From ab25b911ac9dbbdf9e48b07b2ee0232cb51013db Mon Sep 17 00:00:00 2001 From: Sven Grossmann Date: Mon, 28 Apr 2025 15:11:49 +0200 Subject: [PATCH 001/849] ci: move branch name to env var (#104633) * ci: move branch name to env var * quoting --- .github/actions/setup-grafana-bench/action.yml | 3 ++- .github/actions/test-coverage-processor/action.yml | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/actions/setup-grafana-bench/action.yml b/.github/actions/setup-grafana-bench/action.yml index 624ad243b7b..b708d862e2e 100644 --- a/.github/actions/setup-grafana-bench/action.yml +++ b/.github/actions/setup-grafana-bench/action.yml @@ -36,9 +36,10 @@ runs: shell: bash env: GH_TOKEN: ${{ steps.generate_token.outputs.token }} + BRANCH: ${{ inputs.branch }} run: | git clone https://x-access-token:${GH_TOKEN}@github.com/grafana/grafana-bench.git ../grafana-bench cd ../grafana-bench - git switch ${{ inputs.branch }} + git switch "$BRANCH" go install . diff --git a/.github/actions/test-coverage-processor/action.yml b/.github/actions/test-coverage-processor/action.yml index 7560031e846..c22fd0ccb19 100644 --- a/.github/actions/test-coverage-processor/action.yml +++ b/.github/actions/test-coverage-processor/action.yml @@ -28,11 +28,13 @@ runs: steps: - name: Process Go coverage output shell: bash + env: + COVERAGE_FILE: ${{ inputs.coverage-file }} run: | # Ensure valid coverage file even if empty - if [ ! -s ${{ inputs.coverage-file }} ]; then + if [ ! -s "$COVERAGE_FILE" ]; then echo "Coverage file is empty, creating a minimal valid file" - echo "mode: set" > ${{ inputs.coverage-file }} + echo "mode: set" > "$COVERAGE_FILE" fi - name: Report coverage to CodeCov From 2c7c2088d9db51310d33d6343c0c7ba448e4d501 Mon Sep 17 00:00:00 2001 From: Florian Verdonck Date: Mon, 28 Apr 2025 15:32:28 +0200 Subject: [PATCH 002/849] Logs panel: Add meta field to show total hits; add total hits to ElasticSearch plugin response (#104117) * feat: Show total amount of hits in Elastic Search query * Add test with multiple series. --- pkg/tsdb/elasticsearch/client/client.go | 12 ++++- pkg/tsdb/elasticsearch/client/models.go | 8 +++- pkg/tsdb/elasticsearch/response_parser.go | 10 +++- .../elasticsearch/response_parser_test.go | 8 ++-- .../testdata_response/logs.a.golden.jsonc | 6 ++- public/app/features/logs/logsModel.test.ts | 47 +++++++++++++++++++ public/app/features/logs/logsModel.ts | 10 ++++ 7 files changed, 91 insertions(+), 10 deletions(-) diff --git a/pkg/tsdb/elasticsearch/client/client.go b/pkg/tsdb/elasticsearch/client/client.go index 746e670dc78..b9ddc051469 100644 --- a/pkg/tsdb/elasticsearch/client/client.go +++ b/pkg/tsdb/elasticsearch/client/client.go @@ -344,11 +344,19 @@ func processHits(dec *json.Decoder, sr *SearchResponse) error { return err } - if tok == "hits" { + switch tok { + case "hits": if err := streamHitsArray(dec, sr); err != nil { return err } - } else { + case "total": + var total *SearchResponseHitsTotal + err := dec.Decode(&total) + if err != nil { + return err + } + sr.Hits.Total = total + default: // ignore these fields as they are not used in the current implementation err := skipUnknownField(dec) if err != nil { diff --git a/pkg/tsdb/elasticsearch/client/models.go b/pkg/tsdb/elasticsearch/client/models.go index e18fad67f35..c8648f0bdeb 100644 --- a/pkg/tsdb/elasticsearch/client/models.go +++ b/pkg/tsdb/elasticsearch/client/models.go @@ -44,9 +44,15 @@ func (r *SearchRequest) MarshalJSON() ([]byte, error) { return json.Marshal(root) } +type SearchResponseHitsTotal struct { + Value int `json:"value"` + Relation string `json:"relation"` +} + // SearchResponseHits represents search response hits type SearchResponseHits struct { - Hits []map[string]interface{} + Hits []map[string]interface{} + Total *SearchResponseHitsTotal `json:"total"` } // SearchResponse represents a search response diff --git a/pkg/tsdb/elasticsearch/response_parser.go b/pkg/tsdb/elasticsearch/response_parser.go index 541006fedc2..b38b2c1cc07 100644 --- a/pkg/tsdb/elasticsearch/response_parser.go +++ b/pkg/tsdb/elasticsearch/response_parser.go @@ -208,7 +208,12 @@ func processLogsResponse(res *es.SearchResponse, target *Query, configuredFields frames := data.Frames{} frame := data.NewFrame("", fields...) setPreferredVisType(frame, data.VisTypeLogs) - setLogsCustomMeta(frame, searchWords, stringToIntWithDefaultValue(target.Metrics[0].Settings.Get("limit").MustString(), defaultSize)) + + var total int + if res.Hits.Total != nil { + total = res.Hits.Total.Value + } + setLogsCustomMeta(frame, searchWords, stringToIntWithDefaultValue(target.Metrics[0].Settings.Get("limit").MustString(), defaultSize), total) frames = append(frames, frame) queryRes.Frames = frames @@ -1192,7 +1197,7 @@ func setPreferredVisType(frame *data.Frame, visType data.VisType) { frame.Meta.PreferredVisualization = visType } -func setLogsCustomMeta(frame *data.Frame, searchWords map[string]bool, limit int) { +func setLogsCustomMeta(frame *data.Frame, searchWords map[string]bool, limit int, total int) { i := 0 searchWordsList := make([]string, len(searchWords)) for searchWord := range searchWords { @@ -1212,6 +1217,7 @@ func setLogsCustomMeta(frame *data.Frame, searchWords map[string]bool, limit int frame.Meta.Custom = map[string]interface{}{ "searchWords": searchWordsList, "limit": limit, + "total": total, } } diff --git a/pkg/tsdb/elasticsearch/response_parser_test.go b/pkg/tsdb/elasticsearch/response_parser_test.go index a03c5068ed8..85239537bad 100644 --- a/pkg/tsdb/elasticsearch/response_parser_test.go +++ b/pkg/tsdb/elasticsearch/response_parser_test.go @@ -46,6 +46,7 @@ func TestProcessLogsResponse(t *testing.T) { { "aggregations": {}, "hits": { + "total": { "value": 2 }, "hits": [ { "_id": "fdsfs", @@ -107,7 +108,7 @@ func TestProcessLogsResponse(t *testing.T) { logsFrame := frames[0] meta := logsFrame.Meta - require.Equal(t, map[string]any{"searchWords": []string{"hello", "message"}, "limit": 500}, meta.Custom) + require.Equal(t, map[string]any{"searchWords": []string{"hello", "message"}, "limit": 500, "total": 2}, meta.Custom) require.Equal(t, data.VisTypeLogs, string(meta.PreferredVisualization)) logsFieldMap := make(map[string]*data.Field) @@ -431,6 +432,7 @@ func TestProcessLogsResponse(t *testing.T) { require.Equal(t, map[string]any{ "searchWords": []string{"hello", "message"}, "limit": 500, + "total": 109, }, customMeta) }) } @@ -703,7 +705,7 @@ func TestProcessRawDocumentResponse(t *testing.T) { "responses": [ { "hits": { - "total": 100, + "total": { "value": 100 }, "hits": [ { "_id": "1", @@ -3239,7 +3241,7 @@ func TestParseResponse(t *testing.T) { }, { "hits": { - "total": 2, + "total": { "value": 2 }, "hits": [ { "_id": "5", diff --git a/pkg/tsdb/elasticsearch/testdata_response/logs.a.golden.jsonc b/pkg/tsdb/elasticsearch/testdata_response/logs.a.golden.jsonc index 105279a888e..7d5a673da01 100644 --- a/pkg/tsdb/elasticsearch/testdata_response/logs.a.golden.jsonc +++ b/pkg/tsdb/elasticsearch/testdata_response/logs.a.golden.jsonc @@ -10,7 +10,8 @@ // "searchWords": [ // "hello", // "message" -// ] +// ], +// "total": 81 // }, // "preferredVisualisationType": "logs" // } @@ -45,7 +46,8 @@ "searchWords": [ "hello", "message" - ] + ], + "total": 81 }, "preferredVisualisationType": "logs" }, diff --git a/public/app/features/logs/logsModel.test.ts b/public/app/features/logs/logsModel.test.ts index 3c7b599c7f6..62f692e4846 100644 --- a/public/app/features/logs/logsModel.test.ts +++ b/public/app/features/logs/logsModel.test.ts @@ -34,6 +34,7 @@ import { filterLogLevels, getSeriesProperties, LIMIT_LABEL, + TOTAL_LABEL, logRowToSingleRowDataFrame, logSeriesToLogsModel, queryLogsSample, @@ -492,6 +493,52 @@ describe('dataFrameToLogsModel', () => { }); }); + it('given one series with total as custom meta property should return correct total', () => { + const series: DataFrame[] = [ + createDataFrame({ + fields: [], + meta: { + custom: { + total: 9999, + }, + }, + }), + ]; + const logsModel = dataFrameToLogsModel(series, 1); + expect(logsModel.meta![0]).toMatchObject({ + label: TOTAL_LABEL, + value: 9999, + kind: LogsMetaKind.Number, + }); + }); + + it('given multiple series with total as custom meta property should return correct total', () => { + const series: DataFrame[] = [ + createDataFrame({ + fields: [], + meta: { + custom: { + total: 4, + }, + }, + }), + createDataFrame({ + fields: [], + meta: { + custom: { + total: 5, + }, + }, + }), + ]; + const logsModel = dataFrameToLogsModel(series, 1); + expect(logsModel.meta![0]).toMatchObject({ + label: TOTAL_LABEL, + value: 9, + kind: LogsMetaKind.Number, + }); + }); + it('should return the expected meta when the line limit is reached', () => { const series: DataFrame[] = getTestDataFrame(); series[0].meta = { diff --git a/public/app/features/logs/logsModel.ts b/public/app/features/logs/logsModel.ts index 7e75684d423..9c1c266cea6 100644 --- a/public/app/features/logs/logsModel.ts +++ b/public/app/features/logs/logsModel.ts @@ -51,6 +51,7 @@ import { createLogRowsMap, getLogLevel, getLogLevelFromKey, sortInAscendingOrder export const LIMIT_LABEL = 'Line limit'; export const COMMON_LABELS = 'Common labels'; +export const TOTAL_LABEL = 'Total lines'; export const LogLevelColor = { [LogLevel.critical]: colors[7], @@ -492,6 +493,15 @@ export function logSeriesToLogsModel( }); } + const totalValue = logSeries.reduce((acc, series) => (acc += series.meta?.custom?.total), 0); + if (totalValue > 0) { + meta.push({ + label: TOTAL_LABEL, + value: totalValue, + kind: LogsMetaKind.Number, + }); + } + let totalBytes = 0; const queriesVisited: { [refId: string]: boolean } = {}; // To add just 1 error message From 1c70d8cc18c5e90954c7ff6837ee5957ee44c210 Mon Sep 17 00:00:00 2001 From: Sven Grossmann Date: Mon, 28 Apr 2025 16:15:17 +0200 Subject: [PATCH 003/849] ci: move variables into `env` in `issue`/`issue_comment` workflows (#104636) ci: move variables into `env` in `issue` wf --- .github/workflows/dashboards-issue-add-label.yml | 13 +++++++++---- .github/workflows/skye-add-to-project.yml | 6 ++++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/.github/workflows/dashboards-issue-add-label.yml b/.github/workflows/dashboards-issue-add-label.yml index 95abce4355b..c3157a05afb 100644 --- a/.github/workflows/dashboards-issue-add-label.yml +++ b/.github/workflows/dashboards-issue-add-label.yml @@ -38,11 +38,13 @@ jobs: - name: Check if issue is in target project env: GH_TOKEN: ${{ steps.generate_token.outputs.token }} + ISSUE_NUMBER: ${{ github.event.issue.number }} + TARGET_PROJECT: ${{ env.TARGET_PROJECT }} run: | gh api graphql -f query=' query($org: String!, $repo: String!) { repository(name: $repo, owner: $org) { - issue (number: ${{ github.event.issue.number }}) { + issue (number: $ISSUE_NUMBER) { id projectItems(first:20) { nodes { @@ -55,12 +57,14 @@ jobs: } }' -f org=$ORGANIZATION -f repo=$REPO > projects_data.json - echo 'IN_TARGET_PROJ='$(jq '.data.repository.issue.projectItems.nodes[] | select(.project.number==${{ env.TARGET_PROJECT }}) | .project != null' projects_data.json) >> $GITHUB_ENV + echo 'IN_TARGET_PROJ='$(jq '.data.repository.issue.projectItems.nodes[] | select(.project.number=='"$TARGET_PROJECT"') | .project != null' projects_data.json) >> $GITHUB_ENV echo 'ITEM_ID='$(jq '.data.repository.issue.id' projects_data.json) >> $GITHUB_ENV - name: Set up label array if: env.IN_TARGET_PROJ + env: + LABEL_IDS: ${{ env.LABEL_IDS }} run: | - IFS=',' read -ra LABEL_IDs <<< "${{ env.LABEL_IDs }}" + IFS=',' read -ra LABEL_IDs <<< "$LABEL_IDS" for item in "${LABEL_IDs[@]}"; do echo "Item: $item" done @@ -68,6 +72,7 @@ jobs: if: env.IN_TARGET_PROJ env: GH_TOKEN: ${{ steps.generate_token.outputs.token }} + LABEL_IDS: ${{ env.LABEL_IDS }} run: | gh api graphql -f query=' mutation ($labelableId: ID!, $labelIds: [ID!]!) { @@ -76,4 +81,4 @@ jobs: ) { clientMutationId } - }' -f labelableId=$ITEM_ID -f labelIds=${{ env.LABEL_IDs }} + }' -f labelableId=$ITEM_ID -f labelIds=$LABEL_IDS diff --git a/.github/workflows/skye-add-to-project.yml b/.github/workflows/skye-add-to-project.yml index 5e8ccc8e557..6788db2d95e 100644 --- a/.github/workflows/skye-add-to-project.yml +++ b/.github/workflows/skye-add-to-project.yml @@ -50,10 +50,12 @@ jobs: # Check if the user is in the list from the secret - name: Check if user is allowed id: check_user + env: + ALLOWED_USERS: ${{ env.ALLOWED_USERS }} + USERNAME: ${{ github.event.sender.login }} run: | # Convert the comma-separated list to an array - IFS=',' read -ra ALLOWED_USERS <<< "${{ env.ALLOWED_USERS }}" - USERNAME="${{ github.event.sender.login }}" + IFS=',' read -ra ALLOWED_USERS <<< "$ALLOWED_USERS" # Check if user is in the allowed list for allowed_user in "${ALLOWED_USERS[@]}"; do From d3038c6e9aa261f698ab82859c114804eeed7b95 Mon Sep 17 00:00:00 2001 From: Sven Grossmann Date: Mon, 28 Apr 2025 16:17:23 +0200 Subject: [PATCH 004/849] ci: add permissions to pr-patch-check-event (#104635) --- .github/workflows/pr-patch-check-event.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pr-patch-check-event.yml b/.github/workflows/pr-patch-check-event.yml index e1389fdac6a..34f8fa1313c 100644 --- a/.github/workflows/pr-patch-check-event.yml +++ b/.github/workflows/pr-patch-check-event.yml @@ -17,6 +17,9 @@ on: # target branch onto the source branch, to verify compatibility before merging. jobs: dispatch-job: + permissions: + contents: read + actions: write env: HEAD_REF: ${{ github.head_ref }} BASE_REF: ${{ github.base_ref }} From 90e1f245108668c7d658ee8afb547867cb1ea3fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 28 Apr 2025 17:20:12 +0200 Subject: [PATCH 005/849] Dashboard: Style change to hover and selected nodes in outline (#104462) * Dashboard: Style change to hover and selected nodes in outline * Update * Update --- .../edit-pane/DashboardOutline.tsx | 91 ++++++++++++------- .../scene/layout-rows/RowItemEditor.tsx | 4 +- 2 files changed, 61 insertions(+), 34 deletions(-) diff --git a/public/app/features/dashboard-scene/edit-pane/DashboardOutline.tsx b/public/app/features/dashboard-scene/edit-pane/DashboardOutline.tsx index e231c286720..58076081f00 100644 --- a/public/app/features/dashboard-scene/edit-pane/DashboardOutline.tsx +++ b/public/app/features/dashboard-scene/edit-pane/DashboardOutline.tsx @@ -4,7 +4,7 @@ import React, { useEffect, useMemo, useState } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { SceneObject } from '@grafana/scenes'; -import { Box, Icon, Stack, Text, useElementSelection, useStyles2 } from '@grafana/ui'; +import { Box, Icon, Text, useElementSelection, useStyles2, useTheme2 } from '@grafana/ui'; import { t, Trans } from 'app/core/internationalization'; import { DashboardGridItem } from '../scene/layout-default/DashboardGridItem'; @@ -24,7 +24,7 @@ export function DashboardOutline({ editPane }: Props) { const dashboard = getDashboardSceneFor(editPane); return ( - + ); @@ -40,6 +40,7 @@ function DashboardOutlineNode({ depth: number; }) { const [isCollapsed, setIsCollapsed] = useState(depth > 0); + const theme = useTheme2(); const { key } = sceneObject.useState(); const styles = useStyles2(getStyles); const { isSelected, onSelect } = useElementSelection(key); @@ -53,7 +54,7 @@ function DashboardOutlineNode({ const elementCollapsed = editableElement.getCollapsedState?.(); const outlineRename = useOutlineRename(editableElement); - const onNameClicked = (evt: React.PointerEvent) => { + const onNodeClicked = (evt: React.PointerEvent) => { // Only select via clicking outline never deselect if (!isSelected) { onSelect?.(evt); @@ -62,7 +63,8 @@ function DashboardOutlineNode({ editableElement.scrollIntoView?.(); }; - const onToggleCollapse = () => { + const onToggleCollapse = (evt: React.MouseEvent) => { + evt.stopPropagation(); setIsCollapsed(!isCollapsed); // Sync expanded state with canvas element @@ -80,16 +82,19 @@ function DashboardOutlineNode({ return ( <> - +
{elementInfo.isContainer && ( - )} - +
{elementInfo.isContainer && !isCollapsed && ( -
+
+
{children.length > 0 ? ( children.map((child) => ( span': { whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', }, }), - nodeButtonSelected: css({ - color: theme.colors.text.primary, - outline: `1px dashed ${theme.colors.primary.border} !important`, - outlineOffset: '0px', - '&:hover': { - outline: `1px dashed ${theme.colors.primary.border}`, - }, - }), hiddenIcon: css({ color: theme.colors.text.secondary, marginLeft: theme.spacing(1), }), - nodeButtonClone: css({ + nodeNameClone: css({ color: theme.colors.text.secondary, cursor: 'not-allowed', }), outlineInput: css({ - border: `1px solid ${theme.colors.primary.border}`, + border: `1px solid ${theme.components.input.borderColor}`, height: theme.spacing(3), + borderRadius: theme.shape.radius.default, '&:focus': { outline: 'none', boxShadow: 'none', }, }), + nodeChildren: css({ + display: 'flex', + flexDirection: 'column', + position: 'relative', + }), + nodeChildrenLine: css({ + position: 'absolute', + width: '1px', + height: '100%', + left: '7px', + zIndex: 1, + backgroundColor: theme.colors.border.weak, + }), }; } diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowItemEditor.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowItemEditor.tsx index 9f7c0426682..f67256a31c8 100644 --- a/public/app/features/dashboard-scene/scene/layout-rows/RowItemEditor.tsx +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowItemEditor.tsx @@ -24,7 +24,8 @@ export function useEditOptions(model: RowItem, isNewElement: boolean): OptionsPa new OptionsPaneCategoryDescriptor({ title: '', id: 'row-options' }) .addItem( new OptionsPaneItemDescriptor({ - title: t('dashboard.rows-layout.row-options.row.title', 'Title'), + title: '', + skipField: true, render: () => , }) ) @@ -85,6 +86,7 @@ function RowTitleInput({ row, isNewElement }: { row: RowItem; isNewElement: bool return ( Date: Mon, 28 Apr 2025 21:20:21 +0200 Subject: [PATCH 006/849] CI: Pin more actions and fix zizmor findings (#104651) * ci: fix unpinned actions * ci: more pinned actions --- .github/workflows/commands.yml | 2 +- .github/workflows/issue-opened.yml | 6 +++--- .github/workflows/metrics-collector.yml | 5 ++++- .github/workflows/pr-checks.yml | 2 +- .github/workflows/pr-commands.yml | 2 +- .github/workflows/run-dashboard-search-e2e.yml | 12 ++++++++---- 6 files changed, 18 insertions(+), 11 deletions(-) diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml index f733b2f244d..942ea69d5e3 100644 --- a/.github/workflows/commands.yml +++ b/.github/workflows/commands.yml @@ -52,7 +52,7 @@ jobs: private_key: ${{ env.GH_APP_PEM }} - name: Checkout Actions - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: repository: "grafana/grafana-github-actions" path: ./actions diff --git a/.github/workflows/issue-opened.yml b/.github/workflows/issue-opened.yml index 4de94070072..b478f9647e7 100644 --- a/.github/workflows/issue-opened.yml +++ b/.github/workflows/issue-opened.yml @@ -21,7 +21,7 @@ jobs: steps: - name: Checkout Actions - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: repository: "grafana/grafana-github-actions" path: ./actions @@ -83,7 +83,7 @@ jobs: private_key: ${{ env.GH_APP_PEM }} - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Send issue to the auto triager action id: auto_triage @@ -99,7 +99,7 @@ jobs: - name: "Send Slack notification" if: ${{ steps.auto_triage.outputs.triage_labels != '' }} - uses: slackapi/slack-github-action@v1.27.0 + uses: slackapi/slack-github-action@37ebaef184d7626c5f204ab8d3baff4262dd30f0 # v1.27.0 with: payload: > { diff --git a/.github/workflows/metrics-collector.yml b/.github/workflows/metrics-collector.yml index 2e22a830a88..9238b034433 100644 --- a/.github/workflows/metrics-collector.yml +++ b/.github/workflows/metrics-collector.yml @@ -15,6 +15,9 @@ on: issues: types: [opened, closed] +permissions: + contents: read + jobs: config: runs-on: "ubuntu-latest" @@ -35,7 +38,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Actions - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: repository: "grafana/grafana-github-actions" path: ./actions diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index ae2b0898f73..b3bd63fa0d8 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -31,7 +31,7 @@ jobs: if: github.event.pull_request.draft == false steps: - name: Checkout Actions - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: repository: "grafana/grafana-github-actions" path: ./actions diff --git a/.github/workflows/pr-commands.yml b/.github/workflows/pr-commands.yml index 51838dc7ae7..f392752ee8e 100644 --- a/.github/workflows/pr-commands.yml +++ b/.github/workflows/pr-commands.yml @@ -30,7 +30,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Actions - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: repository: "grafana/grafana-github-actions" path: ./actions diff --git a/.github/workflows/run-dashboard-search-e2e.yml b/.github/workflows/run-dashboard-search-e2e.yml index 9eb9930cdd4..5766735a233 100644 --- a/.github/workflows/run-dashboard-search-e2e.yml +++ b/.github/workflows/run-dashboard-search-e2e.yml @@ -107,10 +107,14 @@ jobs: key: ${{ runner.os }}-grafana-${{ hashFiles('go.mod', 'package-lock.json', 'Makefile', 'pkg/storage/**/*.go', 'public/app/features/search/**/*.ts', 'public/app/features/search/**/*.tsx') }} - name: Set the step name id: set_file_name + env: + INI_NAME: ${{ matrix.ini_file }} run: | - FILE_NAME=$(basename "${{ matrix.ini_file }}" .ini) - echo "FILE_NAME=$FILE_NAME" >> $GITHUB_ENV - - name: Run tests for ${{ env.FILE_NAME }} + FILE_NAME=$(basename "$env.INI_NAME" .ini) + echo "FILE_NAME=$FILE_NAME" >> $GITHUB_OUTPUT + - name: Run tests for ${{ steps.set_file_name.outputs.FILE_NAME }} + env: + INI_NAME: ${{ matrix.ini_file }} run: | - cp -rf ${{ matrix.ini_file }} ${{ github.workspace }}/scripts/grafana-server/custom.ini + cp -rf $INI_NAME ${{ github.workspace }}/scripts/grafana-server/custom.ini yarn e2e:dashboards-search || echo "Test failed but marking as success since unified search is behind a feature flag and should not block PRs" From 8f922bf76d64a937542ffc14280f7185cc6ba4e3 Mon Sep 17 00:00:00 2001 From: Sven Grossmann Date: Tue, 29 Apr 2025 13:02:18 +0200 Subject: [PATCH 007/849] CI: Add `zizmor` action (#104676) --- .github/workflows/zizmor.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/zizmor.yml diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml new file mode 100644 index 00000000000..ff42e451797 --- /dev/null +++ b/.github/workflows/zizmor.yml @@ -0,0 +1,27 @@ +name: Zizmor GitHub Actions static analysis +on: + pull_request: + paths: + - ".github/**" + push: + branches: + - main + paths: + - ".github/**" + +jobs: + zizmor: + name: Analyse with Zizmor + + permissions: + actions: read + contents: read + # required to comment on pull requests with the results of the check + pull-requests: write + # required to upload the results to GitHub's code scanning service + security-events: write + + uses: grafana/shared-workflows/.github/workflows/reusable-zizmor.yml@main # zizmor: ignore[unpinned-uses] + with: + fail-severity: high + min-severity: high \ No newline at end of file From 97a1614cde328c388c1163ed1b5d4b9ec4a0bb18 Mon Sep 17 00:00:00 2001 From: Gilles De Mey Date: Tue, 29 Apr 2025 15:45:45 +0200 Subject: [PATCH 008/849] Alerting: Rework rule editor layout (#103744) --- .betterer.results | 115 ++++++++++++ .../RuleEditorGrafanaRecordingRules.test.tsx | 4 +- .../rule-editor/DurationQuickPick.tsx | 6 +- .../alert-rule-form/AlertRuleForm.tsx | 177 +++++------------- .../alert-rule-form/ModifyExportRuleForm.tsx | 59 +++--- .../SimplifiedRuleEditor.test.tsx | 12 +- .../components/rule-viewer/RuleViewer.tsx | 7 +- .../unified/components/rules/RuleDetails.tsx | 8 +- .../rule-editor/CloneRuleEditor.test.tsx | 60 ++++-- .../rule-editor/ExistingRuleEditor.tsx | 115 +++++++++--- .../unified/rule-editor/RuleEditor.tsx | 161 ++++++++-------- .../rule-editor/RuleEditorCloudRules.test.tsx | 2 +- .../rule-editor/RuleEditorExisting.test.tsx | 16 +- .../RuleEditorGrafanaRules.test.tsx | 8 +- .../RuleEditorRecordingRule.test.tsx | 4 +- .../unified/rule-editor/clone.utils.ts | 39 ++++ .../features/alerting/unified/utils/misc.ts | 10 + .../features/alerting/unified/utils/rules.ts | 4 + public/locales/en-US/grafana.json | 15 +- public/test/helpers/alertingRuleEditor.tsx | 2 +- 20 files changed, 506 insertions(+), 318 deletions(-) create mode 100644 public/app/features/alerting/unified/rule-editor/clone.utils.ts diff --git a/.betterer.results b/.betterer.results index 2518e0548be..0a9111cfa72 100644 --- a/.betterer.results +++ b/.betterer.results @@ -1020,7 +1020,122 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "0"] ], "public/app/features/alerting/unified/components/rule-editor/RuleInspector.tsx:5381": [ +<<<<<<< HEAD + [0, 0, 0, "Do not use any type assertions.", "0"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] + ], + "public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/route-settings/MuteTimingFields.tsx:5381": [ + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] + ], + "public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/route-settings/RouteSettings.tsx:5381": [ + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] + ], + "public/app/features/alerting/unified/components/rule-editor/labels/LabelsField.tsx:5381": [ + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] + ], + "public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationRoute.tsx:5381": [ + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] + ], + "public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationRouteDetailsModal.tsx:5381": [ + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] + ], + "public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx:5381": [ + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] + ], + "public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/SmartAlertTypeDetector.tsx:5381": [ + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "3"] + ], + "public/app/features/alerting/unified/components/rule-editor/rule-types/GrafanaManagedAlert.tsx:5381": [ + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] + ], + "public/app/features/alerting/unified/components/rule-editor/rule-types/MimirOrLokiAlert.tsx:5381": [ + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] + ], + "public/app/features/alerting/unified/components/rule-editor/rule-types/MimirOrLokiRecordingRule.tsx:5381": [ + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] + ], + "public/app/features/alerting/unified/components/rule-editor/rule-types/RuleTypePicker.tsx:5381": [ + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] + ], + "public/app/features/alerting/unified/components/rule-viewer/FederatedRuleWarning.tsx:5381": [ + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] + ], + "public/app/features/alerting/unified/components/rule-viewer/PausedBadge.tsx:5381": [ + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] + ], + "public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx:5381": [ + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] + ], + "public/app/features/alerting/unified/components/rules/AlertStateTag.tsx:5381": [ + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] + ], + "public/app/features/alerting/unified/components/rules/CloneRule.tsx:5381": [ + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] + ], + "public/app/features/alerting/unified/components/rules/Filter/RulesFilter.v1.tsx:5381": [ + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] + ], + "public/app/features/alerting/unified/components/rules/RuleConfigStatus.tsx:5381": [ + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] + ], + "public/app/features/alerting/unified/components/rules/RuleDetailsMatchingInstances.tsx:5381": [ + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] + ], + "public/app/features/alerting/unified/components/rules/RuleListErrors.tsx:5381": [ + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "4"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "5"] + ], + "public/app/features/alerting/unified/components/rules/RuleListStateSection.tsx:5381": [ + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] + ], + "public/app/features/alerting/unified/components/rules/RuleState.tsx:5381": [ + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] + ], + "public/app/features/alerting/unified/components/rules/RuleStats.tsx:5381": [ + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] + ], + "public/app/features/alerting/unified/components/rules/RulesGroup.tsx:5381": [ + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] + ], + "public/app/features/alerting/unified/components/rules/state-history/LogRecordViewer.tsx:5381": [ + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] + ], + "public/app/features/alerting/unified/components/rules/state-history/LokiStateHistory.tsx:5381": [ + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] + ], + "public/app/features/alerting/unified/components/rules/state-history/StateHistory.tsx:5381": [ + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] + ], + "public/app/features/alerting/unified/components/settings/VersionManager.tsx:5381": [ + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] + ], + "public/app/features/alerting/unified/components/silences/SilencedAlertsTableRow.tsx:5381": [ + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] + ], + "public/app/features/alerting/unified/components/silences/SilencedInstancesPreview.tsx:5381": [ + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] +======= [0, 0, 0, "Do not use any type assertions.", "0"] +>>>>>>> origin/main ], "public/app/features/alerting/unified/components/silences/SilencesEditor.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] diff --git a/public/app/features/alerting/unified/RuleEditorGrafanaRecordingRules.test.tsx b/public/app/features/alerting/unified/RuleEditorGrafanaRecordingRules.test.tsx index 655b33b2e7e..faed2a0ac8b 100644 --- a/public/app/features/alerting/unified/RuleEditorGrafanaRecordingRules.test.tsx +++ b/public/app/features/alerting/unified/RuleEditorGrafanaRecordingRules.test.tsx @@ -78,7 +78,7 @@ describe('RuleEditor grafana recording rules', () => { await user.type(await ui.inputs.metric.find(), 'metricName'); await selectFolderAndGroup(user); - await user.click(ui.buttons.saveAndExit.get()); + await user.click(ui.buttons.save.get()); const requests = await capture; const serializedRequests = await serializeRequests(requests); @@ -96,7 +96,7 @@ describe('RuleEditor grafana recording rules', () => { await user.type(await ui.inputs.name.find(), 'my great new rule'); await selectFolderAndGroup(user); - await user.click(ui.buttons.saveAndExit.get()); + await user.click(ui.buttons.save.get()); const requests = await capture; expect(requests).toHaveLength(0); }); diff --git a/public/app/features/alerting/unified/components/rule-editor/DurationQuickPick.tsx b/public/app/features/alerting/unified/components/rule-editor/DurationQuickPick.tsx index 36844b9b75c..2e8bcdfc567 100644 --- a/public/app/features/alerting/unified/components/rule-editor/DurationQuickPick.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/DurationQuickPick.tsx @@ -43,9 +43,13 @@ export function DurationQuickPick({ selectedDuration, groupEvaluationInterval, o onSelect(duration); }} > - {duration === '0s' ? t('alerting.duration-quick-pick.none', 'None') : duration} + {stringifyPendingPeriod(duration)} ))} ); } + +export function stringifyPendingPeriod(duration: string): string { + return duration === '0s' ? t('alerting.duration-quick-pick.none', 'None') : duration; +} 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 88549aa16f1..cf8dfd3f589 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 @@ -5,8 +5,7 @@ import { useParams } from 'react-router-dom-v5-compat'; import { GrafanaTheme2 } from '@grafana/data'; import { config, locationService } from '@grafana/runtime'; -import { Alert, Button, ConfirmModal, Spinner, Stack, useStyles2 } from '@grafana/ui'; -import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate'; +import { Alert, Button, Spinner, Stack, useStyles2 } from '@grafana/ui'; import { useAppNotification } from 'app/core/copy/appNotification'; import { contextSrv } from 'app/core/core'; import { Trans, t } from 'app/core/internationalization'; @@ -22,7 +21,7 @@ import { rulerRuleType, } from 'app/features/alerting/unified/utils/rules'; import { isExpressionQuery } from 'app/features/expressions/guards'; -import { RuleGroupIdentifier, RuleIdentifier, RuleWithLocation } from 'app/types/unified-alerting'; +import { RuleGroupIdentifier, RuleWithLocation } from 'app/types/unified-alerting'; import { PostableRuleGrafanaRuleDTO, RulerRuleDTO } from 'app/types/unified-alerting-dto'; import { @@ -41,10 +40,7 @@ import { RulerGroupUpdatedResponse, isGrafanaGroupUpdatedResponse, } from '../../../api/alertRuleModel'; -import { shouldUseAlertingListViewV2, shouldUsePrometheusRulesPrimary } from '../../../featureToggles'; -import { useDeleteRuleFromGroup } from '../../../hooks/ruleGroup/useDeleteRuleFromGroup'; import { useAddRuleToRuleGroup, useUpdateRuleInRuleGroup } from '../../../hooks/ruleGroup/useUpsertRuleFromRuleGroup'; -import { useReturnTo } from '../../../hooks/useReturnTo'; import { defaultFormValuesForRuleType, formValuesFromExistingRule, @@ -63,9 +59,7 @@ import { formValuesToRulerGrafanaRuleDTO, formValuesToRulerRuleDTO, } from '../../../utils/rule-form'; -import * as ruleId from '../../../utils/rule-id'; -import { fromRulerRule, fromRulerRuleAndRuleGroupIdentifier, stringifyIdentifier } from '../../../utils/rule-id'; -import { createRelativeUrl } from '../../../utils/url'; +import { fromRulerRule, fromRulerRuleAndRuleGroupIdentifier } from '../../../utils/rule-id'; import { GrafanaRuleExporter } from '../../export/GrafanaRuleExporter'; import { AlertRuleNameAndMetric } from '../AlertRuleNameInput'; import AnnotationsStep from '../AnnotationsStep'; @@ -83,27 +77,21 @@ type Props = { isManualRestore?: boolean; }; -const prometheusRulesPrimary = shouldUsePrometheusRulesPrimary(); -const alertingListViewV2 = shouldUseAlertingListViewV2(); - export const AlertRuleForm = ({ existing, prefill, isManualRestore }: Props) => { const styles = useStyles2(getStyles); const notifyApp = useAppNotification(); - const { redirectToDetailsPage } = useRedirectToDetailsPage(); + + const routeParams = useParams<{ type: string; id: string }>(); + const uidFromParams = routeParams.id; + + const { redirectToDetailsPage } = useRedirectToDetailsPage(uidFromParams); const [showEditYaml, setShowEditYaml] = useState(false); - const [deleteRuleFromGroup] = useDeleteRuleFromGroup(); const [addRuleToRuleGroup] = useAddRuleToRuleGroup(); const [updateRuleInRuleGroup] = useUpdateRuleInRuleGroup(); - const { returnTo } = useReturnTo(); - const routeParams = useParams<{ type: string; id: string }>(); const ruleType = translateRouteParamToRuleType(routeParams.type); - const uidFromParams = routeParams.id || ''; - - const [showDeleteModal, setShowDeleteModal] = useState(false); - const defaultValues: RuleFormValues = useMemo(() => { // If we have an existing AND a prefill, then we're coming from the restore dialog // and we want to merge the two @@ -157,7 +145,7 @@ export const AlertRuleForm = ({ existing, prefill, isManualRestore }: Props) => }; // @todo why is error not propagated to form? - const submit = async (values: RuleFormValues, exitOnSave: boolean) => { + const submit = async (values: RuleFormValues): Promise => { const { type, evaluateEvery } = values; if (conditionErrorMsg !== '') { @@ -208,41 +196,8 @@ export const AlertRuleForm = ({ existing, prefill, isManualRestore }: Props) => ); } - const { dataSourceName, namespaceName, groupName } = targetRuleGroupIdentifier; - - // V2 list is based on eventually consistent Prometheus API. - // When a new rule group is created it takes a while for the new rule group to be reflected in the V2 list. - // To avoid user confusion we redirect to the details page which is driven by a strongly consistent Ruler API.. - if (alertingListViewV2) { - redirectToDetailsPage(ruleDefinition, targetRuleGroupIdentifier, saveResult); - return; - } - - if (exitOnSave) { - const returnToUrl = returnTo || getReturnToUrl(targetRuleGroupIdentifier, ruleDefinition); - - locationService.push(returnToUrl); - return; - } else { - // we stay in the same page - - // Cloud Ruler rules identifier changes on update due to containing rule name and hash components - // After successful update we need to update the URL to avoid displaying 404 errors - if (rulerRuleType.dataSource.rule(ruleDefinition)) { - const updatedRuleIdentifier = fromRulerRule(dataSourceName, namespaceName, groupName, ruleDefinition); - locationService.replace(`/alerting/${encodeURIComponent(stringifyIdentifier(updatedRuleIdentifier))}/edit`); - } - } - }; - - const deleteRule = async () => { - if (existing) { - const ruleGroupIdentifier = getRuleGroupLocationFromRuleWithLocation(existing); - const ruleIdentifier = fromRulerRuleAndRuleGroupIdentifier(ruleGroupIdentifier, existing.rule); - - await deleteRuleFromGroup.execute(ruleGroupIdentifier, ruleIdentifier); - locationService.replace(returnTo ?? '/alerting/list'); - } + redirectToDetailsPage(ruleDefinition, targetRuleGroupIdentifier, saveResult); + return; }; const onInvalid: SubmitErrorHandler = (errors): void => { @@ -266,49 +221,14 @@ export const AlertRuleForm = ({ existing, prefill, isManualRestore }: Props) => locationService.getHistory().goBack(); }; - const actionButtons = ( - - - - {existing ? ( - - ) : null} - {existing && isCortexLokiOrRecordingRule(watch) && ( - - )} - - ); - - const isPaused = rulerRuleType.grafana.alertingRule(existing?.rule) && isPausedRule(existing?.rule); - if (!type) { return null; } + + const isPaused = rulerRuleType.grafana.rule(existing?.rule) && isPausedRule(existing?.rule); + return ( -
e.preventDefault()} className={styles.form}>
{isManualRestore && ( @@ -348,39 +268,55 @@ export const AlertRuleForm = ({ existing, prefill, isManualRestore }: Props) => {!isRecordingRuleByType(type) && } )} + + {/* actions */} + + + + + + {existing && isCortexLokiOrRecordingRule(watch) && ( + + )} +
- {showDeleteModal ? ( - setShowDeleteModal(false)} - /> - ) : null} + {showEditYaml && ( <> - {isGrafanaManagedRuleByType(type) && ( + {grafanaTypeRule && uidFromParams && ( setShowEditYaml(false)} /> )} - {!isGrafanaManagedRuleByType(type) && setShowEditYaml(false)} />} + {!grafanaTypeRule && setShowEditYaml(false)} />} )}
); }; -function useRedirectToDetailsPage() { +function useRedirectToDetailsPage(existingUid?: string) { const notifyApp = useAppNotification(); const redirectGrafanaRule = useCallback( (saveResult: GrafanaGroupUpdatedResponse) => { - const newOrUpdatedRuleUid = saveResult.created?.at(0) || saveResult.updated?.at(0); + // if the response contains no created or updated rules, we'll use the existing UID. + const newOrUpdatedRuleUid = (saveResult.created?.at(0) || saveResult.updated?.at(0)) ?? existingUid; if (newOrUpdatedRuleUid) { locationService.replace( rulesNav.detailsPageLink('grafana', { uid: newOrUpdatedRuleUid, ruleSourceName: 'grafana' }) @@ -393,7 +329,7 @@ function useRedirectToDetailsPage() { logWarning('Cannot navigate to the new rule details page. The rule was created but the UID is missing.'); } }, - [notifyApp] + [existingUid, notifyApp] ); const redirectCloudRulerRule = useCallback((rule: RulerRuleDTO, groupId: RuleGroupIdentifier) => { @@ -427,27 +363,6 @@ function useRedirectToDetailsPage() { return { redirectToDetailsPage }; } -function getReturnToUrl(groupId: RuleGroupIdentifier, rule: RulerRuleDTO | PostableRuleGrafanaRuleDTO) { - const { dataSourceName, namespaceName, groupName } = groupId; - - if (prometheusRulesPrimary && rulerRuleType.dataSource.rule(rule)) { - const ruleIdentifier = fromRulerRule(dataSourceName, namespaceName, groupName, rule); - return createViewLinkFromIdentifier(ruleIdentifier); - } - - // TODO We could add namespace and group filters but for GMA the namespace = uid which doesn't work with the filters - return '/alerting/list'; -} - -// The result of this function is passed to locationService.push() -// Hence it cannot contain the subpath prefix, so we cannot use createRelativeUrl for it -function createViewLinkFromIdentifier(identifier: RuleIdentifier, returnTo?: string) { - const paramId = encodeURIComponent(ruleId.stringifyIdentifier(identifier)); - const paramSource = encodeURIComponent(identifier.ruleSourceName); - - return createRelativeUrl(`/alerting/${paramSource}/${paramId}/view`, returnTo ? { returnTo } : {}); -} - const isCortexLokiOrRecordingRule = (watch: UseFormWatch) => { const [ruleType, dataSourceName] = watch(['type', 'dataSourceName']); diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/ModifyExportRuleForm.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/ModifyExportRuleForm.tsx index adc000d45eb..8e0c66438e4 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/ModifyExportRuleForm.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/ModifyExportRuleForm.tsx @@ -6,7 +6,6 @@ import { Button, LinkButton, LoadingPlaceholder, Stack } from '@grafana/ui'; import { useAppNotification } from 'app/core/copy/appNotification'; import { Trans, t } from 'app/core/internationalization'; -import { AppChromeUpdate } from '../../../../../../core/components/AppChrome/AppChromeUpdate'; import { PostableRulerRuleGroupDTO, RulerRuleDTO, @@ -82,38 +81,38 @@ export function ModifyExportRuleForm({ ruleForm, alertUid }: ModifyExportRuleFor setExportData(undefined); }, [setExportData]); - const actionButtons = [ - submit(undefined)}> - Cancel - , - , - ]; - return ( - -
e.preventDefault()}> -
- - {/* Step 1 */} - - {/* Step 2 */} - - {/* Step 3-4-5 */} - + + e.preventDefault()}> +
+ + {/* Step 1 */} + + {/* Step 2 */} + + {/* Step 3-4-5 */} + - {/* Step 4 & 5 */} - - {/* Notifications step*/} - - {/* Annotations only for cloud and Grafana */} - - -
- - {exportData && } + {/* Step 4 & 5 */} + + {/* Notifications step*/} + + {/* Annotations only for cloud and Grafana */} + +
+
+ + {exportData && } + + + submit(undefined)}> + Cancel + + +
); } 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 0a949d9bccb..381ce9cb6ca 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 @@ -97,7 +97,7 @@ describe('Can create a new grafana managed alert using simplified routing', () = // do not select a contact point // save and check that call to backend was not made - await user.click(ui.buttons.saveAndExit.get()); + await user.click(ui.buttons.save.get()); expect(await screen.findByText('Contact point is required.')).toBeInTheDocument(); const capturedRequests = await capture; @@ -129,7 +129,7 @@ describe('Can create a new grafana managed alert using simplified routing', () = await selectContactPoint(user, contactPointName); // save and check what was sent to backend - await user.click(ui.buttons.saveAndExit.get()); + await user.click(ui.buttons.save.get()); const requests = await capture; const serializedRequests = await serializeRequests(requests); @@ -162,7 +162,7 @@ describe('Can create a new grafana managed alert using simplified routing', () = await selectContactPoint(user, contactPointName); // save and check what was sent to backend - await user.click(ui.buttons.saveAndExit.get()); + await user.click(ui.buttons.save.get()); const requests = await capture; const serializedRequests = await serializeRequests(requests); expect(serializedRequests).toMatchSnapshot(); @@ -180,7 +180,7 @@ describe('Can create a new grafana managed alert using simplified routing', () = await selectFolderAndGroup(user); // save and check what was sent to backend - await user.click(ui.buttons.saveAndExit.get()); + await user.click(ui.buttons.save.get()); const requests = await capture; const serializedRequests = await serializeRequests(requests); expect(serializedRequests).toMatchSnapshot(); @@ -198,7 +198,7 @@ describe('Can create a new grafana managed alert using simplified routing', () = await user.click(ui.inputs.switchModeBasic(GrafanaRuleFormStep.Notification).get()); // switch notifications step to advanced mode // save and check what was sent to backend - await user.click(ui.buttons.saveAndExit.get()); + await user.click(ui.buttons.save.get()); const requests = await capture; const serializedRequests = await serializeRequests(requests); expect(serializedRequests).toMatchSnapshot(); @@ -218,7 +218,7 @@ describe('Can create a new grafana managed alert using simplified routing', () = await user.click(ui.inputs.switchModeBasic(GrafanaRuleFormStep.Query).get()); // switch query step to advanced mode // save and check what was sent to backend - await user.click(ui.buttons.saveAndExit.get()); + await user.click(ui.buttons.save.get()); const requests = await capture; const serializedRequests = await serializeRequests(requests); expect(serializedRequests).toMatchSnapshot(); diff --git a/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx b/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx index bce2689be64..90e6f9ff1da 100644 --- a/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx +++ b/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx @@ -275,16 +275,17 @@ interface TitleProps { health?: RuleHealth; ruleType?: PromRuleType; ruleOrigin?: RulePluginOrigin; + returnToHref?: string; } -export const Title = ({ name, paused = false, state, health, ruleType, ruleOrigin }: TitleProps) => { +export const Title = ({ name, paused = false, state, health, ruleType, ruleOrigin, returnToHref = '' }: TitleProps) => { const isRecordingRule = ruleType === PromRuleType.Recording; - const { returnTo } = useReturnTo('/alerting/list'); + const { returnTo } = useReturnTo(returnToHref); return ( - + {returnToHref && } {ruleOrigin && } {name} diff --git a/public/app/features/alerting/unified/components/rules/RuleDetails.tsx b/public/app/features/alerting/unified/components/rules/RuleDetails.tsx index d7e6839d810..787a0550037 100644 --- a/public/app/features/alerting/unified/components/rules/RuleDetails.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleDetails.tsx @@ -70,7 +70,7 @@ interface EvaluationBehaviorSummaryProps { } const EvaluationBehaviorSummary = ({ rule }: EvaluationBehaviorSummaryProps) => { - const every = rule.group.interval; + const interval = rule.group.interval; const lastEvaluation = rule.promRule?.lastEvaluation; const lastEvaluationDuration = rule.promRule?.evaluationTime; const metric = rulerRuleType.grafana.recordingRule(rule.rulerRule) @@ -87,10 +87,10 @@ const EvaluationBehaviorSummary = ({ rule }: EvaluationBehaviorSummaryProps) => {metric} )} - {every && ( + {interval && ( - - Every {{ every }} + + Every {{ interval }} )} diff --git a/public/app/features/alerting/unified/rule-editor/CloneRuleEditor.test.tsx b/public/app/features/alerting/unified/rule-editor/CloneRuleEditor.test.tsx index 298be9b1529..c59101fca8e 100644 --- a/public/app/features/alerting/unified/rule-editor/CloneRuleEditor.test.tsx +++ b/public/app/features/alerting/unified/rule-editor/CloneRuleEditor.test.tsx @@ -1,9 +1,10 @@ import * as React from 'react'; import { FormProvider, useForm } from 'react-hook-form'; -import { getWrapper, render, waitFor, waitForElementToBeRemoved, within } from 'test/test-utils'; -import { byRole, byTestId, byText } from 'testing-library-selector'; +import { getWrapper, render, waitFor, within } from 'test/test-utils'; +import { byRole, byTestId } from 'testing-library-selector'; import { MIMIR_DATASOURCE_UID } from 'app/features/alerting/unified/mocks/server/constants'; +import { DashboardSearchItemType } from 'app/features/search/types'; import { AccessControlAction } from 'app/types'; import { RuleWithLocation } from 'app/types/unified-alerting'; import { @@ -18,19 +19,22 @@ import { setupMswServer } from '../mockApi'; import { grantUserPermissions, mockDataSource, + mockFolder, mockRulerAlertingRule, mockRulerGrafanaRule, mockRulerRuleGroup, } from '../mocks'; import { grafanaRulerRule } from '../mocks/grafanaRulerApi'; import { mockRulerRulesApiResponse, mockRulerRulesGroupApiResponse } from '../mocks/rulerApi'; +import { setFolderResponse } from '../mocks/server/configure'; import { AlertingQueryRunner } from '../state/AlertingQueryRunner'; import { setupDataSources } from '../testSetup/datasources'; import { RuleFormValues } from '../types/rule-form'; import { Annotation } from '../utils/constants'; import { hashRulerRule } from '../utils/rule-id'; -import { CloneRuleEditor, cloneRuleDefinition } from './CloneRuleEditor'; +import { ExistingRuleEditor } from './ExistingRuleEditor'; +import { cloneRuleDefinition } from './clone.utils'; import { getDefaultFormValues } from './formDefaults'; jest.mock('../components/rule-editor/ExpressionEditor', () => ({ @@ -54,7 +58,6 @@ const ui = { annotationValue: (idx: number) => byTestId(`annotation-value-${idx}`), labelValue: (idx: number) => byTestId(`label-value-${idx}`), }, - loadingIndicator: byText('Loading the rule...'), }; const Providers = getWrapper({ renderWithRouter: true }); @@ -68,18 +71,50 @@ function Wrapper({ children }: React.PropsWithChildren<{}>) { } describe('CloneRuleEditor', function () { - grantUserPermissions([AccessControlAction.AlertingRuleExternalRead]); + const folder = { + title: 'Folder A', + uid: grafanaRulerRule.grafana_alert.namespace_uid, + id: 1, + type: DashboardSearchItemType.DashDB, + accessControl: { + [AccessControlAction.AlertingRuleUpdate]: true, + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + + grantUserPermissions([ + AccessControlAction.AlertingRuleRead, + AccessControlAction.AlertingRuleCreate, + AccessControlAction.DataSourcesRead, + AccessControlAction.FoldersRead, + AccessControlAction.AlertingRuleExternalRead, + AccessControlAction.AlertingRuleExternalWrite, + ]); + + const dataSources = { + default: mockDataSource({ + uid: MIMIR_DATASOURCE_UID, + type: 'prometheus', + name: 'Mimir', + isDefault: true, + }), + }; + setupDataSources(dataSources.default); + setFolderResponse(mockFolder(folder)); + }); describe('Grafana-managed rules', function () { it('should populate form values from the existing alert rule', async function () { - setupDataSources(); - render( - , + , { wrapper: Wrapper } ); - await waitForElementToBeRemoved(ui.loadingIndicator.query()); await waitFor(() => { expect(within(ui.inputs.group.get()).queryByTestId('Spinner')).not.toBeInTheDocument(); }); @@ -130,20 +165,19 @@ describe('CloneRuleEditor', function () { }); render( - , { wrapper: Wrapper } ); - await waitForElementToBeRemoved(ui.loadingIndicator.query()); - await waitFor(() => { expect(ui.inputs.name.get()).toHaveValue('First Ruler Rule (copy)'); }); diff --git a/public/app/features/alerting/unified/rule-editor/ExistingRuleEditor.tsx b/public/app/features/alerting/unified/rule-editor/ExistingRuleEditor.tsx index 9eb61f1e13b..3626894fac1 100644 --- a/public/app/features/alerting/unified/rule-editor/ExistingRuleEditor.tsx +++ b/public/app/features/alerting/unified/rule-editor/ExistingRuleEditor.tsx @@ -1,76 +1,133 @@ -import { Alert, LoadingPlaceholder } from '@grafana/ui'; -import { useQueryParams } from 'app/core/hooks/useQueryParams'; +import { NavModelItem } from '@grafana/data'; +import { Alert, Stack } from '@grafana/ui'; import { Trans, t } from 'app/core/internationalization'; import { RuleIdentifier } from 'app/types/unified-alerting'; import { AlertWarning } from '../AlertWarning'; +import { AlertingPageWrapper } from '../components/AlertingPageWrapper'; import { AlertRuleForm } from '../components/rule-editor/alert-rule-form/AlertRuleForm'; +import { FederatedRuleWarning } from '../components/rule-viewer/FederatedRuleWarning'; import { useRuleWithLocation } from '../hooks/useCombinedRule'; import { useIsRuleEditable } from '../hooks/useIsRuleEditable'; import { RuleFormValues } from '../types/rule-form'; +import { Annotation } from '../utils/constants'; import { stringifyErrorLike } from '../utils/misc'; +import { rulerRuleToFormValues } from '../utils/rule-form'; import * as ruleId from '../utils/rule-id'; +import { isFederatedRuleGroup, rulerRuleType } from '../utils/rules'; +import { defaultPageNav } from './RuleEditor'; +import { cloneRuleDefinition } from './clone.utils'; interface ExistingRuleEditorProps { identifier: RuleIdentifier; - /** Provide prefill if we are trying to restore an old version of an alert rule but we need the user to manually tweak the values */ + // Provide prefill if we are trying to restore an old version of an alert rule but we need the user to manually tweak the values prefill?: Partial; + // indicate if this is a manual restore + isManualRestore?: boolean; + // indicate if this is a cloning operation + clone?: boolean; } -export function ExistingRuleEditor({ identifier, prefill }: ExistingRuleEditorProps) { - const [queryParams] = useQueryParams(); - const isManualRestore = Boolean(queryParams.isManualRestore); - +export function ExistingRuleEditor({ + identifier, + prefill, + isManualRestore = false, + clone = false, +}: ExistingRuleEditorProps) { + const ruleSourceName = ruleId.ruleIdentifierToRuleSourceName(identifier); const { loading: loadingAlertRule, result: ruleWithLocation, error: fetchRuleError, } = useRuleWithLocation({ ruleIdentifier: identifier }); - - const ruleSourceName = ruleId.ruleIdentifierToRuleSourceName(identifier); const { isEditable, loading: loadingEditable, error: errorEditable, } = useIsRuleEditable(ruleSourceName, ruleWithLocation?.rule); - // error handling for fetching rule and rule RBAC if (fetchRuleError || errorEditable) { return ( - - {stringifyErrorLike(errorEditable ?? fetchRuleError)} - + + + {stringifyErrorLike(errorEditable ?? fetchRuleError)} + + ); } const loading = loadingAlertRule || loadingEditable; - if (loading) { - return ; + return ( + + {null} + + ); } if (!ruleWithLocation && !loading) { return ( - - - Sorry! This rule does not exist. - - + + + + Sorry! This rule does not exist. + + + ); } if (isEditable === false) { return ( - - - Sorry! You do not have permission to edit this rule. - - + + + + Sorry! You do not have permission to edit this rule. + + + ); } - return ; + // we shouldn't get here because loading / error handling happens before this + if (!ruleWithLocation) { + return null; + } + + const rulerRule = ruleWithLocation.rule; + const summary = rulerRuleType.any.alertingRule(rulerRule) ? rulerRule.annotations?.[Annotation.summary] : null; + + const isFederatedRule = isFederatedRuleGroup(ruleWithLocation.group); + const isRecordingRule = rulerRuleType.any.recordingRule(rulerRule); + + const pageTitle = isRecordingRule + ? t('alerting.editor.edit-recording-rule', 'Edit recording rule') + : t('alerting.editor.edit-alert-rule', 'Edit alert rule'); + + return ( + + {summary} + {/* alerts and notifications and stuff */} + {isFederatedRule && } + + } + pageNav={getPageNav({ text: pageTitle })} + > + {clone ? ( + + ) : ( + + )} + + ); } + +const getPageNav = (pageNavOptions?: Partial): NavModelItem => { + return { ...defaultPageNav, id: 'alert-rule-edit', text: '', ...pageNavOptions }; +}; diff --git a/public/app/features/alerting/unified/rule-editor/RuleEditor.tsx b/public/app/features/alerting/unified/rule-editor/RuleEditor.tsx index 8a06815b6b0..eb9d5b005fa 100644 --- a/public/app/features/alerting/unified/rule-editor/RuleEditor.tsx +++ b/public/app/features/alerting/unified/rule-editor/RuleEditor.tsx @@ -1,9 +1,7 @@ -import { useCallback } from 'react'; import { useParams } from 'react-router-dom-v5-compat'; import { NavModelItem } from '@grafana/data'; import { Trans, t } from 'app/core/internationalization'; -import { RuleIdentifier } from 'app/types/unified-alerting'; import { AlertWarning } from '../AlertWarning'; import { AlertingPageWrapper } from '../components/AlertingPageWrapper'; @@ -13,91 +11,98 @@ import { useRulesAccess } from '../utils/accessControlHooks'; import * as ruleId from '../utils/rule-id'; import { withPageErrorBoundary } from '../withPageErrorBoundary'; -import { CloneRuleEditor } from './CloneRuleEditor'; import { ExistingRuleEditor } from './ExistingRuleEditor'; import { formValuesFromQueryParams, translateRouteParamToRuleType } from './formDefaults'; - -type RuleEditorPathParams = { +export type RuleEditorPathParams = { id?: string; type?: 'recording' | 'alerting' | 'grafana-recording'; }; -const defaultPageNav: Partial = { - icon: 'bell', +export const defaultPageNav: Partial = { id: 'alert-rule-view', }; -// sadly we only get the "type" when a new rule is being created, when editing an existing recording rule we can't actually know it from the URL -const getPageNav = (identifier?: RuleIdentifier, type?: RuleEditorPathParams['type']) => { - if (type === 'recording' || type === 'grafana-recording') { - if (identifier) { - // this branch should never trigger actually, the type param isn't used when editing rules - return { ...defaultPageNav, id: 'alert-rule-edit', text: 'Edit recording rule' }; - } else { - return { ...defaultPageNav, id: 'alert-rule-add', text: 'New recording rule' }; - } - } - - if (identifier) { - // keep this one ambiguous, don't mentiond a specific alert type here - return { ...defaultPageNav, id: 'alert-rule-edit', text: 'Edit rule' }; - } else { - return { ...defaultPageNav, id: 'alert-rule-add', text: 'New alert rule' }; - } -}; - const RuleEditor = () => { - const { identifier, type } = useRuleEditorPathParams(); - const { copyFromIdentifier, queryDefaults, isManualRestore } = useRuleEditorQueryParams(); + const { identifier } = useRuleEditorPathParams(); + const cloneIdentifier = useIdentifierFromCopy(); + const isManualRestore = useManualRestore(); const { canCreateGrafanaRules, canCreateCloudRules, canEditRules } = useRulesAccess(); - const getContent = useCallback(() => { - if (!identifier && !canCreateGrafanaRules && !canCreateCloudRules) { - return ( - - - Sorry! You are not allowed to create rules. - - - ); - } + if (!identifier && !canCreateGrafanaRules && !canCreateCloudRules) { + return ( + + + Sorry! You are not allowed to create rules. + + + ); + } - if (identifier && !canEditRules(identifier.ruleSourceName)) { - return ( - - - Sorry! You are not allowed to edit rules. - - - ); - } + if (identifier && !canEditRules(identifier.ruleSourceName)) { + return ( + + + Sorry! You are not allowed to edit rules. + + + ); + } - if (identifier) { - return ; - } + if (identifier) { + return ( + + ); + } - if (copyFromIdentifier) { - return ; - } - // new alert rule - return ; - }, [ - canCreateCloudRules, - canCreateGrafanaRules, - canEditRules, - copyFromIdentifier, - identifier, - queryDefaults, - isManualRestore, - ]); + if (cloneIdentifier) { + return ( + + ); + } + + // for new alerting or recording rules + return ; +}; + +export const RECORDING_TYPE = ['grafana-recording', 'recording']; + +/** + * This one is used for creating new rules (both alerting and recording rules) + */ +function NewRuleEditor() { + const prefill = useDefaultsFromQuery(); + const isManualRestore = useManualRestore(); + const { type = '', identifier = '' } = useRuleEditorPathParams(); + + const isExisting = Boolean(identifier); + const isRecordingRule = RECORDING_TYPE.includes(type); + + const newText = isRecordingRule + ? t('alerting.editor.new-recording-rule', 'New recording rule') + : t('alerting.editor.new-alert-rule', 'New alert rule'); + + const editText = isRecordingRule + ? t('alerting.editor.edit-recording-rule', 'Edit recording rule') + : t('alerting.editor.edit-alert-rule', 'Edit alert rule'); return ( - - {getContent()} + + ); -}; +} // The pageNav property makes it difficult to only rely on AlertingPageWrapper // to catch errors. @@ -112,13 +117,16 @@ function useRuleEditorPathParams() { return { identifier, type }; } -function useRuleEditorQueryParams() { - const { type } = useParams(); - +function useIdentifierFromCopy() { const [searchParams] = useURLSearchParams(); const copyFromId = searchParams.get('copyFrom') ?? undefined; - const copyFromIdentifier = ruleId.tryParse(copyFromId); - const isManualRestore = searchParams.has('isManualRestore'); + + return ruleId.tryParse(copyFromId); +} + +function useDefaultsFromQuery() { + const { type } = useRuleEditorPathParams(); + const [searchParams] = useURLSearchParams(); const ruleType = translateRouteParamToRuleType(type); @@ -126,5 +134,12 @@ function useRuleEditorQueryParams() { ? formValuesFromQueryParams(searchParams.get('defaults') ?? '', ruleType) : undefined; - return { copyFromIdentifier, queryDefaults, isManualRestore }; + return queryDefaults; +} + +function useManualRestore() { + const [searchParams] = useURLSearchParams(); + const isManualRestore = searchParams.has('isManualRestore'); + + return isManualRestore; } diff --git a/public/app/features/alerting/unified/rule-editor/RuleEditorCloudRules.test.tsx b/public/app/features/alerting/unified/rule-editor/RuleEditorCloudRules.test.tsx index 0ed8255932b..185a6b69d32 100644 --- a/public/app/features/alerting/unified/rule-editor/RuleEditorCloudRules.test.tsx +++ b/public/app/features/alerting/unified/rule-editor/RuleEditorCloudRules.test.tsx @@ -80,7 +80,7 @@ describe('RuleEditor cloud', () => { // save and check what was sent to backend const capture = captureRequests(); - await user.click(ui.buttons.saveAndExit.get()); + await user.click(ui.buttons.save.get()); const requests = await capture; const serializedRequests = await serializeRequests(requests); diff --git a/public/app/features/alerting/unified/rule-editor/RuleEditorExisting.test.tsx b/public/app/features/alerting/unified/rule-editor/RuleEditorExisting.test.tsx index ad58c4a002d..b8f3992e31e 100644 --- a/public/app/features/alerting/unified/rule-editor/RuleEditorExisting.test.tsx +++ b/public/app/features/alerting/unified/rule-editor/RuleEditorExisting.test.tsx @@ -96,27 +96,19 @@ describe('RuleEditor grafana managed rules', () => { //check that folder is in the list expect(ui.inputs.folder.get()).toHaveTextContent(new RegExp(folder.title)); expect(ui.inputs.annotationValue(0).get()).toHaveValue(grafanaRulerRule.annotations[Annotation.summary]); + expect(screen.getByText('New folder')).toBeInTheDocument(); //check that slashed folders are not in the list expect(ui.inputs.folder.get()).toHaveTextContent(new RegExp(folder.title)); expect(ui.inputs.folder.get()).not.toHaveTextContent(new RegExp(slashedFolder.title)); - //check that slashes warning is only shown once user search slashes - //todo: move this test to a unit test in FolderAndGroup unit test - // const folderInput = await ui.inputs.folderContainer.find(); - // expect(within(folderInput).queryByText("Folders with '/' character are not allowed.")).not.toBeInTheDocument(); - // await user.type(within(folderInput).getByRole('combobox'), 'new slashed //'); - // expect(within(folderInput).getByText("Folders with '/' character are not allowed.")).toBeInTheDocument(); - // await user.keyboard('{backspace} {backspace}{backspace}'); - // expect(within(folderInput).queryByText("Folders with '/' character are not allowed.")).not.toBeInTheDocument(); - // add an annotation await user.click(screen.getByText('Add custom annotation')); await user.type(screen.getByPlaceholderText('Enter custom annotation name...'), 'custom'); await user.type(screen.getByPlaceholderText('Enter custom annotation content...'), 'value'); // save and check what was sent to backend - await user.click(ui.buttons.saveAndExit.get()); + await user.click(ui.buttons.save.get()); }); it('saves evaluation interval correctly', async () => { @@ -140,7 +132,7 @@ describe('RuleEditor grafana managed rules', () => { (req) => req.method === 'POST' && req.url.includes('/api/ruler/grafana/api/v1/rules/uuid020c61ef') ); - await user.click(ui.buttons.saveAndExit.get()); + await user.click(ui.buttons.save.get()); const [request] = await capture; const postBody = await request.json(); @@ -158,6 +150,6 @@ describe('Data source managed rules', () => { it('should show an error if the data source does not exist', async () => { renderRuleEditor('cri%24grafana-cloudd%24delete me%24delete me 3%24recording_rule_delete_2%24-476183141'); - expect(await screen.findByText(/unable to find data source/i)).toBeInTheDocument(); + expect(await screen.findByText(/not found/i)).toBeInTheDocument(); }); }); diff --git a/public/app/features/alerting/unified/rule-editor/RuleEditorGrafanaRules.test.tsx b/public/app/features/alerting/unified/rule-editor/RuleEditorGrafanaRules.test.tsx index 9505c96fd23..0c9433406c7 100644 --- a/public/app/features/alerting/unified/rule-editor/RuleEditorGrafanaRules.test.tsx +++ b/public/app/features/alerting/unified/rule-editor/RuleEditorGrafanaRules.test.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { renderRuleEditor, ui } from 'test/helpers/alertingRuleEditor'; import { clickSelectOption, selectOptionInTest } from 'test/helpers/selectOptionInTest'; -import { screen } from 'test/test-utils'; +import { screen, waitFor } from 'test/test-utils'; import { byRole } from 'testing-library-selector'; import { contextSrv } from 'app/core/services/context_srv'; @@ -73,7 +73,7 @@ describe('RuleEditor grafana managed rules', () => { await clickSelectOption(groupInput, grafanaRulerGroup.name); await user.type(ui.inputs.annotationValue(1).get(), 'some description'); - await user.click(ui.buttons.saveAndExit.get()); + await user.click(ui.buttons.save.get()); expect(await screen.findByRole('status')).toHaveTextContent('Rule added successfully'); const requests = await capture; @@ -128,12 +128,12 @@ describe('RuleEditor grafana managed rules', () => { const nameInput = await ui.inputs.name.find(); expect(nameInput).toHaveValue(grafanaRulerRule.grafana_alert.title); //check that folder is in the list - expect(ui.inputs.folder.get()).toHaveTextContent(new RegExp(folder.title)); + await waitFor(() => expect(ui.inputs.folder.get()).toHaveTextContent(new RegExp(folder.title))); expect(ui.inputs.annotationValue(0).get()).toHaveValue(grafanaRulerRule.annotations[Annotation.summary]); expect(ui.manualRestoreBanner.get()).toBeInTheDocument(); // check that manual restore banner is shown - await user.click(ui.buttons.saveAndExit.get()); + await user.click(ui.buttons.save.get()); expect(await screen.findByRole('status')).toHaveTextContent('Rule added successfully'); const requests = await capture; diff --git a/public/app/features/alerting/unified/rule-editor/RuleEditorRecordingRule.test.tsx b/public/app/features/alerting/unified/rule-editor/RuleEditorRecordingRule.test.tsx index 29c1f926dbe..19166c01ea4 100644 --- a/public/app/features/alerting/unified/rule-editor/RuleEditorRecordingRule.test.tsx +++ b/public/app/features/alerting/unified/rule-editor/RuleEditorRecordingRule.test.tsx @@ -80,7 +80,7 @@ describe('RuleEditor recording rules', () => { await userEvent.type(await ui.inputs.expr.find(), 'up == 1'); // try to save, find out that recording rule name is invalid - await userEvent.click(ui.buttons.saveAndExit.get()); + await userEvent.click(ui.buttons.save.get()); await waitFor(() => expect( byText( @@ -95,7 +95,7 @@ describe('RuleEditor recording rules', () => { // save and check what was sent to backend const capture = captureRequests(); - await userEvent.click(ui.buttons.saveAndExit.get()); + await userEvent.click(ui.buttons.save.get()); const requests = await capture; const serializedRequests = await serializeRequests(requests); diff --git a/public/app/features/alerting/unified/rule-editor/clone.utils.ts b/public/app/features/alerting/unified/rule-editor/clone.utils.ts new file mode 100644 index 00000000000..a4b38a3b7a7 --- /dev/null +++ b/public/app/features/alerting/unified/rule-editor/clone.utils.ts @@ -0,0 +1,39 @@ +import { cloneDeep } from 'lodash'; + +import { RuleWithLocation } from 'app/types/unified-alerting'; +import { RulerRuleDTO } from 'app/types/unified-alerting-dto'; + +import { generateCopiedName } from '../utils/duplicate'; +import { getRuleName, rulerRuleType } from '../utils/rules'; + +export function changeRuleName(rule: RulerRuleDTO, newName: string) { + if (rulerRuleType.grafana.rule(rule)) { + rule.grafana_alert.title = newName; + } + if (rulerRuleType.dataSource.alertingRule(rule)) { + rule.alert = newName; + } + + if (rulerRuleType.dataSource.recordingRule(rule)) { + rule.record = newName; + } +} + +export function cloneRuleDefinition(rule: RuleWithLocation) { + const ruleClone = cloneDeep(rule); + changeRuleName( + ruleClone.rule, + generateCopiedName(getRuleName(ruleClone.rule), ruleClone.group.rules.map(getRuleName)) + ); + + if (rulerRuleType.grafana.rule(ruleClone.rule)) { + ruleClone.rule.grafana_alert.uid = ''; + + // Provisioned alert rules have provisioned alert group which cannot be used in UI + if (Boolean(ruleClone.rule.grafana_alert.provenance)) { + ruleClone.group = { name: '', rules: ruleClone.group.rules }; + } + } + + return ruleClone; +} diff --git a/public/app/features/alerting/unified/utils/misc.ts b/public/app/features/alerting/unified/utils/misc.ts index 033a93c1272..daa97475a52 100644 --- a/public/app/features/alerting/unified/utils/misc.ts +++ b/public/app/features/alerting/unified/utils/misc.ts @@ -19,6 +19,7 @@ import { DataSourceRuleGroupIdentifier, FilterState, RuleIdentifier, + RuleWithLocation, RulesSource, SilenceFilterState, } from 'app/types/unified-alerting'; @@ -55,6 +56,15 @@ export function createViewLinkV2( return rulesNav.detailsPageLink(ruleSourceName, identifier, returnTo ? { returnTo } : undefined); } +export function createViewLinkFromRuleWithLocation(ruleWithLocation: RuleWithLocation) { + const ruleSourceName = ruleWithLocation.ruleSourceName; + const identifier = ruleId.fromRuleWithLocation(ruleWithLocation); + const paramId = encodeURIComponent(ruleId.stringifyIdentifier(identifier)); + const paramSource = encodeURIComponent(ruleSourceName); + + return createRelativeUrl(`/alerting/${paramSource}/${paramId}/view`); +} + export function createExploreLink(datasource: DataSourceRef, query: string) { const { uid, type } = datasource; diff --git a/public/app/features/alerting/unified/utils/rules.ts b/public/app/features/alerting/unified/utils/rules.ts index 13aa9982983..bbd7a08db95 100644 --- a/public/app/features/alerting/unified/utils/rules.ts +++ b/public/app/features/alerting/unified/utils/rules.ts @@ -208,6 +208,10 @@ export function getPendingPeriod(rule: CombinedRule): string | undefined { return undefined; } +export function getPendingPeriodFromRulerRule(rule: RulerRuleDTO) { + return rulerRuleType.any.alertingRule(rule) ? rule.for : undefined; +} + export function getKeepFiringfor(rule: CombinedRule): string | undefined { if (rulerRuleType.any.recordingRule(rule.rulerRule)) { return undefined; diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index fa21b59fe76..8f807d1f473 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -408,11 +408,9 @@ }, "alert-rule-form": { "action-buttons": { - "delete": "Delete", "edit-yaml": "Edit YAML", - "save-exit": "Save rule and exit" - }, - "title-delete-rule": "Delete rule" + "save": "Save" + } }, "alert-rule-name-and-metric": { "aria-label-name": "name", @@ -895,12 +893,18 @@ "text-loading-template": "Loading template...", "title-failed-to-fetch-notification-template": "Failed to fetch notification template" }, + "editor": { + "edit-alert-rule": "Edit alert rule", + "edit-recording-rule": "Edit recording rule", + "new-alert-rule": "New alert rule", + "new-recording-rule": "New recording rule" + }, "error-modal": { "failed-to-update-your-configuration": "Failed to update your configuration:", "title-something-went-wrong": "Something went wrong" }, "evaluation-behavior-summary": { - "evaluate": "Every {{every}}", + "evaluate": "Every {{interval}}", "label-evaluate": "Evaluate", "label-evaluation-time": "Evaluation time", "label-last-evaluation": "Last evaluation", @@ -929,7 +933,6 @@ "existing-rule-editor": { "sorry-permission": "Sorry! You do not have permission to edit this rule.", "sorry-this-rule-does-not-exist": "Sorry! This rule does not exist.", - "text-loading-rule": "Loading rule...", "title-cannot-edit-rule": "Cannot edit rule", "title-failed-to-load-rule": "Failed to load rule", "title-rule-not-found": "Rule not found" diff --git a/public/test/helpers/alertingRuleEditor.tsx b/public/test/helpers/alertingRuleEditor.tsx index 9e601269b11..0375a9123bd 100644 --- a/public/test/helpers/alertingRuleEditor.tsx +++ b/public/test/helpers/alertingRuleEditor.tsx @@ -39,7 +39,7 @@ export const ui = { byTestId(selectors.components.AlertRules.stepAdvancedModeSwitch(stepNo.toString())), }, buttons: { - saveAndExit: byRole('button', { name: 'Save rule and exit' }), + save: byTestId('save-rule'), addAnnotation: byRole('button', { name: /Add info/ }), addLabel: byRole('button', { name: /Add label/ }), preview: byRole('button', { name: /^Preview$/ }), From 97d10b5095cacaa5d0c989e0461a51a8be610c4d Mon Sep 17 00:00:00 2001 From: Kevin Minehart <5140827+kminehart@users.noreply.github.com> Date: Tue, 29 Apr 2025 10:09:23 -0500 Subject: [PATCH 009/849] CI: remove unused worklow; use GITHUB_TOKEN where possible (#104657) * remove unused worklow; use GITHUB_TOKEN where possible * pin usages of checkout and setup-go * Fix zizmor errors * add zizmor.yml * fix `changelog.yml` * fix `core-plugins-build-and-release.yml` * fix `release-comms.yml` * update release-pr.yml and run-e2e-suite.yml * Fix errors in files outside of .github/workflows * Remove path filter on zizmor.yml --------- Co-authored-by: Sven Grossmann Co-authored-by: joshhunt --- .github/actions/setup-enterprise/action.yml | 2 +- .../actions/setup-grafana-bench/action.yml | 2 +- .../test-coverage-processor/action.yml | 2 +- .github/workflows/alerting-swagger-gen.yml | 8 +- .github/workflows/alerting-update-module.yml | 5 +- .github/workflows/analytics-events-report.yml | 6 +- .github/workflows/backend-code-checks.yml | 7 +- .github/workflows/backend-unit-tests.yml | 22 ++-- .github/workflows/backport.yml | 21 ++-- .github/workflows/bump-version.yml | 32 ++--- .github/workflows/changelog.yml | 44 ++++--- .github/workflows/close-milestone.yml | 44 ------- .github/workflows/codeowners-validator.yml | 6 +- .github/workflows/codeql-analysis.yml | 5 +- .github/workflows/commands.yml | 10 +- .github/workflows/community-release.yml | 2 +- .../core-plugins-build-and-release.yml | 52 ++++---- .../workflows/create-next-release-branch.yml | 2 +- ...te-security-patch-from-security-mirror.yml | 5 +- .../workflows/dashboards-issue-add-label.yml | 2 +- .github/workflows/deploy-pr-preview.yml | 2 +- .../detect-breaking-changes-levitate.yml | 36 +++--- .github/workflows/documentation-ci.yml | 4 +- .../ephemeral-instances-pr-comment.yml | 3 +- .github/workflows/feature-toggles-ci.yml | 4 +- .github/workflows/frontend-lint.yml | 48 +++++--- .github/workflows/github-release.yml | 2 +- .github/workflows/go-lint.yml | 8 +- .../workflows/i18n-crowdin-create-tasks.yml | 6 +- .github/workflows/i18n-crowdin-download.yml | 13 +- .github/workflows/i18n-crowdin-upload.yml | 6 +- .github/workflows/issue-opened.yml | 17 ++- .github/workflows/lint-build-docs.yml | 6 +- .github/workflows/metrics-collector.yml | 1 + .github/workflows/migrate-prs.yml | 2 +- .github/workflows/milestone.yml | 19 --- .github/workflows/pr-backend-coverage.yml | 6 +- .github/workflows/pr-checks.yml | 1 + .../pr-codeql-analysis-javascript.yml | 3 +- .../workflows/pr-codeql-analysis-python.yml | 3 +- .github/workflows/pr-commands.yml | 1 + .../pr-dependabot-update-go-workspace.yml | 9 +- .github/workflows/pr-e2e-tests.yml | 7 +- .github/workflows/pr-frontend-unit-tests.yml | 20 +-- .github/workflows/pr-go-workspace-check.yml | 8 +- .github/workflows/pr-k8s-codegen-check.yml | 8 +- .github/workflows/pr-patch-check-event.yml | 2 + .github/workflows/pr-test-integration.yml | 14 ++- .github/workflows/publish-kinds-next.yml | 5 +- .github/workflows/publish-kinds-release.yml | 7 +- .../publish-technical-documentation-next.yml | 4 +- ...ublish-technical-documentation-release.yml | 5 +- .github/workflows/release-comms.yml | 21 ++-- .github/workflows/release-pr.yml | 114 ++++++++++-------- .github/workflows/remove-milestone.yml | 60 --------- .../workflows/run-dashboard-search-e2e.yml | 22 +++- .github/workflows/run-e2e-suite.yml | 15 ++- .github/workflows/run-schema-v2-e2e.yml | 12 +- .github/workflows/skye-add-to-project.yml | 6 +- .github/workflows/storybook-verification.yml | 8 +- .github/workflows/sync-mirror-event.yml | 23 +++- .github/workflows/trivy-scan.yml | 6 +- .github/workflows/update-changelog.yml | 52 -------- .github/workflows/update-make-docs.yml | 6 +- .github/workflows/verify-kinds.yml | 5 +- .github/workflows/zizmor.yml | 6 +- .github/zizmor.yml | 31 +++++ pkg/build/actions/bump-version/action.yml | 2 +- 68 files changed, 478 insertions(+), 470 deletions(-) delete mode 100644 .github/workflows/close-milestone.yml delete mode 100644 .github/workflows/milestone.yml delete mode 100644 .github/workflows/remove-milestone.yml delete mode 100644 .github/workflows/update-changelog.yml create mode 100644 .github/zizmor.yml diff --git a/.github/actions/setup-enterprise/action.yml b/.github/actions/setup-enterprise/action.yml index e87d3d7bae9..37c09911fc8 100644 --- a/.github/actions/setup-enterprise/action.yml +++ b/.github/actions/setup-enterprise/action.yml @@ -12,7 +12,7 @@ runs: steps: - name: Retrieve GitHub App secrets id: get-secrets - uses: grafana/shared-workflows/actions/get-vault-secrets@get-vault-secrets-v1.0.1 + uses: grafana/shared-workflows/actions/get-vault-secrets@get-vault-secrets-v1.0.1 # zizmor: ignore[unpinned-uses] with: repo_secrets: | APP_ID=${{ inputs.github-app-name }}:app-id diff --git a/.github/actions/setup-grafana-bench/action.yml b/.github/actions/setup-grafana-bench/action.yml index b708d862e2e..f30cc37221d 100644 --- a/.github/actions/setup-grafana-bench/action.yml +++ b/.github/actions/setup-grafana-bench/action.yml @@ -16,7 +16,7 @@ runs: steps: - name: Retrieve GitHub App secrets id: get-secrets - uses: grafana/shared-workflows/actions/get-vault-secrets@get-vault-secrets-v1.0.1 + uses: grafana/shared-workflows/actions/get-vault-secrets@get-vault-secrets-v1.0.1 # zizmor: ignore[unpinned-uses] with: repo_secrets: | APP_ID=${{ inputs.github-app-name }}:app-id diff --git a/.github/actions/test-coverage-processor/action.yml b/.github/actions/test-coverage-processor/action.yml index c22fd0ccb19..bd2458020c3 100644 --- a/.github/actions/test-coverage-processor/action.yml +++ b/.github/actions/test-coverage-processor/action.yml @@ -38,7 +38,7 @@ runs: fi - name: Report coverage to CodeCov - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5 if: inputs.codecov-token != '' with: files: ${{ inputs.coverage-file }} diff --git a/.github/workflows/alerting-swagger-gen.yml b/.github/workflows/alerting-swagger-gen.yml index 7c3af87837f..2526d924d5e 100644 --- a/.github/workflows/alerting-swagger-gen.yml +++ b/.github/workflows/alerting-swagger-gen.yml @@ -10,18 +10,19 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: fetch-depth: 2 + persist-credentials: false - name: Set go version - uses: actions/setup-go@v4 + uses: actions/setup-go@19bb51245e9c80abacb2e91cc42b33fa478b8639 with: go-version-file: go.mod - name: Build swagger run: | make -C pkg/services/ngalert/api/tooling post.json api.json - name: Open Pull Request - uses: peter-evans/create-pull-request@v5 + uses: peter-evans/create-pull-request@4e1beaa7521e8b457b572c090b25bd3db56bf1c5 with: token: ${{ secrets.GITHUB_TOKEN }} commit-message: "chore: update alerting swagger spec" @@ -34,4 +35,3 @@ jobs: labels: 'area/alerting,type/docs,no-changelog' team-reviewers: 'grafana/alerting-backend' draft: false - diff --git a/.github/workflows/alerting-update-module.yml b/.github/workflows/alerting-update-module.yml index 213b243f9b1..d1646d21718 100644 --- a/.github/workflows/alerting-update-module.yml +++ b/.github/workflows/alerting-update-module.yml @@ -18,7 +18,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 - + with: + persist-credentials: false - name: Check if update branch exists run: | if git ls-remote --heads origin update-alerting-module | grep -q 'update-alerting-module'; then @@ -96,7 +97,7 @@ jobs: make update-workspace - id: get-secrets - uses: grafana/shared-workflows/actions/get-vault-secrets@28361cdb22223e5f1e34358c86c20908e7248760 # 1.1.0 + uses: grafana/shared-workflows/actions/get-vault-secrets@main # zizmor: ignore[unpinned-uses] with: repo_secrets: | GITHUB_APP_ID=alerting-team:app-id diff --git a/.github/workflows/analytics-events-report.yml b/.github/workflows/analytics-events-report.yml index 9d4d6907256..9c5c6f3ef09 100644 --- a/.github/workflows/analytics-events-report.yml +++ b/.github/workflows/analytics-events-report.yml @@ -8,10 +8,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + persist-credentials: false - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e with: node-version-file: '.nvmrc' cache: 'yarn' diff --git a/.github/workflows/backend-code-checks.yml b/.github/workflows/backend-code-checks.yml index 75249775bad..b55c07aed22 100644 --- a/.github/workflows/backend-code-checks.yml +++ b/.github/workflows/backend-code-checks.yml @@ -24,10 +24,11 @@ jobs: name: Validate Backend Configs runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + persist-credentials: false - name: Setup Go - uses: actions/setup-go@v5 + uses: actions/setup-go@111f3307d8850f501ac008e886eec1fd1932a34 with: # Explicitly set Go version to 1.24.1 to ensure consistent OpenAPI spec generation # The crypto/x509 package has additional fields in Go 1.24.1 that affect the generated specs diff --git a/.github/workflows/backend-unit-tests.yml b/.github/workflows/backend-unit-tests.yml index f1dccf53779..2c0c238d502 100644 --- a/.github/workflows/backend-unit-tests.yml +++ b/.github/workflows/backend-unit-tests.yml @@ -17,9 +17,7 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} -permissions: - contents: read - id-token: write +permissions: {} jobs: grafana: @@ -29,11 +27,16 @@ jobs: name: Grafana runs-on: ubuntu-latest-8-cores continue-on-error: true + permissions: + contents: read + id-token: write steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + persist-credentials: false - name: Setup Go - uses: actions/setup-go@v5 + uses: actions/setup-go@111f3307d8850f501ac008e886eec1fd1932a34 with: go-version-file: go.mod - name: Generate Go code @@ -46,11 +49,16 @@ jobs: if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false name: Grafana Enterprise runs-on: ubuntu-latest-8-cores + permissions: + contents: read + id-token: write steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + persist-credentials: false - name: Setup Go - uses: actions/setup-go@v5 + uses: actions/setup-go@111f3307d8850f501ac008e886eec1fd1932a34 with: go-version-file: go.mod - name: Setup Enterprise diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index fca35029cd6..fc65898f652 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -5,6 +5,10 @@ on: - closed - labeled +permissions: + contents: write + pull-requests: write + jobs: main: if: github.repository == 'grafana/grafana' @@ -14,20 +18,15 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 with: persist-credentials: false - - name: "Generate token" - id: generate_token - uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 - with: - app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} - private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} - - run: git config --global user.email '132647405+grafana-delivery-bot[bot]@users.noreply.github.com' - - run: git config --global user.name 'grafana-delivery-bot[bot]' + - run: git config --local user.name "github-actions[bot]" + - run: git config --local user.email "github-actions[bot]@users.noreply.github.com" + - run: git config --local --add --bool push.autoSetupRemote true - name: Set remote URL env: - GIT_TOKEN: ${{ steps.generate_token.outputs.token }} + GIT_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | git remote set-url origin "https://grafana-delivery-bot:$GIT_TOKEN@github.com/grafana/grafana.git" - name: Run backport - uses: grafana/grafana-github-actions-go/backport@d4c452f92ed826d515dccf1f62923e537953acd8 # main + uses: grafana/grafana-github-actions-go/backport@main # zizmor: ignore[unpinned-uses] with: - token: ${{ steps.generate_token.outputs.token }} + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/bump-version.yml b/.github/workflows/bump-version.yml index 5f0b7cf3020..8db9fb1e5c3 100644 --- a/.github/workflows/bump-version.yml +++ b/.github/workflows/bump-version.yml @@ -11,33 +11,37 @@ on: dry_run: default: false required: false + +permissions: + contents: write + pull-requests: write + jobs: - main: + bump-version: runs-on: ubuntu-latest steps: - name: Checkout Grafana - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + persist-credentials: false - name: Update package.json versions uses: ./pkg/build/actions/bump-version with: version: ${{ inputs.version }} - - if: ${{ inputs.push }} - name: Generate token - id: generate_token - uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 - with: - app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} - private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} - if: ${{ inputs.push }} name: Push & Create PR + env: + VERSION: ${{ inputs.version }} + DRY_RUN: ${{ inputs.dry_run }} + REF_NAME: ${{ github.ref_name }} + RUN_ID: ${{ github.run_id }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | git config --local user.name "github-actions[bot]" git config --local user.email "github-actions[bot]@users.noreply.github.com" git config --local --add --bool push.autoSetupRemote true - git checkout -b "bump-version/${{ github.run_id }}/${{ inputs.version }}" + git checkout -b "bump-version/${RUN_ID}/${VERSION}" git add . - git commit -m "bump version ${{ inputs.version }}" + git commit -m "bump version ${VERSION}" git push - gh pr create --dry-run=${{ inputs.dry_run }} -l "type/ci" -l "no-changelog" -B "${{ github.ref_name }}" --title "Release: Bump version to ${{ inputs.version }}" --body "Updated version to ${{ inputs.version }}" - env: - GH_TOKEN: ${{ steps.generate_token.outputs.token }} + gh pr create --dry-run=$DRY_RUN -l "type/ci" -l "no-changelog" -B "$REF_NAME" --title "Release: Bump version to ${VERSION}" --body "Updated version to ${VERSION}" diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index d23ca18e612..094279e0c9a 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -51,15 +51,20 @@ on: default: false type: boolean -permissions: - contents: write - pull-requests: write +permissions: {} jobs: main: + env: + RUN_ID: ${{ github.run_id }} + VERSION: ${{ inputs.version }} + PREVIOUS_VERISON: ${{ inputs.previous_version }} + TARGET: ${{ inputs.target }} + DRY_RUN: ${{ inputs.dry_run }} runs-on: ubuntu-latest permissions: contents: write + pull-requests: write steps: - name: "Generate token" id: generate_token @@ -68,7 +73,7 @@ jobs: app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} - name: "Checkout Grafana repo" - uses: "actions/checkout@v4" + uses: "actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683" with: ref: main sparse-checkout: | @@ -79,8 +84,9 @@ jobs: .prettierrc.js fetch-depth: 0 fetch-tags: true + persist-credentials: false - name: Setup nodejs environment - uses: actions/setup-node@v4 + uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e with: node-version-file: .nvmrc - name: "Configure git user" @@ -89,7 +95,7 @@ jobs: git config --local user.email "github-actions[bot]@users.noreply.github.com" git config --local --add --bool push.autoSetupRemote true - name: "Create branch" - run: git checkout -b "changelog/${{ github.run_id }}/${{ inputs.version }}" + run: git checkout -b "changelog/${RUN_ID}/${VERSION}" - name: "Generate changelog" id: changelog uses: ./.github/workflows/actions/changelog @@ -103,24 +109,24 @@ jobs: # Prepare CHANGELOG.md content with version delimiters ( echo - echo "# ${{ inputs.version}} ($(date '+%F'))" + echo "# ${VERSION} ($(date '+%F'))" echo cat changelog_items.md ) > CHANGELOG.part # Check if a version exists in the changelog - if grep -q "" + echo "" cat CHANGELOG.part - echo "" + echo "" cat CHANGELOG.md ) > CHANGELOG.tmp mv CHANGELOG.tmp CHANGELOG.md @@ -138,11 +144,11 @@ jobs: - name: "Create changelog PR" run: > gh pr create \ - --dry-run=${{ inputs.dry_run }} \ + --dry-run=${DRY_RUN} \ --label "no-backport" \ --label "no-changelog" \ - -B "${{ inputs.target }}" \ - --title "Release: update changelog for ${{ inputs.version }}" \ - --body "Changelog changes for release ${{ inputs.version }}" + -B "${TARGET}" \ + --title "Release: update changelog for ${VERSION}" \ + --body "Changelog changes for release ${VERSION}" env: - GH_TOKEN: ${{ steps.generate_token.outputs.token }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/close-milestone.yml b/.github/workflows/close-milestone.yml deleted file mode 100644 index 11613b5fab9..00000000000 --- a/.github/workflows/close-milestone.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Close milestone -on: - workflow_dispatch: - inputs: - version: - required: true - description: Needs to match, exactly, the name of a milestone - workflow_call: - inputs: - version_call: - description: Needs to match, exactly, the name of a milestone - required: true - type: string - -jobs: - main: - if: github.repository == 'grafana/grafana' - runs-on: ubuntu-latest - steps: - - name: Checkout Actions - uses: actions/checkout@v4 - with: - repository: "grafana/grafana-github-actions" - path: ./actions - ref: main - - name: Install Actions - run: npm install --production --prefix ./actions - - name: "Generate token" - id: generate_token - uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 - with: - app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} - private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} - - name: Close milestone (manually invoked) - if: ${{ github.event.inputs.version != '' }} - uses: ./actions/close-milestone - with: - token: ${{ steps.generate_token.outputs.token }} - - name: Close milestone (workflow invoked) - if: ${{ inputs.version_call != '' }} - uses: ./actions/close-milestone - with: - version_call: ${{ inputs.version_call }} - token: ${{ steps.generate_token.outputs.token }} diff --git a/.github/workflows/codeowners-validator.yml b/.github/workflows/codeowners-validator.yml index 12184b2f680..f98bee4e213 100644 --- a/.github/workflows/codeowners-validator.yml +++ b/.github/workflows/codeowners-validator.yml @@ -9,9 +9,11 @@ jobs: runs-on: ubuntu-latest steps: # Checks-out your repository, which is validated in the next step - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + persist-credentials: false - name: GitHub CODEOWNERS Validator - uses: mszostok/codeowners-validator@v0.7.4 + uses: mszostok/codeowners-validator@7f3f5e28c6d7b8dfae5731e54ce2272ca384592f # input parameters with: # ==== GitHub Auth ==== diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index d65cc42baf2..06772bac55c 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -40,15 +40,16 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. fetch-depth: 2 + persist-credentials: false - if: matrix.language == 'go' name: Set go version - uses: actions/setup-go@v4 + uses: actions/setup-go@19bb51245e9c80abacb2e91cc42b33fa478b8639 with: go-version-file: go.mod diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml index 942ea69d5e3..cf61d859443 100644 --- a/.github/workflows/commands.yml +++ b/.github/workflows/commands.yml @@ -12,9 +12,7 @@ on: concurrency: group: issue-commands-${{ github.event.issue.number }} -permissions: - contents: read - id-token: write +permissions: {} jobs: config: @@ -34,10 +32,13 @@ jobs: needs: config if: needs.config.outputs.has-secrets runs-on: ubuntu-latest + permissions: + contents: read + id-token: write steps: - name: "Get vault secrets" id: vault-secrets - uses: grafana/shared-workflows/actions/get-vault-secrets@main + uses: grafana/shared-workflows/actions/get-vault-secrets@main # zizmor: ignore[unpinned-uses] with: # Secrets placed in the ci/repo/grafana/grafana/plugins_platform_issue_commands_github_bot path in Vault repo_secrets: | @@ -57,6 +58,7 @@ jobs: repository: "grafana/grafana-github-actions" path: ./actions ref: main + persist-credentials: false - name: Install Actions run: npm install --production --prefix ./actions diff --git a/.github/workflows/community-release.yml b/.github/workflows/community-release.yml index 86e7703e5c4..73c72749baa 100644 --- a/.github/workflows/community-release.yml +++ b/.github/workflows/community-release.yml @@ -36,7 +36,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Run community-release (manually invoked) - uses: grafana/grafana-github-actions-go/community-release@main + uses: grafana/grafana-github-actions-go/community-release@main # zizmor: ignore[unpinned-uses] with: token: ${{ secrets.GITHUB_TOKEN }} version: ${{ inputs.version }} diff --git a/.github/workflows/core-plugins-build-and-release.yml b/.github/workflows/core-plugins-build-and-release.yml index e4803a2208f..d2aebbc41e8 100644 --- a/.github/workflows/core-plugins-build-and-release.yml +++ b/.github/workflows/core-plugins-build-and-release.yml @@ -33,6 +33,8 @@ permissions: jobs: build-and-publish: + env: + PLUGIN_ID: ${{ inputs.plugin_id }} name: Build and publish ${{ inputs.plugin_id }} runs-on: ubuntu-latest outputs: @@ -41,12 +43,14 @@ jobs: version: ${{ steps.build_frontend.outputs.version }} steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + persist-credentials: false - name: Verify inputs run: | - if [ -z ${{ inputs.plugin_id }} ]; then echo "Missing plugin ID"; exit 1; fi + if [ -z $PLUGIN_ID ]; then echo "Missing plugin ID"; exit 1; fi - id: get-secrets - uses: grafana/shared-workflows/actions/get-vault-secrets@main + uses: grafana/shared-workflows/actions/get-vault-secrets@main # zizmor: ignore[unpinned-uses] with: # Secrets placed in the ci/repo/grafana// path in Vault repo_secrets: | @@ -54,13 +58,13 @@ jobs: PLUGINS_GRAFANA_API_KEY=core-plugins-build-and-release:PLUGINS_GRAFANA_API_KEY PLUGINS_GCOM_TOKEN=core-plugins-build-and-release:PLUGINS_GCOM_TOKEN - name: 'Authenticate to Google Cloud' - uses: 'google-github-actions/auth@v2' + uses: 'google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f' with: credentials_json: '${{ env.PLUGINS_GOOGLE_CREDENTIALS }}' - name: 'Set up Cloud SDK' - uses: 'google-github-actions/setup-gcloud@v2' + uses: 'google-github-actions/setup-gcloud@6189d56e4096ee891640bb02ac264be376592d6a' - name: Setup nodejs environment - uses: actions/setup-node@v4 + uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e with: node-version-file: .nvmrc cache: yarn @@ -70,7 +74,7 @@ jobs: run: | dir=$(dirname \ $(egrep -lir --include=plugin.json --exclude-dir=dist \ - '"id": "${{ inputs.plugin_id }}"' \ + '"id": "${PLUGIN_ID}"' \ public/app/plugins \ ) \ ) @@ -85,19 +89,19 @@ jobs: working-directory: ${{ steps.get_dir.outputs.dir }} run: | [ ! -d ./bin ] && mkdir -pv ./bin || true - curl -fL -o ./bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v${{ env.GRABPL_VERSION }}/grabpl + curl -fL -o ./bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v$GRABPL_VERSION/grabpl chmod 0755 ./bin/grabpl - name: Check backend id: check_backend shell: bash run: | - if egrep -qr --include=main.go 'datasource.Manage\("${{ inputs.plugin_id }}"' pkg/tsdb; then + if egrep -qr --include=main.go 'datasource.Manage\("$PLUGIN_ID"' pkg/tsdb; then echo "has_backend=true" >> $GITHUB_OUTPUT else echo "has_backend=false" >> $GITHUB_OUTPUT fi - name: Setup golang environment - uses: actions/setup-go@v4 + uses: actions/setup-go@19bb51245e9c80abacb2e91cc42b33fa478b8639 if: steps.check_backend.outputs.has_backend == 'true' with: go-version-file: go.mod @@ -151,7 +155,7 @@ jobs: # Release branch, do not add commit hash to version command="plugin:build" fi - yarn $command --scope="@grafana-plugins/${{ inputs.plugin_id }}" + yarn $command --scope="@grafana-plugins/$PLUGIN_ID" version=$(cat ${{ steps.get_dir.outputs.dir }}/dist/plugin.json | jq -r .info.version) echo "version=${version}" >> $GITHUB_OUTPUT - name: build:backend @@ -160,7 +164,7 @@ jobs: env: VERSION: ${{ steps.build_frontend.outputs.version }} run: | - make build-plugin-go PLUGIN_ID=${{ inputs.plugin_id }} + make build-plugin-go PLUGIN_ID=$PLUGIN_ID - name: package working-directory: ${{ steps.get_dir.outputs.dir }} run: | @@ -175,7 +179,7 @@ jobs: VERSION: ${{ steps.build_frontend.outputs.version }} run: | api_res=$(curl -X 'GET' -H "Authorization: Bearer $GCOM_TOKEN" \ - '${{ env.GCOM_API}}/api/plugins/${{ inputs.plugin_id }}?version=$VERSION' \ + '${{ env.GCOM_API}}/api/plugins/$PLUGIN_ID?version=$VERSION' \ -H 'accept: application/json') api_res_code=$(echo $api_res | jq -r .code) if [ "$api_res_code" = "NotFound" ]; then @@ -197,10 +201,10 @@ jobs: run: | echo "Publish release to Google Cloud Storage:" touch ci/packages/windows ci/packages/darwin ci/packages/linux ci/packages/any - gsutil -m cp -r ci/packages/*windows* gs://${{ env.GCP_BUCKET }}/${{ inputs.plugin_id }}/release/${VERSION}/windows - gsutil -m cp -r ci/packages/*linux* gs://${{ env.GCP_BUCKET }}/${{ inputs.plugin_id }}/release/${VERSION}/linux - gsutil -m cp -r ci/packages/*darwin* gs://${{ env.GCP_BUCKET }}/${{ inputs.plugin_id }}/release/${VERSION}/darwin - gsutil -m cp -r ci/packages/*any* gs://${{ env.GCP_BUCKET }}/${{ inputs.plugin_id }}/release/${VERSION}/any + gsutil -m cp -r ci/packages/*windows* gs://${{ env.GCP_BUCKET }}/$PLUGIN_ID/release/${VERSION}/windows + gsutil -m cp -r ci/packages/*linux* gs://${{ env.GCP_BUCKET }}/$PLUGIN_ID/release/${VERSION}/linux + gsutil -m cp -r ci/packages/*darwin* gs://${{ env.GCP_BUCKET }}/$PLUGIN_ID/release/${VERSION}/darwin + gsutil -m cp -r ci/packages/*any* gs://${{ env.GCP_BUCKET }}/$PLUGIN_ID/release/${VERSION}/any - name: Publish new plugin version on grafana.com if: steps.check_backend.outputs.has_backend == 'true' working-directory: ${{ steps.get_dir.outputs.dir }} @@ -214,27 +218,27 @@ jobs: \"url\": \"https://github.com/grafana/grafana/tree/main/${{ steps.get_dir.outputs.dir }}\", \"download\": { \"linux-amd64\": { - \"url\": \"https://storage.googleapis.com/${{ env.GCP_BUCKET }}/${{ inputs.plugin_id }}/release/${VERSION}/linux/${{ inputs.plugin_id }}-${VERSION}.linux_amd64.zip\", + \"url\": \"https://storage.googleapis.com/${{ env.GCP_BUCKET }}/$PLUGIN_ID/release/${VERSION}/linux/$PLUGIN_ID-${VERSION}.linux_amd64.zip\", \"md5\": \"$(cat ci/packages/info-linux_amd64.json | jq -r .plugin.md5)\" }, \"linux-arm64\": { - \"url\": \"https://storage.googleapis.com/${{ env.GCP_BUCKET }}/${{ inputs.plugin_id }}/release/${VERSION}/linux/${{ inputs.plugin_id }}-${VERSION}.linux_arm64.zip\", + \"url\": \"https://storage.googleapis.com/${{ env.GCP_BUCKET }}/$PLUGIN_ID/release/${VERSION}/linux/$PLUGIN_ID-${VERSION}.linux_arm64.zip\", \"md5\": \"$(cat ci/packages/info-linux_arm64.json | jq -r .plugin.md5)\" }, \"linux-arm\": { - \"url\": \"https://storage.googleapis.com/${{ env.GCP_BUCKET }}/${{ inputs.plugin_id }}/release/${VERSION}/linux/${{ inputs.plugin_id }}-${VERSION}.linux_arm.zip\", + \"url\": \"https://storage.googleapis.com/${{ env.GCP_BUCKET }}/$PLUGIN_ID/release/${VERSION}/linux/$PLUGIN_ID-${VERSION}.linux_arm.zip\", \"md5\": \"$(cat ci/packages/info-linux_arm.json | jq -r .plugin.md5)\" }, \"windows-amd64\": { - \"url\": \"https://storage.googleapis.com/${{ env.GCP_BUCKET }}/${{ inputs.plugin_id }}/release/${VERSION}/windows/${{ inputs.plugin_id }}-${VERSION}.windows_amd64.zip\", + \"url\": \"https://storage.googleapis.com/${{ env.GCP_BUCKET }}/$PLUGIN_ID/release/${VERSION}/windows/$PLUGIN_ID-${VERSION}.windows_amd64.zip\", \"md5\": \"$(cat ci/packages/info-windows_amd64.json | jq -r .plugin.md5)\" }, \"darwin-amd64\": { - \"url\": \"https://storage.googleapis.com/${{ env.GCP_BUCKET }}/${{ inputs.plugin_id }}/release/${VERSION}/darwin/${{ inputs.plugin_id }}-${VERSION}.darwin_amd64.zip\", + \"url\": \"https://storage.googleapis.com/${{ env.GCP_BUCKET }}/$PLUGIN_ID/release/${VERSION}/darwin/$PLUGIN_ID-${VERSION}.darwin_amd64.zip\", \"md5\": \"$(cat ci/packages/info-darwin_amd64.json | jq -r .plugin.md5)\" }, \"darwin-arm64\": { - \"url\": \"https://storage.googleapis.com/${{ env.GCP_BUCKET }}/${{ inputs.plugin_id }}/release/${VERSION}/darwin/${{ inputs.plugin_id }}-${VERSION}.darwin_arm64.zip\", + \"url\": \"https://storage.googleapis.com/${{ env.GCP_BUCKET }}/$PLUGIN_ID/release/${VERSION}/darwin/$PLUGIN_ID-${VERSION}.darwin_arm64.zip\", \"md5\": \"$(cat ci/packages/info-darwin_arm64.json | jq -r .plugin.md5)\" } } @@ -257,7 +261,7 @@ jobs: \"url\": \"https://github.com/grafana/grafana/tree/main/${{ steps.get_dir.outputs.dir }}\", \"download\": { \"any\": { - \"url\": \"https://storage.googleapis.com/${{ env.GCP_BUCKET }}/${{ inputs.plugin_id }}/release/${VERSION}/any/${{ inputs.plugin_id }}-${VERSION}.any.zip\", + \"url\": \"https://storage.googleapis.com/${{ env.GCP_BUCKET }}/$PLUGIN_ID/release/${VERSION}/any/$PLUGIN_ID-${VERSION}.any.zip\", \"md5\": \"$(cat ci/packages/info-any.json | jq -r .plugin.md5)\" } } diff --git a/.github/workflows/create-next-release-branch.yml b/.github/workflows/create-next-release-branch.yml index 8fc01cd442d..1107842a765 100644 --- a/.github/workflows/create-next-release-branch.yml +++ b/.github/workflows/create-next-release-branch.yml @@ -46,7 +46,7 @@ jobs: private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} - name: Create release branch id: branch - uses: grafana/grafana-github-actions-go/bump-release@main + uses: grafana/grafana-github-actions-go/bump-release@main # zizmor: ignore[unpinned-uses] with: ownerRepo: ${{ inputs.ownerRepo }} source: ${{ inputs.source }} diff --git a/.github/workflows/create-security-patch-from-security-mirror.yml b/.github/workflows/create-security-patch-from-security-mirror.yml index f85239241ad..7499d925236 100644 --- a/.github/workflows/create-security-patch-from-security-mirror.yml +++ b/.github/workflows/create-security-patch-from-security-mirror.yml @@ -17,7 +17,7 @@ on: jobs: trigger_downstream_create_security_patch: concurrency: create-patch-${{ github.ref_name }} - uses: grafana/security-patch-actions/.github/workflows/create-patch.yml@main + uses: grafana/security-patch-actions/.github/workflows/create-patch.yml@main # zizmor: ignore[unpinned-uses] if: github.repository == 'grafana/grafana-security-mirror' with: repo: "${{ github.repository }}" @@ -25,5 +25,4 @@ jobs: patch_ref: "${{ github.base_ref }}" # this is the target branch name, Ex: "main" patch_repo: "grafana/grafana-security-patches" patch_prefix: "${{ github.event.pull_request.number }}" - secrets: inherit - + secrets: inherit # zizmor: ignore[secrets-inherit] diff --git a/.github/workflows/dashboards-issue-add-label.yml b/.github/workflows/dashboards-issue-add-label.yml index c3157a05afb..4072f062fa7 100644 --- a/.github/workflows/dashboards-issue-add-label.yml +++ b/.github/workflows/dashboards-issue-add-label.yml @@ -22,7 +22,7 @@ jobs: steps: - name: "Get vault secrets" id: vault-secrets - uses: grafana/shared-workflows/actions/get-vault-secrets@main + uses: grafana/shared-workflows/actions/get-vault-secrets@main # zizmor: ignore[unpinned-uses] with: # Secrets placed in the ci/repo/grafana/grafana/plugins_platform_issue_commands_github_bot path in Vault repo_secrets: | diff --git a/.github/workflows/deploy-pr-preview.yml b/.github/workflows/deploy-pr-preview.yml index bfad92ebf73..5c8f4733eeb 100644 --- a/.github/workflows/deploy-pr-preview.yml +++ b/.github/workflows/deploy-pr-preview.yml @@ -12,7 +12,7 @@ on: jobs: deploy-pr-preview: if: "!github.event.pull_request.head.repo.fork" - uses: grafana/writers-toolkit/.github/workflows/deploy-preview.yml@main + uses: grafana/writers-toolkit/.github/workflows/deploy-preview.yml@main # zizmor: ignore[unpinned-uses] with: branch: ${{ github.head_ref }} event_number: ${{ github.event.number }} diff --git a/.github/workflows/detect-breaking-changes-levitate.yml b/.github/workflows/detect-breaking-changes-levitate.yml index 70c823483d9..a2517d5e0a2 100644 --- a/.github/workflows/detect-breaking-changes-levitate.yml +++ b/.github/workflows/detect-breaking-changes-levitate.yml @@ -6,9 +6,7 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true -permissions: - contents: read - id-token: write +permissions: {} on: pull_request: @@ -24,12 +22,16 @@ jobs: defaults: run: working-directory: './pr' + permissions: + contents: read + id-token: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: path: './pr' - - uses: actions/setup-node@v4 + persist-credentials: false + - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e with: node-version: 22.11.0 @@ -67,17 +69,20 @@ jobs: buildBase: name: Build Base packages artifacts runs-on: ubuntu-latest + permissions: + contents: read + id-token: write defaults: run: working-directory: './base' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: path: './base' ref: ${{ github.event.pull_request.base.ref }} - - uses: actions/setup-node@v4 + - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e with: node-version: 22.11.0 @@ -123,8 +128,8 @@ jobs: id-token: 'write' steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e with: node-version: 22.11.0 @@ -145,14 +150,14 @@ jobs: run: unzip -j base_built_packages.zip -d ./base && rm base_built_packages.zip - id: 'auth' - uses: 'google-github-actions/auth@v2' + uses: 'google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f' with: workload_identity_provider: ${{ secrets.WIF_PROVIDER }} service_account: ${{ secrets.LEVITATE_SA }} project_id: 'grafanalabs-global' - name: 'Set up Cloud SDK' - uses: 'google-github-actions/setup-gcloud@v2' + uses: 'google-github-actions/setup-gcloud@6189d56e4096ee891640bb02ac264be376592d6a' with: version: '>= 363.0.0' project_id: 'grafanalabs-global' @@ -180,6 +185,9 @@ jobs: name: Report breaking changes in PR comment runs-on: ubuntu-latest needs: ['Detect'] + permissions: + contents: read + id-token: write steps: - name: "Generate token" @@ -189,7 +197,7 @@ jobs: app_id: ${{ secrets.GRAFANA_PR_AUTOMATION_APP_ID }} private_key: ${{ secrets.GRAFANA_PR_AUTOMATION_APP_PEM }} - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: 'Download artifact' uses: actions/download-artifact@v4 @@ -238,7 +246,7 @@ jobs: # Comment on the PR - name: Comment on PR if: steps.levitate-run.outputs.exit_code == 1 - uses: marocchino/sticky-pull-request-comment@v2 + uses: marocchino/sticky-pull-request-comment@52423e01640425a022ef5fd42c6fb5f633a02728 with: header: levitate-breaking-change-comment number: ${{ github.event.pull_request.number }} @@ -255,7 +263,7 @@ jobs: # Remove comment from the PR (no more breaking changes) - name: Remove comment from PR if: steps.levitate-run.outputs.exit_code == 0 - uses: marocchino/sticky-pull-request-comment@v2 + uses: marocchino/sticky-pull-request-comment@52423e01640425a022ef5fd42c6fb5f633a02728 with: header: levitate-breaking-change-comment number: ${{ github.event.pull_request.number }} diff --git a/.github/workflows/documentation-ci.yml b/.github/workflows/documentation-ci.yml index d27f16c9298..9b8bc7dc53b 100644 --- a/.github/workflows/documentation-ci.yml +++ b/.github/workflows/documentation-ci.yml @@ -10,10 +10,10 @@ jobs: container: image: grafana/vale:latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: persist-credentials: false - - uses: grafana/writers-toolkit/vale-action@vale-action/v1 + - uses: grafana/writers-toolkit/vale-action@vale-action/v1 # zizmor: ignore[unpinned-uses] with: filter: '.Name in ["Grafana.GrafanaCom", "Grafana.WordList", "Grafana.Spelling", "Grafana.ProductPossessives"]' token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ephemeral-instances-pr-comment.yml b/.github/workflows/ephemeral-instances-pr-comment.yml index be07b7ef6ac..e60eca1bad4 100644 --- a/.github/workflows/ephemeral-instances-pr-comment.yml +++ b/.github/workflows/ephemeral-instances-pr-comment.yml @@ -41,12 +41,13 @@ jobs: private_key: ${{ secrets.EI_APP_PRIVATE_KEY }} - name: Checkout ephemeral instances repository - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: repository: grafana/ephemeral-grafana-instances-github-action token: ${{ steps.generate_token.outputs.token }} ref: main path: ephemeral + persist-credentials: false - name: build and deploy ephemeral instance uses: ./ephemeral diff --git a/.github/workflows/feature-toggles-ci.yml b/.github/workflows/feature-toggles-ci.yml index a6c9f5c52dc..7aa1dbd0b42 100644 --- a/.github/workflows/feature-toggles-ci.yml +++ b/.github/workflows/feature-toggles-ci.yml @@ -11,12 +11,12 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: persist-credentials: false - name: Set up Go - uses: actions/setup-go@v5 + uses: actions/setup-go@111f3307d8850f501ac008e886eec1fd1932a34 with: go-version-file: 'go.mod' cache: true diff --git a/.github/workflows/frontend-lint.yml b/.github/workflows/frontend-lint.yml index 148838465fa..42533e0127d 100644 --- a/.github/workflows/frontend-lint.yml +++ b/.github/workflows/frontend-lint.yml @@ -6,17 +6,20 @@ on: - main - release-*.*.* -permissions: - contents: read - id-token: write +permissions: {} jobs: lint-frontend-verify-i18n: name: Verify i18n runs-on: ubuntu-latest + permissions: + contents: read + id-token: write steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + persist-credentials: false + - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e with: node-version-file: '.nvmrc' cache: 'yarn' @@ -34,14 +37,17 @@ jobs: exit 1 fi lint-frontend-prettier: + permissions: + contents: read + id-token: write # Run this workflow only for PRs from forks; if it gets merged into `main` or `release-*`, # the `lint-frontend-prettier-enterprise` workflow will run instead if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true name: Lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e with: node-version-file: '.nvmrc' cache: 'yarn' @@ -50,13 +56,16 @@ jobs: - run: yarn run prettier:check - run: yarn run lint lint-frontend-prettier-enterprise: + permissions: + contents: read + id-token: write # Run this workflow for non-PR events (like pushes to `main` or `release-*`) OR for internal PRs (PRs not from forks) if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false name: Lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e with: node-version-file: '.nvmrc' cache: 'yarn' @@ -69,14 +78,17 @@ jobs: - run: yarn run prettier:check - run: yarn run lint lint-frontend-typecheck: + permissions: + contents: read + id-token: write # Run this workflow only for PRs from forks; if it gets merged into `main` or `release-*`, # the `lint-frontend-typecheck-enterprise` workflow will run instead if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true name: Typecheck runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e with: node-version-file: '.nvmrc' cache: 'yarn' @@ -84,13 +96,16 @@ jobs: - run: yarn install --immutable --check-cache - run: yarn run typecheck lint-frontend-typecheck-enterprise: + permissions: + contents: read + id-token: write # Run this workflow for non-PR events (like pushes to `main` or `release-*`) OR for internal PRs (PRs not from forks) if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false name: Typecheck runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e with: node-version-file: '.nvmrc' cache: 'yarn' @@ -102,11 +117,14 @@ jobs: - run: yarn install --immutable --check-cache - run: yarn run typecheck lint-frontend-betterer: + permissions: + contents: read + id-token: write name: Betterer runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e with: node-version-file: '.nvmrc' cache: 'yarn' diff --git a/.github/workflows/github-release.yml b/.github/workflows/github-release.yml index 8cbe3030ffe..211c9d90fd2 100644 --- a/.github/workflows/github-release.yml +++ b/.github/workflows/github-release.yml @@ -40,7 +40,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Create GitHub release (manually invoked) - uses: grafana/grafana-github-actions-go/github-release@main + uses: grafana/grafana-github-actions-go/github-release@main # zizmor: ignore[unpinned-uses] with: token: ${{ secrets.GITHUB_TOKEN }} version: ${{ inputs.version }} diff --git a/.github/workflows/go-lint.yml b/.github/workflows/go-lint.yml index d6ae5ee85ee..0c65982f9c8 100644 --- a/.github/workflows/go-lint.yml +++ b/.github/workflows/go-lint.yml @@ -16,13 +16,15 @@ jobs: lint-go: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + persist-credentials: false + - uses: actions/setup-go@111f3307d8850f501ac008e886eec1fd1932a34 with: go-version-file: ./go.mod - run: make gen-go - name: golangci-lint - uses: golangci/golangci-lint-action@v7 + uses: golangci/golangci-lint-action@1481404843c368bc19ca9406f87d6e0fc97bdcfd with: version: v2.0.2 args: | diff --git a/.github/workflows/i18n-crowdin-create-tasks.yml b/.github/workflows/i18n-crowdin-create-tasks.yml index f12ca3ff7a2..dbe2be9a5b8 100644 --- a/.github/workflows/i18n-crowdin-create-tasks.yml +++ b/.github/workflows/i18n-crowdin-create-tasks.yml @@ -11,10 +11,12 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + persist-credentials: false - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e with: node-version-file: '.nvmrc' diff --git a/.github/workflows/i18n-crowdin-download.yml b/.github/workflows/i18n-crowdin-download.yml index e0c3c50b9bb..d287c7ee06f 100644 --- a/.github/workflows/i18n-crowdin-download.yml +++ b/.github/workflows/i18n-crowdin-download.yml @@ -22,14 +22,15 @@ jobs: app_id: ${{ secrets.GRAFANA_PR_AUTOMATION_APP_ID }} private_key: ${{ secrets.GRAFANA_PR_AUTOMATION_APP_PEM }} - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: ref: ${{ github.head_ref }} token: ${{ steps.generate_token.outputs.token }} + persist-credentials: false - name: Download sources id: crowdin-download - uses: crowdin/github-action@v2 + uses: crowdin/github-action@b8012bd5491b8aa8578b73ab5b5f5e7c94aaa6e2 with: upload_sources: false upload_translations: false @@ -72,7 +73,7 @@ jobs: GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} - name: Get project board ID - uses: octokit/graphql-action@v2.x + uses: octokit/graphql-action@51bf543c240dcd14761320e2efc625dc32ec0d32 id: get-project-id if: steps.crowdin-download.outputs.pull_request_url with: @@ -92,7 +93,7 @@ jobs: GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} - name: Add to project board - uses: octokit/graphql-action@v2.x + uses: octokit/graphql-action@51bf543c240dcd14761320e2efc625dc32ec0d32 if: steps.crowdin-download.outputs.pull_request_url with: projectid: ${{ fromJson(steps.get-project-id.outputs.data).organization.projectV2.id }} @@ -109,7 +110,7 @@ jobs: GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} - name: Run auto-milestone - uses: grafana/grafana-github-actions-go/auto-milestone@main + uses: grafana/grafana-github-actions-go/auto-milestone@main # zizmor: ignore[unpinned-uses] if: steps.crowdin-download.outputs.pull_request_url with: pr: ${{ steps.crowdin-download.outputs.pull_request_number }} @@ -117,7 +118,7 @@ jobs: - name: Get vault secrets id: vault-secrets - uses: grafana/shared-workflows/actions/get-vault-secrets@main + uses: grafana/shared-workflows/actions/get-vault-secrets@main # zizmor: ignore[unpinned-uses] with: # Secrets placed in ci/repo/grafana/grafana/grafana-pr-approver repo_secrets: | diff --git a/.github/workflows/i18n-crowdin-upload.yml b/.github/workflows/i18n-crowdin-upload.yml index 39a89c5aad2..d7d1a130fcd 100644 --- a/.github/workflows/i18n-crowdin-upload.yml +++ b/.github/workflows/i18n-crowdin-upload.yml @@ -14,10 +14,12 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + persist-credentials: false - name: Upload sources - uses: crowdin/github-action@v2 + uses: crowdin/github-action@b8012bd5491b8aa8578b73ab5b5f5e7c94aaa6e2 with: upload_sources: true upload_sources_args: '--dest=public/locales/en-US/grafana.json' diff --git a/.github/workflows/issue-opened.yml b/.github/workflows/issue-opened.yml index b478f9647e7..91767a5310b 100644 --- a/.github/workflows/issue-opened.yml +++ b/.github/workflows/issue-opened.yml @@ -10,14 +10,15 @@ on: concurrency: group: issue-opened-${{ github.event.issue.number }} -permissions: - contents: read - id-token: write +permissions: {} jobs: main: runs-on: ubuntu-latest if: github.repository == 'grafana/grafana' + permissions: + contents: read + id-token: write steps: - name: Checkout Actions @@ -26,6 +27,7 @@ jobs: repository: "grafana/grafana-github-actions" path: ./actions ref: main + persist-credentials: false - name: Install Actions run: npm install --production --prefix ./actions @@ -37,7 +39,7 @@ jobs: - name: "Get vault secrets" id: vault-secrets - uses: grafana/shared-workflows/actions/get-vault-secrets@main + uses: grafana/shared-workflows/actions/get-vault-secrets@main # zizmor: ignore[unpinned-uses] with: # Secrets placed in the ci/repo/grafana/grafana/plugins_platform_issue_commands_github_bot path in Vault repo_secrets: | @@ -60,13 +62,16 @@ jobs: auto-triage: needs: [main] + permissions: + contents: read + id-token: write if: github.repository == 'grafana/grafana' && github.event.issue.author_association != 'MEMBER' && github.event.issue.author_association != 'OWNER' runs-on: ubuntu-latest steps: - name: "Get vault secrets" id: vault-secrets - uses: grafana/shared-workflows/actions/get-vault-secrets@main + uses: grafana/shared-workflows/actions/get-vault-secrets@main # zizmor: ignore[unpinned-uses] with: # Secrets placed in the ci/repo/grafana/grafana/plugins_platform_issue_triager path in Vault repo_secrets: | @@ -87,7 +92,7 @@ jobs: - name: Send issue to the auto triager action id: auto_triage - uses: grafana/auto-triager@main + uses: grafana/auto-triager@main # zizmor: ignore[unpinned-uses] with: token: ${{ steps.generate_token.outputs.token }} issue_number: ${{ github.event.issue.number }} diff --git a/.github/workflows/lint-build-docs.yml b/.github/workflows/lint-build-docs.yml index ecd6151e228..1477cb2797a 100644 --- a/.github/workflows/lint-build-docs.yml +++ b/.github/workflows/lint-build-docs.yml @@ -22,10 +22,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + persist-credentials: false - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e with: node-version: '22.11.0' cache: 'yarn' diff --git a/.github/workflows/metrics-collector.yml b/.github/workflows/metrics-collector.yml index 9238b034433..281dc6fb350 100644 --- a/.github/workflows/metrics-collector.yml +++ b/.github/workflows/metrics-collector.yml @@ -43,6 +43,7 @@ jobs: repository: "grafana/grafana-github-actions" path: ./actions ref: main + persist-credentials: false - name: Install Actions run: npm install --production --prefix ./actions - name: Run metrics collector diff --git a/.github/workflows/migrate-prs.yml b/.github/workflows/migrate-prs.yml index 31bb8f9f9da..c40a34a6ebb 100644 --- a/.github/workflows/migrate-prs.yml +++ b/.github/workflows/migrate-prs.yml @@ -51,7 +51,7 @@ jobs: app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} - name: Migrate PRs - uses: grafana/grafana-github-actions-go/migrate-open-prs@main + uses: grafana/grafana-github-actions-go/migrate-open-prs@main # zizmor: ignore[unpinned-uses] with: token: ${{ steps.generate_token.outputs.token }} ownerRepo: ${{ inputs.ownerRepo }} diff --git a/.github/workflows/milestone.yml b/.github/workflows/milestone.yml deleted file mode 100644 index f686dee7d55..00000000000 --- a/.github/workflows/milestone.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Close Milestone -on: - workflow_dispatch: - inputs: - version_input: - description: 'The version to be released please respect: major.minor.patch, major.minor.patch-preview or major.minor.patch-preview format. example: 7.4.3, 7.4.3-preview or 7.4.3-preview1' - required: true -jobs: - call-remove-milestone: - uses: grafana/grafana/.github/workflows/remove-milestone.yml@main - with: - version_call: ${{ github.event.inputs.version_input }} - secrets: inherit - call-close-milestone: - uses: grafana/grafana/.github/workflows/close-milestone.yml@main - with: - version_call: ${{ github.event.inputs.version_input }} - secrets: inherit - needs: call-remove-milestone diff --git a/.github/workflows/pr-backend-coverage.yml b/.github/workflows/pr-backend-coverage.yml index b210abcbd91..4d95497e3ec 100644 --- a/.github/workflows/pr-backend-coverage.yml +++ b/.github/workflows/pr-backend-coverage.yml @@ -23,9 +23,11 @@ jobs: runs-on: ubuntu-latest-8-cores steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + persist-credentials: false - name: Setup Go - uses: actions/setup-go@v5 + uses: actions/setup-go@111f3307d8850f501ac008e886eec1fd1932a34 with: go-version-file: go.mod cache: true diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index b3bd63fa0d8..68317f57a0e 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -36,6 +36,7 @@ jobs: repository: "grafana/grafana-github-actions" path: ./actions ref: main + persist-credentials: false - name: Install Actions run: npm install --production --prefix ./actions - name: Run PR Checks diff --git a/.github/workflows/pr-codeql-analysis-javascript.yml b/.github/workflows/pr-codeql-analysis-javascript.yml index d24b7db9671..43457ad4af0 100644 --- a/.github/workflows/pr-codeql-analysis-javascript.yml +++ b/.github/workflows/pr-codeql-analysis-javascript.yml @@ -20,11 +20,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. fetch-depth: 2 + persist-credentials: false # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/pr-codeql-analysis-python.yml b/.github/workflows/pr-codeql-analysis-python.yml index 4e8b1b14747..992a886497d 100644 --- a/.github/workflows/pr-codeql-analysis-python.yml +++ b/.github/workflows/pr-codeql-analysis-python.yml @@ -18,11 +18,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. fetch-depth: 2 + persist-credentials: false # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/pr-commands.yml b/.github/workflows/pr-commands.yml index f392752ee8e..db93ad7e70b 100644 --- a/.github/workflows/pr-commands.yml +++ b/.github/workflows/pr-commands.yml @@ -35,6 +35,7 @@ jobs: repository: "grafana/grafana-github-actions" path: ./actions ref: main + persist-credentials: false - name: Install Actions run: npm install --production --prefix ./actions - name: "Generate token" diff --git a/.github/workflows/pr-dependabot-update-go-workspace.yml b/.github/workflows/pr-dependabot-update-go-workspace.yml index 9fd36a20082..48a005ba6f7 100644 --- a/.github/workflows/pr-dependabot-update-go-workspace.yml +++ b/.github/workflows/pr-dependabot-update-go-workspace.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Retrieve GitHub App secrets id: get-secrets - uses: grafana/shared-workflows/actions/get-vault-secrets@get-vault-secrets-v1.0.1 + uses: grafana/shared-workflows/actions/get-vault-secrets@get-vault-secrets-v1.0.1 # zizmor: ignore[unpinned-uses] with: repo_secrets: | APP_ID=grafana-go-workspace-bot:app-id @@ -37,14 +37,15 @@ jobs: private-key: ${{ env.PRIVATE_KEY }} - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: repository: ${{ github.event.pull_request.head.repo.full_name }} ref: ${{ github.event.pull_request.head.ref }} token: ${{ steps.generate_token.outputs.token }} + persist-credentials: false - name: Set go version - uses: actions/setup-go@v4 + uses: actions/setup-go@19bb51245e9c80abacb2e91cc42b33fa478b8639 with: go-version-file: go.mod @@ -65,4 +66,4 @@ jobs: echo "Committing and pushing workspace changes" git commit -a -m "update workspace" git push origin $BRANCH_NAME - fi \ No newline at end of file + fi diff --git a/.github/workflows/pr-e2e-tests.yml b/.github/workflows/pr-e2e-tests.yml index b7c1f7c8b1b..74e64e5bf38 100644 --- a/.github/workflows/pr-e2e-tests.yml +++ b/.github/workflows/pr-e2e-tests.yml @@ -18,15 +18,16 @@ jobs: outputs: artifact: ${{ steps.artifact.outputs.artifact }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: repository: 'grafana/grafana-build' ref: 'main' - - uses: actions/checkout@v4 + persist-credentials: false + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: path: ./grafana - run: echo "GRAFANA_GO_VERSION=$(grep "go 1." grafana/go.work | cut -d\ -f2)" >> "$GITHUB_ENV" - - uses: dagger/dagger-for-github@8.0.0 + - uses: dagger/dagger-for-github@e47aba410ef9bb9ed81a4d2a97df31061e5e842e with: verb: run args: go run ./cmd artifacts -a targz:grafana:linux/amd64 --grafana-dir=grafana --go-version=${GRAFANA_GO_VERSION} > out.txt diff --git a/.github/workflows/pr-frontend-unit-tests.yml b/.github/workflows/pr-frontend-unit-tests.yml index beced0ac371..e26e3847f8c 100644 --- a/.github/workflows/pr-frontend-unit-tests.yml +++ b/.github/workflows/pr-frontend-unit-tests.yml @@ -6,12 +6,13 @@ on: - main - release-*.*.* -permissions: - contents: read - id-token: write +permissions: {} jobs: frontend-unit-tests: + permissions: + contents: read + id-token: write # Run this workflow only for PRs from forks; if it gets merged into `main` or `release-*`, # the `frontend-unit-tests-enterprise` workflow will run instead if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true @@ -22,8 +23,10 @@ jobs: matrix: chunk: [1, 2, 3, 4, 5, 6, 7, 8] steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + persist-credentials: false + - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e with: node-version-file: '.nvmrc' cache: 'yarn' @@ -36,6 +39,9 @@ jobs: TEST_SHARD_TOTAL: 8 frontend-unit-tests-enterprise: + permissions: + contents: read + id-token: write # Run this workflow for non-PR events (like pushes to `main` or `release-*`) OR for internal PRs (PRs not from forks) if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false runs-on: ubuntu-latest-8-cores @@ -45,8 +51,8 @@ jobs: matrix: chunk: [1, 2, 3, 4, 5, 6, 7, 8] steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e with: node-version-file: '.nvmrc' cache: 'yarn' diff --git a/.github/workflows/pr-go-workspace-check.yml b/.github/workflows/pr-go-workspace-check.yml index f2151dde2fb..d54178f2e35 100644 --- a/.github/workflows/pr-go-workspace-check.yml +++ b/.github/workflows/pr-go-workspace-check.yml @@ -21,10 +21,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + persist-credentials: false - name: Set go version - uses: actions/setup-go@v4 + uses: actions/setup-go@19bb51245e9c80abacb2e91cc42b33fa478b8639 with: cache: false go-version-file: go.mod @@ -42,4 +44,4 @@ jobs: exit 1 fi - name: Ensure Dockerfile contains submodule COPY commands - run: ./scripts/go-workspace/validate-dockerfile.sh \ No newline at end of file + run: ./scripts/go-workspace/validate-dockerfile.sh diff --git a/.github/workflows/pr-k8s-codegen-check.yml b/.github/workflows/pr-k8s-codegen-check.yml index ae5b6d3893a..9a72f469caa 100644 --- a/.github/workflows/pr-k8s-codegen-check.yml +++ b/.github/workflows/pr-k8s-codegen-check.yml @@ -19,10 +19,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + persist-credentials: false - name: Set go version - uses: actions/setup-go@v4 + uses: actions/setup-go@19bb51245e9c80abacb2e91cc42b33fa478b8639 with: go-version-file: go.mod @@ -36,4 +38,4 @@ jobs: git diff echo "Please run './hack/update-codegen.sh' and commit the changes." exit 1 - fi \ No newline at end of file + fi diff --git a/.github/workflows/pr-patch-check-event.yml b/.github/workflows/pr-patch-check-event.yml index 34f8fa1313c..f7605f033ce 100644 --- a/.github/workflows/pr-patch-check-event.yml +++ b/.github/workflows/pr-patch-check-event.yml @@ -13,6 +13,8 @@ on: - "v*.*.*" - "release-*" +permissions: {} + # Since this is run on a pull request, we want to apply the patches intended for the # target branch onto the source branch, to verify compatibility before merging. jobs: diff --git a/.github/workflows/pr-test-integration.yml b/.github/workflows/pr-test-integration.yml index 518e1150531..b49183889d5 100644 --- a/.github/workflows/pr-test-integration.yml +++ b/.github/workflows/pr-test-integration.yml @@ -17,9 +17,11 @@ jobs: runs-on: ubuntu-latest-8-cores steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + persist-credentials: false - name: Setup Go - uses: actions/setup-go@v5 + uses: actions/setup-go@111f3307d8850f501ac008e886eec1fd1932a34 with: go-version-file: go.mod cache: true @@ -45,9 +47,9 @@ jobs: - 3306:3306 steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: Setup Go - uses: actions/setup-go@v5 + uses: actions/setup-go@111f3307d8850f501ac008e886eec1fd1932a34 with: go-version-file: go.mod cache: true @@ -70,9 +72,9 @@ jobs: - 5432:5432 steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: Setup Go - uses: actions/setup-go@v5 + uses: actions/setup-go@111f3307d8850f501ac008e886eec1fd1932a34 with: go-version-file: go.mod cache: true diff --git a/.github/workflows/publish-kinds-next.yml b/.github/workflows/publish-kinds-next.yml index 4aed6cf36c3..ed290abbd2d 100644 --- a/.github/workflows/publish-kinds-next.yml +++ b/.github/workflows/publish-kinds-next.yml @@ -29,12 +29,13 @@ jobs: runs-on: "ubuntu-latest" steps: - name: "Checkout Grafana repo" - uses: "actions/checkout@v4" + uses: "actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683" with: fetch-depth: 0 + persist-credentials: false - name: "Setup Go" - uses: "actions/setup-go@v4" + uses: "actions/setup-go@19bb51245e9c80abacb2e91cc42b33fa478b8639" with: go-version-file: go.mod diff --git a/.github/workflows/publish-kinds-release.yml b/.github/workflows/publish-kinds-release.yml index 691cdff3867..1e60f72ed79 100644 --- a/.github/workflows/publish-kinds-release.yml +++ b/.github/workflows/publish-kinds-release.yml @@ -31,13 +31,14 @@ jobs: runs-on: "ubuntu-latest" steps: - name: "Checkout Grafana repo" - uses: "actions/checkout@v4" + uses: "actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683" with: # required for the `grafana/grafana-github-actions/has-matching-release-tag` action to work fetch-depth: 0 + persist-credentials: false - name: "Setup Go" - uses: "actions/setup-go@v4" + uses: "actions/setup-go@19bb51245e9c80abacb2e91cc42b33fa478b8639" with: go-version-file: go.mod @@ -45,7 +46,7 @@ jobs: run: go run .github/workflows/scripts/kinds/verify-kinds.go - name: "Checkout Actions library" - uses: "actions/checkout@v4" + uses: "actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683" with: repository: "grafana/grafana-github-actions" path: "./actions" diff --git a/.github/workflows/publish-technical-documentation-next.yml b/.github/workflows/publish-technical-documentation-next.yml index 6b2cd7489b3..9d67f1724c5 100644 --- a/.github/workflows/publish-technical-documentation-next.yml +++ b/.github/workflows/publish-technical-documentation-next.yml @@ -15,7 +15,7 @@ jobs: id-token: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: grafana/writers-toolkit/publish-technical-documentation@publish-technical-documentation/v1 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: grafana/writers-toolkit/publish-technical-documentation@publish-technical-documentation/v1 # zizmor: ignore[unpinned-uses] with: website_directory: content/docs/grafana/next diff --git a/.github/workflows/publish-technical-documentation-release.yml b/.github/workflows/publish-technical-documentation-release.yml index 57d779660c5..d5f597686ca 100644 --- a/.github/workflows/publish-technical-documentation-release.yml +++ b/.github/workflows/publish-technical-documentation-release.yml @@ -17,10 +17,11 @@ jobs: id-token: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: fetch-depth: 0 - - uses: grafana/writers-toolkit/publish-technical-documentation-release@publish-technical-documentation-release/v2 + persist-credentials: false + - uses: grafana/writers-toolkit/publish-technical-documentation-release@publish-technical-documentation-release/v2 # zizmor: ignore[unpinned-uses] with: release_tag_regexp: "^v(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$" release_branch_regexp: "^release-(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$" diff --git a/.github/workflows/release-comms.yml b/.github/workflows/release-comms.yml index 008b6552883..e5532ca12ca 100644 --- a/.github/workflows/release-comms.yml +++ b/.github/workflows/release-comms.yml @@ -30,18 +30,16 @@ jobs: release_branch: ${{ steps.output.outputs.release_branch }} dry_run: ${{ steps.output.outputs.dry_run }} latest: ${{ steps.output.outputs.latest }} + env: + HEAD_REF: ${{ github.head_ref }} + DRY_RUN: ${{ inputs.dry_run }} + LATEST: ${{ inputs.latest && '1' || '0' }} + VERSION: ${{ inputs.version }} runs-on: ubuntu-latest steps: - # The github-release action expects a `LATEST` value of a string of either '1' or '0' - - if: ${{ github.event_name == 'workflow_dispatch' }} - run: | - echo setting up GITHUB_ENV for ${{ github.event_name }} - echo "VERSION=${{ inputs.version }}" >> $GITHUB_ENV - echo "DRY_RUN=${{ inputs.dry_run }}" >> $GITHUB_ENV - echo "LATEST=${{ inputs.latest && '1' || '0' }}" >> $GITHUB_ENV - if: ${{ github.event.pull_request.merged == true && startsWith(github.head_ref, 'release/') }} run: | - echo "VERSION=$(echo ${{ github.head_ref }} | sed -e 's/release\/.*\//v/g')" >> $GITHUB_ENV + echo "VERSION=$(echo ${HEAD_REF} | sed -e 's/release\/.*\//v/g')" >> $GITHUB_ENV echo "DRY_RUN=${{ contains(github.event.pull_request.labels.*.name, 'release/dry-run') }}" >> $GITHUB_ENV echo "LATEST=${{ contains(github.event.pull_request.labels.*.name, 'release/latest') && '1' || '0' }}" >> $GITHUB_ENV - id: output @@ -120,7 +118,10 @@ jobs: post_on_slack: needs: setup runs-on: ubuntu-latest + env: + DRY_RUN: ${{ needs.setup.outputs.dry_run }} + VERSION: ${{ needs.setup.outputs.version }} steps: - run: | - echo announce on slack that ${{ needs.setup.outputs.version }} has been released - echo dry run: ${{ needs.setup.outputs.dry_run }} + echo announce on slack that $VERSION has been released + echo dry run: $DRY_RUN diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml index 1234cdc75ef..4d66838b7ec 100644 --- a/.github/workflows/release-pr.yml +++ b/.github/workflows/release-pr.yml @@ -33,12 +33,13 @@ on: default: false type: boolean -permissions: - contents: write - pull-requests: write +permissions: {} jobs: push-changelog-to-main: + permissions: + contents: write + pull-requests: write name: Create PR to main to update the changelog uses: ./.github/workflows/changelog.yml with: @@ -50,41 +51,44 @@ jobs: secrets: GRAFANA_DELIVERY_BOT_APP_ID: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} GRAFANA_DELIVERY_BOT_APP_PEM: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} + create-prs: + permissions: + contents: write + pull-requests: write name: Create Release PR runs-on: ubuntu-latest if: github.repository == 'grafana/grafana' + env: + VERSION: ${{ inputs.version }} + LATEST: ${{ inputs.latest }} + DRY_RUN: ${{ inputs.dry_run }} steps: - - name: Generate bot token - id: generate_token - uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 - with: - app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} - private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} - name: Get release branch id: branch - uses: grafana/grafana-github-actions-go/latest-release-branch@main + uses: grafana/grafana-github-actions-go/latest-release-branch@main # zizmor: ignore[unpinned-uses] with: - token: ${{ steps.generate_token.outputs.token }} + token: ${{ secrets.GITHUB_TOKEN }} ownerRepo: 'grafana/grafana' pattern: ${{ inputs.target }} - name: Checkout Grafana - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: ref: ${{ steps.branch.outputs.branch }} - fetch-depth: 0 fetch-tags: true - token: ${{ steps.generate_token.outputs.token }} + token: ${{ secrets.GITHUB_TOKEN }} + persist-credentials: false - name: Checkout Grafana (main) - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: ref: main fetch-depth: '0' fetch-tags: 'false' path: .grafana-main - token: ${{ steps.generate_token.outputs.token }} + token: ${{ secrets.GITHUB_TOKEN }} + persist-credentials: false - name: Setup nodejs environment - uses: actions/setup-node@v4 + uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e with: node-version-file: .nvmrc - name: Configure git user @@ -94,37 +98,43 @@ jobs: git config --local --add --bool push.autoSetupRemote true - name: Create branch - run: git checkout -b "release/${{ github.run_id }}/${{ inputs.version }}" + run: git checkout -b "release/${{ github.run_id }}/$VERSION" + - name: Generate changelog token + id: generate_changelog_token + uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 + with: + app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} + private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} - name: Generate changelog id: changelog uses: ./.grafana-main/.github/workflows/actions/changelog with: - github_token: ${{ steps.generate_token.outputs.token }} - target: v${{ inputs.version }} + github_token: ${{ steps.generate_changelog_token.outputs.token }} + target: v${{ env.VERSION }} output_file: changelog_items.md - name: Patch CHANGELOG.md run: | # Prepare CHANGELOG.md content with version delimiters ( echo - echo "# ${{ inputs.version}} ($(date '+%F'))" + echo "# $VERSION ($(date '+%F'))" echo cat changelog_items.md ) > CHANGELOG.part # Check if a version exists in the changelog - if grep -q "" + echo "" cat CHANGELOG.part - echo "" + echo "" cat CHANGELOG.md ) > CHANGELOG.tmp mv CHANGELOG.tmp CHANGELOG.md @@ -147,35 +157,45 @@ jobs: run: | git add package.json lerna.json yarn.lock packages public test -e e2e/test-plugins && git add e2e/test-plugins - git commit -m "Update version to ${{ inputs.version }}" + git commit -m "Update version to $VERSION" - name: Git push if: ${{ inputs.dry_run }} != true - run: git push --set-upstream origin release/${{ github.run_id }}/${{ inputs.version }} + run: git push --set-upstream origin "release/${{ github.run_id }}/$VERSION" - name: Create PR without backports if: "${{ inputs.backport == '' }}" - run: > - gh pr create \ - $( [ "x${{ inputs.latest }}" == "xtrue" ] && printf %s '-l "release/latest"') \ - -l "no-changelog" \ - --dry-run=${{ inputs.dry_run }} \ - -B "${{ steps.branch.outputs.branch }}" \ - --title "Release: ${{ inputs.version }}" \ - --body "These code changes must be merged after a release is complete" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BRANCH: ${{ steps.branch.outputs.branch }} + run: | + LATEST_FLAG="" + if [ "$LATEST" = "true" ]; then + LATEST_FLAG='-l "release/latest"' + fi + gh pr create \ + $LATEST_FLAG \ + -l "no-changelog" \ + --dry-run="$DRY_RUN" \ + -B "$BRANCH" \ + --title "Release: $VERSION" \ + --body "These code changes must be merged after a release is complete" - name: Create PR with backports if: "${{ inputs.backport != '' }}" - run: > - gh pr create \ - $( [ "x${{ inputs.latest }}" == "xtrue" ] && printf %s '-l "release/latest"') \ - -l "product-approved" \ - -l "no-changelog" \ - --dry-run=${{ inputs.dry_run }} \ - -B "${{ steps.branch.outputs.branch }}" \ - --title "Release: ${{ inputs.version }}" \ - --body "These code changes must be merged after a release is complete" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BRANCH: ${{ steps.branch.outputs.branch }} + run: | + LATEST_FLAG="" + if [ "$LATEST" = "true" ]; then + LATEST_FLAG='-l "release/latest"' + fi + gh pr create \ + $LATEST_FLAG \ + -l "product-approved" \ + -l "no-changelog" \ + --dry-run="$DRY_RUN" \ + -B "$BRANCH" \ + --title "Release: $VERSION" \ + --body "These code changes must be merged after a release is complete" diff --git a/.github/workflows/remove-milestone.yml b/.github/workflows/remove-milestone.yml deleted file mode 100644 index d41b63f1f51..00000000000 --- a/.github/workflows/remove-milestone.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: Remove milestone -on: - workflow_dispatch: - inputs: - version: - required: true - description: Needs to match, exactly, the name of a milestone - workflow_call: - inputs: - version_call: - description: Needs to match, exactly, the name of a milestone - required: true - type: string - -jobs: - config: - runs-on: "ubuntu-latest" - outputs: - has-secrets: ${{ steps.check.outputs.has-secrets }} - steps: - - name: "Check for secrets" - id: check - shell: bash - run: | - if [ -n "${{ (secrets.GRAFANA_DELIVERY_BOT_APP_ID != '' && secrets.GRAFANA_DELIVERY_BOT_APP_PEM != '') || '' }}" ]; then - echo "has-secrets=1" >> "$GITHUB_OUTPUT" - fi - - main: - needs: config - if: needs.config.outputs.has-secrets - permissions: - issues: write - runs-on: ubuntu-latest - steps: - - name: Checkout Actions - uses: actions/checkout@v4 - with: - repository: "grafana/grafana-github-actions" - path: ./actions - ref: main - - name: Install Actions - run: npm install --production --prefix ./actions - - name: "Generate token" - id: generate_token - uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 - with: - app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} - private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} - - name: Remove milestone from open issues (manually invoked) - if: ${{ github.event.inputs.version != '' }} - uses: ./actions/remove-milestone - with: - token: ${{ steps.generate_token.outputs.token }} - - name: Remove milestone from open issues (workflow invoked) - if: ${{ inputs.version_call != '' }} - uses: ./actions/remove-milestone - with: - version_call: ${{ inputs.version_call }} - token: ${{ steps.generate_token.outputs.token }} diff --git a/.github/workflows/run-dashboard-search-e2e.yml b/.github/workflows/run-dashboard-search-e2e.yml index 5766735a233..7d59bd49fb5 100644 --- a/.github/workflows/run-dashboard-search-e2e.yml +++ b/.github/workflows/run-dashboard-search-e2e.yml @@ -11,6 +11,8 @@ on: env: ARCH: linux-amd64 +permissions: {} + jobs: setup: runs-on: ubuntu-latest @@ -18,16 +20,21 @@ jobs: outputs: ini_files: ${{ steps.get_files.outputs.ini_files }} + permissions: + contents: read + id-token: write steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + persist-credentials: false - name: Pin Go version to mod file - uses: actions/setup-go@v5 + uses: actions/setup-go@111f3307d8850f501ac008e886eec1fd1932a34 with: go-version-file: 'go.mod' cache: true - run: go version - - uses: actions/setup-node@v4 + - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e with: node-version: 20 cache: 'yarn' @@ -44,7 +51,7 @@ jobs: run: yarn install --immutable - name: Install Cypress dependencies if: steps.cache-node-modules.outputs.cache-hit != 'true' - uses: cypress-io/github-action@v6 + uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f with: runTests: false - name: Cache Grafana Build and Dependencies @@ -81,10 +88,13 @@ jobs: matrix: ini_file: ${{ fromJson(needs.setup.outputs.ini_files) }} + permissions: + contents: read + id-token: write + steps: - name: Checkout repository - uses: actions/checkout@v4 - + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: Restore Cached Node Modules uses: actions/cache@v3 with: diff --git a/.github/workflows/run-e2e-suite.yml b/.github/workflows/run-e2e-suite.yml index 8c3c5851325..b99d0ea0b30 100644 --- a/.github/workflows/run-e2e-suite.yml +++ b/.github/workflows/run-e2e-suite.yml @@ -14,19 +14,26 @@ jobs: main: runs-on: ubuntu-latest-8-cores steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + persist-credentials: false - uses: actions/download-artifact@v4 with: name: ${{ inputs.package }} - - uses: dagger/dagger-for-github@8.0.0 + - uses: dagger/dagger-for-github@e47aba410ef9bb9ed81a4d2a97df31061e5e842e if: inputs.old-arch == false with: verb: run args: go run ./pkg/build/e2e --package=grafana.tar.gz --suite=${{ inputs.suite }} - - run: echo "suite=$(echo ${{ inputs.suite }} | sed 's/\//-/g')" >> $GITHUB_ENV + - name: Set suite name + id: set-suite-name + env: + SUITE: ${{ inputs.suite }} + run: | + echo "suite=$(echo $SUITE | sed 's/\//-/g')" >> $GITHUB_OUTPUT - uses: actions/upload-artifact@v4 if: ${{ always() && inputs.old-arch != true }} with: - name: e2e-${{ env.suite }}-${{github.run_number}} + name: e2e-${{ steps.set-suite-name.outputs.suite }}-${{github.run_number}} path: videos retention-days: 1 diff --git a/.github/workflows/run-schema-v2-e2e.yml b/.github/workflows/run-schema-v2-e2e.yml index 8b55aa4c430..62975992acb 100644 --- a/.github/workflows/run-schema-v2-e2e.yml +++ b/.github/workflows/run-schema-v2-e2e.yml @@ -18,13 +18,15 @@ jobs: if: github.event.pull_request.draft == false steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + persist-credentials: false - name: Pin Go version to mod file - uses: actions/setup-go@v5 + uses: actions/setup-go@111f3307d8850f501ac008e886eec1fd1932a34 with: go-version-file: 'go.mod' - run: go version - - uses: actions/setup-node@v4 + - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e with: node-version: 20 cache: 'yarn' @@ -33,7 +35,7 @@ jobs: - name: Build grafana run: make build - name: Install Cypress dependencies - uses: cypress-io/github-action@v6 + uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f with: runTests: false - name: Run dashboard scenes e2e @@ -41,4 +43,4 @@ jobs: - name: Always succeed # This is a workaround to make the job pass even if the previous step fails if: failure() - run: exit 0 \ No newline at end of file + run: exit 0 diff --git a/.github/workflows/skye-add-to-project.yml b/.github/workflows/skye-add-to-project.yml index 6788db2d95e..321f4c40b2a 100644 --- a/.github/workflows/skye-add-to-project.yml +++ b/.github/workflows/skye-add-to-project.yml @@ -30,7 +30,7 @@ jobs: steps: - name: "Get vault secrets" id: vault-secrets - uses: grafana/shared-workflows/actions/get-vault-secrets@main + uses: grafana/shared-workflows/actions/get-vault-secrets@main # zizmor: ignore[unpinned-uses] with: # Vault secret paths: # - ci/repo/grafana/grafana/plugins_platform_issue_commands_github_bot @@ -70,7 +70,7 @@ jobs: - name: Get node ID for item if: steps.check_user.outputs.user_allowed == 'true' id: get_node_id - uses: octokit/graphql-action@v2.x + uses: octokit/graphql-action@51bf543c240dcd14761320e2efc625dc32ec0d32 with: query: | query getNodeId($owner: String!, $repo: String!, $number: Int!) { @@ -91,7 +91,7 @@ jobs: # Finally, add the issue/PR to the project board - name: Add to project board if: steps.check_user.outputs.user_allowed == 'true' - uses: octokit/graphql-action@v2.x + uses: octokit/graphql-action@51bf543c240dcd14761320e2efc625dc32ec0d32 with: query: | mutation addItem($projectid: ID!, $itemid: ID!) { diff --git a/.github/workflows/storybook-verification.yml b/.github/workflows/storybook-verification.yml index efab7af6c4c..99f0c6dfe82 100644 --- a/.github/workflows/storybook-verification.yml +++ b/.github/workflows/storybook-verification.yml @@ -21,10 +21,12 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + persist-credentials: false - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e with: node-version-file: 'package.json' cache: 'yarn' @@ -33,7 +35,7 @@ jobs: run: yarn install --immutable - name: Run Storybook and E2E tests - uses: cypress-io/github-action@v6 + uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f with: browser: chrome start: yarn storybook --quiet diff --git a/.github/workflows/sync-mirror-event.yml b/.github/workflows/sync-mirror-event.yml index b1a1466fdf9..b9387e02069 100644 --- a/.github/workflows/sync-mirror-event.yml +++ b/.github/workflows/sync-mirror-event.yml @@ -10,10 +10,21 @@ on: - "v*.*.*" - "release-*" +permissions: {} + # This is run after the pull request has been merged, so we'll run against the target branch jobs: dispatch-job: runs-on: ubuntu-latest + permissions: + contents: read + actions: write + env: + REF_NAME: ${{ github.ref_name }} + REPO: ${{ github.repository }} + SENDER: ${{ github.event.sender.login }} + SHA: ${{ github.sha }} + PR_COMMIT_SHA: ${{ github.event.pull_request.head.sha }} steps: - name: "Generate token" id: generate_token @@ -28,16 +39,18 @@ jobs: with: github-token: ${{ steps.generate_token.outputs.token }} script: | + const {HEAD_REF, BASE_REF, REPO, SENDER, SHA} = process.env; + await github.rest.actions.createWorkflowDispatch({ owner: 'grafana', repo: 'security-patch-actions', workflow_id: 'mirror-branch-and-apply-patches-event.yml', ref: 'main', inputs: { - src_ref: "${{ github.ref_name }}", - src_repo: "${{ github.repository }}", - src_sha: "${{ github.sha }}", - dest_repo: "${{ github.repository }}-security-mirror", - patch_repo: "${{ github.repository }}-security-patches" + src_ref: REF_NAME, + src_repo: REPO, + src_sha: SHA, + dest_repo: REPO + "-security-mirror", + patch_repo: REPO + "-security-patches" } }) diff --git a/.github/workflows/trivy-scan.yml b/.github/workflows/trivy-scan.yml index 25104af8f29..921d25b76ff 100644 --- a/.github/workflows/trivy-scan.yml +++ b/.github/workflows/trivy-scan.yml @@ -16,9 +16,11 @@ jobs: trivy-scan: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + persist-credentials: false - name: Install Trivy - uses: aquasecurity/setup-trivy@v0.2.2 + uses: aquasecurity/setup-trivy@9ea583eb67910444b1f64abf338bd2e105a0a93d with: version: v0.56.2 cache: true diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml deleted file mode 100644 index db22ab1fb96..00000000000 --- a/.github/workflows/update-changelog.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: Update changelog -on: - workflow_dispatch: - inputs: - version: - required: true - description: 'Needs to match, exactly, the name of a milestone. The version to be released please respect: major.minor.patch, major.minor.patch-preview or major.minor.patch-preview format. example: 7.4.3, 7.4.3-preview or 7.4.3-preview1' - skip_pr: - required: false - default: "0" - skip_community_post: - required: false - default: "0" -jobs: - config: - runs-on: "ubuntu-latest" - outputs: - has-secrets: ${{ steps.check.outputs.has-secrets }} - steps: - - name: "Check for secrets" - id: check - shell: bash - run: | - if [ -n "${{ (secrets.GRAFANA_DELIVERY_BOT_APP_ID != '' && - secrets.GRAFANA_DELIVERY_BOT_APP_PEM != '' && - secrets.GRAFANA_MISC_STATS_API_KEY != '' && - secrets.GRAFANABOT_FORUM_KEY != '' - ) || '' }}" ]; then - echo "has-secrets=1" >> "$GITHUB_OUTPUT" - fi - - main: - needs: config - if: needs.config.outputs.has-secrets - runs-on: ubuntu-latest - steps: - - name: "Generate token" - id: generate_token - uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 - with: - app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} - private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} - - name: Run update changelog (manually invoked) - uses: grafana/grafana-github-actions-go/update-changelog@main - with: - token: ${{ steps.generate_token.outputs.token }} - version: ${{ inputs.version }} - metrics_api_key: ${{ secrets.GRAFANA_MISC_STATS_API_KEY }} - community_api_key: ${{ secrets.GRAFANABOT_FORUM_KEY }} - community_api_username: grafanabot - skip_pr: ${{ inputs.skip_pr }} - skip_community_post: ${{ inputs.skip_community_post }} diff --git a/.github/workflows/update-make-docs.yml b/.github/workflows/update-make-docs.yml index 49b64504bd0..7d727f284df 100644 --- a/.github/workflows/update-make-docs.yml +++ b/.github/workflows/update-make-docs.yml @@ -8,8 +8,10 @@ jobs: if: github.repository == 'grafana/grafana' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: grafana/writers-toolkit/update-make-docs@update-make-docs/v1 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + persist-credentials: false + - uses: grafana/writers-toolkit/update-make-docs@update-make-docs/v1 # zizmor: ignore[unpinned-uses] with: pr_options: > --label 'backport v10.1.x' diff --git a/.github/workflows/verify-kinds.yml b/.github/workflows/verify-kinds.yml index 88b45660d45..ce0a7a00b8a 100644 --- a/.github/workflows/verify-kinds.yml +++ b/.github/workflows/verify-kinds.yml @@ -11,12 +11,13 @@ jobs: runs-on: "ubuntu-latest" steps: - name: "Checkout Grafana repo" - uses: "actions/checkout@v4" + uses: "actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683" with: fetch-depth: 0 + persist-credentials: false - name: "Setup Go" - uses: "actions/setup-go@v4" + uses: "actions/setup-go@19bb51245e9c80abacb2e91cc42b33fa478b8639" with: go-version-file: go.mod diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index ff42e451797..7b8321cce14 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -1,13 +1,9 @@ name: Zizmor GitHub Actions static analysis on: pull_request: - paths: - - ".github/**" push: branches: - main - paths: - - ".github/**" jobs: zizmor: @@ -24,4 +20,4 @@ jobs: uses: grafana/shared-workflows/.github/workflows/reusable-zizmor.yml@main # zizmor: ignore[unpinned-uses] with: fail-severity: high - min-severity: high \ No newline at end of file + min-severity: high diff --git a/.github/zizmor.yml b/.github/zizmor.yml new file mode 100644 index 00000000000..fba4a80be24 --- /dev/null +++ b/.github/zizmor.yml @@ -0,0 +1,31 @@ +rules: + unpinned-uses: + config: + policies: + "*": hash-pin + actions/*: any + github/*: any + grafana/*: any + forbidden-uses: + config: + deny: + # Policy-banned by our security team due to CVE-2025-30066 & CVE-2025-30154. + # https://www.cisa.gov/news-events/alerts/2025/03/18/supply-chain-compromise-third-party-tj-actionschanged-files-cve-2025-30066-and-reviewdogaction + # https://nvd.nist.gov/vuln/detail/cve-2025-30066 + # https://nvd.nist.gov/vuln/detail/cve-2025-30154 + - reviewdog/* + cache-poisoning: + ignore: + - backend-unit-tests.yml + - frontend-lint.yml + - pr-frontend-unit-tests.yml + - pr-test-integration.yml + - publish-kinds-release.yml + dangerous-triggers: + ignore: + - auto-milestone.yml + - backport.yml + - pr-checks.yml + - pr-commands.yml + - pr-patch-check-event.yml + - run-dashboard-search-e2e.yml diff --git a/pkg/build/actions/bump-version/action.yml b/pkg/build/actions/bump-version/action.yml index 783145097a2..24ae182daf2 100644 --- a/pkg/build/actions/bump-version/action.yml +++ b/pkg/build/actions/bump-version/action.yml @@ -11,7 +11,7 @@ runs: with: go-version-file: go.mod - name: Bump versions - uses: dagger/dagger-for-github@v5 + uses: dagger/dagger-for-github@e47aba410ef9bb9ed81a4d2a97df31061e5e842e with: verb: run args: go run ./pkg/build/actions/bump-version -version=${{ inputs.version }} From 2436b4e097a9a2776732f827dc157f394eae911e Mon Sep 17 00:00:00 2001 From: Kevin Minehart <5140827+kminehart@users.noreply.github.com> Date: Tue, 29 Apr 2025 14:24:55 -0500 Subject: [PATCH 010/849] CI: move workflows/actions to actions (#104711) * move workflows/actions to actions * rerun actions * fix setup-go v5 * unpinned unnecessary pins * update CODEOWONERS * update CODEOWONERS * remove remove-milestone from codeowners * remove bad key --- .github/CODEOWNERS | 9 +++---- .../actions/changelog/action.yml | 0 .../actions/changelog/index.js | 0 .../actions/changelog/package.json | 0 .github/workflows/alerting-swagger-gen.yml | 2 +- .github/workflows/alerting-update-module.yml | 2 +- .github/workflows/analytics-events-report.yml | 4 ++-- .github/workflows/backend-code-checks.yml | 5 ++-- .github/workflows/backend-unit-tests.yml | 8 +++---- .github/workflows/backport.yml | 2 +- .github/workflows/bump-version.yml | 2 +- .github/workflows/changelog.yml | 6 ++--- .github/workflows/codeowners-validator.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/commands.yml | 2 +- .../core-plugins-build-and-release.yml | 4 ++-- .../detect-breaking-changes-levitate.yml | 14 +++++------ .github/workflows/documentation-ci.yml | 2 +- .../ephemeral-instances-pr-comment.yml | 2 +- .github/workflows/feature-toggles-ci.yml | 4 ++-- .github/workflows/frontend-lint.yml | 24 +++++++++---------- .github/workflows/go-lint.yml | 4 ++-- .../workflows/i18n-crowdin-create-tasks.yml | 4 ++-- .github/workflows/i18n-crowdin-download.yml | 2 +- .github/workflows/i18n-crowdin-upload.yml | 2 +- .github/workflows/issue-opened.yml | 4 ++-- .github/workflows/lint-build-docs.yml | 4 ++-- .github/workflows/metrics-collector.yml | 2 +- .github/workflows/pr-backend-coverage.yml | 4 ++-- .github/workflows/pr-checks.yml | 2 +- .../pr-codeql-analysis-javascript.yml | 2 +- .../workflows/pr-codeql-analysis-python.yml | 2 +- .github/workflows/pr-commands.yml | 2 +- .../pr-dependabot-update-go-workspace.yml | 2 +- .github/workflows/pr-e2e-tests.yml | 4 ++-- .github/workflows/pr-frontend-unit-tests.yml | 8 +++---- .github/workflows/pr-go-workspace-check.yml | 2 +- .github/workflows/pr-k8s-codegen-check.yml | 2 +- .github/workflows/pr-test-integration.yml | 12 +++++----- .github/workflows/publish-kinds-next.yml | 2 +- .github/workflows/publish-kinds-release.yml | 4 ++-- .../publish-technical-documentation-next.yml | 2 +- ...ublish-technical-documentation-release.yml | 2 +- .github/workflows/release-pr.yml | 8 +++---- .../workflows/run-dashboard-search-e2e.yml | 8 +++---- .github/workflows/run-e2e-suite.yml | 2 +- .github/workflows/run-schema-v2-e2e.yml | 6 ++--- .github/workflows/storybook-verification.yml | 4 ++-- .github/workflows/trivy-scan.yml | 2 +- .github/workflows/update-make-docs.yml | 2 +- .github/workflows/verify-kinds.yml | 2 +- 51 files changed, 100 insertions(+), 104 deletions(-) rename .github/{workflows => }/actions/changelog/action.yml (100%) rename .github/{workflows => }/actions/changelog/index.js (100%) rename .github/{workflows => }/actions/changelog/package.json (100%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c8fdc572b60..a24c909abc9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -768,7 +768,6 @@ embed.go @grafana/grafana-as-code /.github/workflows/backend-unit-tests.yml @grafana/grafana-backend-group /.github/workflows/backport.yml @grafana/grafana-developer-enablement-squad /.github/workflows/bump-version.yml @grafana/grafana-developer-enablement-squad -/.github/workflows/close-milestone.yml @grafana/grafana-developer-enablement-squad /.github/workflows/release-pr.yml @grafana/grafana-developer-enablement-squad /.github/workflows/release-comms.yml @grafana/grafana-developer-enablement-squad /.github/workflows/migrate-prs.yml @grafana/grafana-developer-enablement-squad @@ -780,13 +779,11 @@ embed.go @grafana/grafana-as-code /.github/workflows/detect-breaking-changes-* @grafana/plugins-platform-frontend /.github/workflows/documentation-ci.yml @grafana/docs-tooling /.github/workflows/deploy-pr-preview.yml @grafana/docs-tooling -/.github/workflows/epic-add-to-platform-ux-parent-project.yml @meanmina /.github/workflows/feature-toggles-ci.yml @grafana/docs-tooling /.github/workflows/github-release.yml @grafana/grafana-developer-enablement-squad /.github/workflows/issue-opened.yml @grafana/grafana-community-support /.github/workflows/lint-build-docs.yml @grafana/docs-tooling /.github/workflows/metrics-collector.yml @torkelo -/.github/workflows/milestone.yml @tolzhabayev /.github/workflows/pr-checks.yml @tolzhabayev /.github/workflows/pr-codeql-analysis-javascript.yml @DanCech /.github/workflows/pr-codeql-analysis-python.yml @DanCech @@ -797,11 +794,9 @@ embed.go @grafana/grafana-as-code /.github/workflows/sync-mirror-event.yml @grafana/grafana-developer-enablement-squad /.github/workflows/publish-technical-documentation-next.yml @grafana/docs-tooling /.github/workflows/publish-technical-documentation-release.yml @grafana/docs-tooling -/.github/workflows/remove-milestone.yml @grafana/grafana-developer-enablement-squad /.github/workflows/scripts/json-file-to-job-output.js @grafana/plugins-platform-frontend /.github/workflows/stale.yml @grafana/grafana-developer-enablement-squad /.github/workflows/storybook-verification.yml @grafana/grafana-frontend-platform -/.github/workflows/update-changelog.yml @grafana/grafana-developer-enablement-squad /.github/workflows/update-make-docs.yml @grafana/docs-tooling /.github/workflows/scripts/kinds/verify-kinds.go @grafana/platform-monitoring /.github/workflows/publish-kinds-next.yml @grafana/platform-monitoring @@ -824,13 +819,15 @@ embed.go @grafana/grafana-as-code /.github/workflows/go-lint.yml @grafana/grafana-backend-services-squad /.github/workflows/trivy-scan.yml @grafana/grafana-backend-services-squad /.github/workflows/changelog.yml @zserge -/.github/workflows/actions/changelog @zserge +/.github/actions/changelog @zserge /.github/workflows/pr-frontend-unit-tests.yml @grafana/grafana-frontend-platform /.github/workflows/frontend-lint.yml @grafana/grafana-frontend-platform /.github/workflows/analytics-events-report.yml @grafana/grafana-frontend-platform /.github/workflows/pr-e2e-tests.yml @grafana/grafana-developer-enablement-squad /.github/workflows/run-e2e-suite.yml @grafana/grafana-developer-enablement-squad /.github/workflows/skye-add-to-project.yml @grafana/grafana-frontend-platform +/.github/workflows/zizmor.yml @grafana/grafana-developer-enablement-squad +/.github/zizmor.yml @grafana/grafana-developer-enablement-squad # Generated files not requiring owner approval /packages/grafana-data/src/types/featureToggles.gen.ts @grafanabot diff --git a/.github/workflows/actions/changelog/action.yml b/.github/actions/changelog/action.yml similarity index 100% rename from .github/workflows/actions/changelog/action.yml rename to .github/actions/changelog/action.yml diff --git a/.github/workflows/actions/changelog/index.js b/.github/actions/changelog/index.js similarity index 100% rename from .github/workflows/actions/changelog/index.js rename to .github/actions/changelog/index.js diff --git a/.github/workflows/actions/changelog/package.json b/.github/actions/changelog/package.json similarity index 100% rename from .github/workflows/actions/changelog/package.json rename to .github/actions/changelog/package.json diff --git a/.github/workflows/alerting-swagger-gen.yml b/.github/workflows/alerting-swagger-gen.yml index 2526d924d5e..c06304b38a3 100644 --- a/.github/workflows/alerting-swagger-gen.yml +++ b/.github/workflows/alerting-swagger-gen.yml @@ -10,7 +10,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + uses: actions/checkout@v4 with: fetch-depth: 2 persist-credentials: false diff --git a/.github/workflows/alerting-update-module.yml b/.github/workflows/alerting-update-module.yml index d1646d21718..5bbf260e64a 100644 --- a/.github/workflows/alerting-update-module.yml +++ b/.github/workflows/alerting-update-module.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 + uses: actions/checkout@v4 # 4.2.2 with: persist-credentials: false - name: Check if update branch exists diff --git a/.github/workflows/analytics-events-report.yml b/.github/workflows/analytics-events-report.yml index 9c5c6f3ef09..42f601b793b 100644 --- a/.github/workflows/analytics-events-report.yml +++ b/.github/workflows/analytics-events-report.yml @@ -8,12 +8,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + uses: actions/checkout@v4 with: persist-credentials: false - name: Setup Node.js - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e + uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' cache: 'yarn' diff --git a/.github/workflows/backend-code-checks.yml b/.github/workflows/backend-code-checks.yml index b55c07aed22..b95257d99c3 100644 --- a/.github/workflows/backend-code-checks.yml +++ b/.github/workflows/backend-code-checks.yml @@ -1,5 +1,4 @@ name: Backend Code Checks -description: Validate go.mod and OpenAPI specifications on: pull_request: @@ -24,11 +23,11 @@ jobs: name: Validate Backend Configs runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@v4 with: persist-credentials: false - name: Setup Go - uses: actions/setup-go@111f3307d8850f501ac008e886eec1fd1932a34 + uses: actions/setup-go@v5 with: # Explicitly set Go version to 1.24.1 to ensure consistent OpenAPI spec generation # The crypto/x509 package has additional fields in Go 1.24.1 that affect the generated specs diff --git a/.github/workflows/backend-unit-tests.yml b/.github/workflows/backend-unit-tests.yml index 2c0c238d502..a1357d2e87b 100644 --- a/.github/workflows/backend-unit-tests.yml +++ b/.github/workflows/backend-unit-tests.yml @@ -32,11 +32,11 @@ jobs: id-token: write steps: - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + uses: actions/checkout@v4 with: persist-credentials: false - name: Setup Go - uses: actions/setup-go@111f3307d8850f501ac008e886eec1fd1932a34 + uses: actions/setup-go@v5 with: go-version-file: go.mod - name: Generate Go code @@ -54,11 +54,11 @@ jobs: id-token: write steps: - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + uses: actions/checkout@v4 with: persist-credentials: false - name: Setup Go - uses: actions/setup-go@111f3307d8850f501ac008e886eec1fd1932a34 + uses: actions/setup-go@v5 with: go-version-file: go.mod - name: Setup Enterprise diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index fc65898f652..673dc228fc2 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 + uses: actions/checkout@v4 # 4.2.2 with: persist-credentials: false - run: git config --local user.name "github-actions[bot]" diff --git a/.github/workflows/bump-version.yml b/.github/workflows/bump-version.yml index 8db9fb1e5c3..39fa0566d86 100644 --- a/.github/workflows/bump-version.yml +++ b/.github/workflows/bump-version.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Grafana - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + uses: actions/checkout@v4 with: persist-credentials: false - name: Update package.json versions diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 094279e0c9a..c2dcc2972f1 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -73,7 +73,7 @@ jobs: app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} - name: "Checkout Grafana repo" - uses: "actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683" + uses: "actions/checkout@v4" with: ref: main sparse-checkout: | @@ -86,7 +86,7 @@ jobs: fetch-tags: true persist-credentials: false - name: Setup nodejs environment - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e + uses: actions/setup-node@v4 with: node-version-file: .nvmrc - name: "Configure git user" @@ -98,7 +98,7 @@ jobs: run: git checkout -b "changelog/${RUN_ID}/${VERSION}" - name: "Generate changelog" id: changelog - uses: ./.github/workflows/actions/changelog + uses: ./.github/actions/changelog with: previous: ${{ inputs.previous_version }} github_token: ${{ steps.generate_token.outputs.token }} diff --git a/.github/workflows/codeowners-validator.yml b/.github/workflows/codeowners-validator.yml index f98bee4e213..41afde3a822 100644 --- a/.github/workflows/codeowners-validator.yml +++ b/.github/workflows/codeowners-validator.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: # Checks-out your repository, which is validated in the next step - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@v4 with: persist-credentials: false - name: GitHub CODEOWNERS Validator diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 06772bac55c..c16c5eb353e 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -40,7 +40,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + uses: actions/checkout@v4 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml index cf61d859443..3c3987b549b 100644 --- a/.github/workflows/commands.yml +++ b/.github/workflows/commands.yml @@ -53,7 +53,7 @@ jobs: private_key: ${{ env.GH_APP_PEM }} - name: Checkout Actions - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@v4 # v4.2.2 with: repository: "grafana/grafana-github-actions" path: ./actions diff --git a/.github/workflows/core-plugins-build-and-release.yml b/.github/workflows/core-plugins-build-and-release.yml index d2aebbc41e8..66447cb9ffa 100644 --- a/.github/workflows/core-plugins-build-and-release.yml +++ b/.github/workflows/core-plugins-build-and-release.yml @@ -43,7 +43,7 @@ jobs: version: ${{ steps.build_frontend.outputs.version }} steps: - name: checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + uses: actions/checkout@v4 with: persist-credentials: false - name: Verify inputs @@ -64,7 +64,7 @@ jobs: - name: 'Set up Cloud SDK' uses: 'google-github-actions/setup-gcloud@6189d56e4096ee891640bb02ac264be376592d6a' - name: Setup nodejs environment - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e + uses: actions/setup-node@v4 with: node-version-file: .nvmrc cache: yarn diff --git a/.github/workflows/detect-breaking-changes-levitate.yml b/.github/workflows/detect-breaking-changes-levitate.yml index a2517d5e0a2..640b80a3dd5 100644 --- a/.github/workflows/detect-breaking-changes-levitate.yml +++ b/.github/workflows/detect-breaking-changes-levitate.yml @@ -27,11 +27,11 @@ jobs: id-token: write steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@v4 with: path: './pr' persist-credentials: false - - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e + - uses: actions/setup-node@v4 with: node-version: 22.11.0 @@ -77,12 +77,12 @@ jobs: working-directory: './base' steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@v4 with: path: './base' ref: ${{ github.event.pull_request.base.ref }} - - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e + - uses: actions/setup-node@v4 with: node-version: 22.11.0 @@ -128,8 +128,8 @@ jobs: id-token: 'write' steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: 22.11.0 @@ -197,7 +197,7 @@ jobs: app_id: ${{ secrets.GRAFANA_PR_AUTOMATION_APP_ID }} private_key: ${{ secrets.GRAFANA_PR_AUTOMATION_APP_PEM }} - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@v4 - name: 'Download artifact' uses: actions/download-artifact@v4 diff --git a/.github/workflows/documentation-ci.yml b/.github/workflows/documentation-ci.yml index 9b8bc7dc53b..30c2516412f 100644 --- a/.github/workflows/documentation-ci.yml +++ b/.github/workflows/documentation-ci.yml @@ -10,7 +10,7 @@ jobs: container: image: grafana/vale:latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@v4 with: persist-credentials: false - uses: grafana/writers-toolkit/vale-action@vale-action/v1 # zizmor: ignore[unpinned-uses] diff --git a/.github/workflows/ephemeral-instances-pr-comment.yml b/.github/workflows/ephemeral-instances-pr-comment.yml index e60eca1bad4..ed6b98bbce2 100644 --- a/.github/workflows/ephemeral-instances-pr-comment.yml +++ b/.github/workflows/ephemeral-instances-pr-comment.yml @@ -41,7 +41,7 @@ jobs: private_key: ${{ secrets.EI_APP_PRIVATE_KEY }} - name: Checkout ephemeral instances repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + uses: actions/checkout@v4 with: repository: grafana/ephemeral-grafana-instances-github-action token: ${{ steps.generate_token.outputs.token }} diff --git a/.github/workflows/feature-toggles-ci.yml b/.github/workflows/feature-toggles-ci.yml index 7aa1dbd0b42..a6c9f5c52dc 100644 --- a/.github/workflows/feature-toggles-ci.yml +++ b/.github/workflows/feature-toggles-ci.yml @@ -11,12 +11,12 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@v4 with: persist-credentials: false - name: Set up Go - uses: actions/setup-go@111f3307d8850f501ac008e886eec1fd1932a34 + uses: actions/setup-go@v5 with: go-version-file: 'go.mod' cache: true diff --git a/.github/workflows/frontend-lint.yml b/.github/workflows/frontend-lint.yml index 42533e0127d..0042166d3c7 100644 --- a/.github/workflows/frontend-lint.yml +++ b/.github/workflows/frontend-lint.yml @@ -16,10 +16,10 @@ jobs: contents: read id-token: write steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@v4 with: persist-credentials: false - - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e + - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' cache: 'yarn' @@ -46,8 +46,8 @@ jobs: name: Lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' cache: 'yarn' @@ -64,8 +64,8 @@ jobs: name: Lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' cache: 'yarn' @@ -87,8 +87,8 @@ jobs: name: Typecheck runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' cache: 'yarn' @@ -104,8 +104,8 @@ jobs: name: Typecheck runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' cache: 'yarn' @@ -123,8 +123,8 @@ jobs: name: Betterer runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' cache: 'yarn' diff --git a/.github/workflows/go-lint.yml b/.github/workflows/go-lint.yml index 0c65982f9c8..cdc84874d01 100644 --- a/.github/workflows/go-lint.yml +++ b/.github/workflows/go-lint.yml @@ -16,10 +16,10 @@ jobs: lint-go: runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@v4 with: persist-credentials: false - - uses: actions/setup-go@111f3307d8850f501ac008e886eec1fd1932a34 + - uses: actions/setup-go@v5 with: go-version-file: ./go.mod - run: make gen-go diff --git a/.github/workflows/i18n-crowdin-create-tasks.yml b/.github/workflows/i18n-crowdin-create-tasks.yml index dbe2be9a5b8..60277aed365 100644 --- a/.github/workflows/i18n-crowdin-create-tasks.yml +++ b/.github/workflows/i18n-crowdin-create-tasks.yml @@ -11,12 +11,12 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + uses: actions/checkout@v4 with: persist-credentials: false - name: Setup Node.js - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e + uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' diff --git a/.github/workflows/i18n-crowdin-download.yml b/.github/workflows/i18n-crowdin-download.yml index d287c7ee06f..26f9588069f 100644 --- a/.github/workflows/i18n-crowdin-download.yml +++ b/.github/workflows/i18n-crowdin-download.yml @@ -22,7 +22,7 @@ jobs: app_id: ${{ secrets.GRAFANA_PR_AUTOMATION_APP_ID }} private_key: ${{ secrets.GRAFANA_PR_AUTOMATION_APP_PEM }} - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@v4 with: ref: ${{ github.head_ref }} token: ${{ steps.generate_token.outputs.token }} diff --git a/.github/workflows/i18n-crowdin-upload.yml b/.github/workflows/i18n-crowdin-upload.yml index d7d1a130fcd..7165aa823fa 100644 --- a/.github/workflows/i18n-crowdin-upload.yml +++ b/.github/workflows/i18n-crowdin-upload.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + uses: actions/checkout@v4 with: persist-credentials: false diff --git a/.github/workflows/issue-opened.yml b/.github/workflows/issue-opened.yml index 91767a5310b..a5a5a822446 100644 --- a/.github/workflows/issue-opened.yml +++ b/.github/workflows/issue-opened.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Checkout Actions - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@v4 # v4.2.2 with: repository: "grafana/grafana-github-actions" path: ./actions @@ -88,7 +88,7 @@ jobs: private_key: ${{ env.GH_APP_PEM }} - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@v4 # v4.2.2 - name: Send issue to the auto triager action id: auto_triage diff --git a/.github/workflows/lint-build-docs.yml b/.github/workflows/lint-build-docs.yml index 1477cb2797a..c9da22210b6 100644 --- a/.github/workflows/lint-build-docs.yml +++ b/.github/workflows/lint-build-docs.yml @@ -22,12 +22,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + uses: actions/checkout@v4 with: persist-credentials: false - name: Setup Node.js - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e + uses: actions/setup-node@v4 with: node-version: '22.11.0' cache: 'yarn' diff --git a/.github/workflows/metrics-collector.yml b/.github/workflows/metrics-collector.yml index 281dc6fb350..4e08bef9b10 100644 --- a/.github/workflows/metrics-collector.yml +++ b/.github/workflows/metrics-collector.yml @@ -38,7 +38,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Actions - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@v4 # v4.2.2 with: repository: "grafana/grafana-github-actions" path: ./actions diff --git a/.github/workflows/pr-backend-coverage.yml b/.github/workflows/pr-backend-coverage.yml index 4d95497e3ec..12ed4423587 100644 --- a/.github/workflows/pr-backend-coverage.yml +++ b/.github/workflows/pr-backend-coverage.yml @@ -23,11 +23,11 @@ jobs: runs-on: ubuntu-latest-8-cores steps: - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + uses: actions/checkout@v4 with: persist-credentials: false - name: Setup Go - uses: actions/setup-go@111f3307d8850f501ac008e886eec1fd1932a34 + uses: actions/setup-go@v5 with: go-version-file: go.mod cache: true diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 68317f57a0e..cc8d2531bef 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -31,7 +31,7 @@ jobs: if: github.event.pull_request.draft == false steps: - name: Checkout Actions - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@v4 # v4.2.2 with: repository: "grafana/grafana-github-actions" path: ./actions diff --git a/.github/workflows/pr-codeql-analysis-javascript.yml b/.github/workflows/pr-codeql-analysis-javascript.yml index 43457ad4af0..885c6116f58 100644 --- a/.github/workflows/pr-codeql-analysis-javascript.yml +++ b/.github/workflows/pr-codeql-analysis-javascript.yml @@ -20,7 +20,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + uses: actions/checkout@v4 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. diff --git a/.github/workflows/pr-codeql-analysis-python.yml b/.github/workflows/pr-codeql-analysis-python.yml index 992a886497d..c5fe4b6a10c 100644 --- a/.github/workflows/pr-codeql-analysis-python.yml +++ b/.github/workflows/pr-codeql-analysis-python.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + uses: actions/checkout@v4 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. diff --git a/.github/workflows/pr-commands.yml b/.github/workflows/pr-commands.yml index db93ad7e70b..518c25dfeaa 100644 --- a/.github/workflows/pr-commands.yml +++ b/.github/workflows/pr-commands.yml @@ -30,7 +30,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Actions - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@v4 # v4.2.2 with: repository: "grafana/grafana-github-actions" path: ./actions diff --git a/.github/workflows/pr-dependabot-update-go-workspace.yml b/.github/workflows/pr-dependabot-update-go-workspace.yml index 48a005ba6f7..a83875c4644 100644 --- a/.github/workflows/pr-dependabot-update-go-workspace.yml +++ b/.github/workflows/pr-dependabot-update-go-workspace.yml @@ -37,7 +37,7 @@ jobs: private-key: ${{ env.PRIVATE_KEY }} - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + uses: actions/checkout@v4 with: repository: ${{ github.event.pull_request.head.repo.full_name }} ref: ${{ github.event.pull_request.head.ref }} diff --git a/.github/workflows/pr-e2e-tests.yml b/.github/workflows/pr-e2e-tests.yml index 74e64e5bf38..a8f2e1cd54a 100644 --- a/.github/workflows/pr-e2e-tests.yml +++ b/.github/workflows/pr-e2e-tests.yml @@ -18,12 +18,12 @@ jobs: outputs: artifact: ${{ steps.artifact.outputs.artifact }} steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@v4 with: repository: 'grafana/grafana-build' ref: 'main' persist-credentials: false - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@v4 with: path: ./grafana - run: echo "GRAFANA_GO_VERSION=$(grep "go 1." grafana/go.work | cut -d\ -f2)" >> "$GITHUB_ENV" diff --git a/.github/workflows/pr-frontend-unit-tests.yml b/.github/workflows/pr-frontend-unit-tests.yml index e26e3847f8c..fd7ded43f0d 100644 --- a/.github/workflows/pr-frontend-unit-tests.yml +++ b/.github/workflows/pr-frontend-unit-tests.yml @@ -23,10 +23,10 @@ jobs: matrix: chunk: [1, 2, 3, 4, 5, 6, 7, 8] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@v4 with: persist-credentials: false - - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e + - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' cache: 'yarn' @@ -51,8 +51,8 @@ jobs: matrix: chunk: [1, 2, 3, 4, 5, 6, 7, 8] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' cache: 'yarn' diff --git a/.github/workflows/pr-go-workspace-check.yml b/.github/workflows/pr-go-workspace-check.yml index d54178f2e35..25e013b9484 100644 --- a/.github/workflows/pr-go-workspace-check.yml +++ b/.github/workflows/pr-go-workspace-check.yml @@ -21,7 +21,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + uses: actions/checkout@v4 with: persist-credentials: false diff --git a/.github/workflows/pr-k8s-codegen-check.yml b/.github/workflows/pr-k8s-codegen-check.yml index 9a72f469caa..6c34674e5c9 100644 --- a/.github/workflows/pr-k8s-codegen-check.yml +++ b/.github/workflows/pr-k8s-codegen-check.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + uses: actions/checkout@v4 with: persist-credentials: false diff --git a/.github/workflows/pr-test-integration.yml b/.github/workflows/pr-test-integration.yml index b49183889d5..9505c429dca 100644 --- a/.github/workflows/pr-test-integration.yml +++ b/.github/workflows/pr-test-integration.yml @@ -17,11 +17,11 @@ jobs: runs-on: ubuntu-latest-8-cores steps: - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + uses: actions/checkout@v4 with: persist-credentials: false - name: Setup Go - uses: actions/setup-go@111f3307d8850f501ac008e886eec1fd1932a34 + uses: actions/setup-go@v5 with: go-version-file: go.mod cache: true @@ -47,9 +47,9 @@ jobs: - 3306:3306 steps: - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + uses: actions/checkout@v4 - name: Setup Go - uses: actions/setup-go@111f3307d8850f501ac008e886eec1fd1932a34 + uses: actions/setup-go@v5 with: go-version-file: go.mod cache: true @@ -72,9 +72,9 @@ jobs: - 5432:5432 steps: - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + uses: actions/checkout@v4 - name: Setup Go - uses: actions/setup-go@111f3307d8850f501ac008e886eec1fd1932a34 + uses: actions/setup-go@v5 with: go-version-file: go.mod cache: true diff --git a/.github/workflows/publish-kinds-next.yml b/.github/workflows/publish-kinds-next.yml index ed290abbd2d..b63ba0ef966 100644 --- a/.github/workflows/publish-kinds-next.yml +++ b/.github/workflows/publish-kinds-next.yml @@ -29,7 +29,7 @@ jobs: runs-on: "ubuntu-latest" steps: - name: "Checkout Grafana repo" - uses: "actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683" + uses: "actions/checkout@v4" with: fetch-depth: 0 persist-credentials: false diff --git a/.github/workflows/publish-kinds-release.yml b/.github/workflows/publish-kinds-release.yml index 1e60f72ed79..73962750ef2 100644 --- a/.github/workflows/publish-kinds-release.yml +++ b/.github/workflows/publish-kinds-release.yml @@ -31,7 +31,7 @@ jobs: runs-on: "ubuntu-latest" steps: - name: "Checkout Grafana repo" - uses: "actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683" + uses: "actions/checkout@v4" with: # required for the `grafana/grafana-github-actions/has-matching-release-tag` action to work fetch-depth: 0 @@ -46,7 +46,7 @@ jobs: run: go run .github/workflows/scripts/kinds/verify-kinds.go - name: "Checkout Actions library" - uses: "actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683" + uses: "actions/checkout@v4" with: repository: "grafana/grafana-github-actions" path: "./actions" diff --git a/.github/workflows/publish-technical-documentation-next.yml b/.github/workflows/publish-technical-documentation-next.yml index 9d67f1724c5..f9c2adf0230 100644 --- a/.github/workflows/publish-technical-documentation-next.yml +++ b/.github/workflows/publish-technical-documentation-next.yml @@ -15,7 +15,7 @@ jobs: id-token: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@v4 - uses: grafana/writers-toolkit/publish-technical-documentation@publish-technical-documentation/v1 # zizmor: ignore[unpinned-uses] with: website_directory: content/docs/grafana/next diff --git a/.github/workflows/publish-technical-documentation-release.yml b/.github/workflows/publish-technical-documentation-release.yml index d5f597686ca..52d7da0562f 100644 --- a/.github/workflows/publish-technical-documentation-release.yml +++ b/.github/workflows/publish-technical-documentation-release.yml @@ -17,7 +17,7 @@ jobs: id-token: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@v4 with: fetch-depth: 0 persist-credentials: false diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml index 4d66838b7ec..42dd7051b71 100644 --- a/.github/workflows/release-pr.yml +++ b/.github/workflows/release-pr.yml @@ -72,14 +72,14 @@ jobs: ownerRepo: 'grafana/grafana' pattern: ${{ inputs.target }} - name: Checkout Grafana - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + uses: actions/checkout@v4 with: ref: ${{ steps.branch.outputs.branch }} fetch-tags: true token: ${{ secrets.GITHUB_TOKEN }} persist-credentials: false - name: Checkout Grafana (main) - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + uses: actions/checkout@v4 with: ref: main fetch-depth: '0' @@ -88,7 +88,7 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} persist-credentials: false - name: Setup nodejs environment - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e + uses: actions/setup-node@v4 with: node-version-file: .nvmrc - name: Configure git user @@ -107,7 +107,7 @@ jobs: private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} - name: Generate changelog id: changelog - uses: ./.grafana-main/.github/workflows/actions/changelog + uses: ./.grafana-main/.github/actions/changelog with: github_token: ${{ steps.generate_changelog_token.outputs.token }} target: v${{ env.VERSION }} diff --git a/.github/workflows/run-dashboard-search-e2e.yml b/.github/workflows/run-dashboard-search-e2e.yml index 7d59bd49fb5..76d765f4fcf 100644 --- a/.github/workflows/run-dashboard-search-e2e.yml +++ b/.github/workflows/run-dashboard-search-e2e.yml @@ -25,16 +25,16 @@ jobs: id-token: write steps: - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + uses: actions/checkout@v4 with: persist-credentials: false - name: Pin Go version to mod file - uses: actions/setup-go@111f3307d8850f501ac008e886eec1fd1932a34 + uses: actions/setup-go@v5 with: go-version-file: 'go.mod' cache: true - run: go version - - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e + - uses: actions/setup-node@v4 with: node-version: 20 cache: 'yarn' @@ -94,7 +94,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + uses: actions/checkout@v4 - name: Restore Cached Node Modules uses: actions/cache@v3 with: diff --git a/.github/workflows/run-e2e-suite.yml b/.github/workflows/run-e2e-suite.yml index b99d0ea0b30..5445d21f9bb 100644 --- a/.github/workflows/run-e2e-suite.yml +++ b/.github/workflows/run-e2e-suite.yml @@ -14,7 +14,7 @@ jobs: main: runs-on: ubuntu-latest-8-cores steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@v4 with: persist-credentials: false - uses: actions/download-artifact@v4 diff --git a/.github/workflows/run-schema-v2-e2e.yml b/.github/workflows/run-schema-v2-e2e.yml index 62975992acb..aa8a11c4c7f 100644 --- a/.github/workflows/run-schema-v2-e2e.yml +++ b/.github/workflows/run-schema-v2-e2e.yml @@ -18,15 +18,15 @@ jobs: if: github.event.pull_request.draft == false steps: - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + uses: actions/checkout@v4 with: persist-credentials: false - name: Pin Go version to mod file - uses: actions/setup-go@111f3307d8850f501ac008e886eec1fd1932a34 + uses: actions/setup-go@v5 with: go-version-file: 'go.mod' - run: go version - - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e + - uses: actions/setup-node@v4 with: node-version: 20 cache: 'yarn' diff --git a/.github/workflows/storybook-verification.yml b/.github/workflows/storybook-verification.yml index 99f0c6dfe82..c50b7533d0d 100644 --- a/.github/workflows/storybook-verification.yml +++ b/.github/workflows/storybook-verification.yml @@ -21,12 +21,12 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + uses: actions/checkout@v4 with: persist-credentials: false - name: Setup Node.js - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e + uses: actions/setup-node@v4 with: node-version-file: 'package.json' cache: 'yarn' diff --git a/.github/workflows/trivy-scan.yml b/.github/workflows/trivy-scan.yml index 921d25b76ff..85e2ed7f07f 100644 --- a/.github/workflows/trivy-scan.yml +++ b/.github/workflows/trivy-scan.yml @@ -16,7 +16,7 @@ jobs: trivy-scan: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@v4 with: persist-credentials: false - name: Install Trivy diff --git a/.github/workflows/update-make-docs.yml b/.github/workflows/update-make-docs.yml index 7d727f284df..aab2be84ad9 100644 --- a/.github/workflows/update-make-docs.yml +++ b/.github/workflows/update-make-docs.yml @@ -8,7 +8,7 @@ jobs: if: github.repository == 'grafana/grafana' runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@v4 with: persist-credentials: false - uses: grafana/writers-toolkit/update-make-docs@update-make-docs/v1 # zizmor: ignore[unpinned-uses] diff --git a/.github/workflows/verify-kinds.yml b/.github/workflows/verify-kinds.yml index ce0a7a00b8a..c793dcb2895 100644 --- a/.github/workflows/verify-kinds.yml +++ b/.github/workflows/verify-kinds.yml @@ -11,7 +11,7 @@ jobs: runs-on: "ubuntu-latest" steps: - name: "Checkout Grafana repo" - uses: "actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683" + uses: "actions/checkout@v4" with: fetch-depth: 0 persist-credentials: false From 4d7a4eba54a5b24f82f3ddde87d37f9c8a35a29d Mon Sep 17 00:00:00 2001 From: Alex Bikfalvi Date: Tue, 29 Apr 2025 16:19:32 -0400 Subject: [PATCH 011/849] docs: Trace correlations (#104309) Co-authored-by: Kim Nylander <104772500+knylander-grafana@users.noreply.github.com> --- .../traces-in-grafana/trace-correlations.md | 170 ++++++++++++++++++ docs/sources/explore/trace-integration.md | 8 + 2 files changed, 178 insertions(+) create mode 100644 docs/sources/datasources/tempo/traces-in-grafana/trace-correlations.md diff --git a/docs/sources/datasources/tempo/traces-in-grafana/trace-correlations.md b/docs/sources/datasources/tempo/traces-in-grafana/trace-correlations.md new file mode 100644 index 00000000000..7b28419d2d5 --- /dev/null +++ b/docs/sources/datasources/tempo/traces-in-grafana/trace-correlations.md @@ -0,0 +1,170 @@ +--- +description: Use Grafana correlations with Tempo traces +keywords: + - grafana + - tempo + - guide + - tracing + - correlations +labels: + products: + - cloud + - enterprise + - oss +menuTitle: Trace correlations +title: Trace correlations +weight: 1000 +--- + +# Trace correlations + +You can use Grafana [correlations](/docs/grafana//administration/correlations/) to embed interactive correlation links in your trace view to jump from spans to related logs, metrics, profiles, or external systems. This guide explains how to configure and manage Trace correlations in Grafana. + +## What are trace correlations? + +Trace correlations let you define rules that inject context-sensitive links into your trace spans. When viewing traces in Explore or the Traces panel, users can click these links to navigate directly to relevant queries or URLs. Correlations are similar but more flexible to the [trace to logs, metrics, and profiles links you can configure for the Tempo data source](/docs/grafana//datasources/tempo/configure-tempo-data-source). + +{{< figure src="/media/docs/tempo/screenshot-trace-view-correlations.png" max-width="900px" class="docs-image--no-shadow" alt="Using correlations for a trace" >}} + +## Before you begin + +To use trace correlations, you need: + +- Grafana 12 or later +- A [Tempo data source](https://grafana.com/docs/grafana//datasources/tempo/configure-tempo-data-source/) configured in Grafana +- Admin access to configuration settings or provisioning files in Grafana + +## Set up a trace correlation + +1. Log in to Grafana with an admin account. + +1. Go to **Configuration** > **Plugins & data** > **Correlations**. + +1. Select **Add correlation** or **Add new**. + +1. On step 1, provide a **label** for the correlation, and an optional **description**. + +1. On step 2, configure the correlation **target**. + + - Select the **Type** drop-down list and choose **Query** to link to another data source or choose **External** for a custom URL. + + - For a query **Target**, select the target drop-down list and select the data source that should be queried when the link is clicked. Define the target query. + + - For an external **Target**, enter the **External URL**. + + - For both query and external targets, you can use the following variables based on trace data. Object variables must be parsed into a value variable with a regular expression transformation. + + | Variable | Type | Description | + | -------------- | ------ | ---------------------- | + | `traceId` | String | Trace identifier | + | `spanID` | String | Span identifier | + | `parentSpanID` | String | Parent span identifier | + | `serviceName` | String | Service name | + | `serviceTags` | Object | Resource attributes | + | `tags` | Object | Span attributes | + | `logs` | Object | Trace events | + | `references` | Object | Trace links | + + {{< figure src="/media/docs/tempo/screenshot-grafana-trace-correlations-loki-step-2.png" max-width="900px" class="docs-image--no-shadow" alt="Setting up a correlation for a Loki target using trace variables" >}} + +1. On step 3, configure the correlation data source: + + - Select your Tempo data source in the **Source** drop-down list. + + - Enter the trace data variable you use for the correlation in the **Results field**. + + - Optionally, add one or more **Transformations** to parse the trace data into additional variables. You can use these variables to configure the correlation **Target**. + + {{< figure src="/media/docs/tempo/screenshot-grafana-trace-correlations-loki-step-3.png" max-width="900px" class="docs-image--no-shadow" alt="Setting up a correlation for a Loki data source" >}} + +1. Select **Save** to save the correlation. + +## Verifying correlations in Explore + +1. Open **Explore** and select your Tempo tracing source. + +1. Run a query to load spans. + +1. Hover over the span links menu or open the span details to reveal the correlation link buttons. + + {{< figure src="/media/docs/tempo/screenshot-grafana-trace-view-correlations.png" max-width="900px" class="docs-image--no-shadow" alt="Using correlations for a trace" >}} + +1. Click a correlation link to open a split view or navigate to your target system or query. + +## Examples + +Below are several practical correlation configurations to get you started. + +### Example 1: Trace to logs by service name and trace identifier + +In this example, you configure trace to logs by service name and a trace identifier. + +1. On step 1, add a new correlation with the label **Logs for this service and trace** and an optional description. + + {{< figure src="/media/docs/tempo/screenshot-grafana-trace-view-correlations-example-1-step-1.png" max-width="900px" class="docs-image--no-shadow" alt="Using correlations for a trace" >}} + +1. On step 2, configure the correlation target: + + - Select the target type **Query** and select your Loki data source as **Target**. + + - Define the Loki query, using `serviceName` and `traceID` as variables derived from the span data: + + ``` + {service_name="$serviceName"} | trace_id=`$traceID` |= `` + ``` + + {{< figure src="/media/docs/tempo/screenshot-grafana-trace-view-correlations-example-1-step-2.png" max-width="900px" class="docs-image--no-shadow" alt="Using correlations for a trace" >}} + +1. On step 3, configure the correlation source: + + - Select your Tempo data source as **Source**. + + - Use `traceID` as **Results field**. + + - Add a new transformation to extract the `serviceName` from the span `serviceTags` using the regular expression: + + ``` + {(?=[^\}]*\bkey":"service.name")[^\}]*\bvalue":"(.*?)".*} + ``` + + {{< figure src="/media/docs/tempo/screenshot-grafana-trace-view-correlations-example-1-step-3.png" max-width="900px" class="docs-image--no-shadow" alt="Using correlations for a trace" >}} + +1. Save the correlation. + +### Example 2: Trace to custom URL + +In this example, you configure trace corrections with a custom URL. + +1. On step 1, add a new correlation with the label **Open custom URL** and an optional description. + +1. On step 2, configure the correlation target: + + - Select the target type **External**. + + - Define your target URL, using variables derived from the span data. In this example, we are using `serviceName` and `traceID`. + + ``` + https://my-server.example.com/service=$serviceName&trace=$traceID + ``` + +1. On step 3, configure the correlation source: + + - Select your Tempo data source as **Source**. + + - Use `traceID` as **Results field**. + + - Add a new transformation to extract the `serviceName` from the span `serviceTags` using the regular expression: + + ``` + {(?=[^\}]*\bkey":"service.name")[^\}]*\bvalue":"(.*?)".*} + ``` + +1. Save the correlation. + +## Best practices + +- **Name clearly:** Use descriptive names indicating source and target. For example: **Trace to errors in logs**. + +- **Limit scope**: For high-cardinality fields (like `traceID`), ensure your target system can handle frequent queries. + +- **Template wisely:** Use multiple `$variable` tokens if you need to inject more than one field. diff --git a/docs/sources/explore/trace-integration.md b/docs/sources/explore/trace-integration.md index ecfa1331199..e1675c3d414 100644 --- a/docs/sources/explore/trace-integration.md +++ b/docs/sources/explore/trace-integration.md @@ -157,6 +157,14 @@ For Tempo refer to [Trace to profiles](/docs/grafana//datasourc {{< figure src="/static/img/docs/tempo/profiles/tempo-trace-to-profile.png" max-width="900px" class="docs-image--no-shadow" alt="Selecting a link in the span queries the profile data source" >}} +### Trace correlations + +You can use [correlations](/docs/grafana//administration/correlations/) to define custom links that appear in the trace view based on trace and span information. + +For Tempo, refer to [Trace correlations](/docs/grafana//datasources/tempo/traces-in-grafana/trace-correlations/) for configuration instructions. + +{{< figure src="/media/docs/tempo/screenshot-grafana-trace-view-correlations.png" max-width="900px" class="docs-image--no-shadow" alt="Using correlations for a trace" >}} + ## Node graph You can also expand the node graph for a displayed trace. If the data source supports it, this displays spans of the trace as nodes in the graph, or provides additional context, such as a service graph based on the current trace. From a4f6953f271f2acdcc4859fb02315c2b176eaa71 Mon Sep 17 00:00:00 2001 From: Georges Chaudy Date: Tue, 29 Apr 2025 22:50:45 +0200 Subject: [PATCH 012/849] spanner: skip dasbhoard RBAC e2e tests for spanner (#104043) * skip dasbhoard RBAC e2e for spanner * annotations also relying on dashboard find --- .../annotations/annotationsimpl/annotations_test.go | 4 ++++ .../dashboards/database/database_folder_test.go | 3 +++ pkg/services/sqlstore/permissions/dashboard_test.go | 12 ++++++++++-- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/pkg/services/annotations/annotationsimpl/annotations_test.go b/pkg/services/annotations/annotationsimpl/annotations_test.go index 19b3c1cbe2e..ed3701f2aa3 100644 --- a/pkg/services/annotations/annotationsimpl/annotations_test.go +++ b/pkg/services/annotations/annotationsimpl/annotations_test.go @@ -204,6 +204,10 @@ func TestIntegrationAnnotationListingWithInheritedRBAC(t *testing.T) { t.Skip("skipping integration test") } + if db.IsTestDBSpanner() { + t.Skip("skipping integration test") + } + orgID := int64(1) permissions := []accesscontrol.Permission{ { diff --git a/pkg/services/dashboards/database/database_folder_test.go b/pkg/services/dashboards/database/database_folder_test.go index ec52d14989a..664c264c1cd 100644 --- a/pkg/services/dashboards/database/database_folder_test.go +++ b/pkg/services/dashboards/database/database_folder_test.go @@ -235,6 +235,9 @@ func TestIntegrationDashboardInheritedFolderRBAC(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") } + if db.IsTestDBSpanner() { + t.Skip("skipping integration test") + } // the maximux nested folder hierarchy starting from parent down to subfolders nestedFolders := make([]*folder.Folder, 0, folder.MaxNestedFolderDepth+1) diff --git a/pkg/services/sqlstore/permissions/dashboard_test.go b/pkg/services/sqlstore/permissions/dashboard_test.go index 69ca82560d6..69f62f7b494 100644 --- a/pkg/services/sqlstore/permissions/dashboard_test.go +++ b/pkg/services/sqlstore/permissions/dashboard_test.go @@ -445,7 +445,9 @@ func TestIntegration_DashboardNestedPermissionFilter(t *testing.T) { expectedResult: []string{"parent"}, }, } - + if db.IsTestDBSpanner() { + t.Skip("skipping integration test") + } origNewGuardian := guardian.New guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanViewValue: true, CanSaveValue: true}) t.Cleanup(func() { @@ -558,7 +560,9 @@ func TestIntegration_DashboardNestedPermissionFilter_WithSelfContainedPermission expectedResult: []string{"parent"}, }, } - + if db.IsTestDBSpanner() { + t.Skip("skipping integration test") + } origNewGuardian := guardian.New guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanViewValue: true, CanSaveValue: true}) t.Cleanup(func() { @@ -672,6 +676,10 @@ func TestIntegration_DashboardNestedPermissionFilter_WithActionSets(t *testing.T }, } + if db.IsTestDBSpanner() { + t.Skip("skipping integration test") + } + origNewGuardian := guardian.New guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanViewValue: true, CanSaveValue: true}) t.Cleanup(func() { From 9968576acf669c2e9c2b7a8026a8c4470608ee64 Mon Sep 17 00:00:00 2001 From: Stephanie Hingtgen Date: Tue, 29 Apr 2025 18:03:21 -0600 Subject: [PATCH 013/849] Folders & Dashboards: Cleanup timestamps and error codes (#104665) K8s: Fix timestamps and error codes --- .../apis/dashboard/legacy/sql_dashboards.go | 3 +- pkg/registry/apis/folders/legacy_storage.go | 2 +- .../dashboards/service/dashboard_service.go | 4 ++- pkg/services/folder/folderimpl/conversions.go | 4 +-- .../folder/folderimpl/conversions_test.go | 4 +-- .../folderimpl/folder_unifiedstorage_test.go | 28 +++++++++++++++---- 6 files changed, 33 insertions(+), 12 deletions(-) diff --git a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go index 3c032613b05..63048f6f7b2 100644 --- a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go +++ b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go @@ -10,6 +10,7 @@ import ( "sync" "time" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/ptr" @@ -434,7 +435,7 @@ func (a *dashboardSqlAccess) SaveDashboard(ctx context.Context, orgId int64, das return nil, created, err } if failOnExisting && !created { - return nil, created, dashboards.ErrDashboardWithSameUIDExists + return nil, created, apierrors.NewConflict(dashboardV1.DashboardResourceInfo.GroupResource(), dash.Name, dashboards.ErrDashboardWithSameUIDExists) } out, err := a.dashStore.SaveDashboard(ctx, *cmd) diff --git a/pkg/registry/apis/folders/legacy_storage.go b/pkg/registry/apis/folders/legacy_storage.go index b813f066a1a..f71993bfea2 100644 --- a/pkg/registry/apis/folders/legacy_storage.go +++ b/pkg/registry/apis/folders/legacy_storage.go @@ -273,7 +273,7 @@ func (s *legacyStorage) Update(ctx context.Context, NewParentUID: newParent, }) if err != nil { - return nil, created, fmt.Errorf("error changing parent folder spec") + return nil, created, err } } diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index e1cf5e22ff7..cffd14ccca0 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -2274,7 +2274,9 @@ func (dr *DashboardServiceImpl) unstructuredToLegacyDashboardWithUsers(item *uns out.Created = obj.GetCreationTimestamp().Time updated, err := obj.GetUpdatedTimestamp() if err == nil && updated != nil { - out.Updated = *updated + // old apis return in local time, created is already doing that + localTime := updated.Local() + out.Updated = localTime } else { // by default, set updated to created out.Updated = out.Created diff --git a/pkg/services/folder/folderimpl/conversions.go b/pkg/services/folder/folderimpl/conversions.go index fcb9cf79d58..03e4d560756 100644 --- a/pkg/services/folder/folderimpl/conversions.go +++ b/pkg/services/folder/folderimpl/conversions.go @@ -36,12 +36,12 @@ func parseUnstructuredToLegacyFolder(item *unstructured.Unstructured) (*folder.F url = dashboards.GetFolderURL(uid, slug) } - created := meta.GetCreationTimestamp().UTC() + created := meta.GetCreationTimestamp().Local() updated, _ := meta.GetUpdatedTimestamp() if updated == nil { updated = &created } else { - tmp := updated.UTC() + tmp := updated.Local() updated = &tmp } diff --git a/pkg/services/folder/folderimpl/conversions_test.go b/pkg/services/folder/folderimpl/conversions_test.go index a136cec785d..1ad648a1e0b 100644 --- a/pkg/services/folder/folderimpl/conversions_test.go +++ b/pkg/services/folder/folderimpl/conversions_test.go @@ -45,7 +45,7 @@ func TestFolderConversions(t *testing.T) { require.NoError(t, err) created, err := time.Parse(time.RFC3339, "2022-12-02T02:02:02Z") - created = created.UTC() + created = created.Local() require.NoError(t, err) fake := usertest.NewUserServiceFake() @@ -232,7 +232,7 @@ func TestFolderListConversions(t *testing.T) { require.NoError(t, err) created, err := time.Parse(time.RFC3339, "2022-12-02T02:02:02Z") - created = created.UTC() + created = created.Local() require.NoError(t, err) fake := usertest.NewUserServiceFake() diff --git a/pkg/services/folder/folderimpl/folder_unifiedstorage_test.go b/pkg/services/folder/folderimpl/folder_unifiedstorage_test.go index 78943c926a9..b0f9439e58e 100644 --- a/pkg/services/folder/folderimpl/folder_unifiedstorage_test.go +++ b/pkg/services/folder/folderimpl/folder_unifiedstorage_test.go @@ -54,6 +54,24 @@ func (r rcp) GetRestConfig(ctx context.Context) (*clientrest.Config, error) { }, nil } +func compareFoldersNormalizeTime(t *testing.T, expected, actual *folder.Folder) { + require.Equal(t, expected.Title, actual.Title) + require.Equal(t, expected.UID, actual.UID) + require.Equal(t, expected.OrgID, actual.OrgID) + require.Equal(t, expected.URL, actual.URL) + require.Equal(t, expected.Fullpath, actual.Fullpath) + require.Equal(t, expected.FullpathUIDs, actual.FullpathUIDs) + require.Equal(t, expected.CreatedByUID, actual.CreatedByUID) + require.Equal(t, expected.UpdatedByUID, actual.UpdatedByUID) + require.Equal(t, expected.ParentUID, actual.ParentUID) + require.Equal(t, expected.Description, actual.Description) + require.Equal(t, expected.HasACL, actual.HasACL) + require.Equal(t, expected.Version, actual.Version) + require.Equal(t, expected.ManagedBy, actual.ManagedBy) + require.Equal(t, expected.Created.Local(), actual.Created.Local()) + require.Equal(t, expected.Updated.Local(), actual.Updated.Local()) +} + func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") @@ -313,7 +331,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) { SignedInUser: usr, }) require.NoError(t, err) - require.Equal(t, f, actualFolder) + compareFoldersNormalizeTime(t, f, actualFolder) }) t.Run("When creating folder should return error if uid is general", func(t *testing.T) { @@ -403,8 +421,8 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) { OrgID: fooFolder.OrgID, SignedInUser: usr, }) - require.Equal(t, fooFolder, actual) require.NoError(t, err) + compareFoldersNormalizeTime(t, fooFolder, actual) }) t.Run("When get folder by uid and uid is general should return the root folder object", func(t *testing.T) { @@ -437,8 +455,8 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) { } actual, err := folderService.Get(context.Background(), query) - require.Equal(t, fooFolder, actual) require.NoError(t, err) + compareFoldersNormalizeTime(t, fooFolder, actual) }) t.Run("When get folder by non existing ID should return not found error", func(t *testing.T) { @@ -471,8 +489,8 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) { } actual, err := folderService.Get(context.Background(), query) - require.Equal(t, fooFolder, actual) require.NoError(t, err) + compareFoldersNormalizeTime(t, fooFolder, actual) }) t.Run("When get folder by non existing Title should return not found error", func(t *testing.T) { @@ -847,7 +865,7 @@ func TestGetFoldersFromApiServer(t *testing.T) { CreatedByUID: ":0", UpdatedByUID: ":0", } - require.Equal(t, expectedResult, result) + compareFoldersNormalizeTime(t, expectedResult, result) fakeK8sClient.AssertExpectations(t) }) } From 129e8bb1e4999eed5288f2ba6cb4673cdd0b7295 Mon Sep 17 00:00:00 2001 From: Stephanie Hingtgen Date: Tue, 29 Apr 2025 18:03:57 -0600 Subject: [PATCH 014/849] Dashboards: Fix moving to general folder (#104655) --- pkg/registry/apis/dashboard/register.go | 2 +- .../dashboard/integration/api_validation_test.go | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/pkg/registry/apis/dashboard/register.go b/pkg/registry/apis/dashboard/register.go index 7874457bbd2..ee5219e38f8 100644 --- a/pkg/registry/apis/dashboard/register.go +++ b/pkg/registry/apis/dashboard/register.go @@ -332,7 +332,7 @@ func (b *DashboardsAPIBuilder) validateUpdate(ctx context.Context, a admission.A } // Validate folder existence if specified and changed - if !a.IsDryRun() && newAccessor.GetFolder() != oldAccessor.GetFolder() { + if !a.IsDryRun() && newAccessor.GetFolder() != oldAccessor.GetFolder() && newAccessor.GetFolder() != "" { id, err := identity.GetRequester(ctx) if err != nil { return fmt.Errorf("error getting requester: %w", err) diff --git a/pkg/tests/apis/dashboard/integration/api_validation_test.go b/pkg/tests/apis/dashboard/integration/api_validation_test.go index 3992ad69f71..a7422606b4d 100644 --- a/pkg/tests/apis/dashboard/integration/api_validation_test.go +++ b/pkg/tests/apis/dashboard/integration/api_validation_test.go @@ -246,6 +246,20 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { _, err := createDashboard(t, adminClient, "Dashboard in Non-existent Folder", &nonExistentFolderUID, nil) ctx.Helper.EnsureStatusError(err, http.StatusNotFound, "folders.folder.grafana.app \"non-existent-folder-uid\" not found") }) + + t.Run("allow moving folder to general folder", func(t *testing.T) { + folder1 := createFolderObject(t, "folder1", "default", "") + folder1UID := folder1.GetName() + dash, err := createDashboard(t, adminClient, "Dashboard in a Folder", &folder1UID, nil) + require.NoError(t, err) + + generalFolderUID := "" + _, err = updateDashboard(t, adminClient, dash, "Move dashboard into the General Folder", &generalFolderUID) + require.NoError(t, err) + + err = adminClient.Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) + require.NoError(t, err) + }) }) t.Run("Dashboard schema validations", func(t *testing.T) { @@ -942,7 +956,7 @@ func createDashboard(t *testing.T, client *apis.K8sResourceClient, title string, t.Helper() var folderUIDStr string - if folderUID != nil && *folderUID != "" { + if folderUID != nil { folderUIDStr = *folderUID } From 9edf2f6356d60d84c6185926d249892ec70d8c5c Mon Sep 17 00:00:00 2001 From: Stephanie Hingtgen Date: Tue, 29 Apr 2025 18:04:30 -0600 Subject: [PATCH 015/849] Folders and Dashboards: Version check fixes (#104663) Folders: Fix version mismatch errors --- pkg/registry/apis/dashboard/register.go | 10 ---------- pkg/registry/apis/folders/conversions.go | 2 ++ pkg/services/folder/folderimpl/conversions.go | 2 ++ .../folder/folderimpl/folder_unifiedstorage.go | 2 ++ pkg/services/folder/folderimpl/unifiedstore.go | 5 +++++ 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/pkg/registry/apis/dashboard/register.go b/pkg/registry/apis/dashboard/register.go index ee5219e38f8..92c09689838 100644 --- a/pkg/registry/apis/dashboard/register.go +++ b/pkg/registry/apis/dashboard/register.go @@ -352,16 +352,6 @@ func (b *DashboardsAPIBuilder) validateUpdate(ctx context.Context, a admission.A return apierrors.NewBadRequest(err.Error()) } - allowOverwrite := false // TODO: Add support for overwrite flag - // check for is someone else has written in between - if newAccessor.GetGeneration() != oldAccessor.GetGeneration() { - if allowOverwrite { - newAccessor.SetGeneration(oldAccessor.GetGeneration()) - } else { - return apierrors.NewBadRequest(dashboards.ErrDashboardVersionMismatch.Error()) - } - } - return nil } diff --git a/pkg/registry/apis/folders/conversions.go b/pkg/registry/apis/folders/conversions.go index 39ca3e1137b..bf743893816 100644 --- a/pkg/registry/apis/folders/conversions.go +++ b/pkg/registry/apis/folders/conversions.go @@ -22,6 +22,7 @@ func LegacyCreateCommandToUnstructured(cmd *folder.CreateFolderCommand) (*unstru "spec": map[string]any{ "title": cmd.Title, "description": cmd.Description, + "version": 1, }, }, } @@ -52,6 +53,7 @@ func convertToK8sResource(v *folder.Folder, namespacer request.NamespaceMapper) ResourceVersion: fmt.Sprintf("%d", v.Updated.UnixMilli()), CreationTimestamp: metav1.NewTime(v.Created), Namespace: namespacer(v.OrgID), + Generation: int64(v.Version), }, Spec: folders.FolderSpec{ Title: v.Title, diff --git a/pkg/services/folder/folderimpl/conversions.go b/pkg/services/folder/folderimpl/conversions.go index 03e4d560756..d682ad604d0 100644 --- a/pkg/services/folder/folderimpl/conversions.go +++ b/pkg/services/folder/folderimpl/conversions.go @@ -88,6 +88,7 @@ func (ss *FolderUnifiedStoreImpl) UnstructuredToLegacyFolder(ctx context.Context updaterId = creatorId } + folder.Version = int(item.GetGeneration()) folder.CreatedBy = creatorId folder.UpdatedBy = updaterId @@ -124,6 +125,7 @@ func (ss *FolderUnifiedStoreImpl) UnstructuredToLegacyFolderList(ctx context.Con updaterId = creatorId } + folder.Version = int(item.GetGeneration()) folder.CreatedBy = creatorId folder.UpdatedBy = updaterId folders = append(folders, folder) diff --git a/pkg/services/folder/folderimpl/folder_unifiedstorage.go b/pkg/services/folder/folderimpl/folder_unifiedstorage.go index b9f3633b292..8a7f5134e3d 100644 --- a/pkg/services/folder/folderimpl/folder_unifiedstorage.go +++ b/pkg/services/folder/folderimpl/folder_unifiedstorage.go @@ -591,6 +591,8 @@ func (s *Service) updateOnApiServer(ctx context.Context, cmd *folder.UpdateFolde NewTitle: cmd.NewTitle, NewDescription: cmd.NewDescription, SignedInUser: user, + Overwrite: cmd.Overwrite, + Version: cmd.Version, }) if err != nil { diff --git a/pkg/services/folder/folderimpl/unifiedstore.go b/pkg/services/folder/folderimpl/unifiedstore.go index 7cd4b0fece4..193167358ed 100644 --- a/pkg/services/folder/folderimpl/unifiedstore.go +++ b/pkg/services/folder/folderimpl/unifiedstore.go @@ -104,6 +104,11 @@ func (ss *FolderUnifiedStoreImpl) Update(ctx context.Context, cmd folder.UpdateF return nil, err } meta.SetFolder(*cmd.NewParentUID) + } else { + // only compare versions if not moving the folder + if !cmd.Overwrite && (cmd.Version != int(obj.GetGeneration())) { + return nil, dashboards.ErrDashboardVersionMismatch + } } out, err := ss.k8sclient.Update(ctx, updated, cmd.OrgID, v1.UpdateOptions{ From f0686a61cec94465c79d91c3fe3b4bb426035082 Mon Sep 17 00:00:00 2001 From: Jev Forsberg <46619047+baldm0mma@users.noreply.github.com> Date: Tue, 29 Apr 2025 18:17:38 -0600 Subject: [PATCH 016/849] Chore: Use Vault secrets in `sync-mirror-event.yml` (#104705) * baldm0mma/ update to using vault secrets * Update .github/workflows/sync-mirror-event.yml Co-authored-by: Kevin Minehart <5140827+kminehart@users.noreply.github.com> * Update .github/workflows/sync-mirror-event.yml Co-authored-by: Kevin Minehart <5140827+kminehart@users.noreply.github.com> --------- Co-authored-by: Kevin Minehart <5140827+kminehart@users.noreply.github.com> --- .github/workflows/sync-mirror-event.yml | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/.github/workflows/sync-mirror-event.yml b/.github/workflows/sync-mirror-event.yml index b9387e02069..13c9563846a 100644 --- a/.github/workflows/sync-mirror-event.yml +++ b/.github/workflows/sync-mirror-event.yml @@ -10,7 +10,8 @@ on: - "v*.*.*" - "release-*" -permissions: {} +permissions: + id-token: write # This is run after the pull request has been merged, so we'll run against the target branch jobs: @@ -22,24 +23,30 @@ jobs: env: REF_NAME: ${{ github.ref_name }} REPO: ${{ github.repository }} - SENDER: ${{ github.event.sender.login }} SHA: ${{ github.sha }} - PR_COMMIT_SHA: ${{ github.event.pull_request.head.sha }} steps: + - name: "Get vault secrets" + id: vault-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@main + with: + # Secrets placed in the ci/data/repo/grafana/grafana/delivery-bot-app path in Vault + repo_secrets: | + GRAFANA_DELIVERY_BOT_APP_PEM=delivery-bot-app:PRIVATE_KEY + - name: "Generate token" id: generate_token uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a with: # App needs Actions: Read/Write for the grafana/security-patch-actions repo - app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} - private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} + app_id: ${{ vars.DELIVERY_BOT_APP_ID }} + private_key: ${{ env.GRAFANA_DELIVERY_BOT_APP_PEM }} - uses: actions/github-script@v7 if: github.repository == 'grafana/grafana' with: github-token: ${{ steps.generate_token.outputs.token }} script: | - const {HEAD_REF, BASE_REF, REPO, SENDER, SHA} = process.env; + const {REF_NAME, REPO, SHA} = process.env; await github.rest.actions.createWorkflowDispatch({ owner: 'grafana', From f7fe8b7f7ebe48a5c11194a7967cd58b9147fbe9 Mon Sep 17 00:00:00 2001 From: Jev Forsberg <46619047+baldm0mma@users.noreply.github.com> Date: Tue, 29 Apr 2025 18:26:54 -0600 Subject: [PATCH 017/849] Chore: Use Vault secrets in `pr-patch-check-event.yml` (#104725) * baldm0mma/ update to use vault * Update .github/workflows/pr-patch-check-event.yml Co-authored-by: Kevin Minehart <5140827+kminehart@users.noreply.github.com> * Update .github/workflows/pr-patch-check-event.yml Co-authored-by: Kevin Minehart <5140827+kminehart@users.noreply.github.com> --------- Co-authored-by: Kevin Minehart <5140827+kminehart@users.noreply.github.com> --- .github/workflows/pr-patch-check-event.yml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr-patch-check-event.yml b/.github/workflows/pr-patch-check-event.yml index f7605f033ce..d16e6580c51 100644 --- a/.github/workflows/pr-patch-check-event.yml +++ b/.github/workflows/pr-patch-check-event.yml @@ -13,7 +13,9 @@ on: - "v*.*.*" - "release-*" -permissions: {} +permissions: + contents: read + id-token: write # Since this is run on a pull request, we want to apply the patches intended for the # target branch onto the source branch, to verify compatibility before merging. @@ -31,13 +33,20 @@ jobs: PR_COMMIT_SHA: ${{ github.event.pull_request.head.sha }} runs-on: ubuntu-latest steps: + - name: "Get vault secrets" + id: vault-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@main + with: + # Secrets placed in the ci/data/repo/grafana/grafana/delivery-bot-app path in Vault + repo_secrets: | + GRAFANA_DELIVERY_BOT_APP_PEM=delivery-bot-app:PRIVATE_KEY - name: "Generate token" id: generate_token uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a with: # App needs Actions: Read/Write for the grafana/security-patch-actions repo - app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} - private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} + app_id: ${{ vars.DELIVERY_BOT_APP_ID }} + private_key: ${{ env.GRAFANA_DELIVERY_BOT_APP_PEM }} - name: "Dispatch job" uses: actions/github-script@v7 with: From 32ad884379ea429b2428820bd5cc9f02d8d29ab7 Mon Sep 17 00:00:00 2001 From: Jev Forsberg <46619047+baldm0mma@users.noreply.github.com> Date: Tue, 29 Apr 2025 18:27:16 -0600 Subject: [PATCH 018/849] Chore: Use Vault secrets in `release-pr.yml` (#104723) * baldm0mma/ update to use vault * Update .github/workflows/release-pr.yml Co-authored-by: Kevin Minehart <5140827+kminehart@users.noreply.github.com> * Update .github/workflows/release-pr.yml Co-authored-by: Kevin Minehart <5140827+kminehart@users.noreply.github.com> --------- Co-authored-by: Kevin Minehart <5140827+kminehart@users.noreply.github.com> --- .github/workflows/release-pr.yml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml index 42dd7051b71..997adc59523 100644 --- a/.github/workflows/release-pr.yml +++ b/.github/workflows/release-pr.yml @@ -33,7 +33,9 @@ on: default: false type: boolean -permissions: {} +permissions: + contents: read + id-token: write jobs: push-changelog-to-main: @@ -48,9 +50,6 @@ jobs: latest: ${{ inputs.latest }} dry_run: ${{ inputs.dry_run }} target: main - secrets: - GRAFANA_DELIVERY_BOT_APP_ID: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} - GRAFANA_DELIVERY_BOT_APP_PEM: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} create-prs: permissions: @@ -64,6 +63,13 @@ jobs: LATEST: ${{ inputs.latest }} DRY_RUN: ${{ inputs.dry_run }} steps: + - name: "Get vault secrets" + id: vault-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@main + with: + # Secrets placed in the ci/data/repo/grafana/grafana/delivery-bot-app path in Vault + repo_secrets: | + GRAFANA_DELIVERY_BOT_APP_PEM=delivery-bot-app:PRIVATE_KEY - name: Get release branch id: branch uses: grafana/grafana-github-actions-go/latest-release-branch@main # zizmor: ignore[unpinned-uses] @@ -103,8 +109,8 @@ jobs: id: generate_changelog_token uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 with: - app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} - private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} + app_id: ${{ vars.DELIVERY_BOT_APP_ID }} + private_key: ${{ env.GRAFANA_DELIVERY_BOT_APP_PEM }} - name: Generate changelog id: changelog uses: ./.grafana-main/.github/actions/changelog From 403f938a664bbfab83cb4c652be1fa4fcfd9269b Mon Sep 17 00:00:00 2001 From: Jev Forsberg <46619047+baldm0mma@users.noreply.github.com> Date: Tue, 29 Apr 2025 18:28:15 -0600 Subject: [PATCH 019/849] Chore: Use Vault secrets in `migrate-prs.yml` (#104714) * baldm0mma/ update to use vault * baldm0mma/ rem zizmor comm * Update .github/workflows/migrate-prs.yml Co-authored-by: Kevin Minehart <5140827+kminehart@users.noreply.github.com> * Update .github/workflows/migrate-prs.yml Co-authored-by: Kevin Minehart <5140827+kminehart@users.noreply.github.com> --------- Co-authored-by: Kevin Minehart <5140827+kminehart@users.noreply.github.com> --- .github/workflows/migrate-prs.yml | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/.github/workflows/migrate-prs.yml b/.github/workflows/migrate-prs.yml index c40a34a6ebb..d690245be19 100644 --- a/.github/workflows/migrate-prs.yml +++ b/.github/workflows/migrate-prs.yml @@ -15,11 +15,6 @@ on: description: Owner/repo of the repository where the branch is created (e.g. 'grafana/grafana') required: true type: string - secrets: - GRAFANA_DELIVERY_BOT_APP_ID: - required: true - GRAFANA_DELIVERY_BOT_APP_PEM: - required: true workflow_dispatch: inputs: from: @@ -34,24 +29,30 @@ on: description: Owner/repo of the repository where the branch is created (e.g. 'grafana/grafana') required: true type: string - secrets: - GRAFANA_DELIVERY_BOT_APP_ID: - required: true - GRAFANA_DELIVERY_BOT_APP_PEM: - required: true + +permissions: + contents: read + id-token: write jobs: main: runs-on: ubuntu-latest steps: + - name: "Get vault secrets" + id: vault-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@main + with: + # Secrets placed in the ci/data/repo/grafana/grafana/delivery-bot-app path in Vault + repo_secrets: | + GRAFANA_DELIVERY_BOT_APP_PEM=delivery-bot-app:PRIVATE_KEY - name: "Generate token" id: generate_token uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 with: - app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} - private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} + app_id: ${{ vars.DELIVERY_BOT_APP_ID }} + private_key: ${{ env.GRAFANA_DELIVERY_BOT_APP_PEM }} - name: Migrate PRs - uses: grafana/grafana-github-actions-go/migrate-open-prs@main # zizmor: ignore[unpinned-uses] + uses: grafana/grafana-github-actions-go/migrate-open-prs@main with: token: ${{ steps.generate_token.outputs.token }} ownerRepo: ${{ inputs.ownerRepo }} From 7089b5978ed1ca2e5f75e431b86a4bf02230cb93 Mon Sep 17 00:00:00 2001 From: Jev Forsberg <46619047+baldm0mma@users.noreply.github.com> Date: Tue, 29 Apr 2025 18:28:39 -0600 Subject: [PATCH 020/849] Chore: Use Vault secrets in `create-next-release-branch.yml` (#104730) * baldm0mma/ update to use vault * Update .github/workflows/create-next-release-branch.yml Co-authored-by: Kevin Minehart <5140827+kminehart@users.noreply.github.com> * Update .github/workflows/create-next-release-branch.yml Co-authored-by: Kevin Minehart <5140827+kminehart@users.noreply.github.com> --------- Co-authored-by: Kevin Minehart <5140827+kminehart@users.noreply.github.com> --- .../workflows/create-next-release-branch.yml | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/.github/workflows/create-next-release-branch.yml b/.github/workflows/create-next-release-branch.yml index 1107842a765..6a48327dc04 100644 --- a/.github/workflows/create-next-release-branch.yml +++ b/.github/workflows/create-next-release-branch.yml @@ -10,11 +10,6 @@ on: description: The release branch to increment (eg providing `release-11.2.3` will result in `release-11.2.4` being created) type: string required: true - secrets: - GRAFANA_DELIVERY_BOT_APP_ID: - required: true - GRAFANA_DELIVERY_BOT_APP_PEM: - required: true outputs: branch: description: The new branch that was created @@ -27,23 +22,30 @@ on: description: The release branch to increment (eg providing `release-11.2.3` will result in `release-11.2.4` being created) type: string required: true - secrets: - GRAFANA_DELIVERY_BOT_APP_ID: - required: true - GRAFANA_DELIVERY_BOT_APP_PEM: - required: true + +permissions: + contents: read + id-token: write + jobs: main: runs-on: ubuntu-latest outputs: branch: ${{ steps.branch.outputs.branch }} steps: + - name: "Get vault secrets" + id: vault-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@main + with: + # Secrets placed in the ci/data/repo/grafana/grafana/delivery-bot-app path in Vault + repo_secrets: | + GRAFANA_DELIVERY_BOT_APP_PEM=delivery-bot-app:PRIVATE_KEY - name: "Generate token" id: generate_token uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 with: - app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} - private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} + app_id: ${{ vars.DELIVERY_BOT_APP_ID }} + private_key: ${{ env.GRAFANA_DELIVERY_BOT_APP_PEM }} - name: Create release branch id: branch uses: grafana/grafana-github-actions-go/bump-release@main # zizmor: ignore[unpinned-uses] From fa17fd108feb99e668a83cb439ef60afed390ee2 Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Tue, 29 Apr 2025 19:39:46 -0500 Subject: [PATCH 021/849] Transformations: Omit showing base field names when field.name is unique (#104660) --- .../app/features/transformers/utils.test.ts | 21 +++++++++++++++++++ public/app/features/transformers/utils.ts | 18 +++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/public/app/features/transformers/utils.test.ts b/public/app/features/transformers/utils.test.ts index 536ffbea701..095ff0c5fa1 100644 --- a/public/app/features/transformers/utils.test.ts +++ b/public/app/features/transformers/utils.test.ts @@ -90,4 +90,25 @@ describe('useAllFieldNamesFromDataFrames', () => { expect(names).toEqual(['T', 'N', 'S', 'T (A)', 'N (A)', 'S (A)', 'T (B)', 'N (B)', 'S (B)']); }); + + it('omit base names when field.name is unique', () => { + let frames = [ + toDataFrame({ + refId: 'A', + fields: [ + { name: 'T', config: { displayName: 't' }, type: FieldType.time, values: [1, 2, 3] }, + { name: 'N', config: { displayName: 'n' }, type: FieldType.number, values: [100, 200, 300] }, + { name: 'S', config: { displayName: 's' }, type: FieldType.string, values: ['1', '2', '3'] }, + ], + }), + toDataFrame({ + refId: 'B', + fields: [{ name: 'T', config: { displayName: 't2' }, type: FieldType.time, values: [1, 2, 3] }], + }), + ]; + + const names = getAllFieldNamesFromDataFrames(frames, true); + + expect(names).toEqual(['T', 't', 'n', 's', 't2']); + }); }); diff --git a/public/app/features/transformers/utils.ts b/public/app/features/transformers/utils.ts index 9109c5c0411..540de803932 100644 --- a/public/app/features/transformers/utils.ts +++ b/public/app/features/transformers/utils.ts @@ -16,7 +16,23 @@ export const getAllFieldNamesFromDataFrames = (frames: DataFrame[], withBaseFiel let names = frames.flatMap((frame) => frame.fields.map((field) => getFieldDisplayName(field, frame, frames))); if (withBaseFieldNames) { - let baseNames = frames.flatMap((frame) => frame.fields.map((field) => field.name)); + // only add base names of fields that have same field.name + let baseNameCounts = new Map(); + + frames.forEach((frame) => + frame.fields.forEach((field) => { + let count = baseNameCounts.get(field.name) ?? 0; + baseNameCounts.set(field.name, count + 1); + }) + ); + + let baseNames: string[] = []; + + baseNameCounts.forEach((count, name) => { + if (count > 1) { + baseNames.push(name); + } + }); // prepend base names + uniquify names = [...new Set(baseNames.concat(names))]; From 75a226c4c7ba605cd4b28869e02750acea8e763f Mon Sep 17 00:00:00 2001 From: Kristina Date: Tue, 29 Apr 2025 19:58:08 -0500 Subject: [PATCH 022/849] Transformation: Allow boolean for calculate field (#104659) --- .../CalculateFieldTransformerEditor.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/transformers/editors/CalculateFieldTransformerEditor/CalculateFieldTransformerEditor.tsx b/public/app/features/transformers/editors/CalculateFieldTransformerEditor/CalculateFieldTransformerEditor.tsx index 7b36031b0ec..42080df35cb 100644 --- a/public/app/features/transformers/editors/CalculateFieldTransformerEditor/CalculateFieldTransformerEditor.tsx +++ b/public/app/features/transformers/editors/CalculateFieldTransformerEditor/CalculateFieldTransformerEditor.tsx @@ -56,7 +56,7 @@ if (cfg.featureToggles.addFieldFromCalculationStatFunctions) { ); } -const okTypes = new Set([FieldType.time, FieldType.number, FieldType.string]); +const okTypes = new Set([FieldType.time, FieldType.number, FieldType.string, FieldType.boolean]); export const CalculateFieldTransformerEditor = (props: CalculateFieldTransformerEditorProps) => { const { options, onChange, input } = props; From bb9c56c9d5a67f0c629f7877e480a45d849c58b3 Mon Sep 17 00:00:00 2001 From: Kevin Minehart <5140827+kminehart@users.noreply.github.com> Date: Tue, 29 Apr 2025 20:19:36 -0500 Subject: [PATCH 023/849] CI: use shallow clone with backport action (#104750) use shallow clone with backport action --- .github/workflows/backport.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 673dc228fc2..278f7ac2209 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -18,6 +18,8 @@ jobs: uses: actions/checkout@v4 # 4.2.2 with: persist-credentials: false + fetch-depth: 1 + fetch-tags: false - run: git config --local user.name "github-actions[bot]" - run: git config --local user.email "github-actions[bot]@users.noreply.github.com" - run: git config --local --add --bool push.autoSetupRemote true From 6d1f918150f68cfe59d42121f8e2415feb39af11 Mon Sep 17 00:00:00 2001 From: Kevin Minehart <5140827+kminehart@users.noreply.github.com> Date: Tue, 29 Apr 2025 20:56:29 -0500 Subject: [PATCH 024/849] CI: manually `git clone` for backport action (#104751) * manually clone for backport * fix syntax error --- .github/workflows/backport.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 278f7ac2209..ef014739728 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -14,12 +14,8 @@ jobs: if: github.repository == 'grafana/grafana' runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v4 # 4.2.2 - with: - persist-credentials: false - fetch-depth: 1 - fetch-tags: false + - name: Clone + run: git clone --depth=1 "https://github.com/grafana/grafana.git" - run: git config --local user.name "github-actions[bot]" - run: git config --local user.email "github-actions[bot]@users.noreply.github.com" - run: git config --local --add --bool push.autoSetupRemote true From d739481c9a2a830e0f4cd98f1299f41aba020c98 Mon Sep 17 00:00:00 2001 From: Kristina Date: Tue, 29 Apr 2025 21:00:43 -0500 Subject: [PATCH 025/849] Transformations: Use field name matcher for finding group by fields (#104664) Co-authored-by: Leon Sorokin --- .../transformers/groupBy.test.ts | 46 +++++++++++++++++++ .../transformations/transformers/groupBy.ts | 28 +++++++---- 2 files changed, 64 insertions(+), 10 deletions(-) diff --git a/packages/grafana-data/src/transformations/transformers/groupBy.test.ts b/packages/grafana-data/src/transformations/transformers/groupBy.test.ts index abfcc57c2c0..64a81b033df 100644 --- a/packages/grafana-data/src/transformations/transformers/groupBy.test.ts +++ b/packages/grafana-data/src/transformations/transformers/groupBy.test.ts @@ -461,4 +461,50 @@ describe('GroupBy transformer', () => { expect(result[0].fields).toEqual(expected); }); }); + + it('should match on base name if did not match on displayName', async () => { + const testSeries = toDataFrame({ + name: 'A', + fields: [ + { name: 'message', type: FieldType.string, values: ['A', 'A'], config: { displayName: 'MyMessage' } }, + { name: 'values', type: FieldType.number, values: [1, 2] }, + ], + }); + + const cfg: DataTransformerConfig = { + id: DataTransformerID.groupBy, + options: { + fields: { + message: { + operation: GroupByOperationID.groupBy, + aggregations: [], + }, + values: { + operation: GroupByOperationID.aggregate, + aggregations: [ReducerID.sum], + }, + }, + }, + }; + + await expect(transformDataFrame([cfg], [testSeries])).toEmitValuesWith((received) => { + const result = received[0]; + const expected: Field[] = [ + { + name: 'message', + type: FieldType.string, + values: ['A'], + config: { displayName: 'MyMessage' }, + }, + { + name: 'values (sum)', + type: FieldType.number, + values: [3], + config: {}, + }, + ]; + + expect(result[0].fields).toEqual(expected); + }); + }); }); diff --git a/packages/grafana-data/src/transformations/transformers/groupBy.ts b/packages/grafana-data/src/transformations/transformers/groupBy.ts index 3ee7b106ffb..ebc878547f1 100644 --- a/packages/grafana-data/src/transformations/transformers/groupBy.ts +++ b/packages/grafana-data/src/transformations/transformers/groupBy.ts @@ -4,6 +4,8 @@ import { getFieldDisplayName } from '../../field/fieldState'; import { DataFrame, Field } from '../../types/dataFrame'; import { DataTransformerInfo, TransformationApplicabilityLevels } from '../../types/transformations'; import { getFieldTypeForReducer, reduceField, ReducerID } from '../fieldReducer'; +import { getFieldMatcher } from '../matchers'; +import { FieldMatcherID } from '../matchers/ids'; import { DataTransformerID } from './ids'; import { findMaxFields } from './utils'; @@ -57,20 +59,31 @@ export const groupByTransformer: DataTransformerInfo operator: (options) => (source) => source.pipe( map((data) => { - const hasValidConfig = Object.keys(options.fields).find( - (name) => options.fields[name].operation === GroupByOperationID.groupBy - ); + const groupByFieldNames: string[] = []; - if (!hasValidConfig) { + for (const [k, v] of Object.entries(options.fields)) { + if (v.operation === GroupByOperationID.groupBy) { + groupByFieldNames.push(k); + } + } + + if (groupByFieldNames.length === 0) { return data; } + const matcher = getFieldMatcher({ + id: FieldMatcherID.byNames, + options: { names: groupByFieldNames }, + }); + const processed: DataFrame[] = []; for (const frame of data) { // Create a list of fields to group on // If there are none we skip the rest - const groupByFields: Field[] = frame.fields.filter((field) => shouldGroupOnField(field, options)); + + const groupByFields: Field[] = frame.fields.filter((field) => matcher(field, frame, data)); + if (groupByFields.length === 0) { continue; } @@ -131,11 +144,6 @@ export const groupByTransformer: DataTransformerInfo ), }; -const shouldGroupOnField = (field: Field, options: GroupByTransformerOptions): boolean => { - const fieldName = getFieldDisplayName(field); - return options?.fields[fieldName]?.operation === GroupByOperationID.groupBy; -}; - const shouldCalculateField = (field: Field, options: GroupByTransformerOptions): boolean => { const fieldName = getFieldDisplayName(field); return ( From 24351851c916df9650414db5041bb959d7bd4732 Mon Sep 17 00:00:00 2001 From: Kevin Minehart <5140827+kminehart@users.noreply.github.com> Date: Tue, 29 Apr 2025 21:01:09 -0500 Subject: [PATCH 026/849] CI: `cd grafana` after clone in backport action (#104752) cd grafana after clone --- .github/workflows/backport.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index ef014739728..54c79b1ca28 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Clone - run: git clone --depth=1 "https://github.com/grafana/grafana.git" + run: git clone --depth=1 "https://github.com/grafana/grafana.git" && cd grafana - run: git config --local user.name "github-actions[bot]" - run: git config --local user.email "github-actions[bot]@users.noreply.github.com" - run: git config --local --add --bool push.autoSetupRemote true From be729ea5626d283949c2213d2f68f6e181c5baa1 Mon Sep 17 00:00:00 2001 From: Kevin Minehart <5140827+kminehart@users.noreply.github.com> Date: Tue, 29 Apr 2025 21:17:05 -0500 Subject: [PATCH 027/849] CI: cd grafana in backport action (#104753) cd grafana --- .github/workflows/backport.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 54c79b1ca28..c333e6f2386 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -15,16 +15,16 @@ jobs: runs-on: ubuntu-latest steps: - name: Clone - run: git clone --depth=1 "https://github.com/grafana/grafana.git" && cd grafana - - run: git config --local user.name "github-actions[bot]" - - run: git config --local user.email "github-actions[bot]@users.noreply.github.com" - - run: git config --local --add --bool push.autoSetupRemote true + run: git clone --depth=1 "https://github.com/grafana/grafana.git" + - run: cd grafana && git config --local user.name "github-actions[bot]" + - run: cd grafana && git config --local user.email "github-actions[bot]@users.noreply.github.com" + - run: cd grafana && git config --local --add --bool push.autoSetupRemote true - name: Set remote URL env: GIT_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - git remote set-url origin "https://grafana-delivery-bot:$GIT_TOKEN@github.com/grafana/grafana.git" + run: cd grafana && git remote set-url origin "https://grafana-delivery-bot:$GIT_TOKEN@github.com/grafana/grafana.git" - name: Run backport + working-directory: grafana uses: grafana/grafana-github-actions-go/backport@main # zizmor: ignore[unpinned-uses] with: token: ${{ secrets.GITHUB_TOKEN }} From 7430a18bd3a5a6cc80c5784e39d22185e5701dc5 Mon Sep 17 00:00:00 2001 From: Stephanie Hingtgen Date: Tue, 29 Apr 2025 20:23:52 -0600 Subject: [PATCH 028/849] Dashboards: Fix missing folder info in /search for dashboards (#104666) Dashboards: add missing folder info to /search --- .../dashboards/service/dashboard_service.go | 27 +++++++++++++++---- .../service/dashboard_service_test.go | 9 +++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index cffd14ccca0..9256c615779 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -1541,6 +1541,13 @@ func (dr *DashboardServiceImpl) FindDashboards(ctx context.Context, query *dashb finalResults := make([]dashboards.DashboardSearchProjection, len(response.Hits)) for i, hit := range response.Hits { + folderTitle := "" + folderID := int64(0) + if f, ok := folderNames[hit.Folder]; ok { + folderTitle = f.Title + folderID = f.ID + } + result := dashboards.DashboardSearchProjection{ ID: hit.Field.GetNestedInt64(resource.SEARCH_FIELD_LEGACY_ID), UID: hit.Name, @@ -1549,7 +1556,9 @@ func (dr *DashboardServiceImpl) FindDashboards(ctx context.Context, query *dashb Slug: slugify.Slugify(hit.Title), IsFolder: false, FolderUID: hit.Folder, - FolderTitle: folderNames[hit.Folder], + FolderTitle: folderTitle, + FolderID: folderID, + FolderSlug: slugify.Slugify(folderTitle), Tags: hit.Tags, } @@ -1574,7 +1583,12 @@ func (dr *DashboardServiceImpl) FindDashboards(ctx context.Context, query *dashb return dr.dashboardStore.FindDashboards(ctx, query) } -func (dr *DashboardServiceImpl) fetchFolderNames(ctx context.Context, query *dashboards.FindPersistedDashboardsQuery, hits []dashboardv0.DashboardHit) (map[string]string, error) { +type folderRes struct { + Title string + ID int64 +} + +func (dr *DashboardServiceImpl) fetchFolderNames(ctx context.Context, query *dashboards.FindPersistedDashboardsQuery, hits []dashboardv0.DashboardHit) (map[string]folderRes, error) { // call this with elevated permissions so we can get folder names where user does not have access // some dashboards are shared directly with user, but the folder is not accessible via the folder permissions serviceCtx, serviceIdent := identity.WithServiceIdentity(ctx, query.OrgId) @@ -1589,9 +1603,12 @@ func (dr *DashboardServiceImpl) fetchFolderNames(ctx context.Context, query *das return nil, folder.ErrInternal.Errorf("failed to fetch parent folders: %w", err) } - folderNames := make(map[string]string) + folderNames := make(map[string]folderRes) for _, f := range folders { - folderNames[f.UID] = f.Title + folderNames[f.UID] = folderRes{ + Title: f.Title, + ID: f.ID, + } } return folderNames, nil } @@ -1671,7 +1688,7 @@ func makeQueryResult(query *dashboards.FindPersistedDashboardsQuery, res []dashb } // nolint:staticcheck - if item.FolderID > 0 { + if item.FolderID > 0 || item.FolderUID != "" { hit.FolderURL = dashboards.GetFolderURL(item.FolderUID, item.FolderSlug) } diff --git a/pkg/services/dashboards/service/dashboard_service_test.go b/pkg/services/dashboards/service/dashboard_service_test.go index a6607d36800..ba0407dc932 100644 --- a/pkg/services/dashboards/service/dashboard_service_test.go +++ b/pkg/services/dashboards/service/dashboard_service_test.go @@ -1586,6 +1586,8 @@ func TestSearchDashboards(t *testing.T) { }, FolderTitle: "testing-folder-1", FolderUID: "f1", + FolderID: 1, + FolderURL: "/dashboards/f/f1/testing-folder-1", }, { UID: "uid2", @@ -1597,6 +1599,8 @@ func TestSearchDashboards(t *testing.T) { Tags: []string{}, FolderTitle: "testing-folder-1", FolderUID: "f1", + FolderID: 1, + FolderURL: "/dashboards/f/f1/testing-folder-1", }, } query := dashboards.FindPersistedDashboardsQuery{ @@ -1612,7 +1616,9 @@ func TestSearchDashboards(t *testing.T) { Title: "Dashboard 1", Tags: []string{"tag1", "tag2"}, FolderTitle: "testing-folder-1", + FolderSlug: "testing-folder-1", FolderUID: "f1", + FolderID: 1, }, { UID: "uid2", @@ -1620,7 +1626,9 @@ func TestSearchDashboards(t *testing.T) { OrgID: 1, Title: "Dashboard 2", FolderTitle: "testing-folder-1", + FolderSlug: "testing-folder-1", FolderUID: "f1", + FolderID: 1, }, }, nil).Once() result, err := service.SearchDashboards(context.Background(), &query) @@ -1635,6 +1643,7 @@ func TestSearchDashboards(t *testing.T) { { UID: "f1", Title: "testing-folder-1", + ID: 1, }, } fakeFolders.ExpectedHitList = expectedFolders From 645af8df333cdec64a12408e4bfec9cacdc02aa4 Mon Sep 17 00:00:00 2001 From: Kevin Minehart <5140827+kminehart@users.noreply.github.com> Date: Tue, 29 Apr 2025 21:30:52 -0500 Subject: [PATCH 029/849] CI: Backport action can't combine `with` and `working-directory` (#104754) * Can't combine with and working-directory * add missing uses: --- .github/workflows/backport.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index c333e6f2386..07e64fe437b 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -24,7 +24,7 @@ jobs: GIT_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: cd grafana && git remote set-url origin "https://grafana-delivery-bot:$GIT_TOKEN@github.com/grafana/grafana.git" - name: Run backport - working-directory: grafana - uses: grafana/grafana-github-actions-go/backport@main # zizmor: ignore[unpinned-uses] + uses: grafana/grafana-github-actions-go/backport@main with: + path: grafana token: ${{ secrets.GITHUB_TOKEN }} From 3a8575ea1b9ee6500803139beb43ab438ee8d55c Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 30 Apr 2025 10:06:44 +0300 Subject: [PATCH 030/849] Provisioning: Show in NavTree based on org role, not access control (#104599) --- pkg/services/navtree/navtreeimpl/admin.go | 11 ++++++----- .../provisioning/GettingStarted/GettingStarted.tsx | 5 +---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/pkg/services/navtree/navtreeimpl/admin.go b/pkg/services/navtree/navtreeimpl/admin.go index 6b1d0428ed9..a9db04da82c 100644 --- a/pkg/services/navtree/navtreeimpl/admin.go +++ b/pkg/services/navtree/navtreeimpl/admin.go @@ -1,6 +1,7 @@ package navtreeimpl import ( + "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/login/social" ac "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/ssoutils" @@ -60,15 +61,15 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink Url: s.cfg.AppSubURL + "/admin/migrate-to-cloud", }) } - if hasAccess(ac.EvalPermission(ac.ActionSettingsRead, ac.ScopeSettingsAll)) { - provisioningNode := &navtree.NavLink{ + if c.HasRole(identity.RoleAdmin) && + (s.cfg.StackID == "" || // show OnPrem even when provisioning is disabled + s.features.IsEnabledGlobally(featuremgmt.FlagProvisioning)) { + configNodes = append(configNodes, &navtree.NavLink{ Text: "Provisioning", Id: "provisioning", SubTitle: "View and manage your provisioning connections", Url: s.cfg.AppSubURL + "/admin/provisioning", - } - - configNodes = append(configNodes, provisioningNode) + }) } generalNode := &navtree.NavLink{ diff --git a/public/app/features/provisioning/GettingStarted/GettingStarted.tsx b/public/app/features/provisioning/GettingStarted/GettingStarted.tsx index 4c9a4563f8f..e239f511a7e 100644 --- a/public/app/features/provisioning/GettingStarted/GettingStarted.tsx +++ b/public/app/features/provisioning/GettingStarted/GettingStarted.tsx @@ -18,11 +18,8 @@ const featureIni = `# In your custom.ini file [feature_toggles] provisioning = true -kubernetesClientDashboardsFolders = true kubernetesDashboards = true ; use k8s from browser - -# If you want easy kubectl setup development mode -grafanaAPIServerEnsureKubectlAccess = true`; +`; const ngrokExample = `ngrok http 3000 From 971f92e45d41c51f0fd464f2dd6b36c64f567d8a Mon Sep 17 00:00:00 2001 From: Pepe Cano <825430+ppcano@users.noreply.github.com> Date: Wed, 30 Apr 2025 11:15:51 +0200 Subject: [PATCH 031/849] docs(alerting): add recommendation to reduce duplicated `DatasourceError` alerts (#104679) docs(alerting): add recommendation to reduce `DatasourceError` alerts --- .../fundamentals/alert-rule-evaluation/state-and-health.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/sources/alerting/fundamentals/alert-rule-evaluation/state-and-health.md b/docs/sources/alerting/fundamentals/alert-rule-evaluation/state-and-health.md index b7987e29828..cf877958a02 100644 --- a/docs/sources/alerting/fundamentals/alert-rule-evaluation/state-and-health.md +++ b/docs/sources/alerting/fundamentals/alert-rule-evaluation/state-and-health.md @@ -39,6 +39,11 @@ refs: destination: /docs/grafana//alerting/fundamentals/notifications/ - pattern: /docs/grafana-cloud/ destination: /docs/grafana-cloud/alerting-and-irm/alerting/fundamentals/notifications/ + notification-policies: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/fundamentals/notifications/notification-policies/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/fundamentals/notifications/notification-policies/ --- # State and health of alerts @@ -131,6 +136,8 @@ To minimize the number of **No Data** or **Error** state alerts received, try th 1. Change the default [evaluation time out](https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#evaluation_timeout). The default is set at 30 seconds. To increase the default evaluation timeout, open a support ticket from the [Cloud Portal](https://grafana.com/docs/grafana-cloud/account-management/support/#grafana-cloud-support-options). Note that this should be a last resort, because it may affect the performance of all alert rules and cause missed evaluations if the timeout is too long. +1. To reduce multiple notifications from **Error** alerts, define a [notification policy](ref:notification-policies) to handle all related alerts with `alertname=DatasourceError`, and filter and group errors from the same data source using the `datasource_uid` label. + ### Keep last state The "Keep Last State" option helps mitigate temporary data source issues, preventing alerts from unintentionally firing, resolving, and re-firing. From afa97a5970b3eeba6f8d8c3be84bec52a7ab71bf Mon Sep 17 00:00:00 2001 From: linoman <2051016+linoman@users.noreply.github.com> Date: Wed, 30 Apr 2025 11:33:26 +0200 Subject: [PATCH 032/849] SCIM: Rename `assertion_attribute_external_uid` (#104613) Rename `assertion_attribute_external_uid` --- .../ssosettings/strategies/saml_strategy.go | 76 +++++++++---------- .../strategies/saml_strategy_test.go | 76 +++++++++---------- 2 files changed, 76 insertions(+), 76 deletions(-) diff --git a/pkg/services/ssosettings/strategies/saml_strategy.go b/pkg/services/ssosettings/strategies/saml_strategy.go index a863b9f491e..99c0dd9fd81 100644 --- a/pkg/services/ssosettings/strategies/saml_strategy.go +++ b/pkg/services/ssosettings/strategies/saml_strategy.go @@ -32,44 +32,44 @@ func (s *SAMLStrategy) GetProviderConfig(_ context.Context, provider string) (ma func (s *SAMLStrategy) loadSAMLSettings() map[string]any { section := s.settingsProvider.Section("auth.saml") result := map[string]any{ - "allow_idp_initiated": section.KeyValue("allow_idp_initiated").MustBool(false), - "allow_sign_up": section.KeyValue("allow_sign_up").MustBool(false), - "allowed_organizations": section.KeyValue("allowed_organizations").MustString(""), - "assertion_attribute_email": section.KeyValue("assertion_attribute_email").MustString(""), - "assertion_attribute_groups": section.KeyValue("assertion_attribute_groups").MustString(""), - "assertion_attribute_login": section.KeyValue("assertion_attribute_login").MustString(""), - "assertion_attribute_name": section.KeyValue("assertion_attribute_name").MustString(""), - "assertion_attribute_org": section.KeyValue("assertion_attribute_org").MustString(""), - "assertion_attribute_role": section.KeyValue("assertion_attribute_role").MustString(""), - "auto_login": section.KeyValue("auto_login").MustBool(false), - "certificate": section.KeyValue("certificate").MustString(""), - "certificate_path": section.KeyValue("certificate_path").MustString(""), - "client_id": section.KeyValue("client_id").MustString(""), - "client_secret": section.KeyValue("client_secret").MustString(""), - "enabled": section.KeyValue("enabled").MustBool(false), - "entity_id": section.KeyValue("entity_id").MustString(""), - "external_uid_assertion_name": section.KeyValue("external_uid_assertion_name").MustString(""), - "force_use_graph_api": section.KeyValue("force_use_graph_api").MustBool(false), - "idp_metadata": section.KeyValue("idp_metadata").MustString(""), - "idp_metadata_path": section.KeyValue("idp_metadata_path").MustString(""), - "idp_metadata_url": section.KeyValue("idp_metadata_url").MustString(""), - "max_issue_delay": section.KeyValue("max_issue_delay").MustDuration(90 * time.Second), - "metadata_valid_duration": section.KeyValue("metadata_valid_duration").MustDuration(48 * time.Hour), - "name": section.KeyValue("name").MustString("SAML"), - "name_id_format": section.KeyValue("name_id_format").MustString(""), - "org_mapping": section.KeyValue("org_mapping").MustString(""), - "private_key": section.KeyValue("private_key").MustString(""), - "private_key_path": section.KeyValue("private_key_path").MustString(""), - "relay_state": section.KeyValue("relay_state").MustString(""), - "role_values_admin": section.KeyValue("role_values_admin").MustString(""), - "role_values_editor": section.KeyValue("role_values_editor").MustString(""), - "role_values_grafana_admin": section.KeyValue("role_values_grafana_admin").MustString(""), - "role_values_none": section.KeyValue("role_values_none").MustString(""), - "role_values_viewer": section.KeyValue("role_values_viewer").MustString(""), - "signature_algorithm": section.KeyValue("signature_algorithm").MustString(""), - "single_logout": section.KeyValue("single_logout").MustBool(false), - "skip_org_role_sync": section.KeyValue("skip_org_role_sync").MustBool(false), - "token_url": section.KeyValue("token_url").MustString(""), + "allow_idp_initiated": section.KeyValue("allow_idp_initiated").MustBool(false), + "allow_sign_up": section.KeyValue("allow_sign_up").MustBool(false), + "allowed_organizations": section.KeyValue("allowed_organizations").MustString(""), + "assertion_attribute_email": section.KeyValue("assertion_attribute_email").MustString(""), + "assertion_attribute_external_uid": section.KeyValue("assertion_attribute_external_uid").MustString(""), + "assertion_attribute_groups": section.KeyValue("assertion_attribute_groups").MustString(""), + "assertion_attribute_login": section.KeyValue("assertion_attribute_login").MustString(""), + "assertion_attribute_name": section.KeyValue("assertion_attribute_name").MustString(""), + "assertion_attribute_org": section.KeyValue("assertion_attribute_org").MustString(""), + "assertion_attribute_role": section.KeyValue("assertion_attribute_role").MustString(""), + "auto_login": section.KeyValue("auto_login").MustBool(false), + "certificate": section.KeyValue("certificate").MustString(""), + "certificate_path": section.KeyValue("certificate_path").MustString(""), + "client_id": section.KeyValue("client_id").MustString(""), + "client_secret": section.KeyValue("client_secret").MustString(""), + "enabled": section.KeyValue("enabled").MustBool(false), + "entity_id": section.KeyValue("entity_id").MustString(""), + "force_use_graph_api": section.KeyValue("force_use_graph_api").MustBool(false), + "idp_metadata": section.KeyValue("idp_metadata").MustString(""), + "idp_metadata_path": section.KeyValue("idp_metadata_path").MustString(""), + "idp_metadata_url": section.KeyValue("idp_metadata_url").MustString(""), + "max_issue_delay": section.KeyValue("max_issue_delay").MustDuration(90 * time.Second), + "metadata_valid_duration": section.KeyValue("metadata_valid_duration").MustDuration(48 * time.Hour), + "name": section.KeyValue("name").MustString("SAML"), + "name_id_format": section.KeyValue("name_id_format").MustString(""), + "org_mapping": section.KeyValue("org_mapping").MustString(""), + "private_key": section.KeyValue("private_key").MustString(""), + "private_key_path": section.KeyValue("private_key_path").MustString(""), + "relay_state": section.KeyValue("relay_state").MustString(""), + "role_values_admin": section.KeyValue("role_values_admin").MustString(""), + "role_values_editor": section.KeyValue("role_values_editor").MustString(""), + "role_values_grafana_admin": section.KeyValue("role_values_grafana_admin").MustString(""), + "role_values_none": section.KeyValue("role_values_none").MustString(""), + "role_values_viewer": section.KeyValue("role_values_viewer").MustString(""), + "signature_algorithm": section.KeyValue("signature_algorithm").MustString(""), + "single_logout": section.KeyValue("single_logout").MustBool(false), + "skip_org_role_sync": section.KeyValue("skip_org_role_sync").MustBool(false), + "token_url": section.KeyValue("token_url").MustString(""), } return result } diff --git a/pkg/services/ssosettings/strategies/saml_strategy_test.go b/pkg/services/ssosettings/strategies/saml_strategy_test.go index 507005a45b2..0affe7fbd11 100644 --- a/pkg/services/ssosettings/strategies/saml_strategy_test.go +++ b/pkg/services/ssosettings/strategies/saml_strategy_test.go @@ -54,44 +54,44 @@ var ( ` expectedSAMLInfo = map[string]any{ - "enabled": true, - "entity_id": "custom-entity-id", - "external_uid_assertion_name": "", - "single_logout": true, - "allow_sign_up": true, - "auto_login": true, - "name": "SAML Test", - "certificate": "devenv/docker/blocks/auth/saml-enterprise/cert.crt", - "certificate_path": "/path/to/cert", - "private_key": "dGhpcyBpcyBteSBwcml2YXRlIGtleSB0aGF0IEkgd2FudCB0byBnZXQgZW5jb2RlZCBpbiBiYXNlIDY0", - "private_key_path": "devenv/docker/blocks/auth/saml-enterprise/key.pem", - "signature_algorithm": "rsa-sha256", - "idp_metadata": "dGhpcyBpcyBteSBwcml2YXRlIGtleSB0aGF0IEkgd2FudCB0byBnZXQgZW5jb2RlZCBpbiBiYXNlIDY0", - "idp_metadata_path": "/path/to/metadata", - "idp_metadata_url": "http://localhost:8086/realms/grafana/protocol/saml/descriptor", - "max_issue_delay": 90 * time.Second, - "metadata_valid_duration": 48 * time.Hour, - "allow_idp_initiated": false, - "relay_state": "relay_state", - "assertion_attribute_name": "name", - "assertion_attribute_login": "login", - "assertion_attribute_email": "email", - "assertion_attribute_groups": "groups", - "assertion_attribute_role": "roles", - "assertion_attribute_org": "orgs", - "allowed_organizations": "org1 org2", - "org_mapping": "org1:1:editor, *:2:viewer", - "role_values_viewer": "viewer", - "role_values_editor": "editor", - "role_values_admin": "admin", - "role_values_grafana_admin": "serveradmin", - "name_id_format": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", - "skip_org_role_sync": false, - "role_values_none": "guest disabled", - "token_url": "http://localhost:8086/auth/realms/grafana/protocol/openid-connect/token", - "client_id": "grafana", - "client_secret": "grafana", - "force_use_graph_api": false, + "enabled": true, + "entity_id": "custom-entity-id", + "single_logout": true, + "allow_sign_up": true, + "auto_login": true, + "name": "SAML Test", + "certificate": "devenv/docker/blocks/auth/saml-enterprise/cert.crt", + "certificate_path": "/path/to/cert", + "private_key": "dGhpcyBpcyBteSBwcml2YXRlIGtleSB0aGF0IEkgd2FudCB0byBnZXQgZW5jb2RlZCBpbiBiYXNlIDY0", + "private_key_path": "devenv/docker/blocks/auth/saml-enterprise/key.pem", + "signature_algorithm": "rsa-sha256", + "idp_metadata": "dGhpcyBpcyBteSBwcml2YXRlIGtleSB0aGF0IEkgd2FudCB0byBnZXQgZW5jb2RlZCBpbiBiYXNlIDY0", + "idp_metadata_path": "/path/to/metadata", + "idp_metadata_url": "http://localhost:8086/realms/grafana/protocol/saml/descriptor", + "max_issue_delay": 90 * time.Second, + "metadata_valid_duration": 48 * time.Hour, + "allow_idp_initiated": false, + "relay_state": "relay_state", + "assertion_attribute_name": "name", + "assertion_attribute_login": "login", + "assertion_attribute_email": "email", + "assertion_attribute_external_uid": "", + "assertion_attribute_groups": "groups", + "assertion_attribute_role": "roles", + "assertion_attribute_org": "orgs", + "allowed_organizations": "org1 org2", + "org_mapping": "org1:1:editor, *:2:viewer", + "role_values_viewer": "viewer", + "role_values_editor": "editor", + "role_values_admin": "admin", + "role_values_grafana_admin": "serveradmin", + "name_id_format": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", + "skip_org_role_sync": false, + "role_values_none": "guest disabled", + "token_url": "http://localhost:8086/auth/realms/grafana/protocol/openid-connect/token", + "client_id": "grafana", + "client_secret": "grafana", + "force_use_graph_api": false, } ) From 5a589bb51a4c32ac1ed0168021f2386a1060e425 Mon Sep 17 00:00:00 2001 From: Santiago Date: Wed, 30 Apr 2025 12:18:47 +0200 Subject: [PATCH 033/849] Alerting: Enable the remote Alertmanager feature using only feature toggles (#101410) * Alerting: Enable the remote Alertmanager feature using only feature toggles * Trigger build --- conf/defaults.ini | 6 ------ pkg/services/ngalert/ngalert.go | 2 +- pkg/setting/setting_unified_alerting.go | 2 -- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/conf/defaults.ini b/conf/defaults.ini index e5b1f010c8f..ea6f45ddb3d 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -1561,13 +1561,7 @@ default_datasource_uid = [recording_rules.custom_headers] # exampleHeader = exampleValue -# NOTE: this configuration options are not used yet. [remote.alertmanager] - -# Enable the use of the configured remote Alertmanager and disable the internal one. -# The default value is `false`. -enabled = false - # URL of the remote Alertmanager that will replace the internal one. # This URL should be the root path, Grafana will automatically append an "/alertmanager" suffix for certain HTTP calls. # Required if `enabled` is set to `true`. diff --git a/pkg/services/ngalert/ngalert.go b/pkg/services/ngalert/ngalert.go index 5985904c4e9..58e79374be9 100644 --- a/pkg/services/ngalert/ngalert.go +++ b/pkg/services/ngalert/ngalert.go @@ -188,7 +188,7 @@ func (ng *AlertNG) init() error { remoteOnly := ng.FeatureToggles.IsEnabled(initCtx, featuremgmt.FlagAlertmanagerRemoteOnly) remotePrimary := ng.FeatureToggles.IsEnabled(initCtx, featuremgmt.FlagAlertmanagerRemotePrimary) remoteSecondary := ng.FeatureToggles.IsEnabled(initCtx, featuremgmt.FlagAlertmanagerRemoteSecondary) - if ng.Cfg.UnifiedAlerting.RemoteAlertmanager.Enable { + if remoteOnly || remotePrimary || remoteSecondary { autogenFn := remote.NoopAutogenFn if ng.FeatureToggles.IsEnabled(initCtx, featuremgmt.FlagAlertingSimplifiedRouting) { autogenFn = func(ctx context.Context, logger log.Logger, orgID int64, cfg *definitions.PostableApiAlertingConfig, skipInvalid bool) error { diff --git a/pkg/setting/setting_unified_alerting.go b/pkg/setting/setting_unified_alerting.go index 84461d3c82f..b22484dbd69 100644 --- a/pkg/setting/setting_unified_alerting.go +++ b/pkg/setting/setting_unified_alerting.go @@ -148,7 +148,6 @@ type RecordingRuleSettings struct { // RemoteAlertmanagerSettings contains the configuration needed // to disable the internal Alertmanager and use an external one instead. type RemoteAlertmanagerSettings struct { - Enable bool URL string TenantID string Password string @@ -389,7 +388,6 @@ func (cfg *Cfg) ReadUnifiedAlertingSettings(iniFile *ini.File) error { remoteAlertmanager := iniFile.Section("remote.alertmanager") uaCfgRemoteAM := RemoteAlertmanagerSettings{ - Enable: remoteAlertmanager.Key("enabled").MustBool(false), URL: remoteAlertmanager.Key("url").MustString(""), TenantID: remoteAlertmanager.Key("tenant").MustString(""), Password: remoteAlertmanager.Key("password").MustString(""), From 3732ec74e79b3898f576f868810aa3e52cd20bea Mon Sep 17 00:00:00 2001 From: Tobias Skarhed <1438972+tskarhed@users.noreply.github.com> Date: Wed, 30 Apr 2025 12:25:12 +0200 Subject: [PATCH 034/849] Scopes: Highlight current active item/dashboard (#104403) * Highlight current active item * Add error boundary for scopes selector * Expand containing folder of active item * Add tests --- .../core/components/AppChrome/AppChrome.tsx | 6 +- .../ScopesDashboardsService.test.ts | 326 ++++++++++++++++++ .../dashboards/ScopesDashboardsService.ts | 27 +- .../dashboards/ScopesDashboardsTree.tsx | 2 +- .../ScopesDashboardsTreeFolderItem.tsx | 2 +- .../dashboards/ScopesNavigationTreeLink.tsx | 46 ++- 6 files changed, 391 insertions(+), 18 deletions(-) create mode 100644 public/app/features/scopes/dashboards/ScopesDashboardsService.test.ts diff --git a/public/app/core/components/AppChrome/AppChrome.tsx b/public/app/core/components/AppChrome/AppChrome.tsx index c8b49ab0520..6497973d7ae 100644 --- a/public/app/core/components/AppChrome/AppChrome.tsx +++ b/public/app/core/components/AppChrome/AppChrome.tsx @@ -5,7 +5,7 @@ import { PropsWithChildren, useEffect } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { locationSearchToObject, locationService, useScopes } from '@grafana/runtime'; -import { getDragStyles, LinkButton, useStyles2 } from '@grafana/ui'; +import { ErrorBoundaryAlert, getDragStyles, LinkButton, useStyles2 } from '@grafana/ui'; import { useGrafana } from 'app/core/context/GrafanaContext'; import { useMediaQueryMinWidth } from 'app/core/hooks/useMediaQueryMinWidth'; import { Trans } from 'app/core/internationalization'; @@ -122,7 +122,9 @@ export function AppChrome({ children }: Props) { [styles.scopesDashboardsContainerDocked]: menuDockedAndOpen, })} > - + + +
)}
({ + ...jest.requireActual('@grafana/runtime'), + config: { + featureToggles: { + useScopesNavigationEndpoint: false, + }, + }, + locationService: { + getLocation: jest.fn(), + }, +})); + +describe('ScopesDashboardsService', () => { + let service: ScopesDashboardsService; + let mockApiClient: jest.Mocked; + + beforeEach(() => { + mockApiClient = { + fetchDashboards: jest.fn(), + fetchScopeNavigations: jest.fn(), + } as unknown as jest.Mocked; + + service = new ScopesDashboardsService(mockApiClient); + }); + + describe('folder expansion based on location', () => { + it('should expand folders when current location matches dashboard ID', async () => { + // Mock current location to be a dashboard + (locationService.getLocation as jest.Mock).mockReturnValue({ pathname: '/d/dashboard1' } as Location); + + const mockDashboards: ScopeDashboardBinding[] = [ + { + spec: { + scope: 'scope1', + dashboard: 'dashboard1', + }, + status: { + dashboardTitle: 'Test Dashboard', + groups: ['group1'], + }, + metadata: { + name: 'dashboard1', + }, + }, + ]; + + mockApiClient.fetchDashboards.mockResolvedValue(mockDashboards); + await service.fetchDashboards(['scope1']); + + // Verify that the folder is expanded because the current dashboard ID matches + expect(service.state.folders[''].folders['group1'].expanded).toBe(true); + }); + + it('should expand folders when current location matches URL path and navigation endpoint is enabled', async () => { + // Enable the navigation endpoint feature toggle + config.featureToggles.useScopesNavigationEndpoint = true; + + // Mock current location to match a URL-based navigation + (locationService.getLocation as jest.Mock).mockReturnValue({ pathname: '/test-url' } as Location); + + const mockNavigations: ScopeNavigation[] = [ + { + spec: { + scope: 'scope1', + url: '/test-url', + }, + status: { + title: 'Test URL', + groups: ['group1'], + }, + metadata: { + name: 'url1', + }, + }, + ]; + + mockApiClient.fetchScopeNavigations.mockResolvedValue(mockNavigations); + await service.fetchDashboards(['scope1']); + + // Verify that the folder is expanded because the current URL path matches + expect(service.state.folders[''].folders['group1'].expanded).toBe(true); + + // Reset the feature toggle + config.featureToggles.useScopesNavigationEndpoint = false; + }); + + it('should not expand folders when current location does not match any navigation', async () => { + // Mock current location to not match any navigation + (locationService.getLocation as jest.Mock).mockReturnValue({ pathname: '/different-path' } as Location); + + const mockDashboards: ScopeDashboardBinding[] = [ + { + spec: { + scope: 'scope1', + dashboard: 'dashboard1', + }, + status: { + dashboardTitle: 'Test Dashboard', + groups: ['group1'], + }, + metadata: { + name: 'dashboard1', + }, + }, + ]; + + mockApiClient.fetchDashboards.mockResolvedValue(mockDashboards); + await service.fetchDashboards(['scope1']); + + // Verify that the folder is not expanded because the current location doesn't match + expect(service.state.folders[''].folders['group1'].expanded).toBe(false); + }); + + it('should expand folders when current location matches nested dashboard path', async () => { + // Mock current location to be a nested dashboard path + (locationService.getLocation as jest.Mock).mockReturnValue({ + pathname: '/d/dashboard1/very-important', + } as Location); + + const mockDashboards: ScopeDashboardBinding[] = [ + { + spec: { + scope: 'scope1', + dashboard: 'dashboard1', + }, + status: { + dashboardTitle: 'Test Dashboard', + groups: ['group1'], + }, + metadata: { + name: 'dashboard1', + }, + }, + ]; + + mockApiClient.fetchDashboards.mockResolvedValue(mockDashboards); + await service.fetchDashboards(['scope1']); + + // Verify that the folder is expanded because the current path starts with the dashboard ID + expect(service.state.folders[''].folders['group1'].expanded).toBe(true); + }); + + it('should not expand folders containing different dashboards', async () => { + // Mock current location to be a specific dashboard + (locationService.getLocation as jest.Mock).mockReturnValue({ pathname: '/d/dashboard1' } as Location); + + const mockDashboards: ScopeDashboardBinding[] = [ + { + spec: { + scope: 'scope1', + dashboard: 'dashboard1', + }, + status: { + dashboardTitle: 'Test Dashboard', + groups: ['group1'], + }, + metadata: { + name: 'dashboard1', + }, + }, + { + spec: { + scope: 'scope1', + dashboard: 'dashboard2', + }, + status: { + dashboardTitle: 'Another Dashboard', + groups: ['group2'], + }, + metadata: { + name: 'dashboard2', + }, + }, + ]; + + mockApiClient.fetchDashboards.mockResolvedValue(mockDashboards); + await service.fetchDashboards(['scope1']); + + // Verify that only the folder containing the current dashboard is expanded + expect(service.state.folders[''].folders['group1'].expanded).toBe(true); + expect(service.state.folders[''].folders['group2'].expanded).toBe(false); + }); + + describe('with useScopesNavigationEndpoint enabled', () => { + beforeEach(() => { + config.featureToggles.useScopesNavigationEndpoint = true; + }); + + afterEach(() => { + config.featureToggles.useScopesNavigationEndpoint = false; + }); + + it('should expand folders when current location matches a navigation URL', async () => { + // Mock current location to match a navigation URL + (locationService.getLocation as jest.Mock).mockReturnValue({ pathname: '/custom-page' } as Location); + + const mockNavigations: ScopeNavigation[] = [ + { + spec: { + scope: 'scope1', + url: '/custom-page', + }, + status: { + title: 'Custom Page', + groups: ['group1'], + }, + metadata: { + name: 'nav1', + }, + }, + ]; + + mockApiClient.fetchScopeNavigations.mockResolvedValue(mockNavigations); + await service.fetchDashboards(['scope1']); + + // Verify that the folder is expanded because the current URL matches a navigation + expect(service.state.folders[''].folders['group1'].expanded).toBe(true); + }); + + it('should expand folders when current location matches a nested navigation URL', async () => { + // Mock current location to match a nested navigation URL + (locationService.getLocation as jest.Mock).mockReturnValue({ pathname: '/custom-page/details' } as Location); + + const mockNavigations: ScopeNavigation[] = [ + { + spec: { + scope: 'scope1', + url: '/custom-page', + }, + status: { + title: 'Custom Page', + groups: ['group1'], + }, + metadata: { + name: 'nav1', + }, + }, + ]; + + mockApiClient.fetchScopeNavigations.mockResolvedValue(mockNavigations); + await service.fetchDashboards(['scope1']); + + // Verify that the folder is expanded because the current URL starts with a navigation URL + expect(service.state.folders[''].folders['group1'].expanded).toBe(true); + }); + + it('should not expand folders when current location does not match any navigation', async () => { + // Mock current location to not match any navigation + (locationService.getLocation as jest.Mock).mockReturnValue({ pathname: '/unrelated-page' } as Location); + + const mockNavigations: ScopeNavigation[] = [ + { + spec: { + scope: 'scope1', + url: '/custom-page', + }, + status: { + title: 'Custom Page', + groups: ['group1'], + }, + metadata: { + name: 'nav1', + }, + }, + ]; + + mockApiClient.fetchScopeNavigations.mockResolvedValue(mockNavigations); + await service.fetchDashboards(['scope1']); + + // Verify that the folder is not expanded because the current URL doesn't match any navigation + expect(service.state.folders[''].folders['group1'].expanded).toBe(false); + }); + + it('should not expand folders containing different navigations', async () => { + // Mock current location to match a specific navigation + (locationService.getLocation as jest.Mock).mockReturnValue({ pathname: '/custom-page' } as Location); + + const mockNavigations: ScopeNavigation[] = [ + { + spec: { + scope: 'scope1', + url: '/custom-page', + }, + status: { + title: 'Custom Page', + groups: ['group1'], + }, + metadata: { + name: 'nav1', + }, + }, + { + spec: { + scope: 'scope1', + url: '/other-page', + }, + status: { + title: 'Other Page', + groups: ['group2'], + }, + metadata: { + name: 'nav2', + }, + }, + ]; + + mockApiClient.fetchScopeNavigations.mockResolvedValue(mockNavigations); + await service.fetchDashboards(['scope1']); + + // Verify that only the folder containing the current navigation is expanded + expect(service.state.folders[''].folders['group1'].expanded).toBe(true); + expect(service.state.folders[''].folders['group2'].expanded).toBe(false); + }); + }); + }); +}); diff --git a/public/app/features/scopes/dashboards/ScopesDashboardsService.ts b/public/app/features/scopes/dashboards/ScopesDashboardsService.ts index 5044867eab6..8ab1e7bff0c 100644 --- a/public/app/features/scopes/dashboards/ScopesDashboardsService.ts +++ b/public/app/features/scopes/dashboards/ScopesDashboardsService.ts @@ -1,7 +1,7 @@ import { isEqual } from 'lodash'; import { ScopeDashboardBinding } from '@grafana/data'; -import { config } from '@grafana/runtime'; +import { config, locationService } from '@grafana/runtime'; import { ScopesApiClient } from '../ScopesApiClient'; import { ScopesServiceBase } from '../ScopesServiceBase'; @@ -113,6 +113,9 @@ export class ScopesDashboardsService extends ScopesServiceBase ): SuggestedNavigationsFoldersMap => { + const currentPath = locationService.getLocation().pathname; + const isCurrentDashboard = currentPath.startsWith('/d/'); + const folders: SuggestedNavigationsFoldersMap = { '': { title: '', @@ -127,15 +130,33 @@ export class ScopesDashboardsService extends ScopesServiceBase { - if (group && !rootNode.folders[group]) { + const groupExists = !!rootNode.folders[group]; + const groupCurrentlyExpanded = groupExists && rootNode.folders[group].expanded; + + if (group && !groupExists) { rootNode.folders[group] = { title: group, - expanded: false, + expanded, folders: {}, suggestedNavigations: {}, }; } + if (group && expanded && !groupCurrentlyExpanded) { + rootNode.folders[group].expanded = true; + } }); const targets = diff --git a/public/app/features/scopes/dashboards/ScopesDashboardsTree.tsx b/public/app/features/scopes/dashboards/ScopesDashboardsTree.tsx index 5e276837d2e..0f19594dcfc 100644 --- a/public/app/features/scopes/dashboards/ScopesDashboardsTree.tsx +++ b/public/app/features/scopes/dashboards/ScopesDashboardsTree.tsx @@ -30,7 +30,7 @@ export function ScopesDashboardsTree({ folders, folderPath, onFolderUpdate }: Sc ))} {Object.values(folder.suggestedNavigations).map((navigation) => ( { marginTop: theme.spacing(0.25), }), children: css({ - paddingLeft: theme.spacing(4), + paddingLeft: theme.spacing(3), }), }; }; diff --git a/public/app/features/scopes/dashboards/ScopesNavigationTreeLink.tsx b/public/app/features/scopes/dashboards/ScopesNavigationTreeLink.tsx index 03c25d4f8c7..a1876b22741 100644 --- a/public/app/features/scopes/dashboards/ScopesNavigationTreeLink.tsx +++ b/public/app/features/scopes/dashboards/ScopesNavigationTreeLink.tsx @@ -1,6 +1,6 @@ -import { css } from '@emotion/css'; +import { css, cx } from '@emotion/css'; import { useMemo } from 'react'; -import { Link } from 'react-router-dom-v5-compat'; +import { Link, useLocation } from 'react-router-dom-v5-compat'; import { GrafanaTheme2, IconName, locationUtil } from '@grafana/data'; import { Icon, useStyles2 } from '@grafana/ui'; @@ -14,10 +14,22 @@ export interface ScopesNavigationTreeLinkProps { export function ScopesNavigationTreeLink({ to, title, id }: ScopesNavigationTreeLinkProps) { const styles = useStyles2(getStyles); const linkIcon = useMemo(() => getLinkIcon(to), [to]); + const isDashboard = to.startsWith('/d/'); + + // For dashboards, the title is appended to the path. We need to diregard this + const currentPath = isDashboard ? useLocation().pathname.split('/').slice(0, 3).join('/') : useLocation().pathname; + + const isCurrent = to.startsWith(currentPath); return ( - - {title} + + {title} ); } @@ -48,21 +60,33 @@ const getStyles = (theme: GrafanaTheme2) => { return { container: css({ display: 'flex', - alignItems: 'flex-start', + alignItems: 'center', gap: theme.spacing(1), - padding: theme.spacing(0.5, 0), + padding: theme.spacing(0.75, 0), textAlign: 'left', + paddingLeft: theme.spacing(1), + wordBreak: 'break-word', - '&:last-child': css({ - paddingBottom: 0, - }), '&:hover, &:focus': css({ textDecoration: 'underline', }), }), - icon: css({ - marginTop: theme.spacing(0.25), + current: css({ + position: 'relative', + background: theme.colors.action.selected, + borderRadius: `0 ${theme.shape.radius.default} ${theme.shape.radius.default} 0`, + '&::before': { + backgroundImage: theme.colors.gradients.brandVertical, + borderRadius: theme.shape.radius.default, + content: '" "', + display: 'block', + height: '100%', + position: 'absolute', + width: theme.spacing(0.5), + top: 0, + left: 0, + }, }), }; }; From 188a02723ff3b0c7ca94952d1a96569ad8cc26e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Philippe=20Qu=C3=A9m=C3=A9ner?= Date: Wed, 30 Apr 2025 12:29:09 +0200 Subject: [PATCH 035/849] fix(unified-storage): only fetch from history table if rv changed (#104740) --- pkg/storage/unified/sql/notifier_sql.go | 15 +++++++++++++-- pkg/storage/unified/sql/notifier_sql_test.go | 19 ++++++++++++------- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/pkg/storage/unified/sql/notifier_sql.go b/pkg/storage/unified/sql/notifier_sql.go index 0972829ccc9..29888923c44 100644 --- a/pkg/storage/unified/sql/notifier_sql.go +++ b/pkg/storage/unified/sql/notifier_sql.go @@ -8,6 +8,7 @@ import ( "go.opentelemetry.io/otel/trace" "github.com/grafana/grafana-app-sdk/logging" + "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" ) @@ -138,7 +139,7 @@ func (p *pollingNotifier) poller(ctx context.Context, since groupResourceRV, str continue } for group, items := range grv { - for resource := range items { + for resource, latestRV := range items { // If we haven't seen this resource before, we start from 0. if _, ok := since[group]; !ok { since[group] = make(map[string]int64) @@ -147,7 +148,17 @@ func (p *pollingNotifier) poller(ctx context.Context, since groupResourceRV, str since[group][resource] = 0 } - // Poll for new events. + // We don't need to poll if the RV hasn't changed. + if since[group][resource] >= latestRV { + p.log.Debug("polling for resource skipped", + "group", group, + "resource", resource, + "latestKnownRV", since[group][resource], + "latestFetchedRV", latestRV) + continue + } + + // Poll for new events since the last known RV. next, err := p.poll(ctx, group, resource, since[group][resource], stream) if err != nil { p.log.Error("polling for resource", "err", err) diff --git a/pkg/storage/unified/sql/notifier_sql_test.go b/pkg/storage/unified/sql/notifier_sql_test.go index b2c8630f264..af4344c0c85 100644 --- a/pkg/storage/unified/sql/notifier_sql_test.go +++ b/pkg/storage/unified/sql/notifier_sql_test.go @@ -221,23 +221,28 @@ func TestPollingNotifier(t *testing.T) { Action: 1, } - var latestRVsCalled bool + var listLatestRVsCalledCounter int listLatestRVs := func(ctx context.Context) (groupResourceRV, error) { - latestRVsCalled = true + // On the first call return 0, then the highest known RV. + var value int64 = 0 + if listLatestRVsCalledCounter > 0 { + value = testEvent.ResourceVersion + } + listLatestRVsCalledCounter++ return groupResourceRV{ "test-group": map[string]int64{ - "test-resource": 0, + "test-resource": value, }, }, nil } - var historyPollCalled bool + var historyPollCalledCounter int once := sync.Once{} historyPoll := func(ctx context.Context, grp string, res string, since int64) ([]*historyPollResponse, error) { // only assert the first time - this may be called multiple times // depending on the host hardware etc, due to timing issues... + historyPollCalledCounter++ once.Do(func() { - historyPollCalled = true require.Equal(t, "test-group", grp) require.Equal(t, "test-resource", res) require.Equal(t, int64(0), since) @@ -274,8 +279,8 @@ func TestPollingNotifier(t *testing.T) { require.Equal(t, "test-name", event.Key.Name) require.Equal(t, int64(2), event.ResourceVersion) require.Equal(t, "test-folder", event.Folder) - require.True(t, latestRVsCalled, "listLatestRVs should be called") - require.True(t, historyPollCalled, "historyPoll should be called") + require.True(t, listLatestRVsCalledCounter > 0, "listLatestRVs should be called at least once") + require.True(t, historyPollCalledCounter == 1, "historyPoll should be called exactly once") case <-time.After(100 * time.Millisecond): t.Fatal("timeout waiting for event") } From 7c66ab1b9bdc6c08c8a9887b3b323d3c6ecce5a3 Mon Sep 17 00:00:00 2001 From: Gilles De Mey Date: Wed, 30 Apr 2025 13:48:46 +0200 Subject: [PATCH 036/849] update betterer results file after merge conflict introduced in #103744 (#104685) --- .betterer.results | 115 ---------------------------------------------- 1 file changed, 115 deletions(-) diff --git a/.betterer.results b/.betterer.results index 0a9111cfa72..2518e0548be 100644 --- a/.betterer.results +++ b/.betterer.results @@ -1020,122 +1020,7 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "0"] ], "public/app/features/alerting/unified/components/rule-editor/RuleInspector.tsx:5381": [ -<<<<<<< HEAD - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] - ], - "public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/route-settings/MuteTimingFields.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] - ], - "public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/route-settings/RouteSettings.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] - ], - "public/app/features/alerting/unified/components/rule-editor/labels/LabelsField.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] - ], - "public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationRoute.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] - ], - "public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationRouteDetailsModal.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] - ], - "public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] - ], - "public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/SmartAlertTypeDetector.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "3"] - ], - "public/app/features/alerting/unified/components/rule-editor/rule-types/GrafanaManagedAlert.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] - ], - "public/app/features/alerting/unified/components/rule-editor/rule-types/MimirOrLokiAlert.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] - ], - "public/app/features/alerting/unified/components/rule-editor/rule-types/MimirOrLokiRecordingRule.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] - ], - "public/app/features/alerting/unified/components/rule-editor/rule-types/RuleTypePicker.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] - ], - "public/app/features/alerting/unified/components/rule-viewer/FederatedRuleWarning.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] - ], - "public/app/features/alerting/unified/components/rule-viewer/PausedBadge.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] - ], - "public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] - ], - "public/app/features/alerting/unified/components/rules/AlertStateTag.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] - ], - "public/app/features/alerting/unified/components/rules/CloneRule.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] - ], - "public/app/features/alerting/unified/components/rules/Filter/RulesFilter.v1.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] - ], - "public/app/features/alerting/unified/components/rules/RuleConfigStatus.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] - ], - "public/app/features/alerting/unified/components/rules/RuleDetailsMatchingInstances.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] - ], - "public/app/features/alerting/unified/components/rules/RuleListErrors.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "4"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "5"] - ], - "public/app/features/alerting/unified/components/rules/RuleListStateSection.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] - ], - "public/app/features/alerting/unified/components/rules/RuleState.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] - ], - "public/app/features/alerting/unified/components/rules/RuleStats.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] - ], - "public/app/features/alerting/unified/components/rules/RulesGroup.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] - ], - "public/app/features/alerting/unified/components/rules/state-history/LogRecordViewer.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] - ], - "public/app/features/alerting/unified/components/rules/state-history/LokiStateHistory.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] - ], - "public/app/features/alerting/unified/components/rules/state-history/StateHistory.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] - ], - "public/app/features/alerting/unified/components/settings/VersionManager.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] - ], - "public/app/features/alerting/unified/components/silences/SilencedAlertsTableRow.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] - ], - "public/app/features/alerting/unified/components/silences/SilencedInstancesPreview.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] -======= [0, 0, 0, "Do not use any type assertions.", "0"] ->>>>>>> origin/main ], "public/app/features/alerting/unified/components/silences/SilencesEditor.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] From 3b3c2e225c0e8892b0ed87ee51262e25c0a728c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Jim=C3=A9nez=20S=C3=A1nchez?= Date: Wed, 30 Apr 2025 14:05:19 +0200 Subject: [PATCH 037/849] Provisioning: cover most cases in go-git clone (#104513) * Add some clone tests * Add case to create ref if it doesn't not exist * Add unit tests for context cancellation * Bare repository not needed * Make tests work without git command --- .../provisioning/repository/go-git/wrapper.go | 25 +- .../repository/go-git/wrapper_test.go | 307 +++++++++++++++++- 2 files changed, 315 insertions(+), 17 deletions(-) diff --git a/pkg/registry/apis/provisioning/repository/go-git/wrapper.go b/pkg/registry/apis/provisioning/repository/go-git/wrapper.go index a6f9ecfd655..870b30c3cd6 100644 --- a/pkg/registry/apis/provisioning/repository/go-git/wrapper.go +++ b/pkg/registry/apis/provisioning/repository/go-git/wrapper.go @@ -90,6 +90,14 @@ func Clone( return nil, fmt.Errorf("missing root config") } + if config.Namespace == "" { + return nil, fmt.Errorf("config is missing namespace") + } + + if config.Name == "" { + return nil, fmt.Errorf("config is missing name") + } + if opts.BeforeFn != nil { if err := opts.BeforeFn(); err != nil { return nil, err @@ -113,7 +121,7 @@ func Clone( return nil, fmt.Errorf("create root dir: %w", err) } - dir, err := mkdirTempClone(root, config) + dir, err := os.MkdirTemp(root, fmt.Sprintf("clone-%s-%s-", config.Namespace, config.Name)) if err != nil { return nil, fmt.Errorf("create temp clone dir: %w", err) } @@ -144,7 +152,10 @@ func Clone( func clone(ctx context.Context, config *provisioning.Repository, opts repository.CloneOptions, decrypted []byte, dir string, progress io.Writer) (*git.Repository, *git.Worktree, error) { gitcfg := config.Spec.GitHub - url := fmt.Sprintf("%s.git", gitcfg.URL) + url := gitcfg.URL + if !strings.HasPrefix(url, "file://") { + url = fmt.Sprintf("%s.git", url) + } branch := plumbing.NewBranchReferenceName(gitcfg.Branch) cloneOpts := &git.CloneOptions{ @@ -201,16 +212,6 @@ func clone(ctx context.Context, config *provisioning.Repository, opts repository return repo, worktree, nil } -func mkdirTempClone(root string, config *provisioning.Repository) (string, error) { - if config.Namespace == "" { - return "", fmt.Errorf("config is missing namespace") - } - if config.Name == "" { - return "", fmt.Errorf("config is missing name") - } - return os.MkdirTemp(root, fmt.Sprintf("clone-%s-%s-", config.Namespace, config.Name)) -} - // After making changes to the worktree, push changes func (g *GoGitRepo) Push(ctx context.Context, opts repository.PushOptions) error { timeout := maxOperationTimeout diff --git a/pkg/registry/apis/provisioning/repository/go-git/wrapper_test.go b/pkg/registry/apis/provisioning/repository/go-git/wrapper_test.go index 8accb7f85cf..e8eb405c946 100644 --- a/pkg/registry/apis/provisioning/repository/go-git/wrapper_test.go +++ b/pkg/registry/apis/provisioning/repository/go-git/wrapper_test.go @@ -19,20 +19,24 @@ import ( "github.com/go-git/go-billy/v5/memfs" "github.com/go-git/go-git/v5" plumbing "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/object" + "github.com/go-git/go-git/v5/plumbing/transport/client" githttp "github.com/go-git/go-git/v5/plumbing/transport/http" + "github.com/go-git/go-git/v5/plumbing/transport/server" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" apierrors "k8s.io/apimachinery/pkg/api/errors" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/go-git/go-git/v5/storage/memory" "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository" + "github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets" ) type dummySecret struct{} -// Decrypt implements secrets.Service. -func (d *dummySecret) Decrypt(ctx context.Context, data []byte) ([]byte, error) { +func (d *dummySecret) Decrypt(ctx context.Context, encrypted []byte) ([]byte, error) { token, ok := os.LookupEnv("gitwraptoken") if !ok { return nil, fmt.Errorf("missing token in environment") @@ -40,9 +44,8 @@ func (d *dummySecret) Decrypt(ctx context.Context, data []byte) ([]byte, error) return []byte(token), nil } -// Encrypt implements secrets.Service. -func (d *dummySecret) Encrypt(ctx context.Context, data []byte) ([]byte, error) { - panic("unimplemented") +func (d *dummySecret) Encrypt(ctx context.Context, plain []byte) ([]byte, error) { + panic("not implemented") } // FIXME!! NOTE!!!!! @@ -1179,6 +1182,7 @@ func TestGoGitRepo_Push(t *testing.T) { }) } } + func TestGoGitRepo_ReadTree(t *testing.T) { tests := []struct { name string @@ -1370,3 +1374,296 @@ func TestGoGitRepo_ReadTree(t *testing.T) { }) } } + +func TestClone(t *testing.T) { + tests := []struct { + name string + root string + config *v0alpha1.Repository + createRepo bool + opts repository.CloneOptions + setupMock func(secrets *secrets.MockService) + expectError bool + errorMsg string + }{ + { + name: "successful clone", + root: "testdata/clone", + config: &v0alpha1.Repository{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "test-ns", + Name: "test-repo", + }, + Spec: v0alpha1.RepositorySpec{ + GitHub: &v0alpha1.GitHubRepositoryConfig{ + URL: "https://github.com/test/repo", + Branch: "main", + }, + }, + }, + createRepo: true, + opts: repository.CloneOptions{ + PushOnWrites: false, + }, + setupMock: func(mockSecrets *secrets.MockService) { + mockSecrets.On("Decrypt", mock.Anything, mock.Anything).Return([]byte("test-token"), nil) + }, + expectError: false, + }, + { + name: "successful clone with create if not exists", + root: "testdata/clone", + config: &v0alpha1.Repository{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "test-ns", + Name: "test-repo", + }, + Spec: v0alpha1.RepositorySpec{ + GitHub: &v0alpha1.GitHubRepositoryConfig{ + URL: "https://github.com/test/repo", + Branch: "non-existent-branch", + }, + }, + }, + createRepo: true, + opts: repository.CloneOptions{ + PushOnWrites: false, + CreateIfNotExists: true, + }, + setupMock: func(mockSecrets *secrets.MockService) { + mockSecrets.On("Decrypt", mock.Anything, mock.Anything).Return([]byte("test-token"), nil) + }, + expectError: false, + }, + { + name: "timeout cancellation", + root: "testdata/clone", + config: &v0alpha1.Repository{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "test-ns", + Name: "test-repo", + }, + Spec: v0alpha1.RepositorySpec{ + GitHub: &v0alpha1.GitHubRepositoryConfig{ + URL: "https://github.com/test/repo", + Branch: "main", + }, + }, + }, + opts: repository.CloneOptions{ + Timeout: 1 * time.Millisecond, // Very short timeout to trigger cancellation + }, + setupMock: func(mockSecrets *secrets.MockService) { + mockSecrets.On("Decrypt", mock.Anything, mock.Anything).Return([]byte("test-token"), nil) + // Simulate a slow operation that will be cancelled by timeout + time.Sleep(20 * time.Millisecond) + }, + expectError: true, + errorMsg: "context deadline exceeded", + }, + { + name: "empty root", + root: "", + config: &v0alpha1.Repository{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "test-ns", + Name: "test-repo", + }, + }, + setupMock: func(mockSecrets *secrets.MockService) {}, + expectError: true, + errorMsg: "missing root config", + }, + { + name: "missing namespace", + root: "testdata/clone", + config: &v0alpha1.Repository{ + ObjectMeta: v1.ObjectMeta{ + Name: "test-repo", + }, + }, + setupMock: func(mockSecrets *secrets.MockService) {}, + expectError: true, + errorMsg: "missing namespace", + }, + { + name: "missing name", + root: "testdata/clone", + config: &v0alpha1.Repository{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "test-ns", + }, + }, + setupMock: func(mockSecrets *secrets.MockService) {}, + expectError: true, + errorMsg: "missing name", + }, + { + name: "beforeFn error", + root: "testdata/clone", + config: &v0alpha1.Repository{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "test-ns", + Name: "test-repo", + }, + }, + opts: repository.CloneOptions{ + BeforeFn: func() error { + return fmt.Errorf("beforeFn error") + }, + }, + setupMock: func(mockSecrets *secrets.MockService) {}, + expectError: true, + errorMsg: "beforeFn error", + }, + { + name: "secret decryption error", + root: "testdata/clone", + config: &v0alpha1.Repository{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "test-ns", + Name: "test-repo", + }, + Spec: v0alpha1.RepositorySpec{ + GitHub: &v0alpha1.GitHubRepositoryConfig{ + EncryptedToken: []byte("test-token"), + }, + }, + }, + setupMock: func(mockSecrets *secrets.MockService) { + mockSecrets.On("Decrypt", mock.Anything, mock.Anything).Return([]byte("test-token"), fmt.Errorf("error decrypting token")) + }, + expectError: true, + errorMsg: "error decrypting token", + }, + { + name: "clone error", + root: "testdata/clone", + config: &v0alpha1.Repository{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "test-ns", + Name: "test-repo", + }, + Spec: v0alpha1.RepositorySpec{ + GitHub: &v0alpha1.GitHubRepositoryConfig{ + URL: "https://github.com/test/repo", + Branch: "main", + }, + }, + }, + setupMock: func(mockSecrets *secrets.MockService) { + mockSecrets.On("Decrypt", mock.Anything, mock.Anything).Return([]byte("test-token"), nil) + }, + expectError: true, + errorMsg: "clone error", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Setup test environment + mockSecrets := secrets.NewMockService(t) + tt.setupMock(mockSecrets) + + // Create a temporary directory for each test + if tt.root != "" { + tempDir := t.TempDir() + tt.root = tempDir + } + if tt.createRepo { + tt.config.Spec.GitHub.URL = createTestRepo(t) + } + + // Execute the test + ctx := context.Background() + repo, err := Clone(ctx, tt.root, tt.config, tt.opts, mockSecrets) + + // Verify results + if tt.expectError { + require.Error(t, err) + require.Contains(t, err.Error(), tt.errorMsg) + require.Nil(t, repo) + } else { + require.NoError(t, err) + require.NotNil(t, repo) + + // Verify the returned repository + gitRepo, ok := repo.(*GoGitRepo) + require.True(t, ok) + require.Equal(t, tt.config, gitRepo.config) + require.NotEmpty(t, gitRepo.dir) + require.NotNil(t, gitRepo.tree) + require.NotNil(t, gitRepo.repo) + + // Clean up + err = repo.Remove(ctx) + require.NoError(t, err) + } + mockSecrets.AssertExpectations(t) + }) + } +} + +func createTestRepo(t *testing.T) string { + // Create memory filesystem + fs := memfs.New() + + // Initialize new repo + repo, err := git.Init(memory.NewStorage(), fs) + require.NoError(t, err, "Failed to init test repo") + + w, err := repo.Worktree() + require.NoError(t, err, "Failed to get worktree") + + // Create a dummy file + f, err := fs.Create("README.md") + require.NoError(t, err, "Failed to create file") + _, err = f.Write([]byte("Hello, world!")) + require.NoError(t, err, "Failed to write content") + err = f.Close() + require.NoError(t, err, "Failed to close file") + + // Add and commit the file + _, err = w.Add("README.md") + require.NoError(t, err, "Failed to add file") + + // Create initial commit + _, err = w.Commit("initial commit", &git.CommitOptions{ + Author: &object.Signature{ + Name: "Test User", + Email: "test@example.com", + When: time.Now(), + }, + }) + require.NoError(t, err, "Failed to commit") + // Create a branch + headRef, err := repo.Head() + require.NoError(t, err, "Failed to get HEAD reference") + + // Create a new branch reference pointing to the current HEAD commit + branchRef := plumbing.NewBranchReferenceName("main") + ref := plumbing.NewHashReference(branchRef, headRef.Hash()) + + // Save the reference to create the branch + err = repo.Storer.SetReference(ref) + require.NoError(t, err, "Failed to create branch") + + // Checkout the new branch + err = w.Checkout(&git.CheckoutOptions{ + Branch: branchRef, + }) + require.NoError(t, err, "Failed to checkout branch") + + // Create a map of repositories for the server + repos := make(map[string]*git.Repository) + repos["test-repo.git"] = repo + + // Create and install the server + loader := server.MapLoader{ + "file://test-repo.git": repo.Storer, + } + srv := server.NewServer(loader) + client.InstallProtocol("file", srv) + + return "file://test-repo.git" +} From f413721435b716a3b1315e3b1313dc75cda77e8a Mon Sep 17 00:00:00 2001 From: Alex Khomenko Date: Wed, 30 Apr 2025 15:36:28 +0300 Subject: [PATCH 038/849] Provisioning: Update graphic (#104770) --- .../GettingStarted/GettingStarted.tsx | 10 ++++-- public/img/provisioning/provisioning.svg | 29 ++++++++++++++++++ public/img/provisioning/provisioning.webp | Bin 27522 -> 0 bytes 3 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 public/img/provisioning/provisioning.svg delete mode 100644 public/img/provisioning/provisioning.webp diff --git a/public/app/features/provisioning/GettingStarted/GettingStarted.tsx b/public/app/features/provisioning/GettingStarted/GettingStarted.tsx index e239f511a7e..a734d7029b4 100644 --- a/public/app/features/provisioning/GettingStarted/GettingStarted.tsx +++ b/public/app/features/provisioning/GettingStarted/GettingStarted.tsx @@ -144,7 +144,7 @@ export default function GettingStarted({ items }: Props) { )} - +
- + {'Grafana
{(!hasPublicAccess || !hasImageRenderer) && hasItems && ( @@ -182,7 +186,7 @@ export default function GettingStarted({ items }: Props) { function getStyles(theme: GrafanaTheme2) { return { imageContainer: css({ - height: 400, + height: 350, display: `flex`, alignItems: `center`, justifyContent: `center`, diff --git a/public/img/provisioning/provisioning.svg b/public/img/provisioning/provisioning.svg new file mode 100644 index 00000000000..18944432ac6 --- /dev/null +++ b/public/img/provisioning/provisioning.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/img/provisioning/provisioning.webp b/public/img/provisioning/provisioning.webp deleted file mode 100644 index 75e7958bb49ef925da99cb804800bf4add185607..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27522 zcmeFXV~}M{*DYGMZFJeT?W!&tUAAo-UAFD&vTfV8ZSC97_s093b5Gp3=lya1+=!by z_FTDE?pQ0=%#kza7(11u#Kk?WfPmD+gnz32$p)qdf&c>JkC)7oEiNLV zBu06S078K_vjy~XqmhYYPH?fg-cPgNh0786u6(EswM2fuee`?{?CR}&=YGUp^jzh# z>oLAs zmF=i+jw`#?nMcA~0po5Ufl0tD0C3?00BqX5Fs#ZvYcD-~t6#75g+0x@?X(lVbxZUZ z177_aUVs39&Vd1-fWO5aQPa;EVDVkr|MzhhpK|vX7Yw@s76M(q3%)QrJ&iGv-})~r z&xE^vPTi6KD*#I$AW{FoFYs&f73wqUZRK_6n(%i{y};S?srBD(nDGVpn7q}#_uTj$ z3cUK2zJ7k{-|y`C*>p{OtA8?azXbi9`OyEI zxywz^C;i({2{8GHyT*Ntd+2%Zne_(qWBnciNPHq5*-gy=0FFCN0sz2`H~IHG;0x;8 zcN+2SUYGNYKFd2*o?oVvkZd6?;cR5g`QWhK;s0;=&s=iEq=c4``tYB4Oo2U_Sc=N; zKk>9n=BmKSe>f1!4ME@k#|Mxq@^?{EPwsz$2#>~c7%7?m07=4^hpdHLOP9NV|LuL# z-yS0L{!@4ofvf&+?|F`$wn_L%f5DjolJGCc@2mgYZ|==pB9d*RCPf2t@~WkWc@4iO zq{0faDiy|r%!pH@62r*kg5^fW+`)hVGs)I!ZN#Pqw_ z@IVYZr8|+bCKV0}n8$ouy1pFa@j1&s-G@G{mjL{-C(!%aHsI1?Jq>@(x; zuYXQB{^xfX)#+v)Joo0ELTPv%7W4Nr;~Qbh)%0u#Iy0Fc`L5)cqu>0NsiOqs@D}h9 z`dfXVVi3p)xfRPw085Nf4M(qcs?ud4~HY5b|XcU?;M?{ z9|A;6mVb}$oT>H%M1w#1zfPIGL~NoEpY9~uqlC$A^zI#!L0W5QBKQAgmMj0OW)TSy z+(e8n(NVe9Pv8VNg9mGV@N;xgZZQ0Ea{l|Vf{dknsR>|;=G+tCWvBX;U3B+lWz%P$ zHi{}$&Ohfrlc`(u(&1JO;8O?2NPyXYgyH&IiM#?)Mik=`%uz98fB3^o5ShJq*zQz8 zgFO_>7`)=O5=~H%IzL*5SST?gfej6g4sD&E?FiM#XNgr~Zq+)dnA@)t15bT%9!29$ zG_Xb6R7W0QlYoc_aw2vLmSx7H(j!Z>jP3L#2uF98WSMOf0VMIEi(o2T$oZrMfqZ1> z#&M~&efv81yW4aAx88wS8RD=dR+eJB>(jO$S0%X%6h3<6*GY;rL8`mXt2s!Vn|c!= zqOLoEtkLv4-lb)>2nv}7+8x4_*A;+~+h@rr;r2opf!u|O8u*9?c5!$Ykn~ibQO79h zD|I){p6X3c=QopZHmaLhmPd?!GlZ{jn3u?dC-~f5xGFpnY>}T13mP`V4ixSpkm2IPkr2^8ish->sfZwfL12qSvGssj}nzHMYuh1iXoXDyMP9O+heeR1*P6zS|0 zZqw4P6PMGj5^K4A!ksFkJw?N0#0ohyLED;vW0L=5jsCkbV3tC4J&yD(GqEeyga)fA zw*C!LQJOYS%TZQc7rfvqUI*RnT4)Y9!0UP!GX&bI)+kLU&fJ1if6Cy)K+5)^H^`Rr z+sIia4qenfZ*M+MLOBH{h!$<8>Jwss34U%8Cj<1yd*C3FXz~9m_GHT?=@(u3;xzR# z9nNvMa(3@<^G8s_4W7efhwprL^lX-8V}RA&utUf8+uq*)`(_0#e&T5#*A2lG$p~uTdojhrN4An|7;o zS6Ls&uWVI}uos%4(26#1)$j>vh`VvQp;8HdbbXeoEq!m~fT5B^(iI6>WdMyf_&BjF z45P;{^TTmvvWOn}JLU#qvh0bIl)gXCK-cnbx658!$@kf8e`H_qOC;~Yfn^NW(y{pa zt98KlODAu5>iWU+97~KF zTsHWrrP{|uq3&-13fC8~JtIW=0I`{i&QkPbWV!zpd@M-}I#mXhQFC%kP-{ydZ=DPy zq?t9#Yd;cb2e}mNxbuV27ySjCP1uHQ7cDa@zC81_rl0<2aYirx=W$% zOV+80+1C01{6AB+I@T#=Q8o%4M!u&C zzEWfqRpOTLd`Sy03 z?1)L!785%5TY<6Bac0@cxBJePvwBn~`C;wE_eLUqrZh{ZI%C1xSTlr+72urfwfE2&YCU(*&gz~*ddm2$@F7obwWhv+jpZWl4CIsbKD#K~4y>1i2 z&3xNFUAy?NZ&`Wl4>?hant*x!$|HuqKR@w3E*^ zjhQ*`gc7TT{VK_y>zS5fV)0wn_HPDlgje9{Ir~SAj{g|Y=h1WXIKbA#tz|{b7pUH2DhittpIdMz`D6hyEg)f!YiO~aN_y*x%Ew5FD2P6YKS&) z@fEE3AI7od1UN8Wt)O2N>M$L&7}~2vu454c0(#mrM={bpq(0m#e_{9qmjS{=s>6f} zm@1;IW=#{ppBZbm zk9j~Mf~d4+7sUxD8u~l0s@M#DVH1e=O}d+uz=j1_g@`uRGYh}HwixFV_B7%PLMrgd3BH@m(S5(l{93DQscr?Qg*Dezi9PHddoEo&f1 z5s3oWD7&B^4w4Md_!YSIl&>@E4yYG-rwr`EA2||y@QuD@EjHC6qx@7i!0`?)$CvD~ z!{tK>3YnyG5=fE}OcYiVam*r0pm)`&!0WDmoHe8Mk3zUgZjfF|IMg)CdddhAzKCZf z0cjcm8nH|f>P;)^^Z)eo`s=zqm-ivSG|pYbC|c?2g2HDiB%x1m6N>wuP z^wUO&cOAH+=)eK~l}?m$Qjm#-542-7dh6`no#a>^rIRdLI8oCfXI6c3UnIT(qeZqy z;}B#cjEH+LO*)@6RRljMsu1Dv04o!Gqn=}tV&-p55Fz&!WF>s}7@i%}4B+X&{ZRYN zKsC91#RmGZ$LQ2Wl##10RGjZ(9U_<*i`^cU2uypjwT-GMueq+%ji@FGp3SJ$9_3Uo z4ll?h`yB#vYVKtVs+3rRu#o4{rTJx)BDYM7z#Y8yXLM?0hlwT5%{MtoswPb)uQmxs zz)bXWPM&43;=H}4vSQ&MbKYLf^RLr?U2~6QBTN6aNn0jA^bfBbHJ-g?WgB$exPmAL zB*^nH_jO27gAXMr%U$ETner+@3@z#II5n3RgWr-IlTTo+A9U>&Hjo@&y)+N!98ZU& zJDXhOoZgZErOc9nwR=qaKOE&PhY%1%I#vsifoFwk$?~BcX&HB*XKL^|NOVu$f)+(H zF)Vbv_H>?+Md9RNIc2P%(iGpClJu74`fdf{1g&pX_EW4WDX3`3 zY9UZY+Fd3^12&}UrFSOpZY8~s5|f@O&>cx(;24eF8TWzWDwGjr*ir=25mfg9G2uJxRD;Vfk5nGi}QA#tv$vKfnWt;z(A%l?Vaw@|BGF~%>3mT zqZa?v|K+{E{PlMom}g3K>Ic}kWf^+x6I8+X9f_aOyYvW!kii%e_J7MAyzBo&)c@&? z$#xOP(*IXldq?^=NL=jxC6oUV<^Rt#g#VA}EuT{V8Q=d4-~U`j=m3fPUw!?5K#a;x z{5w(qGjU{^%!~6s(_C`1YyW3}{Xa-%@?$Z50A=+5MD0neH?sFz9FkO$c~`tXf3^>7 z_{aW3_Wm20|5oV!iU)vPN*Q~d~Xn3wZ|#lN+uJ0PHM0L8=nN_(s!(BA^P`H%g7 zD04WqZ6jvx+j058~t{mgM*dmDPDYV?Km;i@^4L+DYYKQ5s7!b@B&&w%4 zqjr27?94c!UqX6Vs8kuoX$~(s3lc{xssc>dO~DyZLll7sH$UY-kw#5BlG-ieOvjj) zB|x)wK?mUNGDOBSfIn#4bG0_2g=aF|`G^=D(YEVE zi*fwo#yNV;%1vntz@hE(`Qpp$I*JXWqLAT2yeF4opa_S2V7>NL1X1@KXRgDjhdNc< zlYRmAh&Vzuf&5rQxqY-7{*EqG)$x`VuUzl~MuzpmGo{TJI`9lD{Ly35RBrk4>&=Pu zByuIEm*%Eu&jX-V(eNQa0iI}iwlr)uLSDBhC_I7B${Z27vR~=GHOo22Kz^|}K(eva zC?WEUK}aO!Dw8jqv$!(e%|BJ6kPSSU-|Ya{`iNqNdM79|aD|>dxKIWJYl(!iv~93r zHA(GMN}4ybEp*JA6cUZeyFLG`uZjR3sIB@9a5Cvs=_h0`=%RTLOyE&{LSd1o^%4wc z@II&M6;av>9J!#d2dwAfy=ty{f%|N*gHk_&RKQFS1-SlaFJW^!E z%Vj2wEDIIu)*S{|!(W}30b!TJ7`Ubo(NM9@d>A+aOtZmvyP)mYG<5!jeN;hSJZx*e zBq+!P$O|Pgo~S<|kXl~X{4bG#)P(0WeM>l?At5P5WzgbHg zwd|c~Bb0dXj#kYJKRpZE&6o>BUm+g{N~uF@zXWvd^wP{P+*$^{?@Z+APyYvn2=)C) ztX?6dXJeS0!O5ba$;SCM&3LSyn zI{XO8iM>M_omjPZ4X_BDU^&np(fV?q`93zq)<8jJI6Q5Vltwo<5a$UrV&DT51Y0gg zNSHup6X;w^J3?2E7aTp7nK>^L3%gJKFqD(S6B`bPqvW{&yRvM)nzW;d%S|oxOR^Lx zusYpAZS>q0^eX2|92Ap?l})=R--$RWs`;J+#tMZD)~pQCTwl|xTAa%G4KTt%6V z?IyH|&W=n~f+%n;zA8L8Ry2K@OBPL!B; z9dNZe^|lLu487wy@2G^QeptR35taBTGsaPds&aaZ=6R^l)=rZ&y5CJaq^L)m9hb_ z$rETs?E7@{l?Kh%^@{;8U9udZZ+9jY3sM(;&8{OHzh;YAm}qz12s~h+hwLe!c~H)e zVnjk$f+UxX6@k@6hDFuSvT#lWNl9ACxev~-ytCofU1E!w7)zXARtB)TJPY;5ua@q8 z2_<~A(K$ZIpGWX@3?Ge)sg*yo6RPe>qbD(u;GZVz90Ft5YKaJHl|I5b8B!k_iN<`K z2j+OZ zR9Z=HB z?sg?v#nn`=Q91+2^QEc0g_#X6k2Q3^7Z=7(tJW$8kbl3E=3xrg=OKzsLL<~Q*3TcY z$^C9dDWL=={CZAs-NOknm?J58)87Pfhi!h@Pi-MtW2p;#F1+z#+S`#_4Q+5Q$2H#f zLYXQHbvIExw*VInKWBL#vRtphO?`MqZRR6DnQyL*RI@sB;OKkiH9E^&9FGi{Hk$_W zx~n{E2!SDqOCj&U%mwo9{o`r*W4%lkXu*?J0sdU%nwq`UxYoz`YS3Xsnv5X36;nma z5FD~1I=(!-q^IfX6|W|KJBQqSsoXqlvIfV?cNbE{e@(}oOle$g!%KeaIRuivC)Fd=wh# zL`{(LW$pnCb;8^lJuV=9KaKnAm50{=(R8fPfh^uTGR1P22{*twwjDl&CD~0 z>k0h3bFb*@c%@loKbD4~W**1k3UHu5$G z$$td<>ZcX%1=Naff{j(~^*c>J98$o+20x(~#Q;fAkjNYFc3GAl(%TOHk)qG)k# z{!9&cuRye=&V0jm$+zB-L3a9=j^9iPTIU1l4hC2@!}O{SYM^fT&(!Sk2E1I4KYP|9 zth#N$Zth&s*9`w~bT~)k;32ZZl~VaZ*#Br=oHN5Ok+07WhWV6_7>J)z`(1_Dy`r1K zIYlWKi^0M~!GXXVSC3yn*DdcUO_St=_WO*y9%JbWviAuXnNk9 zKyg$NLT3!hwhT+5j6!Gm_0DrOx}yk^lPFaTpi@CUHVxOa{oc#RDyU0_Y{ja4tUsNJ zt95K<>e*(eQz@(=6FTHgp_oTLJ{>v8Q1v^*Rt?$V-hOLmp%%birC zr>1Sw@m%HrE)>=M4Qq2}1C0HbOo8C20hJ`p&o_V7VAXT!@(*26zZ$*B-XEMIbgg1Icm39=55B=K%y zOWTL^Ky}x=As|rLr_>C)oyh$ge&~?~cI9cpGP(0vF=D75xzc3oQ&Ja+%#R7FHjD@I zDbQI76I|AZr{ut#-aQnQ`v?OTdwOlx5NeDtfoNZ>vh(p9U%1S0z%4O}F?rae(hA-h zqJ_$dhc=quP*R{%s%(DyYjWt<7e3kT5~%5_&IHo59B+m2E&(+FWXgLj}MY&|bb&qY7`X zUW(MZC$UBS@85CSsk*55nv(-M^Pq`2s{zaR=lBW}sNYHsqvz-p0_6&o+_deVpho(P zxeEJBm|*l7t&frtBLXQ!2}8gyn-0=5tiEq=3$0#mIPfr8P%-CS?@SS%DYc3HrYZ7D zubc(Sizu6#S3i~P5h)2%K|=G{E}*i;D`~8oh+~8{#(Ck7+|pf`vwN_ zhY(JX@~%1PX23ag?-t=08@ArGtEn*$w&;CjBW`we2h0w&KxoV6cd3yP;{l zld;1D4Tj-h5J221)cTGoqz#MZBrG9xi(LX#r+6-{M5Ice2zo=R{ISh3`C8*L3LR1J z`#k&~eO)Ft?>AFbGiaWcOl)^HAoDGySP$u%WwMl^JYT0`O5za_4L=gjhqNT4f{Z6m zeYVYhBc)fVrZr1QuF_l%7?5PF#CTKS9Ocxp@@YfHa)ZUTO`SvKn5M~Cmh>0-~ z`-A&wtv$ww5V%Q=!tv-@x&d%5VTRld+5PW_iL(h%eVDL+D>7Lh<~}IKaZ?oJp%lJqR)EX|!R9GxHidB- zKXP%B?Q%?e3?{5~0Y#B!CF-m6wET@xOfn}Sa9#cA{KXg%Uidr0;rHUHt#p+V2sj_6 zHk8I3rkXzj#ISCY&@TqB@}#|DgSWcE57<88kc8lB?eur*pRK?u^yN`V_rC=5vV$!V zT))1@^a^Hm023aIG=92V^D6Pd$Y{=N#rZS~JZfelx5P%sRCZLMNn4i2AHGu>6l!|m z;4;I>Zi!KrUdpMqziws4AEM2c^)huDE_5WUx6Q^Nm^rIu9iW|Sd-f3BLHr`Q^9s%^ zDFsJ=R-sc-r?!h)iC4&}xk*r&KiP@BQdb=&HZ zQk3uz%$NK@Z8_e4?>4jL*MU5cSLq_tM@F!1XS=oRt0E8*;rV?jMB!^%P$xv6B(HNn zN4UO4IcJK^sB?~3V!W9u-LQvv_?cyNQoO7qB_w4>A8co9DU=_9FC?w zEiDQVk?o?W($NTm^exu{+CWe?O!t)|zGGSzby*<*gd*K4xv1cusE+B#8s)W1c5}NV zUmb~tkSW(B>-SrOUgCWd<^%{7skyD3IeOkN{+c8n{$w$R{4i~dCZWXMrcJT@dKOOI zD-hlSKKM9dX`X!A6+70tpge%0#f!%*RrziX5NgV0sWBw_v(tGay^nw84<_Jo0I|-`2}cre%;rek4em!cYzqAhK;o_IamS zq|N$WwcTu^HNtU==q5GejZ=_QnPsr3d_^hl9PIg3xV3EpgwO{m4hln-F>DNHuokT$ zZ4i0RorjVO0vj)tMpi&~Na;a&k^}WblC|5g^Z(#{kdDmhPZFh)DY@DA3>`u&?jL&6 z40U!RcB}BUvxm&x2p6GG8J$Z2NJP!6M-+RcFOuwTYt`_~pvacE4bi}*X3(g6LAu_*5^zKYnN{{={Jj?c z&<^zY=E-NMN_O$m?P%{uUG$h;inV~hlQ4-94eN$K%&KA-rF43jN_lnLc(TCyEFJ1{BkWs&VRoE}VU!H=7{ z6UU4FmA4T(yz?|h`tcU0UHlbmDNUs`SqJWz)G|`4)k13r629&|{MDM7%}o#GjG9-R zl^dsMC&EgEK$>zZ`x?X??z#*HNZ_Ixb$a$j3}ulNb>~C~I_TGmegQK2bEV&)hOrD7 zv0QIqg7SM_kk7_j7p>*A&8`jtQLr~VjiiR((B)Eu;X-f#UI_WOJkpWaZQyd-ULX5-3l)f3?zTDZIOWmX!4Z3LG*{qCn5 z3fJn2%T#!UFGt6AIcm{v;*3;)k>(ViQK&5_fjg=y(`1iy;K2~j4|ms|76C-x`w^8` z=#?x688ORZbtxJ)!TmEaFz>Rd*nvrAbfrQTLW@b5+56pVDfIjo4i+bt6uKU2a z0AFhmd(Kpj@mRX!SntIXdV5JxRV`8iq&icd(h~V*9iW5l>$9u{xh!V&S9Z=wMufiegyh!QC`|6nrKcdb8w%-tc^2voqtU(Hn5ePbPS) zwOrsp;LXzuz3X61W6U&1V$=1OlyV1TYxRP*mQb#K}a-C7d5rNr{1o8enMu!Z!%A0a?PPqu{!BV&oO>P*-_ zIr5YBH|+K9!JDJ>BwfAMR!`t>fWkv{x7}-Mt!F^%@VjvkYng~Sqmi1jJ)5rPF|>(B z{*E9@LFFALdXOHOo~KlOVlA9IDGuUIL_3z7*+nR)>IA`5rh%*wTHdbzb!I^p{%qCG z)3?>Ac_%5}dzq1)inL|$?9()$0l{c8+?hYIDQJp0U%!1fpR6Qp5qULpd>S5F4ji&< zZdr-!KT*t|F2qv}1_;S-Qb>*>&q54$h;7{$XnE(%nd7A8b5!O8cdBdvg$&;b?Cp(n z*v!D=MI|++S3XZOR-Xw{Fs%!e(&Kjo~v-n6f~c3uT1iEOSA)FCoP$b)DUUZU%Ow8CS&D#P!*onf@KPJu}-+^k$js;qC zLq=POHEr!wdqvP_*f6)&FOV%1gh5+OeS9r*tFyw^*EdLUwo3;IimYD!CNGKBDzSFO zl%+o*8TYDX9FZ_^&ZT4&Nb&o|=p5XJ7al1Eo|GX$?Uy~5#WNBB<(B1yKuQZSH-c8T zW5R3hWJv%ipTl^xmQ4WFI*UK25Z0toUv_N?(7jI&bpDm4`prW%8SloRT1pIi$e@ja z!2uYyVIeS3Rn9-@rR@Kiks*?GdQc4ON+SOI3WrP|iLuf-ymx!o8)-~xMpM1bkr+61 zyl(V=tRFE3S9W7LV2)CX>WNh~QM@yCw1h1uRjA|SPeL5 zOs^c=NYKY{;jz3YjoRm09iSA7W(|ah9h*vpP*jGght36a0A$Z2NN2Xf8<%y7FB}b{ z{3WXnqJJzu9tCjoez(^0!LF%=Wt0ZYA`1DP?XbZP7Nc+QcPKQ%br3NaLua@8DYflP z16t1&E6ICIyQ$A@L|U8${%W1W>?gZw60hkan7W`Ug4hYF6P3RQ`!4=4eo<&Md=Y%E zi?9fBKx?Gz`A%3L?7|2C*uGM?!LP>_&mv%qJ*J)%t`jSt)Jcer%FLt$DS@}d#8xtl z+tomWW)sO?}e=XeBg?TEcBB=P(nkZCu zZbIFA>h54ChsQ68EmvR{Owr|N34{W~5Njww<9vI){;FUhI zF3BwFDF!p3+&!J0@5!e3=Jsf+AOwv`8XEsxu~%c1{q-GOV@VldH@m*p{LB3O?n*EY zw+AwVo`xBkVT2ipMZ4cC8Zh8QegbF=&^;Nrmcc}+2&*<|Nm8tt>8V0*U<08iUEKaq z#mjnhVINQ+M3WMslyz22@k6_NA0k?u}|0fbJtfu)sLhoIS{UcO_Cc1W`@Q{IgFrjK-%kA> zu`$^XFxdqryI~y5+$cKl-Bs@=D|?A}F}<%T?Yr|Zrw^^iUSRNfp5u3iwL7E>Uo?Yw z$8&X_`2=C66N3kW>sTz{-ZMO{62Zp zK7?B-ngx)=pUBKOQx%!(*XZwm-b)coZ5h8oI2{6aN3+WcTB2Uu_%4BGA2TcqS23Qh zml&d96NPD)GgF4+16Oq(AaTpXftMkm5fk5J(F){keAB>WY;M7VqZldcxawjUIo>8f zxeoDWI2lSx=V0xsH5NE?quU%4J|FRj5pVgy#XSD*zHCLZq?u^aub{~I2fLc97ITYGxc( z$4YNxny(yhPA_YQU;J|>-QQdQ-!;>Z?K9~Sn3(8r-{`1}ja&i*c0IQWAa|x_kNY8j zM8cFfiYYs8Ie8jGGelZ$XzlGdSLwE|*l_Q*eV-xz#fMnKn)d#e)*MNWYS&*xgJ99z zAGM9PHXwF?t)GyhRoD4+a$pXnseA7(G6c^$Ugu;ENz|B62nK5KvN9WIi|8`W|J|0@ z6_=NonehXJz&Iy?<=OugF};w4f92jB5*{y+;0A^$@O1-#``bg^I_zI6=o;q!rvy}ag6glvGr2a(jLF(Hvq;mHs^ zF`cT7M5I#Rls!+W93p7sgtxSX;#a0})gOVzpHF81YfkU@P{c+4yDK3TZoc;;de@$x z>kzR#wV7xh4ASLIg?9ObU6in=2Vw_IzzL^6W7j2mq!{}F;jXo<*#+dX{YI##fTqBC zcZrA3;^d=qdSGX)2ydrVVDxYVtTASOCUkyL-Vf>a;~EK72XSf%(^q+cC^Z^V|3C%- z5_CU9{~zP_7j20Tzd0O8Wv(XxL@Dq5M%>)79A(auz9@RtA}Q zDp`aguA?*YO;3Ed#ElpVwQ#73s}b|lzCiqg`h{2;x-KYF^!%i`Nd)0WjLbRsBmTqs zlVa!yjUp%Sxo)0lWT~~KJv~}bp6<25mGr`=s;TKplPdZ`=}SlMb)I@5{Ms zCAHy7tZM^@2ZgJFwdrrZcy`zH^hV08j>N>)P1v_zM^6WT>uh7PXxsF_W-><>)@Hkk z;aKO@!WG1EMWDxGo3MKVMTi(^-LmOit&a3_Di&xwnS9WYT4@0)tJId)>+UxZx4$+s ztFK)337$5|`H^%=hriHZ9GiOc?L!=KXMP6NMZ58xfEzK~7|!`jm({nM$2&f6g9I*@ zQAhOPAKEHIWBz)zXy4M$1nWW<@X?N@QyKbN1v#=cjbW}k`kFUf2>gaQUq_k%HPs&j z-?k2MYi(%?j~EgzBSwjdve@o5G=s>wFhTfW+AFmSmll+ga%|Ca1vYDqHJ4naCa%so zL&Z$T9APG}n}Ju^bPiNZy_P@M*0ESv$o63FSPGxGz<9jBzYAZSFR!CUON@tcAfiw7 zT)1AHz{4usCkOLtrXbgF58*~$`XjQUMc<_*p~BxoZ79pmPjao?TI{}rfrRR^&VD$8 z2zwMY^TG6z1)*FpNuB5@U^3~(v<4M^GixA8h(KgVsv?Nanu`BB>S^&y7pB04I zUi879QR%w@1#4YS5kA;0Xp7MJL|TrnWSKE zCh4fh`@P=p$CG*fcHp(pyCtP! zaI=C+Pc>6gmG=*E4;=^Q2>h|P+yOyi>QuRW=>iC;)LjB>{Y!>2?utb}Vi_UT4O>_Yb`+Jm)(&KR2NSimwqY!_hlw+o8@H-Y zkuJ3|5{+rn0{(n748x`Btq11HQi=W0Ap9jf6lAGtevAHf;$zIvVd%38$~*CAa2)`& z^qIw&)0m_Rk9$eZ{^a)EwR!?)DiH^#4EdOfgBn?N;ypNLS%qMJ=%wba+teF7zWIb)Ll@6tDyCHJ$1= zFv;p+pnRFa2s2))~7_fuhpB?%;7vP{QeT_`~z^uQS_we!D z%9^MQ?2m9LgA&Eob%-5-gupA}F?D?R_%Z#FXbIh&T@e3d_~>{Q-1WZD>my5M4pkMb zS6Lc~$%X@)(3Bl#kW>&xjjtf@*B>21O?r{s46I-7-SvEePwQvvPeP&~@CUlWgLt%q zg@n$Ri>*(BiSWPU0fmiG`+E@_fxH1lV^$_~7?oKo;6ScE_7}&BBv?i$+5#A;ti0#m zb~QGghsMbqM4L?Q;1alU9p6cK-%`mH3e2rOz6V z&>jA$oIk_izv7>$>H^Gjo}BMXM6WzO$1Oe*qU=I>5+26U+e|?{F9O~1wPyyWHC7D2r9M>B?+i(=|zq^ zlfjozunJ6$gRwPU&&%kp(IPt9W+vG&{`$hN*)Iy0THNA}w=smLws$j)2%uKpbIm8> zYnxthb-MN`RG&FVp-IMS634?kZdC|5Kpm6QsoxK^)!64g#E732aBr|@Y7|BjwItz5 zE5|jFQNU=jIDl%_>Wb+umN1wu5b)I$K8W0V=uuOv9lxK_{cvz*a?GX2E z2|+o+ic7gQ2~K`D%4pqCo`{g*w;Q6mhJAN?7(Akp>`K#8>=F3XI=(W02@_pBz$9yy z%z?b9;#|E;#0`R)Fd~D8T~CY^bVY>TAv6K0KTPn_T$9$6i5DG7dFaxsCfzZA?h#d@ zq`=jTtxE~ZcWmoRA|j7OId=`oH61!XrEY+F?f-fg9jy1J75WMpgtY;?Ke}qm4JqZ` zZHs;;TdN-2EnHYn1)}y|j^#juTdcL~Y09>6*k2YcqOo+UU;z?q=FTT5dq;H4w5tL0 zZdXhYt94x@5Kw?PfEkD0A2gJY)y7+vBj9vq5ZBr-6Er89@iSQc8dgJ% zS?HI@wp`vS?kHosrh2BxaQZI*-xWcM&VY}RuHYN~p~ZYtM>w@b5y!80xcxeSboE8M zkTq89jMy{1X0ME65BA$6N+?Bd9%)2BL2n!La|SZ=E4Ruz#C;fxK8?l!+z)xMP$WIQ zr?zjG8HR{c(aMFDeDhw@6%K@OYM0!(D7tzl5M(M#3CPC#1ItNFZ1L;W+Il`<17xK; zk3m}MQJz@pOUBY_q945{ED60XHv8W-`!p3&+AsOa^(vi!%{s$Wk-2Ho zn^)E~-DEZj@qJ_6G#AlCvCzhddZ0){Xq@t(>rvM!X-mw#!tN*5Br1eI^FK>;|I~p7 zgP=|#tm(G?SVD(Fwku}{7VT4ur^L!pC4loCr}8Je0g~P9rPJ#KHj8X6hmT!eNI-ah zk*H6#OVM6ZXa9vMbc9`-T%WBMxi^9#sT0%<^Q z1rKHo!OHg3`|<1-eDO!z7#F+^m50l9HB5wNXz_{?Yfsi>q&$;Bcw8~?>TgnXbKXVU zfecfiOo~9Vj175I+{%2xPK^fU(W>+9#AfF267f3-(xX?Qq)}n?ks7r)+KBi_uhl-? zx4N&nyvMOu0)o>|DVAkRm1*c+)v*%Bu~Hz+ zwOuC7tH;N#+MZBXR=?U)@7iEi5NcCav}#(3{7<&eX||3f=&N6#Deye7HBvS_1V;ue z{Xdi;!b>y+6dGS(M zpRSM_7sr~g* zk@9zRf4hvo(c`D7H>FjAu5|miUIyjS6IwOvEVZ(+Ws!x|6M9-ofEDlx2>w8=xtK8z z<5&oaM~+7o1u4ST2UhL}2KjL+$UNOcA>dZ?XQ{QGMQhUl^qMOu%1tbbWkbp*$d^?2 zM}s;Xv<5sO{Hbl&$0LX)cR6GbV=bUOe5#GGnY9u(ZDUjE(_+h70Oa)7ev{XTj%dni zN_@X$ablVA?9Y=jfuST&5kAM9-D&;fXYbUCF7@{G%$u>*3kq&8AP-3s>xa0WZD1_d z9yy@*$L=zIE)3Q!65|1q2+;h=-kaqY z!nJaB-1*U6oUxW=rml}~?J3$k!m=x3@i%hm1;O1Bl!G`^S*9I`pQ3h0&Pb9q(p>_y ziNnO`pOl6oPwq+_?IvVt%BKuz-2A0Pa^O)-N?<>TP|S!zwM(Mvncxq}uyKJt24Tw~ zZH!@Rp-n)nCFdalkl}zyUUug4*dOegKYmOC?cXZ-)-hN63ivTt(;oxJlH~!fp89KZ znGGMF=X)Y)`WrZ7B|ONVRA8~{3J+#SAoAej7#IMf?LjSSL0Fu)VnqmEep}p$?+muN zBx4#uW~DUGG9C4bPDMY^cT%p3)5aQr-{$;Z0cj(pOO3_y|v`K)tN)gTfULyFw;_f0mp3=F4KS=kUb3ycGG7+e2& zCkf@pBpf@!T{Z&SRGncD&PSIfOCN_sDCFFMV!!3iU|TQ4s93N04|^>4tH0lRSR>0K zWMZAKlHu8gT+ZEgq%H|HJy-Gb3S~6L88vdiK5IA;^a`4exL0SB7x(B4XRGa2x%X~-bVJPaXl?+}OxLiKOsDSeJxu68wa zzZ6#dOHKj%jG8A29>?uGDQMR$4}T))Z$sRzB3a zq8)Ofs|Q+C5omY1JEp>j41SS*f=yZ?c7%Kt zUc0{?w?FrlQKFZyTrpz>v^>ulb#U(u71xHR^<0n=yl zERI}$oSWicoH_H)vDYTbk#mMv0>+w65`?|{{;O&F=w((BeQn+SA5R4Ce2ArKE2E#Y zan}4f&rLhvK5N$*D@K=%jnC3keUhSbU+u8pR zZg+48KF#r=eNqU$q6#<_Sk1{CE7=x5a>qaU(hi)H2NyDqo`sM9-|QqIjP$a~{tTh7 zT}P>Y#3Dki9E_-G5PvtqoAR&yw2rulSuRzJ#{8@yP)91wQ0l~rw8W5H3UlUSA^mT% zkbE3}0L)8QSxV|^ifeJeLFwPelTb7V#6*5(c_|#sj4va;PF#^+5hFd7VOa&S6dx!qD1;bn=$5_8!p z6Y)4rjC|k1+5%I-3#=U<+H1Sh=sDPEyfjYkn{WOAP`vAOQhtufu!tJP+WXqCmt@7; z*E1@g3^7($_JP?tA@P`Ngb(MS$y$?wI|20pm6iI8>6 z2GiEvG$>bYyHXm?0&ERe$%Zi4fNz${|9geV1t`Z#=BRn6NTTXkMZ58eU42=HmcF<- zTOE~j_=eW8zn0G6)L;t5EKA8}d{wx43ss|`b990#*-9I?0(#58KLToOVAz%#@ie^* z_%we4+S6mmXi3C)5F#j}D^&}7tQ_XJn_^OEpAY)`)W%Ik_KN>LOa&*=t248ex{rrC zbG?-|%{qBCcJBqX;(#ir8m@9#$LrW6zF5knw2Kv2@-16Rlz~pb9^ME~j*%B)C%T9; zx{SRMV`oW9yhK|!VbV|?NvZ-iCALdFn7aKh$<(d6N`5V`1tY~jV!$gFRz; z=0iyTh%wJe&m#r8i2tP3u=wlb#znEPTqn7V^XZqDDu4?6I>)qo}Dmn2aW(f3ruq-*8MgE|Z_9zj% zu_m|XBLb%v`i??*hA@|2(igjM*~C`5bb!6X0}qef&SiRH3{e^wK7kqG`OVuB_jG|4s2d<3PQ@mO7@4_#SxtuEw^hI(o? zPn5}XD6D)gqpO@6;KD6-LLD6n$tD+f99UXijUo%oLC$89Qb=Z0Gc?tG06DEA4-^PN zqCZW2Pkw%LT-)VlUOkuHW3LMC>dx`TM=SMfsUXrE*Be>0>;k;)JA+R3t`1Vpz8|fHLbf3`=W#%B-}V%o~@7a;}}^q4n4<9 zjX0p(K^^rHT>ATnfth_M6(hfOHC@yftk?^_Vy?P|(Dp{oXPN0jG5#LmXdNvnMl3T6 zHJCu*n{OVCAlScdj(&PZoQvBnCr_g@X5KSl;{|M#QL8E~5rH!z%>-ksrdFZLfAB2B z$+&N!s?TZEofEGwa14(mxY%axslU4(_o}jY$yT;b61U(_Z^&)_C2QT1ajd*RM0DIM z^rZ6!ZkhBUVJhoCic}UHLBn>DZiV?zX8whoZS8F& zz@&us3Sn!d1Y1*fQ!R!Yo@*4tv1m!O^{+n11~VyEBQ0V~&!rk7`3V_he({^%I6zQ& zPXkymoOvkS>v7d=kJW~(FZ8VQ3TQ0W_H1`Zs97q|nP#5DhX)EVCdcyZc5W*3UqSh^-<*0|&G?^+J5^Yy& zC;i~t^Y>XUE9U}1JWtB}?vOIO1_#QC@Iemze9@4}go+C~60*qR09)NE`%<&85#IiU z$E4Wk(*t*P9TtHZvRw(!lOhlx_DaLm=tvZklt81%FQ~q-0m0$B7P{CFeo8&LP0S6dk1H0!B!# zZ3>M6Ct}vm0ImNvHO?f72h~{!c^@@fiQ-Q)-*Yek`JPU%&&8dUlZ9SXt@?$Dry_dr zG)yVgGBjxPUnZdOgr6*f`SmM%A|r5+3h<6+T<8X*TdH2%6wZ}KKjr}5Z~6%QPdc)y z4aT;WW4yC+t_T3@Pcii-@)jQQqdI z>UWT$*4b`ZJ;ZUP%RI0rDJ}JR?VxgonwIpuv+ZXo)rcsoc(U(wFO263UU&fv;w@?A zoqWmZIrr}hFemk9@b3n-U$SFg%lC12o7MD%akc(=Ey)NKO6SXmv65cB@u-FwJTe_G z$dXD>^14}P_Hw%Vr8lw->;MKkwb+o7zXf|%8Fus=;w9lXLC}RRujId8+$caHm%@$1$%k(Mg^-LFy>cxuKWshs|)mWCX#EWQuT+e z9j@qo$8nHTD!R8K&?rHK1f>wQ%xsuNHWCmso07dM#65wm00aAqxX?b>Zqy<>m{9b= zwI7H#yhl<*Uv)Rlz4Mlhzv3VUuiwoyplg=p{;mW^GOGOk1?1jTRE3fKMwIack=*FZ zZq<1O`viLV@3He)(ek}U;{Gva(G1+O&z1(UI8{VgXo1A$$Fbw@J;FZ22_wC2WHPUc ziLSo>mivxv7fBZ5Rn=v#0B(`dMpyfav#8xi*x2*BE5Q9)u{zB_g1HgMJFpAqB*8b> zs_xV&8C)chj3CcUb;xlXHT+xDKAb@y0a|K{-u#;TbOI^{OK)iK#tgTuNR!1MAt*t} zB2?*YEL(y8+Aufd_ZIOM_@y##l5}AWnoYfI43pNQoH5^WtZ#zE-Q;0`r6nZEV;kcS zt#hFzO^YP;!{VUfaw9K^)=C;7g1x;fk(PI6Wl+8FGbqCbF~p!VME`!;)VQ?i4tHo!)BGGU-|2=p+5Jq>^L7&sKr;1 zVp+X)m$RMfqn7WT0Zmm&q$zmcd^mYEc4;0+!vtVSCk31!VFg+F-MhZ3Y7@g&&51no zD9hWwO6pd7++3JY8H46KSFcW%d|YI=$W*gfscjWb8%L_8(<803x~w(0LK?(uq>cPt zQ&ag89L(2>_&up$kGDcq2gs<_S|9*wSP=+B<5;^B=ypFu*1c{0M>m^MK|G3NeicnE4xjQ`~p**4`hKKZ_ zIH{-CAGOpM>_l;gPf2S!c;DM;uKiAKmLQRV+OXr4ek@7j$e^m*gSBLW_h}H~DUx$H z;34+k-C(m-lf*1cK$9*B1>wVe>qwNciubao!tmL5RosLlv)>y8e6R9qiuhwj>amW` zeo`{y1pc)%O#=1}Di~P<)Or$!rbaPUmPsY{kzF#5Lx;<*!$ir1#I@S;sKZETj~Mn0JTnY%~O|tRMr=W+ED0qu83Ob%#&G z-hvrE3bA1C|H(lUv!Ikyze$ei;*p|E5!msaP(Y^q5~6@!BD{4GvU=nmcK6h8Rx@^9 zkKe{-u}{NY!yElEBka!R8Nc~b$Xwvo8(Y5kh9R0e8bP{ws*h*HRqwmmD*&}lU>dvD zDAtcuhV_{q`C6VCwG89~C*X-Ylg_&0xXkb0ox8!v2(GD2O(q3|m6{E(^= zv2H+*ty?(My{F4+PB2DC8;C*nMpimWRD!I?Wb_P)ii_S2xz<}fZ{}<$qq)S@rqCGy zjyQwz$x(!3Y>I7zu0cc9U@0^{*ch@_+$hbP$RWmWQY3vV$W35n$^)3GBvslmA3L;M9`5@GIY z&xYnfu#*4QU+^|&vvycLaMM+)$;nf8JANsUNLVRBt1hsir&a!Szcoe@zWiH-Uk>OX|9Jw{w&) z3P71kYGZi@`JlEMo>ueLu@YuhJgVowCss;9bBH7yc=NXlSLao0WUoI%VZyJ@+)NnK zgR=*mg0g7-CUGACGlNZ#SPpGT15R*WPm~NQF+{cR__D+e8V|ijDd{k@zQ}}Vt@1zH8du{(t3T&X0%fQdF$a%o$EXWs!PeEJS*Di{frHb+chC`NW*a)T*`Lw zin-372?_c@a}rf03gfNAhH~fp2}()gCDdZCUvS!X?1d1Ls-R1K+BUWU=N4pT(`#{Z zA$+3lJUSW1!jehFjzY(gB3SGEtkrjI_=wQmyBw=00zW-7E@6TPxE`t5O7ilJs!S(M z>fVt#nco%ELAIluTHgIYwO2DogNq2|hkveC0#s9P&pA7C|BH<8R$ryIq{jS)EY?a) z+S>H-Mf2F+XMBGkMq;FZH9Q^m>4gfs8E*$W#98pFMKFvZI=R`_5B-TL{yhcje2Lb+ zD+We7%&TA-LwQ0ysB2dc55^U?*LkgK+D}aICaPA1`Ad*3ppUtTMONsylo{tr5?o$- zwaI{(C5tiFB2Ru4$uX`(gij+1Tr(kRW{+MB@S-_^U;ecaxUi=eVq_C?L5sJ=HXC

x3eNBV;f?4~D)6zGD6U08!J*a^^WUqf-7kd+fl ztwcu%RVozQ-tC>V^xZG)!lNfd9>jxv%~ZaHsAz*wVe%f+I>T^YMmy|qH~d!FKvmP* z*iZp7p>r2^p3;RZWnU+QsF6j}3XHRkFn{sGi(m=Tbk`^>B*Yg5fLx;yH2$ZTG{{3` zlf7sTq8^IYu1Mj}#=Qc2Mr9tsd6HCRWcIvEr~)$!y`bsR&y2ssk6UNPWYm!ZB76j$$LEi5JoSr2YSrxs~cv%ox=T^-~@x#(6C zOy=)A$Ay_^vT_66%I~i-AV$#z=XGj!A}EQGsop&{2}XCP3pw&yPfmdtZlx*;HqK%N z0+A2<{~t~>TNj=o-Vo%de^TCHzLCW0mzu=KWqj^(U7Z9Kd*-w;Pqhz#Ku`;CzfvyH zAa~> z?Y7Y|CG=YP*Wu+^!iCk5Pa758SQ zF`rzWR8hu>;!98_oKBXX;W=MO3)Rabfb_r_ue$nopwdGYNlUJ&YPEB8$V{H`u(pH? zOlndw0Yzp2Jadbmnvl9XP*2Xs;{U?*yJ_P9ah~CFsBNW!aN=R$ST@100D=ervIqfK zLqN9;sHd`V@zflq4%ByK0}HMW5SVU6=xSj{I~$ogL!HPCj1LG0SG<|Qsm^1F5C{`= zLD!c--4TO%u7*FTo7op=355(cdS0Pu|G`u`P$#zy`pl7y3+x?Z@v?n3w@cmnybYVA z-VnylKlLDBXXZW8bcH{Za~1Q1{RM`5msaleh)vUQhAAC5g+_w?9gj9M+=%3GitPCa z&bfL#n;L&0C|^iZj|kP_WsBoJc*Ud!IM$wMK7ofEEwM6IM$cpKdo1gg%q$;Ke~M6w zc8a@Ocj$6+X}|mP?9t>0Q%T07<+ibAiyYVkUw7@*7M2gsWY=Cr!M^8M zlOC4S$=n5|tfOLPU8w-4ZXtg{Tt5})NO5W4Yna;dJ+y2(w_5ushGkektKq>ykgbzm zj2^1tU1NJ=yrO}x6>LowdF&{Xl`iW}_-S=gS{=xJXec<(1H~t?hwdSJlRoM7xtzhxG2@EOL~&Jm>hn-PKsERN(O6L-4(9mic(#yI{gl1TB_pL3vZO*HJovQh-x@z1|N8Li<%eck zF27+vsqW8KW4W(kOc~DCqrQX0A^q3^M(zE&Sf4&bIm-%3MG)4KA&(4`%SE~x1c*;E zGS4=sGrxs0n`6(nttC>m!~pIHM?w?1z-c*ixh`}lm)ZhA1v_u6fpK-i9()La00pi6 z)eFnPLx;_p%Nw9xN`x~Y?}NC8y1|~lp6*VT{_@edNDF~c97DlLsQ4lol4(m!2`Ljh zBXV`(5^;Xr- z#8__6EyHAXjU7Eo6>-YW^gmCX(1U57xeb(>PpPFRIeD1UliT1mwJ`C|jNWx-_hyZn z%SIw>3T0}CZzwBrYTNajSf+XDEVuLJPYq+#7_)e&RKfhhyHreO*Kwn3m0)UJBQk&l zf_3mO3JE4LWPNGC9hQpD=P#$mvU7t_*fRT8Z|E6S{SB3^^yBNBUk1?A!Y2Ts_=L10 z+{m3%<+>8Gi(yGQ@8_d3M}1Lu&3zg^KOaUpxN|A)MrV2s{(OPu<7IxWa5I($iB4pP zjLXWEZflCq_FvBle%*r(#3EH}*%!f6XD37IXG0E0(V-2Q{~;AJ9Ba(Luph85G-CPN z7T3-l^$}=-O$t_~!2PX|w^`#Gdzc90%vdl)(%vFfpnbJFpbxr3VhqUQT zk>MIA9I<58nd*wQL;RYo+@&k+{jPM8`H+^whka(eJjbZ8)*pAuBihL7XB$J|^#|eA zaV@~&F7%p5GHzT7s&0wVy7E|{FXHw`-Z}!&Tf0w9V$ZdexzRpHyR(^)tIe1u!9{bOqvR>5xoKU2(`oUbZX0!)V&(KPK10eV5xguN!#?b%UVe+ecJlAe zrDUZR3xg9_q%YaSYspb1+tE)T!ShP>1`m2+Z(b`!WkG~*MoA{E`}Ph?pC6C36DuxI zPO3MZ)5zje$R~d0cnkx`+%p1oCs_aU63qAfd9fX=KO^NWf;FGkal6kB*}=;(=+hvR zFGCeVW9XnHhMD&v;5(W5P}>N5twRf7xfeA z`98{TZ@yX~0uY4$LW6vRVCd&CLfQ8!uCtWo?9t7j7S6FfLw7wI1?lpeVtA8idoTC& zig+%OOVlQ7d$PhteXNI$OSLZ(N)8f3OOk(@>id`f29haSti-+8|Mw!)h?KQ|#-9^T zTW&k@*Cwc@lsL-Y$lQkeiL+$bhey=P;jnGC-!EKt1tfvJW@t`{++op=WT&bRjA+c9 zy{u;IlgZGO+lC8W?`Wl`3*7vrpRNw#FF3B|fg><@F5I^cQ7elDW95|grreq$BR$!c zRQog=dWWsorES0Qh#CTYf|}A%^VD6q{_@xBQTtD`i>+Ai{_=`L8;#StyUJJ8Te@Y& zAQ8TRB~H!dMG)=#qu!Cpb6_m+>34GR@3 z*Dfu%*lJr-sm9=jgT%miXf`e3W(Pc>bV&3+6%6OnkAwz(!Qz2MUe|G<$dbL_#Edhr z2J?pV>AU+h4^g4zX5wt5Qo~bnr=Mo!28(i*>S#5%8i_|_wzh6Bf}{ID(N^VqE+b9E{Z#iTf(n z%NUtH({ z)T9jix)|PwuQvy`sTg5nSGbU%i(g86G2EgC0R4u1mim9f9}{A=vVG1BH>zbXO`WMY z)hc-}%@h0T7J}E5^;=>4?x^c04`U>7c~y;2CoaWhK&_q?rY-(aPefdY(lZw6jXRJM zH3635*+mBO4}7@}DAAryMo4nXw`6e~T{~JtdKFdZ^zp8(bx7O+ewT5KJH<$kz7@?q zrrt0Dtkg0LvBWxmLZ`m;B)9x^_R;)fl!W9%Eqil-%E$D@# zdR3r%sIy6uE}pG=S=mkzL~s$()F%jx$Sw4b>P}_ouxr84`>+TfwI|bd67p@N+((eJ zA+0)VeafN;ENYzP(Ym;IiCzwRuUaJ? zSNQFxG!X57Oc=!S$JMiBN0Lm=*dL2uY0kBCFM{7|?Ch52e>{<8o=1_i1|r0oV(tTV z)2zq!q44@LeKfLFYx!4O$JnZc>&xhiV+Qo2kt-;spJcar5?|BWf*U>{quR|^`~Y;% z5SRvbi_;&7f7Cx3`&fkGdUZge!YRJ?&6Z$PiE-rd;u|yOKmY&%%wfnt;dMv#=eEls zqUYU#Auo`y&6qpLrp{m43`hj?DG62dfbsn4fC|GqDI`E+)W4~YWlSqyZg8&14%-<7hr@4zgdoDk5Km_t<$g3QKM`TsQPY?ic1DqPB0000000000 z00000000000002uFaQ7m00000007ZkN&o-=07VV}00000000000000007j1tH~;_u tE=m9Z002g&*#H0l00K;QVSoT732fsI000000sPw-00000Mk)XR006)0#IpbZ From 77a2b34b02a89f75cf70649fdf52d73f1a72120e Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Wed, 30 Apr 2025 13:51:38 +0100 Subject: [PATCH 039/849] Alerting: Make receiver optional in routing preview and handle fallback case (#104639) --- .../NotificationPreviewByAlertManager.tsx | 7 ++-- .../notificaton-preview/NotificationRoute.tsx | 40 ++++++++++++------- .../NotificationRouteDetailsModal.tsx | 19 ++++++--- .../UnknownContactPointDetails.tsx | 23 +++++++++++ public/locales/en-US/grafana.json | 4 ++ 5 files changed, 69 insertions(+), 24 deletions(-) create mode 100644 public/app/features/alerting/unified/components/rule-editor/notificaton-preview/UnknownContactPointDetails.tsx diff --git a/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationPreviewByAlertManager.tsx b/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationPreviewByAlertManager.tsx index 1b5c9b29bdc..c4fdda45976 100644 --- a/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationPreviewByAlertManager.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationPreviewByAlertManager.tsx @@ -73,14 +73,13 @@ function NotificationPreviewByAlertManager({ if (!route) { return null; } - if (!receiver) { - throw new Error('Receiver not found'); - } return ( ; instancesCount: number; alertManagerSourceName: string; expandRoute: boolean; onExpandRouteClick: (expand: boolean) => void; -}) { +} + +function NotificationRouteHeader({ + route, + receiver, + receiverNameFromRoute, + routesByIdMap, + instancesCount, + alertManagerSourceName, + expandRoute, + onExpandRouteClick, +}: NotificationRouteHeaderProps) { const styles = useStyles2(getStyles); const [showDetails, setShowDetails] = useState(false); @@ -76,7 +85,7 @@ function NotificationRouteHeader({ @ Delivered to {' '} - {receiver.name} + {receiver ? receiver.name : }

@@ -92,6 +101,7 @@ function NotificationRouteHeader({ onClose={() => setShowDetails(false)} route={route} receiver={receiver} + receiverNameFromRoute={receiverNameFromRoute} routesByIdMap={routesByIdMap} alertManagerSourceName={alertManagerSourceName} /> @@ -100,9 +110,9 @@ function NotificationRouteHeader({ ); } -interface NotificationRouteProps { +interface NotificationRouteProps extends ReceiverNameProps { route: RouteWithPath; - receiver: Receiver; + receiver?: Receiver; instanceMatches: AlertInstanceMatch[]; routesByIdMap: Map; alertManagerSourceName: string; @@ -112,6 +122,7 @@ export function NotificationRoute({ route, instanceMatches, receiver, + receiverNameFromRoute, routesByIdMap, alertManagerSourceName, }: NotificationRouteProps) { @@ -126,6 +137,7 @@ export function NotificationRoute({ void; route: RouteWithPath; - receiver: Receiver; + receiver?: Receiver; routesByIdMap: Map; alertManagerSourceName: string; } @@ -63,6 +65,7 @@ export function NotificationRouteDetailsModal({ onClose, route, receiver, + receiverNameFromRoute, routesByIdMap, alertManagerSourceName, }: NotificationRouteDetailsModalProps) { @@ -107,13 +110,17 @@ export function NotificationRouteDetailsModal({ Contact point - {receiver.name} + + {receiver ? receiver.name : } + - - See details - + {receiver ? ( + + See details + + ) : null}
diff --git a/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/UnknownContactPointDetails.tsx b/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/UnknownContactPointDetails.tsx new file mode 100644 index 00000000000..cae79f06f26 --- /dev/null +++ b/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/UnknownContactPointDetails.tsx @@ -0,0 +1,23 @@ +import { Tooltip } from '@grafana/ui'; +import { Trans, t } from 'app/core/internationalization'; + +const UnknownContactPointDetails = ({ receiverName }: { receiverName?: string }) => ( + + + + {receiverName ? ( + receiverName + ) : ( + Unknown contact point + )} + + + +); + +export default UnknownContactPointDetails; diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 8f807d1f473..0d1291a7644 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -2353,6 +2353,10 @@ "type-selector-button": { "add-expression": "Add expression" }, + "unknown-contact-point-details": { + "unknown-contact-point": "Unknown contact point", + "unknown-contact-point-tooltip": "Details could not be found. This may be because you do not have access to the contact point" + }, "unknown-rule-list-item": { "title-unknown-rule-type": "Unknown rule type" }, From b56a4a5295b67c776857d1c9ca5ec7db4a2a4c08 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Wed, 30 Apr 2025 14:03:54 +0100 Subject: [PATCH 040/849] Border radius: Improve rule and fix remaining violations (#104569) * improve rule and fix remaining borderRadius violations * prettier * Add test case for nested classes * Fix unnecessary string wrapping --------- Co-authored-by: Tom Ratcliffe --- eslint.config.js | 1 + .../rules/no-border-radius-literal.cjs | 40 +++++-------------- .../tests/no-border-radius-literal.test.js | 34 +++++++++------- .../src/FlameGraphHeader.tsx | 3 +- .../TimeRangePicker/CalendarBody.tsx | 2 +- .../src/components/Table/TableNG/TableNG.tsx | 1 + .../src/themes/GlobalStyles/card.ts | 4 +- .../src/themes/GlobalStyles/code.ts | 2 +- .../src/themes/GlobalStyles/dashboardGrid.ts | 2 +- .../src/themes/GlobalStyles/dashdiff.ts | 2 +- .../src/themes/GlobalStyles/elements.ts | 1 + .../src/themes/GlobalStyles/filterTable.ts | 2 +- .../unified/components/AlertStateDot.tsx | 3 +- .../contact-points/ContactPoint.tsx | 2 +- .../TransformationEditor.tsx | 29 -------------- .../app/plugins/panel/canvas/globalStyles.ts | 2 +- public/app/plugins/panel/datagrid/utils.ts | 1 + 17 files changed, 44 insertions(+), 87 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index ece56c24c9d..9a112633e0c 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -48,6 +48,7 @@ module.exports = [ 'scripts/grafana-server/tmp', '!.betterer.eslint.config.js', 'packages/grafana-ui/src/graveyard', // deprecated UI components slated for removal + 'public/build-swagger', // swagger build output ], }, // Conditionally run the betterer rules if enabled in dev's config diff --git a/packages/grafana-eslint-rules/rules/no-border-radius-literal.cjs b/packages/grafana-eslint-rules/rules/no-border-radius-literal.cjs index d145bf970bb..27535a5e76c 100644 --- a/packages/grafana-eslint-rules/rules/no-border-radius-literal.cjs +++ b/packages/grafana-eslint-rules/rules/no-border-radius-literal.cjs @@ -1,4 +1,3 @@ -// @ts-check const { ESLintUtils, AST_NODE_TYPES } = require('@typescript-eslint/utils'); const createRule = ESLintUtils.RuleCreator( @@ -8,36 +7,17 @@ const createRule = ESLintUtils.RuleCreator( const borderRadiusRule = createRule({ create(context) { return { - CallExpression(node) { - if (node.callee.type === AST_NODE_TYPES.Identifier && node.callee.name === 'css') { - const cssObjects = node.arguments.flatMap((node) => { - switch (node.type) { - case AST_NODE_TYPES.ObjectExpression: - return [node]; - case AST_NODE_TYPES.ArrayExpression: - return node.elements.filter((v) => v?.type === AST_NODE_TYPES.ObjectExpression); - default: - return []; - } + [`${AST_NODE_TYPES.CallExpression}[callee.name="css"] ${AST_NODE_TYPES.Property}`]: function (node) { + if ( + node.type === AST_NODE_TYPES.Property && + node.key.type === AST_NODE_TYPES.Identifier && + node.key.name === 'borderRadius' && + node.value.type === AST_NODE_TYPES.Literal + ) { + context.report({ + node, + messageId: 'borderRadiusId', }); - - for (const cssObject of cssObjects) { - if (cssObject?.type === AST_NODE_TYPES.ObjectExpression) { - for (const property of cssObject.properties) { - if ( - property.type === AST_NODE_TYPES.Property && - property.key.type === AST_NODE_TYPES.Identifier && - property.key.name === 'borderRadius' && - property.value.type === AST_NODE_TYPES.Literal - ) { - context.report({ - node: property, - messageId: 'borderRadiusId', - }); - } - } - } - } } }, }; diff --git a/packages/grafana-eslint-rules/tests/no-border-radius-literal.test.js b/packages/grafana-eslint-rules/tests/no-border-radius-literal.test.js index a4a90c150c6..7410059db50 100644 --- a/packages/grafana-eslint-rules/tests/no-border-radius-literal.test.js +++ b/packages/grafana-eslint-rules/tests/no-border-radius-literal.test.js @@ -14,6 +14,10 @@ RuleTester.setDefaultConfig({ }, }); +const expectedError = { + messageId: 'borderRadiusId', +}; + const ruleTester = new RuleTester(); ruleTester.run('eslint no-border-radius-literal', noBorderRadiusLiteral, { @@ -32,27 +36,27 @@ ruleTester.run('eslint no-border-radius-literal', noBorderRadiusLiteral, { invalid: [ { code: `css({ borderRadius: '2px' })`, - errors: [ - { - message: 'Prefer using theme.shape.radius tokens instead of literal values.', - }, - ], + errors: [expectedError], }, { code: `css({ lineHeight: 1 }, { borderRadius: '2px' })`, - errors: [ - { - message: 'Prefer using theme.shape.radius tokens instead of literal values.', - }, - ], + errors: [expectedError], }, { code: `css([{ lineHeight: 1 }, { borderRadius: '2px' }])`, - errors: [ - { - message: 'Prefer using theme.shape.radius tokens instead of literal values.', - }, - ], + errors: [expectedError], + }, + { + name: 'nested classes', + code: ` +css({ + foo: { + nested: { + borderRadius: '100px', + }, + }, +})`, + errors: [expectedError], }, ], }); diff --git a/packages/grafana-flamegraph/src/FlameGraphHeader.tsx b/packages/grafana-flamegraph/src/FlameGraphHeader.tsx index 8024dd68d8c..30ad99d8309 100644 --- a/packages/grafana-flamegraph/src/FlameGraphHeader.tsx +++ b/packages/grafana-flamegraph/src/FlameGraphHeader.tsx @@ -302,8 +302,7 @@ const getStyles = (theme: GrafanaTheme2) => ({ display: 'inline-block', width: '10px', height: '10px', - // eslint-disable-next-line @grafana/no-border-radius-literal - borderRadius: '50%', + borderRadius: theme.shape.radius.circle, }), colorDotDiff: css({ label: 'colorDotDiff', diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/CalendarBody.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/CalendarBody.tsx index e5e835dd188..6c3823b652e 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/CalendarBody.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/CalendarBody.tsx @@ -169,7 +169,7 @@ export const getBodyStyles = (theme: GrafanaTheme2) => { abbr: { backgroundColor: theme.colors.primary.main, - borderRadius: '100px', + borderRadius: theme.shape.radius.pill, display: 'block', paddingTop: '2px', height: '26px', diff --git a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx index d130cd6e441..4b3b27fb074 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx @@ -1002,6 +1002,7 @@ const getStyles = (theme: GrafanaTheme2) => ({ }, '::-webkit-scrollbar-thumb': { backgroundColor: 'rgba(204, 204, 220, 0.16)', + // eslint-disable-next-line @grafana/no-border-radius-literal borderRadius: '4px', }, '::-webkit-scrollbar-track': { diff --git a/packages/grafana-ui/src/themes/GlobalStyles/card.ts b/packages/grafana-ui/src/themes/GlobalStyles/card.ts index 97d236f2b41..e5eb15cae29 100644 --- a/packages/grafana-ui/src/themes/GlobalStyles/card.ts +++ b/packages/grafana-ui/src/themes/GlobalStyles/card.ts @@ -21,7 +21,7 @@ export function getCardStyles(theme: GrafanaTheme2) { background: theme.colors.background.secondary, boxShadow: 'none', padding: theme.spacing(2), - borderRadius: '4px', + borderRadius: theme.shape.radius.default, '&:hover': { background: theme.colors.emphasize(theme.colors.background.secondary, 0.03), @@ -158,7 +158,7 @@ export function getCardStyles(theme: GrafanaTheme2) { }, '.card-item': { - borderRadius: '2px', + borderRadius: theme.shape.radius.default, }, '.card-item-header': { diff --git a/packages/grafana-ui/src/themes/GlobalStyles/code.ts b/packages/grafana-ui/src/themes/GlobalStyles/code.ts index 78c45452a59..b22d6c84c24 100644 --- a/packages/grafana-ui/src/themes/GlobalStyles/code.ts +++ b/packages/grafana-ui/src/themes/GlobalStyles/code.ts @@ -10,7 +10,7 @@ export function getCodeStyles(theme: GrafanaTheme2) { backgroundColor: theme.colors.background.primary, color: theme.colors.text.primary, border: `1px solid ${theme.colors.border.medium}`, - borderRadius: '4px', + borderRadius: theme.shape.radius.default, }, code: { diff --git a/packages/grafana-ui/src/themes/GlobalStyles/dashboardGrid.ts b/packages/grafana-ui/src/themes/GlobalStyles/dashboardGrid.ts index 60eb0a50adf..a5ef166844f 100644 --- a/packages/grafana-ui/src/themes/GlobalStyles/dashboardGrid.ts +++ b/packages/grafana-ui/src/themes/GlobalStyles/dashboardGrid.ts @@ -113,7 +113,7 @@ export function getDashboardGridStyles(theme: GrafanaTheme2) { '&:is(:hover),&:not(:hover)': { outline: `2px solid ${theme.colors.primary.border}`, outlineOffset: '0px', - borderRadius: '2px', + borderRadius: theme.shape.radius.default, }, }, diff --git a/packages/grafana-ui/src/themes/GlobalStyles/dashdiff.ts b/packages/grafana-ui/src/themes/GlobalStyles/dashdiff.ts index 8ffb3e250b0..df5ff83e056 100644 --- a/packages/grafana-ui/src/themes/GlobalStyles/dashdiff.ts +++ b/packages/grafana-ui/src/themes/GlobalStyles/dashdiff.ts @@ -142,7 +142,7 @@ export function getDashDiffStyles(theme: GrafanaTheme2) { '.diff-label': { backgroundColor: theme.colors.action.hover, - borderRadius: '3px', + borderRadius: theme.shape.radius.default, color: theme.colors.text.primary, display: 'inline', fontSize: `${theme.typography.fontSize}px`, diff --git a/packages/grafana-ui/src/themes/GlobalStyles/elements.ts b/packages/grafana-ui/src/themes/GlobalStyles/elements.ts index abe6ac1560a..c8409c1db46 100644 --- a/packages/grafana-ui/src/themes/GlobalStyles/elements.ts +++ b/packages/grafana-ui/src/themes/GlobalStyles/elements.ts @@ -276,6 +276,7 @@ export function getElementStyles(theme: GrafanaTheme2, isExtensionSidebarOpen?: // 2. Correct font properties not being inherited. // 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 'button, input, optgroup, select, textarea': { + // eslint-disable-next-line @grafana/no-border-radius-literal borderRadius: 0, color: 'inherit', font: 'inherit', diff --git a/packages/grafana-ui/src/themes/GlobalStyles/filterTable.ts b/packages/grafana-ui/src/themes/GlobalStyles/filterTable.ts index 920b9bcb342..602d5d51496 100644 --- a/packages/grafana-ui/src/themes/GlobalStyles/filterTable.ts +++ b/packages/grafana-ui/src/themes/GlobalStyles/filterTable.ts @@ -66,7 +66,7 @@ export function getFilterTableStyles(theme: GrafanaTheme2) { '.filter-table__avatar': { width: '25px', height: '25px', - borderRadius: '50%', + borderRadius: theme.shape.radius.circle, }, '&--hover': { diff --git a/public/app/features/alerting/unified/components/AlertStateDot.tsx b/public/app/features/alerting/unified/components/AlertStateDot.tsx index 67e20b5b555..ad6f8308f5e 100644 --- a/public/app/features/alerting/unified/components/AlertStateDot.tsx +++ b/public/app/features/alerting/unified/components/AlertStateDot.tsx @@ -32,8 +32,7 @@ const getDotStyles = (theme: GrafanaTheme2, props: DotStylesProps) => { width: size, height: size, - // eslint-disable-next-line @grafana/no-border-radius-literal - borderRadius: '100%', + borderRadius: theme.shape.radius.circle, backgroundColor: theme.colors.secondary.main, outline: `solid ${outlineSize} ${theme.colors.secondary.transparent}`, diff --git a/public/app/features/alerting/unified/components/contact-points/ContactPoint.tsx b/public/app/features/alerting/unified/components/contact-points/ContactPoint.tsx index dfe2d07589c..8b5ef02c827 100644 --- a/public/app/features/alerting/unified/components/contact-points/ContactPoint.tsx +++ b/public/app/features/alerting/unified/components/contact-points/ContactPoint.tsx @@ -273,7 +273,7 @@ const ContactPointReceiverMetadataRow = ({ diagnostics, sendingResolved }: Conta const getStyles = (theme: GrafanaTheme2) => ({ contactPointWrapper: css({ - borderRadius: `${theme.shape.radius.default}`, + borderRadius: theme.shape.radius.default, border: `solid 1px ${theme.colors.border.weak}`, borderBottom: 'none', }), diff --git a/public/app/features/dashboard/components/TransformationsEditor/TransformationEditor.tsx b/public/app/features/dashboard/components/TransformationsEditor/TransformationEditor.tsx index f761bc684a6..5624830b261 100644 --- a/public/app/features/dashboard/components/TransformationsEditor/TransformationEditor.tsx +++ b/public/app/features/dashboard/components/TransformationsEditor/TransformationEditor.tsx @@ -86,35 +86,6 @@ export const TransformationEditor = ({ const getStyles = (theme: GrafanaTheme2) => { return { - title: css({ - display: 'flex', - padding: '4px 8px 4px 8px', - position: 'relative', - height: '35px', - // eslint-disable-next-line @grafana/no-border-radius-literal - borderRadius: '4px 4px 0 0', - flexWrap: 'nowrap', - justifyContent: 'space-between', - alignItems: 'center', - }), - name: css({ - fontWeight: theme.typography.fontWeightMedium, - color: theme.colors.primary.text, - }), - iconRow: css({ - display: 'flex', - }), - icon: css({ - background: 'transparent', - border: 'none', - boxShadow: 'none', - cursor: 'pointer', - color: theme.colors.text.secondary, - marginLeft: theme.spacing(1), - '&:hover': { - color: theme.colors.text.primary, - }, - }), debugWrapper: css({ display: 'flex', flexDirection: 'row', diff --git a/public/app/plugins/panel/canvas/globalStyles.ts b/public/app/plugins/panel/canvas/globalStyles.ts index a61d39d39d5..a40387c5b2a 100644 --- a/public/app/plugins/panel/canvas/globalStyles.ts +++ b/public/app/plugins/panel/canvas/globalStyles.ts @@ -157,7 +157,7 @@ export function getGlobalStyles(theme: GrafanaTheme2) { '&.rc-tree-checkbox-indeterminate.rc-tree-checkbox-disabled': { position: 'relative', background: '#ccc', - borderRadius: '3px', + borderRadius: theme.shape.radius.default, '&::after': { position: 'absolute', top: '5px', diff --git a/public/app/plugins/panel/datagrid/utils.ts b/public/app/plugins/panel/datagrid/utils.ts index a32c1df5383..2fb003578fb 100644 --- a/public/app/plugins/panel/datagrid/utils.ts +++ b/public/app/plugins/panel/datagrid/utils.ts @@ -263,6 +263,7 @@ export const getStyles = (theme: GrafanaTheme2, isResizeInProgress: boolean) => background: theme.colors.background.primary, }, '::-webkit-scrollbar-thumb': { + // eslint-disable-next-line @grafana/no-border-radius-literal borderRadius: '10px', }, '::-webkit-scrollbar-corner': { From 9ed5b4efa23b9aa85f973d3956e46b9cba38912e Mon Sep 17 00:00:00 2001 From: Juan Cabanas Date: Wed, 30 Apr 2025 10:04:31 -0300 Subject: [PATCH 041/849] Grafana UI: Update `CollapsableSection` to be controlled (#104642) --- .../Collapse/CollapsableSection.tsx | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/grafana-ui/src/components/Collapse/CollapsableSection.tsx b/packages/grafana-ui/src/components/Collapse/CollapsableSection.tsx index ac5852785a3..d36e48528d6 100644 --- a/packages/grafana-ui/src/components/Collapse/CollapsableSection.tsx +++ b/packages/grafana-ui/src/components/Collapse/CollapsableSection.tsx @@ -38,9 +38,12 @@ export const CollapsableSection = ({ contentDataTestId, unmountContentWhenClosed = true, }: Props) => { - const [open, toggleOpen] = useState(isOpen); + const [internalOpenState, toggleInternalOpenState] = useState(isOpen); const styles = useStyles2(collapsableSectionStyles); + const isControlled = isOpen !== undefined && onToggle !== undefined; + const isSectionOpen = isControlled ? isOpen : internalOpenState; + const onClick = (e: React.MouseEvent) => { if (e.target instanceof HTMLElement && e.target.tagName === 'A') { return; @@ -49,8 +52,11 @@ export const CollapsableSection = ({ e.preventDefault(); e.stopPropagation(); - onToggle?.(!open); - toggleOpen(!open); + onToggle?.(!isOpen); + + if (!isControlled) { + toggleInternalOpenState(!internalOpenState); + } }; const { current: id } = useRef(uniqueId()); @@ -60,7 +66,7 @@ export const CollapsableSection = ({
@@ -79,21 +85,21 @@ export const CollapsableSection = ({ id={`collapse-button-${id}`} className={styles.button} onClick={onClick} - aria-expanded={open && !loading} + aria-expanded={isSectionOpen && !loading} aria-controls={`collapse-content-${id}`} aria-labelledby={buttonLabelId} > {loading ? ( ) : ( - + )}
{label}
- {unmountContentWhenClosed ? open && content : content} + {unmountContentWhenClosed ? isSectionOpen && content : content} ); }; From da32b9e16fea8906aebdcc492fac30d8e67e09dd Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 30 Apr 2025 15:05:39 +0200 Subject: [PATCH 042/849] Zanzana: Fix health check endpoint (#104670) --- pkg/services/authz/zanzana/server/server.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/services/authz/zanzana/server/server.go b/pkg/services/authz/zanzana/server/server.go index b0d17dc76d4..6803fdb594f 100644 --- a/pkg/services/authz/zanzana/server/server.go +++ b/pkg/services/authz/zanzana/server/server.go @@ -9,6 +9,7 @@ import ( "github.com/fullstorydev/grpchan/inprocgrpc" authzv1 "github.com/grafana/authlib/authz/proto/v1" openfgav1 "github.com/openfga/api/proto/openfga/v1" + "google.golang.org/protobuf/types/known/wrapperspb" dashboardalpha1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" "github.com/grafana/grafana/pkg/infra/localcache" @@ -70,7 +71,10 @@ func NewServer(cfg setting.ZanzanaServerSettings, openfga OpenFGAServer, logger } func (s *Server) IsHealthy(ctx context.Context) (bool, error) { - return s.openfga.IsReady(ctx) + _, err := s.openfga.ListStores(ctx, &openfgav1.ListStoresRequest{ + PageSize: wrapperspb.Int32(1), + }) + return err == nil, nil } func (s *Server) getContextuals(subject string) (*openfgav1.ContextualTupleKeys, error) { From 439df585e0eedde313f814aca75991cf78e92e88 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 30 Apr 2025 16:22:59 +0300 Subject: [PATCH 043/849] TestData: Support a steps scenario (#104736) --- .../kinds/query.go | 1 + .../kinds/query.panel.schema.json | 3 +- .../kinds/query.request.schema.json | 3 +- .../kinds/query.types.json | 5 ++- .../grafana-testdata-datasource/scenarios.go | 12 ++++-- .../QueryEditor.tsx | 4 ++ .../grafana-testdata-datasource/dataquery.ts | 1 + .../grafana-testdata-datasource/datasource.ts | 37 +++++++++++++++++++ 8 files changed, 59 insertions(+), 7 deletions(-) diff --git a/pkg/tsdb/grafana-testdata-datasource/kinds/query.go b/pkg/tsdb/grafana-testdata-datasource/kinds/query.go index a2fc0bf6f05..1e325205c5b 100644 --- a/pkg/tsdb/grafana-testdata-datasource/kinds/query.go +++ b/pkg/tsdb/grafana-testdata-datasource/kinds/query.go @@ -77,6 +77,7 @@ const ( TestDataQueryTypeRandomWalkWithError TestDataQueryType = "random_walk_with_error" TestDataQueryTypeRawFrame TestDataQueryType = "raw_frame" TestDataQueryTypeServerError500 TestDataQueryType = "server_error_500" + TestDataQueryTypeSteps TestDataQueryType = "steps" TestDataQueryTypeSimulation TestDataQueryType = "simulation" TestDataQueryTypeSlowQuery TestDataQueryType = "slow_query" TestDataQueryTypeStreamingClient TestDataQueryType = "streaming_client" diff --git a/pkg/tsdb/grafana-testdata-datasource/kinds/query.panel.schema.json b/pkg/tsdb/grafana-testdata-datasource/kinds/query.panel.schema.json index 6fe899b2139..419e2afcc29 100644 --- a/pkg/tsdb/grafana-testdata-datasource/kinds/query.panel.schema.json +++ b/pkg/tsdb/grafana-testdata-datasource/kinds/query.panel.schema.json @@ -229,7 +229,7 @@ "additionalProperties": false }, "scenarioId": { - "description": "Possible enum values:\n - `\"annotations\"` \n - `\"arrow\"` \n - `\"csv_content\"` \n - `\"csv_file\"` \n - `\"csv_metric_values\"` \n - `\"datapoints_outside_range\"` \n - `\"error_with_source\"` \n - `\"exponential_heatmap_bucket_data\"` \n - `\"flame_graph\"` \n - `\"grafana_api\"` \n - `\"linear_heatmap_bucket_data\"` \n - `\"live\"` \n - `\"logs\"` \n - `\"manual_entry\"` \n - `\"no_data_points\"` \n - `\"node_graph\"` \n - `\"predictable_csv_wave\"` \n - `\"predictable_pulse\"` \n - `\"random_walk\"` \n - `\"random_walk_table\"` \n - `\"random_walk_with_error\"` \n - `\"raw_frame\"` \n - `\"server_error_500\"` \n - `\"simulation\"` \n - `\"slow_query\"` \n - `\"streaming_client\"` \n - `\"table_static\"` \n - `\"trace\"` \n - `\"usa\"` \n - `\"variables-query\"` ", + "description": "Possible enum values:\n - `\"annotations\"` \n - `\"arrow\"` \n - `\"csv_content\"` \n - `\"csv_file\"` \n - `\"csv_metric_values\"` \n - `\"datapoints_outside_range\"` \n - `\"error_with_source\"` \n - `\"exponential_heatmap_bucket_data\"` \n - `\"flame_graph\"` \n - `\"grafana_api\"` \n - `\"linear_heatmap_bucket_data\"` \n - `\"live\"` \n - `\"logs\"` \n - `\"manual_entry\"` \n - `\"no_data_points\"` \n - `\"node_graph\"` \n - `\"predictable_csv_wave\"` \n - `\"predictable_pulse\"` \n - `\"random_walk\"` \n - `\"random_walk_table\"` \n - `\"random_walk_with_error\"` \n - `\"raw_frame\"` \n - `\"server_error_500\"` \n - `\"steps\"` \n - `\"simulation\"` \n - `\"slow_query\"` \n - `\"streaming_client\"` \n - `\"table_static\"` \n - `\"trace\"` \n - `\"usa\"` \n - `\"variables-query\"` ", "type": "string", "enum": [ "annotations", @@ -255,6 +255,7 @@ "random_walk_with_error", "raw_frame", "server_error_500", + "steps", "simulation", "slow_query", "streaming_client", diff --git a/pkg/tsdb/grafana-testdata-datasource/kinds/query.request.schema.json b/pkg/tsdb/grafana-testdata-datasource/kinds/query.request.schema.json index 75795b52ee7..824bea0896b 100644 --- a/pkg/tsdb/grafana-testdata-datasource/kinds/query.request.schema.json +++ b/pkg/tsdb/grafana-testdata-datasource/kinds/query.request.schema.json @@ -239,7 +239,7 @@ "additionalProperties": false }, "scenarioId": { - "description": "Possible enum values:\n - `\"annotations\"` \n - `\"arrow\"` \n - `\"csv_content\"` \n - `\"csv_file\"` \n - `\"csv_metric_values\"` \n - `\"datapoints_outside_range\"` \n - `\"error_with_source\"` \n - `\"exponential_heatmap_bucket_data\"` \n - `\"flame_graph\"` \n - `\"grafana_api\"` \n - `\"linear_heatmap_bucket_data\"` \n - `\"live\"` \n - `\"logs\"` \n - `\"manual_entry\"` \n - `\"no_data_points\"` \n - `\"node_graph\"` \n - `\"predictable_csv_wave\"` \n - `\"predictable_pulse\"` \n - `\"random_walk\"` \n - `\"random_walk_table\"` \n - `\"random_walk_with_error\"` \n - `\"raw_frame\"` \n - `\"server_error_500\"` \n - `\"simulation\"` \n - `\"slow_query\"` \n - `\"streaming_client\"` \n - `\"table_static\"` \n - `\"trace\"` \n - `\"usa\"` \n - `\"variables-query\"` ", + "description": "Possible enum values:\n - `\"annotations\"` \n - `\"arrow\"` \n - `\"csv_content\"` \n - `\"csv_file\"` \n - `\"csv_metric_values\"` \n - `\"datapoints_outside_range\"` \n - `\"error_with_source\"` \n - `\"exponential_heatmap_bucket_data\"` \n - `\"flame_graph\"` \n - `\"grafana_api\"` \n - `\"linear_heatmap_bucket_data\"` \n - `\"live\"` \n - `\"logs\"` \n - `\"manual_entry\"` \n - `\"no_data_points\"` \n - `\"node_graph\"` \n - `\"predictable_csv_wave\"` \n - `\"predictable_pulse\"` \n - `\"random_walk\"` \n - `\"random_walk_table\"` \n - `\"random_walk_with_error\"` \n - `\"raw_frame\"` \n - `\"server_error_500\"` \n - `\"steps\"` \n - `\"simulation\"` \n - `\"slow_query\"` \n - `\"streaming_client\"` \n - `\"table_static\"` \n - `\"trace\"` \n - `\"usa\"` \n - `\"variables-query\"` ", "type": "string", "enum": [ "annotations", @@ -265,6 +265,7 @@ "random_walk_with_error", "raw_frame", "server_error_500", + "steps", "simulation", "slow_query", "streaming_client", diff --git a/pkg/tsdb/grafana-testdata-datasource/kinds/query.types.json b/pkg/tsdb/grafana-testdata-datasource/kinds/query.types.json index 2a6a17b811f..af7cb24a888 100644 --- a/pkg/tsdb/grafana-testdata-datasource/kinds/query.types.json +++ b/pkg/tsdb/grafana-testdata-datasource/kinds/query.types.json @@ -8,7 +8,7 @@ { "metadata": { "name": "default", - "resourceVersion": "1728405292506", + "resourceVersion": "1745998648052", "creationTimestamp": "2024-03-01T02:53:35Z" }, "spec": { @@ -151,7 +151,7 @@ "type": "string" }, "scenarioId": { - "description": "Possible enum values:\n - `\"annotations\"` \n - `\"arrow\"` \n - `\"csv_content\"` \n - `\"csv_file\"` \n - `\"csv_metric_values\"` \n - `\"datapoints_outside_range\"` \n - `\"error_with_source\"` \n - `\"exponential_heatmap_bucket_data\"` \n - `\"flame_graph\"` \n - `\"grafana_api\"` \n - `\"linear_heatmap_bucket_data\"` \n - `\"live\"` \n - `\"logs\"` \n - `\"manual_entry\"` \n - `\"no_data_points\"` \n - `\"node_graph\"` \n - `\"predictable_csv_wave\"` \n - `\"predictable_pulse\"` \n - `\"random_walk\"` \n - `\"random_walk_table\"` \n - `\"random_walk_with_error\"` \n - `\"raw_frame\"` \n - `\"server_error_500\"` \n - `\"simulation\"` \n - `\"slow_query\"` \n - `\"streaming_client\"` \n - `\"table_static\"` \n - `\"trace\"` \n - `\"usa\"` \n - `\"variables-query\"` ", + "description": "Possible enum values:\n - `\"annotations\"` \n - `\"arrow\"` \n - `\"csv_content\"` \n - `\"csv_file\"` \n - `\"csv_metric_values\"` \n - `\"datapoints_outside_range\"` \n - `\"error_with_source\"` \n - `\"exponential_heatmap_bucket_data\"` \n - `\"flame_graph\"` \n - `\"grafana_api\"` \n - `\"linear_heatmap_bucket_data\"` \n - `\"live\"` \n - `\"logs\"` \n - `\"manual_entry\"` \n - `\"no_data_points\"` \n - `\"node_graph\"` \n - `\"predictable_csv_wave\"` \n - `\"predictable_pulse\"` \n - `\"random_walk\"` \n - `\"random_walk_table\"` \n - `\"random_walk_with_error\"` \n - `\"raw_frame\"` \n - `\"server_error_500\"` \n - `\"steps\"` \n - `\"simulation\"` \n - `\"slow_query\"` \n - `\"streaming_client\"` \n - `\"table_static\"` \n - `\"trace\"` \n - `\"usa\"` \n - `\"variables-query\"` ", "enum": [ "annotations", "arrow", @@ -176,6 +176,7 @@ "random_walk_with_error", "raw_frame", "server_error_500", + "steps", "simulation", "slow_query", "streaming_client", diff --git a/pkg/tsdb/grafana-testdata-datasource/scenarios.go b/pkg/tsdb/grafana-testdata-datasource/scenarios.go index 7b5c4d66d30..6a0b20068cc 100644 --- a/pkg/tsdb/grafana-testdata-datasource/scenarios.go +++ b/pkg/tsdb/grafana-testdata-datasource/scenarios.go @@ -12,13 +12,13 @@ import ( "strings" "time" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" + "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend/log" "github.com/grafana/grafana-plugin-sdk-go/backend/tracing" "github.com/grafana/grafana-plugin-sdk-go/data" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" - "github.com/grafana/grafana/pkg/tsdb/grafana-testdata-datasource/kinds" ) @@ -110,6 +110,12 @@ Timestamps will line up evenly on timeStepSeconds (For example, 60 seconds means handler: s.handleClientSideScenario, }) + s.registerScenario(&Scenario{ + ID: kinds.TestDataQueryTypeSteps, + Name: "Steps", + handler: s.handleClientSideScenario, + }) + s.registerScenario(&Scenario{ ID: kinds.TestDataQueryTypeSimulation, Name: "Simulation", diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/QueryEditor.tsx b/public/app/plugins/datasource/grafana-testdata-datasource/QueryEditor.tsx index 66eb259dbde..95b67bf1f0c 100644 --- a/public/app/plugins/datasource/grafana-testdata-datasource/QueryEditor.tsx +++ b/public/app/plugins/datasource/grafana-testdata-datasource/QueryEditor.tsx @@ -117,6 +117,9 @@ export const QueryEditor = ({ query, datasource, onChange, onRunQuery }: Props) case TestDataQueryType.Annotations: update.lines = 10; break; + case TestDataQueryType.Steps: + update.csvContent = 'a\nb\nc\n'; + break; case TestDataQueryType.USA: update.usa = { mode: usaQueryModes[0].value, @@ -293,6 +296,7 @@ export const QueryEditor = ({ query, datasource, onChange, onRunQuery }: Props) {scenarioId === TestDataQueryType.CSVContent && ( )} + {scenarioId === TestDataQueryType.Steps && } {scenarioId === TestDataQueryType.Logs && ( diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/dataquery.ts b/public/app/plugins/datasource/grafana-testdata-datasource/dataquery.ts index 620f5233b0a..4d4a2084fc1 100644 --- a/public/app/plugins/datasource/grafana-testdata-datasource/dataquery.ts +++ b/public/app/plugins/datasource/grafana-testdata-datasource/dataquery.ts @@ -27,6 +27,7 @@ export enum TestDataQueryType { RawFrame = 'raw_frame', ServerError500 = 'server_error_500', Simulation = 'simulation', + Steps = 'steps', SlowQuery = 'slow_query', StreamingClient = 'streaming_client', TableStatic = 'table_static', diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/datasource.ts b/public/app/plugins/datasource/grafana-testdata-datasource/datasource.ts index dbe61c56f79..3fd778a7659 100644 --- a/public/app/plugins/datasource/grafana-testdata-datasource/datasource.ts +++ b/public/app/plugins/datasource/grafana-testdata-datasource/datasource.ts @@ -17,6 +17,7 @@ import { MutableDataFrame, AnnotationQuery, getSearchFilterScopedVar, + FieldType, } from '@grafana/data'; import { DataSourceWithBackend, getBackendSrv, getGrafanaLiveSrv, getTemplateSrv, TemplateSrv } from '@grafana/runtime'; @@ -97,6 +98,9 @@ export class TestDataDataSource extends DataSourceWithBackend case 'flame_graph': streams.push(this.flameGraphQuery(target)); break; + case 'steps': + streams.push(this.stepsQuery(target)); + break; case 'trace': streams.push(this.trace(options)); break; @@ -348,6 +352,39 @@ export class TestDataDataSource extends DataSourceWithBackend } } + // Incremented with each refresh in a step query + step = 0; + + stepsQuery(target: TestDataDataQuery): Observable { + let steps = (target.csvContent ?? `a\n,b\nc\n`) + .split('\n') + .map((v) => v.trim()) + .filter((v) => Boolean(v.length)); + + this.step = this.step % steps.length; + const step = target.alias?.length ? target.alias : 'step'; + + const frame: DataFrame = { + refId: target.refId, + fields: [ + { name: 'time', type: FieldType.time, values: [Date.now()], config: {} }, + { name: 'index', type: FieldType.number, values: [this.step], config: {} }, + { name: step, type: FieldType.string, values: [steps[this.step]], config: {} }, + ], + length: 1, + }; + for (let i = 0; i < steps.length; i++) { + frame.fields.push({ + name: `${step}-${steps[i]}`, + type: FieldType.boolean, + values: [i <= this.step], + config: {}, + }); + } + this.step++; + return of({ data: [frame] }).pipe(delay(50)); + } + serverErrorQuery( target: TestDataDataQuery, options: DataQueryRequest From 1c5545da04c6f4fdd9783930990ef6b4458bb401 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Wed, 30 Apr 2025 14:40:15 +0100 Subject: [PATCH 044/849] Internationalisation: Check and mark up tooltip content prop (#104536) * make lint rule check for "content" * undo unnecessary translation --- .../rules/no-untranslated-strings.cjs | 2 +- .../TimeRangePicker/TimeRangeContent.tsx | 6 +- .../PanelChrome/LoadingIndicator.tsx | 3 +- .../components/PanelChrome/PanelChrome.tsx | 10 +- .../AccessControl/PermissionListItem.tsx | 8 +- .../ForgottenPassword/ChangePassword.tsx | 5 +- .../admin/AdminFeatureTogglesTable.tsx | 7 +- .../app/features/admin/Users/UsersTable.tsx | 13 ++- .../admin/ldap/LdapConnectionStatus.tsx | 14 ++- .../features/admin/ldap/LdapUserGroups.tsx | 9 +- .../bridges/DeclareIncidentButton.tsx | 14 ++- .../mute-timings/MuteTimingTimeRange.tsx | 8 +- .../notification-policies/Policy.tsx | 24 ++++- .../components/receivers/TemplatesTable.tsx | 6 +- .../rule-editor/DashboardPicker.tsx | 14 ++- .../rule-types/DisabledTooltip.tsx | 9 +- .../components/rules/AlertStateTag.tsx | 9 +- .../unified/components/rules/RuleDetails.tsx | 8 +- .../components/rules/RuleListErrors.tsx | 5 +- .../unified/components/rules/RuleState.tsx | 10 +- .../unified/components/rules/RulesTable.tsx | 7 +- .../CentralAlertHistoryScene.tsx | 4 +- .../rules/state-history/LokiStateHistory.tsx | 7 +- .../unified/plugins/PluginOriginBadge.tsx | 13 ++- .../rule-list/components/GroupStatus.tsx | 3 +- public/app/features/api-keys/ApiKeysTable.tsx | 7 +- .../variables/VariableEditorListRow.tsx | 14 ++- .../variables/VariablesUnknownTable.tsx | 9 +- .../version-history/VersionHistoryButtons.tsx | 10 +- .../explore/CorrelationEditorModeBar.tsx | 9 +- .../features/explore/Logs/LogsVolumePanel.tsx | 3 +- .../SpanFilters/SpanFilters.tsx | 5 +- .../TracePageHeader/TracePageHeader.tsx | 12 ++- .../TraceView/components/common/CopyIcon.tsx | 4 +- .../app/features/logs/components/LogRow.tsx | 8 +- .../admin/components/UpdateAllModalBody.tsx | 4 +- .../features/plugins/admin/pages/Browse.tsx | 5 +- .../features/profile/UserProfileEditForm.tsx | 7 +- .../components/ServiceAccountTokensTable.tsx | 11 +- public/locales/en-US/grafana.json | 101 +++++++++++++++--- 40 files changed, 356 insertions(+), 71 deletions(-) diff --git a/packages/grafana-eslint-rules/rules/no-untranslated-strings.cjs b/packages/grafana-eslint-rules/rules/no-untranslated-strings.cjs index 4dca9276341..b82adf2b412 100644 --- a/packages/grafana-eslint-rules/rules/no-untranslated-strings.cjs +++ b/packages/grafana-eslint-rules/rules/no-untranslated-strings.cjs @@ -21,7 +21,7 @@ const createRule = ESLintUtils.RuleCreator( ); /** @type {string[]} */ -const propsToCheck = ['label', 'description', 'placeholder', 'aria-label', 'title', 'text', 'tooltip']; +const propsToCheck = ['content', 'label', 'description', 'placeholder', 'aria-label', 'title', 'text', 'tooltip']; /** @type {RuleDefinition} */ const noUntranslatedStrings = createRule({ diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimeRangeContent.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimeRangeContent.tsx index 1a87d264f7a..ecb236f077b 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimeRangeContent.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimeRangeContent.tsx @@ -136,13 +136,15 @@ export const TimeRangeContent = (props: Props) => { }; const fiscalYear = rangeUtil.convertRawToRange({ from: 'now/fy', to: 'now/fy' }, timeZone, fiscalYearStartMonth); - const fiscalYearMessage = t('time-picker.range-content.fiscal-year', 'Fiscal year'); const fyTooltip = (
{rangeUtil.isFiscal(value) ? ( diff --git a/packages/grafana-ui/src/components/PanelChrome/LoadingIndicator.tsx b/packages/grafana-ui/src/components/PanelChrome/LoadingIndicator.tsx index dc1357c521d..6fbb87ccbb3 100644 --- a/packages/grafana-ui/src/components/PanelChrome/LoadingIndicator.tsx +++ b/packages/grafana-ui/src/components/PanelChrome/LoadingIndicator.tsx @@ -4,6 +4,7 @@ import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { useStyles2 } from '../../themes'; +import { t } from '../../utils/i18n'; import { Icon } from '../Icon/Icon'; import { Tooltip } from '../Tooltip/Tooltip'; @@ -27,7 +28,7 @@ export const LoadingIndicator = ({ onCancel, loading }: LoadingIndicatorProps) = } return ( - + {loadingState === LoadingState.Streaming && ( - + @@ -301,7 +307,7 @@ export function PanelChrome({ )} {loadingState === LoadingState.Loading && onCancelQuery && ( - + ) : ( - + )} {installed === false && ( - + @@ -64,7 +69,12 @@ export const DeclareIncidentMenuItem = ({ title = '', severity = '', url = '' }: /> )} {installed === false && ( - + { ( - + {children} )} diff --git a/public/app/features/alerting/unified/components/notification-policies/Policy.tsx b/public/app/features/alerting/unified/components/notification-policies/Policy.tsx index 68d9cfce27d..f49cdc34e87 100644 --- a/public/app/features/alerting/unified/components/notification-policies/Policy.tsx +++ b/public/app/features/alerting/unified/components/notification-policies/Policy.tsx @@ -623,7 +623,13 @@ export function isAutoGeneratedRootAndSimplifiedEnabled(route: RouteWithID) { } const ProvisionedTooltip = (children: ReactNode) => ( - + {children} ); @@ -649,7 +655,13 @@ const Errors: FC<{ errors: React.ReactNode[] }> = ({ errors }) => ( const ContinueMatchingIndicator: FC = () => { const styles = useStyles2(getStyles); return ( - +
@@ -660,7 +672,13 @@ const ContinueMatchingIndicator: FC = () => { const AllMatchesIndicator: FC = () => { const styles = useStyles2(getStyles); return ( - +
diff --git a/public/app/features/alerting/unified/components/receivers/TemplatesTable.tsx b/public/app/features/alerting/unified/components/receivers/TemplatesTable.tsx index c023a052d13..632dafb9ae3 100644 --- a/public/app/features/alerting/unified/components/receivers/TemplatesTable.tsx +++ b/public/app/features/alerting/unified/components/receivers/TemplatesTable.tsx @@ -143,8 +143,10 @@ function TemplateRow({ notificationTemplate, idx, alertManagerName, onDeleteClic This template is misconfigured.
- Templates must be defined in both the and{' '} - sections of your alertmanager configuration. + Templates must be defined in both the{' '} + and{' '} + sections of your + alertmanager configuration. } diff --git a/public/app/features/alerting/unified/components/rule-editor/DashboardPicker.tsx b/public/app/features/alerting/unified/components/rule-editor/DashboardPicker.tsx index 62782920142..48de57ff77c 100644 --- a/public/app/features/alerting/unified/components/rule-editor/DashboardPicker.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/DashboardPicker.tsx @@ -153,12 +153,22 @@ export const DashboardPicker = ({ dashboardUid, panelId, isOpen, onChange, onDis {panelTitle}
{!isAlertingCompatible && !disabled && ( - + )} {disabled && ( - + )} diff --git a/public/app/features/alerting/unified/components/rule-editor/rule-types/DisabledTooltip.tsx b/public/app/features/alerting/unified/components/rule-editor/rule-types/DisabledTooltip.tsx index ffbffcfa635..5d480b44096 100644 --- a/public/app/features/alerting/unified/components/rule-editor/rule-types/DisabledTooltip.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/rule-types/DisabledTooltip.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { Tooltip } from '@grafana/ui'; +import { t } from 'app/core/internationalization'; type Props = { visible: boolean; @@ -12,7 +13,13 @@ const DisabledTooltip = ({ children, visible = false }: React.PropsWithChildren< } return ( - +
{children}
); diff --git a/public/app/features/alerting/unified/components/rules/AlertStateTag.tsx b/public/app/features/alerting/unified/components/rules/AlertStateTag.tsx index b8467088695..f13294540ca 100644 --- a/public/app/features/alerting/unified/components/rules/AlertStateTag.tsx +++ b/public/app/features/alerting/unified/components/rules/AlertStateTag.tsx @@ -2,6 +2,7 @@ import { memo } from 'react'; import { AlertState } from '@grafana/data'; import { Icon, Tooltip } from '@grafana/ui'; +import { t } from 'app/core/internationalization'; import { GrafanaAlertState, GrafanaAlertStateWithReason, PromAlertingRuleState } from 'app/types/unified-alerting-dto'; import { Trans } from '../../../../../core/internationalization'; @@ -17,7 +18,13 @@ interface Props { export const AlertStateTag = memo(({ state, isPaused = false, size = 'md', muted = false }: Props) => { if (isPaused) { return ( - + Paused diff --git a/public/app/features/alerting/unified/components/rules/RuleDetails.tsx b/public/app/features/alerting/unified/components/rules/RuleDetails.tsx index 787a0550037..bf9d4c57b3a 100644 --- a/public/app/features/alerting/unified/components/rules/RuleDetails.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleDetails.tsx @@ -116,6 +116,7 @@ const EvaluationBehaviorSummary = ({ rule }: EvaluationBehaviorSummaryProps) => > @@ -133,7 +134,12 @@ const EvaluationBehaviorSummary = ({ rule }: EvaluationBehaviorSummaryProps) => label={t('alerting.evaluation-behavior-summary.label-evaluation-time', 'Evaluation time')} horizontal={true} > - + {Time({ timeInMs: lastEvaluationDuration * 1000, humanize: true })} diff --git a/public/app/features/alerting/unified/components/rules/RuleListErrors.tsx b/public/app/features/alerting/unified/components/rules/RuleListErrors.tsx index e760c2b28a2..c7717199836 100644 --- a/public/app/features/alerting/unified/components/rules/RuleListErrors.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleListErrors.tsx @@ -147,7 +147,10 @@ const ErrorSummaryButton: FC = ({ count, onClick }) => { return (
- + diff --git a/public/app/features/alerting/unified/components/rules/state-history/LokiStateHistory.tsx b/public/app/features/alerting/unified/components/rules/state-history/LokiStateHistory.tsx index e4769d5f293..5d812cb3743 100644 --- a/public/app/features/alerting/unified/components/rules/state-history/LokiStateHistory.tsx +++ b/public/app/features/alerting/unified/components/rules/state-history/LokiStateHistory.tsx @@ -121,7 +121,12 @@ const LokiStateHistory = ({ ruleUID }: Props) => { Common labels - + diff --git a/public/app/features/alerting/unified/plugins/PluginOriginBadge.tsx b/public/app/features/alerting/unified/plugins/PluginOriginBadge.tsx index 03c1eca7620..f5035dbeee2 100644 --- a/public/app/features/alerting/unified/plugins/PluginOriginBadge.tsx +++ b/public/app/features/alerting/unified/plugins/PluginOriginBadge.tsx @@ -3,6 +3,7 @@ import { useAsync } from 'react-use'; import { Badge, IconSize, Tooltip } from '@grafana/ui'; import { getSvgSize } from '@grafana/ui/internal'; +import { t } from '../../../../core/internationalization'; import { getPluginSettings } from '../../../plugins/pluginSettings'; interface PluginOriginBadgeProps { @@ -31,5 +32,15 @@ export function PluginOriginBadge({ pluginId, size = 'md' }: PluginOriginBadgePr ); - return {badgeIcon}; + return ( + + {badgeIcon} + + ); } diff --git a/public/app/features/alerting/unified/rule-list/components/GroupStatus.tsx b/public/app/features/alerting/unified/rule-list/components/GroupStatus.tsx index fb5af7d3b4f..c631ada6a0f 100644 --- a/public/app/features/alerting/unified/rule-list/components/GroupStatus.tsx +++ b/public/app/features/alerting/unified/rule-list/components/GroupStatus.tsx @@ -2,6 +2,7 @@ import { css, keyframes } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data'; import { Icon, Tooltip, useStyles2 } from '@grafana/ui'; +import { t } from 'app/core/internationalization'; interface GroupStatusProps { status: 'deleting'; // We don't support other statuses yet @@ -14,7 +15,7 @@ export function GroupStatus({ status }: GroupStatusProps) {
{status === 'deleting' && ( - +
diff --git a/public/app/features/api-keys/ApiKeysTable.tsx b/public/app/features/api-keys/ApiKeysTable.tsx index 983903d9c15..7cb523d92bc 100644 --- a/public/app/features/api-keys/ApiKeysTable.tsx +++ b/public/app/features/api-keys/ApiKeysTable.tsx @@ -50,7 +50,12 @@ export const ApiKeysTable = ({ apiKeys, timeZone, onDelete, onMigrate }: Props) {formatDate(key.expiration, timeZone)} {isExpired && ( - + diff --git a/public/app/features/dashboard-scene/settings/variables/VariableEditorListRow.tsx b/public/app/features/dashboard-scene/settings/variables/VariableEditorListRow.tsx index f02a58a3d5b..991f870e40c 100644 --- a/public/app/features/dashboard-scene/settings/variables/VariableEditorListRow.tsx +++ b/public/app/features/dashboard-scene/settings/variables/VariableEditorListRow.tsx @@ -146,7 +146,12 @@ function VariableCheckIndicator({ passed }: VariableCheckIndicatorProps): ReactE const styles = useStyles2(getStyles); if (passed) { return ( - + + Renamed or missing variables - + diff --git a/public/app/features/dashboard-scene/settings/version-history/VersionHistoryButtons.tsx b/public/app/features/dashboard-scene/settings/version-history/VersionHistoryButtons.tsx index 020124ece5e..b3d3db9cdee 100644 --- a/public/app/features/dashboard-scene/settings/version-history/VersionHistoryButtons.tsx +++ b/public/app/features/dashboard-scene/settings/version-history/VersionHistoryButtons.tsx @@ -1,5 +1,5 @@ import { Tooltip, Button, Stack } from '@grafana/ui'; -import { Trans } from 'app/core/internationalization'; +import { Trans, t } from 'app/core/internationalization'; import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions'; type VersionsButtonsType = { @@ -30,7 +30,13 @@ export const VersionsHistoryButtons = ({ Show more versions )} - + diff --git a/public/app/features/explore/CorrelationEditorModeBar.tsx b/public/app/features/explore/CorrelationEditorModeBar.tsx index 26aaba11a1c..6dafc41893b 100644 --- a/public/app/features/explore/CorrelationEditorModeBar.tsx +++ b/public/app/features/explore/CorrelationEditorModeBar.tsx @@ -6,7 +6,7 @@ import { GrafanaTheme2, colorManipulator } from '@grafana/data'; import { reportInteraction } from '@grafana/runtime'; import { Button, Icon, Stack, Tooltip, useStyles2 } from '@grafana/ui'; import { Prompt } from 'app/core/components/FormPrompt/Prompt'; -import { Trans } from 'app/core/internationalization'; +import { Trans, t } from 'app/core/internationalization'; import { CORRELATION_EDITOR_POST_CONFIRM_ACTION, ExploreItemState, useDispatch, useSelector } from 'app/types'; import { CorrelationUnsavedChangesModal } from './CorrelationUnsavedChangesModal'; @@ -231,7 +231,12 @@ export const CorrelationEditorModeBar = ({ panes }: { panes: Array<[string, Expl )}
- + diff --git a/packages/grafana-ui/src/components/VizTooltip/VizTooltipFooter.tsx b/packages/grafana-ui/src/components/VizTooltip/VizTooltipFooter.tsx index 2552e1e6541..fc666c36cb5 100644 --- a/packages/grafana-ui/src/components/VizTooltip/VizTooltipFooter.tsx +++ b/packages/grafana-ui/src/components/VizTooltip/VizTooltipFooter.tsx @@ -73,7 +73,7 @@ const renderDataLinks = makeRenderLinksOrActions( const renderActions = makeRenderLinksOrActions( (title) => Click to {{ actionTitle: title }}, - (item, i, styles) => + (item, i) => ); export const VizTooltipFooter = ({ dataLinks, actions = [], annotate }: VizTooltipFooterProps) => { diff --git a/public/app/features/actions/ActionEditor.tsx b/public/app/features/actions/ActionEditor.tsx index 626156f87a5..58cdf0483d5 100644 --- a/public/app/features/actions/ActionEditor.tsx +++ b/public/app/features/actions/ActionEditor.tsx @@ -2,7 +2,17 @@ import { css } from '@emotion/css'; import { memo } from 'react'; import { Action, GrafanaTheme2, httpMethodOptions, HttpRequestMethod, VariableSuggestion } from '@grafana/data'; -import { Switch, Field, InlineField, InlineFieldRow, RadioButtonGroup, JSONFormatter, useStyles2 } from '@grafana/ui'; +import { + Switch, + Field, + InlineField, + InlineFieldRow, + RadioButtonGroup, + JSONFormatter, + useStyles2, + ColorPicker, + useTheme2, +} from '@grafana/ui'; import { t } from 'app/core/internationalization'; import { HTMLElementType, SuggestionsInput } from '../transformers/suggestionsInput/SuggestionsInput'; @@ -21,6 +31,7 @@ const LABEL_WIDTH = 13; export const ActionEditor = memo(({ index, value, onChange, suggestions, showOneClick }: ActionEditorProps) => { const styles = useStyles2(getStyles); + const theme = useTheme2(); const onTitleChange = (title: string) => { onChange(index, { ...value, title }); @@ -84,6 +95,16 @@ export const ActionEditor = memo(({ index, value, onChange, suggestions, showOne }); }; + const onBackgroundColorChange = (backgroundColor: string) => { + onChange(index, { + ...value, + style: { + ...value.style, + backgroundColor, + }, + }); + }; + const renderJSON = (data = '{}') => { try { const json = JSON.parse(data); @@ -133,6 +154,19 @@ export const ActionEditor = memo(({ index, value, onChange, suggestions, showOne /> + + + + + + {showOneClick && ( ({ inputField: css({ marginRight: 4, }), + colorPicker: css({ + display: 'flex', + alignItems: 'center', + }), }); ActionEditor.displayName = 'ActionEditor'; diff --git a/public/app/features/actions/utils.ts b/public/app/features/actions/utils.ts index 3874b166444..e8771075178 100644 --- a/public/app/features/actions/utils.ts +++ b/public/app/features/actions/utils.ts @@ -13,7 +13,7 @@ import { textUtil, ValueLinkConfig, } from '@grafana/data'; -import { BackendSrvRequest, getBackendSrv } from '@grafana/runtime'; +import { BackendSrvRequest, getBackendSrv, config as grafanaConfig } from '@grafana/runtime'; import { appEvents } from 'app/core/core'; import { HttpRequestMethod } from '../../plugins/panel/canvas/panelcfg.gen'; @@ -63,6 +63,9 @@ export const getActions = ( buildActionOnClick(action, boundReplaceVariables); }, oneClick: action.oneClick ?? false, + style: { + backgroundColor: action.style?.backgroundColor ?? grafanaConfig.theme2.colors.secondary.main, + }, }; return actionModel; diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 483ba0d2b9c..724c3e565fa 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -44,6 +44,11 @@ }, "actions": { "action-editor": { + "button": { + "style": { + "background-color": "Color" + } + }, "label-headers": "Headers", "label-url": "URL", "placeholder-url": "URL" @@ -5242,7 +5247,8 @@ "action-editor": { "button": { "confirm": "Confirm", - "confirm-action": "Confirm action" + "confirm-action": "Confirm action", + "style": "Button style" }, "inline": { "add-action": "Add action", From cd5fa7943e0095e957dc30f12f0a926ac51707ef Mon Sep 17 00:00:00 2001 From: Jev Forsberg <46619047+baldm0mma@users.noreply.github.com> Date: Wed, 30 Apr 2025 08:43:58 -0600 Subject: [PATCH 053/849] Chore: Use Vault secrets in `release-comms.yml` (#104727) * baldm0mma/ update to use vault * baldm0mma/ update permissions --- .github/workflows/release-comms.yml | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/.github/workflows/release-comms.yml b/.github/workflows/release-comms.yml index e5532ca12ca..4a5bedfe2e1 100644 --- a/.github/workflows/release-comms.yml +++ b/.github/workflows/release-comms.yml @@ -21,8 +21,13 @@ on: - 'main' - 'release-*.*.*' +permissions: {} + jobs: setup: + permissions: + contents: read + id-token: write if: ${{ github.event_name == 'workflow_dispatch' || (github.event.pull_request.merged == true && startsWith(github.head_ref, 'release/')) }} name: Setup and establish latest outputs: @@ -56,9 +61,6 @@ jobs: name: Create next release branch (Grafana) needs: setup uses: ./.github/workflows/create-next-release-branch.yml - secrets: - GRAFANA_DELIVERY_BOT_APP_ID: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} - GRAFANA_DELIVERY_BOT_APP_PEM: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} with: ownerRepo: 'grafana/grafana' source: ${{ needs.setup.outputs.release_branch }} @@ -66,9 +68,6 @@ jobs: name: Create next release branch (Grafana Enterprise) needs: setup uses: ./.github/workflows/create-next-release-branch.yml - secrets: - GRAFANA_DELIVERY_BOT_APP_ID: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} - GRAFANA_DELIVERY_BOT_APP_PEM: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} with: ownerRepo: 'grafana/grafana-enterprise' source: ${{ needs.setup.outputs.release_branch }} @@ -77,9 +76,6 @@ jobs: - setup - create_next_release_branch_grafana uses: ./.github/workflows/migrate-prs.yml - secrets: - GRAFANA_DELIVERY_BOT_APP_ID: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} - GRAFANA_DELIVERY_BOT_APP_PEM: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} with: ownerRepo: 'grafana/grafana' from: ${{ needs.setup.outputs.release_branch }} @@ -89,9 +85,6 @@ jobs: - setup - create_next_release_branch_enterprise uses: ./.github/workflows/migrate-prs.yml - secrets: - GRAFANA_DELIVERY_BOT_APP_ID: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} - GRAFANA_DELIVERY_BOT_APP_PEM: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} with: ownerRepo: 'grafana/grafana-enterprise' from: ${{ needs.setup.outputs.release_branch }} @@ -99,9 +92,6 @@ jobs: post_changelog_on_forum: needs: setup uses: ./.github/workflows/community-release.yml - secrets: - GRAFANA_MISC_STATS_API_KEY: ${{ secrets.GRAFANA_MISC_STATS_API_KEY }} - GRAFANABOT_FORUM_KEY: ${{ secrets.GRAFANABOT_FORUM_KEY }} with: version: ${{ needs.setup.outputs.version }} dry_run: ${{ needs.setup.outputs.dry_run == 'true' }} From d0644d081f8d18e3db9751a7129cd020e79b0a82 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 30 Apr 2025 17:56:48 +0300 Subject: [PATCH 054/849] StateTimeline: Improve auto migration from discrete panel (#104671) --- .../__snapshots__/migrations.test.ts.snap | 8 +++ .../panel/state-timeline/migrations.test.ts | 10 ++++ .../panel/state-timeline/migrations.ts | 57 ++++++++++++++++++- 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/public/app/plugins/panel/state-timeline/__snapshots__/migrations.test.ts.snap b/public/app/plugins/panel/state-timeline/__snapshots__/migrations.test.ts.snap index bf4852cfbe9..5ce47782afb 100644 --- a/public/app/plugins/panel/state-timeline/__snapshots__/migrations.test.ts.snap +++ b/public/app/plugins/panel/state-timeline/__snapshots__/migrations.test.ts.snap @@ -15,6 +15,7 @@ exports[`Timeline Migrations from discrete panel 1`] = ` "color": "#7EB26D", }, "111": { + "color": "#F0F", "text": "ONE", }, "20": { @@ -32,6 +33,12 @@ exports[`Timeline Migrations from discrete panel 1`] = ` "90": { "color": "#6ED0E0", }, + "AAA": { + "color": "#FF0", + }, + "ONE": { + "color": "#F0F", + }, }, "type": "value", }, @@ -39,6 +46,7 @@ exports[`Timeline Migrations from discrete panel 1`] = ` "options": { "from": 1, "result": { + "color": "#FF0", "text": "AAA", }, "to": 3, diff --git a/public/app/plugins/panel/state-timeline/migrations.test.ts b/public/app/plugins/panel/state-timeline/migrations.test.ts index 98b10d22a5a..d1511060df9 100644 --- a/public/app/plugins/panel/state-timeline/migrations.test.ts +++ b/public/app/plugins/panel/state-timeline/migrations.test.ts @@ -47,6 +47,16 @@ const discreteInV8 = { color: '#E24D42', text: '5', }, + { + $$hashKey: 'object:369', + color: '#FF0', // Should get linked to the range map below + text: 'AAA', + }, + { + $$hashKey: 'object:369', + color: '#F0F', // Should get linked to the range map below + text: 'ONE', + }, ], crosshairColor: '#8F070C', display: 'timeline', diff --git a/public/app/plugins/panel/state-timeline/migrations.ts b/public/app/plugins/panel/state-timeline/migrations.ts index c8762339409..30cb5eaaf95 100644 --- a/public/app/plugins/panel/state-timeline/migrations.ts +++ b/public/app/plugins/panel/state-timeline/migrations.ts @@ -1,6 +1,6 @@ import { isArray } from 'lodash'; -import { FieldConfigSource, MappingType, PanelModel, ValueMap } from '@grafana/data'; +import { FieldConfigSource, MappingType, PanelModel, ValueMap, RangeMap, ValueMapping } from '@grafana/data'; import { FieldConfig, Options } from './panelcfg.gen'; @@ -74,9 +74,64 @@ export const timelinePanelChangedHandler = ( } } + if (fieldConfig.defaults.mappings?.length) { + fieldConfig.defaults.mappings = expandColorMappings(fieldConfig.defaults.mappings); + } + // mutates the input panel.fieldConfig = fieldConfig; } return options; }; + +function expandColorMappings(mappings: ValueMapping[]): ValueMapping[] { + let keyToColor: Record = {}; + for (const m of mappings) { + if (isValueToText(m)) { + for (const key in m.options) { + const target = m.options[key]; + if (target.color?.length) { + keyToColor[key] = target.color; + } + } + } else if (isRangeMap(m)) { + const { text, color } = m.options.result; + if (text?.length && color?.length && !keyToColor[text]) { + keyToColor[text] = color; + } + } + } + + // Set a color for values that match + return mappings.map((m) => { + if (isValueToText(m)) { + for (const key in m.options) { + const target = m.options[key]; + if (!target.color?.length) { + let c = keyToColor[key]; + if (!c && target.text) { + c = keyToColor[target.text]; + } + if (c) { + target.color = c; // link the mapped color + } + } + } + } else if (isRangeMap(m)) { + const { text, color } = m.options.result; + if (!color && text && keyToColor[text]) { + m.options.result.color = keyToColor[text]; + } + } + return m; + }); +} + +function isValueToText(m: ValueMapping): m is ValueMap { + return m.type === MappingType.ValueToText; +} + +function isRangeMap(m: ValueMapping): m is RangeMap { + return m.type === MappingType.RangeToText; +} From ecd1f5ba92b201fe57d447bdf3ef0872c020857c Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Wed, 30 Apr 2025 17:39:26 +0200 Subject: [PATCH 055/849] Chore: Fix remaining levitate zizmur issues and move to use vault (#104782) * Chore: Fix remaining levitate zizmur issues and move to use vault * Levitate CI: Remove secrets usage for WIF identity provider and SA (#104783) --------- Co-authored-by: Giuseppe Guerra --- .../detect-breaking-changes-levitate.yml | 45 ++++++++++++++----- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/.github/workflows/detect-breaking-changes-levitate.yml b/.github/workflows/detect-breaking-changes-levitate.yml index 640b80a3dd5..9ec76c0bf64 100644 --- a/.github/workflows/detect-breaking-changes-levitate.yml +++ b/.github/workflows/detect-breaking-changes-levitate.yml @@ -31,6 +31,7 @@ jobs: with: path: './pr' persist-credentials: false + - uses: actions/setup-node@v4 with: node-version: 22.11.0 @@ -81,6 +82,7 @@ jobs: with: path: './base' ref: ${{ github.event.pull_request.base.ref }} + persist-credentials: false - uses: actions/setup-node@v4 with: @@ -129,6 +131,9 @@ jobs: steps: - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: actions/setup-node@v4 with: node-version: 22.11.0 @@ -152,8 +157,8 @@ jobs: - id: 'auth' uses: 'google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f' with: - workload_identity_provider: ${{ secrets.WIF_PROVIDER }} - service_account: ${{ secrets.LEVITATE_SA }} + workload_identity_provider: projects/304398677251/locations/global/workloadIdentityPools/github/providers/github-provider + service_account: github-plugins-data-levitate@grafanalabs-workload-identity.iam.gserviceaccount.com project_id: 'grafanalabs-global' - name: 'Set up Cloud SDK' @@ -172,7 +177,11 @@ jobs: - name: Persisting the check output run: | mkdir -p ./levitate - 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 + echo "{ \"exit_code\": ${IS_BREAKING}, \"message\": \"${MESSAGE}\", \"pr_number\": \"${PR_NUMBER}\" }" > ./levitate/result.json + env: + IS_BREAKING: ${{ steps.breaking-changes.outputs.is_breaking }} + MESSAGE: ${{ steps.breaking-changes.outputs.message }} + PR_NUMBER: ${{ github.event.pull_request.number }} - name: Upload check output as artifact uses: actions/upload-artifact@v4 @@ -190,14 +199,24 @@ jobs: id-token: write steps: - - name: "Generate token" - id: generate_token - uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 + - id: get-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@28361cdb22223e5f1e34358c86c20908e7248760 # get-vault-secrets-v1.1.0 with: - app_id: ${{ secrets.GRAFANA_PR_AUTOMATION_APP_ID }} - private_key: ${{ secrets.GRAFANA_PR_AUTOMATION_APP_PEM }} + # Secrets placed in the ci/repo/grafana/plugin-tools in vault + repo_secrets: | + GITHUB_APP_ID=grafana_pr_automation_app:app_id + GITHUB_APP_PRIVATE_KEY=grafana_pr_automation_app:app_pem + + - name: Generate token + id: generate_token + uses: actions/create-github-app-token@3ff1caaa28b64c9cc276ce0a02e2ff584f3900c5 # v2.0.2 + with: + app-id: ${{ env.GITHUB_APP_ID }} + private-key: ${{ env.GITHUB_APP_PRIVATE_KEY }} - uses: actions/checkout@v4 + with: + persist-credentials: false - name: 'Download artifact' uses: actions/download-artifact@v4 @@ -343,7 +362,7 @@ jobs: # Related issue: https://github.com/renovatebot/renovate/issues/1908 - name: Add "grafana/plugins-platform-frontend" as a reviewer if: steps.levitate-run.outputs.exit_code == 1 - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: PR_NUMBER: ${{ steps.levitate-run.outputs.pr_number }} with: @@ -360,7 +379,7 @@ jobs: # Remove reviewers (no more breaking changes) - name: Remove "grafana/plugins-platform-frontend" from the list of reviewers if: steps.levitate-run.outputs.exit_code == 0 - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: PR_NUMBER: ${{ steps.levitate-run.outputs.pr_number }} with: @@ -376,9 +395,11 @@ jobs: - name: Exit run: | - if [ "${{ steps.levitate-run.outputs.exit_code }}" -ne 0 ]; then + if [ "${LV_EXIT_CODE}" -ne 0 ]; then echo "Breaking changes detected. Please check the levitate report in your pull request. This workflow won't block merging." fi - exit ${{ steps.levitate-run.outputs.exit_code }} + exit ${LV_EXIT_CODE} shell: bash + env: + LV_EXIT_CODE: ${{ steps.levitate-run.outputs.exit_code }} From 821b44182e683261698b32e7933238fb8af0facc Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Wed, 30 Apr 2025 17:20:57 +0100 Subject: [PATCH 056/849] Chore: don't persist creds when checking out in actions (#104778) * don't persist creds when checking out in actions * kick CI * kick CI * actually commit the merge... * don't need * don't need --- .github/workflows/frontend-lint.yml | 10 ++++++++++ .github/workflows/issue-opened.yml | 2 ++ .github/workflows/pr-e2e-tests.yml | 1 + .github/workflows/pr-frontend-unit-tests.yml | 2 ++ .github/workflows/pr-test-integration.yml | 4 ++++ .github/workflows/publish-kinds-release.yml | 1 + .../workflows/publish-technical-documentation-next.yml | 2 ++ .github/workflows/run-dashboard-search-e2e.yml | 4 +++- .../notifications/pkg/apis/alerting_manifest.go | 2 -- .../pkg/apis/receiver/v0alpha1/receiver_schema_gen.go | 2 +- .../templategroup/v0alpha1/templategroup_schema_gen.go | 2 +- .../timeinterval/v0alpha1/timeinterval_schema_gen.go | 2 +- 12 files changed, 28 insertions(+), 6 deletions(-) diff --git a/.github/workflows/frontend-lint.yml b/.github/workflows/frontend-lint.yml index 0042166d3c7..425257dd67e 100644 --- a/.github/workflows/frontend-lint.yml +++ b/.github/workflows/frontend-lint.yml @@ -47,6 +47,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' @@ -65,6 +67,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' @@ -88,6 +92,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' @@ -105,6 +111,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' @@ -124,6 +132,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' diff --git a/.github/workflows/issue-opened.yml b/.github/workflows/issue-opened.yml index a5a5a822446..4dddfe42519 100644 --- a/.github/workflows/issue-opened.yml +++ b/.github/workflows/issue-opened.yml @@ -89,6 +89,8 @@ jobs: - name: Checkout uses: actions/checkout@v4 # v4.2.2 + with: + persist-credentials: false - name: Send issue to the auto triager action id: auto_triage diff --git a/.github/workflows/pr-e2e-tests.yml b/.github/workflows/pr-e2e-tests.yml index a8f2e1cd54a..7fb27a34ebd 100644 --- a/.github/workflows/pr-e2e-tests.yml +++ b/.github/workflows/pr-e2e-tests.yml @@ -26,6 +26,7 @@ jobs: - uses: actions/checkout@v4 with: path: ./grafana + persist-credentials: false - run: echo "GRAFANA_GO_VERSION=$(grep "go 1." grafana/go.work | cut -d\ -f2)" >> "$GITHUB_ENV" - uses: dagger/dagger-for-github@e47aba410ef9bb9ed81a4d2a97df31061e5e842e with: diff --git a/.github/workflows/pr-frontend-unit-tests.yml b/.github/workflows/pr-frontend-unit-tests.yml index fd7ded43f0d..ef320ce66c5 100644 --- a/.github/workflows/pr-frontend-unit-tests.yml +++ b/.github/workflows/pr-frontend-unit-tests.yml @@ -52,6 +52,8 @@ jobs: chunk: [1, 2, 3, 4, 5, 6, 7, 8] steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' diff --git a/.github/workflows/pr-test-integration.yml b/.github/workflows/pr-test-integration.yml index 9505c429dca..0f5c2f34a6e 100644 --- a/.github/workflows/pr-test-integration.yml +++ b/.github/workflows/pr-test-integration.yml @@ -48,6 +48,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + persist-credentials: false - name: Setup Go uses: actions/setup-go@v5 with: @@ -73,6 +75,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + persist-credentials: false - name: Setup Go uses: actions/setup-go@v5 with: diff --git a/.github/workflows/publish-kinds-release.yml b/.github/workflows/publish-kinds-release.yml index 73962750ef2..d3711376ac7 100644 --- a/.github/workflows/publish-kinds-release.yml +++ b/.github/workflows/publish-kinds-release.yml @@ -50,6 +50,7 @@ jobs: with: repository: "grafana/grafana-github-actions" path: "./actions" + persist-credentials: false - name: "Install Actions from library" run: "npm install --production --prefix ./actions" diff --git a/.github/workflows/publish-technical-documentation-next.yml b/.github/workflows/publish-technical-documentation-next.yml index f9c2adf0230..0047e2992e0 100644 --- a/.github/workflows/publish-technical-documentation-next.yml +++ b/.github/workflows/publish-technical-documentation-next.yml @@ -16,6 +16,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: grafana/writers-toolkit/publish-technical-documentation@publish-technical-documentation/v1 # zizmor: ignore[unpinned-uses] with: website_directory: content/docs/grafana/next diff --git a/.github/workflows/run-dashboard-search-e2e.yml b/.github/workflows/run-dashboard-search-e2e.yml index 76d765f4fcf..fd62c1d8c42 100644 --- a/.github/workflows/run-dashboard-search-e2e.yml +++ b/.github/workflows/run-dashboard-search-e2e.yml @@ -2,7 +2,7 @@ name: run-dashboard-search-e2e on: workflow_run: - workflows: + workflows: - trigger-dashboard-search-e2e types: - completed @@ -95,6 +95,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + with: + persist-credentials: false - name: Restore Cached Node Modules uses: actions/cache@v3 with: diff --git a/apps/alerting/notifications/pkg/apis/alerting_manifest.go b/apps/alerting/notifications/pkg/apis/alerting_manifest.go index 9e40415b859..9a97a47f78a 100644 --- a/apps/alerting/notifications/pkg/apis/alerting_manifest.go +++ b/apps/alerting/notifications/pkg/apis/alerting_manifest.go @@ -11,8 +11,6 @@ import ( "github.com/grafana/grafana-app-sdk/app" ) -var () - var appManifestData = app.ManifestData{ AppName: "alerting", Group: "notifications.alerting.grafana.app", diff --git a/apps/alerting/notifications/pkg/apis/receiver/v0alpha1/receiver_schema_gen.go b/apps/alerting/notifications/pkg/apis/receiver/v0alpha1/receiver_schema_gen.go index 48b5d72fbd9..d159d0d9cb9 100644 --- a/apps/alerting/notifications/pkg/apis/receiver/v0alpha1/receiver_schema_gen.go +++ b/apps/alerting/notifications/pkg/apis/receiver/v0alpha1/receiver_schema_gen.go @@ -13,7 +13,7 @@ import ( // schema is unexported to prevent accidental overwrites var ( schemaReceiver = resource.NewSimpleSchema("notifications.alerting.grafana.app", "v0alpha1", &Receiver{}, &ReceiverList{}, resource.WithKind("Receiver"), - resource.WithPlural("receivers"), resource.WithScope(resource.NamespacedScope), resource.WithSelectableFields([]resource.SelectableField{resource.SelectableField{ + resource.WithPlural("receivers"), resource.WithScope(resource.NamespacedScope), resource.WithSelectableFields([]resource.SelectableField{{ FieldSelector: "spec.title", FieldValueFunc: func(o resource.Object) (string, error) { cast, ok := o.(*Receiver) diff --git a/apps/alerting/notifications/pkg/apis/templategroup/v0alpha1/templategroup_schema_gen.go b/apps/alerting/notifications/pkg/apis/templategroup/v0alpha1/templategroup_schema_gen.go index 256fbab3116..073e8eb9058 100644 --- a/apps/alerting/notifications/pkg/apis/templategroup/v0alpha1/templategroup_schema_gen.go +++ b/apps/alerting/notifications/pkg/apis/templategroup/v0alpha1/templategroup_schema_gen.go @@ -13,7 +13,7 @@ import ( // schema is unexported to prevent accidental overwrites var ( schemaTemplateGroup = resource.NewSimpleSchema("notifications.alerting.grafana.app", "v0alpha1", &TemplateGroup{}, &TemplateGroupList{}, resource.WithKind("TemplateGroup"), - resource.WithPlural("templategroups"), resource.WithScope(resource.NamespacedScope), resource.WithSelectableFields([]resource.SelectableField{resource.SelectableField{ + resource.WithPlural("templategroups"), resource.WithScope(resource.NamespacedScope), resource.WithSelectableFields([]resource.SelectableField{{ FieldSelector: "spec.title", FieldValueFunc: func(o resource.Object) (string, error) { cast, ok := o.(*TemplateGroup) diff --git a/apps/alerting/notifications/pkg/apis/timeinterval/v0alpha1/timeinterval_schema_gen.go b/apps/alerting/notifications/pkg/apis/timeinterval/v0alpha1/timeinterval_schema_gen.go index 627e02a9572..af8ff6454a5 100644 --- a/apps/alerting/notifications/pkg/apis/timeinterval/v0alpha1/timeinterval_schema_gen.go +++ b/apps/alerting/notifications/pkg/apis/timeinterval/v0alpha1/timeinterval_schema_gen.go @@ -13,7 +13,7 @@ import ( // schema is unexported to prevent accidental overwrites var ( schemaTimeInterval = resource.NewSimpleSchema("notifications.alerting.grafana.app", "v0alpha1", &TimeInterval{}, &TimeIntervalList{}, resource.WithKind("TimeInterval"), - resource.WithPlural("timeintervals"), resource.WithScope(resource.NamespacedScope), resource.WithSelectableFields([]resource.SelectableField{resource.SelectableField{ + resource.WithPlural("timeintervals"), resource.WithScope(resource.NamespacedScope), resource.WithSelectableFields([]resource.SelectableField{{ FieldSelector: "spec.name", FieldValueFunc: func(o resource.Object) (string, error) { cast, ok := o.(*TimeInterval) From 2df2b169ccedc06f60844183785bc9c56024877f Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 30 Apr 2025 19:33:21 +0300 Subject: [PATCH 057/849] Provisioning: Require author name (#104789) --- .../apis/provisioning/repository/go-git/wrapper.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/registry/apis/provisioning/repository/go-git/wrapper.go b/pkg/registry/apis/provisioning/repository/go-git/wrapper.go index 870b30c3cd6..dd49f278812 100644 --- a/pkg/registry/apis/provisioning/repository/go-git/wrapper.go +++ b/pkg/registry/apis/provisioning/repository/go-git/wrapper.go @@ -371,9 +371,13 @@ func (g *GoGitRepo) maybeCommit(ctx context.Context, message string) error { return nil } - opts := &git.CommitOptions{} + opts := &git.CommitOptions{ + Author: &object.Signature{ + Name: "grafana", + }, + } sig := repository.GetAuthorSignature(ctx) - if sig != nil { + if sig != nil && sig.Name != "" { opts.Author = &object.Signature{ Name: sig.Name, Email: sig.Email, From 36b810e1cc0f12753b879ae3b9c238b70f396eac Mon Sep 17 00:00:00 2001 From: Artur Wierzbicki Date: Wed, 30 Apr 2025 20:38:14 +0400 Subject: [PATCH 058/849] Dashboard API: Update dashboard version docs (#104565) --- .../developers/http_api/dashboard_versions.md | 53 ++++++++++--------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/docs/sources/developers/http_api/dashboard_versions.md b/docs/sources/developers/http_api/dashboard_versions.md index ef25424bbc7..33b59b97e48 100644 --- a/docs/sources/developers/http_api/dashboard_versions.md +++ b/docs/sources/developers/http_api/dashboard_versions.md @@ -47,30 +47,33 @@ HTTP/1.1 200 OK Content-Type: application/json; charset=UTF-8 Content-Length: 428 -[ - { - "id": 2, - "dashboardId": 1, - "uid": "QA7wKklGz", - "parentVersion": 1, - "restoredFrom": 0, - "version": 2, - "created": "2017-06-08T17:24:33-04:00", - "createdBy": "admin", - "message": "Updated panel title" - }, - { - "id": 1, - "dashboardId": 1, - "uid": "QA7wKklGz", - "parentVersion": 0, - "restoredFrom": 0, - "version": 1, - "created": "2017-06-08T17:23:33-04:00", - "createdBy": "admin", - "message": "Initial save" - } -] +{ + "continueToken": "", + "versions": [ + { + "id": 2, + "dashboardId": 1, + "uid": "QA7wKklGz", + "parentVersion": 1, + "restoredFrom": 0, + "version": 2, + "created": "2017-06-08T17:24:33-04:00", + "createdBy": "admin", + "message": "Updated panel title" + }, + { + "id": 1, + "dashboardId": 1, + "uid": "QA7wKklGz", + "parentVersion": 0, + "restoredFrom": 0, + "version": 1, + "created": "2017-06-08T17:23:33-04:00", + "createdBy": "admin", + "message": "Initial save" + } + ] +} ``` Status Codes: @@ -89,7 +92,7 @@ Get the dashboard version with the given version, for the dashboard with the giv **Example request for getting a dashboard version**: ```http -GET /api/dashboards/id/1/versions/1 HTTP/1.1 +GET /api/dashboards/uid/QA7wKklGz/versions/1 HTTP/1.1 Accept: application/json Content-Type: application/json Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk From e36d774d0cedbafde726b450c095548496b47080 Mon Sep 17 00:00:00 2001 From: Kevin Minehart <5140827+kminehart@users.noreply.github.com> Date: Wed, 30 Apr 2025 12:32:44 -0500 Subject: [PATCH 059/849] CI: update permissions on workflows which get external secrets (#104792) update permissions --- .github/workflows/changelog.yml | 5 ++--- .github/workflows/pr-patch-check-event.yml | 5 ++--- .github/workflows/sync-mirror-event.yml | 4 ++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 4570a13ec13..0d3abeb352f 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -46,9 +46,7 @@ on: default: false type: boolean -permissions: - contents: read - id-token: write +permissions: {} jobs: main: @@ -60,6 +58,7 @@ jobs: DRY_RUN: ${{ inputs.dry_run }} runs-on: ubuntu-latest permissions: + id-token: write contents: write pull-requests: write steps: diff --git a/.github/workflows/pr-patch-check-event.yml b/.github/workflows/pr-patch-check-event.yml index d16e6580c51..19264f5ada0 100644 --- a/.github/workflows/pr-patch-check-event.yml +++ b/.github/workflows/pr-patch-check-event.yml @@ -13,15 +13,14 @@ on: - "v*.*.*" - "release-*" -permissions: - contents: read - id-token: write +permissions: {} # Since this is run on a pull request, we want to apply the patches intended for the # target branch onto the source branch, to verify compatibility before merging. jobs: dispatch-job: permissions: + id-token: write contents: read actions: write env: diff --git a/.github/workflows/sync-mirror-event.yml b/.github/workflows/sync-mirror-event.yml index 13c9563846a..4a073c9b2b3 100644 --- a/.github/workflows/sync-mirror-event.yml +++ b/.github/workflows/sync-mirror-event.yml @@ -10,14 +10,14 @@ on: - "v*.*.*" - "release-*" -permissions: - id-token: write +permissions: {} # This is run after the pull request has been merged, so we'll run against the target branch jobs: dispatch-job: runs-on: ubuntu-latest permissions: + id-token: write contents: read actions: write env: From 1faf52cd07cd4e430af85a037ff98b4991b24004 Mon Sep 17 00:00:00 2001 From: Kevin Minehart <5140827+kminehart@users.noreply.github.com> Date: Wed, 30 Apr 2025 14:31:14 -0500 Subject: [PATCH 060/849] CI: update permissions for external workflows (#104800) update permissions From 3bb5a13275357df2233b183c64cb280a0bd1dcc0 Mon Sep 17 00:00:00 2001 From: Pepe Cano <825430+ppcano@users.noreply.github.com> Date: Wed, 30 Apr 2025 21:51:08 +0200 Subject: [PATCH 061/849] docs(alerting): add new guides for handling missing data and connectivity errors (#104765) * New `Learn` section and Handling connectivity errors guide * guide: connectivity errors * update metadata * edit `Learn` page --- docs/sources/alerting/learn/_index.md | 20 ++ .../alerting/learn/connectivity-errors.md | 234 ++++++++++++++++++ docs/sources/alerting/learn/missing-data.md | 208 ++++++++++++++++ 3 files changed, 462 insertions(+) create mode 100644 docs/sources/alerting/learn/_index.md create mode 100644 docs/sources/alerting/learn/connectivity-errors.md create mode 100644 docs/sources/alerting/learn/missing-data.md diff --git a/docs/sources/alerting/learn/_index.md b/docs/sources/alerting/learn/_index.md new file mode 100644 index 00000000000..12c3154ec01 --- /dev/null +++ b/docs/sources/alerting/learn/_index.md @@ -0,0 +1,20 @@ +--- +canonical: https://grafana.com/docs/grafana/latest/alerting/learn/ +description: This section provides a set of guides for useful alerting practices and recommendations +keywords: + - grafana +labels: + products: + - cloud + - enterprise + - oss +menuTitle: Learn +title: Grafana Alerting Guides +weight: 170 +--- + +# Grafana Alerting Guides + +This section provides a set of guides with useful alerting practices and recommendations. + +{{< section >}} diff --git a/docs/sources/alerting/learn/connectivity-errors.md b/docs/sources/alerting/learn/connectivity-errors.md new file mode 100644 index 00000000000..9aaec5dbc76 --- /dev/null +++ b/docs/sources/alerting/learn/connectivity-errors.md @@ -0,0 +1,234 @@ +--- +canonical: https://grafana.com/docs/grafana/latest/alerting/learn/connectivity-errors/ +description: Learn how to detect and handle connectivity issues in alerts using Prometheus, Grafana Alerting, or both. +keywords: + - grafana + - alerting + - guide + - rules + - create +labels: + products: + - cloud + - enterprise + - oss +menuTitle: Handling connectivity errors +title: Handling connectivity errors in alerts +weight: 1010 +refs: + pending-period: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/fundamentals/notifications/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/fundamentals/notifications/ + notifications: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/fundamentals/notifications/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/fundamentals/notifications/ + no-data-and-error-alerts: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/fundamentals/alert-rule-evaluation/state-and-health/#no-data-and-error-alerts + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/fundamentals/alert-rule-evaluation/state-and-health/#no-data-and-error-alerts + configure-nodata-and-error-handling: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/fundamentals/alert-rule-evaluation/state-and-health/#modify-the-no-data-or-error-state + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/fundamentals/alert-rule-evaluation/state-and-health/#modify-the-no-data-or-error-state + missing-data-guide: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/learn/missing-data/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/learn/missing-data/ +--- + +# Handling connectivity errors in alerts + +Connectivity issues are one of the common causes of misleading alerts or unnoticed failures. + +Maybe your target went offline, or Prometheus couldn't scrape it. Or maybe your alert query failed because its target timed out or the network went down. These situations might look similar, but require different considerations in your alerting setup. + +This guide walks through how to detect and handle these types of failures, whether you're writing alert rules in Prometheus, using Grafana Alerting, or combining both. It covers both availability monitoring and alert query failures, and outlines strategies to improve the reliability of your alerts. + +## Understanding connectivity issues in alerts + +Typically, connectivity issues fall into a few common scenarios: + +- Servers or containers crashed or were shut down. +- Service overload or timeout. +- Misconfigured authentication or incorrect permissions. +- Network issues like DNS problems or ISP outages. + +When we talk about connectivity errors in alerting, we’re usually referring to one of two use cases: + +1. **Your target is down or unreachable.** + The service crashed, the host was down, or a firewall or DNS issue blocked the connection. These are **availability problems**. + +1. **Your alert query failed.** + The alert couldn’t evaluate its query—maybe because the data source timed out or an invalid query. These are **execution errors**. + +It helps to separate these cases early, because they behave differently and require different strategies. + +Keep in mind that most alert rules don’t hit the target directly. They query metrics from a monitoring system like Prometheus, which scrapes data from your actual infrastructure or application. That gives us two typical alerting setups where connectivity issues can show up: + +1. **Alert rule → Target** + For example, an alert rule querying an external data source like a database. + +2. **Alert rule → Prometheus ← Target** + More common in observability stacks. For instance, Prometheus scrapes a node or container, and the alert rule queries the metrics later. + + In this second setup, you can run into connectivity issues on either side. If Prometheus fails to scrape the target, your alert rule might not fire, even though something is likely wrong. + +## Detecting target availability with the Prometheus `up` metric + +Prometheus scrapes metrics from its targets regularly, following the [`scrape_interval`](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config) period. The default scrape interval is 60 seconds, which is generally considered common practice. + +Prometheus provides a built-in metric called `up` for every scrape target, a simple method to indicate whether scraping is successful: + +- `up == 1`: Your target is reachable; Prometheus collected the target metrics as expected. + +- `up == 0`: Prometheus couldn't reach your target—indicating possible downtime or network errors. + +A typical PromQL expression for an alert rule to detect when a target becomes unreachable is: + +`up == 0` + +But this alert rule might result in noisy alerts as one brief hiccup (a single scrape failure) will fire the alert. To reduce noise, you should add a delay: + +`up == 0 for: 5m` + +The `for` option in Prometheus (or [pending period](ref:pending-period) in Grafana) delays the alert until the condition has been true for the full duration. + +In this example, waiting for 5 minutes helps skip temporary hiccups. Since Prometheus scrapes metrics every minute by default, the alert only fires after five consecutive failures. + +However, this kind of `up` alert has a few gotchas: + +- **Failures can slip between scrape intervals**: An outage that starts and ends between two evaluations go undetected. You could shorten the `for` duration, but this might lead to temporary hiccups triggering false alarms. +- **Intermittent recoveries reset the `for` timer**: A single successful scrape resets the alert timer, masking intermittent outages. + +Brief connectivity drops are common in real-world environments, so expect some flakiness in `up` alerts. For example: + +| Scrape result (`up`) | Alert rule evaluation | +| :------------------- | :--------------------------------------------------- | +| 00:00 `up == 0` | Timer starts | +| 01:00 `up == 0` | Timer continues | +| 02:00 `up == 0` | Timer continues | +| 03:00 `up == 1` | Successful scrape resets timer | +| 04:00 `up == 0` | Timer starts again | +| 05:00 `up == 0` | No alert yet—timer hasn’t reached the `for` duration | + +The longer the period, the more likely this is to happen. + +A single recovery resets the alert, that’s why `up == 0 for: 5m` can sometimes be unreliable. Even if the target is down most of the time, the alert didn't fire, leaving you unaware of a potential persistent issue. + +### Using `avg_over_time` + +One way to work around these issues is to smooth the signal by averaging the `up` metric over a similar or longer period: + +`avg_over_time(up[10m]) < 0.8` + +This alert rule fires when the target is unreachable for more than 20% of the last 10 minutes, rather than looking for consecutive scrape failures. With a one-minute scrape interval, three or more failed scrapes within the last 10 minutes will now trigger the alert. + +Since this query uses a threshold and time window to control accuracy, you can now lower the `for` duration (or [pending period](ref:pending-period) in Grafana) to something shorter—`0m` or `1m`—so the alert fires faster. + +This approach gives you more flexibility in detecting real crashes or network issues. As always, adjust the threshold and period based on your noise tolerance and how critical the target is. + +### Using synthetic checks for monitoring external availability + +Prometheus often runs inside the same network as the target it monitors. That means Prometheus might be able to reach the target, but doesn’t ensure it’s reachable to users on the outside. + +Firewalls, DNS misconfigurations, or other network issues might block public traffic while Prometheus scraping `up` successfully. + +This is where synthetic monitoring helps. Tools like the [Blackbox Exporter](https://github.com/prometheus/blackbox_exporter) let you continuously verify whether a service is available and reachable from outside your network—not just internally. + +The Blackbox Exporter exposes the results of these checks as metrics, which Prometheus can scrape like any other target. For example, the `probe_success` metric reports whether the probe was able to reach the service. The setup looks like this: + +**Alert rules → Prometheus ← Blackbox Exporter (external probe) → Target** + +To detect when a service isn’t reachable externally, you can define an alert using the `probe_success` metric: + +`probe_success == 0 for: 5m` + +This alert fires when the probe has failed continuously for 5 minutes—indicating that the service couldn’t be reached from the outside. + +You can then combine internal and external checks to make the detection of connectivity errors more reliable. This alert catches when the internal scrape fails or the service is externally unreachable. + +`up == 0 or probe_success == 0` + +As with the `up` metric, you might want to smooth this out using `avg_over_time()` for more robust detection. The smooth version might look like: + +`avg_over_time(up[10m]) < 0.8 or avg_over_time(probe_success[10m]) < 0.8` + +This alert fires when Prometheus couldn't scrape the target successfully for more than 20% of the past 10 minutes, or when the external probes have been failing more than 20% of the time. This smoothing technique can be applied to any binary availability signal. + +## When only some hosts stop reporting + +In many setups, Prometheus scrapes multiple hosts under the same target — for example, a fleet of servers or containers behind a common job label. It’s common for one host to go offline while the others continue to report metrics normally. + +If your alert only checks the general `up` metric without breaking it down by labels (like `instance`, `host`, or `pod`), you might miss when a host stops reporting. For example, an alert that looks only at the aggregated status of all instances will likely fail to catch when individual instances go missing. + +This isn't a connectivity error in this context — it’s not that the alert or Prometheus can't reach anything, it’s that one or more specific targets have gone silent. These kinds of problems aren’t caught by `up == 0` alerts. + +For these cases, see the complementary [guide on handling missing data](ref:missing-data-guide) — it covers common scenarios where the alert queries return no data at all, or where only some targets stop reporting. These aren't full availability failures or execution errors, but they can still lead to blind spots in alert detection. + +## Handling query errors in Grafana Alerting + +Not all connectivity issues come from targets going offline. Sometimes, the alert rule fails when querying its target. These aren’t availability problems—they’re query execution errors: maybe the data source timed out, the network dropped, or the query was invalid. + +These errors lead to broken alerts. But they come from a different part of the stack: between the alert rule and the data source, not between the data source (e.g., Prometheus) and its target. + +This difference matters. Availability issues are typically handled using metrics like `up` or `probe_success` but execution errors require a different setup. + +Grafana Alerting has built-in handling for execution errors, regardless of the data source. That includes Prometheus, and others like Graphite, InfluxDB, PostgreSQL, etc. By default, Grafana Alerting automatically handles query errors so you don’t miss critical failures. When an alert rule fails to execute, Grafana fires a special `DatasourceError` alert. + +You can configure this behavior depending on how critical the alert is—and whether you already have other alerts detecting the issue. In [**Configure no data and error handling**](ref:configure-nodata-and-error-handling), click **Alert state if execution error or timeout**, and choose the desired option for the alert: + +- **Error (default)**: Triggers a separate `DatasourceError` alert. This default ensures alert rules always inform about query errors but can create noise. +- **Alerting**: Treats the error as if the alert condition is firing. Grafana transitions all existing instances for that rule to the `Alerting` state. +- **Normal**: Ignores the query error and transitions all alert instances to the `Normal` state. This is useful if the error isn’t critical or if you already have other alerts detecting connectivity issues. +- **Keep Last State**: Keeps the previous state until the query succeeds again. Suitable for unstable environments to avoid flapping alerts. + + {{< figure src="/media/docs/alerting/alert-rule-configure-no-data-and-error-v2.png" alt="A screenshot of the `Configure error handling` option in Grafana Alerting." max-width="500px" >}} + +This applies even when alert rules query Prometheus itself—not just external data sources. + +### Designing alerts for connectivity errors + +In practice, start by deciding if you want to create explicit alert rules — for example, using `up` or `probe_success` — to detect when a target is down or having connectivity issues. + +Then, for each alert rule, choose the error-handling behavior based on whether you already have dedicated connectivity alerts, the stability of the target, and how critical the alert is. Prioritize alerts based on symptom severity rather than just infrastructure signals that might not impact users. + +### Reducing redundant error notifications + +A single data source error can lead to multiple alerts firing simultaneously, sometimes bombarding you with many alerts and generating too much noise. + +As described previously, you can control the error-handling behavior for Grafana alerts. The **Keep Last State** or **Normal** option prevents alerts from firing and helps avoid redundant alerts, especially for services already covered by `up` or `probe_success` alerts. + +When using the default behavior, a single connectivity error will likely trigger multiple `DatasourceError` alerts. + +These alerts are separate from the original alerts—they’re not just a different state of the original alert. They fire immediately, ignore the pending period, and don’t inherit all the labels. This can catch you off guard if you expect them to behave like the original alerts. + +Consider not treating these alerts in the same way as the original alerts, and implement dedicated strategies for their notifications: + +- Reduce duplicate notifications by grouping `DatasourceError` alerts. Use the `datasource_uid` label to group errors from the same data source. + +- Route `DatasourceError` alerts separately, sending them to different teams or channels depending on their impact and urgency. + +For details on how to configure grouping and routing, refer to [handling notifications](ref:notifications) and [`No Data` and `Error` alerts](ref:no-data-and-error-alerts) documentation. + +## Wrapping up + +Connectivity issues are one of the common causes of noisy or misleading alerts. This guide covered two distinct types: + +- **Availability issues**, where the target itself is down or unreachable (e.g., due to a crash or network failure). + +- **Query execution errors**, where the alert rule can't reach its data source (e.g., due to timeouts, invalid queries, or data source outages). + +These problems come from different parts of your stack, and require its own techniques. Prometheus and Grafana allow you to detect them, and combining distinct techniques can make your alerts more resilient. + +With Prometheus, avoid relying solely on `up == 0`. Smooth queries to account for intermittent failures, and use synthetic monitoring to detect reachability issues from outside your network. + +In Grafana Alerting, configure error handling explicitly. Not all alerts are equal or have the same urgency. Tune the error-handling behavior based on the reliability and severity of the alerts and whether you already have alerts dedicated to connectivity problems. + +And don’t forget the third case: **missing data**. If only one host from a fleet silently disappears, you might not get alerted. If you're dealing with individual instances that stopped reporting data, see the [Guide on handling missing data](ref:missing-data-guide) to continue exploring this topic. diff --git a/docs/sources/alerting/learn/missing-data.md b/docs/sources/alerting/learn/missing-data.md new file mode 100644 index 00000000000..9673aa888e3 --- /dev/null +++ b/docs/sources/alerting/learn/missing-data.md @@ -0,0 +1,208 @@ +--- +canonical: https://grafana.com/docs/grafana/latest/alerting/learn/missing-data/ +description: Learn how to detect missing metrics and design alerts that handle gaps in data in Prometheus and Grafana Alerting. +keywords: + - grafana + - alerting + - guide + - rules + - create +labels: + products: + - cloud + - enterprise + - oss +menuTitle: Handling missing data +title: Handling missing data in Grafana Alerting +weight: 1020 +refs: + connectivity-errors-guide: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/learn/connectivity-errors/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/learn/connectivity-errors/ + connectivity-errors-reduce-alert-fatigue: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/learn/connectivity-errors/#reducing-notification-fatigue-from-datasourceerror-alerts + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/learn/connectivity-errors/ + alert-history: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/monitor-status/view-alert-state-history/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/monitor-status/view-alert-state-history/ + configure-nodata-and-error-handling: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/fundamentals/alert-rule-evaluation/state-and-health/#modify-the-no-data-or-error-state + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/fundamentals/alert-rule-evaluation/state-and-health/#modify-the-no-data-or-error-state + stale-alert-instances: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/fundamentals/alert-rule-evaluation/state-and-health/#stale-alert-instances-missingseries + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/fundamentals/alert-rule-evaluation/state-and-health/#stale-alert-instances-missingseries + no-data-and-error-alerts: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/fundamentals/alert-rule-evaluation/state-and-health/#no-data-and-error-alerts + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/fundamentals/alert-rule-evaluation/state-and-health/#no-data-and-error-alerts +--- + +# Handling missing data in Grafana Alerting + +Missing data, or when a target stops reporting metric data, is one of the most common issues when troubleshooting alerts. In cloud-native environments, this happens all the time — pods or nodes scale down to match demand, or an entire job quietly disappears. + +When this happens, alerts won’t fire, and you might not notice the system has stopped reporting. + +Sometimes it's just a lack of data from a few instances. Other times, it's a connectivity issue where the entire target is unreachable. + +This guide covers different scenarios where the underlying data is missing and how to design your alerts to act on those cases. If you're troubleshooting an unreachable host or a network failure, see [Handling connectivity errors](ref:connectivity-errors-guide) as well. + +## No Data vs. Missing Series + +There are a few common causes when an instance stops reporting data, similar to [connectivity errors](ref:connectivity-errors-guide): + +- Host crash: The system is down, and Prometheus stops scraping the target. +- Temporary network failures: Intermittent scrape failures cause data gaps. +- Deployment changes: Decommissioning, Kubernetes pod eviction, or scaling down resources. +- Ephemeral workloads: Metrics intentionally stop reporting. +- And more. + +The first thing to understand is the difference between a query failure (or connectivity error), _No Data_, and a _Missing Series_. + +Alert queries often return multiple time series — one per instance, pod, region, or label combination. This is known as a **multi-dimensional alert**, meaning a single alert rule can trigger multiple alert instances (alerts). + +For example, imagine a recorded metric, `http_request_latency_seconds`, that reports latency per second in the regions where the application is deployed. The query returns one series per region — for instance, `region1` and `region2` — and generates only two alert instances. In this scenario, you may experience: + +- **Connectivity Error** if the alert rule query fails. +- **No Data** if the query runs successfully but returns no data at all. +- **Missing Series** if one or more specific series, which previously returned data, are missing, but other series still return data. + +In both _No Data_ and _Missing Series_ cases, the query still technically "works", but the alert won’t fire unless you explicitly configure it to handle these situations. + +Let’s walk through both scenarios using the previous example, with an alert that triggers if the latency exceeds 2 seconds in any region: `avg_over_time(http_request_latency_seconds[5m]) > 2` + +**No Data Scenario:** The query returns no data for any series: + +| Time | region1 | region2 | Alert triggered | +| :---- | :--------- | :--------- | :--------------------------- | +| 00:00 | 1.5s 🟢 | 1s 🟢 | ✅ No Alert | +| 01:00 | No Data ⚠️ | No Data ⚠️ | ⚠️ No Alert (Silent Failure) | +| 02:00 | No Data ⚠️ | No Data ⚠️ | ⚠️ No Alert (Silent Failure) | +| 03:00 | 1.4s 🟢 | 1s 🟢 | ✅ No Alert | + +**MissingSeries Scenario:** Only a specific series (`region2`) disappears: + +| Time | region1 | region2 | Alert triggered | +| :---- | :------ | :---------------- | :--------------------------- | +| 00:00 | 1.5s 🟢 | 1s 🟢 | ✅ No Alert | +| 01:00 | 1.6s 🟢 | Missing Series ⚠️ | ⚠️ No Alert (Silent Failure) | +| 02:00 | 1.6s 🟢 | Missing Series ⚠️ | ⚠️ No Alert (Silent Failure) | +| 03:00 | 1.4s 🟢 | 1s 🟢 | ✅ No Alert | + +In both cases, something broke silently. + +## Detecting missing data in Prometheus + +Prometheus doesn't fire alerts when the query returns no data. It simply assumes there was nothing to report, like with query errors. Missing data won’t trigger existing alerts unless you explicitly check for it. + +In Prometheus, a common way to catch missing data is by using the `absent_over_time` function. + +`absent_over_time(http_request_latency_seconds[5m]) == 1` + +This triggers when all series for `http_request_latency_seconds` are absent for 5 minutes — catching the _No Data_ case when the entire metric disappears. + +However, `absent_over_time()` can’t detect which specific series are missing since it doesn’t preserve labels. The alert won’t tell you which series stopped reporting — only that the query returns no data. + +If you want to check for missing data per-region or label, you can specify the label in the alert query as follows: + +`absent_over_time(http_request_latency_seconds{region="region1"}[5m]) == 1` +`or` +`absent_over_time(http_request_latency_seconds{region="region2"}[5m]) == 1` + +But this doesn't scale well. Hardcoding queries for each label set is fragile, especially in dynamic cloud environments where instances can appear or disappear at any time. + +## Handling No Data in Grafana alerts + +While Prometheus provides functions like `absent_over_time()` to detect missing data, not all data sources — like Graphite, InfluxDB, PostgreSQL, and others — available to Grafana alerts support a similar function. + +To handle this, Grafana Alerting implements a built-in `No Data` state logic, so you don’t need to detect missing data with `absent_*` queries. Instead, you can configure in the alert rule settings how alerts behave when no data is returned. + +Similar to error handling, Grafana by default triggers a special _No data_ alert and lets you control this behavior. In [**Configure no data and error handling**](ref:configure-nodata-and-error-handling), click **Alert state if no data or all values are null**, and choose one of the following options: + +- **No Data (default):** Triggers a new `DatasourceNoData` alert, treating _No data_ as a specific problem. +- **Alerting:** Transition each existing alert instance into the `Alerting` state when data disappears. +- **Normal:** Ignores missing data and transitions all instances to the `Normal` state. Useful when receiving intermittent data, such as from experimental services, sporadic actions, or periodic reports. +- **Keep Last State:** Leaves the alert in its previous state until the data returns. This is common in environments where brief metric gaps happen regularly, like with flaky exporters or noisy environments. + + {{< figure src="/media/docs/alerting/alert-rule-configure-no-data.png" alt="A screenshot of the `Configure no data handling` option in Grafana Alerting." max-width="500px" >}} + +### Handling DatasourceNoData notifications + +When Grafana triggers a [NoData alert](ref:no-data-and-error-alerts), it creates a distinct alert instance, separate from the original alert instance. These alerts behave differently: + +- They use a dedicated `alertname: DatasourceNoData`. +- They don’t inherit all the labels from the original alert instances. +- They trigger immediately, ignoring the pending period. + +Because of this, `DatasourceNoData` alerts might require a dedicated setup to handle their notifications. For general recommendations, see [Reduce redundant DatasourceError alerts](ref:connectivity-errors-reduce-alert-fatigue) — similar practices can apply to _NoData_ alerts. + +## Evicting alert instances for missing series + +_MissingSeries_ occurs when only some series disappear but not all. This case is subtle — but important. + +Grafana marks missing series as [**stale**](ref:stale-alert-instances) after two evaluation intervals and triggers the alert instance eviction process. Here’s what happens under the hood: + +- Alert instances with missing data keep their last state for two evaluation intervals. +- If still missing after that: + - Grafana adds the annotation `grafana_state_reason: MissingSeries`. + - The alert instance transitions to the `Normal` state. + - A **resolved notification** is sent if the alert was previously firing. + - The **alert instance is removed** from the Grafana UI. + +If an alert instance becomes stale, you’ll find in the [alert history](ref:alert-history) as `Normal (Missing Series)` before it disappears. This table shows the eviction process from the previous example: + +| Time | region1 | region2 | Alert triggered | +| :---- | :-------------------- | :--------------------------------- | :--------------------------------------------------------------------------------------------- | +| 00:00 | 1.5s 🟢 | 1s 🟢 | 🟢🟢 No Alerts | +| 01:00 | 3s 🔴
`Alerting` | 3s 🔴
`Alerting` | 🔴🔴 Alert instances triggered for both regions | +| 02:00 | 1.6s 🟢 | MissingSeries ⚠️
`Alerting` ️ | 🟢🔴 Region2 missing, state maintained. | +| 03:00 | 1.6s 🟢 | MissingSeries ⚠️ `Alerting`️ | 🟢🔴Region2 missing, state maintained. | +| 04:00 | 1.4s 🟢 | — | 🟢 🟢 `region2` Normal (Missing Series), resolved, and instance evicted; 📩 Notification sent. | +| 05:00 | 1.4s 🟢 | — | 🟢 No Alerts | + +### + +### Why doesn’t MissingSeries match No Data behaviour? + +In dynamic environments — autoscaling groups, ephemeral pods, spot instances — series naturally come and go. **MissingSeries** normally signals infrastructure or deployment changes. + +By default, **No Data** triggers an alert to indicate a potential problem. + +The eviction process for **MissingSeries** is designed to prevent alert flapping when a pod or instance disappears, reducing alert noise. + +In environments with frequent scale events, prioritize symptom-based alerts over individual infrastructure signals and use aggregate alerts unless you explicitly need to track individual instances. + +### Handling MissingSeries notifications + +A stale alert instance triggers a **resolved notification** if it transitions from a firing state (such as `Alerting`, `No Data`, or `Error`) to `Normal`. + +You can display the `MissingSeries` annotation in notifications to indicate the alert wasn’t resolved by recovery but evicted due to series data going missing. + +Review these notifications to confirm whether something broke or if the alert was unnecessary. To reduce noise: + +- Silence or mute alerts during planned maintenance or rollouts. +- Adjust alert rules to avoid triggering on series you expect to come and go, and use aggregated alerts instead. + +## Wrapping up + +Missing data isn’t always a failure. It’s a common scenario in dynamic environments when certain targets stop reporting. + +Grafana Alerting handles distinct scenarios automatically. Here’s how to think about it: + +- Use Grafana’s _No Data_ handling options to define what happens when a query returns nothing. +- Understand `DatasourceNoData` and `MissingSeries` notifications, since they don’t behave like regular alerts. +- Use `absent()` or `absent_over_time()` in Prometheus for fine-grained detection when a metric or label disappears entirely. +- Don’t alert on every instance by default. In dynamic environments, it’s better to aggregate and alert on symptoms — unless a missing individual instance directly impacts users. +- If you’re getting too much noise from disappearing data, consider adjusting alerts, using `Keep Last State`, or routing those alerts differently. +- For connectivity issues involving alert query failures, see the sibling guide: [Handling connectivity errors in Grafana Alerting](ref:connectivity-errors-guide). From b8c5ca063240116dc6b8f9b48a48fb3711ce098a Mon Sep 17 00:00:00 2001 From: Oscar Kilhed Date: Thu, 1 May 2025 11:26:58 +0200 Subject: [PATCH 062/849] E2E: Fix failing old arch test. (#104793) * Wait for all queries * wait for the item to become a button instead --- e2e/old-arch/dashboards-suite/set-options-from-ui.spec.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/e2e/old-arch/dashboards-suite/set-options-from-ui.spec.ts b/e2e/old-arch/dashboards-suite/set-options-from-ui.spec.ts index d7ec950487b..6d87f7ce939 100644 --- a/e2e/old-arch/dashboards-suite/set-options-from-ui.spec.ts +++ b/e2e/old-arch/dashboards-suite/set-options-from-ui.spec.ts @@ -71,7 +71,10 @@ describe('Variables - Set options from ui', () => { e2e.components.LoadingIndicator.icon().should('have.length', 0); - e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('AA').should('be.visible').click(); + e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('AA') + .should('be.visible') + .should('match', 'button') + .click(); e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownDropDown() .should('be.visible') From 195dedf0fe92638667df0bcc9ad33f4c1f56535a Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Thu, 1 May 2025 11:02:28 +0100 Subject: [PATCH 063/849] Chore: Fix crowdin download action (#104809) fix crowdin download action --- .github/workflows/i18n-crowdin-download.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/i18n-crowdin-download.yml b/.github/workflows/i18n-crowdin-download.yml index e019dfc5a43..094748ba831 100644 --- a/.github/workflows/i18n-crowdin-download.yml +++ b/.github/workflows/i18n-crowdin-download.yml @@ -126,7 +126,7 @@ jobs: token: ${{ steps.generate_token.outputs.token }} - name: Get vault secrets - id: vault-secrets + id: vault-secrets-approver uses: grafana/shared-workflows/actions/get-vault-secrets@main # zizmor: ignore[unpinned-uses] with: # Secrets placed in ci/repo/grafana/grafana/grafana-pr-approver From 035ecc15b242e18ac9cde82585eb58eb295aaecb Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Thu, 1 May 2025 11:10:52 +0100 Subject: [PATCH 064/849] CI: Fix Skye and E2E GHA workflows (#104811) * CI: Use pr_automation_app in skye workflow * CI: Fix e2e workflow artifact name (cherry picked from commit e9fe1dedf760ef234e5e8ee8eec30364e7ec4b26) * remove old-arch check (cherry picked from commit 960e2d057b86fdcbb208699a8b6737833b9e86b5) --- .github/workflows/run-e2e-suite.yml | 4 ++-- .github/workflows/skye-add-to-project.yml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/run-e2e-suite.yml b/.github/workflows/run-e2e-suite.yml index 5445d21f9bb..ae0cb4cfd3a 100644 --- a/.github/workflows/run-e2e-suite.yml +++ b/.github/workflows/run-e2e-suite.yml @@ -21,18 +21,18 @@ jobs: with: name: ${{ inputs.package }} - uses: dagger/dagger-for-github@e47aba410ef9bb9ed81a4d2a97df31061e5e842e - if: inputs.old-arch == false with: verb: run args: go run ./pkg/build/e2e --package=grafana.tar.gz --suite=${{ inputs.suite }} - name: Set suite name id: set-suite-name + if: always() env: SUITE: ${{ inputs.suite }} run: | echo "suite=$(echo $SUITE | sed 's/\//-/g')" >> $GITHUB_OUTPUT - uses: actions/upload-artifact@v4 - if: ${{ always() && inputs.old-arch != true }} + if: always() with: name: e2e-${{ steps.set-suite-name.outputs.suite }}-${{github.run_number}} path: videos diff --git a/.github/workflows/skye-add-to-project.yml b/.github/workflows/skye-add-to-project.yml index 321f4c40b2a..7aee160cbcb 100644 --- a/.github/workflows/skye-add-to-project.yml +++ b/.github/workflows/skye-add-to-project.yml @@ -33,11 +33,11 @@ jobs: uses: grafana/shared-workflows/actions/get-vault-secrets@main # zizmor: ignore[unpinned-uses] with: # Vault secret paths: - # - ci/repo/grafana/grafana/plugins_platform_issue_commands_github_bot + # - ci/repo/grafana/grafana/grafana_pr_automation_app # - ci/repo/grafana/grafana/frontend_platform_skye_usernames (comma separated list of usernames) repo_secrets: | - GH_APP_ID=plugins_platform_issue_commands_github_bot:app_id - GH_APP_PEM=plugins_platform_issue_commands_github_bot:app_pem + GH_APP_ID=grafana_pr_automation_app:app_id + GH_APP_PEM=grafana_pr_automation_app:app_pem ALLOWED_USERS=frontend_platform_skye_usernames:allowed_users - name: Generate token From 162fed84b5c318fa6d62d8559b5f606b31aa8863 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Thu, 1 May 2025 11:31:43 +0100 Subject: [PATCH 065/849] Chore: No fail-fast on e2e tests (#104812) no fail-fast on e2e tests --- .github/workflows/pr-e2e-tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pr-e2e-tests.yml b/.github/workflows/pr-e2e-tests.yml index 7fb27a34ebd..969d0a3158e 100644 --- a/.github/workflows/pr-e2e-tests.yml +++ b/.github/workflows/pr-e2e-tests.yml @@ -44,6 +44,7 @@ jobs: e2e-matrix: name: ${{ matrix.suite }} strategy: + fail-fast: false matrix: suite: - various-suite @@ -59,6 +60,7 @@ jobs: e2e-matrix-old-arch: name: ${{ matrix.suite }} (old arch) strategy: + fail-fast: false matrix: suite: - old-arch/various-suite From b8ac9fd86603a1af7ca87cd384de5c76e9459161 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Thu, 1 May 2025 11:33:36 +0100 Subject: [PATCH 066/849] Chore: i18n action - get secrets stuff from vault (#104816) get PR_AUTOMATION stuff from vault --- .github/workflows/i18n-crowdin-download.yml | 28 +++++++++++---------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/.github/workflows/i18n-crowdin-download.yml b/.github/workflows/i18n-crowdin-download.yml index 094748ba831..32a2ceab514 100644 --- a/.github/workflows/i18n-crowdin-download.yml +++ b/.github/workflows/i18n-crowdin-download.yml @@ -15,19 +15,6 @@ jobs: id-token: write # needed to get vault secrets steps: - - name: Generate token - id: generate_token - uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 - with: - app_id: ${{ secrets.GRAFANA_PR_AUTOMATION_APP_ID }} - private_key: ${{ secrets.GRAFANA_PR_AUTOMATION_APP_PEM }} - - - uses: actions/checkout@v4 - with: - ref: ${{ github.head_ref }} - token: ${{ steps.generate_token.outputs.token }} - persist-credentials: false - - name: "Get vault secrets" id: vault-secrets uses: grafana/shared-workflows/actions/get-vault-secrets@main # zizmor: ignore[unpinned-uses] @@ -36,6 +23,21 @@ jobs: # - ci/repo/grafana/grafana/grafana_frontend_platform_crowdin_bot repo_secrets: | CROWDIN_TOKEN=grafana_frontend_platform_crowdin_bot:access_token + GRAFANA_PR_AUTOMATION_APP_ID=grafana_pr_automation_app:app_id + GRAFANA_PR_AUTOMATION_APP_PEM=grafana_pr_automation_app:app_pem + + - name: Generate token + id: generate_token + uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 + with: + app_id: ${{ env.GRAFANA_PR_AUTOMATION_APP_ID }} + private_key: ${{ env.GRAFANA_PR_AUTOMATION_APP_PEM }} + + - uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + token: ${{ steps.generate_token.outputs.token }} + persist-credentials: false - name: Download sources id: crowdin-download From af29132fc03314f0d990e4293616e288cf8a2a5b Mon Sep 17 00:00:00 2001 From: "grafana-pr-automation[bot]" <140550294+grafana-pr-automation[bot]@users.noreply.github.com> Date: Thu, 1 May 2025 13:52:25 +0300 Subject: [PATCH 067/849] I18n: Download translations from Crowdin (#104817) New Crowdin translations by GitHub Action Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- public/locales/cs-CZ/grafana.json | 142 ++++++++++++++++++++++------ public/locales/de-DE/grafana.json | 142 ++++++++++++++++++++++------ public/locales/es-ES/grafana.json | 142 ++++++++++++++++++++++------ public/locales/fr-FR/grafana.json | 142 ++++++++++++++++++++++------ public/locales/hu-HU/grafana.json | 142 ++++++++++++++++++++++------ public/locales/id-ID/grafana.json | 142 ++++++++++++++++++++++------ public/locales/it-IT/grafana.json | 142 ++++++++++++++++++++++------ public/locales/ja-JP/grafana.json | 142 ++++++++++++++++++++++------ public/locales/ko-KR/grafana.json | 142 ++++++++++++++++++++++------ public/locales/nl-NL/grafana.json | 142 ++++++++++++++++++++++------ public/locales/pl-PL/grafana.json | 142 ++++++++++++++++++++++------ public/locales/pt-BR/grafana.json | 142 ++++++++++++++++++++++------ public/locales/pt-PT/grafana.json | 142 ++++++++++++++++++++++------ public/locales/ru-RU/grafana.json | 142 ++++++++++++++++++++++------ public/locales/sv-SE/grafana.json | 142 ++++++++++++++++++++++------ public/locales/tr-TR/grafana.json | 142 ++++++++++++++++++++++------ public/locales/zh-Hans/grafana.json | 142 ++++++++++++++++++++++------ public/locales/zh-Hant/grafana.json | 142 ++++++++++++++++++++++------ 18 files changed, 2070 insertions(+), 486 deletions(-) diff --git a/public/locales/cs-CZ/grafana.json b/public/locales/cs-CZ/grafana.json index f2b73f0cced..40f7fdc9c03 100644 --- a/public/locales/cs-CZ/grafana.json +++ b/public/locales/cs-CZ/grafana.json @@ -21,7 +21,9 @@ "permission-list-item": { "inherited": "Zděděno ze složky", "locked-aria-label": "", - "remove-aria-label": "" + "remove-aria-label": "", + "tooltip-inherited-permission": "", + "tooltip-provisioned-permission": "" }, "permissions": { "add-label": "Přidat oprávnění", @@ -42,6 +44,11 @@ }, "actions": { "action-editor": { + "button": { + "style": { + "background-color": "" + } + }, "label-headers": "", "label-url": "", "placeholder-url": "" @@ -70,6 +77,7 @@ "confirm-modal-body-2": "", "get-stage-cell": { "beta": "", + "content-general-availability": "", "deprecated": "", "ga": "" }, @@ -128,6 +136,12 @@ "test-mapping-heading": "Testovat mapování uživatele", "test-mapping-run-button": "Spustit" }, + "ldap-connection-status": { + "columns": { + "content-connection-is-available": "", + "content-connection-is-not-available": "" + } + }, "ldap-error-box": { "title-connection-error": "" }, @@ -161,6 +175,9 @@ "title": "Synchronizace LDAP" }, "ldap-user-groups": { + "columns": { + "content-no-matching-organizations-found": "" + }, "no-org-found": "" }, "ldap-user-info": { @@ -284,12 +301,14 @@ }, "users-table": { "columns": { + "content-grafana-admin": "", "title-edit-user": "" }, "edit-aria-label": "", "edit-tooltip": "", "last-seen-never": "Nikdy", - "no-licensed-roles": "Nepřiřazeno" + "no-licensed-roles": "Nepřiřazeno", + "tooltip-assigned-role": "" } }, "alert-labels": { @@ -416,11 +435,9 @@ }, "alert-rule-form": { "action-buttons": { - "delete": "", "edit-yaml": "", - "save-exit": "" - }, - "title-delete-rule": "" + "save": "" + } }, "alert-rule-name-and-metric": { "aria-label-name": "", @@ -452,6 +469,7 @@ "unprocessed-the-alert-is-received": "" }, "alert-state-tag": { + "content-alert-evaluation-is-currently-paused": "", "paused": "" }, "alert-warning": { @@ -548,6 +566,9 @@ "warning-restore-manually": "Ručně obnovujete starou verzi tohoto pravidla výstrahy. Před uložením definice pravidla si pečlivě přečtěte změny.", "warning-restore-manually-title": "Ruční obnovení pravidla" }, + "all-matches-indicator": { + "content-this-policy-matches-all-labels": "" + }, "am-root-route-form": { "am-group-description-label": "", "am-group-interval-description": "", @@ -660,6 +681,9 @@ "of": "", "when": "" }, + "clear-filter-button-object-renderer": { + "content-clear-filter": "" + }, "clone-rule-button": { "title-copy": "" }, @@ -790,6 +814,9 @@ "contactPointFilter": { "label": "Kontaktní bod" }, + "continue-matching-indicator": { + "content-route-continue-matching-other-policies": "" + }, "copy-to-clipboard": "Kopírovat „{{label}}“ do schránky", "create-metadata": { "view-dashboard": "", @@ -818,6 +845,10 @@ "current-selection-dashboard": "", "current-selection-panel": "", "fallback-dashboards-string": "", + "panel-row": { + "content-panel-valid-identifier": "", + "tooltip-alert-tab-support": "" + }, "placeholder-search-dashboard": "", "placeholder-search-panel": "", "select-dashboard-available-panels": "", @@ -839,9 +870,11 @@ "update-datasource": "" }, "declare-incident-button": { + "content-grafana-incident-installed-configured-correctly": "", "declare-incident": "" }, "declare-incident-menu-item": { + "content-grafana-incident-installed-configured-correctly": "", "label-declare-incident": "" }, "delete-rule-modal": { @@ -877,6 +910,9 @@ }, "unknown": "" }, + "disabled-tooltip": { + "content-appear-compatible-datasources": "" + }, "draggable-rules-table": { "evals-to-start-alerting": "", "pending-period": "", @@ -907,10 +943,19 @@ "text-loading-template": "", "title-failed-to-fetch-notification-template": "" }, + "editor": { + "edit-alert-rule": "", + "edit-recording-rule": "", + "new-alert-rule": "", + "new-recording-rule": "" + }, "error-modal": { "failed-to-update-your-configuration": "", "title-something-went-wrong": "" }, + "error-summary-button": { + "content-show-all-errors": "" + }, "evaluation-behavior-summary": { "evaluate": "", "label-evaluate": "", @@ -941,7 +986,6 @@ "existing-rule-editor": { "sorry-permission": "", "sorry-this-rule-does-not-exist": "", - "text-loading-rule": "", "title-cannot-edit-rule": "", "title-failed-to-load-rule": "", "title-rule-not-found": "" @@ -1192,6 +1236,9 @@ "group-loader": { "group-load-failed": "" }, + "group-status": { + "content-the-group-is-being-deleted": "" + }, "header": { "tooltip-remove": "" }, @@ -1363,7 +1410,8 @@ "common-labels": "", "error-unable-to-fetch": "", "loading": "", - "title-error-fetching-the-state-history": "" + "title-error-fetching-the-state-history": "", + "tooltip-common-labels": "" }, "manage-permissions": { "button": "Spravovat oprávnění", @@ -1456,6 +1504,7 @@ }, "mute-timing-time-range": { "add-another-time-range": "", + "content-this-time-interval-is-disabled": "", "description-time-range": "", "label-end-time": "", "label-start-time": "", @@ -1611,6 +1660,9 @@ "plugin-integrations": { "tailored-apps": "" }, + "plugin-origin-badge": { + "tooltip-managed-by-plugin": "" + }, "policies": { "aria-label-collapse": "", "aria-label-expand": "", @@ -1714,6 +1766,9 @@ "prometheus-consistency-check": { "title-unable-to-check-the-rule-status": "" }, + "provisioned-tooltip": { + "content-provisioned-items-cannot-edited": "" + }, "provisioning": { "badge-tooltip-provenance": "Tento zdroj byl zajištěn prostřednictvím {{provenance}} a nelze ho upravovat přes uživatelské rozhraní", "badge-tooltip-standard": "Tento zdroj byl zajištěn a nelze ho upravovat přes uživatelské rozhraní", @@ -1951,14 +2006,14 @@ }, "threshold": { "recovery": { - "stop-alerting-above": "Zastavit výstrahy, když je přes", - "stop-alerting-bellow": "Zastavit výstrahy, když je pod", - "stop-alerting-equal": "Zastavit výstrahy, když se rovná", - "stop-alerting-inside-range": "Zastavit výstrahy, když je v rozsahu", - "stop-alerting-less": "Zastavit výstrahy, když je méně než", - "stop-alerting-more": "Zastavit výstrahy, když je více než", - "stop-alerting-not-equal": "Zastavit výstrahy, když se nerovná", - "stop-alerting-outside-range": "Zastavit výstrahy, když je mimo rozsah", + "stop-alerting-above": "", + "stop-alerting-bellow": "", + "stop-alerting-equal": "", + "stop-alerting-inside-range": "", + "stop-alerting-less": "", + "stop-alerting-more": "", + "stop-alerting-not-equal": "", + "stop-alerting-outside-range": "", "title": "Vlastní práh obnovy" } } @@ -2062,7 +2117,10 @@ "for": "", "na": "", "paused": "Pozastaveno", - "recording-rule": "Pravidlo nahrávání" + "recording-rule": "Pravidlo nahrávání", + "recording-rule-state": { + "content-recording-rule-evaluation-is-currently-paused": "" + } }, "rule-stats": { "firing": "", @@ -2308,6 +2366,8 @@ "refresh": "" }, "template-row": { + "content-templatefiles": "", + "content-templates": "", "tooltip-copy-template-group": "", "tooltip-delete-template-group": "", "tooltip-edit-template-group": "", @@ -2331,7 +2391,7 @@ }, "misconfigured-badge-text": "Nesprávně nakonfigurováno", "misconfigured-warning": "Tato šablona je nesprávně nakonfigurována.", - "misconfigured-warning-details": "Šablony musí být definovány v oddílech <1> a <4> konfigurace správce výstrah." + "misconfigured-warning-details": "" }, "templates-picker": { "button-edit": "", @@ -2376,6 +2436,10 @@ "type-selector-button": { "add-expression": "" }, + "unknown-contact-point-details": { + "unknown-contact-point": "", + "unknown-contact-point-tooltip": "" + }, "unknown-rule-list-item": { "title-unknown-rule-type": "" }, @@ -2476,6 +2540,7 @@ }, "api-keys-table": { "aria-label-delete-api-key": "", + "content-this-api-key-has-expired": "", "expires": "", "last-used-at": "", "migrate-to-service-account": "", @@ -4196,7 +4261,9 @@ }, "variable-check-indicator": { "aria-label-variable-referenced-dashboard": "", - "aria-label-variable-referenced-other-variables-dashboard": "" + "aria-label-variable-referenced-other-variables-dashboard": "", + "content-variable-not-referenced-other-variables-dashboard": "", + "content-variable-referenced-other-variables-dashboard": "" }, "variable-editor-form": { "aria-label-variable-editor-form": "", @@ -4256,6 +4323,7 @@ }, "versions-history-buttons": { "compare-versions": "", + "content-select-two-versions-to-start-comparing": "", "show-more-versions": "" }, "visualization-button": { @@ -4609,6 +4677,7 @@ } }, "correlation-editor-mode-bar": { + "content-correlations-editor-explore-experimental-feature": "", "exit-correlation-editor": "", "save": "" }, @@ -4766,6 +4835,9 @@ "label-select-query": "", "tooltip-select-query-visualize-table": "" }, + "logs-volume-panel": { + "content-streaming": "" + }, "logs-volume-panel-list": { "label-reload-log-volume": "", "loading": "", @@ -4965,6 +5037,7 @@ "label-tags": "", "placeholder-all-service-names": "", "placeholder-all-span-names": "", + "tooltip-collapse": "", "tooltip-duration": "", "tooltip-tags": "" }, @@ -5043,7 +5116,8 @@ "label-show-paths": "" }, "trace-view": { - "no-data": "" + "no-data": "", + "tooltip-copy-icon": "" }, "trace-view-container": { "title-trace": "" @@ -5157,7 +5231,8 @@ "default-password-alert": "", "new-password-label": "", "skip-button": "Přeskočit", - "submit-button": "Odeslat" + "submit-button": "Odeslat", + "tooltip-skip-button": "" }, "contact-admin": "Zapomněli jste uživatelské jméno nebo e-mail? Obraťte se na správce Grafany.", "email-sent": "Na e-mailovou adresu byl odeslán e-mail s odkazem na obnovení. Brzy byste ho měli obdržet.", @@ -5214,7 +5289,8 @@ "action-editor": { "button": { "confirm": "Potvrdit", - "confirm-action": "Potvrdit akci" + "confirm-action": "Potvrdit akci", + "style": "" }, "inline": { "add-action": "Přidat akci", @@ -5421,7 +5497,11 @@ "previous-page": "předchozí strana" }, "panel-chrome": { - "aria-label-toggle-collapse": "" + "aria-label-toggle-collapse": "", + "tooltip-cancel": "", + "tooltip-cancel-loading": "", + "tooltip-stop-streaming": "", + "tooltip-streaming": "" }, "panel-menu": { "label": "Nabídka pro panel {{ title }}", @@ -6013,7 +6093,8 @@ "log-row-message": { "ellipsis": "… ", "more": "další", - "see-details": "Zobrazit podrobnosti protokolu" + "see-details": "Zobrazit podrobnosti protokolu", + "tooltip-error": "" }, "log-rows": { "disable-popover": { @@ -6990,7 +7071,8 @@ "label-search": "", "label-state": "", "label-type": "", - "subtitle": "" + "subtitle": "", + "tooltip-filter-disabled": "" }, "catalog": { "no-updates-available": "Nejsou k dispozici žádné aktualizace", @@ -6999,7 +7081,7 @@ "available-header": "Dostupné", "button": "Aktualizovat vše ({{length}})", "cloud-update-message": "*Může trvat několik minut, než budou pluginy dostupné.", - "error": "Chyba při aktualizaci pluginu:", + "error": "", "error-status-text": "nezdařilo se – viz chybová hlášení", "header": "Následující pluginy mají k dispozici aktualizaci", "installed-header": "Nainstalované", @@ -7209,6 +7291,9 @@ "feature-toggles-age": { "enable-in-config": "" }, + "input-suffix": { + "content-login-details-locked-because-managed-another": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -8152,10 +8237,12 @@ "title-disable-service-account": "" }, "token-expiration": { + "content-this-token-has-expired": "", "expired-label": "", "never": "" }, "token-revoked": { + "content-token-publicly-exposed-please-rotate": "", "revoked-label": "" } }, @@ -8561,7 +8648,7 @@ "range-content": { "apply-button": "Použít časový rozsah", "default-error": "", - "fiscal-year": "Fiskální rok", + "fiscal-year": "", "from-input": "Od", "open-input-calendar": "Otevřít kalendář", "range-error": "„Od“ nemůže být po „Do“", @@ -9013,6 +9100,7 @@ "loading": "Načítání…", "no-unknowns": "Nebyly nalezeny žádné přejmenované nebo chybějící proměnné.", "renamed-or-missing-variables": "Přejmenované nebo chybějící proměnné", + "tooltip-renamed-or-missing-variables": "", "variable": "Proměnná" }, "variable-check-indicator": { diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index 1ad178e3d4f..00fc656ecdb 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -21,7 +21,9 @@ "permission-list-item": { "inherited": "Geerbt von Ordner", "locked-aria-label": "", - "remove-aria-label": "" + "remove-aria-label": "", + "tooltip-inherited-permission": "", + "tooltip-provisioned-permission": "" }, "permissions": { "add-label": "Berechtigung hinzufügen", @@ -42,6 +44,11 @@ }, "actions": { "action-editor": { + "button": { + "style": { + "background-color": "" + } + }, "label-headers": "", "label-url": "", "placeholder-url": "" @@ -70,6 +77,7 @@ "confirm-modal-body-2": "", "get-stage-cell": { "beta": "", + "content-general-availability": "", "deprecated": "", "ga": "" }, @@ -128,6 +136,12 @@ "test-mapping-heading": "Testbenutzer-Mapping", "test-mapping-run-button": "Ausführen" }, + "ldap-connection-status": { + "columns": { + "content-connection-is-available": "", + "content-connection-is-not-available": "" + } + }, "ldap-error-box": { "title-connection-error": "" }, @@ -161,6 +175,9 @@ "title": "LDAP-Synchronisierung" }, "ldap-user-groups": { + "columns": { + "content-no-matching-organizations-found": "" + }, "no-org-found": "" }, "ldap-user-info": { @@ -284,12 +301,14 @@ }, "users-table": { "columns": { + "content-grafana-admin": "", "title-edit-user": "" }, "edit-aria-label": "", "edit-tooltip": "", "last-seen-never": "", - "no-licensed-roles": "Nicht zugewiesen" + "no-licensed-roles": "Nicht zugewiesen", + "tooltip-assigned-role": "" } }, "alert-labels": { @@ -408,11 +427,9 @@ }, "alert-rule-form": { "action-buttons": { - "delete": "", "edit-yaml": "", - "save-exit": "" - }, - "title-delete-rule": "" + "save": "" + } }, "alert-rule-name-and-metric": { "aria-label-name": "", @@ -444,6 +461,7 @@ "unprocessed-the-alert-is-received": "" }, "alert-state-tag": { + "content-alert-evaluation-is-currently-paused": "", "paused": "" }, "alert-warning": { @@ -540,6 +558,9 @@ "warning-restore-manually": "Sie stellen gerade manuell eine alte Version dieser Warnregel wieder her. Bitte überprüfen Sie die Änderungen sorgfältig, bevor Sie die Regeldefinition speichern.", "warning-restore-manually-title": "Regel manuell wiederherstellen" }, + "all-matches-indicator": { + "content-this-policy-matches-all-labels": "" + }, "am-root-route-form": { "am-group-description-label": "", "am-group-interval-description": "", @@ -652,6 +673,9 @@ "of": "", "when": "" }, + "clear-filter-button-object-renderer": { + "content-clear-filter": "" + }, "clone-rule-button": { "title-copy": "" }, @@ -778,6 +802,9 @@ "contactPointFilter": { "label": "Kontaktpunkt" }, + "continue-matching-indicator": { + "content-route-continue-matching-other-policies": "" + }, "copy-to-clipboard": "„{{label}}“ in die Zwischenablage kopieren", "create-metadata": { "view-dashboard": "", @@ -806,6 +833,10 @@ "current-selection-dashboard": "", "current-selection-panel": "", "fallback-dashboards-string": "", + "panel-row": { + "content-panel-valid-identifier": "", + "tooltip-alert-tab-support": "" + }, "placeholder-search-dashboard": "", "placeholder-search-panel": "", "select-dashboard-available-panels": "", @@ -827,9 +858,11 @@ "update-datasource": "" }, "declare-incident-button": { + "content-grafana-incident-installed-configured-correctly": "", "declare-incident": "" }, "declare-incident-menu-item": { + "content-grafana-incident-installed-configured-correctly": "", "label-declare-incident": "" }, "delete-rule-modal": { @@ -865,6 +898,9 @@ }, "unknown": "" }, + "disabled-tooltip": { + "content-appear-compatible-datasources": "" + }, "draggable-rules-table": { "evals-to-start-alerting": "Evaluierungen, um Warnungen zu starten", "pending-period": "Wartezeit", @@ -895,10 +931,19 @@ "text-loading-template": "", "title-failed-to-fetch-notification-template": "" }, + "editor": { + "edit-alert-rule": "", + "edit-recording-rule": "", + "new-alert-rule": "", + "new-recording-rule": "" + }, "error-modal": { "failed-to-update-your-configuration": "", "title-something-went-wrong": "" }, + "error-summary-button": { + "content-show-all-errors": "" + }, "evaluation-behavior-summary": { "evaluate": "", "label-evaluate": "", @@ -929,7 +974,6 @@ "existing-rule-editor": { "sorry-permission": "", "sorry-this-rule-does-not-exist": "", - "text-loading-rule": "", "title-cannot-edit-rule": "", "title-failed-to-load-rule": "", "title-rule-not-found": "" @@ -1180,6 +1224,9 @@ "group-loader": { "group-load-failed": "" }, + "group-status": { + "content-the-group-is-being-deleted": "" + }, "header": { "tooltip-remove": "" }, @@ -1349,7 +1396,8 @@ "common-labels": "", "error-unable-to-fetch": "", "loading": "", - "title-error-fetching-the-state-history": "" + "title-error-fetching-the-state-history": "", + "tooltip-common-labels": "" }, "manage-permissions": { "button": "", @@ -1442,6 +1490,7 @@ }, "mute-timing-time-range": { "add-another-time-range": "", + "content-this-time-interval-is-disabled": "", "description-time-range": "", "label-end-time": "", "label-start-time": "", @@ -1597,6 +1646,9 @@ "plugin-integrations": { "tailored-apps": "" }, + "plugin-origin-badge": { + "tooltip-managed-by-plugin": "" + }, "policies": { "aria-label-collapse": "", "aria-label-expand": "", @@ -1696,6 +1748,9 @@ "prometheus-consistency-check": { "title-unable-to-check-the-rule-status": "" }, + "provisioned-tooltip": { + "content-provisioned-items-cannot-edited": "" + }, "provisioning": { "badge-tooltip-provenance": "Diese Ressource wurde über {{provenance}} bereitgestellt und kann nicht über die Benutzeroberfläche bearbeitet werden", "badge-tooltip-standard": "Diese Ressource wurde bereitgestellt und kann nicht über die Benutzeroberfläche bearbeitet werden", @@ -1931,14 +1986,14 @@ }, "threshold": { "recovery": { - "stop-alerting-above": "Warnung stoppen, wenn über", - "stop-alerting-bellow": "Warnung stoppen, wenn unter", - "stop-alerting-equal": "Warnung stoppen, wenn gleich", - "stop-alerting-inside-range": "Warnung stoppen, wenn innerhalb des Bereichs", - "stop-alerting-less": "Warnung stoppen, wenn weniger als", - "stop-alerting-more": "Warnung stoppen, wenn mehr als", - "stop-alerting-not-equal": "Warnung stoppen, wenn nicht gleich", - "stop-alerting-outside-range": "Warnung stoppen, wenn außerhalb des Bereichs", + "stop-alerting-above": "", + "stop-alerting-bellow": "", + "stop-alerting-equal": "", + "stop-alerting-inside-range": "", + "stop-alerting-less": "", + "stop-alerting-more": "", + "stop-alerting-not-equal": "", + "stop-alerting-outside-range": "", "title": "Benutzerdefinierter Wiederherstellungsschwellenwert" } } @@ -2038,7 +2093,10 @@ "for": "", "na": "", "paused": "Pausiert", - "recording-rule": "Aufnahmeregel" + "recording-rule": "Aufnahmeregel", + "recording-rule-state": { + "content-recording-rule-evaluation-is-currently-paused": "" + } }, "rule-stats": { "firing": "", @@ -2282,6 +2340,8 @@ "refresh": "" }, "template-row": { + "content-templatefiles": "", + "content-templates": "", "tooltip-copy-template-group": "", "tooltip-delete-template-group": "", "tooltip-edit-template-group": "", @@ -2305,7 +2365,7 @@ }, "misconfigured-badge-text": "Falsch konfiguriert", "misconfigured-warning": "Diese Vorlage ist falsch konfiguriert.", - "misconfigured-warning-details": "Vorlagen müssen sowohl in den Abschnitten <1> als auch <4> Ihrer Alertmanager-Konfiguration definiert werden." + "misconfigured-warning-details": "" }, "templates-picker": { "button-edit": "", @@ -2350,6 +2410,10 @@ "type-selector-button": { "add-expression": "" }, + "unknown-contact-point-details": { + "unknown-contact-point": "", + "unknown-contact-point-tooltip": "" + }, "unknown-rule-list-item": { "title-unknown-rule-type": "" }, @@ -2450,6 +2514,7 @@ }, "api-keys-table": { "aria-label-delete-api-key": "", + "content-this-api-key-has-expired": "", "expires": "", "last-used-at": "", "migrate-to-service-account": "", @@ -4154,7 +4219,9 @@ }, "variable-check-indicator": { "aria-label-variable-referenced-dashboard": "", - "aria-label-variable-referenced-other-variables-dashboard": "" + "aria-label-variable-referenced-other-variables-dashboard": "", + "content-variable-not-referenced-other-variables-dashboard": "", + "content-variable-referenced-other-variables-dashboard": "" }, "variable-editor-form": { "aria-label-variable-editor-form": "", @@ -4214,6 +4281,7 @@ }, "versions-history-buttons": { "compare-versions": "", + "content-select-two-versions-to-start-comparing": "", "show-more-versions": "" }, "visualization-button": { @@ -4567,6 +4635,7 @@ } }, "correlation-editor-mode-bar": { + "content-correlations-editor-explore-experimental-feature": "", "exit-correlation-editor": "", "save": "" }, @@ -4724,6 +4793,9 @@ "label-select-query": "", "tooltip-select-query-visualize-table": "" }, + "logs-volume-panel": { + "content-streaming": "" + }, "logs-volume-panel-list": { "label-reload-log-volume": "", "loading": "", @@ -4923,6 +4995,7 @@ "label-tags": "", "placeholder-all-service-names": "", "placeholder-all-span-names": "", + "tooltip-collapse": "", "tooltip-duration": "", "tooltip-tags": "" }, @@ -5001,7 +5074,8 @@ "label-show-paths": "" }, "trace-view": { - "no-data": "" + "no-data": "", + "tooltip-copy-icon": "" }, "trace-view-container": { "title-trace": "" @@ -5115,7 +5189,8 @@ "default-password-alert": "", "new-password-label": "", "skip-button": "Überspringen", - "submit-button": "Absenden" + "submit-button": "Absenden", + "tooltip-skip-button": "" }, "contact-admin": "Haben Sie Ihren Benutzernamen oder Ihre E-Mail-Adresse vergessen? Wenden Sie sich an Ihren Grafana-Administrator.", "email-sent": "Eine E-Mail mit einem Link zum Zurücksetzen wurde an die E-Mail-Adresse gesendet. Sie sollten sie in Kürze erhalten.", @@ -5172,7 +5247,8 @@ "action-editor": { "button": { "confirm": "Bestätigen", - "confirm-action": "Aktion bestätigen" + "confirm-action": "Aktion bestätigen", + "style": "" }, "inline": { "add-action": "Aktion hinzufügen", @@ -5379,7 +5455,11 @@ "previous-page": "vorherige Seite" }, "panel-chrome": { - "aria-label-toggle-collapse": "" + "aria-label-toggle-collapse": "", + "tooltip-cancel": "", + "tooltip-cancel-loading": "", + "tooltip-stop-streaming": "", + "tooltip-streaming": "" }, "panel-menu": { "label": "Menü für das Panel {{ title }}", @@ -5961,7 +6041,8 @@ "log-row-message": { "ellipsis": "… ", "more": "mehr", - "see-details": "Protokolldetails anzeigen" + "see-details": "Protokolldetails anzeigen", + "tooltip-error": "" }, "log-rows": { "disable-popover": { @@ -6938,7 +7019,8 @@ "label-search": "", "label-state": "", "label-type": "", - "subtitle": "" + "subtitle": "", + "tooltip-filter-disabled": "" }, "catalog": { "no-updates-available": "Keine Updates vorhanden", @@ -6947,7 +7029,7 @@ "available-header": "Verfügbar", "button": "Alle aktualisieren ({{length}})", "cloud-update-message": "*Es kann ggf. einige Minuten dauern, bis die Plugins verwendet werden können.", - "error": "Fehler beim Aktualisieren des Plugins:", + "error": "", "error-status-text": "fehlgeschlagen – siehe Fehlermeldungen", "header": "Für die folgenden Plugins sind Updates verfügbar", "installed-header": "Installiert", @@ -7157,6 +7239,9 @@ "feature-toggles-age": { "enable-in-config": "" }, + "input-suffix": { + "content-login-details-locked-because-managed-another": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -8090,10 +8175,12 @@ "title-disable-service-account": "" }, "token-expiration": { + "content-this-token-has-expired": "", "expired-label": "", "never": "" }, "token-revoked": { + "content-token-publicly-exposed-please-rotate": "", "revoked-label": "" } }, @@ -8499,7 +8586,7 @@ "range-content": { "apply-button": "Zeitbereich anwenden", "default-error": "", - "fiscal-year": "Geschäftsjahr", + "fiscal-year": "", "from-input": "Von", "open-input-calendar": "Kalender öffnen", "range-error": "„Von“ darf nicht nach „Bis“ sein", @@ -8951,6 +9038,7 @@ "loading": "", "no-unknowns": "Keine umbenannten oder fehlenden Variablen gefunden.", "renamed-or-missing-variables": "Umbenannte oder fehlende Variablen", + "tooltip-renamed-or-missing-variables": "", "variable": "Variable" }, "variable-check-indicator": { diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index 72aae2d0ed0..4453aa5c352 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -21,7 +21,9 @@ "permission-list-item": { "inherited": "Heredado de la carpeta", "locked-aria-label": "", - "remove-aria-label": "" + "remove-aria-label": "", + "tooltip-inherited-permission": "", + "tooltip-provisioned-permission": "" }, "permissions": { "add-label": "Añadir un permiso", @@ -42,6 +44,11 @@ }, "actions": { "action-editor": { + "button": { + "style": { + "background-color": "" + } + }, "label-headers": "", "label-url": "", "placeholder-url": "" @@ -70,6 +77,7 @@ "confirm-modal-body-2": "", "get-stage-cell": { "beta": "", + "content-general-availability": "", "deprecated": "", "ga": "" }, @@ -128,6 +136,12 @@ "test-mapping-heading": "Prueba de asignación de usuarios", "test-mapping-run-button": "Ejecutar" }, + "ldap-connection-status": { + "columns": { + "content-connection-is-available": "", + "content-connection-is-not-available": "" + } + }, "ldap-error-box": { "title-connection-error": "" }, @@ -161,6 +175,9 @@ "title": "Sincronización de LDAP" }, "ldap-user-groups": { + "columns": { + "content-no-matching-organizations-found": "" + }, "no-org-found": "" }, "ldap-user-info": { @@ -284,12 +301,14 @@ }, "users-table": { "columns": { + "content-grafana-admin": "", "title-edit-user": "" }, "edit-aria-label": "", "edit-tooltip": "", "last-seen-never": "", - "no-licensed-roles": "Sin asignar" + "no-licensed-roles": "Sin asignar", + "tooltip-assigned-role": "" } }, "alert-labels": { @@ -408,11 +427,9 @@ }, "alert-rule-form": { "action-buttons": { - "delete": "", "edit-yaml": "", - "save-exit": "" - }, - "title-delete-rule": "" + "save": "" + } }, "alert-rule-name-and-metric": { "aria-label-name": "", @@ -444,6 +461,7 @@ "unprocessed-the-alert-is-received": "" }, "alert-state-tag": { + "content-alert-evaluation-is-currently-paused": "", "paused": "" }, "alert-warning": { @@ -540,6 +558,9 @@ "warning-restore-manually": "Estás restaurando manualmente una versión anterior de esta regla de alerta. Revisa los cambios cuidadosamente antes de guardar la definición de la regla.", "warning-restore-manually-title": "Restaurando regla manualmente" }, + "all-matches-indicator": { + "content-this-policy-matches-all-labels": "" + }, "am-root-route-form": { "am-group-description-label": "", "am-group-interval-description": "", @@ -652,6 +673,9 @@ "of": "", "when": "" }, + "clear-filter-button-object-renderer": { + "content-clear-filter": "" + }, "clone-rule-button": { "title-copy": "" }, @@ -778,6 +802,9 @@ "contactPointFilter": { "label": "Punto de contacto" }, + "continue-matching-indicator": { + "content-route-continue-matching-other-policies": "" + }, "copy-to-clipboard": "Copiar «{{label}}» al portapapeles", "create-metadata": { "view-dashboard": "", @@ -806,6 +833,10 @@ "current-selection-dashboard": "", "current-selection-panel": "", "fallback-dashboards-string": "", + "panel-row": { + "content-panel-valid-identifier": "", + "tooltip-alert-tab-support": "" + }, "placeholder-search-dashboard": "", "placeholder-search-panel": "", "select-dashboard-available-panels": "", @@ -827,9 +858,11 @@ "update-datasource": "" }, "declare-incident-button": { + "content-grafana-incident-installed-configured-correctly": "", "declare-incident": "" }, "declare-incident-menu-item": { + "content-grafana-incident-installed-configured-correctly": "", "label-declare-incident": "" }, "delete-rule-modal": { @@ -865,6 +898,9 @@ }, "unknown": "" }, + "disabled-tooltip": { + "content-appear-compatible-datasources": "" + }, "draggable-rules-table": { "evals-to-start-alerting": "Evaluaciones para iniciar alerta", "pending-period": "Periodo pendiente", @@ -895,10 +931,19 @@ "text-loading-template": "", "title-failed-to-fetch-notification-template": "" }, + "editor": { + "edit-alert-rule": "", + "edit-recording-rule": "", + "new-alert-rule": "", + "new-recording-rule": "" + }, "error-modal": { "failed-to-update-your-configuration": "", "title-something-went-wrong": "" }, + "error-summary-button": { + "content-show-all-errors": "" + }, "evaluation-behavior-summary": { "evaluate": "", "label-evaluate": "", @@ -929,7 +974,6 @@ "existing-rule-editor": { "sorry-permission": "", "sorry-this-rule-does-not-exist": "", - "text-loading-rule": "", "title-cannot-edit-rule": "", "title-failed-to-load-rule": "", "title-rule-not-found": "" @@ -1180,6 +1224,9 @@ "group-loader": { "group-load-failed": "" }, + "group-status": { + "content-the-group-is-being-deleted": "" + }, "header": { "tooltip-remove": "" }, @@ -1349,7 +1396,8 @@ "common-labels": "", "error-unable-to-fetch": "", "loading": "", - "title-error-fetching-the-state-history": "" + "title-error-fetching-the-state-history": "", + "tooltip-common-labels": "" }, "manage-permissions": { "button": "", @@ -1442,6 +1490,7 @@ }, "mute-timing-time-range": { "add-another-time-range": "", + "content-this-time-interval-is-disabled": "", "description-time-range": "", "label-end-time": "", "label-start-time": "", @@ -1597,6 +1646,9 @@ "plugin-integrations": { "tailored-apps": "" }, + "plugin-origin-badge": { + "tooltip-managed-by-plugin": "" + }, "policies": { "aria-label-collapse": "", "aria-label-expand": "", @@ -1696,6 +1748,9 @@ "prometheus-consistency-check": { "title-unable-to-check-the-rule-status": "" }, + "provisioned-tooltip": { + "content-provisioned-items-cannot-edited": "" + }, "provisioning": { "badge-tooltip-provenance": "Este recurso se ha aprovisionado a través de {{provenance}} y no se puede editar mediante la interfaz de usuario", "badge-tooltip-standard": "Este recurso se ha aprovisionado y no se puede editar a través de la interfaz de usuario", @@ -1931,14 +1986,14 @@ }, "threshold": { "recovery": { - "stop-alerting-above": "Dejar de alertar por encima de", - "stop-alerting-bellow": "Dejar de alertar por debajo de", - "stop-alerting-equal": "Dejar de alertar cuando sea igual a", - "stop-alerting-inside-range": "Dejar de alertar cuando esté dentro del rango", - "stop-alerting-less": "Dejar de alertar cuando sea menor que", - "stop-alerting-more": "Dejar de alertar cuando sea mayor que", - "stop-alerting-not-equal": "Dejar de alertar cuando no sea igual a", - "stop-alerting-outside-range": "Dejar de alertar cuando esté fuera del rango", + "stop-alerting-above": "", + "stop-alerting-bellow": "", + "stop-alerting-equal": "", + "stop-alerting-inside-range": "", + "stop-alerting-less": "", + "stop-alerting-more": "", + "stop-alerting-not-equal": "", + "stop-alerting-outside-range": "", "title": "Umbral de recuperación personalizado" } } @@ -2038,7 +2093,10 @@ "for": "", "na": "", "paused": "En pausa", - "recording-rule": "Registrando regla" + "recording-rule": "Registrando regla", + "recording-rule-state": { + "content-recording-rule-evaluation-is-currently-paused": "" + } }, "rule-stats": { "firing": "", @@ -2282,6 +2340,8 @@ "refresh": "" }, "template-row": { + "content-templatefiles": "", + "content-templates": "", "tooltip-copy-template-group": "", "tooltip-delete-template-group": "", "tooltip-edit-template-group": "", @@ -2305,7 +2365,7 @@ }, "misconfigured-badge-text": "Configuración incorrecta", "misconfigured-warning": "La configuración de esta plantilla es incorrecta.", - "misconfigured-warning-details": "Las plantillas deben definirse en las secciones <1> y <4> de la configuración de Alertmanager." + "misconfigured-warning-details": "" }, "templates-picker": { "button-edit": "", @@ -2350,6 +2410,10 @@ "type-selector-button": { "add-expression": "" }, + "unknown-contact-point-details": { + "unknown-contact-point": "", + "unknown-contact-point-tooltip": "" + }, "unknown-rule-list-item": { "title-unknown-rule-type": "" }, @@ -2450,6 +2514,7 @@ }, "api-keys-table": { "aria-label-delete-api-key": "", + "content-this-api-key-has-expired": "", "expires": "", "last-used-at": "", "migrate-to-service-account": "", @@ -4154,7 +4219,9 @@ }, "variable-check-indicator": { "aria-label-variable-referenced-dashboard": "", - "aria-label-variable-referenced-other-variables-dashboard": "" + "aria-label-variable-referenced-other-variables-dashboard": "", + "content-variable-not-referenced-other-variables-dashboard": "", + "content-variable-referenced-other-variables-dashboard": "" }, "variable-editor-form": { "aria-label-variable-editor-form": "", @@ -4214,6 +4281,7 @@ }, "versions-history-buttons": { "compare-versions": "", + "content-select-two-versions-to-start-comparing": "", "show-more-versions": "" }, "visualization-button": { @@ -4567,6 +4635,7 @@ } }, "correlation-editor-mode-bar": { + "content-correlations-editor-explore-experimental-feature": "", "exit-correlation-editor": "", "save": "" }, @@ -4724,6 +4793,9 @@ "label-select-query": "", "tooltip-select-query-visualize-table": "" }, + "logs-volume-panel": { + "content-streaming": "" + }, "logs-volume-panel-list": { "label-reload-log-volume": "", "loading": "", @@ -4923,6 +4995,7 @@ "label-tags": "", "placeholder-all-service-names": "", "placeholder-all-span-names": "", + "tooltip-collapse": "", "tooltip-duration": "", "tooltip-tags": "" }, @@ -5001,7 +5074,8 @@ "label-show-paths": "" }, "trace-view": { - "no-data": "" + "no-data": "", + "tooltip-copy-icon": "" }, "trace-view-container": { "title-trace": "" @@ -5115,7 +5189,8 @@ "default-password-alert": "", "new-password-label": "", "skip-button": "Omitir", - "submit-button": "Enviar" + "submit-button": "Enviar", + "tooltip-skip-button": "" }, "contact-admin": "¿Has olvidado tu nombre de usuario o correo electrónico? Ponte en contacto con el administrador de Grafana.", "email-sent": "Se ha enviado un correo electrónico con un enlace de restablecimiento a la dirección de correo electrónico. Lo recibirás en breve.", @@ -5172,7 +5247,8 @@ "action-editor": { "button": { "confirm": "Confirmar", - "confirm-action": "Confirmar acción" + "confirm-action": "Confirmar acción", + "style": "" }, "inline": { "add-action": "Añadir acción", @@ -5379,7 +5455,11 @@ "previous-page": "página anterior" }, "panel-chrome": { - "aria-label-toggle-collapse": "" + "aria-label-toggle-collapse": "", + "tooltip-cancel": "", + "tooltip-cancel-loading": "", + "tooltip-stop-streaming": "", + "tooltip-streaming": "" }, "panel-menu": { "label": "Menú para el panel {{ title }}", @@ -5961,7 +6041,8 @@ "log-row-message": { "ellipsis": "… ", "more": "más", - "see-details": "Ver detalles del registro" + "see-details": "Ver detalles del registro", + "tooltip-error": "" }, "log-rows": { "disable-popover": { @@ -6938,7 +7019,8 @@ "label-search": "", "label-state": "", "label-type": "", - "subtitle": "" + "subtitle": "", + "tooltip-filter-disabled": "" }, "catalog": { "no-updates-available": "No hay actualizaciones disponibles", @@ -6947,7 +7029,7 @@ "available-header": "Disponible", "button": "Actualizar todo ({{length}})", "cloud-update-message": "* Los complementos pueden tardar unos minutos en estar disponibles para usarlos.", - "error": "Error al actualizar el complemento:", + "error": "", "error-status-text": "error: consulta los mensajes de error", "header": "Los siguientes complementos tienen una actualización disponible", "installed-header": "Instalada", @@ -7157,6 +7239,9 @@ "feature-toggles-age": { "enable-in-config": "" }, + "input-suffix": { + "content-login-details-locked-because-managed-another": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -8090,10 +8175,12 @@ "title-disable-service-account": "" }, "token-expiration": { + "content-this-token-has-expired": "", "expired-label": "", "never": "" }, "token-revoked": { + "content-token-publicly-exposed-please-rotate": "", "revoked-label": "" } }, @@ -8499,7 +8586,7 @@ "range-content": { "apply-button": "Aplicar intervalo de tiempo", "default-error": "", - "fiscal-year": "Ejercicio fiscal", + "fiscal-year": "", "from-input": "Desde", "open-input-calendar": "Abrir calendario", "range-error": "«Desde» no puede ser posterior a «hasta»", @@ -8951,6 +9038,7 @@ "loading": "", "no-unknowns": "No se han encontrado variables renombradas o que falten.", "renamed-or-missing-variables": "Variables renombradas o que faltan", + "tooltip-renamed-or-missing-variables": "", "variable": "Variable" }, "variable-check-indicator": { diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index c1affc0b43b..98bf5275693 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -21,7 +21,9 @@ "permission-list-item": { "inherited": "Hérité du dossier", "locked-aria-label": "", - "remove-aria-label": "" + "remove-aria-label": "", + "tooltip-inherited-permission": "", + "tooltip-provisioned-permission": "" }, "permissions": { "add-label": "Ajouter une autorisation", @@ -42,6 +44,11 @@ }, "actions": { "action-editor": { + "button": { + "style": { + "background-color": "" + } + }, "label-headers": "", "label-url": "", "placeholder-url": "" @@ -70,6 +77,7 @@ "confirm-modal-body-2": "", "get-stage-cell": { "beta": "", + "content-general-availability": "", "deprecated": "", "ga": "" }, @@ -128,6 +136,12 @@ "test-mapping-heading": "Tester le mappage des utilisateurs", "test-mapping-run-button": "Exécuter" }, + "ldap-connection-status": { + "columns": { + "content-connection-is-available": "", + "content-connection-is-not-available": "" + } + }, "ldap-error-box": { "title-connection-error": "" }, @@ -161,6 +175,9 @@ "title": "Synchronisation LDAP" }, "ldap-user-groups": { + "columns": { + "content-no-matching-organizations-found": "" + }, "no-org-found": "" }, "ldap-user-info": { @@ -284,12 +301,14 @@ }, "users-table": { "columns": { + "content-grafana-admin": "", "title-edit-user": "" }, "edit-aria-label": "", "edit-tooltip": "", "last-seen-never": "Jamais", - "no-licensed-roles": "Non attribué" + "no-licensed-roles": "Non attribué", + "tooltip-assigned-role": "" } }, "alert-labels": { @@ -408,11 +427,9 @@ }, "alert-rule-form": { "action-buttons": { - "delete": "", "edit-yaml": "", - "save-exit": "" - }, - "title-delete-rule": "" + "save": "" + } }, "alert-rule-name-and-metric": { "aria-label-name": "", @@ -444,6 +461,7 @@ "unprocessed-the-alert-is-received": "" }, "alert-state-tag": { + "content-alert-evaluation-is-currently-paused": "", "paused": "" }, "alert-warning": { @@ -540,6 +558,9 @@ "warning-restore-manually": "Vous restaurez manuellement une ancienne version de cette règle d'alerte. Veuillez examiner attentivement les modifications avant d'enregistrer la définition de la règle.", "warning-restore-manually-title": "Restauration manuelle de la règle" }, + "all-matches-indicator": { + "content-this-policy-matches-all-labels": "" + }, "am-root-route-form": { "am-group-description-label": "", "am-group-interval-description": "", @@ -652,6 +673,9 @@ "of": "", "when": "" }, + "clear-filter-button-object-renderer": { + "content-clear-filter": "" + }, "clone-rule-button": { "title-copy": "" }, @@ -778,6 +802,9 @@ "contactPointFilter": { "label": "Point de contact" }, + "continue-matching-indicator": { + "content-route-continue-matching-other-policies": "" + }, "copy-to-clipboard": "Copier « {{label}} » dans le presse-papiers", "create-metadata": { "view-dashboard": "", @@ -806,6 +833,10 @@ "current-selection-dashboard": "", "current-selection-panel": "", "fallback-dashboards-string": "", + "panel-row": { + "content-panel-valid-identifier": "", + "tooltip-alert-tab-support": "" + }, "placeholder-search-dashboard": "", "placeholder-search-panel": "", "select-dashboard-available-panels": "", @@ -827,9 +858,11 @@ "update-datasource": "" }, "declare-incident-button": { + "content-grafana-incident-installed-configured-correctly": "", "declare-incident": "" }, "declare-incident-menu-item": { + "content-grafana-incident-installed-configured-correctly": "", "label-declare-incident": "" }, "delete-rule-modal": { @@ -865,6 +898,9 @@ }, "unknown": "" }, + "disabled-tooltip": { + "content-appear-compatible-datasources": "" + }, "draggable-rules-table": { "evals-to-start-alerting": "", "pending-period": "", @@ -895,10 +931,19 @@ "text-loading-template": "", "title-failed-to-fetch-notification-template": "" }, + "editor": { + "edit-alert-rule": "", + "edit-recording-rule": "", + "new-alert-rule": "", + "new-recording-rule": "" + }, "error-modal": { "failed-to-update-your-configuration": "", "title-something-went-wrong": "" }, + "error-summary-button": { + "content-show-all-errors": "" + }, "evaluation-behavior-summary": { "evaluate": "", "label-evaluate": "", @@ -929,7 +974,6 @@ "existing-rule-editor": { "sorry-permission": "", "sorry-this-rule-does-not-exist": "", - "text-loading-rule": "", "title-cannot-edit-rule": "", "title-failed-to-load-rule": "", "title-rule-not-found": "" @@ -1180,6 +1224,9 @@ "group-loader": { "group-load-failed": "" }, + "group-status": { + "content-the-group-is-being-deleted": "" + }, "header": { "tooltip-remove": "" }, @@ -1349,7 +1396,8 @@ "common-labels": "", "error-unable-to-fetch": "", "loading": "", - "title-error-fetching-the-state-history": "" + "title-error-fetching-the-state-history": "", + "tooltip-common-labels": "" }, "manage-permissions": { "button": "Gérer les autorisations", @@ -1442,6 +1490,7 @@ }, "mute-timing-time-range": { "add-another-time-range": "", + "content-this-time-interval-is-disabled": "", "description-time-range": "", "label-end-time": "", "label-start-time": "", @@ -1597,6 +1646,9 @@ "plugin-integrations": { "tailored-apps": "" }, + "plugin-origin-badge": { + "tooltip-managed-by-plugin": "" + }, "policies": { "aria-label-collapse": "", "aria-label-expand": "", @@ -1696,6 +1748,9 @@ "prometheus-consistency-check": { "title-unable-to-check-the-rule-status": "" }, + "provisioned-tooltip": { + "content-provisioned-items-cannot-edited": "" + }, "provisioning": { "badge-tooltip-provenance": "Cette ressource a été mise en service via {{provenance}} et ne peut pas être modifiée via l'interface utilisateur", "badge-tooltip-standard": "Cette ressource a été mise en service et ne peut pas être modifiée via l'interface utilisateur", @@ -1931,14 +1986,14 @@ }, "threshold": { "recovery": { - "stop-alerting-above": "Arrêter d'alerter lorsque au-dessus de", - "stop-alerting-bellow": "Arrêter d'alerter lorsque en dessous de", - "stop-alerting-equal": "Arrêter d'alerter lorsque égal à", - "stop-alerting-inside-range": "Arrêter d'alerter lorsque dans la plage", - "stop-alerting-less": "Arrêter d'alerter lorsque inférieur à", - "stop-alerting-more": "Arrêter d'alerter lorsque supérieur à", - "stop-alerting-not-equal": "Arrêter d'alerter lorsque non égal à", - "stop-alerting-outside-range": "Arrêter d'alerter lorsque hors de la plage", + "stop-alerting-above": "", + "stop-alerting-bellow": "", + "stop-alerting-equal": "", + "stop-alerting-inside-range": "", + "stop-alerting-less": "", + "stop-alerting-more": "", + "stop-alerting-not-equal": "", + "stop-alerting-outside-range": "", "title": "Seuil de récupération personnalisé" } } @@ -2038,7 +2093,10 @@ "for": "", "na": "", "paused": "En pause", - "recording-rule": "Règle d'enregistrement" + "recording-rule": "Règle d'enregistrement", + "recording-rule-state": { + "content-recording-rule-evaluation-is-currently-paused": "" + } }, "rule-stats": { "firing": "", @@ -2282,6 +2340,8 @@ "refresh": "" }, "template-row": { + "content-templatefiles": "", + "content-templates": "", "tooltip-copy-template-group": "", "tooltip-delete-template-group": "", "tooltip-edit-template-group": "", @@ -2305,7 +2365,7 @@ }, "misconfigured-badge-text": "Mal configuré", "misconfigured-warning": "Ce modèle est mal configuré.", - "misconfigured-warning-details": "Les modèles doivent être définis dans les sections <1> et <4> de la configuration de votre Alertmanager." + "misconfigured-warning-details": "" }, "templates-picker": { "button-edit": "", @@ -2350,6 +2410,10 @@ "type-selector-button": { "add-expression": "" }, + "unknown-contact-point-details": { + "unknown-contact-point": "", + "unknown-contact-point-tooltip": "" + }, "unknown-rule-list-item": { "title-unknown-rule-type": "" }, @@ -2450,6 +2514,7 @@ }, "api-keys-table": { "aria-label-delete-api-key": "", + "content-this-api-key-has-expired": "", "expires": "", "last-used-at": "", "migrate-to-service-account": "", @@ -4154,7 +4219,9 @@ }, "variable-check-indicator": { "aria-label-variable-referenced-dashboard": "", - "aria-label-variable-referenced-other-variables-dashboard": "" + "aria-label-variable-referenced-other-variables-dashboard": "", + "content-variable-not-referenced-other-variables-dashboard": "", + "content-variable-referenced-other-variables-dashboard": "" }, "variable-editor-form": { "aria-label-variable-editor-form": "", @@ -4214,6 +4281,7 @@ }, "versions-history-buttons": { "compare-versions": "", + "content-select-two-versions-to-start-comparing": "", "show-more-versions": "" }, "visualization-button": { @@ -4567,6 +4635,7 @@ } }, "correlation-editor-mode-bar": { + "content-correlations-editor-explore-experimental-feature": "", "exit-correlation-editor": "", "save": "" }, @@ -4724,6 +4793,9 @@ "label-select-query": "", "tooltip-select-query-visualize-table": "" }, + "logs-volume-panel": { + "content-streaming": "" + }, "logs-volume-panel-list": { "label-reload-log-volume": "", "loading": "", @@ -4923,6 +4995,7 @@ "label-tags": "", "placeholder-all-service-names": "", "placeholder-all-span-names": "", + "tooltip-collapse": "", "tooltip-duration": "", "tooltip-tags": "" }, @@ -5001,7 +5074,8 @@ "label-show-paths": "" }, "trace-view": { - "no-data": "" + "no-data": "", + "tooltip-copy-icon": "" }, "trace-view-container": { "title-trace": "" @@ -5115,7 +5189,8 @@ "default-password-alert": "", "new-password-label": "", "skip-button": "Passer", - "submit-button": "Envoyer" + "submit-button": "Envoyer", + "tooltip-skip-button": "" }, "contact-admin": "Avez-vous oublié votre nom d'utilisateur ou votre adresse e-mail ? Contactez votre administrateur Grafana.", "email-sent": "Un e-mail contenant un lien de réinitialisation a été envoyé à l'adresse e-mail. Vous devriez le recevoir sous peu.", @@ -5172,7 +5247,8 @@ "action-editor": { "button": { "confirm": "Confirmer", - "confirm-action": "Confirmer l'action" + "confirm-action": "Confirmer l'action", + "style": "" }, "inline": { "add-action": "Ajouter l'action", @@ -5379,7 +5455,11 @@ "previous-page": "page précédente" }, "panel-chrome": { - "aria-label-toggle-collapse": "" + "aria-label-toggle-collapse": "", + "tooltip-cancel": "", + "tooltip-cancel-loading": "", + "tooltip-stop-streaming": "", + "tooltip-streaming": "" }, "panel-menu": { "label": "Menu pour le panneau {{ title }}", @@ -5961,7 +6041,8 @@ "log-row-message": { "ellipsis": "… ", "more": "plus", - "see-details": "Voir les détails du journal" + "see-details": "Voir les détails du journal", + "tooltip-error": "" }, "log-rows": { "disable-popover": { @@ -6938,7 +7019,8 @@ "label-search": "", "label-state": "", "label-type": "", - "subtitle": "" + "subtitle": "", + "tooltip-filter-disabled": "" }, "catalog": { "no-updates-available": "Aucune mise à jour disponible", @@ -6947,7 +7029,7 @@ "available-header": "Disponible", "button": "Tout mettre à jour ({{length}})", "cloud-update-message": "* Cela peut prendre quelques minutes pour que les plugins soient disponibles.", - "error": "Erreur lors de la mise à jour du plugin :", + "error": "", "error-status-text": "échec – voir les messages d'erreur", "header": "Les plugins suivants ont une mise à jour disponible", "installed-header": "Installé", @@ -7157,6 +7239,9 @@ "feature-toggles-age": { "enable-in-config": "" }, + "input-suffix": { + "content-login-details-locked-because-managed-another": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -8090,10 +8175,12 @@ "title-disable-service-account": "" }, "token-expiration": { + "content-this-token-has-expired": "", "expired-label": "", "never": "" }, "token-revoked": { + "content-token-publicly-exposed-please-rotate": "", "revoked-label": "" } }, @@ -8499,7 +8586,7 @@ "range-content": { "apply-button": "Appliquer la plage temporelle", "default-error": "", - "fiscal-year": "Exercice fiscal", + "fiscal-year": "", "from-input": "De", "open-input-calendar": "Ouvrir le calendrier", "range-error": "« De » ne peut pas être ultérieur à « À »", @@ -8951,6 +9038,7 @@ "loading": "Chargement en cours...", "no-unknowns": "Aucune variable renommée ou manquante n'a été trouvée.", "renamed-or-missing-variables": "Variables renommées ou manquantes", + "tooltip-renamed-or-missing-variables": "", "variable": "Variable" }, "variable-check-indicator": { diff --git a/public/locales/hu-HU/grafana.json b/public/locales/hu-HU/grafana.json index 389f3e8a2ba..55ac384fb1a 100644 --- a/public/locales/hu-HU/grafana.json +++ b/public/locales/hu-HU/grafana.json @@ -21,7 +21,9 @@ "permission-list-item": { "inherited": "Mappából örökölve", "locked-aria-label": "", - "remove-aria-label": "" + "remove-aria-label": "", + "tooltip-inherited-permission": "", + "tooltip-provisioned-permission": "" }, "permissions": { "add-label": "Engedély hozzáadása", @@ -42,6 +44,11 @@ }, "actions": { "action-editor": { + "button": { + "style": { + "background-color": "" + } + }, "label-headers": "", "label-url": "", "placeholder-url": "" @@ -70,6 +77,7 @@ "confirm-modal-body-2": "", "get-stage-cell": { "beta": "", + "content-general-availability": "", "deprecated": "", "ga": "" }, @@ -128,6 +136,12 @@ "test-mapping-heading": "Felhasználói hozzárendelés tesztelése", "test-mapping-run-button": "Futtatás" }, + "ldap-connection-status": { + "columns": { + "content-connection-is-available": "", + "content-connection-is-not-available": "" + } + }, "ldap-error-box": { "title-connection-error": "" }, @@ -161,6 +175,9 @@ "title": "LDAP-szinkronizálás" }, "ldap-user-groups": { + "columns": { + "content-no-matching-organizations-found": "" + }, "no-org-found": "" }, "ldap-user-info": { @@ -284,12 +301,14 @@ }, "users-table": { "columns": { + "content-grafana-admin": "", "title-edit-user": "" }, "edit-aria-label": "", "edit-tooltip": "", "last-seen-never": "Soha", - "no-licensed-roles": "Nincs kiosztva" + "no-licensed-roles": "Nincs kiosztva", + "tooltip-assigned-role": "" } }, "alert-labels": { @@ -408,11 +427,9 @@ }, "alert-rule-form": { "action-buttons": { - "delete": "", "edit-yaml": "", - "save-exit": "" - }, - "title-delete-rule": "" + "save": "" + } }, "alert-rule-name-and-metric": { "aria-label-name": "", @@ -444,6 +461,7 @@ "unprocessed-the-alert-is-received": "" }, "alert-state-tag": { + "content-alert-evaluation-is-currently-paused": "", "paused": "" }, "alert-warning": { @@ -540,6 +558,9 @@ "warning-restore-manually": "Manuálisan állítja vissza ennek a riasztási szabálynak egy régi verzióját. Kérjük, figyelmesen tekintse át a módosításokat a szabálydefiníció mentése előtt.", "warning-restore-manually-title": "Szabály manuális visszaállítása" }, + "all-matches-indicator": { + "content-this-policy-matches-all-labels": "" + }, "am-root-route-form": { "am-group-description-label": "", "am-group-interval-description": "", @@ -652,6 +673,9 @@ "of": "", "when": "" }, + "clear-filter-button-object-renderer": { + "content-clear-filter": "" + }, "clone-rule-button": { "title-copy": "" }, @@ -778,6 +802,9 @@ "contactPointFilter": { "label": "Kapcsolattartási pont" }, + "continue-matching-indicator": { + "content-route-continue-matching-other-policies": "" + }, "copy-to-clipboard": "„{{label}}” másolása a vágólapra", "create-metadata": { "view-dashboard": "", @@ -806,6 +833,10 @@ "current-selection-dashboard": "", "current-selection-panel": "", "fallback-dashboards-string": "", + "panel-row": { + "content-panel-valid-identifier": "", + "tooltip-alert-tab-support": "" + }, "placeholder-search-dashboard": "", "placeholder-search-panel": "", "select-dashboard-available-panels": "", @@ -827,9 +858,11 @@ "update-datasource": "" }, "declare-incident-button": { + "content-grafana-incident-installed-configured-correctly": "", "declare-incident": "" }, "declare-incident-menu-item": { + "content-grafana-incident-installed-configured-correctly": "", "label-declare-incident": "" }, "delete-rule-modal": { @@ -865,6 +898,9 @@ }, "unknown": "" }, + "disabled-tooltip": { + "content-appear-compatible-datasources": "" + }, "draggable-rules-table": { "evals-to-start-alerting": "Értékelések a riasztás megkezdéséhez", "pending-period": "Függőben lévő időszak", @@ -895,10 +931,19 @@ "text-loading-template": "", "title-failed-to-fetch-notification-template": "" }, + "editor": { + "edit-alert-rule": "", + "edit-recording-rule": "", + "new-alert-rule": "", + "new-recording-rule": "" + }, "error-modal": { "failed-to-update-your-configuration": "", "title-something-went-wrong": "" }, + "error-summary-button": { + "content-show-all-errors": "" + }, "evaluation-behavior-summary": { "evaluate": "", "label-evaluate": "", @@ -929,7 +974,6 @@ "existing-rule-editor": { "sorry-permission": "", "sorry-this-rule-does-not-exist": "", - "text-loading-rule": "", "title-cannot-edit-rule": "", "title-failed-to-load-rule": "", "title-rule-not-found": "" @@ -1180,6 +1224,9 @@ "group-loader": { "group-load-failed": "" }, + "group-status": { + "content-the-group-is-being-deleted": "" + }, "header": { "tooltip-remove": "" }, @@ -1349,7 +1396,8 @@ "common-labels": "", "error-unable-to-fetch": "", "loading": "", - "title-error-fetching-the-state-history": "" + "title-error-fetching-the-state-history": "", + "tooltip-common-labels": "" }, "manage-permissions": { "button": "Engedélyek kezelése", @@ -1442,6 +1490,7 @@ }, "mute-timing-time-range": { "add-another-time-range": "", + "content-this-time-interval-is-disabled": "", "description-time-range": "", "label-end-time": "", "label-start-time": "", @@ -1597,6 +1646,9 @@ "plugin-integrations": { "tailored-apps": "" }, + "plugin-origin-badge": { + "tooltip-managed-by-plugin": "" + }, "policies": { "aria-label-collapse": "", "aria-label-expand": "", @@ -1696,6 +1748,9 @@ "prometheus-consistency-check": { "title-unable-to-check-the-rule-status": "" }, + "provisioned-tooltip": { + "content-provisioned-items-cannot-edited": "" + }, "provisioning": { "badge-tooltip-provenance": "Ez az erőforrás ki van építve ({{provenance}}), és nem szerkeszthető a felhasználói felületen keresztül", "badge-tooltip-standard": "Ez az erőforrás ki van építve, és nem szerkeszthető a felhasználói felületen keresztül", @@ -1931,14 +1986,14 @@ }, "threshold": { "recovery": { - "stop-alerting-above": "Riasztás leállítása e felett:", - "stop-alerting-bellow": "Riasztás leállítása ez alatt:", - "stop-alerting-equal": "Riasztás leállítása, ha egyenlő ezzel:", - "stop-alerting-inside-range": "Riasztás leállítása tartományon belül", - "stop-alerting-less": "Riasztás leállítása, ha kevesebb mint", - "stop-alerting-more": "Riasztás leállítása, ha több mint", - "stop-alerting-not-equal": "Riasztás leállítása, ha nem egyenlő ezzel:", - "stop-alerting-outside-range": "Riasztás leállítása tartományon kívül", + "stop-alerting-above": "", + "stop-alerting-bellow": "", + "stop-alerting-equal": "", + "stop-alerting-inside-range": "", + "stop-alerting-less": "", + "stop-alerting-more": "", + "stop-alerting-not-equal": "", + "stop-alerting-outside-range": "", "title": "Egyedi helyreállítási küszöbérték" } } @@ -2038,7 +2093,10 @@ "for": "", "na": "", "paused": "Szüneteltetett", - "recording-rule": "Felvételi szabály" + "recording-rule": "Felvételi szabály", + "recording-rule-state": { + "content-recording-rule-evaluation-is-currently-paused": "" + } }, "rule-stats": { "firing": "", @@ -2282,6 +2340,8 @@ "refresh": "" }, "template-row": { + "content-templatefiles": "", + "content-templates": "", "tooltip-copy-template-group": "", "tooltip-delete-template-group": "", "tooltip-edit-template-group": "", @@ -2305,7 +2365,7 @@ }, "misconfigured-badge-text": "Hibás konfiguráció", "misconfigured-warning": "Ez a sablon rosszul van konfigurálva.", - "misconfigured-warning-details": "A sablonokat a riasztáskezelő konfigurációjának <1> és <4> szakaszában is meg kell határozni." + "misconfigured-warning-details": "" }, "templates-picker": { "button-edit": "", @@ -2350,6 +2410,10 @@ "type-selector-button": { "add-expression": "" }, + "unknown-contact-point-details": { + "unknown-contact-point": "", + "unknown-contact-point-tooltip": "" + }, "unknown-rule-list-item": { "title-unknown-rule-type": "" }, @@ -2450,6 +2514,7 @@ }, "api-keys-table": { "aria-label-delete-api-key": "", + "content-this-api-key-has-expired": "", "expires": "", "last-used-at": "", "migrate-to-service-account": "", @@ -4154,7 +4219,9 @@ }, "variable-check-indicator": { "aria-label-variable-referenced-dashboard": "", - "aria-label-variable-referenced-other-variables-dashboard": "" + "aria-label-variable-referenced-other-variables-dashboard": "", + "content-variable-not-referenced-other-variables-dashboard": "", + "content-variable-referenced-other-variables-dashboard": "" }, "variable-editor-form": { "aria-label-variable-editor-form": "", @@ -4214,6 +4281,7 @@ }, "versions-history-buttons": { "compare-versions": "", + "content-select-two-versions-to-start-comparing": "", "show-more-versions": "" }, "visualization-button": { @@ -4567,6 +4635,7 @@ } }, "correlation-editor-mode-bar": { + "content-correlations-editor-explore-experimental-feature": "", "exit-correlation-editor": "", "save": "" }, @@ -4724,6 +4793,9 @@ "label-select-query": "", "tooltip-select-query-visualize-table": "" }, + "logs-volume-panel": { + "content-streaming": "" + }, "logs-volume-panel-list": { "label-reload-log-volume": "", "loading": "", @@ -4923,6 +4995,7 @@ "label-tags": "", "placeholder-all-service-names": "", "placeholder-all-span-names": "", + "tooltip-collapse": "", "tooltip-duration": "", "tooltip-tags": "" }, @@ -5001,7 +5074,8 @@ "label-show-paths": "" }, "trace-view": { - "no-data": "" + "no-data": "", + "tooltip-copy-icon": "" }, "trace-view-container": { "title-trace": "" @@ -5115,7 +5189,8 @@ "default-password-alert": "", "new-password-label": "", "skip-button": "Kihagyás", - "submit-button": "Küldés" + "submit-button": "Küldés", + "tooltip-skip-button": "" }, "contact-admin": "Elfelejtette a felhasználónevét vagy az e-mail-címét? Vegye fel a kapcsolatot Grafana-rendszergazdájával.", "email-sent": "A visszaállítási hivatkozást tartalmazó e-mailt elküldtük az e-mail-címre. Hamarosan meg kell kapnia.", @@ -5172,7 +5247,8 @@ "action-editor": { "button": { "confirm": "Megerősítés", - "confirm-action": "Művelet megerősítése" + "confirm-action": "Művelet megerősítése", + "style": "" }, "inline": { "add-action": "Művelet hozzáadása", @@ -5379,7 +5455,11 @@ "previous-page": "előző oldal" }, "panel-chrome": { - "aria-label-toggle-collapse": "" + "aria-label-toggle-collapse": "", + "tooltip-cancel": "", + "tooltip-cancel-loading": "", + "tooltip-stop-streaming": "", + "tooltip-streaming": "" }, "panel-menu": { "label": "{{ title }} panel menüje", @@ -5961,7 +6041,8 @@ "log-row-message": { "ellipsis": "… ", "more": "továbbiak", - "see-details": "A napló részleteinek megtekintése" + "see-details": "A napló részleteinek megtekintése", + "tooltip-error": "" }, "log-rows": { "disable-popover": { @@ -6938,7 +7019,8 @@ "label-search": "", "label-state": "", "label-type": "", - "subtitle": "" + "subtitle": "", + "tooltip-filter-disabled": "" }, "catalog": { "no-updates-available": "Nem érhetők el frissítések", @@ -6947,7 +7029,7 @@ "available-header": "Elérhető", "button": "Összes frissítése ({{length}})", "cloud-update-message": "*Eltarthat néhány percig, amíg a bővítmények használatra készen állnak.", - "error": "Hiba a bővítmény frissítésekor:", + "error": "", "error-status-text": "sikertelen – lásd a hibaüzeneteket", "header": "A következő bővítményekhez érhető el frissítés", "installed-header": "Telepítve", @@ -7157,6 +7239,9 @@ "feature-toggles-age": { "enable-in-config": "" }, + "input-suffix": { + "content-login-details-locked-because-managed-another": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -8090,10 +8175,12 @@ "title-disable-service-account": "" }, "token-expiration": { + "content-this-token-has-expired": "", "expired-label": "", "never": "" }, "token-revoked": { + "content-token-publicly-exposed-please-rotate": "", "revoked-label": "" } }, @@ -8499,7 +8586,7 @@ "range-content": { "apply-button": "Időtartomány alkalmazása", "default-error": "", - "fiscal-year": "Pénzügyi év", + "fiscal-year": "", "from-input": "Kezdete", "open-input-calendar": "Naptár megnyitása", "range-error": "A „Kezdete” nem lehet a „Vége” után", @@ -8951,6 +9038,7 @@ "loading": "Betöltés...", "no-unknowns": "Nem található átnevezett vagy hiányzó változó.", "renamed-or-missing-variables": "Átnevezett vagy hiányzó változók", + "tooltip-renamed-or-missing-variables": "", "variable": "Változó" }, "variable-check-indicator": { diff --git a/public/locales/id-ID/grafana.json b/public/locales/id-ID/grafana.json index 70da04320eb..1dc4681f043 100644 --- a/public/locales/id-ID/grafana.json +++ b/public/locales/id-ID/grafana.json @@ -21,7 +21,9 @@ "permission-list-item": { "inherited": "Diwarisi dari folder", "locked-aria-label": "", - "remove-aria-label": "" + "remove-aria-label": "", + "tooltip-inherited-permission": "", + "tooltip-provisioned-permission": "" }, "permissions": { "add-label": "Tambahkan izin", @@ -42,6 +44,11 @@ }, "actions": { "action-editor": { + "button": { + "style": { + "background-color": "" + } + }, "label-headers": "", "label-url": "", "placeholder-url": "" @@ -70,6 +77,7 @@ "confirm-modal-body-2": "", "get-stage-cell": { "beta": "", + "content-general-availability": "", "deprecated": "", "ga": "" }, @@ -128,6 +136,12 @@ "test-mapping-heading": "Uji pemetaan pengguna", "test-mapping-run-button": "Jalankan" }, + "ldap-connection-status": { + "columns": { + "content-connection-is-available": "", + "content-connection-is-not-available": "" + } + }, "ldap-error-box": { "title-connection-error": "" }, @@ -161,6 +175,9 @@ "title": "Sinkronisasi LDAP" }, "ldap-user-groups": { + "columns": { + "content-no-matching-organizations-found": "" + }, "no-org-found": "" }, "ldap-user-info": { @@ -284,12 +301,14 @@ }, "users-table": { "columns": { + "content-grafana-admin": "", "title-edit-user": "" }, "edit-aria-label": "", "edit-tooltip": "", "last-seen-never": "Tidak pernah", - "no-licensed-roles": "Tidak ditugaskan" + "no-licensed-roles": "Tidak ditugaskan", + "tooltip-assigned-role": "" } }, "alert-labels": { @@ -404,11 +423,9 @@ }, "alert-rule-form": { "action-buttons": { - "delete": "", "edit-yaml": "", - "save-exit": "" - }, - "title-delete-rule": "" + "save": "" + } }, "alert-rule-name-and-metric": { "aria-label-name": "", @@ -440,6 +457,7 @@ "unprocessed-the-alert-is-received": "" }, "alert-state-tag": { + "content-alert-evaluation-is-currently-paused": "", "paused": "" }, "alert-warning": { @@ -536,6 +554,9 @@ "warning-restore-manually": "Anda secara manual memulihkan versi lama dari aturan peringatan ini. Tinjau perubahan dengan saksama sebelum menyimpan definisi aturan.", "warning-restore-manually-title": "Memulihkan aturan secara manual" }, + "all-matches-indicator": { + "content-this-policy-matches-all-labels": "" + }, "am-root-route-form": { "am-group-description-label": "", "am-group-interval-description": "", @@ -648,6 +669,9 @@ "of": "", "when": "" }, + "clear-filter-button-object-renderer": { + "content-clear-filter": "" + }, "clone-rule-button": { "title-copy": "" }, @@ -772,6 +796,9 @@ "contactPointFilter": { "label": "Titik kontak" }, + "continue-matching-indicator": { + "content-route-continue-matching-other-policies": "" + }, "copy-to-clipboard": "Salin \"{{label}}\" ke papan klip", "create-metadata": { "view-dashboard": "", @@ -800,6 +827,10 @@ "current-selection-dashboard": "", "current-selection-panel": "", "fallback-dashboards-string": "", + "panel-row": { + "content-panel-valid-identifier": "", + "tooltip-alert-tab-support": "" + }, "placeholder-search-dashboard": "", "placeholder-search-panel": "", "select-dashboard-available-panels": "", @@ -821,9 +852,11 @@ "update-datasource": "" }, "declare-incident-button": { + "content-grafana-incident-installed-configured-correctly": "", "declare-incident": "" }, "declare-incident-menu-item": { + "content-grafana-incident-installed-configured-correctly": "", "label-declare-incident": "" }, "delete-rule-modal": { @@ -859,6 +892,9 @@ }, "unknown": "" }, + "disabled-tooltip": { + "content-appear-compatible-datasources": "" + }, "draggable-rules-table": { "evals-to-start-alerting": "Evaluasi untuk mulai memperingatkan", "pending-period": "Periode tertunda", @@ -889,10 +925,19 @@ "text-loading-template": "", "title-failed-to-fetch-notification-template": "" }, + "editor": { + "edit-alert-rule": "", + "edit-recording-rule": "", + "new-alert-rule": "", + "new-recording-rule": "" + }, "error-modal": { "failed-to-update-your-configuration": "", "title-something-went-wrong": "" }, + "error-summary-button": { + "content-show-all-errors": "" + }, "evaluation-behavior-summary": { "evaluate": "", "label-evaluate": "", @@ -923,7 +968,6 @@ "existing-rule-editor": { "sorry-permission": "", "sorry-this-rule-does-not-exist": "", - "text-loading-rule": "", "title-cannot-edit-rule": "", "title-failed-to-load-rule": "", "title-rule-not-found": "" @@ -1174,6 +1218,9 @@ "group-loader": { "group-load-failed": "" }, + "group-status": { + "content-the-group-is-being-deleted": "" + }, "header": { "tooltip-remove": "" }, @@ -1342,7 +1389,8 @@ "common-labels": "", "error-unable-to-fetch": "", "loading": "", - "title-error-fetching-the-state-history": "" + "title-error-fetching-the-state-history": "", + "tooltip-common-labels": "" }, "manage-permissions": { "button": "Kelola izin", @@ -1435,6 +1483,7 @@ }, "mute-timing-time-range": { "add-another-time-range": "", + "content-this-time-interval-is-disabled": "", "description-time-range": "", "label-end-time": "", "label-start-time": "", @@ -1590,6 +1639,9 @@ "plugin-integrations": { "tailored-apps": "" }, + "plugin-origin-badge": { + "tooltip-managed-by-plugin": "" + }, "policies": { "aria-label-collapse": "", "aria-label-expand": "", @@ -1687,6 +1739,9 @@ "prometheus-consistency-check": { "title-unable-to-check-the-rule-status": "" }, + "provisioned-tooltip": { + "content-provisioned-items-cannot-edited": "" + }, "provisioning": { "badge-tooltip-provenance": "Sumber daya ini telah disediakan melalui {{provenance}} dan tidak dapat diedit melalui UI", "badge-tooltip-standard": "Sumber daya ini telah disediakan dan tidak dapat diedit melalui UI", @@ -1921,14 +1976,14 @@ }, "threshold": { "recovery": { - "stop-alerting-above": "Hentikan alerting saat di atas", - "stop-alerting-bellow": "Hentikan alerting saat di bawah ini", - "stop-alerting-equal": "Hentikan alerting saat sama dengan", - "stop-alerting-inside-range": "Hentikan alerting saat berada dalam rentang", - "stop-alerting-less": "Hentikan alerting saat kurang dari", - "stop-alerting-more": "Hentikan alerting saat lebih dari", - "stop-alerting-not-equal": "Hentikan alerting saat tidak sama dengan", - "stop-alerting-outside-range": "Hentikan alerting saat berada di luar rentang", + "stop-alerting-above": "", + "stop-alerting-bellow": "", + "stop-alerting-equal": "", + "stop-alerting-inside-range": "", + "stop-alerting-less": "", + "stop-alerting-more": "", + "stop-alerting-not-equal": "", + "stop-alerting-outside-range": "", "title": "Ambang batas pemulihan kustom" } } @@ -2026,7 +2081,10 @@ "for": "", "na": "", "paused": "Dijeda", - "recording-rule": "Aturan pencatatan" + "recording-rule": "Aturan pencatatan", + "recording-rule-state": { + "content-recording-rule-evaluation-is-currently-paused": "" + } }, "rule-stats": { "firing": "", @@ -2269,6 +2327,8 @@ "refresh": "" }, "template-row": { + "content-templatefiles": "", + "content-templates": "", "tooltip-copy-template-group": "", "tooltip-delete-template-group": "", "tooltip-edit-template-group": "", @@ -2292,7 +2352,7 @@ }, "misconfigured-badge-text": "Salah konfigurasi", "misconfigured-warning": "Templat ini salah dikonfigurasi.", - "misconfigured-warning-details": "Templat harus ditentukan di bagian <1> dan <4> dari konfigurasi alertmanager Anda." + "misconfigured-warning-details": "" }, "templates-picker": { "button-edit": "", @@ -2337,6 +2397,10 @@ "type-selector-button": { "add-expression": "" }, + "unknown-contact-point-details": { + "unknown-contact-point": "", + "unknown-contact-point-tooltip": "" + }, "unknown-rule-list-item": { "title-unknown-rule-type": "" }, @@ -2437,6 +2501,7 @@ }, "api-keys-table": { "aria-label-delete-api-key": "", + "content-this-api-key-has-expired": "", "expires": "", "last-used-at": "", "migrate-to-service-account": "", @@ -4133,7 +4198,9 @@ }, "variable-check-indicator": { "aria-label-variable-referenced-dashboard": "", - "aria-label-variable-referenced-other-variables-dashboard": "" + "aria-label-variable-referenced-other-variables-dashboard": "", + "content-variable-not-referenced-other-variables-dashboard": "", + "content-variable-referenced-other-variables-dashboard": "" }, "variable-editor-form": { "aria-label-variable-editor-form": "", @@ -4193,6 +4260,7 @@ }, "versions-history-buttons": { "compare-versions": "", + "content-select-two-versions-to-start-comparing": "", "show-more-versions": "" }, "visualization-button": { @@ -4546,6 +4614,7 @@ } }, "correlation-editor-mode-bar": { + "content-correlations-editor-explore-experimental-feature": "", "exit-correlation-editor": "", "save": "" }, @@ -4703,6 +4772,9 @@ "label-select-query": "", "tooltip-select-query-visualize-table": "" }, + "logs-volume-panel": { + "content-streaming": "" + }, "logs-volume-panel-list": { "label-reload-log-volume": "", "loading": "", @@ -4902,6 +4974,7 @@ "label-tags": "", "placeholder-all-service-names": "", "placeholder-all-span-names": "", + "tooltip-collapse": "", "tooltip-duration": "", "tooltip-tags": "" }, @@ -4980,7 +5053,8 @@ "label-show-paths": "" }, "trace-view": { - "no-data": "" + "no-data": "", + "tooltip-copy-icon": "" }, "trace-view-container": { "title-trace": "" @@ -5094,7 +5168,8 @@ "default-password-alert": "", "new-password-label": "", "skip-button": "Lewati", - "submit-button": "Kirim" + "submit-button": "Kirim", + "tooltip-skip-button": "" }, "contact-admin": "Lupa nama pengguna atau email Anda? Hubungi administrator Grafana Anda.", "email-sent": "Email dengan tautan pengaturan ulang telah dikirim ke alamat email. Anda akan segera menerimanya.", @@ -5151,7 +5226,8 @@ "action-editor": { "button": { "confirm": "Konfirmasi", - "confirm-action": "Konfirmasi tindakan" + "confirm-action": "Konfirmasi tindakan", + "style": "" }, "inline": { "add-action": "Tambah tindakan", @@ -5358,7 +5434,11 @@ "previous-page": "halaman sebelumnya" }, "panel-chrome": { - "aria-label-toggle-collapse": "" + "aria-label-toggle-collapse": "", + "tooltip-cancel": "", + "tooltip-cancel-loading": "", + "tooltip-stop-streaming": "", + "tooltip-streaming": "" }, "panel-menu": { "label": "Menu untuk panel {{ title }} ", @@ -5935,7 +6015,8 @@ "log-row-message": { "ellipsis": "… ", "more": "lainnya", - "see-details": "Lihat detail log" + "see-details": "Lihat detail log", + "tooltip-error": "" }, "log-rows": { "disable-popover": { @@ -6912,7 +6993,8 @@ "label-search": "", "label-state": "", "label-type": "", - "subtitle": "" + "subtitle": "", + "tooltip-filter-disabled": "" }, "catalog": { "no-updates-available": "Tidak ada pembaruan yang tersedia", @@ -6921,7 +7003,7 @@ "available-header": "Tersedia", "button": "Perbarui semua ({{length}})", "cloud-update-message": "* Mungkin perlu beberapa menit agar plugin tersedia untuk digunakan.", - "error": "Kesalahan saat memperbarui plugin:", + "error": "", "error-status-text": "gagal - melihat pesan kesalahan", "header": "Plugin berikut memiliki pembaruan yang tersedia", "installed-header": "Diinstal", @@ -7131,6 +7213,9 @@ "feature-toggles-age": { "enable-in-config": "" }, + "input-suffix": { + "content-login-details-locked-because-managed-another": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -8059,10 +8144,12 @@ "title-disable-service-account": "" }, "token-expiration": { + "content-this-token-has-expired": "", "expired-label": "", "never": "" }, "token-revoked": { + "content-token-publicly-exposed-please-rotate": "", "revoked-label": "" } }, @@ -8468,7 +8555,7 @@ "range-content": { "apply-button": "Gunakan rentang waktu", "default-error": "", - "fiscal-year": "Tahun fiskal", + "fiscal-year": "", "from-input": "Dari", "open-input-calendar": "Buka kalender", "range-error": "\"Dari\" tidak boleh setelah \"Hingga\"", @@ -8920,6 +9007,7 @@ "loading": "Memuat...", "no-unknowns": "Tidak ada variabel yang diganti namanya atau hilang.", "renamed-or-missing-variables": "Variabel yang diganti namanya atau hilang", + "tooltip-renamed-or-missing-variables": "", "variable": "Variabel" }, "variable-check-indicator": { diff --git a/public/locales/it-IT/grafana.json b/public/locales/it-IT/grafana.json index e55529eedb2..76bcf39ecfe 100644 --- a/public/locales/it-IT/grafana.json +++ b/public/locales/it-IT/grafana.json @@ -21,7 +21,9 @@ "permission-list-item": { "inherited": "Ereditato dalla cartella", "locked-aria-label": "", - "remove-aria-label": "" + "remove-aria-label": "", + "tooltip-inherited-permission": "", + "tooltip-provisioned-permission": "" }, "permissions": { "add-label": "Aggiungi un'autorizzazione", @@ -42,6 +44,11 @@ }, "actions": { "action-editor": { + "button": { + "style": { + "background-color": "" + } + }, "label-headers": "", "label-url": "", "placeholder-url": "" @@ -70,6 +77,7 @@ "confirm-modal-body-2": "", "get-stage-cell": { "beta": "", + "content-general-availability": "", "deprecated": "", "ga": "" }, @@ -128,6 +136,12 @@ "test-mapping-heading": "Mappatura utente di prova", "test-mapping-run-button": "Esegui" }, + "ldap-connection-status": { + "columns": { + "content-connection-is-available": "", + "content-connection-is-not-available": "" + } + }, "ldap-error-box": { "title-connection-error": "" }, @@ -161,6 +175,9 @@ "title": "Sincronizzazione LDAP" }, "ldap-user-groups": { + "columns": { + "content-no-matching-organizations-found": "" + }, "no-org-found": "" }, "ldap-user-info": { @@ -284,12 +301,14 @@ }, "users-table": { "columns": { + "content-grafana-admin": "", "title-edit-user": "" }, "edit-aria-label": "", "edit-tooltip": "", "last-seen-never": "Mai", - "no-licensed-roles": "Non assegnato" + "no-licensed-roles": "Non assegnato", + "tooltip-assigned-role": "" } }, "alert-labels": { @@ -408,11 +427,9 @@ }, "alert-rule-form": { "action-buttons": { - "delete": "", "edit-yaml": "", - "save-exit": "" - }, - "title-delete-rule": "" + "save": "" + } }, "alert-rule-name-and-metric": { "aria-label-name": "", @@ -444,6 +461,7 @@ "unprocessed-the-alert-is-received": "" }, "alert-state-tag": { + "content-alert-evaluation-is-currently-paused": "", "paused": "" }, "alert-warning": { @@ -540,6 +558,9 @@ "warning-restore-manually": "Stai ripristinando manualmente una vecchia versione di questa regola di avviso. Rivedi attentamente le modifiche prima di salvare la definizione della regola.", "warning-restore-manually-title": "Ripristino manuale della regola" }, + "all-matches-indicator": { + "content-this-policy-matches-all-labels": "" + }, "am-root-route-form": { "am-group-description-label": "", "am-group-interval-description": "", @@ -652,6 +673,9 @@ "of": "", "when": "" }, + "clear-filter-button-object-renderer": { + "content-clear-filter": "" + }, "clone-rule-button": { "title-copy": "" }, @@ -778,6 +802,9 @@ "contactPointFilter": { "label": "Punto di contatto" }, + "continue-matching-indicator": { + "content-route-continue-matching-other-policies": "" + }, "copy-to-clipboard": "Copia \"{{label}}\" negli appunti", "create-metadata": { "view-dashboard": "", @@ -806,6 +833,10 @@ "current-selection-dashboard": "", "current-selection-panel": "", "fallback-dashboards-string": "", + "panel-row": { + "content-panel-valid-identifier": "", + "tooltip-alert-tab-support": "" + }, "placeholder-search-dashboard": "", "placeholder-search-panel": "", "select-dashboard-available-panels": "", @@ -827,9 +858,11 @@ "update-datasource": "" }, "declare-incident-button": { + "content-grafana-incident-installed-configured-correctly": "", "declare-incident": "" }, "declare-incident-menu-item": { + "content-grafana-incident-installed-configured-correctly": "", "label-declare-incident": "" }, "delete-rule-modal": { @@ -865,6 +898,9 @@ }, "unknown": "" }, + "disabled-tooltip": { + "content-appear-compatible-datasources": "" + }, "draggable-rules-table": { "evals-to-start-alerting": "Valutazioni per avviare l'avviso", "pending-period": "Periodo di attesa", @@ -895,10 +931,19 @@ "text-loading-template": "", "title-failed-to-fetch-notification-template": "" }, + "editor": { + "edit-alert-rule": "", + "edit-recording-rule": "", + "new-alert-rule": "", + "new-recording-rule": "" + }, "error-modal": { "failed-to-update-your-configuration": "", "title-something-went-wrong": "" }, + "error-summary-button": { + "content-show-all-errors": "" + }, "evaluation-behavior-summary": { "evaluate": "", "label-evaluate": "", @@ -929,7 +974,6 @@ "existing-rule-editor": { "sorry-permission": "", "sorry-this-rule-does-not-exist": "", - "text-loading-rule": "", "title-cannot-edit-rule": "", "title-failed-to-load-rule": "", "title-rule-not-found": "" @@ -1180,6 +1224,9 @@ "group-loader": { "group-load-failed": "" }, + "group-status": { + "content-the-group-is-being-deleted": "" + }, "header": { "tooltip-remove": "" }, @@ -1349,7 +1396,8 @@ "common-labels": "", "error-unable-to-fetch": "", "loading": "", - "title-error-fetching-the-state-history": "" + "title-error-fetching-the-state-history": "", + "tooltip-common-labels": "" }, "manage-permissions": { "button": "Gestisci autorizzazioni", @@ -1442,6 +1490,7 @@ }, "mute-timing-time-range": { "add-another-time-range": "", + "content-this-time-interval-is-disabled": "", "description-time-range": "", "label-end-time": "", "label-start-time": "", @@ -1597,6 +1646,9 @@ "plugin-integrations": { "tailored-apps": "" }, + "plugin-origin-badge": { + "tooltip-managed-by-plugin": "" + }, "policies": { "aria-label-collapse": "", "aria-label-expand": "", @@ -1696,6 +1748,9 @@ "prometheus-consistency-check": { "title-unable-to-check-the-rule-status": "" }, + "provisioned-tooltip": { + "content-provisioned-items-cannot-edited": "" + }, "provisioning": { "badge-tooltip-provenance": "Questa risorsa è stata sottoposta a provisioning tramite {{provenance}} e non può essere modificata tramite l'interfaccia utente", "badge-tooltip-standard": "Questa risorsa è stata sottoposta a provisioning e non può essere modificata tramite l'interfaccia utente", @@ -1931,14 +1986,14 @@ }, "threshold": { "recovery": { - "stop-alerting-above": "Interrompi l'avviso quando è al di sopra di", - "stop-alerting-bellow": "Interrompi l'avviso quando è al di sotto di", - "stop-alerting-equal": "Interrompi l'avviso quando è uguale a", - "stop-alerting-inside-range": "Interrompi l'avviso quando è all'interno dell'intervallo", - "stop-alerting-less": "Interrompi l'avviso quando è inferiore a", - "stop-alerting-more": "Interrompi l'avviso quando è superiore a", - "stop-alerting-not-equal": "Interrompi l'avviso quando non è uguale a", - "stop-alerting-outside-range": "Interrompi l'avviso quando è fuori intervallo", + "stop-alerting-above": "", + "stop-alerting-bellow": "", + "stop-alerting-equal": "", + "stop-alerting-inside-range": "", + "stop-alerting-less": "", + "stop-alerting-more": "", + "stop-alerting-not-equal": "", + "stop-alerting-outside-range": "", "title": "Soglia di recupero personalizzata" } } @@ -2038,7 +2093,10 @@ "for": "", "na": "", "paused": "In pausa", - "recording-rule": "Regola di registrazione" + "recording-rule": "Regola di registrazione", + "recording-rule-state": { + "content-recording-rule-evaluation-is-currently-paused": "" + } }, "rule-stats": { "firing": "", @@ -2282,6 +2340,8 @@ "refresh": "" }, "template-row": { + "content-templatefiles": "", + "content-templates": "", "tooltip-copy-template-group": "", "tooltip-delete-template-group": "", "tooltip-edit-template-group": "", @@ -2305,7 +2365,7 @@ }, "misconfigured-badge-text": "Configurazione errata", "misconfigured-warning": "Questo modello non è configurato correttamente.", - "misconfigured-warning-details": "I modelli devono essere definiti in entrambe le sezioni <1> e <4> della configurazione di Alertmanager." + "misconfigured-warning-details": "" }, "templates-picker": { "button-edit": "", @@ -2350,6 +2410,10 @@ "type-selector-button": { "add-expression": "" }, + "unknown-contact-point-details": { + "unknown-contact-point": "", + "unknown-contact-point-tooltip": "" + }, "unknown-rule-list-item": { "title-unknown-rule-type": "" }, @@ -2450,6 +2514,7 @@ }, "api-keys-table": { "aria-label-delete-api-key": "", + "content-this-api-key-has-expired": "", "expires": "", "last-used-at": "", "migrate-to-service-account": "", @@ -4154,7 +4219,9 @@ }, "variable-check-indicator": { "aria-label-variable-referenced-dashboard": "", - "aria-label-variable-referenced-other-variables-dashboard": "" + "aria-label-variable-referenced-other-variables-dashboard": "", + "content-variable-not-referenced-other-variables-dashboard": "", + "content-variable-referenced-other-variables-dashboard": "" }, "variable-editor-form": { "aria-label-variable-editor-form": "", @@ -4214,6 +4281,7 @@ }, "versions-history-buttons": { "compare-versions": "", + "content-select-two-versions-to-start-comparing": "", "show-more-versions": "" }, "visualization-button": { @@ -4567,6 +4635,7 @@ } }, "correlation-editor-mode-bar": { + "content-correlations-editor-explore-experimental-feature": "", "exit-correlation-editor": "", "save": "" }, @@ -4724,6 +4793,9 @@ "label-select-query": "", "tooltip-select-query-visualize-table": "" }, + "logs-volume-panel": { + "content-streaming": "" + }, "logs-volume-panel-list": { "label-reload-log-volume": "", "loading": "", @@ -4923,6 +4995,7 @@ "label-tags": "", "placeholder-all-service-names": "", "placeholder-all-span-names": "", + "tooltip-collapse": "", "tooltip-duration": "", "tooltip-tags": "" }, @@ -5001,7 +5074,8 @@ "label-show-paths": "" }, "trace-view": { - "no-data": "" + "no-data": "", + "tooltip-copy-icon": "" }, "trace-view-container": { "title-trace": "" @@ -5115,7 +5189,8 @@ "default-password-alert": "", "new-password-label": "", "skip-button": "Ignora", - "submit-button": "Invia" + "submit-button": "Invia", + "tooltip-skip-button": "" }, "contact-admin": "Hai dimenticato il nome utente o l'email? Contatta l'amministratore di Grafana.", "email-sent": "Un'email con un link di ripristino è stata inviata all'indirizzo email. Dovresti riceverla a breve.", @@ -5172,7 +5247,8 @@ "action-editor": { "button": { "confirm": "Conferma", - "confirm-action": "Conferma azione" + "confirm-action": "Conferma azione", + "style": "" }, "inline": { "add-action": "Aggiungi azione", @@ -5379,7 +5455,11 @@ "previous-page": "pagina precedente" }, "panel-chrome": { - "aria-label-toggle-collapse": "" + "aria-label-toggle-collapse": "", + "tooltip-cancel": "", + "tooltip-cancel-loading": "", + "tooltip-stop-streaming": "", + "tooltip-streaming": "" }, "panel-menu": { "label": "Menu per il pannello {{ title }}", @@ -5961,7 +6041,8 @@ "log-row-message": { "ellipsis": "… ", "more": "altro", - "see-details": "Consulta i dettagli del registro" + "see-details": "Consulta i dettagli del registro", + "tooltip-error": "" }, "log-rows": { "disable-popover": { @@ -6938,7 +7019,8 @@ "label-search": "", "label-state": "", "label-type": "", - "subtitle": "" + "subtitle": "", + "tooltip-filter-disabled": "" }, "catalog": { "no-updates-available": "Non sono disponibili aggiornamenti", @@ -6947,7 +7029,7 @@ "available-header": "Disponibile", "button": "Aggiorna tutto ({{length}})", "cloud-update-message": "* Potrebbero essere necessari alcuni minuti prima che i plug-in siano disponibili per l'utilizzo.", - "error": "Errore durante l'aggiornamento del plug-in:", + "error": "", "error-status-text": "non riuscito - vedi messaggi di errore", "header": "Sono disponibili aggiornamenti per i seguenti plug-in", "installed-header": "Installato", @@ -7157,6 +7239,9 @@ "feature-toggles-age": { "enable-in-config": "" }, + "input-suffix": { + "content-login-details-locked-because-managed-another": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -8090,10 +8175,12 @@ "title-disable-service-account": "" }, "token-expiration": { + "content-this-token-has-expired": "", "expired-label": "", "never": "" }, "token-revoked": { + "content-token-publicly-exposed-please-rotate": "", "revoked-label": "" } }, @@ -8499,7 +8586,7 @@ "range-content": { "apply-button": "Applica intervallo di tempo", "default-error": "", - "fiscal-year": "Anno fiscale", + "fiscal-year": "", "from-input": "Da", "open-input-calendar": "Apri calendario", "range-error": "\"Da\" non può essere successivo a \"A\"", @@ -8951,6 +9038,7 @@ "loading": "Caricamento in corso...", "no-unknowns": "Nessuna variabile rinominata o mancante trovata.", "renamed-or-missing-variables": "Variabili rinominate o mancanti", + "tooltip-renamed-or-missing-variables": "", "variable": "Variabile" }, "variable-check-indicator": { diff --git a/public/locales/ja-JP/grafana.json b/public/locales/ja-JP/grafana.json index de4771b9926..bcb6b2a6961 100644 --- a/public/locales/ja-JP/grafana.json +++ b/public/locales/ja-JP/grafana.json @@ -21,7 +21,9 @@ "permission-list-item": { "inherited": "フォルダから継承", "locked-aria-label": "", - "remove-aria-label": "" + "remove-aria-label": "", + "tooltip-inherited-permission": "", + "tooltip-provisioned-permission": "" }, "permissions": { "add-label": "権限を追加する", @@ -42,6 +44,11 @@ }, "actions": { "action-editor": { + "button": { + "style": { + "background-color": "" + } + }, "label-headers": "", "label-url": "", "placeholder-url": "" @@ -70,6 +77,7 @@ "confirm-modal-body-2": "", "get-stage-cell": { "beta": "", + "content-general-availability": "", "deprecated": "", "ga": "" }, @@ -128,6 +136,12 @@ "test-mapping-heading": "テストユーザーマッピング", "test-mapping-run-button": "Run" }, + "ldap-connection-status": { + "columns": { + "content-connection-is-available": "", + "content-connection-is-not-available": "" + } + }, "ldap-error-box": { "title-connection-error": "" }, @@ -161,6 +175,9 @@ "title": "LDAP同期" }, "ldap-user-groups": { + "columns": { + "content-no-matching-organizations-found": "" + }, "no-org-found": "" }, "ldap-user-info": { @@ -284,12 +301,14 @@ }, "users-table": { "columns": { + "content-grafana-admin": "", "title-edit-user": "" }, "edit-aria-label": "", "edit-tooltip": "", "last-seen-never": "絶対にしない", - "no-licensed-roles": "割り当てなし" + "no-licensed-roles": "割り当てなし", + "tooltip-assigned-role": "" } }, "alert-labels": { @@ -404,11 +423,9 @@ }, "alert-rule-form": { "action-buttons": { - "delete": "", "edit-yaml": "", - "save-exit": "" - }, - "title-delete-rule": "" + "save": "" + } }, "alert-rule-name-and-metric": { "aria-label-name": "", @@ -440,6 +457,7 @@ "unprocessed-the-alert-is-received": "" }, "alert-state-tag": { + "content-alert-evaluation-is-currently-paused": "", "paused": "" }, "alert-warning": { @@ -536,6 +554,9 @@ "warning-restore-manually": "このアラートルールの古いバージョンを手動で復元しています。ルール定義を保存する前に、変更を注意深く確認してください。", "warning-restore-manually-title": "手動でルールを復元する" }, + "all-matches-indicator": { + "content-this-policy-matches-all-labels": "" + }, "am-root-route-form": { "am-group-description-label": "", "am-group-interval-description": "", @@ -648,6 +669,9 @@ "of": "", "when": "" }, + "clear-filter-button-object-renderer": { + "content-clear-filter": "" + }, "clone-rule-button": { "title-copy": "" }, @@ -772,6 +796,9 @@ "contactPointFilter": { "label": "コンタクトポイント" }, + "continue-matching-indicator": { + "content-route-continue-matching-other-policies": "" + }, "copy-to-clipboard": "「{{label}}」をクリップボードにコピー", "create-metadata": { "view-dashboard": "", @@ -800,6 +827,10 @@ "current-selection-dashboard": "", "current-selection-panel": "", "fallback-dashboards-string": "", + "panel-row": { + "content-panel-valid-identifier": "", + "tooltip-alert-tab-support": "" + }, "placeholder-search-dashboard": "", "placeholder-search-panel": "", "select-dashboard-available-panels": "", @@ -821,9 +852,11 @@ "update-datasource": "" }, "declare-incident-button": { + "content-grafana-incident-installed-configured-correctly": "", "declare-incident": "" }, "declare-incident-menu-item": { + "content-grafana-incident-installed-configured-correctly": "", "label-declare-incident": "" }, "delete-rule-modal": { @@ -859,6 +892,9 @@ }, "unknown": "" }, + "disabled-tooltip": { + "content-appear-compatible-datasources": "" + }, "draggable-rules-table": { "evals-to-start-alerting": "アラートを開始するための評価", "pending-period": "保留期間", @@ -889,10 +925,19 @@ "text-loading-template": "", "title-failed-to-fetch-notification-template": "" }, + "editor": { + "edit-alert-rule": "", + "edit-recording-rule": "", + "new-alert-rule": "", + "new-recording-rule": "" + }, "error-modal": { "failed-to-update-your-configuration": "", "title-something-went-wrong": "" }, + "error-summary-button": { + "content-show-all-errors": "" + }, "evaluation-behavior-summary": { "evaluate": "", "label-evaluate": "", @@ -923,7 +968,6 @@ "existing-rule-editor": { "sorry-permission": "", "sorry-this-rule-does-not-exist": "", - "text-loading-rule": "", "title-cannot-edit-rule": "", "title-failed-to-load-rule": "", "title-rule-not-found": "" @@ -1174,6 +1218,9 @@ "group-loader": { "group-load-failed": "" }, + "group-status": { + "content-the-group-is-being-deleted": "" + }, "header": { "tooltip-remove": "" }, @@ -1342,7 +1389,8 @@ "common-labels": "", "error-unable-to-fetch": "", "loading": "", - "title-error-fetching-the-state-history": "" + "title-error-fetching-the-state-history": "", + "tooltip-common-labels": "" }, "manage-permissions": { "button": "権限を管理する", @@ -1435,6 +1483,7 @@ }, "mute-timing-time-range": { "add-another-time-range": "", + "content-this-time-interval-is-disabled": "", "description-time-range": "", "label-end-time": "", "label-start-time": "", @@ -1590,6 +1639,9 @@ "plugin-integrations": { "tailored-apps": "" }, + "plugin-origin-badge": { + "tooltip-managed-by-plugin": "" + }, "policies": { "aria-label-collapse": "", "aria-label-expand": "", @@ -1687,6 +1739,9 @@ "prometheus-consistency-check": { "title-unable-to-check-the-rule-status": "" }, + "provisioned-tooltip": { + "content-provisioned-items-cannot-edited": "" + }, "provisioning": { "badge-tooltip-provenance": "このリソースは{{provenance}}を介してプロビジョニングされており、UIから編集することはできません", "badge-tooltip-standard": "このリソースはプロビジョニングされており、UIから編集することはできません", @@ -1921,14 +1976,14 @@ }, "threshold": { "recovery": { - "stop-alerting-above": "上記の場合、アラートを停止する", - "stop-alerting-bellow": "下記の場合、アラートを停止する", - "stop-alerting-equal": "等しい場合、アラートを停止する", - "stop-alerting-inside-range": "範囲内の場合、アラートを停止する", - "stop-alerting-less": "次未満の場合、アラートを停止する", - "stop-alerting-more": "次を超える場合、アラートを停止する", - "stop-alerting-not-equal": "等しくない場合、アラートを停止する", - "stop-alerting-outside-range": "範囲外の場合、アラートを停止する", + "stop-alerting-above": "", + "stop-alerting-bellow": "", + "stop-alerting-equal": "", + "stop-alerting-inside-range": "", + "stop-alerting-less": "", + "stop-alerting-more": "", + "stop-alerting-not-equal": "", + "stop-alerting-outside-range": "", "title": "カスタム回復のしきい値" } } @@ -2026,7 +2081,10 @@ "for": "", "na": "", "paused": "中断しています", - "recording-rule": "録画ルール" + "recording-rule": "録画ルール", + "recording-rule-state": { + "content-recording-rule-evaluation-is-currently-paused": "" + } }, "rule-stats": { "firing": "", @@ -2269,6 +2327,8 @@ "refresh": "" }, "template-row": { + "content-templatefiles": "", + "content-templates": "", "tooltip-copy-template-group": "", "tooltip-delete-template-group": "", "tooltip-edit-template-group": "", @@ -2292,7 +2352,7 @@ }, "misconfigured-badge-text": "誤って構成されています", "misconfigured-warning": "このテンプレートは誤って構成されています。", - "misconfigured-warning-details": "テンプレートは、Alertmanager構成の<1>と<4>セクションの両方で定義する必要があります。" + "misconfigured-warning-details": "" }, "templates-picker": { "button-edit": "", @@ -2337,6 +2397,10 @@ "type-selector-button": { "add-expression": "" }, + "unknown-contact-point-details": { + "unknown-contact-point": "", + "unknown-contact-point-tooltip": "" + }, "unknown-rule-list-item": { "title-unknown-rule-type": "" }, @@ -2437,6 +2501,7 @@ }, "api-keys-table": { "aria-label-delete-api-key": "", + "content-this-api-key-has-expired": "", "expires": "", "last-used-at": "", "migrate-to-service-account": "", @@ -4133,7 +4198,9 @@ }, "variable-check-indicator": { "aria-label-variable-referenced-dashboard": "", - "aria-label-variable-referenced-other-variables-dashboard": "" + "aria-label-variable-referenced-other-variables-dashboard": "", + "content-variable-not-referenced-other-variables-dashboard": "", + "content-variable-referenced-other-variables-dashboard": "" }, "variable-editor-form": { "aria-label-variable-editor-form": "", @@ -4193,6 +4260,7 @@ }, "versions-history-buttons": { "compare-versions": "", + "content-select-two-versions-to-start-comparing": "", "show-more-versions": "" }, "visualization-button": { @@ -4546,6 +4614,7 @@ } }, "correlation-editor-mode-bar": { + "content-correlations-editor-explore-experimental-feature": "", "exit-correlation-editor": "", "save": "" }, @@ -4703,6 +4772,9 @@ "label-select-query": "", "tooltip-select-query-visualize-table": "" }, + "logs-volume-panel": { + "content-streaming": "" + }, "logs-volume-panel-list": { "label-reload-log-volume": "", "loading": "", @@ -4902,6 +4974,7 @@ "label-tags": "", "placeholder-all-service-names": "", "placeholder-all-span-names": "", + "tooltip-collapse": "", "tooltip-duration": "", "tooltip-tags": "" }, @@ -4980,7 +5053,8 @@ "label-show-paths": "" }, "trace-view": { - "no-data": "" + "no-data": "", + "tooltip-copy-icon": "" }, "trace-view-container": { "title-trace": "" @@ -5094,7 +5168,8 @@ "default-password-alert": "", "new-password-label": "", "skip-button": "スキップ", - "submit-button": "送信" + "submit-button": "送信", + "tooltip-skip-button": "" }, "contact-admin": "ユーザー名またはメールアドレスをお忘れですか?Grafana管理者にお問い合わせください。", "email-sent": "リセットリンクが記載されたメールがメールアドレスに送信されました。まもなく受け取るはずです。", @@ -5151,7 +5226,8 @@ "action-editor": { "button": { "confirm": "確定", - "confirm-action": "操作を確認" + "confirm-action": "操作を確認", + "style": "" }, "inline": { "add-action": "操作を追加", @@ -5358,7 +5434,11 @@ "previous-page": "前のページ" }, "panel-chrome": { - "aria-label-toggle-collapse": "" + "aria-label-toggle-collapse": "", + "tooltip-cancel": "", + "tooltip-cancel-loading": "", + "tooltip-stop-streaming": "", + "tooltip-streaming": "" }, "panel-menu": { "label": "パネル、{{ title }}のメニュー", @@ -5935,7 +6015,8 @@ "log-row-message": { "ellipsis": "… ", "more": "さらに", - "see-details": "ログの詳細を見る" + "see-details": "ログの詳細を見る", + "tooltip-error": "" }, "log-rows": { "disable-popover": { @@ -6912,7 +6993,8 @@ "label-search": "", "label-state": "", "label-type": "", - "subtitle": "" + "subtitle": "", + "tooltip-filter-disabled": "" }, "catalog": { "no-updates-available": "更新が見つかりませんでした", @@ -6921,7 +7003,7 @@ "available-header": "可能", "button": "すべて({{length}})を更新", "cloud-update-message": "*プラグインが使用可能になるまでに数分かかる場合があります。", - "error": "プラグインの更新中にエラーが発生しました。", + "error": "", "error-status-text": "失敗しました - エラーメッセージを参照してください", "header": "次のプラグインには利用可能な更新があります", "installed-header": "インストール済み", @@ -7131,6 +7213,9 @@ "feature-toggles-age": { "enable-in-config": "" }, + "input-suffix": { + "content-login-details-locked-because-managed-another": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -8059,10 +8144,12 @@ "title-disable-service-account": "" }, "token-expiration": { + "content-this-token-has-expired": "", "expired-label": "", "never": "" }, "token-revoked": { + "content-token-publicly-exposed-please-rotate": "", "revoked-label": "" } }, @@ -8468,7 +8555,7 @@ "range-content": { "apply-button": "時間範囲を適用", "default-error": "", - "fiscal-year": "会計年度", + "fiscal-year": "", "from-input": "開始月", "open-input-calendar": "カレンダー", "range-error": "「開始月」を「終了月」より後にすることはできません", @@ -8920,6 +9007,7 @@ "loading": "読み込み中...", "no-unknowns": "名前が変更された変数や欠落している変数は見つかりませんでした。", "renamed-or-missing-variables": "名前が変更された、または欠落している変数", + "tooltip-renamed-or-missing-variables": "", "variable": "変数" }, "variable-check-indicator": { diff --git a/public/locales/ko-KR/grafana.json b/public/locales/ko-KR/grafana.json index 3af7b1237ed..2ee0d9fc7d7 100644 --- a/public/locales/ko-KR/grafana.json +++ b/public/locales/ko-KR/grafana.json @@ -21,7 +21,9 @@ "permission-list-item": { "inherited": "폴더에서 상속됨", "locked-aria-label": "", - "remove-aria-label": "" + "remove-aria-label": "", + "tooltip-inherited-permission": "", + "tooltip-provisioned-permission": "" }, "permissions": { "add-label": "권한 추가", @@ -42,6 +44,11 @@ }, "actions": { "action-editor": { + "button": { + "style": { + "background-color": "" + } + }, "label-headers": "", "label-url": "", "placeholder-url": "" @@ -70,6 +77,7 @@ "confirm-modal-body-2": "", "get-stage-cell": { "beta": "", + "content-general-availability": "", "deprecated": "", "ga": "" }, @@ -128,6 +136,12 @@ "test-mapping-heading": "사용자 매핑 테스트", "test-mapping-run-button": "실행" }, + "ldap-connection-status": { + "columns": { + "content-connection-is-available": "", + "content-connection-is-not-available": "" + } + }, "ldap-error-box": { "title-connection-error": "" }, @@ -161,6 +175,9 @@ "title": "LDAP 동기화" }, "ldap-user-groups": { + "columns": { + "content-no-matching-organizations-found": "" + }, "no-org-found": "" }, "ldap-user-info": { @@ -284,12 +301,14 @@ }, "users-table": { "columns": { + "content-grafana-admin": "", "title-edit-user": "" }, "edit-aria-label": "", "edit-tooltip": "", "last-seen-never": "기록 없음", - "no-licensed-roles": "할당되지 않음" + "no-licensed-roles": "할당되지 않음", + "tooltip-assigned-role": "" } }, "alert-labels": { @@ -404,11 +423,9 @@ }, "alert-rule-form": { "action-buttons": { - "delete": "", "edit-yaml": "", - "save-exit": "" - }, - "title-delete-rule": "" + "save": "" + } }, "alert-rule-name-and-metric": { "aria-label-name": "", @@ -440,6 +457,7 @@ "unprocessed-the-alert-is-received": "" }, "alert-state-tag": { + "content-alert-evaluation-is-currently-paused": "", "paused": "" }, "alert-warning": { @@ -536,6 +554,9 @@ "warning-restore-manually": "이 경고 규칙의 이전 버전을 수동으로 복구하고 있습니다. 규칙 정의를 저장하기 전에 변경 사항을 주의 깊게 검토해 주세요.", "warning-restore-manually-title": "수동으로 규칙 복구" }, + "all-matches-indicator": { + "content-this-policy-matches-all-labels": "" + }, "am-root-route-form": { "am-group-description-label": "", "am-group-interval-description": "", @@ -648,6 +669,9 @@ "of": "", "when": "" }, + "clear-filter-button-object-renderer": { + "content-clear-filter": "" + }, "clone-rule-button": { "title-copy": "" }, @@ -772,6 +796,9 @@ "contactPointFilter": { "label": "연락처" }, + "continue-matching-indicator": { + "content-route-continue-matching-other-policies": "" + }, "copy-to-clipboard": "'{{label}}'을(를) 클립보드로 복사", "create-metadata": { "view-dashboard": "", @@ -800,6 +827,10 @@ "current-selection-dashboard": "", "current-selection-panel": "", "fallback-dashboards-string": "", + "panel-row": { + "content-panel-valid-identifier": "", + "tooltip-alert-tab-support": "" + }, "placeholder-search-dashboard": "", "placeholder-search-panel": "", "select-dashboard-available-panels": "", @@ -821,9 +852,11 @@ "update-datasource": "" }, "declare-incident-button": { + "content-grafana-incident-installed-configured-correctly": "", "declare-incident": "" }, "declare-incident-menu-item": { + "content-grafana-incident-installed-configured-correctly": "", "label-declare-incident": "" }, "delete-rule-modal": { @@ -859,6 +892,9 @@ }, "unknown": "" }, + "disabled-tooltip": { + "content-appear-compatible-datasources": "" + }, "draggable-rules-table": { "evals-to-start-alerting": "경고를 시작하기 위한 평가", "pending-period": "보류 기간", @@ -889,10 +925,19 @@ "text-loading-template": "", "title-failed-to-fetch-notification-template": "" }, + "editor": { + "edit-alert-rule": "", + "edit-recording-rule": "", + "new-alert-rule": "", + "new-recording-rule": "" + }, "error-modal": { "failed-to-update-your-configuration": "", "title-something-went-wrong": "" }, + "error-summary-button": { + "content-show-all-errors": "" + }, "evaluation-behavior-summary": { "evaluate": "", "label-evaluate": "", @@ -923,7 +968,6 @@ "existing-rule-editor": { "sorry-permission": "", "sorry-this-rule-does-not-exist": "", - "text-loading-rule": "", "title-cannot-edit-rule": "", "title-failed-to-load-rule": "", "title-rule-not-found": "" @@ -1174,6 +1218,9 @@ "group-loader": { "group-load-failed": "" }, + "group-status": { + "content-the-group-is-being-deleted": "" + }, "header": { "tooltip-remove": "" }, @@ -1342,7 +1389,8 @@ "common-labels": "", "error-unable-to-fetch": "", "loading": "", - "title-error-fetching-the-state-history": "" + "title-error-fetching-the-state-history": "", + "tooltip-common-labels": "" }, "manage-permissions": { "button": "권한 관리", @@ -1435,6 +1483,7 @@ }, "mute-timing-time-range": { "add-another-time-range": "", + "content-this-time-interval-is-disabled": "", "description-time-range": "", "label-end-time": "", "label-start-time": "", @@ -1590,6 +1639,9 @@ "plugin-integrations": { "tailored-apps": "" }, + "plugin-origin-badge": { + "tooltip-managed-by-plugin": "" + }, "policies": { "aria-label-collapse": "", "aria-label-expand": "", @@ -1687,6 +1739,9 @@ "prometheus-consistency-check": { "title-unable-to-check-the-rule-status": "" }, + "provisioned-tooltip": { + "content-provisioned-items-cannot-edited": "" + }, "provisioning": { "badge-tooltip-provenance": "이 리소스는 {{provenance}}을(를) 통해 프로비저닝되었으며 UI를 통해 편집할 수 없습니다.", "badge-tooltip-standard": "이 리소스는 프로비저닝되었으며 UI를 통해 편집할 수 없습니다.", @@ -1921,14 +1976,14 @@ }, "threshold": { "recovery": { - "stop-alerting-above": "다음 값을 초과할 때 경고 중지", - "stop-alerting-bellow": "다음 값 미만일 때 경고 중지", - "stop-alerting-equal": "다음 값과 같을 때 경고 중지", - "stop-alerting-inside-range": "다음 범위 내에 있을 때 경고 중지", - "stop-alerting-less": "다음 값 이하일 때 경고 중지", - "stop-alerting-more": "다음 값보다 이상일 때 경고 중지", - "stop-alerting-not-equal": "다음 값과 다를 때 경고 중지", - "stop-alerting-outside-range": "다음 범위를 벗어날 때 경고 중지", + "stop-alerting-above": "", + "stop-alerting-bellow": "", + "stop-alerting-equal": "", + "stop-alerting-inside-range": "", + "stop-alerting-less": "", + "stop-alerting-more": "", + "stop-alerting-not-equal": "", + "stop-alerting-outside-range": "", "title": "사용자 지정 복구 임계값" } } @@ -2026,7 +2081,10 @@ "for": "", "na": "", "paused": "일시 중지됨", - "recording-rule": "기록 규칙" + "recording-rule": "기록 규칙", + "recording-rule-state": { + "content-recording-rule-evaluation-is-currently-paused": "" + } }, "rule-stats": { "firing": "", @@ -2269,6 +2327,8 @@ "refresh": "" }, "template-row": { + "content-templatefiles": "", + "content-templates": "", "tooltip-copy-template-group": "", "tooltip-delete-template-group": "", "tooltip-edit-template-group": "", @@ -2292,7 +2352,7 @@ }, "misconfigured-badge-text": "잘못 구성됨", "misconfigured-warning": "이 템플릿은 잘못 구성되었습니다.", - "misconfigured-warning-details": "템플릿은 alertmanager 구성의 <1> 및 <4> 섹션에서 모두 정의되어야 합니다." + "misconfigured-warning-details": "" }, "templates-picker": { "button-edit": "", @@ -2337,6 +2397,10 @@ "type-selector-button": { "add-expression": "" }, + "unknown-contact-point-details": { + "unknown-contact-point": "", + "unknown-contact-point-tooltip": "" + }, "unknown-rule-list-item": { "title-unknown-rule-type": "" }, @@ -2437,6 +2501,7 @@ }, "api-keys-table": { "aria-label-delete-api-key": "", + "content-this-api-key-has-expired": "", "expires": "", "last-used-at": "", "migrate-to-service-account": "", @@ -4133,7 +4198,9 @@ }, "variable-check-indicator": { "aria-label-variable-referenced-dashboard": "", - "aria-label-variable-referenced-other-variables-dashboard": "" + "aria-label-variable-referenced-other-variables-dashboard": "", + "content-variable-not-referenced-other-variables-dashboard": "", + "content-variable-referenced-other-variables-dashboard": "" }, "variable-editor-form": { "aria-label-variable-editor-form": "", @@ -4193,6 +4260,7 @@ }, "versions-history-buttons": { "compare-versions": "", + "content-select-two-versions-to-start-comparing": "", "show-more-versions": "" }, "visualization-button": { @@ -4546,6 +4614,7 @@ } }, "correlation-editor-mode-bar": { + "content-correlations-editor-explore-experimental-feature": "", "exit-correlation-editor": "", "save": "" }, @@ -4703,6 +4772,9 @@ "label-select-query": "", "tooltip-select-query-visualize-table": "" }, + "logs-volume-panel": { + "content-streaming": "" + }, "logs-volume-panel-list": { "label-reload-log-volume": "", "loading": "", @@ -4902,6 +4974,7 @@ "label-tags": "", "placeholder-all-service-names": "", "placeholder-all-span-names": "", + "tooltip-collapse": "", "tooltip-duration": "", "tooltip-tags": "" }, @@ -4980,7 +5053,8 @@ "label-show-paths": "" }, "trace-view": { - "no-data": "" + "no-data": "", + "tooltip-copy-icon": "" }, "trace-view-container": { "title-trace": "" @@ -5094,7 +5168,8 @@ "default-password-alert": "", "new-password-label": "", "skip-button": "건너뛰기", - "submit-button": "제출" + "submit-button": "제출", + "tooltip-skip-button": "" }, "contact-admin": "사용자 이름이나 이메일을 잊으셨나요? Grafana 관리자에게 문의하세요.", "email-sent": "재설정 링크가 포함된 이메일이 이메일 주소로 전송되었습니다. 곧 받으실 수 있습니다.", @@ -5151,7 +5226,8 @@ "action-editor": { "button": { "confirm": "확인", - "confirm-action": "작업 확인" + "confirm-action": "작업 확인", + "style": "" }, "inline": { "add-action": "작업 추가", @@ -5358,7 +5434,11 @@ "previous-page": "이전 페이지" }, "panel-chrome": { - "aria-label-toggle-collapse": "" + "aria-label-toggle-collapse": "", + "tooltip-cancel": "", + "tooltip-cancel-loading": "", + "tooltip-stop-streaming": "", + "tooltip-streaming": "" }, "panel-menu": { "label": "{{ title }} 패널의 메뉴", @@ -5935,7 +6015,8 @@ "log-row-message": { "ellipsis": "… ", "more": "더 보기", - "see-details": "로그 세부 정보 보기" + "see-details": "로그 세부 정보 보기", + "tooltip-error": "" }, "log-rows": { "disable-popover": { @@ -6912,7 +6993,8 @@ "label-search": "", "label-state": "", "label-type": "", - "subtitle": "" + "subtitle": "", + "tooltip-filter-disabled": "" }, "catalog": { "no-updates-available": "사용 가능 업데이트 없음", @@ -6921,7 +7003,7 @@ "available-header": "사용 가능", "button": "모두 업데이트({{length}})", "cloud-update-message": "* 플러그인을 사용할 수 있게 되기까지 몇 분 정도 걸릴 수 있습니다.", - "error": "플러그인 업데이트 중 오류 발생:", + "error": "", "error-status-text": "실패 - 오류 메시지 참고", "header": "다음 플러그인에 대한 업데이트를 사용할 수 있습니다.", "installed-header": "설치됨", @@ -7131,6 +7213,9 @@ "feature-toggles-age": { "enable-in-config": "" }, + "input-suffix": { + "content-login-details-locked-because-managed-another": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -8059,10 +8144,12 @@ "title-disable-service-account": "" }, "token-expiration": { + "content-this-token-has-expired": "", "expired-label": "", "never": "" }, "token-revoked": { + "content-token-publicly-exposed-please-rotate": "", "revoked-label": "" } }, @@ -8468,7 +8555,7 @@ "range-content": { "apply-button": "시간 범위 적용", "default-error": "", - "fiscal-year": "회계 연도", + "fiscal-year": "", "from-input": "시작 시간", "open-input-calendar": "캘린더 열기", "range-error": "'시작 시간'은 '종료 시간' 이후일 수 없습니다.", @@ -8920,6 +9007,7 @@ "loading": "로딩 중...", "no-unknowns": "이름이 변경되었거나 누락된 변수가 없습니다.", "renamed-or-missing-variables": "이름이 변경되었거나 누락된 변수", + "tooltip-renamed-or-missing-variables": "", "variable": "변수" }, "variable-check-indicator": { diff --git a/public/locales/nl-NL/grafana.json b/public/locales/nl-NL/grafana.json index 609ea743ded..36fb129f1c5 100644 --- a/public/locales/nl-NL/grafana.json +++ b/public/locales/nl-NL/grafana.json @@ -21,7 +21,9 @@ "permission-list-item": { "inherited": "Overgenomen van map", "locked-aria-label": "", - "remove-aria-label": "" + "remove-aria-label": "", + "tooltip-inherited-permission": "", + "tooltip-provisioned-permission": "" }, "permissions": { "add-label": "Een toestemming toevoegen", @@ -42,6 +44,11 @@ }, "actions": { "action-editor": { + "button": { + "style": { + "background-color": "" + } + }, "label-headers": "", "label-url": "", "placeholder-url": "" @@ -70,6 +77,7 @@ "confirm-modal-body-2": "", "get-stage-cell": { "beta": "", + "content-general-availability": "", "deprecated": "", "ga": "" }, @@ -128,6 +136,12 @@ "test-mapping-heading": "Test gebruikerstoewijzing", "test-mapping-run-button": "Uitvoeren" }, + "ldap-connection-status": { + "columns": { + "content-connection-is-available": "", + "content-connection-is-not-available": "" + } + }, "ldap-error-box": { "title-connection-error": "" }, @@ -161,6 +175,9 @@ "title": "LDAP-synchronisatie" }, "ldap-user-groups": { + "columns": { + "content-no-matching-organizations-found": "" + }, "no-org-found": "" }, "ldap-user-info": { @@ -284,12 +301,14 @@ }, "users-table": { "columns": { + "content-grafana-admin": "", "title-edit-user": "" }, "edit-aria-label": "", "edit-tooltip": "", "last-seen-never": "Nooit", - "no-licensed-roles": "Niet toegewezen" + "no-licensed-roles": "Niet toegewezen", + "tooltip-assigned-role": "" } }, "alert-labels": { @@ -408,11 +427,9 @@ }, "alert-rule-form": { "action-buttons": { - "delete": "", "edit-yaml": "", - "save-exit": "" - }, - "title-delete-rule": "" + "save": "" + } }, "alert-rule-name-and-metric": { "aria-label-name": "", @@ -444,6 +461,7 @@ "unprocessed-the-alert-is-received": "" }, "alert-state-tag": { + "content-alert-evaluation-is-currently-paused": "", "paused": "" }, "alert-warning": { @@ -540,6 +558,9 @@ "warning-restore-manually": "Je herstelt handmatig een oude versie van deze waarschuwingsregel. Controleer de wijzigingen zorgvuldig voordat je de regeldefinitie opslaat.", "warning-restore-manually-title": "Regel handmatig herstellen" }, + "all-matches-indicator": { + "content-this-policy-matches-all-labels": "" + }, "am-root-route-form": { "am-group-description-label": "", "am-group-interval-description": "", @@ -652,6 +673,9 @@ "of": "", "when": "" }, + "clear-filter-button-object-renderer": { + "content-clear-filter": "" + }, "clone-rule-button": { "title-copy": "" }, @@ -778,6 +802,9 @@ "contactPointFilter": { "label": "Contactpunt" }, + "continue-matching-indicator": { + "content-route-continue-matching-other-policies": "" + }, "copy-to-clipboard": "Label '{{label}}' kopiëren naar klembord", "create-metadata": { "view-dashboard": "", @@ -806,6 +833,10 @@ "current-selection-dashboard": "", "current-selection-panel": "", "fallback-dashboards-string": "", + "panel-row": { + "content-panel-valid-identifier": "", + "tooltip-alert-tab-support": "" + }, "placeholder-search-dashboard": "", "placeholder-search-panel": "", "select-dashboard-available-panels": "", @@ -827,9 +858,11 @@ "update-datasource": "" }, "declare-incident-button": { + "content-grafana-incident-installed-configured-correctly": "", "declare-incident": "" }, "declare-incident-menu-item": { + "content-grafana-incident-installed-configured-correctly": "", "label-declare-incident": "" }, "delete-rule-modal": { @@ -865,6 +898,9 @@ }, "unknown": "" }, + "disabled-tooltip": { + "content-appear-compatible-datasources": "" + }, "draggable-rules-table": { "evals-to-start-alerting": "", "pending-period": "", @@ -895,10 +931,19 @@ "text-loading-template": "", "title-failed-to-fetch-notification-template": "" }, + "editor": { + "edit-alert-rule": "", + "edit-recording-rule": "", + "new-alert-rule": "", + "new-recording-rule": "" + }, "error-modal": { "failed-to-update-your-configuration": "", "title-something-went-wrong": "" }, + "error-summary-button": { + "content-show-all-errors": "" + }, "evaluation-behavior-summary": { "evaluate": "", "label-evaluate": "", @@ -929,7 +974,6 @@ "existing-rule-editor": { "sorry-permission": "", "sorry-this-rule-does-not-exist": "", - "text-loading-rule": "", "title-cannot-edit-rule": "", "title-failed-to-load-rule": "", "title-rule-not-found": "" @@ -1180,6 +1224,9 @@ "group-loader": { "group-load-failed": "" }, + "group-status": { + "content-the-group-is-being-deleted": "" + }, "header": { "tooltip-remove": "" }, @@ -1349,7 +1396,8 @@ "common-labels": "", "error-unable-to-fetch": "", "loading": "", - "title-error-fetching-the-state-history": "" + "title-error-fetching-the-state-history": "", + "tooltip-common-labels": "" }, "manage-permissions": { "button": "Toestemmingen beheren", @@ -1442,6 +1490,7 @@ }, "mute-timing-time-range": { "add-another-time-range": "", + "content-this-time-interval-is-disabled": "", "description-time-range": "", "label-end-time": "", "label-start-time": "", @@ -1597,6 +1646,9 @@ "plugin-integrations": { "tailored-apps": "" }, + "plugin-origin-badge": { + "tooltip-managed-by-plugin": "" + }, "policies": { "aria-label-collapse": "", "aria-label-expand": "", @@ -1696,6 +1748,9 @@ "prometheus-consistency-check": { "title-unable-to-check-the-rule-status": "" }, + "provisioned-tooltip": { + "content-provisioned-items-cannot-edited": "" + }, "provisioning": { "badge-tooltip-provenance": "Deze bron is geprovisioneerd via {{provenance}} en kan niet worden bewerkt via de gebruikersinterface", "badge-tooltip-standard": "Deze bron is geprovisioneerd en kan niet worden bewerkt via de gebruikersinterface", @@ -1931,14 +1986,14 @@ }, "threshold": { "recovery": { - "stop-alerting-above": "Waarschuwingen stoppen wanneer boven", - "stop-alerting-bellow": "Waarschuwingen stoppen wanneer onder", - "stop-alerting-equal": "Waarschuwingen stoppen wanneer gelijk aan", - "stop-alerting-inside-range": "Waarschuwingen stoppen wanneer binnen bereik", - "stop-alerting-less": "Waarschuwingen stoppen wanneer minder dan", - "stop-alerting-more": "Waarschuwingen stoppen wanneer meer dan", - "stop-alerting-not-equal": "Waarschuwingen stoppen wanneer gelijk aan", - "stop-alerting-outside-range": "Waarschuwingen stoppen wanneer buiten bereik", + "stop-alerting-above": "", + "stop-alerting-bellow": "", + "stop-alerting-equal": "", + "stop-alerting-inside-range": "", + "stop-alerting-less": "", + "stop-alerting-more": "", + "stop-alerting-not-equal": "", + "stop-alerting-outside-range": "", "title": "Aangepaste hersteldrempel" } } @@ -2038,7 +2093,10 @@ "for": "", "na": "", "paused": "Gepauzeerd", - "recording-rule": "Opnameregel" + "recording-rule": "Opnameregel", + "recording-rule-state": { + "content-recording-rule-evaluation-is-currently-paused": "" + } }, "rule-stats": { "firing": "", @@ -2282,6 +2340,8 @@ "refresh": "" }, "template-row": { + "content-templatefiles": "", + "content-templates": "", "tooltip-copy-template-group": "", "tooltip-delete-template-group": "", "tooltip-edit-template-group": "", @@ -2305,7 +2365,7 @@ }, "misconfigured-badge-text": "Verkeerd geconfigureerd", "misconfigured-warning": "Dit sjabloon is verkeerd geconfigureerd.", - "misconfigured-warning-details": "Sjablonen moeten worden gedefinieerd in zowel de sectie <1> als <4> van je waarschuwingsmanager-configuratie." + "misconfigured-warning-details": "" }, "templates-picker": { "button-edit": "", @@ -2350,6 +2410,10 @@ "type-selector-button": { "add-expression": "" }, + "unknown-contact-point-details": { + "unknown-contact-point": "", + "unknown-contact-point-tooltip": "" + }, "unknown-rule-list-item": { "title-unknown-rule-type": "" }, @@ -2450,6 +2514,7 @@ }, "api-keys-table": { "aria-label-delete-api-key": "", + "content-this-api-key-has-expired": "", "expires": "", "last-used-at": "", "migrate-to-service-account": "", @@ -4154,7 +4219,9 @@ }, "variable-check-indicator": { "aria-label-variable-referenced-dashboard": "", - "aria-label-variable-referenced-other-variables-dashboard": "" + "aria-label-variable-referenced-other-variables-dashboard": "", + "content-variable-not-referenced-other-variables-dashboard": "", + "content-variable-referenced-other-variables-dashboard": "" }, "variable-editor-form": { "aria-label-variable-editor-form": "", @@ -4214,6 +4281,7 @@ }, "versions-history-buttons": { "compare-versions": "", + "content-select-two-versions-to-start-comparing": "", "show-more-versions": "" }, "visualization-button": { @@ -4567,6 +4635,7 @@ } }, "correlation-editor-mode-bar": { + "content-correlations-editor-explore-experimental-feature": "", "exit-correlation-editor": "", "save": "" }, @@ -4724,6 +4793,9 @@ "label-select-query": "", "tooltip-select-query-visualize-table": "" }, + "logs-volume-panel": { + "content-streaming": "" + }, "logs-volume-panel-list": { "label-reload-log-volume": "", "loading": "", @@ -4923,6 +4995,7 @@ "label-tags": "", "placeholder-all-service-names": "", "placeholder-all-span-names": "", + "tooltip-collapse": "", "tooltip-duration": "", "tooltip-tags": "" }, @@ -5001,7 +5074,8 @@ "label-show-paths": "" }, "trace-view": { - "no-data": "" + "no-data": "", + "tooltip-copy-icon": "" }, "trace-view-container": { "title-trace": "" @@ -5115,7 +5189,8 @@ "default-password-alert": "", "new-password-label": "", "skip-button": "Overslaan", - "submit-button": "Verzenden" + "submit-button": "Verzenden", + "tooltip-skip-button": "" }, "contact-admin": "Ben je je gebruikersnaam of e-mailadres vergeten? Neem contact op met je Grafana-beheerder.", "email-sent": "Er is een e-mail met een resetlink verzonden naar het e-mailadres. Je ontvangt deze binnenkort.", @@ -5172,7 +5247,8 @@ "action-editor": { "button": { "confirm": "Bevestigen", - "confirm-action": "Actie bevestigen" + "confirm-action": "Actie bevestigen", + "style": "" }, "inline": { "add-action": "Actie toevoegen", @@ -5379,7 +5455,11 @@ "previous-page": "vorige pagina" }, "panel-chrome": { - "aria-label-toggle-collapse": "" + "aria-label-toggle-collapse": "", + "tooltip-cancel": "", + "tooltip-cancel-loading": "", + "tooltip-stop-streaming": "", + "tooltip-streaming": "" }, "panel-menu": { "label": "Menu voor paneel {{ title }}", @@ -5961,7 +6041,8 @@ "log-row-message": { "ellipsis": "… ", "more": "meer", - "see-details": "Zie logboek voor details" + "see-details": "Zie logboek voor details", + "tooltip-error": "" }, "log-rows": { "disable-popover": { @@ -6938,7 +7019,8 @@ "label-search": "", "label-state": "", "label-type": "", - "subtitle": "" + "subtitle": "", + "tooltip-filter-disabled": "" }, "catalog": { "no-updates-available": "Geen updates beschikbaar", @@ -6947,7 +7029,7 @@ "available-header": "Beschikbaar", "button": "Alles bijwerken ({{length}})", "cloud-update-message": "* Het kan een paar minuten duren voordat de plug-ins beschikbaar zijn voor gebruik.", - "error": "Fout bij updaten plug-in:", + "error": "", "error-status-text": "mislukt - zie foutmeldingen", "header": "Voor de volgende plug-ins zijn updates beschikbaar", "installed-header": "Geïnstalleerd", @@ -7157,6 +7239,9 @@ "feature-toggles-age": { "enable-in-config": "" }, + "input-suffix": { + "content-login-details-locked-because-managed-another": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -8090,10 +8175,12 @@ "title-disable-service-account": "" }, "token-expiration": { + "content-this-token-has-expired": "", "expired-label": "", "never": "" }, "token-revoked": { + "content-token-publicly-exposed-please-rotate": "", "revoked-label": "" } }, @@ -8499,7 +8586,7 @@ "range-content": { "apply-button": "Tijdsbereik toepassen", "default-error": "", - "fiscal-year": "Boekjaar", + "fiscal-year": "", "from-input": "Van", "open-input-calendar": "Open kalender", "range-error": "'Van' mag niet na 'Tot' zijn", @@ -8951,6 +9038,7 @@ "loading": "Bezig met laden ...", "no-unknowns": "Geen hernoemde of ontbrekende variabelen gevonden.", "renamed-or-missing-variables": "Hernoemde of ontbrekende variabelen", + "tooltip-renamed-or-missing-variables": "", "variable": "Variabele" }, "variable-check-indicator": { diff --git a/public/locales/pl-PL/grafana.json b/public/locales/pl-PL/grafana.json index 43c7a1c70ca..e105dc8a62c 100644 --- a/public/locales/pl-PL/grafana.json +++ b/public/locales/pl-PL/grafana.json @@ -21,7 +21,9 @@ "permission-list-item": { "inherited": "Dziedziczone z folderu", "locked-aria-label": "", - "remove-aria-label": "" + "remove-aria-label": "", + "tooltip-inherited-permission": "", + "tooltip-provisioned-permission": "" }, "permissions": { "add-label": "Dodaj uprawnienie", @@ -42,6 +44,11 @@ }, "actions": { "action-editor": { + "button": { + "style": { + "background-color": "" + } + }, "label-headers": "", "label-url": "", "placeholder-url": "" @@ -70,6 +77,7 @@ "confirm-modal-body-2": "", "get-stage-cell": { "beta": "", + "content-general-availability": "", "deprecated": "", "ga": "" }, @@ -128,6 +136,12 @@ "test-mapping-heading": "Przetestuj mapowanie użytkowników", "test-mapping-run-button": "Uruchom" }, + "ldap-connection-status": { + "columns": { + "content-connection-is-available": "", + "content-connection-is-not-available": "" + } + }, "ldap-error-box": { "title-connection-error": "" }, @@ -161,6 +175,9 @@ "title": "Synchronizacja LDAP" }, "ldap-user-groups": { + "columns": { + "content-no-matching-organizations-found": "" + }, "no-org-found": "" }, "ldap-user-info": { @@ -284,12 +301,14 @@ }, "users-table": { "columns": { + "content-grafana-admin": "", "title-edit-user": "" }, "edit-aria-label": "", "edit-tooltip": "", "last-seen-never": "Nigdy", - "no-licensed-roles": "Nieprzypisane" + "no-licensed-roles": "Nieprzypisane", + "tooltip-assigned-role": "" } }, "alert-labels": { @@ -416,11 +435,9 @@ }, "alert-rule-form": { "action-buttons": { - "delete": "", "edit-yaml": "", - "save-exit": "" - }, - "title-delete-rule": "" + "save": "" + } }, "alert-rule-name-and-metric": { "aria-label-name": "", @@ -452,6 +469,7 @@ "unprocessed-the-alert-is-received": "" }, "alert-state-tag": { + "content-alert-evaluation-is-currently-paused": "", "paused": "" }, "alert-warning": { @@ -548,6 +566,9 @@ "warning-restore-manually": "Ręcznie przywracasz starą wersję tej reguły alertu. Przed zapisaniem definicji reguły dokładnie zapoznaj się ze zmianami.", "warning-restore-manually-title": "Ręczne przywracanie reguły" }, + "all-matches-indicator": { + "content-this-policy-matches-all-labels": "" + }, "am-root-route-form": { "am-group-description-label": "", "am-group-interval-description": "", @@ -660,6 +681,9 @@ "of": "", "when": "" }, + "clear-filter-button-object-renderer": { + "content-clear-filter": "" + }, "clone-rule-button": { "title-copy": "" }, @@ -790,6 +814,9 @@ "contactPointFilter": { "label": "Punkt kontaktowy" }, + "continue-matching-indicator": { + "content-route-continue-matching-other-policies": "" + }, "copy-to-clipboard": "Kopiuj „{{label}}” do schowka", "create-metadata": { "view-dashboard": "", @@ -818,6 +845,10 @@ "current-selection-dashboard": "", "current-selection-panel": "", "fallback-dashboards-string": "", + "panel-row": { + "content-panel-valid-identifier": "", + "tooltip-alert-tab-support": "" + }, "placeholder-search-dashboard": "", "placeholder-search-panel": "", "select-dashboard-available-panels": "", @@ -839,9 +870,11 @@ "update-datasource": "" }, "declare-incident-button": { + "content-grafana-incident-installed-configured-correctly": "", "declare-incident": "" }, "declare-incident-menu-item": { + "content-grafana-incident-installed-configured-correctly": "", "label-declare-incident": "" }, "delete-rule-modal": { @@ -877,6 +910,9 @@ }, "unknown": "" }, + "disabled-tooltip": { + "content-appear-compatible-datasources": "" + }, "draggable-rules-table": { "evals-to-start-alerting": "Oceny umożliwiające uruchomienie alertu", "pending-period": "Okres oczekiwania", @@ -907,10 +943,19 @@ "text-loading-template": "", "title-failed-to-fetch-notification-template": "" }, + "editor": { + "edit-alert-rule": "", + "edit-recording-rule": "", + "new-alert-rule": "", + "new-recording-rule": "" + }, "error-modal": { "failed-to-update-your-configuration": "", "title-something-went-wrong": "" }, + "error-summary-button": { + "content-show-all-errors": "" + }, "evaluation-behavior-summary": { "evaluate": "", "label-evaluate": "", @@ -941,7 +986,6 @@ "existing-rule-editor": { "sorry-permission": "", "sorry-this-rule-does-not-exist": "", - "text-loading-rule": "", "title-cannot-edit-rule": "", "title-failed-to-load-rule": "", "title-rule-not-found": "" @@ -1192,6 +1236,9 @@ "group-loader": { "group-load-failed": "" }, + "group-status": { + "content-the-group-is-being-deleted": "" + }, "header": { "tooltip-remove": "" }, @@ -1363,7 +1410,8 @@ "common-labels": "", "error-unable-to-fetch": "", "loading": "", - "title-error-fetching-the-state-history": "" + "title-error-fetching-the-state-history": "", + "tooltip-common-labels": "" }, "manage-permissions": { "button": "Zarządzaj uprawnieniami", @@ -1456,6 +1504,7 @@ }, "mute-timing-time-range": { "add-another-time-range": "", + "content-this-time-interval-is-disabled": "", "description-time-range": "", "label-end-time": "", "label-start-time": "", @@ -1611,6 +1660,9 @@ "plugin-integrations": { "tailored-apps": "" }, + "plugin-origin-badge": { + "tooltip-managed-by-plugin": "" + }, "policies": { "aria-label-collapse": "", "aria-label-expand": "", @@ -1714,6 +1766,9 @@ "prometheus-consistency-check": { "title-unable-to-check-the-rule-status": "" }, + "provisioned-tooltip": { + "content-provisioned-items-cannot-edited": "" + }, "provisioning": { "badge-tooltip-provenance": "Ten zasób został skonfigurowany za pośrednictwem {{provenance}} i nie można go edytować z poziomu interfejsu użytkownika", "badge-tooltip-standard": "Ten zasób został skonfigurowany i nie można go edytować z poziomu interfejsu użytkownika", @@ -1951,14 +2006,14 @@ }, "threshold": { "recovery": { - "stop-alerting-above": "Zatrzymaj alerty, gdy powyżej", - "stop-alerting-bellow": "Zatrzymaj alerty, gdy poniżej", - "stop-alerting-equal": "Zatrzymaj alerty, gdy równe", - "stop-alerting-inside-range": "Zatrzymaj alerty, gdy w zakresie", - "stop-alerting-less": "Zatrzymaj alerty, gdy mniej niż", - "stop-alerting-more": "Zatrzymaj alerty, gdy więcej niż", - "stop-alerting-not-equal": "Zatrzymaj alerty, gdy różne od", - "stop-alerting-outside-range": "Zatrzymaj alerty, gdy poza zakresem", + "stop-alerting-above": "", + "stop-alerting-bellow": "", + "stop-alerting-equal": "", + "stop-alerting-inside-range": "", + "stop-alerting-less": "", + "stop-alerting-more": "", + "stop-alerting-not-equal": "", + "stop-alerting-outside-range": "", "title": "Niestandardowy próg odzysku" } } @@ -2062,7 +2117,10 @@ "for": "", "na": "", "paused": "Wstrzymano", - "recording-rule": "Reguła rejestracji" + "recording-rule": "Reguła rejestracji", + "recording-rule-state": { + "content-recording-rule-evaluation-is-currently-paused": "" + } }, "rule-stats": { "firing": "", @@ -2308,6 +2366,8 @@ "refresh": "" }, "template-row": { + "content-templatefiles": "", + "content-templates": "", "tooltip-copy-template-group": "", "tooltip-delete-template-group": "", "tooltip-edit-template-group": "", @@ -2331,7 +2391,7 @@ }, "misconfigured-badge-text": "Źle skonfigurowane", "misconfigured-warning": "Ten szablon jest źle skonfigurowany.", - "misconfigured-warning-details": "Szablony muszą być zdefiniowane zarówno w sekcjach <1>, jak i <4> konfiguracji menedżera alertów." + "misconfigured-warning-details": "" }, "templates-picker": { "button-edit": "", @@ -2376,6 +2436,10 @@ "type-selector-button": { "add-expression": "" }, + "unknown-contact-point-details": { + "unknown-contact-point": "", + "unknown-contact-point-tooltip": "" + }, "unknown-rule-list-item": { "title-unknown-rule-type": "" }, @@ -2476,6 +2540,7 @@ }, "api-keys-table": { "aria-label-delete-api-key": "", + "content-this-api-key-has-expired": "", "expires": "", "last-used-at": "", "migrate-to-service-account": "", @@ -4196,7 +4261,9 @@ }, "variable-check-indicator": { "aria-label-variable-referenced-dashboard": "", - "aria-label-variable-referenced-other-variables-dashboard": "" + "aria-label-variable-referenced-other-variables-dashboard": "", + "content-variable-not-referenced-other-variables-dashboard": "", + "content-variable-referenced-other-variables-dashboard": "" }, "variable-editor-form": { "aria-label-variable-editor-form": "", @@ -4256,6 +4323,7 @@ }, "versions-history-buttons": { "compare-versions": "", + "content-select-two-versions-to-start-comparing": "", "show-more-versions": "" }, "visualization-button": { @@ -4609,6 +4677,7 @@ } }, "correlation-editor-mode-bar": { + "content-correlations-editor-explore-experimental-feature": "", "exit-correlation-editor": "", "save": "" }, @@ -4766,6 +4835,9 @@ "label-select-query": "", "tooltip-select-query-visualize-table": "" }, + "logs-volume-panel": { + "content-streaming": "" + }, "logs-volume-panel-list": { "label-reload-log-volume": "", "loading": "", @@ -4965,6 +5037,7 @@ "label-tags": "", "placeholder-all-service-names": "", "placeholder-all-span-names": "", + "tooltip-collapse": "", "tooltip-duration": "", "tooltip-tags": "" }, @@ -5043,7 +5116,8 @@ "label-show-paths": "" }, "trace-view": { - "no-data": "" + "no-data": "", + "tooltip-copy-icon": "" }, "trace-view-container": { "title-trace": "" @@ -5157,7 +5231,8 @@ "default-password-alert": "", "new-password-label": "", "skip-button": "Pomiń", - "submit-button": "Prześlij" + "submit-button": "Prześlij", + "tooltip-skip-button": "" }, "contact-admin": "Nie pamiętasz nazwy użytkownika lub adresu e-mail? Skontaktuj się z administratorem usługi Grafana.", "email-sent": "Na adres e-mail została wysłana wiadomość z linkiem do ustawienia nowego hasła. Powinna się niedługo pojawić w skrzynce.", @@ -5214,7 +5289,8 @@ "action-editor": { "button": { "confirm": "Potwierdź", - "confirm-action": "Potwierdź działanie" + "confirm-action": "Potwierdź działanie", + "style": "" }, "inline": { "add-action": "Dodaj działanie", @@ -5421,7 +5497,11 @@ "previous-page": "poprzednia strona" }, "panel-chrome": { - "aria-label-toggle-collapse": "" + "aria-label-toggle-collapse": "", + "tooltip-cancel": "", + "tooltip-cancel-loading": "", + "tooltip-stop-streaming": "", + "tooltip-streaming": "" }, "panel-menu": { "label": "Menu panelu {{ title }}", @@ -6013,7 +6093,8 @@ "log-row-message": { "ellipsis": "… ", "more": "więcej", - "see-details": "Zobacz szczegóły logu" + "see-details": "Zobacz szczegóły logu", + "tooltip-error": "" }, "log-rows": { "disable-popover": { @@ -6990,7 +7071,8 @@ "label-search": "", "label-state": "", "label-type": "", - "subtitle": "" + "subtitle": "", + "tooltip-filter-disabled": "" }, "catalog": { "no-updates-available": "Brak dostępnych aktualizacji", @@ -6999,7 +7081,7 @@ "available-header": "Dostępne", "button": "Zaktualizuj wszystko ({{length}})", "cloud-update-message": "* Może minąć kilka minut, zanim wtyczki będą dostępne do użytku.", - "error": "Błąd aktualizacji wtyczki:", + "error": "", "error-status-text": "niepowodzenie – zobacz komunikaty o błędach", "header": "Dla następujących wtyczek są dostępne aktualizacje", "installed-header": "Zainstalowane", @@ -7209,6 +7291,9 @@ "feature-toggles-age": { "enable-in-config": "" }, + "input-suffix": { + "content-login-details-locked-because-managed-another": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -8152,10 +8237,12 @@ "title-disable-service-account": "" }, "token-expiration": { + "content-this-token-has-expired": "", "expired-label": "", "never": "" }, "token-revoked": { + "content-token-publicly-exposed-please-rotate": "", "revoked-label": "" } }, @@ -8561,7 +8648,7 @@ "range-content": { "apply-button": "Zastosuj zakres czasu", "default-error": "", - "fiscal-year": "Rok podatkowy", + "fiscal-year": "", "from-input": "Od", "open-input-calendar": "Otwórz kalendarz", "range-error": "Wartość „Od” nie może następować po wartości „Do”", @@ -9013,6 +9100,7 @@ "loading": "Ładowanie…", "no-unknowns": "Nie znaleziono przemianowanych ani brakujących zmiennych.", "renamed-or-missing-variables": "Przemianowane lub brakujące zmienne", + "tooltip-renamed-or-missing-variables": "", "variable": "Zmienna" }, "variable-check-indicator": { diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index dbf76560d65..b328bb2a806 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -21,7 +21,9 @@ "permission-list-item": { "inherited": "Herdados da pasta", "locked-aria-label": "", - "remove-aria-label": "" + "remove-aria-label": "", + "tooltip-inherited-permission": "", + "tooltip-provisioned-permission": "" }, "permissions": { "add-label": "Adicionar uma permissão", @@ -42,6 +44,11 @@ }, "actions": { "action-editor": { + "button": { + "style": { + "background-color": "" + } + }, "label-headers": "", "label-url": "", "placeholder-url": "" @@ -70,6 +77,7 @@ "confirm-modal-body-2": "", "get-stage-cell": { "beta": "", + "content-general-availability": "", "deprecated": "", "ga": "" }, @@ -128,6 +136,12 @@ "test-mapping-heading": "Mapeamento de usuário de teste", "test-mapping-run-button": "Iniciar" }, + "ldap-connection-status": { + "columns": { + "content-connection-is-available": "", + "content-connection-is-not-available": "" + } + }, "ldap-error-box": { "title-connection-error": "" }, @@ -161,6 +175,9 @@ "title": "Sincronização do LDAP" }, "ldap-user-groups": { + "columns": { + "content-no-matching-organizations-found": "" + }, "no-org-found": "" }, "ldap-user-info": { @@ -284,12 +301,14 @@ }, "users-table": { "columns": { + "content-grafana-admin": "", "title-edit-user": "" }, "edit-aria-label": "", "edit-tooltip": "", "last-seen-never": "", - "no-licensed-roles": "Não atribuído" + "no-licensed-roles": "Não atribuído", + "tooltip-assigned-role": "" } }, "alert-labels": { @@ -408,11 +427,9 @@ }, "alert-rule-form": { "action-buttons": { - "delete": "", "edit-yaml": "", - "save-exit": "" - }, - "title-delete-rule": "" + "save": "" + } }, "alert-rule-name-and-metric": { "aria-label-name": "", @@ -444,6 +461,7 @@ "unprocessed-the-alert-is-received": "" }, "alert-state-tag": { + "content-alert-evaluation-is-currently-paused": "", "paused": "" }, "alert-warning": { @@ -540,6 +558,9 @@ "warning-restore-manually": "Você está restaurando manualmente uma versão antiga desta regra de alerta. Revise as alterações com cuidado antes de salvar a definição da regra.", "warning-restore-manually-title": "Restaurando a regra manualmente" }, + "all-matches-indicator": { + "content-this-policy-matches-all-labels": "" + }, "am-root-route-form": { "am-group-description-label": "", "am-group-interval-description": "", @@ -652,6 +673,9 @@ "of": "", "when": "" }, + "clear-filter-button-object-renderer": { + "content-clear-filter": "" + }, "clone-rule-button": { "title-copy": "" }, @@ -778,6 +802,9 @@ "contactPointFilter": { "label": "Ponto de contato" }, + "continue-matching-indicator": { + "content-route-continue-matching-other-policies": "" + }, "copy-to-clipboard": "Copiar \"{{label}}\" para a área de transferência", "create-metadata": { "view-dashboard": "", @@ -806,6 +833,10 @@ "current-selection-dashboard": "", "current-selection-panel": "", "fallback-dashboards-string": "", + "panel-row": { + "content-panel-valid-identifier": "", + "tooltip-alert-tab-support": "" + }, "placeholder-search-dashboard": "", "placeholder-search-panel": "", "select-dashboard-available-panels": "", @@ -827,9 +858,11 @@ "update-datasource": "" }, "declare-incident-button": { + "content-grafana-incident-installed-configured-correctly": "", "declare-incident": "" }, "declare-incident-menu-item": { + "content-grafana-incident-installed-configured-correctly": "", "label-declare-incident": "" }, "delete-rule-modal": { @@ -865,6 +898,9 @@ }, "unknown": "" }, + "disabled-tooltip": { + "content-appear-compatible-datasources": "" + }, "draggable-rules-table": { "evals-to-start-alerting": "Avaliações para começar a alertar", "pending-period": "Período pendente", @@ -895,10 +931,19 @@ "text-loading-template": "", "title-failed-to-fetch-notification-template": "" }, + "editor": { + "edit-alert-rule": "", + "edit-recording-rule": "", + "new-alert-rule": "", + "new-recording-rule": "" + }, "error-modal": { "failed-to-update-your-configuration": "", "title-something-went-wrong": "" }, + "error-summary-button": { + "content-show-all-errors": "" + }, "evaluation-behavior-summary": { "evaluate": "", "label-evaluate": "", @@ -929,7 +974,6 @@ "existing-rule-editor": { "sorry-permission": "", "sorry-this-rule-does-not-exist": "", - "text-loading-rule": "", "title-cannot-edit-rule": "", "title-failed-to-load-rule": "", "title-rule-not-found": "" @@ -1180,6 +1224,9 @@ "group-loader": { "group-load-failed": "" }, + "group-status": { + "content-the-group-is-being-deleted": "" + }, "header": { "tooltip-remove": "" }, @@ -1349,7 +1396,8 @@ "common-labels": "", "error-unable-to-fetch": "", "loading": "", - "title-error-fetching-the-state-history": "" + "title-error-fetching-the-state-history": "", + "tooltip-common-labels": "" }, "manage-permissions": { "button": "", @@ -1442,6 +1490,7 @@ }, "mute-timing-time-range": { "add-another-time-range": "", + "content-this-time-interval-is-disabled": "", "description-time-range": "", "label-end-time": "", "label-start-time": "", @@ -1597,6 +1646,9 @@ "plugin-integrations": { "tailored-apps": "" }, + "plugin-origin-badge": { + "tooltip-managed-by-plugin": "" + }, "policies": { "aria-label-collapse": "", "aria-label-expand": "", @@ -1696,6 +1748,9 @@ "prometheus-consistency-check": { "title-unable-to-check-the-rule-status": "" }, + "provisioned-tooltip": { + "content-provisioned-items-cannot-edited": "" + }, "provisioning": { "badge-tooltip-provenance": "Este recurso foi provisionado via {{provenance}} e não pode ser editado por meio da interface do usuário", "badge-tooltip-standard": "Este recurso foi provisionado e não pode ser editado por meio da interface do usuário", @@ -1931,14 +1986,14 @@ }, "threshold": { "recovery": { - "stop-alerting-above": "Parar de alertar quando for superior a", - "stop-alerting-bellow": "Parar de alertar quando for inferior a", - "stop-alerting-equal": "Parar de alertar quando for igual a", - "stop-alerting-inside-range": "Parar de alertar quando estiver dentro do intervalo", - "stop-alerting-less": "Parar de alertar quando for menor que", - "stop-alerting-more": "Parar de alertar quando for maior que", - "stop-alerting-not-equal": "Parar de alertar quando não for igual a", - "stop-alerting-outside-range": "Parar de alertar quando estiver fora do intervalo", + "stop-alerting-above": "", + "stop-alerting-bellow": "", + "stop-alerting-equal": "", + "stop-alerting-inside-range": "", + "stop-alerting-less": "", + "stop-alerting-more": "", + "stop-alerting-not-equal": "", + "stop-alerting-outside-range": "", "title": "Limite de recuperação personalizado" } } @@ -2038,7 +2093,10 @@ "for": "", "na": "", "paused": "Pausado", - "recording-rule": "Regra de registro" + "recording-rule": "Regra de registro", + "recording-rule-state": { + "content-recording-rule-evaluation-is-currently-paused": "" + } }, "rule-stats": { "firing": "", @@ -2282,6 +2340,8 @@ "refresh": "" }, "template-row": { + "content-templatefiles": "", + "content-templates": "", "tooltip-copy-template-group": "", "tooltip-delete-template-group": "", "tooltip-edit-template-group": "", @@ -2305,7 +2365,7 @@ }, "misconfigured-badge-text": "Configuração incorreta", "misconfigured-warning": "Este modelo está configurado incorretamente.", - "misconfigured-warning-details": "Os modelos devem ser definidos nas seções <1> e <4> da configuração do seu alertmanager." + "misconfigured-warning-details": "" }, "templates-picker": { "button-edit": "", @@ -2350,6 +2410,10 @@ "type-selector-button": { "add-expression": "" }, + "unknown-contact-point-details": { + "unknown-contact-point": "", + "unknown-contact-point-tooltip": "" + }, "unknown-rule-list-item": { "title-unknown-rule-type": "" }, @@ -2450,6 +2514,7 @@ }, "api-keys-table": { "aria-label-delete-api-key": "", + "content-this-api-key-has-expired": "", "expires": "", "last-used-at": "", "migrate-to-service-account": "", @@ -4154,7 +4219,9 @@ }, "variable-check-indicator": { "aria-label-variable-referenced-dashboard": "", - "aria-label-variable-referenced-other-variables-dashboard": "" + "aria-label-variable-referenced-other-variables-dashboard": "", + "content-variable-not-referenced-other-variables-dashboard": "", + "content-variable-referenced-other-variables-dashboard": "" }, "variable-editor-form": { "aria-label-variable-editor-form": "", @@ -4214,6 +4281,7 @@ }, "versions-history-buttons": { "compare-versions": "", + "content-select-two-versions-to-start-comparing": "", "show-more-versions": "" }, "visualization-button": { @@ -4567,6 +4635,7 @@ } }, "correlation-editor-mode-bar": { + "content-correlations-editor-explore-experimental-feature": "", "exit-correlation-editor": "", "save": "" }, @@ -4724,6 +4793,9 @@ "label-select-query": "", "tooltip-select-query-visualize-table": "" }, + "logs-volume-panel": { + "content-streaming": "" + }, "logs-volume-panel-list": { "label-reload-log-volume": "", "loading": "", @@ -4923,6 +4995,7 @@ "label-tags": "", "placeholder-all-service-names": "", "placeholder-all-span-names": "", + "tooltip-collapse": "", "tooltip-duration": "", "tooltip-tags": "" }, @@ -5001,7 +5074,8 @@ "label-show-paths": "" }, "trace-view": { - "no-data": "" + "no-data": "", + "tooltip-copy-icon": "" }, "trace-view-container": { "title-trace": "" @@ -5115,7 +5189,8 @@ "default-password-alert": "", "new-password-label": "", "skip-button": "Pular", - "submit-button": "Enviar" + "submit-button": "Enviar", + "tooltip-skip-button": "" }, "contact-admin": "Esqueceu seu nome de usuário ou e-mail? Entre em contato com o administrador da Grafana.", "email-sent": "Enviamos um e-mail com um link de redefinição. Você receberá o e-mail em breve.", @@ -5172,7 +5247,8 @@ "action-editor": { "button": { "confirm": "Confirmar", - "confirm-action": "Confirmar ação" + "confirm-action": "Confirmar ação", + "style": "" }, "inline": { "add-action": "Adicionar ação", @@ -5379,7 +5455,11 @@ "previous-page": "página anterior" }, "panel-chrome": { - "aria-label-toggle-collapse": "" + "aria-label-toggle-collapse": "", + "tooltip-cancel": "", + "tooltip-cancel-loading": "", + "tooltip-stop-streaming": "", + "tooltip-streaming": "" }, "panel-menu": { "label": "Menu para o painel {{ title }}", @@ -5961,7 +6041,8 @@ "log-row-message": { "ellipsis": "… ", "more": "mais", - "see-details": "Veja os detalhes do log" + "see-details": "Veja os detalhes do log", + "tooltip-error": "" }, "log-rows": { "disable-popover": { @@ -6938,7 +7019,8 @@ "label-search": "", "label-state": "", "label-type": "", - "subtitle": "" + "subtitle": "", + "tooltip-filter-disabled": "" }, "catalog": { "no-updates-available": "Nenhuma atualização disponível", @@ -6947,7 +7029,7 @@ "available-header": "Disponível", "button": "Atualizar tudo ({{length}})", "cloud-update-message": "* Pode levar alguns minutos para que os plug-ins estejam disponíveis para uso.", - "error": "Erro ao atualizar o plug-in:", + "error": "", "error-status-text": "erro - veja as mensagens", "header": "Há atualizações disponíveis para os seguintes plug-ins", "installed-header": "Instalado", @@ -7157,6 +7239,9 @@ "feature-toggles-age": { "enable-in-config": "" }, + "input-suffix": { + "content-login-details-locked-because-managed-another": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -8090,10 +8175,12 @@ "title-disable-service-account": "" }, "token-expiration": { + "content-this-token-has-expired": "", "expired-label": "", "never": "" }, "token-revoked": { + "content-token-publicly-exposed-please-rotate": "", "revoked-label": "" } }, @@ -8499,7 +8586,7 @@ "range-content": { "apply-button": "Aplicar intervalo de tempo", "default-error": "", - "fiscal-year": "Ano fiscal", + "fiscal-year": "", "from-input": "De", "open-input-calendar": "Abrir calendário", "range-error": "\"De\" não pode ser após \"Para\"", @@ -8951,6 +9038,7 @@ "loading": "", "no-unknowns": "Nenhuma variável renomeada ou ausente foi encontrada.", "renamed-or-missing-variables": "Variáveis renomeadas ou ausentes", + "tooltip-renamed-or-missing-variables": "", "variable": "Variável" }, "variable-check-indicator": { diff --git a/public/locales/pt-PT/grafana.json b/public/locales/pt-PT/grafana.json index 565a52159e3..34c16702b31 100644 --- a/public/locales/pt-PT/grafana.json +++ b/public/locales/pt-PT/grafana.json @@ -21,7 +21,9 @@ "permission-list-item": { "inherited": "Herdado da pasta", "locked-aria-label": "", - "remove-aria-label": "" + "remove-aria-label": "", + "tooltip-inherited-permission": "", + "tooltip-provisioned-permission": "" }, "permissions": { "add-label": "Adicionar uma permissão", @@ -42,6 +44,11 @@ }, "actions": { "action-editor": { + "button": { + "style": { + "background-color": "" + } + }, "label-headers": "", "label-url": "", "placeholder-url": "" @@ -70,6 +77,7 @@ "confirm-modal-body-2": "", "get-stage-cell": { "beta": "", + "content-general-availability": "", "deprecated": "", "ga": "" }, @@ -128,6 +136,12 @@ "test-mapping-heading": "Mapeamento de utilizador de teste", "test-mapping-run-button": "Executar" }, + "ldap-connection-status": { + "columns": { + "content-connection-is-available": "", + "content-connection-is-not-available": "" + } + }, "ldap-error-box": { "title-connection-error": "" }, @@ -161,6 +175,9 @@ "title": "Sincronização do LDAP" }, "ldap-user-groups": { + "columns": { + "content-no-matching-organizations-found": "" + }, "no-org-found": "" }, "ldap-user-info": { @@ -284,12 +301,14 @@ }, "users-table": { "columns": { + "content-grafana-admin": "", "title-edit-user": "" }, "edit-aria-label": "", "edit-tooltip": "", "last-seen-never": "Nunca", - "no-licensed-roles": "Não atribuído" + "no-licensed-roles": "Não atribuído", + "tooltip-assigned-role": "" } }, "alert-labels": { @@ -408,11 +427,9 @@ }, "alert-rule-form": { "action-buttons": { - "delete": "", "edit-yaml": "", - "save-exit": "" - }, - "title-delete-rule": "" + "save": "" + } }, "alert-rule-name-and-metric": { "aria-label-name": "", @@ -444,6 +461,7 @@ "unprocessed-the-alert-is-received": "" }, "alert-state-tag": { + "content-alert-evaluation-is-currently-paused": "", "paused": "" }, "alert-warning": { @@ -540,6 +558,9 @@ "warning-restore-manually": "Está a restaurar manualmente uma versão antiga desta regra de alerta. Reveja as alterações cuidadosamente antes de guardar a definição da regra.", "warning-restore-manually-title": "Restaurar a regra manualmente" }, + "all-matches-indicator": { + "content-this-policy-matches-all-labels": "" + }, "am-root-route-form": { "am-group-description-label": "", "am-group-interval-description": "", @@ -652,6 +673,9 @@ "of": "", "when": "" }, + "clear-filter-button-object-renderer": { + "content-clear-filter": "" + }, "clone-rule-button": { "title-copy": "" }, @@ -778,6 +802,9 @@ "contactPointFilter": { "label": "Ponto de contacto" }, + "continue-matching-indicator": { + "content-route-continue-matching-other-policies": "" + }, "copy-to-clipboard": "Copiar a \"{{label}}\" para a área de transferência", "create-metadata": { "view-dashboard": "", @@ -806,6 +833,10 @@ "current-selection-dashboard": "", "current-selection-panel": "", "fallback-dashboards-string": "", + "panel-row": { + "content-panel-valid-identifier": "", + "tooltip-alert-tab-support": "" + }, "placeholder-search-dashboard": "", "placeholder-search-panel": "", "select-dashboard-available-panels": "", @@ -827,9 +858,11 @@ "update-datasource": "" }, "declare-incident-button": { + "content-grafana-incident-installed-configured-correctly": "", "declare-incident": "" }, "declare-incident-menu-item": { + "content-grafana-incident-installed-configured-correctly": "", "label-declare-incident": "" }, "delete-rule-modal": { @@ -865,6 +898,9 @@ }, "unknown": "" }, + "disabled-tooltip": { + "content-appear-compatible-datasources": "" + }, "draggable-rules-table": { "evals-to-start-alerting": "Avaliações para iniciar alerta", "pending-period": "Período pendente", @@ -895,10 +931,19 @@ "text-loading-template": "", "title-failed-to-fetch-notification-template": "" }, + "editor": { + "edit-alert-rule": "", + "edit-recording-rule": "", + "new-alert-rule": "", + "new-recording-rule": "" + }, "error-modal": { "failed-to-update-your-configuration": "", "title-something-went-wrong": "" }, + "error-summary-button": { + "content-show-all-errors": "" + }, "evaluation-behavior-summary": { "evaluate": "", "label-evaluate": "", @@ -929,7 +974,6 @@ "existing-rule-editor": { "sorry-permission": "", "sorry-this-rule-does-not-exist": "", - "text-loading-rule": "", "title-cannot-edit-rule": "", "title-failed-to-load-rule": "", "title-rule-not-found": "" @@ -1180,6 +1224,9 @@ "group-loader": { "group-load-failed": "" }, + "group-status": { + "content-the-group-is-being-deleted": "" + }, "header": { "tooltip-remove": "" }, @@ -1349,7 +1396,8 @@ "common-labels": "", "error-unable-to-fetch": "", "loading": "", - "title-error-fetching-the-state-history": "" + "title-error-fetching-the-state-history": "", + "tooltip-common-labels": "" }, "manage-permissions": { "button": "Gerir permissões", @@ -1442,6 +1490,7 @@ }, "mute-timing-time-range": { "add-another-time-range": "", + "content-this-time-interval-is-disabled": "", "description-time-range": "", "label-end-time": "", "label-start-time": "", @@ -1597,6 +1646,9 @@ "plugin-integrations": { "tailored-apps": "" }, + "plugin-origin-badge": { + "tooltip-managed-by-plugin": "" + }, "policies": { "aria-label-collapse": "", "aria-label-expand": "", @@ -1696,6 +1748,9 @@ "prometheus-consistency-check": { "title-unable-to-check-the-rule-status": "" }, + "provisioned-tooltip": { + "content-provisioned-items-cannot-edited": "" + }, "provisioning": { "badge-tooltip-provenance": "Este recurso foi provisionado através de {{provenance}} e não pode ser editado através da interface do utilizador", "badge-tooltip-standard": "Este recurso foi provisionado e não pode ser editado através da interface do utilizador", @@ -1931,14 +1986,14 @@ }, "threshold": { "recovery": { - "stop-alerting-above": "Parar de alertar quando superior a", - "stop-alerting-bellow": "Parar de alertar quando inferior a", - "stop-alerting-equal": "Parar de alertar quando igual a", - "stop-alerting-inside-range": "Parar de alertar quando dentro do intervalo", - "stop-alerting-less": "Parar de alertar quando menor que", - "stop-alerting-more": "Parar de alertar quando maior que", - "stop-alerting-not-equal": "Parar de alertar quando não igual a", - "stop-alerting-outside-range": "Parar de alertar quando fora do intervalo", + "stop-alerting-above": "", + "stop-alerting-bellow": "", + "stop-alerting-equal": "", + "stop-alerting-inside-range": "", + "stop-alerting-less": "", + "stop-alerting-more": "", + "stop-alerting-not-equal": "", + "stop-alerting-outside-range": "", "title": "Limite de recuperação personalizado" } } @@ -2038,7 +2093,10 @@ "for": "", "na": "", "paused": "Em pausa", - "recording-rule": "Regra de gravação" + "recording-rule": "Regra de gravação", + "recording-rule-state": { + "content-recording-rule-evaluation-is-currently-paused": "" + } }, "rule-stats": { "firing": "", @@ -2282,6 +2340,8 @@ "refresh": "" }, "template-row": { + "content-templatefiles": "", + "content-templates": "", "tooltip-copy-template-group": "", "tooltip-delete-template-group": "", "tooltip-edit-template-group": "", @@ -2305,7 +2365,7 @@ }, "misconfigured-badge-text": "Mal configurado", "misconfigured-warning": "Este modelo está mal configurado.", - "misconfigured-warning-details": "Os modelos devem ser definidos nas secções <1> e <4> da configuração do seu Alertmanager." + "misconfigured-warning-details": "" }, "templates-picker": { "button-edit": "", @@ -2350,6 +2410,10 @@ "type-selector-button": { "add-expression": "" }, + "unknown-contact-point-details": { + "unknown-contact-point": "", + "unknown-contact-point-tooltip": "" + }, "unknown-rule-list-item": { "title-unknown-rule-type": "" }, @@ -2450,6 +2514,7 @@ }, "api-keys-table": { "aria-label-delete-api-key": "", + "content-this-api-key-has-expired": "", "expires": "", "last-used-at": "", "migrate-to-service-account": "", @@ -4154,7 +4219,9 @@ }, "variable-check-indicator": { "aria-label-variable-referenced-dashboard": "", - "aria-label-variable-referenced-other-variables-dashboard": "" + "aria-label-variable-referenced-other-variables-dashboard": "", + "content-variable-not-referenced-other-variables-dashboard": "", + "content-variable-referenced-other-variables-dashboard": "" }, "variable-editor-form": { "aria-label-variable-editor-form": "", @@ -4214,6 +4281,7 @@ }, "versions-history-buttons": { "compare-versions": "", + "content-select-two-versions-to-start-comparing": "", "show-more-versions": "" }, "visualization-button": { @@ -4567,6 +4635,7 @@ } }, "correlation-editor-mode-bar": { + "content-correlations-editor-explore-experimental-feature": "", "exit-correlation-editor": "", "save": "" }, @@ -4724,6 +4793,9 @@ "label-select-query": "", "tooltip-select-query-visualize-table": "" }, + "logs-volume-panel": { + "content-streaming": "" + }, "logs-volume-panel-list": { "label-reload-log-volume": "", "loading": "", @@ -4923,6 +4995,7 @@ "label-tags": "", "placeholder-all-service-names": "", "placeholder-all-span-names": "", + "tooltip-collapse": "", "tooltip-duration": "", "tooltip-tags": "" }, @@ -5001,7 +5074,8 @@ "label-show-paths": "" }, "trace-view": { - "no-data": "" + "no-data": "", + "tooltip-copy-icon": "" }, "trace-view-container": { "title-trace": "" @@ -5115,7 +5189,8 @@ "default-password-alert": "", "new-password-label": "", "skip-button": "Ignorar", - "submit-button": "Enviar" + "submit-button": "Enviar", + "tooltip-skip-button": "" }, "contact-admin": "Esqueceu-se do seu nome de utilizador ou e-mail? Contacte o seu administrador da Grafana.", "email-sent": "Foi enviado um e-mail com um link de reposição para o endereço de e-mail. Deverá recebê-lo em breve.", @@ -5172,7 +5247,8 @@ "action-editor": { "button": { "confirm": "Confirmar", - "confirm-action": "Confirmar ação" + "confirm-action": "Confirmar ação", + "style": "" }, "inline": { "add-action": "Adicionar ação", @@ -5379,7 +5455,11 @@ "previous-page": "página anterior" }, "panel-chrome": { - "aria-label-toggle-collapse": "" + "aria-label-toggle-collapse": "", + "tooltip-cancel": "", + "tooltip-cancel-loading": "", + "tooltip-stop-streaming": "", + "tooltip-streaming": "" }, "panel-menu": { "label": "Menu para o painel {{ title }}", @@ -5961,7 +6041,8 @@ "log-row-message": { "ellipsis": "… ", "more": "mais", - "see-details": "Ver detalhes do registo" + "see-details": "Ver detalhes do registo", + "tooltip-error": "" }, "log-rows": { "disable-popover": { @@ -6938,7 +7019,8 @@ "label-search": "", "label-state": "", "label-type": "", - "subtitle": "" + "subtitle": "", + "tooltip-filter-disabled": "" }, "catalog": { "no-updates-available": "Nenhuma atualização disponível", @@ -6947,7 +7029,7 @@ "available-header": "Disponível", "button": "Atualizar tudo ({{length}})", "cloud-update-message": "* Pode demorar alguns minutos até que os plugins estejam disponíveis para utilização.", - "error": "Erro ao atualizar o plugin:", + "error": "", "error-status-text": "falhou - ver mensagens de erro", "header": "Os seguintes plugins têm atualizações disponíveis", "installed-header": "Instalado", @@ -7157,6 +7239,9 @@ "feature-toggles-age": { "enable-in-config": "" }, + "input-suffix": { + "content-login-details-locked-because-managed-another": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -8090,10 +8175,12 @@ "title-disable-service-account": "" }, "token-expiration": { + "content-this-token-has-expired": "", "expired-label": "", "never": "" }, "token-revoked": { + "content-token-publicly-exposed-please-rotate": "", "revoked-label": "" } }, @@ -8499,7 +8586,7 @@ "range-content": { "apply-button": "Aplicar intervalo de tempo", "default-error": "", - "fiscal-year": "Ano fiscal", + "fiscal-year": "", "from-input": "De", "open-input-calendar": "Abrir calendário", "range-error": "\"De\" não pode ser posterior a \"Até\"", @@ -8951,6 +9038,7 @@ "loading": "A carregar...", "no-unknowns": "Não foram encontradas variáveis renomeadas ou em falta.", "renamed-or-missing-variables": "Variáveis renomeadas ou em falta", + "tooltip-renamed-or-missing-variables": "", "variable": "Variável" }, "variable-check-indicator": { diff --git a/public/locales/ru-RU/grafana.json b/public/locales/ru-RU/grafana.json index 14b7b36c5a7..c90c6d85455 100644 --- a/public/locales/ru-RU/grafana.json +++ b/public/locales/ru-RU/grafana.json @@ -21,7 +21,9 @@ "permission-list-item": { "inherited": "Унаследованные от папки", "locked-aria-label": "", - "remove-aria-label": "" + "remove-aria-label": "", + "tooltip-inherited-permission": "", + "tooltip-provisioned-permission": "" }, "permissions": { "add-label": "Добавить разрешение", @@ -42,6 +44,11 @@ }, "actions": { "action-editor": { + "button": { + "style": { + "background-color": "" + } + }, "label-headers": "", "label-url": "", "placeholder-url": "" @@ -70,6 +77,7 @@ "confirm-modal-body-2": "", "get-stage-cell": { "beta": "", + "content-general-availability": "", "deprecated": "", "ga": "" }, @@ -128,6 +136,12 @@ "test-mapping-heading": "Тестирование сопоставления пользователей", "test-mapping-run-button": "Выполнить" }, + "ldap-connection-status": { + "columns": { + "content-connection-is-available": "", + "content-connection-is-not-available": "" + } + }, "ldap-error-box": { "title-connection-error": "" }, @@ -161,6 +175,9 @@ "title": "Синхронизация LDAP" }, "ldap-user-groups": { + "columns": { + "content-no-matching-organizations-found": "" + }, "no-org-found": "" }, "ldap-user-info": { @@ -284,12 +301,14 @@ }, "users-table": { "columns": { + "content-grafana-admin": "", "title-edit-user": "" }, "edit-aria-label": "", "edit-tooltip": "", "last-seen-never": "Никогда", - "no-licensed-roles": "Не назначено" + "no-licensed-roles": "Не назначено", + "tooltip-assigned-role": "" } }, "alert-labels": { @@ -416,11 +435,9 @@ }, "alert-rule-form": { "action-buttons": { - "delete": "", "edit-yaml": "", - "save-exit": "" - }, - "title-delete-rule": "" + "save": "" + } }, "alert-rule-name-and-metric": { "aria-label-name": "", @@ -452,6 +469,7 @@ "unprocessed-the-alert-is-received": "" }, "alert-state-tag": { + "content-alert-evaluation-is-currently-paused": "", "paused": "" }, "alert-warning": { @@ -548,6 +566,9 @@ "warning-restore-manually": "Вы вручную восстанавливаете старую версию этого правила оповещения. Внимательно просмотрите изменения перед сохранением определения правила.", "warning-restore-manually-title": "Восстановление правила вручную" }, + "all-matches-indicator": { + "content-this-policy-matches-all-labels": "" + }, "am-root-route-form": { "am-group-description-label": "", "am-group-interval-description": "", @@ -660,6 +681,9 @@ "of": "", "when": "" }, + "clear-filter-button-object-renderer": { + "content-clear-filter": "" + }, "clone-rule-button": { "title-copy": "" }, @@ -790,6 +814,9 @@ "contactPointFilter": { "label": "Точка контакта" }, + "continue-matching-indicator": { + "content-route-continue-matching-other-policies": "" + }, "copy-to-clipboard": "Копировать метку «{{label}}» в буфер обмена", "create-metadata": { "view-dashboard": "", @@ -818,6 +845,10 @@ "current-selection-dashboard": "", "current-selection-panel": "", "fallback-dashboards-string": "", + "panel-row": { + "content-panel-valid-identifier": "", + "tooltip-alert-tab-support": "" + }, "placeholder-search-dashboard": "", "placeholder-search-panel": "", "select-dashboard-available-panels": "", @@ -839,9 +870,11 @@ "update-datasource": "" }, "declare-incident-button": { + "content-grafana-incident-installed-configured-correctly": "", "declare-incident": "" }, "declare-incident-menu-item": { + "content-grafana-incident-installed-configured-correctly": "", "label-declare-incident": "" }, "delete-rule-modal": { @@ -877,6 +910,9 @@ }, "unknown": "" }, + "disabled-tooltip": { + "content-appear-compatible-datasources": "" + }, "draggable-rules-table": { "evals-to-start-alerting": "Оценки для запуска отправки оповещений", "pending-period": "Период ожидания", @@ -907,10 +943,19 @@ "text-loading-template": "", "title-failed-to-fetch-notification-template": "" }, + "editor": { + "edit-alert-rule": "", + "edit-recording-rule": "", + "new-alert-rule": "", + "new-recording-rule": "" + }, "error-modal": { "failed-to-update-your-configuration": "", "title-something-went-wrong": "" }, + "error-summary-button": { + "content-show-all-errors": "" + }, "evaluation-behavior-summary": { "evaluate": "", "label-evaluate": "", @@ -941,7 +986,6 @@ "existing-rule-editor": { "sorry-permission": "", "sorry-this-rule-does-not-exist": "", - "text-loading-rule": "", "title-cannot-edit-rule": "", "title-failed-to-load-rule": "", "title-rule-not-found": "" @@ -1192,6 +1236,9 @@ "group-loader": { "group-load-failed": "" }, + "group-status": { + "content-the-group-is-being-deleted": "" + }, "header": { "tooltip-remove": "" }, @@ -1363,7 +1410,8 @@ "common-labels": "", "error-unable-to-fetch": "", "loading": "", - "title-error-fetching-the-state-history": "" + "title-error-fetching-the-state-history": "", + "tooltip-common-labels": "" }, "manage-permissions": { "button": "Управление разрешениями", @@ -1456,6 +1504,7 @@ }, "mute-timing-time-range": { "add-another-time-range": "", + "content-this-time-interval-is-disabled": "", "description-time-range": "", "label-end-time": "", "label-start-time": "", @@ -1611,6 +1660,9 @@ "plugin-integrations": { "tailored-apps": "" }, + "plugin-origin-badge": { + "tooltip-managed-by-plugin": "" + }, "policies": { "aria-label-collapse": "", "aria-label-expand": "", @@ -1714,6 +1766,9 @@ "prometheus-consistency-check": { "title-unable-to-check-the-rule-status": "" }, + "provisioned-tooltip": { + "content-provisioned-items-cannot-edited": "" + }, "provisioning": { "badge-tooltip-provenance": "Ресурс был подготовлен через {{provenance}} и не может быть изменен через пользовательский интерфейс", "badge-tooltip-standard": "Ресурс был подготовлен и не может быть изменен через пользовательский интерфейс", @@ -1951,14 +2006,14 @@ }, "threshold": { "recovery": { - "stop-alerting-above": "Прекратить отправку оповещений, если значение выше", - "stop-alerting-bellow": "Прекратить отправку оповещений, если значение ниже", - "stop-alerting-equal": "Прекратить отправку оповещений, если значение равно", - "stop-alerting-inside-range": "Прекратить отправку оповещений, если значение находится в пределах диапазона", - "stop-alerting-less": "Прекратить отправку оповещений, если значение меньше", - "stop-alerting-more": "Прекратить отправку оповещений, если значение больше", - "stop-alerting-not-equal": "Прекратить отправку оповещений, если значение не равно", - "stop-alerting-outside-range": "Прекратить отправку оповещений, если значение находится за пределами диапазона", + "stop-alerting-above": "", + "stop-alerting-bellow": "", + "stop-alerting-equal": "", + "stop-alerting-inside-range": "", + "stop-alerting-less": "", + "stop-alerting-more": "", + "stop-alerting-not-equal": "", + "stop-alerting-outside-range": "", "title": "Пользовательский порог восстановления" } } @@ -2062,7 +2117,10 @@ "for": "", "na": "", "paused": "Приостановлено", - "recording-rule": "Правило записи" + "recording-rule": "Правило записи", + "recording-rule-state": { + "content-recording-rule-evaluation-is-currently-paused": "" + } }, "rule-stats": { "firing": "", @@ -2308,6 +2366,8 @@ "refresh": "" }, "template-row": { + "content-templatefiles": "", + "content-templates": "", "tooltip-copy-template-group": "", "tooltip-delete-template-group": "", "tooltip-edit-template-group": "", @@ -2331,7 +2391,7 @@ }, "misconfigured-badge-text": "Неправильно настроенный", "misconfigured-warning": "Неправильно настроенный шаблон.", - "misconfigured-warning-details": "Шаблоны должны задаваться в разделах <1> и <4> конфигурации вашего обработчика оповещений Alertmanager." + "misconfigured-warning-details": "" }, "templates-picker": { "button-edit": "", @@ -2376,6 +2436,10 @@ "type-selector-button": { "add-expression": "" }, + "unknown-contact-point-details": { + "unknown-contact-point": "", + "unknown-contact-point-tooltip": "" + }, "unknown-rule-list-item": { "title-unknown-rule-type": "" }, @@ -2476,6 +2540,7 @@ }, "api-keys-table": { "aria-label-delete-api-key": "", + "content-this-api-key-has-expired": "", "expires": "", "last-used-at": "", "migrate-to-service-account": "", @@ -4196,7 +4261,9 @@ }, "variable-check-indicator": { "aria-label-variable-referenced-dashboard": "", - "aria-label-variable-referenced-other-variables-dashboard": "" + "aria-label-variable-referenced-other-variables-dashboard": "", + "content-variable-not-referenced-other-variables-dashboard": "", + "content-variable-referenced-other-variables-dashboard": "" }, "variable-editor-form": { "aria-label-variable-editor-form": "", @@ -4256,6 +4323,7 @@ }, "versions-history-buttons": { "compare-versions": "", + "content-select-two-versions-to-start-comparing": "", "show-more-versions": "" }, "visualization-button": { @@ -4609,6 +4677,7 @@ } }, "correlation-editor-mode-bar": { + "content-correlations-editor-explore-experimental-feature": "", "exit-correlation-editor": "", "save": "" }, @@ -4766,6 +4835,9 @@ "label-select-query": "", "tooltip-select-query-visualize-table": "" }, + "logs-volume-panel": { + "content-streaming": "" + }, "logs-volume-panel-list": { "label-reload-log-volume": "", "loading": "", @@ -4965,6 +5037,7 @@ "label-tags": "", "placeholder-all-service-names": "", "placeholder-all-span-names": "", + "tooltip-collapse": "", "tooltip-duration": "", "tooltip-tags": "" }, @@ -5043,7 +5116,8 @@ "label-show-paths": "" }, "trace-view": { - "no-data": "" + "no-data": "", + "tooltip-copy-icon": "" }, "trace-view-container": { "title-trace": "" @@ -5157,7 +5231,8 @@ "default-password-alert": "", "new-password-label": "", "skip-button": "Пропустить", - "submit-button": "Отправить" + "submit-button": "Отправить", + "tooltip-skip-button": "" }, "contact-admin": "Забыли имя пользователя или адрес электронной почты? Обратитесь к администратору Grafana.", "email-sent": "На ваш адрес электронной почты отправлена ссылка для сброса пароля. Вы должны получить ее в ближайшее время.", @@ -5214,7 +5289,8 @@ "action-editor": { "button": { "confirm": "Подтвердить", - "confirm-action": "Подтвердить действие" + "confirm-action": "Подтвердить действие", + "style": "" }, "inline": { "add-action": "Добавить действие", @@ -5421,7 +5497,11 @@ "previous-page": "предыдущая страница" }, "panel-chrome": { - "aria-label-toggle-collapse": "" + "aria-label-toggle-collapse": "", + "tooltip-cancel": "", + "tooltip-cancel-loading": "", + "tooltip-stop-streaming": "", + "tooltip-streaming": "" }, "panel-menu": { "label": "Меню для панели {{ title }}", @@ -6013,7 +6093,8 @@ "log-row-message": { "ellipsis": "… ", "more": "больше", - "see-details": "См. данные журнала" + "see-details": "См. данные журнала", + "tooltip-error": "" }, "log-rows": { "disable-popover": { @@ -6990,7 +7071,8 @@ "label-search": "", "label-state": "", "label-type": "", - "subtitle": "" + "subtitle": "", + "tooltip-filter-disabled": "" }, "catalog": { "no-updates-available": "Обновления отсутствуют", @@ -6999,7 +7081,7 @@ "available-header": "Доступно", "button": "Обновить все ({{length}})", "cloud-update-message": "* Проверьте, доступны ли плагины, через несколько минут.", - "error": "Ошибка при обновлении плагина:", + "error": "", "error-status-text": "сбой — см. сообщения об ошибках", "header": "Доступны обновления для следующих плагинов", "installed-header": "Установлено", @@ -7209,6 +7291,9 @@ "feature-toggles-age": { "enable-in-config": "" }, + "input-suffix": { + "content-login-details-locked-because-managed-another": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -8152,10 +8237,12 @@ "title-disable-service-account": "" }, "token-expiration": { + "content-this-token-has-expired": "", "expired-label": "", "never": "" }, "token-revoked": { + "content-token-publicly-exposed-please-rotate": "", "revoked-label": "" } }, @@ -8561,7 +8648,7 @@ "range-content": { "apply-button": "Применить временной диапазон", "default-error": "", - "fiscal-year": "Отчетный год", + "fiscal-year": "", "from-input": "Время начала", "open-input-calendar": "Открыть календарь", "range-error": "Время начала не может быть позже времени окончания", @@ -9013,6 +9100,7 @@ "loading": "Загрузка…", "no-unknowns": "Переименованных или отсутствующих переменных не найдено.", "renamed-or-missing-variables": "Переименованные или отсутствующие переменные", + "tooltip-renamed-or-missing-variables": "", "variable": "Переменная" }, "variable-check-indicator": { diff --git a/public/locales/sv-SE/grafana.json b/public/locales/sv-SE/grafana.json index a9596c05382..6ffd18dcb9f 100644 --- a/public/locales/sv-SE/grafana.json +++ b/public/locales/sv-SE/grafana.json @@ -21,7 +21,9 @@ "permission-list-item": { "inherited": "Ärvd från mapp", "locked-aria-label": "", - "remove-aria-label": "" + "remove-aria-label": "", + "tooltip-inherited-permission": "", + "tooltip-provisioned-permission": "" }, "permissions": { "add-label": "Lägg till en behörighet", @@ -42,6 +44,11 @@ }, "actions": { "action-editor": { + "button": { + "style": { + "background-color": "" + } + }, "label-headers": "", "label-url": "", "placeholder-url": "" @@ -70,6 +77,7 @@ "confirm-modal-body-2": "", "get-stage-cell": { "beta": "", + "content-general-availability": "", "deprecated": "", "ga": "" }, @@ -128,6 +136,12 @@ "test-mapping-heading": "Testa användarmappning", "test-mapping-run-button": "Kör" }, + "ldap-connection-status": { + "columns": { + "content-connection-is-available": "", + "content-connection-is-not-available": "" + } + }, "ldap-error-box": { "title-connection-error": "" }, @@ -161,6 +175,9 @@ "title": "LDAP-synkronisering" }, "ldap-user-groups": { + "columns": { + "content-no-matching-organizations-found": "" + }, "no-org-found": "" }, "ldap-user-info": { @@ -284,12 +301,14 @@ }, "users-table": { "columns": { + "content-grafana-admin": "", "title-edit-user": "" }, "edit-aria-label": "", "edit-tooltip": "", "last-seen-never": "Aldrig", - "no-licensed-roles": "Ej tilldelad" + "no-licensed-roles": "Ej tilldelad", + "tooltip-assigned-role": "" } }, "alert-labels": { @@ -408,11 +427,9 @@ }, "alert-rule-form": { "action-buttons": { - "delete": "", "edit-yaml": "", - "save-exit": "" - }, - "title-delete-rule": "" + "save": "" + } }, "alert-rule-name-and-metric": { "aria-label-name": "", @@ -444,6 +461,7 @@ "unprocessed-the-alert-is-received": "" }, "alert-state-tag": { + "content-alert-evaluation-is-currently-paused": "", "paused": "" }, "alert-warning": { @@ -540,6 +558,9 @@ "warning-restore-manually": "Du återställer manuellt en gammal version av den här varningsregeln. Granska ändringarna noggrant innan du sparar regeldefinitionen.", "warning-restore-manually-title": "Återställ regel manuellt" }, + "all-matches-indicator": { + "content-this-policy-matches-all-labels": "" + }, "am-root-route-form": { "am-group-description-label": "", "am-group-interval-description": "", @@ -652,6 +673,9 @@ "of": "", "when": "" }, + "clear-filter-button-object-renderer": { + "content-clear-filter": "" + }, "clone-rule-button": { "title-copy": "" }, @@ -778,6 +802,9 @@ "contactPointFilter": { "label": "Kontaktpunkt" }, + "continue-matching-indicator": { + "content-route-continue-matching-other-policies": "" + }, "copy-to-clipboard": "Kopiera ”{{label}}” till klippbordet", "create-metadata": { "view-dashboard": "", @@ -806,6 +833,10 @@ "current-selection-dashboard": "", "current-selection-panel": "", "fallback-dashboards-string": "", + "panel-row": { + "content-panel-valid-identifier": "", + "tooltip-alert-tab-support": "" + }, "placeholder-search-dashboard": "", "placeholder-search-panel": "", "select-dashboard-available-panels": "", @@ -827,9 +858,11 @@ "update-datasource": "" }, "declare-incident-button": { + "content-grafana-incident-installed-configured-correctly": "", "declare-incident": "" }, "declare-incident-menu-item": { + "content-grafana-incident-installed-configured-correctly": "", "label-declare-incident": "" }, "delete-rule-modal": { @@ -865,6 +898,9 @@ }, "unknown": "" }, + "disabled-tooltip": { + "content-appear-compatible-datasources": "" + }, "draggable-rules-table": { "evals-to-start-alerting": "Utvärderingar för att börja varna", "pending-period": "Väntande period", @@ -895,10 +931,19 @@ "text-loading-template": "", "title-failed-to-fetch-notification-template": "" }, + "editor": { + "edit-alert-rule": "", + "edit-recording-rule": "", + "new-alert-rule": "", + "new-recording-rule": "" + }, "error-modal": { "failed-to-update-your-configuration": "", "title-something-went-wrong": "" }, + "error-summary-button": { + "content-show-all-errors": "" + }, "evaluation-behavior-summary": { "evaluate": "", "label-evaluate": "", @@ -929,7 +974,6 @@ "existing-rule-editor": { "sorry-permission": "", "sorry-this-rule-does-not-exist": "", - "text-loading-rule": "", "title-cannot-edit-rule": "", "title-failed-to-load-rule": "", "title-rule-not-found": "" @@ -1180,6 +1224,9 @@ "group-loader": { "group-load-failed": "" }, + "group-status": { + "content-the-group-is-being-deleted": "" + }, "header": { "tooltip-remove": "" }, @@ -1349,7 +1396,8 @@ "common-labels": "", "error-unable-to-fetch": "", "loading": "", - "title-error-fetching-the-state-history": "" + "title-error-fetching-the-state-history": "", + "tooltip-common-labels": "" }, "manage-permissions": { "button": "Hantera behörigheter", @@ -1442,6 +1490,7 @@ }, "mute-timing-time-range": { "add-another-time-range": "", + "content-this-time-interval-is-disabled": "", "description-time-range": "", "label-end-time": "", "label-start-time": "", @@ -1597,6 +1646,9 @@ "plugin-integrations": { "tailored-apps": "" }, + "plugin-origin-badge": { + "tooltip-managed-by-plugin": "" + }, "policies": { "aria-label-collapse": "", "aria-label-expand": "", @@ -1696,6 +1748,9 @@ "prometheus-consistency-check": { "title-unable-to-check-the-rule-status": "" }, + "provisioned-tooltip": { + "content-provisioned-items-cannot-edited": "" + }, "provisioning": { "badge-tooltip-provenance": "Den här resursen har etablerats via {{provenance}} och kan inte redigeras via användargränssnittet", "badge-tooltip-standard": "Denna resurs har etablerats och kan inte redigeras via användargränssnittet", @@ -1931,14 +1986,14 @@ }, "threshold": { "recovery": { - "stop-alerting-above": "Sluta varna när över", - "stop-alerting-bellow": "Sluta varna när under", - "stop-alerting-equal": "Sluta varna när lika med", - "stop-alerting-inside-range": "Sluta varna när inom intervallet", - "stop-alerting-less": "Sluta varna när mindre än", - "stop-alerting-more": "Sluta varna när mer än", - "stop-alerting-not-equal": "Sluta varna när inte lika med", - "stop-alerting-outside-range": "Sluta varna när utanför intervallet", + "stop-alerting-above": "", + "stop-alerting-bellow": "", + "stop-alerting-equal": "", + "stop-alerting-inside-range": "", + "stop-alerting-less": "", + "stop-alerting-more": "", + "stop-alerting-not-equal": "", + "stop-alerting-outside-range": "", "title": "Anpassad återställningsgräns" } } @@ -2038,7 +2093,10 @@ "for": "", "na": "", "paused": "Pausad", - "recording-rule": "Inspelningsregel" + "recording-rule": "Inspelningsregel", + "recording-rule-state": { + "content-recording-rule-evaluation-is-currently-paused": "" + } }, "rule-stats": { "firing": "", @@ -2282,6 +2340,8 @@ "refresh": "" }, "template-row": { + "content-templatefiles": "", + "content-templates": "", "tooltip-copy-template-group": "", "tooltip-delete-template-group": "", "tooltip-edit-template-group": "", @@ -2305,7 +2365,7 @@ }, "misconfigured-badge-text": "Felkonfigurerad", "misconfigured-warning": "Den här mallen är felkonfigurerad.", - "misconfigured-warning-details": "Mallar måste definieras i både avsnitten <1> och <4> i alertmanager-konfigurationen." + "misconfigured-warning-details": "" }, "templates-picker": { "button-edit": "", @@ -2350,6 +2410,10 @@ "type-selector-button": { "add-expression": "" }, + "unknown-contact-point-details": { + "unknown-contact-point": "", + "unknown-contact-point-tooltip": "" + }, "unknown-rule-list-item": { "title-unknown-rule-type": "" }, @@ -2450,6 +2514,7 @@ }, "api-keys-table": { "aria-label-delete-api-key": "", + "content-this-api-key-has-expired": "", "expires": "", "last-used-at": "", "migrate-to-service-account": "", @@ -4154,7 +4219,9 @@ }, "variable-check-indicator": { "aria-label-variable-referenced-dashboard": "", - "aria-label-variable-referenced-other-variables-dashboard": "" + "aria-label-variable-referenced-other-variables-dashboard": "", + "content-variable-not-referenced-other-variables-dashboard": "", + "content-variable-referenced-other-variables-dashboard": "" }, "variable-editor-form": { "aria-label-variable-editor-form": "", @@ -4214,6 +4281,7 @@ }, "versions-history-buttons": { "compare-versions": "", + "content-select-two-versions-to-start-comparing": "", "show-more-versions": "" }, "visualization-button": { @@ -4567,6 +4635,7 @@ } }, "correlation-editor-mode-bar": { + "content-correlations-editor-explore-experimental-feature": "", "exit-correlation-editor": "", "save": "" }, @@ -4724,6 +4793,9 @@ "label-select-query": "", "tooltip-select-query-visualize-table": "" }, + "logs-volume-panel": { + "content-streaming": "" + }, "logs-volume-panel-list": { "label-reload-log-volume": "", "loading": "", @@ -4923,6 +4995,7 @@ "label-tags": "", "placeholder-all-service-names": "", "placeholder-all-span-names": "", + "tooltip-collapse": "", "tooltip-duration": "", "tooltip-tags": "" }, @@ -5001,7 +5074,8 @@ "label-show-paths": "" }, "trace-view": { - "no-data": "" + "no-data": "", + "tooltip-copy-icon": "" }, "trace-view-container": { "title-trace": "" @@ -5115,7 +5189,8 @@ "default-password-alert": "", "new-password-label": "", "skip-button": "Hoppa över", - "submit-button": "Skicka in" + "submit-button": "Skicka in", + "tooltip-skip-button": "" }, "contact-admin": "Har du glömt ditt användarnamn eller e-postadress? Kontakta din Grafana-administratör.", "email-sent": "Ett e-postmeddelande med en återställningslänk har skickats till e-postadressen. Du bör få den inom kort.", @@ -5172,7 +5247,8 @@ "action-editor": { "button": { "confirm": "Bekräfta", - "confirm-action": "Bekräfta åtgärd" + "confirm-action": "Bekräfta åtgärd", + "style": "" }, "inline": { "add-action": "Lägg till åtgärd", @@ -5379,7 +5455,11 @@ "previous-page": "föregående sida" }, "panel-chrome": { - "aria-label-toggle-collapse": "" + "aria-label-toggle-collapse": "", + "tooltip-cancel": "", + "tooltip-cancel-loading": "", + "tooltip-stop-streaming": "", + "tooltip-streaming": "" }, "panel-menu": { "label": "Meny för panel {{ title }}", @@ -5961,7 +6041,8 @@ "log-row-message": { "ellipsis": "… ", "more": "mer", - "see-details": "Se loggdetaljer" + "see-details": "Se loggdetaljer", + "tooltip-error": "" }, "log-rows": { "disable-popover": { @@ -6938,7 +7019,8 @@ "label-search": "", "label-state": "", "label-type": "", - "subtitle": "" + "subtitle": "", + "tooltip-filter-disabled": "" }, "catalog": { "no-updates-available": "Inga uppdateringar tillgängliga", @@ -6947,7 +7029,7 @@ "available-header": "Tillgänglig", "button": "Uppdatera alla ({{length}})", "cloud-update-message": "* Det kan ta några minuter innan tilläggsprogrammen är tillgängliga för användning.", - "error": "Fel vid uppdatering av tilläggsprogram:", + "error": "", "error-status-text": "misslyckades – se felmeddelanden", "header": "Följande tilläggsprogram har en uppdatering tillgänglig", "installed-header": "Installerad", @@ -7157,6 +7239,9 @@ "feature-toggles-age": { "enable-in-config": "" }, + "input-suffix": { + "content-login-details-locked-because-managed-another": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -8090,10 +8175,12 @@ "title-disable-service-account": "" }, "token-expiration": { + "content-this-token-has-expired": "", "expired-label": "", "never": "" }, "token-revoked": { + "content-token-publicly-exposed-please-rotate": "", "revoked-label": "" } }, @@ -8499,7 +8586,7 @@ "range-content": { "apply-button": "Tillämpa tidsintervall", "default-error": "", - "fiscal-year": "Räkenskapsår", + "fiscal-year": "", "from-input": "Från", "open-input-calendar": "Öppna kalendern", "range-error": "”Från” kan inte vara efter ”Till”", @@ -8951,6 +9038,7 @@ "loading": "Laddar …", "no-unknowns": "Inga omdöpta eller saknade variabler hittades.", "renamed-or-missing-variables": "Omdöpta eller saknade variabler", + "tooltip-renamed-or-missing-variables": "", "variable": "Variabel" }, "variable-check-indicator": { diff --git a/public/locales/tr-TR/grafana.json b/public/locales/tr-TR/grafana.json index 76e90cd6df0..35ca445ecc0 100644 --- a/public/locales/tr-TR/grafana.json +++ b/public/locales/tr-TR/grafana.json @@ -21,7 +21,9 @@ "permission-list-item": { "inherited": "Klasörden devralındı", "locked-aria-label": "", - "remove-aria-label": "" + "remove-aria-label": "", + "tooltip-inherited-permission": "", + "tooltip-provisioned-permission": "" }, "permissions": { "add-label": "İzin ekle", @@ -42,6 +44,11 @@ }, "actions": { "action-editor": { + "button": { + "style": { + "background-color": "" + } + }, "label-headers": "", "label-url": "", "placeholder-url": "" @@ -70,6 +77,7 @@ "confirm-modal-body-2": "", "get-stage-cell": { "beta": "", + "content-general-availability": "", "deprecated": "", "ga": "" }, @@ -128,6 +136,12 @@ "test-mapping-heading": "Kullanıcı eşleme testi", "test-mapping-run-button": "Çalıştır" }, + "ldap-connection-status": { + "columns": { + "content-connection-is-available": "", + "content-connection-is-not-available": "" + } + }, "ldap-error-box": { "title-connection-error": "" }, @@ -161,6 +175,9 @@ "title": "LDAP Eşitlemesi" }, "ldap-user-groups": { + "columns": { + "content-no-matching-organizations-found": "" + }, "no-org-found": "" }, "ldap-user-info": { @@ -284,12 +301,14 @@ }, "users-table": { "columns": { + "content-grafana-admin": "", "title-edit-user": "" }, "edit-aria-label": "", "edit-tooltip": "", "last-seen-never": "Hiçbir zaman", - "no-licensed-roles": "Atanmadı" + "no-licensed-roles": "Atanmadı", + "tooltip-assigned-role": "" } }, "alert-labels": { @@ -408,11 +427,9 @@ }, "alert-rule-form": { "action-buttons": { - "delete": "", "edit-yaml": "", - "save-exit": "" - }, - "title-delete-rule": "" + "save": "" + } }, "alert-rule-name-and-metric": { "aria-label-name": "", @@ -444,6 +461,7 @@ "unprocessed-the-alert-is-received": "" }, "alert-state-tag": { + "content-alert-evaluation-is-currently-paused": "", "paused": "" }, "alert-warning": { @@ -540,6 +558,9 @@ "warning-restore-manually": "Bu uyarı kuralının eski bir sürümünü manuel olarak geri yüklüyorsunuz. Kural tanımını kaydetmeden önce değişiklikleri dikkatlice inceleyin.", "warning-restore-manually-title": "Kural manuel olarak geri yükleniyor" }, + "all-matches-indicator": { + "content-this-policy-matches-all-labels": "" + }, "am-root-route-form": { "am-group-description-label": "", "am-group-interval-description": "", @@ -652,6 +673,9 @@ "of": "", "when": "" }, + "clear-filter-button-object-renderer": { + "content-clear-filter": "" + }, "clone-rule-button": { "title-copy": "" }, @@ -778,6 +802,9 @@ "contactPointFilter": { "label": "İletişim noktası" }, + "continue-matching-indicator": { + "content-route-continue-matching-other-policies": "" + }, "copy-to-clipboard": "\"{{label}}\" metnini panoya kopyala", "create-metadata": { "view-dashboard": "", @@ -806,6 +833,10 @@ "current-selection-dashboard": "", "current-selection-panel": "", "fallback-dashboards-string": "", + "panel-row": { + "content-panel-valid-identifier": "", + "tooltip-alert-tab-support": "" + }, "placeholder-search-dashboard": "", "placeholder-search-panel": "", "select-dashboard-available-panels": "", @@ -827,9 +858,11 @@ "update-datasource": "" }, "declare-incident-button": { + "content-grafana-incident-installed-configured-correctly": "", "declare-incident": "" }, "declare-incident-menu-item": { + "content-grafana-incident-installed-configured-correctly": "", "label-declare-incident": "" }, "delete-rule-modal": { @@ -865,6 +898,9 @@ }, "unknown": "" }, + "disabled-tooltip": { + "content-appear-compatible-datasources": "" + }, "draggable-rules-table": { "evals-to-start-alerting": "Uyarı vermeye başlamak için değerlendirmeler", "pending-period": "Bekleme süresi", @@ -895,10 +931,19 @@ "text-loading-template": "", "title-failed-to-fetch-notification-template": "" }, + "editor": { + "edit-alert-rule": "", + "edit-recording-rule": "", + "new-alert-rule": "", + "new-recording-rule": "" + }, "error-modal": { "failed-to-update-your-configuration": "", "title-something-went-wrong": "" }, + "error-summary-button": { + "content-show-all-errors": "" + }, "evaluation-behavior-summary": { "evaluate": "", "label-evaluate": "", @@ -929,7 +974,6 @@ "existing-rule-editor": { "sorry-permission": "", "sorry-this-rule-does-not-exist": "", - "text-loading-rule": "", "title-cannot-edit-rule": "", "title-failed-to-load-rule": "", "title-rule-not-found": "" @@ -1180,6 +1224,9 @@ "group-loader": { "group-load-failed": "" }, + "group-status": { + "content-the-group-is-being-deleted": "" + }, "header": { "tooltip-remove": "" }, @@ -1349,7 +1396,8 @@ "common-labels": "", "error-unable-to-fetch": "", "loading": "", - "title-error-fetching-the-state-history": "" + "title-error-fetching-the-state-history": "", + "tooltip-common-labels": "" }, "manage-permissions": { "button": "İzinleri yönet", @@ -1442,6 +1490,7 @@ }, "mute-timing-time-range": { "add-another-time-range": "", + "content-this-time-interval-is-disabled": "", "description-time-range": "", "label-end-time": "", "label-start-time": "", @@ -1597,6 +1646,9 @@ "plugin-integrations": { "tailored-apps": "" }, + "plugin-origin-badge": { + "tooltip-managed-by-plugin": "" + }, "policies": { "aria-label-collapse": "", "aria-label-expand": "", @@ -1696,6 +1748,9 @@ "prometheus-consistency-check": { "title-unable-to-check-the-rule-status": "" }, + "provisioned-tooltip": { + "content-provisioned-items-cannot-edited": "" + }, "provisioning": { "badge-tooltip-provenance": "Bu kaynak {{provenance}} aracılığıyla sağlanmıştır ve kullanıcı arayüzü üzerinden düzenlenemez", "badge-tooltip-standard": "Bu kaynak sağlanmıştır ve kullanıcı arayüzü üzerinden düzenlenemez", @@ -1931,14 +1986,14 @@ }, "threshold": { "recovery": { - "stop-alerting-above": "Belirtilen değerin üzerine çıktığında uyarıyı durdur", - "stop-alerting-bellow": "Belirtilen değerin altına düştüğünde uyarıyı durdur", - "stop-alerting-equal": "Belirtilen değere eşit olduğunda uyarıyı durdur", - "stop-alerting-inside-range": "Belirtilen aralık içinde olduğunda uyarıyı durdur", - "stop-alerting-less": "Belirtilen değerden küçük olduğunda uyarıyı durdur", - "stop-alerting-more": "Belirtilen değerden büyük olduğunda uyarıyı durdur", - "stop-alerting-not-equal": "Belirtilen değere eşit olmadığında uyarıyı durdur", - "stop-alerting-outside-range": "Belirtilen aralığın dışında olduğunda uyarıyı durdur", + "stop-alerting-above": "", + "stop-alerting-bellow": "", + "stop-alerting-equal": "", + "stop-alerting-inside-range": "", + "stop-alerting-less": "", + "stop-alerting-more": "", + "stop-alerting-not-equal": "", + "stop-alerting-outside-range": "", "title": "Özel kurtarma eşiği" } } @@ -2038,7 +2093,10 @@ "for": "", "na": "", "paused": "Duraklatıldı", - "recording-rule": "Kayıt kuralı" + "recording-rule": "Kayıt kuralı", + "recording-rule-state": { + "content-recording-rule-evaluation-is-currently-paused": "" + } }, "rule-stats": { "firing": "", @@ -2282,6 +2340,8 @@ "refresh": "" }, "template-row": { + "content-templatefiles": "", + "content-templates": "", "tooltip-copy-template-group": "", "tooltip-delete-template-group": "", "tooltip-edit-template-group": "", @@ -2305,7 +2365,7 @@ }, "misconfigured-badge-text": "Yanlış yapılandırılmış", "misconfigured-warning": "Bu şablon yanlış yapılandırılmış.", - "misconfigured-warning-details": "Şablonlar, alertmanager yapılandırmanızın hem <1> hem de <4> bölümlerinde tanımlanmalıdır." + "misconfigured-warning-details": "" }, "templates-picker": { "button-edit": "", @@ -2350,6 +2410,10 @@ "type-selector-button": { "add-expression": "" }, + "unknown-contact-point-details": { + "unknown-contact-point": "", + "unknown-contact-point-tooltip": "" + }, "unknown-rule-list-item": { "title-unknown-rule-type": "" }, @@ -2450,6 +2514,7 @@ }, "api-keys-table": { "aria-label-delete-api-key": "", + "content-this-api-key-has-expired": "", "expires": "", "last-used-at": "", "migrate-to-service-account": "", @@ -4154,7 +4219,9 @@ }, "variable-check-indicator": { "aria-label-variable-referenced-dashboard": "", - "aria-label-variable-referenced-other-variables-dashboard": "" + "aria-label-variable-referenced-other-variables-dashboard": "", + "content-variable-not-referenced-other-variables-dashboard": "", + "content-variable-referenced-other-variables-dashboard": "" }, "variable-editor-form": { "aria-label-variable-editor-form": "", @@ -4214,6 +4281,7 @@ }, "versions-history-buttons": { "compare-versions": "", + "content-select-two-versions-to-start-comparing": "", "show-more-versions": "" }, "visualization-button": { @@ -4567,6 +4635,7 @@ } }, "correlation-editor-mode-bar": { + "content-correlations-editor-explore-experimental-feature": "", "exit-correlation-editor": "", "save": "" }, @@ -4724,6 +4793,9 @@ "label-select-query": "", "tooltip-select-query-visualize-table": "" }, + "logs-volume-panel": { + "content-streaming": "" + }, "logs-volume-panel-list": { "label-reload-log-volume": "", "loading": "", @@ -4923,6 +4995,7 @@ "label-tags": "", "placeholder-all-service-names": "", "placeholder-all-span-names": "", + "tooltip-collapse": "", "tooltip-duration": "", "tooltip-tags": "" }, @@ -5001,7 +5074,8 @@ "label-show-paths": "" }, "trace-view": { - "no-data": "" + "no-data": "", + "tooltip-copy-icon": "" }, "trace-view-container": { "title-trace": "" @@ -5115,7 +5189,8 @@ "default-password-alert": "", "new-password-label": "", "skip-button": "Atla", - "submit-button": "Gönder" + "submit-button": "Gönder", + "tooltip-skip-button": "" }, "contact-admin": "Kullanıcı adınızı veya e-posta adresinizi mi unuttunuz? Grafana yöneticinizle iletişime geçin.", "email-sent": "E-posta adresine, bir sıfırlama bağlantısı içeren bir e-posta gönderildi. Kısa süre içinde e-postayı almanız gerekir.", @@ -5172,7 +5247,8 @@ "action-editor": { "button": { "confirm": "Onayla", - "confirm-action": "İşlemi onayla" + "confirm-action": "İşlemi onayla", + "style": "" }, "inline": { "add-action": "İşlem ekle", @@ -5379,7 +5455,11 @@ "previous-page": "önceki sayfa" }, "panel-chrome": { - "aria-label-toggle-collapse": "" + "aria-label-toggle-collapse": "", + "tooltip-cancel": "", + "tooltip-cancel-loading": "", + "tooltip-stop-streaming": "", + "tooltip-streaming": "" }, "panel-menu": { "label": "Panel için menü {{ title }}", @@ -5961,7 +6041,8 @@ "log-row-message": { "ellipsis": "… ", "more": "daha fazla", - "see-details": "Günlük ayrıntılarını göster" + "see-details": "Günlük ayrıntılarını göster", + "tooltip-error": "" }, "log-rows": { "disable-popover": { @@ -6938,7 +7019,8 @@ "label-search": "", "label-state": "", "label-type": "", - "subtitle": "" + "subtitle": "", + "tooltip-filter-disabled": "" }, "catalog": { "no-updates-available": "Güncelleme yok", @@ -6947,7 +7029,7 @@ "available-header": "Mevcut", "button": "Tümünü güncelle ({{length}})", "cloud-update-message": "* Eklentilerin kullanılabilir hâle gelmesi birkaç dakika sürebilir.", - "error": "Eklenti güncellenirken hata oluştu:", + "error": "", "error-status-text": "Başarısız - Hata mesajlarına göz atın", "header": "Aşağıdaki eklentiler için güncelleme mevcut", "installed-header": "Yüklü", @@ -7157,6 +7239,9 @@ "feature-toggles-age": { "enable-in-config": "" }, + "input-suffix": { + "content-login-details-locked-because-managed-another": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -8090,10 +8175,12 @@ "title-disable-service-account": "" }, "token-expiration": { + "content-this-token-has-expired": "", "expired-label": "", "never": "" }, "token-revoked": { + "content-token-publicly-exposed-please-rotate": "", "revoked-label": "" } }, @@ -8499,7 +8586,7 @@ "range-content": { "apply-button": "Zaman aralığını uygula", "default-error": "", - "fiscal-year": "Mali yıl", + "fiscal-year": "", "from-input": "Başlangıç", "open-input-calendar": "Takvimi aç", "range-error": "\"Başlangıç\", \"Bitiş\" sonrasına denk gelen bir tarih olamaz.", @@ -8951,6 +9038,7 @@ "loading": "Yükleniyor...", "no-unknowns": "Yeniden adlandırılmış veya eksik değişken bulunamadı.", "renamed-or-missing-variables": "Yeniden adlandırılmış veya eksik değişkenler", + "tooltip-renamed-or-missing-variables": "", "variable": "Değişken" }, "variable-check-indicator": { diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index 97c652f3dc2..d9ae8fc420e 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -21,7 +21,9 @@ "permission-list-item": { "inherited": "继承自文件夹", "locked-aria-label": "", - "remove-aria-label": "" + "remove-aria-label": "", + "tooltip-inherited-permission": "", + "tooltip-provisioned-permission": "" }, "permissions": { "add-label": "添加权限", @@ -42,6 +44,11 @@ }, "actions": { "action-editor": { + "button": { + "style": { + "background-color": "" + } + }, "label-headers": "", "label-url": "", "placeholder-url": "" @@ -70,6 +77,7 @@ "confirm-modal-body-2": "", "get-stage-cell": { "beta": "", + "content-general-availability": "", "deprecated": "", "ga": "" }, @@ -128,6 +136,12 @@ "test-mapping-heading": "测试用户映射", "test-mapping-run-button": "运行" }, + "ldap-connection-status": { + "columns": { + "content-connection-is-available": "", + "content-connection-is-not-available": "" + } + }, "ldap-error-box": { "title-connection-error": "" }, @@ -161,6 +175,9 @@ "title": "LDAP 同步" }, "ldap-user-groups": { + "columns": { + "content-no-matching-organizations-found": "" + }, "no-org-found": "" }, "ldap-user-info": { @@ -284,12 +301,14 @@ }, "users-table": { "columns": { + "content-grafana-admin": "", "title-edit-user": "" }, "edit-aria-label": "", "edit-tooltip": "", "last-seen-never": "", - "no-licensed-roles": "未分配" + "no-licensed-roles": "未分配", + "tooltip-assigned-role": "" } }, "alert-labels": { @@ -404,11 +423,9 @@ }, "alert-rule-form": { "action-buttons": { - "delete": "", "edit-yaml": "", - "save-exit": "" - }, - "title-delete-rule": "" + "save": "" + } }, "alert-rule-name-and-metric": { "aria-label-name": "", @@ -440,6 +457,7 @@ "unprocessed-the-alert-is-received": "" }, "alert-state-tag": { + "content-alert-evaluation-is-currently-paused": "", "paused": "" }, "alert-warning": { @@ -536,6 +554,9 @@ "warning-restore-manually": "您正在手动恢复此提醒规则的旧版本。在保存规则定义之前,请仔细查看更改。", "warning-restore-manually-title": "手动恢复规则" }, + "all-matches-indicator": { + "content-this-policy-matches-all-labels": "" + }, "am-root-route-form": { "am-group-description-label": "", "am-group-interval-description": "", @@ -648,6 +669,9 @@ "of": "", "when": "" }, + "clear-filter-button-object-renderer": { + "content-clear-filter": "" + }, "clone-rule-button": { "title-copy": "" }, @@ -772,6 +796,9 @@ "contactPointFilter": { "label": "联络点" }, + "continue-matching-indicator": { + "content-route-continue-matching-other-policies": "" + }, "copy-to-clipboard": "将“{{label}}”复制到剪贴板", "create-metadata": { "view-dashboard": "", @@ -800,6 +827,10 @@ "current-selection-dashboard": "", "current-selection-panel": "", "fallback-dashboards-string": "", + "panel-row": { + "content-panel-valid-identifier": "", + "tooltip-alert-tab-support": "" + }, "placeholder-search-dashboard": "", "placeholder-search-panel": "", "select-dashboard-available-panels": "", @@ -821,9 +852,11 @@ "update-datasource": "" }, "declare-incident-button": { + "content-grafana-incident-installed-configured-correctly": "", "declare-incident": "" }, "declare-incident-menu-item": { + "content-grafana-incident-installed-configured-correctly": "", "label-declare-incident": "" }, "delete-rule-modal": { @@ -859,6 +892,9 @@ }, "unknown": "" }, + "disabled-tooltip": { + "content-appear-compatible-datasources": "" + }, "draggable-rules-table": { "evals-to-start-alerting": "", "pending-period": "", @@ -889,10 +925,19 @@ "text-loading-template": "", "title-failed-to-fetch-notification-template": "" }, + "editor": { + "edit-alert-rule": "", + "edit-recording-rule": "", + "new-alert-rule": "", + "new-recording-rule": "" + }, "error-modal": { "failed-to-update-your-configuration": "", "title-something-went-wrong": "" }, + "error-summary-button": { + "content-show-all-errors": "" + }, "evaluation-behavior-summary": { "evaluate": "", "label-evaluate": "", @@ -923,7 +968,6 @@ "existing-rule-editor": { "sorry-permission": "", "sorry-this-rule-does-not-exist": "", - "text-loading-rule": "", "title-cannot-edit-rule": "", "title-failed-to-load-rule": "", "title-rule-not-found": "" @@ -1174,6 +1218,9 @@ "group-loader": { "group-load-failed": "" }, + "group-status": { + "content-the-group-is-being-deleted": "" + }, "header": { "tooltip-remove": "" }, @@ -1342,7 +1389,8 @@ "common-labels": "", "error-unable-to-fetch": "", "loading": "", - "title-error-fetching-the-state-history": "" + "title-error-fetching-the-state-history": "", + "tooltip-common-labels": "" }, "manage-permissions": { "button": "", @@ -1435,6 +1483,7 @@ }, "mute-timing-time-range": { "add-another-time-range": "", + "content-this-time-interval-is-disabled": "", "description-time-range": "", "label-end-time": "", "label-start-time": "", @@ -1590,6 +1639,9 @@ "plugin-integrations": { "tailored-apps": "" }, + "plugin-origin-badge": { + "tooltip-managed-by-plugin": "" + }, "policies": { "aria-label-collapse": "", "aria-label-expand": "", @@ -1687,6 +1739,9 @@ "prometheus-consistency-check": { "title-unable-to-check-the-rule-status": "" }, + "provisioned-tooltip": { + "content-provisioned-items-cannot-edited": "" + }, "provisioning": { "badge-tooltip-provenance": "此资源已通过 {{provenance}} 配置,无法通过用户界面编辑", "badge-tooltip-standard": "此资源已配置,无法通过用户界面编辑", @@ -1921,14 +1976,14 @@ }, "threshold": { "recovery": { - "stop-alerting-above": "当高于以下值时停止提醒:", - "stop-alerting-bellow": "当低于以下值时停止提醒:", - "stop-alerting-equal": "当等于以下值时停止提醒:", - "stop-alerting-inside-range": "当在以下范围内时停止提醒:", - "stop-alerting-less": "当少于以下值时停止提醒:", - "stop-alerting-more": "当多于以下值时停止提醒:", - "stop-alerting-not-equal": "当不等于以下值时停止提醒:", - "stop-alerting-outside-range": "当在以下范围外时停止提醒:", + "stop-alerting-above": "", + "stop-alerting-bellow": "", + "stop-alerting-equal": "", + "stop-alerting-inside-range": "", + "stop-alerting-less": "", + "stop-alerting-more": "", + "stop-alerting-not-equal": "", + "stop-alerting-outside-range": "", "title": "自定义恢复阈值" } } @@ -2026,7 +2081,10 @@ "for": "", "na": "", "paused": "已暂停", - "recording-rule": "录制规则" + "recording-rule": "录制规则", + "recording-rule-state": { + "content-recording-rule-evaluation-is-currently-paused": "" + } }, "rule-stats": { "firing": "", @@ -2269,6 +2327,8 @@ "refresh": "" }, "template-row": { + "content-templatefiles": "", + "content-templates": "", "tooltip-copy-template-group": "", "tooltip-delete-template-group": "", "tooltip-edit-template-group": "", @@ -2292,7 +2352,7 @@ }, "misconfigured-badge-text": "配置错误", "misconfigured-warning": "此模板配置错误。", - "misconfigured-warning-details": "模板必须在 Alertmanager 配置的 <1> 和 <4> 部分中定义。" + "misconfigured-warning-details": "" }, "templates-picker": { "button-edit": "", @@ -2337,6 +2397,10 @@ "type-selector-button": { "add-expression": "" }, + "unknown-contact-point-details": { + "unknown-contact-point": "", + "unknown-contact-point-tooltip": "" + }, "unknown-rule-list-item": { "title-unknown-rule-type": "" }, @@ -2437,6 +2501,7 @@ }, "api-keys-table": { "aria-label-delete-api-key": "", + "content-this-api-key-has-expired": "", "expires": "", "last-used-at": "", "migrate-to-service-account": "", @@ -4133,7 +4198,9 @@ }, "variable-check-indicator": { "aria-label-variable-referenced-dashboard": "", - "aria-label-variable-referenced-other-variables-dashboard": "" + "aria-label-variable-referenced-other-variables-dashboard": "", + "content-variable-not-referenced-other-variables-dashboard": "", + "content-variable-referenced-other-variables-dashboard": "" }, "variable-editor-form": { "aria-label-variable-editor-form": "", @@ -4193,6 +4260,7 @@ }, "versions-history-buttons": { "compare-versions": "", + "content-select-two-versions-to-start-comparing": "", "show-more-versions": "" }, "visualization-button": { @@ -4546,6 +4614,7 @@ } }, "correlation-editor-mode-bar": { + "content-correlations-editor-explore-experimental-feature": "", "exit-correlation-editor": "", "save": "" }, @@ -4703,6 +4772,9 @@ "label-select-query": "", "tooltip-select-query-visualize-table": "" }, + "logs-volume-panel": { + "content-streaming": "" + }, "logs-volume-panel-list": { "label-reload-log-volume": "", "loading": "", @@ -4902,6 +4974,7 @@ "label-tags": "", "placeholder-all-service-names": "", "placeholder-all-span-names": "", + "tooltip-collapse": "", "tooltip-duration": "", "tooltip-tags": "" }, @@ -4980,7 +5053,8 @@ "label-show-paths": "" }, "trace-view": { - "no-data": "" + "no-data": "", + "tooltip-copy-icon": "" }, "trace-view-container": { "title-trace": "" @@ -5094,7 +5168,8 @@ "default-password-alert": "", "new-password-label": "", "skip-button": "跳过", - "submit-button": "提交" + "submit-button": "提交", + "tooltip-skip-button": "" }, "contact-admin": "忘记了用户名或邮箱?请与您的 Grafana 管理员联系。", "email-sent": "一封包含重置链接的电子邮件已发送到您的电子邮箱。您很快就会收到它。", @@ -5151,7 +5226,8 @@ "action-editor": { "button": { "confirm": "确认", - "confirm-action": "确认操作" + "confirm-action": "确认操作", + "style": "" }, "inline": { "add-action": "添加操作", @@ -5358,7 +5434,11 @@ "previous-page": "上一页" }, "panel-chrome": { - "aria-label-toggle-collapse": "" + "aria-label-toggle-collapse": "", + "tooltip-cancel": "", + "tooltip-cancel-loading": "", + "tooltip-stop-streaming": "", + "tooltip-streaming": "" }, "panel-menu": { "label": "面板 {{ title }} 的菜单", @@ -5935,7 +6015,8 @@ "log-row-message": { "ellipsis": "… ", "more": "更多", - "see-details": "查看日志详细信息" + "see-details": "查看日志详细信息", + "tooltip-error": "" }, "log-rows": { "disable-popover": { @@ -6912,7 +6993,8 @@ "label-search": "", "label-state": "", "label-type": "", - "subtitle": "" + "subtitle": "", + "tooltip-filter-disabled": "" }, "catalog": { "no-updates-available": "没有可用更新", @@ -6921,7 +7003,7 @@ "available-header": "可用", "button": "全部更新 ({{length}})", "cloud-update-message": "* 插件可能需要等几分钟才能使用。", - "error": "更新插件时出错:", + "error": "", "error-status-text": "失败 - 请参阅错误消息", "header": "以下插件有可用的更新", "installed-header": "已安装", @@ -7131,6 +7213,9 @@ "feature-toggles-age": { "enable-in-config": "" }, + "input-suffix": { + "content-login-details-locked-because-managed-another": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -8059,10 +8144,12 @@ "title-disable-service-account": "" }, "token-expiration": { + "content-this-token-has-expired": "", "expired-label": "", "never": "" }, "token-revoked": { + "content-token-publicly-exposed-please-rotate": "", "revoked-label": "" } }, @@ -8468,7 +8555,7 @@ "range-content": { "apply-button": "应用时间范围", "default-error": "", - "fiscal-year": "财政年度", + "fiscal-year": "", "from-input": "发件人", "open-input-calendar": "打开日历", "range-error": "“发件人”不能在“收件人”之后", @@ -8920,6 +9007,7 @@ "loading": "", "no-unknowns": "未找到重命名或缺失的变量。", "renamed-or-missing-variables": "重命名或缺失的变量", + "tooltip-renamed-or-missing-variables": "", "variable": "变量" }, "variable-check-indicator": { diff --git a/public/locales/zh-Hant/grafana.json b/public/locales/zh-Hant/grafana.json index 29ae087b7aa..b803d2d924b 100644 --- a/public/locales/zh-Hant/grafana.json +++ b/public/locales/zh-Hant/grafana.json @@ -21,7 +21,9 @@ "permission-list-item": { "inherited": "繼承自資料夾", "locked-aria-label": "", - "remove-aria-label": "" + "remove-aria-label": "", + "tooltip-inherited-permission": "", + "tooltip-provisioned-permission": "" }, "permissions": { "add-label": "新增權限", @@ -42,6 +44,11 @@ }, "actions": { "action-editor": { + "button": { + "style": { + "background-color": "" + } + }, "label-headers": "", "label-url": "", "placeholder-url": "" @@ -70,6 +77,7 @@ "confirm-modal-body-2": "", "get-stage-cell": { "beta": "", + "content-general-availability": "", "deprecated": "", "ga": "" }, @@ -128,6 +136,12 @@ "test-mapping-heading": "測試使用者對應", "test-mapping-run-button": "執行" }, + "ldap-connection-status": { + "columns": { + "content-connection-is-available": "", + "content-connection-is-not-available": "" + } + }, "ldap-error-box": { "title-connection-error": "" }, @@ -161,6 +175,9 @@ "title": "LDAP 同步處理" }, "ldap-user-groups": { + "columns": { + "content-no-matching-organizations-found": "" + }, "no-org-found": "" }, "ldap-user-info": { @@ -284,12 +301,14 @@ }, "users-table": { "columns": { + "content-grafana-admin": "", "title-edit-user": "" }, "edit-aria-label": "", "edit-tooltip": "", "last-seen-never": "從不", - "no-licensed-roles": "未指派" + "no-licensed-roles": "未指派", + "tooltip-assigned-role": "" } }, "alert-labels": { @@ -404,11 +423,9 @@ }, "alert-rule-form": { "action-buttons": { - "delete": "", "edit-yaml": "", - "save-exit": "" - }, - "title-delete-rule": "" + "save": "" + } }, "alert-rule-name-and-metric": { "aria-label-name": "", @@ -440,6 +457,7 @@ "unprocessed-the-alert-is-received": "" }, "alert-state-tag": { + "content-alert-evaluation-is-currently-paused": "", "paused": "" }, "alert-warning": { @@ -536,6 +554,9 @@ "warning-restore-manually": "您正在手動還原此警報規則的舊版本。在儲存規則定義之前,請仔細檢查變更。", "warning-restore-manually-title": "手動還原規則" }, + "all-matches-indicator": { + "content-this-policy-matches-all-labels": "" + }, "am-root-route-form": { "am-group-description-label": "", "am-group-interval-description": "", @@ -648,6 +669,9 @@ "of": "", "when": "" }, + "clear-filter-button-object-renderer": { + "content-clear-filter": "" + }, "clone-rule-button": { "title-copy": "" }, @@ -772,6 +796,9 @@ "contactPointFilter": { "label": "聯絡點" }, + "continue-matching-indicator": { + "content-route-continue-matching-other-policies": "" + }, "copy-to-clipboard": "將「{{label}}」複製到剪貼簿", "create-metadata": { "view-dashboard": "", @@ -800,6 +827,10 @@ "current-selection-dashboard": "", "current-selection-panel": "", "fallback-dashboards-string": "", + "panel-row": { + "content-panel-valid-identifier": "", + "tooltip-alert-tab-support": "" + }, "placeholder-search-dashboard": "", "placeholder-search-panel": "", "select-dashboard-available-panels": "", @@ -821,9 +852,11 @@ "update-datasource": "" }, "declare-incident-button": { + "content-grafana-incident-installed-configured-correctly": "", "declare-incident": "" }, "declare-incident-menu-item": { + "content-grafana-incident-installed-configured-correctly": "", "label-declare-incident": "" }, "delete-rule-modal": { @@ -859,6 +892,9 @@ }, "unknown": "" }, + "disabled-tooltip": { + "content-appear-compatible-datasources": "" + }, "draggable-rules-table": { "evals-to-start-alerting": "", "pending-period": "", @@ -889,10 +925,19 @@ "text-loading-template": "", "title-failed-to-fetch-notification-template": "" }, + "editor": { + "edit-alert-rule": "", + "edit-recording-rule": "", + "new-alert-rule": "", + "new-recording-rule": "" + }, "error-modal": { "failed-to-update-your-configuration": "", "title-something-went-wrong": "" }, + "error-summary-button": { + "content-show-all-errors": "" + }, "evaluation-behavior-summary": { "evaluate": "", "label-evaluate": "", @@ -923,7 +968,6 @@ "existing-rule-editor": { "sorry-permission": "", "sorry-this-rule-does-not-exist": "", - "text-loading-rule": "", "title-cannot-edit-rule": "", "title-failed-to-load-rule": "", "title-rule-not-found": "" @@ -1174,6 +1218,9 @@ "group-loader": { "group-load-failed": "" }, + "group-status": { + "content-the-group-is-being-deleted": "" + }, "header": { "tooltip-remove": "" }, @@ -1342,7 +1389,8 @@ "common-labels": "", "error-unable-to-fetch": "", "loading": "", - "title-error-fetching-the-state-history": "" + "title-error-fetching-the-state-history": "", + "tooltip-common-labels": "" }, "manage-permissions": { "button": "管理權限", @@ -1435,6 +1483,7 @@ }, "mute-timing-time-range": { "add-another-time-range": "", + "content-this-time-interval-is-disabled": "", "description-time-range": "", "label-end-time": "", "label-start-time": "", @@ -1590,6 +1639,9 @@ "plugin-integrations": { "tailored-apps": "" }, + "plugin-origin-badge": { + "tooltip-managed-by-plugin": "" + }, "policies": { "aria-label-collapse": "", "aria-label-expand": "", @@ -1687,6 +1739,9 @@ "prometheus-consistency-check": { "title-unable-to-check-the-rule-status": "" }, + "provisioned-tooltip": { + "content-provisioned-items-cannot-edited": "" + }, "provisioning": { "badge-tooltip-provenance": "此資源已透過 {{provenance}} 設定,無法透過使用者介面編輯", "badge-tooltip-standard": "此資源已設定,無法透過使用者介面編輯", @@ -1921,14 +1976,14 @@ }, "threshold": { "recovery": { - "stop-alerting-above": "當高於以下值時停止警報", - "stop-alerting-bellow": "當低於以下值時停止警報", - "stop-alerting-equal": "當等於以下值時停止警報", - "stop-alerting-inside-range": "當在以下範圍內時停止警報", - "stop-alerting-less": "當小於以下值時停止警報", - "stop-alerting-more": "當大於以下值時停止警報", - "stop-alerting-not-equal": "當不等於以下值時停止警報", - "stop-alerting-outside-range": "當超出以下範圍時停止警報", + "stop-alerting-above": "", + "stop-alerting-bellow": "", + "stop-alerting-equal": "", + "stop-alerting-inside-range": "", + "stop-alerting-less": "", + "stop-alerting-more": "", + "stop-alerting-not-equal": "", + "stop-alerting-outside-range": "", "title": "自訂恢復閾值" } } @@ -2026,7 +2081,10 @@ "for": "", "na": "", "paused": "已暫停", - "recording-rule": "錄製規則" + "recording-rule": "錄製規則", + "recording-rule-state": { + "content-recording-rule-evaluation-is-currently-paused": "" + } }, "rule-stats": { "firing": "", @@ -2269,6 +2327,8 @@ "refresh": "" }, "template-row": { + "content-templatefiles": "", + "content-templates": "", "tooltip-copy-template-group": "", "tooltip-delete-template-group": "", "tooltip-edit-template-group": "", @@ -2292,7 +2352,7 @@ }, "misconfigured-badge-text": "設定錯誤", "misconfigured-warning": "此範本設定錯誤。", - "misconfigured-warning-details": "必須在警報管理設定的<1>和<4>兩個部分中定義範本。" + "misconfigured-warning-details": "" }, "templates-picker": { "button-edit": "", @@ -2337,6 +2397,10 @@ "type-selector-button": { "add-expression": "" }, + "unknown-contact-point-details": { + "unknown-contact-point": "", + "unknown-contact-point-tooltip": "" + }, "unknown-rule-list-item": { "title-unknown-rule-type": "" }, @@ -2437,6 +2501,7 @@ }, "api-keys-table": { "aria-label-delete-api-key": "", + "content-this-api-key-has-expired": "", "expires": "", "last-used-at": "", "migrate-to-service-account": "", @@ -4133,7 +4198,9 @@ }, "variable-check-indicator": { "aria-label-variable-referenced-dashboard": "", - "aria-label-variable-referenced-other-variables-dashboard": "" + "aria-label-variable-referenced-other-variables-dashboard": "", + "content-variable-not-referenced-other-variables-dashboard": "", + "content-variable-referenced-other-variables-dashboard": "" }, "variable-editor-form": { "aria-label-variable-editor-form": "", @@ -4193,6 +4260,7 @@ }, "versions-history-buttons": { "compare-versions": "", + "content-select-two-versions-to-start-comparing": "", "show-more-versions": "" }, "visualization-button": { @@ -4546,6 +4614,7 @@ } }, "correlation-editor-mode-bar": { + "content-correlations-editor-explore-experimental-feature": "", "exit-correlation-editor": "", "save": "" }, @@ -4703,6 +4772,9 @@ "label-select-query": "", "tooltip-select-query-visualize-table": "" }, + "logs-volume-panel": { + "content-streaming": "" + }, "logs-volume-panel-list": { "label-reload-log-volume": "", "loading": "", @@ -4902,6 +4974,7 @@ "label-tags": "", "placeholder-all-service-names": "", "placeholder-all-span-names": "", + "tooltip-collapse": "", "tooltip-duration": "", "tooltip-tags": "" }, @@ -4980,7 +5053,8 @@ "label-show-paths": "" }, "trace-view": { - "no-data": "" + "no-data": "", + "tooltip-copy-icon": "" }, "trace-view-container": { "title-trace": "" @@ -5094,7 +5168,8 @@ "default-password-alert": "", "new-password-label": "", "skip-button": "跳過", - "submit-button": "提交" + "submit-button": "提交", + "tooltip-skip-button": "" }, "contact-admin": "忘記了使用者名稱或電子郵件嗎?請聯絡您的 Grafana 管理員。", "email-sent": "包含重設連結的電子郵件已傳送至電子郵件地址。您應該很快就會收到。", @@ -5151,7 +5226,8 @@ "action-editor": { "button": { "confirm": "確認", - "confirm-action": "確認動作" + "confirm-action": "確認動作", + "style": "" }, "inline": { "add-action": "新增動作", @@ -5358,7 +5434,11 @@ "previous-page": "上一頁" }, "panel-chrome": { - "aria-label-toggle-collapse": "" + "aria-label-toggle-collapse": "", + "tooltip-cancel": "", + "tooltip-cancel-loading": "", + "tooltip-stop-streaming": "", + "tooltip-streaming": "" }, "panel-menu": { "label": "面板「{{ title }}」功能表", @@ -5935,7 +6015,8 @@ "log-row-message": { "ellipsis": "… ", "more": "更多", - "see-details": "查看日誌詳細資訊" + "see-details": "查看日誌詳細資訊", + "tooltip-error": "" }, "log-rows": { "disable-popover": { @@ -6912,7 +6993,8 @@ "label-search": "", "label-state": "", "label-type": "", - "subtitle": "" + "subtitle": "", + "tooltip-filter-disabled": "" }, "catalog": { "no-updates-available": "沒有可用的更新", @@ -6921,7 +7003,7 @@ "available-header": "可用", "button": "全部更新 ({{length}})", "cloud-update-message": "* 外掛程式可能需要幾分鐘才能使用。", - "error": "更新外掛程式時發生錯誤:", + "error": "", "error-status-text": "失敗 - 請參閱錯誤訊息", "header": "以下外掛程式有可用的更新", "installed-header": "已安裝", @@ -7131,6 +7213,9 @@ "feature-toggles-age": { "enable-in-config": "" }, + "input-suffix": { + "content-login-details-locked-because-managed-another": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -8059,10 +8144,12 @@ "title-disable-service-account": "" }, "token-expiration": { + "content-this-token-has-expired": "", "expired-label": "", "never": "" }, "token-revoked": { + "content-token-publicly-exposed-please-rotate": "", "revoked-label": "" } }, @@ -8468,7 +8555,7 @@ "range-content": { "apply-button": "套用時間範圍", "default-error": "", - "fiscal-year": "財政年度", + "fiscal-year": "", "from-input": "自", "open-input-calendar": "開啟行事曆", "range-error": "「自」不能晚於「至」", @@ -8920,6 +9007,7 @@ "loading": "正在載入…", "no-unknowns": "未找到重新命名或遺失的變數。", "renamed-or-missing-variables": "重新命名或遺失的變數", + "tooltip-renamed-or-missing-variables": "", "variable": "變數" }, "variable-check-indicator": { From ba6d0f59ff8dc76adaafa4f8415487a2a042f4a1 Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Thu, 1 May 2025 13:31:59 +0200 Subject: [PATCH 068/849] Chore: Issue triager, update vault paths (#104814) * Chore: Issue triager, update vault paths * Empty commit * update commands tasjk --- .github/workflows/commands.yml | 12 ++++++------ .github/workflows/issue-opened.yml | 24 ++++++++++++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml index 3c3987b549b..81d4a7d81a0 100644 --- a/.github/workflows/commands.yml +++ b/.github/workflows/commands.yml @@ -42,15 +42,15 @@ jobs: with: # Secrets placed in the ci/repo/grafana/grafana/plugins_platform_issue_commands_github_bot path in Vault repo_secrets: | - GH_APP_ID=plugins_platform_issue_commands_github_bot:app_id - GH_APP_PEM=plugins_platform_issue_commands_github_bot:app_pem + GITHUB_APP_ID=grafana_pr_automation_app:app_id + GITHUB_APP_PRIVATE_KEY=grafana_pr_automation_app:app_pem - - name: "Generate token" + - name: Generate token id: generate_token - uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 + uses: actions/create-github-app-token@3ff1caaa28b64c9cc276ce0a02e2ff584f3900c5 # v2.0.2 with: - app_id: ${{ env.GH_APP_ID }} - private_key: ${{ env.GH_APP_PEM }} + app-id: ${{ env.GITHUB_APP_ID }} + private-key: ${{ env.GITHUB_APP_PRIVATE_KEY }} - name: Checkout Actions uses: actions/checkout@v4 # v4.2.2 diff --git a/.github/workflows/issue-opened.yml b/.github/workflows/issue-opened.yml index 4dddfe42519..8e3264664fc 100644 --- a/.github/workflows/issue-opened.yml +++ b/.github/workflows/issue-opened.yml @@ -43,15 +43,15 @@ jobs: with: # Secrets placed in the ci/repo/grafana/grafana/plugins_platform_issue_commands_github_bot path in Vault repo_secrets: | - GH_APP_ID=plugins_platform_issue_commands_github_bot:app_id - GH_APP_PEM=plugins_platform_issue_commands_github_bot:app_pem + GITHUB_APP_ID=grafana_pr_automation_app:app_id + GITHUB_APP_PRIVATE_KEY=grafana_pr_automation_app:app_pem - - name: "Generate token" + - name: Generate token id: generate_token - uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 + uses: actions/create-github-app-token@3ff1caaa28b64c9cc276ce0a02e2ff584f3900c5 # v2.0.2 with: - app_id: ${{ env.GH_APP_ID }} - private_key: ${{ env.GH_APP_PEM }} + app-id: ${{ env.GITHUB_APP_ID }} + private-key: ${{ env.GITHUB_APP_PRIVATE_KEY }} - name: Run Commands uses: ./actions/commands @@ -77,15 +77,15 @@ jobs: repo_secrets: | AUTOTRIAGER_OPENAI_API_KEY=plugins_platform_issue_triager:AUTOTRIAGER_OPENAI_API_KEY AUTOTRIAGER_SLACK_WEBHOOK_URL=plugins_platform_issue_triager:AUTOTRIAGER_SLACK_WEBHOOK_URL - GH_APP_ID=plugins_platform_issue_commands_github_bot:app_id - GH_APP_PEM=plugins_platform_issue_commands_github_bot:app_pem + GITHUB_APP_ID=plugins_platform_issue_triager_github_bot:app_id + GITHUB_APP_PRIVATE_KEY=plugins_platform_issue_triager_github_bot:app_pem - - name: "Generate token" + - name: Generate token id: generate_token - uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 + uses: actions/create-github-app-token@3ff1caaa28b64c9cc276ce0a02e2ff584f3900c5 # v2.0.2 with: - app_id: ${{ env.GH_APP_ID }} - private_key: ${{ env.GH_APP_PEM }} + app-id: ${{ env.GITHUB_APP_ID }} + private-key: ${{ env.GITHUB_APP_PRIVATE_KEY }} - name: Checkout uses: actions/checkout@v4 # v4.2.2 From 3773429d10d3030564df821f8aa4d0d62afce6c9 Mon Sep 17 00:00:00 2001 From: Joey <90795735+joey-grafana@users.noreply.github.com> Date: Thu, 1 May 2025 12:34:30 +0100 Subject: [PATCH 069/849] Tempo: Add support for structural operators (#104400) Add support for structural operators --- .../app/plugins/datasource/tempo/package.json | 2 +- .../datasource/tempo/traceql/autocomplete.ts | 73 +++++++++++++++++++ yarn.lock | 10 +-- 3 files changed, 79 insertions(+), 6 deletions(-) diff --git a/public/app/plugins/datasource/tempo/package.json b/public/app/plugins/datasource/tempo/package.json index b9248534a4f..409886cb0d2 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.5", "@grafana/data": "workspace:*", "@grafana/e2e-selectors": "workspace:*", - "@grafana/lezer-traceql": "0.0.21", + "@grafana/lezer-traceql": "0.0.22", "@grafana/monaco-logql": "^0.0.8", "@grafana/o11y-ds-frontend": "workspace:*", "@grafana/plugin-ui": "0.10.5", diff --git a/public/app/plugins/datasource/tempo/traceql/autocomplete.ts b/public/app/plugins/datasource/tempo/traceql/autocomplete.ts index 1c4e13fd611..906d96416d6 100644 --- a/public/app/plugins/datasource/tempo/traceql/autocomplete.ts +++ b/public/app/plugins/datasource/tempo/traceql/autocomplete.ts @@ -128,6 +128,7 @@ export class CompletionProvider implements monacoTypes.languages.CompletionItemP detail: 'Negated regular expression', }, ]; + // https://grafana.com/docs/tempo/latest/traceql/#structural static readonly structuralOps: MinimalCompletionItem[] = [ { label: '>>', @@ -164,6 +165,78 @@ export class CompletionProvider implements monacoTypes.languages.CompletionItemP documentation: 'Sibling operator. Checks that spans matching {condA} and {condB} are siblings of the same parent span.', }, + // Union structural operators + { + label: '&>>', + insertText: '&>>', + detail: 'Union Descendant', + documentation: + 'The descendant operator (>>) looks for spans matching {condB} that are descendants of a span matching {condA}', + }, + { + label: '&>', + insertText: '&>', + detail: 'Union Child', + documentation: + 'The child operator (>) looks for spans matching {condB} that are direct child spans of a parent matching {condA}', + }, + { + label: '&<<', + insertText: '&<<', + detail: 'Union Ancestor', + documentation: + 'The ancestor operator (<<) looks for spans matching {condB} that are ancestor of a span matching {condA}', + }, + { + label: '&<', + insertText: '&<', + detail: 'Union Parent', + documentation: + 'The parent operator (<) looks for spans matching {condB} that are direct parent spans of a child matching {condA}', + }, + { + label: '&~', + insertText: '&~', + detail: 'Union Sibling', + documentation: + 'The sibling operator (~) looks at spans matching {condB} that have at least one sibling matching {condA}', + }, + // Negated structural operators + { + label: '!>>', + insertText: '!>>', + detail: 'Not Descendant', + documentation: + 'The not-descendant operator (!>>) looks for spans matching {condB} that are not descendant spans of a parent matching {condA}', + }, + { + label: '!>', + insertText: '!>', + detail: 'Not Child', + documentation: + 'The not-child operator (!>) looks for spans matching {condB} that are not direct child spans of a parent matching {condA}', + }, + { + label: '!<<', + insertText: '!<<', + detail: 'Not Ancestor', + documentation: + 'The not-ancestor operator (!<<) looks for spans matching {condB} that are not ancestor spans of a child matching {condA}', + }, + { + label: '!<', + insertText: '!<', + detail: 'Not Parent', + documentation: + 'The not-parent operator (!<) looks for spans matching {condB} that are not direct parent spans of a child matching {condA}', + }, + { + label: '!~', + insertText: '!~', + detail: 'Not Sibling', + documentation: + 'The not-sibling operator (!~) looks for spans matching {condB} that do not have at least one sibling matching {condA}', + }, ]; static readonly spansetOps: MinimalCompletionItem[] = [ diff --git a/yarn.lock b/yarn.lock index bab3b3d8e73..c2d92d559fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2823,7 +2823,7 @@ __metadata: "@emotion/css": "npm:11.13.5" "@grafana/data": "workspace:*" "@grafana/e2e-selectors": "workspace:*" - "@grafana/lezer-traceql": "npm:0.0.21" + "@grafana/lezer-traceql": "npm:0.0.22" "@grafana/monaco-logql": "npm:^0.0.8" "@grafana/o11y-ds-frontend": "workspace:*" "@grafana/plugin-configs": "npm:12.1.0-pre" @@ -3178,12 +3178,12 @@ __metadata: languageName: node linkType: hard -"@grafana/lezer-traceql@npm:0.0.21": - version: 0.0.21 - resolution: "@grafana/lezer-traceql@npm:0.0.21" +"@grafana/lezer-traceql@npm:0.0.22": + version: 0.0.22 + resolution: "@grafana/lezer-traceql@npm:0.0.22" peerDependencies: "@lezer/lr": ^1.4.2 - checksum: 10/de27346b3f7e45cc10fae18e7172c7686a10d6979b057a33e72f3d4aeca498b7c5ba80202f1e59207f417b69c3f1703bef313dbde155b7a6ee211726d665f085 + checksum: 10/e7f640e902d0738c72950a7584b5ceed68114582eb9c33546bf3fbc938baf7a460b93cd9b90e65a0d8f16a3870ecc282c6ecc12a6c321232247abc05111662df languageName: node linkType: hard From 700f208b6e6c738be0aa56d3a2ae6e8a17973088 Mon Sep 17 00:00:00 2001 From: Yulia Shanyrova Date: Thu, 1 May 2025 13:39:50 +0200 Subject: [PATCH 070/849] Plugins: Add sponsorship link to plugin details panel (#104687) Add sponsorship link to plugin details panel --- public/app/features/plugins/admin/api.ts | 1 + .../components/PluginDetailsPanel.test.tsx | 6 +++++- .../admin/components/PluginDetailsPanel.tsx | 19 ++++++++++++++++++- public/app/features/plugins/admin/types.ts | 2 ++ public/locales/en-US/grafana.json | 1 + 5 files changed, 27 insertions(+), 2 deletions(-) diff --git a/public/app/features/plugins/admin/api.ts b/public/app/features/plugins/admin/api.ts index 120bdf3a66e..a6bbf4625ec 100644 --- a/public/app/features/plugins/admin/api.ts +++ b/public/app/features/plugins/admin/api.ts @@ -39,6 +39,7 @@ export async function getPluginDetails(id: string): Promise=9.0.0', statusContext: 'stable', }, @@ -139,12 +140,13 @@ describe('PluginDetailsPanel', () => { expect(panel).toHaveStyle({ width: '300px' }); }); - it('should render license, documentation, repository, raise issue links', () => { + it('should render license, documentation, repository, raise issue, sponsorship links', () => { render(); const repositoryLink = screen.getByTestId('plugin-details-repository-link'); const licenseLink = screen.getByTestId('plugin-details-license-link'); const documentationLink = screen.getByTestId('plugin-details-documentation-link'); const raiseIssueLink = screen.getByTestId('plugin-details-raise-issue-link'); + const sponsorshipLink = screen.getByTestId('plugin-details-sponsorship-link'); expect(repositoryLink).toBeInTheDocument(); expect(repositoryLink).toHaveAttribute('href', 'https://github.com/grafana/test-plugin'); @@ -154,6 +156,8 @@ describe('PluginDetailsPanel', () => { expect(documentationLink).toHaveAttribute('href', 'https://test-plugin.com/docs'); expect(raiseIssueLink).toBeInTheDocument(); expect(raiseIssueLink).toHaveAttribute('href', 'https://github.com/grafana/test-plugin/issues/new'); + expect(sponsorshipLink).toBeInTheDocument(); + expect(sponsorshipLink).toHaveAttribute('href', 'https://github.com/sponsors/grafana'); }); it('should not render license, documentation, repository, raise issue links in custom links', () => { diff --git a/public/app/features/plugins/admin/components/PluginDetailsPanel.tsx b/public/app/features/plugins/admin/components/PluginDetailsPanel.tsx index 1d88ea3ece8..2305f49f7d1 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsPanel.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsPanel.tsx @@ -40,13 +40,18 @@ export function PluginDetailsPanel(props: Props): React.ReactElement | null { plugin.details?.licenseUrl, plugin.details?.documentationUrl, plugin.details?.raiseAnIssueUrl, + plugin.details?.sponsorshipUrl, ] .map(normalizeURL) .includes(normalizeURL(link.url)); return customLinksFiltered; }); const shouldRenderLinks = - plugin.url || plugin.details?.licenseUrl || plugin.details?.documentationUrl || plugin.details?.raiseAnIssueUrl; + plugin.url || + plugin.details?.licenseUrl || + plugin.details?.documentationUrl || + plugin.details?.raiseAnIssueUrl || + plugin.details?.sponsorshipUrl; const styles = useStyles2(getStyles); @@ -148,6 +153,18 @@ export function PluginDetailsPanel(props: Props): React.ReactElement | null { Documentation )} + {plugin.details?.sponsorshipUrl && ( + + Sponsor this developer + + )}
diff --git a/public/app/features/plugins/admin/types.ts b/public/app/features/plugins/admin/types.ts index 06ed5de7787..00fd9e85d19 100644 --- a/public/app/features/plugins/admin/types.ts +++ b/public/app/features/plugins/admin/types.ts @@ -85,6 +85,7 @@ export interface CatalogPluginDetails { lastCommitDate?: string; licenseUrl?: string; documentationUrl?: string; + sponsorshipUrl?: string; raiseAnIssueUrl?: string; signatureType?: PluginSignatureType; signature?: PluginSignatureStatus; @@ -154,6 +155,7 @@ export type RemotePlugin = { lastCommitDate?: string; licenseUrl?: string; documentationUrl?: string; + sponsorshipUrl?: string; raiseAnIssueUrl?: string; }; diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 724c3e565fa..57ff9764163 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -7083,6 +7083,7 @@ "reportAbuseTooltip": "Report issues related to malicious or harmful plugins directly to Grafana Labs.", "repository": "Repository", "signature": "Signature", + "sponsorDeveloper": "Sponsor this developer", "status": "Status" }, "modal": { From 065be6117e47f301a8dc0d457ef0632a6d832b31 Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Thu, 1 May 2025 14:23:18 +0200 Subject: [PATCH 071/849] Chore: remove misc stats secret for issue commands (#104819) remove cehck for misc stats --- .github/workflows/commands.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml index 81d4a7d81a0..2c0f8586132 100644 --- a/.github/workflows/commands.yml +++ b/.github/workflows/commands.yml @@ -24,7 +24,7 @@ jobs: id: check shell: bash run: | - if [ "${{ github.repository }}" == "grafana/grafana" ] && [ -n "${{ secrets.GRAFANA_MISC_STATS_API_KEY }}" ]; then + if [ "${{ github.repository }}" == "grafana/grafana" ]; then echo "has-secrets=1" >> "$GITHUB_OUTPUT" fi @@ -65,6 +65,6 @@ jobs: - name: Run Commands uses: ./actions/commands with: - metricsWriteAPIKey: ${{secrets.GRAFANA_MISC_STATS_API_KEY}} + metricsWriteAPIKey: "" token: ${{ steps.generate_token.outputs.token }} configPath: commands From 26ce124208dcfad36e15fb0a7df60fd02208a8e2 Mon Sep 17 00:00:00 2001 From: Alexa V <239999+axelavargas@users.noreply.github.com> Date: Thu, 1 May 2025 15:47:04 +0200 Subject: [PATCH 072/849] Dashboard: SchemaV2 - Fix Import showing grafana datasources (#104461) * Fix: do not map when identifying default grafana ds * add also datasource type * Refactor code, add unit test * Fix types references and linting * Update public/app/features/manage-dashboards/state/actions.test.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../manage-dashboards/state/actions.test.ts | 65 +++++++++++++++++-- .../manage-dashboards/state/actions.ts | 15 +++-- 2 files changed, 70 insertions(+), 10 deletions(-) diff --git a/public/app/features/manage-dashboards/state/actions.test.ts b/public/app/features/manage-dashboards/state/actions.test.ts index da5693ad8c4..c4aebee06de 100644 --- a/public/app/features/manage-dashboards/state/actions.test.ts +++ b/public/app/features/manage-dashboards/state/actions.test.ts @@ -3,11 +3,11 @@ import { thunkTester } from 'test/core/thunk/thunkTester'; import { DataSourceInstanceSettings, ThresholdsMode } from '@grafana/data'; import { defaultDashboard, FieldColorModeId } from '@grafana/schema'; import { - DashboardV2Spec, - defaultDashboardV2Spec, + Spec as DashboardV2Spec, + defaultSpec as defaultDashboardV2Spec, defaultPanelSpec, defaultQueryVariableSpec, -} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0'; +} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; import { browseDashboardsAPI } from 'app/features/browse-dashboards/api/browseDashboardsAPI'; import { getLibraryPanel } from 'app/features/library-panels/state/api'; @@ -16,7 +16,13 @@ import { LibraryElementDTO } from '../../library-panels/types'; import { DashboardJson } from '../types'; import { validateDashboardJson } from '../utils/validation'; -import { getLibraryPanelInputs, importDashboard, processDashboard, processV2Datasources } from './actions'; +import { + getLibraryPanelInputs, + importDashboard, + processDashboard, + processV2DatasourceInput, + processV2Datasources, +} from './actions'; import { DataSourceInput, ImportDashboardDTO, initialImportDashboardState, InputType } from './reducers'; jest.mock('app/features/library-panels/state/api'); @@ -29,7 +35,7 @@ jest.mock('@grafana/runtime', () => ({ getDataSourceSrv: () => ({ ...jest.requireActual('@grafana/runtime').getDataSourceSrv(), get: jest.fn().mockImplementation((dsType: { type: string }) => { - const dsList: { + const dsListTypeDSMock: { [key: string]: { uid: string; name: string; @@ -55,8 +61,15 @@ jest.mock('@grafana/runtime', () => ({ type: 'grafana', meta: { id: 'grafana' }, }, + // "datasource" type is what we call "--Dashboard--" datasource + datasource: { + uid: '--Dashboard--', + name: '--Dashboard--', + type: 'datasource', + meta: { id: 'dashboard' }, + }, }; - return dsList[dsType.type]; + return dsListTypeDSMock[dsType.type]; }), }), })); @@ -959,3 +972,43 @@ describe('processV2Datasources', () => { ); }); }); + +describe('processV2DatasourceInput', () => { + // should not map grafana datasource input or dashboard datasource input + it('Should not map grafana datasource input', async () => { + const queryVariable = { + kind: 'QueryVariable', + spec: { + ...defaultQueryVariableSpec(), + name: 'var2WithGrafanaDs', + query: { + kind: 'grafana', + spec: { + panelId: 2, + }, + }, + }, + }; + const result = await processV2DatasourceInput(queryVariable.spec, {}); + expect(result).toEqual({}); + }); + + it('Should not map dashboard datasource input', async () => { + // create a panel with dashboard datasource input + const panelQuery = { + kind: 'PanelQuery', + spec: { + refId: 'A', + hidden: false, + query: { + kind: 'datasource', + spec: { + panelId: 2, + }, + }, + }, + }; + const result = await processV2DatasourceInput(panelQuery.spec, {}); + expect(result).toEqual({}); + }); +}); diff --git a/public/app/features/manage-dashboards/state/actions.ts b/public/app/features/manage-dashboards/state/actions.ts index fd82c2faace..1b5b02e24e4 100644 --- a/public/app/features/manage-dashboards/state/actions.ts +++ b/public/app/features/manage-dashboards/state/actions.ts @@ -162,26 +162,26 @@ export function processV2Datasources(dashboard: DashboardV2Spec): ThunkResult = {}; + let inputs: Record = {}; for (const element of Object.values(elements)) { if (element.kind !== 'Panel') { throw new Error('Only panels are currenlty supported in v2 dashboards'); } if (element.spec.data.spec.queries.length > 0) { for (const query of element.spec.data.spec.queries) { - await processV2DatasourceInput(query.spec, inputs); + inputs = await processV2DatasourceInput(query.spec, inputs); } } } for (const variable of variables) { if (variable.kind === 'QueryVariable') { - await processV2DatasourceInput(variable.spec, inputs); + inputs = await processV2DatasourceInput(variable.spec, inputs); } } for (const annotation of annotations) { - await processV2DatasourceInput(annotation.spec, inputs); + inputs = await processV2DatasourceInput(annotation.spec, inputs); } dispatch(setInputs(Object.values(inputs))); @@ -337,6 +337,12 @@ export async function processV2DatasourceInput( const datasourceRef = obj?.datasource; if (!datasourceRef && obj?.query) { const dsType = obj.query.kind; + // if dsType is grafana, it means we are using a built-in annotation or default grafana datasource, in those + // cases we don't need to map it + // "datasource" type is what we call "--Dashboard--" datasource <.-.> + if (dsType === 'grafana' || dsType === 'datasource') { + return inputs; + } const datasource = await getDatasourceSrv().get({ type: dsType }); let dataSourceInput: DataSourceInput | undefined; if (datasource) { @@ -363,4 +369,5 @@ export async function processV2DatasourceInput( inputs[dsType] = dataSourceInput; } } + return inputs; } From 575a13e19d99783ba17ed4de191ffc4b9a4a6a41 Mon Sep 17 00:00:00 2001 From: Christopher Moyer <35463610+chri2547@users.noreply.github.com> Date: Thu, 1 May 2025 09:03:56 -0500 Subject: [PATCH 073/849] Docs: incorporates learning journey feedback (#104735) incorporates learning journey feedback Co-authored-by: Jack Baldry --- docs/sources/dashboards/_index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/dashboards/_index.md b/docs/sources/dashboards/_index.md index c24b99d34cc..69696e00405 100644 --- a/docs/sources/dashboards/_index.md +++ b/docs/sources/dashboards/_index.md @@ -59,9 +59,9 @@ refs: {{< shared id="dashboard-overview" >}} -A Grafana dashboard is a set of one or more [panels](ref:panels), organized and arranged into one or more rows, that provide an at-a-glance view of related information. These panels are created using components that query and transform raw data from a data source into charts, graphs, and other visualizations. +A Grafana dashboard is a set of one or more [panels](ref:panels), organized and arranged into one or more rows, that provide an at-a-glance view of related information. These panels are created using components that query and transform raw data from a data source into visualizations. -A data source can be an SQL database, Grafana Loki, Grafana Mimir, or a JSON-based API. It can even be a basic CSV file. Data source plugins take a query you want answered, retrieve the data from the data source, and reconcile the differences between the data model of the data source and the data model of Grafana dashboards. +A data source can be an SQL database, Grafana Loki, Grafana Mimir, or an API endpoint. It can even be a basic CSV file. Data source plugins take a query you want answered, retrieve the data from the data source, and reconcile the differences between the data model of the data source and the data model of Grafana dashboards. {{< /shared >}} From 75f1ed6d31ce0ccd5b55d3ce56825e8d60ba87c7 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Thu, 1 May 2025 15:17:47 +0100 Subject: [PATCH 074/849] Internationalisation: add workflow to automatically create tasks in Crowdin (#104405) * convert to ts * fix path * add yarn install step * revert to commonjs for now * weird syntax... * test task creation * just use workflow step id * update workflow * get workflow step id from crowdin * testing... * final test * tidy up * typescript with type assertion until upstream is fixed * fix CODEOWNERS --- .github/CODEOWNERS | 2 +- .../workflows/i18n-crowdin-create-tasks.yml | 8 +- .../workflows/scripts/crowdin/create-tasks.js | 84 ------------- .../workflows/scripts/crowdin/create-tasks.ts | 110 ++++++++++++++++++ 4 files changed, 117 insertions(+), 87 deletions(-) delete mode 100644 .github/workflows/scripts/crowdin/create-tasks.js create mode 100644 .github/workflows/scripts/crowdin/create-tasks.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 699670c1071..d59e2171c4a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -812,7 +812,7 @@ embed.go @grafana/grafana-as-code /.github/workflows/i18n-crowdin-upload.yml @grafana/grafana-frontend-platform /.github/workflows/i18n-crowdin-download.yml @grafana/grafana-frontend-platform /.github/workflows/i18n-crowdin-create-tasks.yml @grafana/grafana-frontend-platform -/.github/workflows/scripts/crowdin/create-tasks.js @grafana/grafana-frontend-platform +/.github/workflows/scripts/crowdin/create-tasks.ts @grafana/grafana-frontend-platform /.github/workflows/pr-go-workspace-check.yml @grafana/grafana-app-platform-squad /.github/workflows/pr-dependabot-update-go-workspace.yml @grafana/grafana-app-platform-squad /.github/workflows/pr-k8s-codegen-check.yml @grafana/grafana-app-platform-squad diff --git a/.github/workflows/i18n-crowdin-create-tasks.yml b/.github/workflows/i18n-crowdin-create-tasks.yml index be4a5c3c270..f17552ea362 100644 --- a/.github/workflows/i18n-crowdin-create-tasks.yml +++ b/.github/workflows/i18n-crowdin-create-tasks.yml @@ -2,8 +2,10 @@ name: Crowdin Create Tasks on: workflow_dispatch: + # TODO uncomment when we're confident this works + # once a week on Sunday at midnight # schedule: - # - cron: "0 0 * * *" + # - cron: "0 0 * * 0" jobs: create-tasks-in-crowdin: @@ -33,8 +35,10 @@ jobs: with: node-version-file: '.nvmrc' + - run: yarn install --immutable --check-cache + - name: Create tasks env: CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} - run: node ./.github/workflows/scripts/crowdin/create-tasks.js + run: node --experimental-strip-types ./.github/workflows/scripts/crowdin/create-tasks.ts diff --git a/.github/workflows/scripts/crowdin/create-tasks.js b/.github/workflows/scripts/crowdin/create-tasks.js deleted file mode 100644 index d3085f8afa0..00000000000 --- a/.github/workflows/scripts/crowdin/create-tasks.js +++ /dev/null @@ -1,84 +0,0 @@ -const crowdin = require('@crowdin/crowdin-api-client'); -const TRANSLATED_CONNECTOR_DESCRIPTION = '{{tos_service_type: premium}}'; - -const API_TOKEN = process.env.CROWDIN_PERSONAL_TOKEN; -if (!API_TOKEN) { - console.error('Error: CROWDIN_PERSONAL_TOKEN environment variable is not set'); - process.exit(1); -} - -const PROJECT_ID = process.env.CROWDIN_PROJECT_ID; -if (!PROJECT_ID) { - console.error('Error: CROWDIN_PROJECT_ID environment variable is not set'); - process.exit(1); -} - -const { tasksApi, projectsGroupsApi, sourceFilesApi } = new crowdin.default({ - token: API_TOKEN, - organization: 'grafana' -}); - -const languages = await getLanguages(); -const fileIds = await getFileIds(); -console.log('Languages: ', languages); -console.log('File IDs: ', fileIds); - -// for (const language of languages) { -// const { name, id } = language; -// await createTask(`Translate to ${name}`, id, fileIds); -// } - -async function getLanguages() { - try { - const project = await projectsGroupsApi.getProject(PROJECT_ID); - const languages = project.data.targetLanguages; - return languages; - } catch (error) { - console.error('Failed to fetch languages: ', error.message); - if (error.response && error.response.data) { - console.error('Error details: ', JSON.stringify(error.response.data, null, 2)); - } - process.exit(1); - } -} - -async function getFileIds() { - try { - const response = await sourceFilesApi.listProjectFiles(PROJECT_ID); - const files = response.data; - const fileIds = files.map(file => file.data.id); - return fileIds; - } catch (error) { - console.error('Failed to fetch file IDs: ', error.message); - if (error.response && error.response.data) { - console.error('Error details: ', JSON.stringify(error.response.data, null, 2)); - } - process.exit(1); - } -} - -async function createTask(title, languageId, fileIds) { - try { - const taskParams = { - title, - description: TRANSLATED_CONNECTOR_DESCRIPTION, - languageId, - type: 2, // Translation by vendor - workflowStepId: 78, // Translation step ID - skipAssignedStrings: true, - fileIds, - }; - - console.log(`Creating Crowdin task: "${title}" for language ${languageId}`); - - const response = await tasksApi.addTask(PROJECT_ID, taskParams); - console.log(`Task created successfully! Task ID: ${response.data.id}`); - return response.data; - } catch (error) { - console.error('Failed to create Crowdin task: ', error.message); - if (error.response && error.response.data) { - console.error('Error details: ', JSON.stringify(error.response.data, null, 2)); - } - process.exit(1); - } -} diff --git a/.github/workflows/scripts/crowdin/create-tasks.ts b/.github/workflows/scripts/crowdin/create-tasks.ts new file mode 100644 index 00000000000..b4f76e699bd --- /dev/null +++ b/.github/workflows/scripts/crowdin/create-tasks.ts @@ -0,0 +1,110 @@ +import crowdinImport from '@crowdin/crowdin-api-client'; +const TRANSLATED_CONNECTOR_DESCRIPTION = '{{tos_service_type: premium}}'; +const TRANSLATE_BY_VENDOR_WORKFLOW_TYPE = 'TranslateByVendor' + +// TODO Remove this type assertion when https://github.com/crowdin/crowdin-api-client-js/issues/508 is fixed +// @ts-expect-error +const crowdin = crowdinImport.default as typeof crowdinImport; + +const API_TOKEN = process.env.CROWDIN_PERSONAL_TOKEN; +if (!API_TOKEN) { + console.error('Error: CROWDIN_PERSONAL_TOKEN environment variable is not set'); + process.exit(1); +} + +const PROJECT_ID = process.env.CROWDIN_PROJECT_ID ? parseInt(process.env.CROWDIN_PROJECT_ID, 10) : undefined; +if (!PROJECT_ID) { + console.error('Error: CROWDIN_PROJECT_ID environment variable is not set'); + process.exit(1); +} + +const credentials = { + token: API_TOKEN, + organization: 'grafana' +}; + +const { tasksApi, projectsGroupsApi, sourceFilesApi, workflowsApi } = new crowdin(credentials); + +const languages = await getLanguages(PROJECT_ID); +const fileIds = await getFileIds(PROJECT_ID); +const workflowStepId = await getWorkflowStepId(PROJECT_ID); + +for (const language of languages) { + const { name, id } = language; + await createTask(PROJECT_ID, `Translate to ${name}`, id, fileIds, workflowStepId); +} + +async function getLanguages(projectId) { + try { + const project = await projectsGroupsApi.getProject(projectId); + const languages = project.data.targetLanguages; + console.log('Fetched languages successfully!'); + return languages; + } catch (error) { + console.error('Failed to fetch languages: ', error.message); + if (error.response && error.response.data) { + console.error('Error details: ', JSON.stringify(error.response.data, null, 2)); + } + process.exit(1); + } +} + +async function getFileIds(projectId) { + try { + const response = await sourceFilesApi.listProjectFiles(projectId); + const files = response.data; + const fileIds = files.map(file => file.data.id); + console.log('Fetched file ids successfully!'); + return fileIds; + } catch (error) { + console.error('Failed to fetch file IDs: ', error.message); + if (error.response && error.response.data) { + console.error('Error details: ', JSON.stringify(error.response.data, null, 2)); + } + process.exit(1); + } +} + +async function getWorkflowStepId(projectId) { + try { + const response = await workflowsApi.listWorkflowSteps(projectId); + const workflowSteps = response.data; + const workflowStepId = workflowSteps.find(step => step.data.type === TRANSLATE_BY_VENDOR_WORKFLOW_TYPE)?.data.id; + if (!workflowStepId) { + throw new Error(`Workflow step with type "${TRANSLATE_BY_VENDOR_WORKFLOW_TYPE}" not found`); + } + console.log('Fetched workflow step ID successfully!'); + return workflowStepId; + } catch (error) { + console.error('Failed to fetch workflow step ID: ', error.message); + if (error.response && error.response.data) { + console.error('Error details: ', JSON.stringify(error.response.data, null, 2)); + } + process.exit(1); + } +} + +async function createTask(projectId, title, languageId, fileIds, workflowStepId) { + try { + const taskParams = { + title, + description: TRANSLATED_CONNECTOR_DESCRIPTION, + languageId, + workflowStepId, + skipAssignedStrings: true, + fileIds, + }; + + console.log(`Creating Crowdin task: "${title}" for language ${languageId}`); + + const response = await tasksApi.addTask(projectId, taskParams); + console.log(`Task created successfully! Task ID: ${response.data.id}`); + return response.data; + } catch (error) { + console.error('Failed to create Crowdin task: ', error.message); + if (error.response && error.response.data) { + console.error('Error details: ', JSON.stringify(error.response.data, null, 2)); + } + process.exit(1); + } +} From 6244b4d501c119ede1aa62497ed767da4a13be06 Mon Sep 17 00:00:00 2001 From: Stephanie Hingtgen Date: Thu, 1 May 2025 08:27:13 -0600 Subject: [PATCH 075/849] Dashboards: Fix cleanup job (#104822) --- pkg/services/dashboards/dashboard.go | 2 +- pkg/services/dashboards/dashboard_service_mock.go | 10 +++++----- pkg/services/dashboards/service/dashboard_service.go | 6 +++--- .../dashboards/service/dashboard_service_test.go | 4 +++- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/pkg/services/dashboards/dashboard.go b/pkg/services/dashboards/dashboard.go index 5959d07b4ed..c0cc9cf2d23 100644 --- a/pkg/services/dashboards/dashboard.go +++ b/pkg/services/dashboards/dashboard.go @@ -35,7 +35,7 @@ type DashboardService interface { CountInFolders(ctx context.Context, orgID int64, folderUIDs []string, user identity.Requester) (int64, error) GetAllDashboards(ctx context.Context) ([]*Dashboard, error) GetAllDashboardsByOrgId(ctx context.Context, orgID int64) ([]*Dashboard, error) - CleanUpDashboard(ctx context.Context, dashboardUID string, orgId int64) error + CleanUpDashboard(ctx context.Context, dashboardUID string, dashboardId int64, orgId int64) error CountDashboardsInOrg(ctx context.Context, orgID int64) (int64, error) SetDefaultPermissions(ctx context.Context, dto *SaveDashboardDTO, dash *Dashboard, provisioned bool) UnstructuredToLegacyDashboard(ctx context.Context, item *unstructured.Unstructured, orgID int64) (*Dashboard, error) diff --git a/pkg/services/dashboards/dashboard_service_mock.go b/pkg/services/dashboards/dashboard_service_mock.go index d24985eecf8..4a044d20431 100644 --- a/pkg/services/dashboards/dashboard_service_mock.go +++ b/pkg/services/dashboards/dashboard_service_mock.go @@ -48,17 +48,17 @@ func (_m *FakeDashboardService) BuildSaveDashboardCommand(ctx context.Context, d return r0, r1 } -// CleanUpDashboard provides a mock function with given fields: ctx, dashboardUID, orgId -func (_m *FakeDashboardService) CleanUpDashboard(ctx context.Context, dashboardUID string, orgId int64) error { - ret := _m.Called(ctx, dashboardUID, orgId) +// CleanUpDashboard provides a mock function with given fields: ctx, dashboardUID, dashboardId, orgId +func (_m *FakeDashboardService) CleanUpDashboard(ctx context.Context, dashboardUID string, dashboardId int64, orgId int64) error { + ret := _m.Called(ctx, dashboardUID, dashboardId, orgId) if len(ret) == 0 { panic("no return value specified for CleanUpDashboard") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, int64) error); ok { - r0 = rf(ctx, dashboardUID, orgId) + if rf, ok := ret.Get(0).(func(context.Context, string, int64, int64) error); ok { + r0 = rf(ctx, dashboardUID, dashboardId, orgId) } else { r0 = ret.Error(0) } diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index 9256c615779..0d1c9cebcb0 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -353,7 +353,7 @@ func (dr *DashboardServiceImpl) processDashboardBatch(ctx context.Context, orgID "deletionTimestamp", deletionTimestamp, "resourceVersion", resourceVersion) - if err = dr.CleanUpDashboard(ctx, dash.UID, orgID); err != nil { + if err = dr.CleanUpDashboard(ctx, dash.UID, dash.ID, orgID); err != nil { errs = append(errs, fmt.Errorf("failed to clean up dashboard %s: %w", dash.UID, err)) } itemsProcessed++ @@ -1790,7 +1790,7 @@ func (dr *DashboardServiceImpl) DeleteInFolders(ctx context.Context, orgID int64 func (dr *DashboardServiceImpl) Kind() string { return entity.StandardKindDashboard } -func (dr *DashboardServiceImpl) CleanUpDashboard(ctx context.Context, dashboardUID string, orgId int64) error { +func (dr *DashboardServiceImpl) CleanUpDashboard(ctx context.Context, dashboardUID string, dashboardID int64, orgId int64) error { ctx, span := tracer.Start(ctx, "dashboards.service.CleanUpDashboard") defer span.End() @@ -1800,7 +1800,7 @@ func (dr *DashboardServiceImpl) CleanUpDashboard(ctx context.Context, dashboardU return err } - return dr.dashboardStore.CleanupAfterDelete(ctx, &dashboards.DeleteDashboardCommand{OrgID: orgId, UID: dashboardUID}) + return dr.dashboardStore.CleanupAfterDelete(ctx, &dashboards.DeleteDashboardCommand{OrgID: orgId, UID: dashboardUID, ID: dashboardID}) } // ----------------------------------------------------------------------------------------- diff --git a/pkg/services/dashboards/service/dashboard_service_test.go b/pkg/services/dashboards/service/dashboard_service_test.go index ba0407dc932..ce9d97e650e 100644 --- a/pkg/services/dashboards/service/dashboard_service_test.go +++ b/pkg/services/dashboards/service/dashboard_service_test.go @@ -2578,6 +2578,7 @@ func TestCleanUpDashboard(t *testing.T) { ctx := context.Background() dashboardUID := "dash-uid" + dashboardID := int64(1) orgID := int64(1) // Setup mocks @@ -2587,11 +2588,12 @@ func TestCleanUpDashboard(t *testing.T) { fakeStore.On("CleanupAfterDelete", mock.Anything, &dashboards.DeleteDashboardCommand{ OrgID: orgID, UID: dashboardUID, + ID: dashboardID, }).Return(tc.cleanupError).Maybe() } // Execute - err := service.CleanUpDashboard(ctx, dashboardUID, orgID) + err := service.CleanUpDashboard(ctx, dashboardUID, dashboardID, orgID) // Assert if tc.expectedError != nil { From fd4afdbd2cd2d0cc466d42d101003e870b4ff93f Mon Sep 17 00:00:00 2001 From: Kevin Minehart <5140827+kminehart@users.noreply.github.com> Date: Thu, 1 May 2025 09:32:35 -0500 Subject: [PATCH 076/849] CI: Use docker creds from ci/common (#104827) Use docker creds from ci/common --- .drone.yml | 6 +++--- scripts/drone/vault.star | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.drone.yml b/.drone.yml index d27ffd886ad..97ad9bc747b 100644 --- a/.drone.yml +++ b/.drone.yml @@ -5061,13 +5061,13 @@ name: prerelease_bucket --- get: name: username - path: infra/data/ci/grafanaci-docker-hub + path: ci/data/common/dockerhub kind: secret name: docker_username --- get: name: password - path: infra/data/ci/grafanaci-docker-hub + path: ci/data/common/dockerhub kind: secret name: docker_password --- @@ -5210,6 +5210,6 @@ kind: secret name: gcr_credentials --- kind: signature -hmac: 16029e3922ae0a13a31233717aa172c06bf0e6fc8cf01f5148de62147c259ac8 +hmac: 38513409bb4d2834e2140e41a9873c81052683ad8d9c86f3136060c44d099b6f ... diff --git a/scripts/drone/vault.star b/scripts/drone/vault.star index aa77f81be39..fe1e049a969 100644 --- a/scripts/drone/vault.star +++ b/scripts/drone/vault.star @@ -55,8 +55,8 @@ def secrets(): vault_secret(gar_pull_secret, "secret/data/common/gar", ".dockerconfigjson"), vault_secret(drone_token, "infra/data/ci/drone", "machine-user-token"), vault_secret(prerelease_bucket, "infra/data/ci/grafana/prerelease", "bucket"), - vault_secret(docker_username, "infra/data/ci/grafanaci-docker-hub", "username"), - vault_secret(docker_password, "infra/data/ci/grafanaci-docker-hub", "password"), + vault_secret(docker_username, "ci/data/common/dockerhub", "username"), + vault_secret(docker_password, "ci/data/common/dockerhub", "password"), vault_secret( gcp_upload_artifacts_key, "infra/data/ci/grafana/releng/artifacts-uploader-service-account", From ca2ae82e80bb8830f395441b028d305a316985de Mon Sep 17 00:00:00 2001 From: Scott Lepper Date: Thu, 1 May 2025 11:00:04 -0400 Subject: [PATCH 077/849] allow setting multiple feature toggles in run-suite (#104821) * allow setting multiple feature toggles in run-suite --- e2e/cypress/support/e2e.js | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/e2e/cypress/support/e2e.js b/e2e/cypress/support/e2e.js index 284ac975ef0..29385190b9c 100644 --- a/e2e/cypress/support/e2e.js +++ b/e2e/cypress/support/e2e.js @@ -45,14 +45,27 @@ Cypress.on('uncaught:exception', (err) => { // }); // +// TODO: read from toggles_gen.csv? +const featureToggles = ['kubernetesDashboards', 'dashboardNewLayouts']; + beforeEach(() => { + let toggles = []; + if (Cypress.env('DISABLE_SCENES')) { cy.logToConsole('disabling dashboardScene feature toggle in localstorage'); - cy.setLocalStorage('grafana.featureToggles', 'dashboardScene=false'); + toggles.push('dashboardScene=false'); } - if (Cypress.env('kubernetesDashboards')) { - cy.logToConsole('enabling kubernetes dashboards API in localstorage'); - cy.setLocalStorage('grafana.featureToggles', 'kubernetesDashboards=true'); + for (const toggle of featureToggles) { + const toggleValue = Cypress.env(toggle); + if (toggleValue !== undefined) { + cy.logToConsole(`setting ${toggle} to ${toggleValue} in localstorage`); + toggles.push(`${toggle}=${toggleValue}`); + } + } + + if (toggles.length > 0) { + cy.logToConsole('setting feature toggles in localstorage'); + cy.setLocalStorage('grafana.featureToggles', toggles.join(',')); } }); From 1114d33936b89936c8acba1b1eff87773f5ac9c9 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Thu, 1 May 2025 17:06:07 +0200 Subject: [PATCH 078/849] Dashboard export: Allow exports by resource and in YAML format (#104149) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Dashboard export: Allow YAML export * use js-yaml to fix ci errors * add yaml transformation in the "Copy to cilpboard" * simplify * new ui for resource exports * simplify * Don't show export mode for v2 dashboard * Add metadata, apiVersion, add logic for export type resources * i18n; switch title to as code * update export as file button * Remove managedFields from metadata export * Remove metadata fields that are not needed for sharing externally * Copy * fix legacy mode * address bugs * Update public/app/features/dashboard-scene/sharing/ExportButton/ExportMenu.tsx Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com> * i18n * improve classic mode; rename * Update public/app/features/dashboard-scene/sharing/ShareExportTab.tsx Co-authored-by: Dominik Prokop * change order --------- Co-authored-by: Haris Rozajac Co-authored-by: Haris Rozajac <58232930+harisrozajac@users.noreply.github.com> Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com> --- .../dashboard-scene/scene/DashboardScene.tsx | 12 +- .../actions/ExportDashboardButton.tsx | 46 +-- .../serialization/DashboardSceneSerializer.ts | 1 + .../transformSaveModelSchemaV2ToScene.ts | 4 +- .../transformSaveModelToScene.ts | 8 +- .../{ExportAsJson.tsx => ExportAsCode.tsx} | 86 +++--- .../sharing/ExportButton/ExportMenu.tsx | 8 +- .../sharing/ExportButton/ResourceExport.tsx | 113 ++++++++ .../sharing/ShareDrawer/ShareDrawer.tsx | 4 +- .../sharing/ShareExportTab.tsx | 264 ++++++++++++++---- public/app/features/dashboard/api/v1.ts | 12 +- public/app/features/dashboard/api/v2.ts | 12 +- public/locales/en-US/grafana.json | 15 +- 13 files changed, 441 insertions(+), 144 deletions(-) rename public/app/features/dashboard-scene/sharing/ExportButton/{ExportAsJson.tsx => ExportAsCode.tsx} (63%) create mode 100644 public/app/features/dashboard-scene/sharing/ExportButton/ResourceExport.tsx diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.tsx index 29433b0bb56..d4ac35e827b 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.tsx @@ -725,17 +725,23 @@ export class DashboardScene extends SceneObjectBase impleme }; /** Hacky temp function until we refactor transformSaveModelToScene a bit */ - setInitialSaveModel(model?: Dashboard, meta?: DashboardMeta): void; - setInitialSaveModel(model?: DashboardV2Spec, meta?: DashboardWithAccessInfo['metadata']): void; + setInitialSaveModel(model?: Dashboard, meta?: DashboardMeta, apiVersion?: string): void; + setInitialSaveModel( + model?: DashboardV2Spec, + meta?: DashboardWithAccessInfo['metadata'], + apiVersion?: string + ): void; public setInitialSaveModel( saveModel?: Dashboard | DashboardV2Spec, - meta?: DashboardMeta | DashboardWithAccessInfo['metadata'] + meta?: DashboardMeta | DashboardWithAccessInfo['metadata'], + apiVersion?: string ): void { this.serializer.initializeElementMapping(saveModel); this.serializer.initializeDSReferencesMapping(saveModel); const sortedModel = sortedDeepCloneWithoutNulls(saveModel); this.serializer.initialSaveModel = sortedModel; this.serializer.metadata = meta; + this.serializer.apiVersion = apiVersion; } public getTrackingInformation() { diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/actions/ExportDashboardButton.tsx b/public/app/features/dashboard-scene/scene/new-toolbar/actions/ExportDashboardButton.tsx index 4d361dfcdfb..f95b79ecf2b 100644 --- a/public/app/features/dashboard-scene/scene/new-toolbar/actions/ExportDashboardButton.tsx +++ b/public/app/features/dashboard-scene/scene/new-toolbar/actions/ExportDashboardButton.tsx @@ -1,5 +1,5 @@ import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; -import { locationService } from '@grafana/runtime'; +import { config, locationService } from '@grafana/runtime'; import { t } from 'app/core/internationalization'; import { getTrackingSource, shareDashboardType } from 'app/features/dashboard/components/ShareModal/utils'; @@ -11,23 +11,29 @@ import { ShareExportDashboardButton } from './ShareExportDashboardButton'; const newExportButtonSelector = e2eSelectors.pages.Dashboard.DashNav.NewExportButton; -export const ExportDashboardButton = ({ dashboard }: ToolbarActionProps) => ( - } - groupTestId={newExportButtonSelector.container} - buttonLabel={t('dashboard.toolbar.new.export.title', 'Export')} - buttonTooltip={t('dashboard.toolbar.new.export.tooltip', 'Export as JSON')} - buttonTestId={newExportButtonSelector.container} - onButtonClick={() => { - locationService.partial({ shareView: shareDashboardType.export }); +export const ExportDashboardButton = ({ dashboard }: ToolbarActionProps) => { + const buttonTooltip = config.featureToggles.kubernetesDashboards + ? t('dashboard.toolbar.new.export.tooltip.as-code', 'Export as code') + : t('dashboard.toolbar.new.export.tooltip.json', 'Export as JSON'); - DashboardInteractions.sharingCategoryClicked({ - item: shareDashboardType.export, - shareResource: getTrackingSource(), - }); - }} - arrowLabel={t('dashboard.toolbar.new.export.arrow', 'Export')} - arrowTestId={newExportButtonSelector.arrowMenu} - dashboard={dashboard} - /> -); + return ( + } + groupTestId={newExportButtonSelector.container} + buttonLabel={t('dashboard.toolbar.new.export.title', 'Export')} + buttonTooltip={buttonTooltip} + buttonTestId={newExportButtonSelector.container} + onButtonClick={() => { + locationService.partial({ shareView: shareDashboardType.export }); + + DashboardInteractions.sharingCategoryClicked({ + item: shareDashboardType.export, + shareResource: getTrackingSource(), + }); + }} + arrowLabel={t('dashboard.toolbar.new.export.arrow', 'Export')} + arrowTestId={newExportButtonSelector.arrowMenu} + dashboard={dashboard} + /> + ); +}; diff --git a/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.ts b/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.ts index 373b72358c8..fc1083b9a0d 100644 --- a/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.ts +++ b/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.ts @@ -35,6 +35,7 @@ export interface DashboardSceneSerializerLike T; diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts index d89c53dbd47..0fa343a5c4e 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts @@ -87,7 +87,7 @@ export type TypedVariableModelV2 = | AdhocVariableKind; export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo): DashboardScene { - const { spec: dashboard, metadata } = dto; + const { spec: dashboard, metadata, apiVersion } = dto; // annotations might not come with the builtIn Grafana annotation, we need to add it @@ -221,7 +221,7 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo) { +function ExportAsCodeRenderer({ model }: SceneComponentProps) { const styles = useStyles2(getStyles); - const { isSharingExternally } = model.useState(); + const { isSharingExternally, isViewingYAML, exportMode } = model.useState(); const dashboardJson = useAsync(async () => { const json = await model.getExportableDashboardJson(); + return json; - }, [isSharingExternally]); + }, [isSharingExternally, exportMode]); const stringifiedDashboardJson = JSON.stringify(dashboardJson.value?.json, null, 2); - const hasLibraryPanels = dashboardJson.value?.hasLibraryPanels; - const isV2Dashboard = dashboardJson.value?.json && 'elements' in dashboardJson.value.json; - const showV2LibPanelAlert = isV2Dashboard && isSharingExternally && hasLibraryPanels; + const stringifiedDashboardYAML = yaml.dump(dashboardJson.value?.json, { + skipInvalid: true, + }); + const stringifiedDashboard = isViewingYAML ? stringifiedDashboardYAML : stringifiedDashboardJson; const onClickDownload = async () => { await model.onSaveAsFile(); @@ -61,50 +56,41 @@ function ExportAsJsonRenderer({ model }: SceneComponentProps) {

- Copy or download a JSON file containing the JSON of your dashboard + Copy or download a file containing the definition of your dashboard

- - + + {config.featureToggles.kubernetesDashboards ? ( + + ) : ( + + )} - {showV2LibPanelAlert && ( - - - The dynamic dashboard functionality is experimental, and has not full feature parity with current - dashboards behaviour. It is based on a new schema format, that does not support library panels. This means - that when exporting the dashboard to use it in another instance, we will not include library panels. We - intend to support them as we progress in the feature{' '} - - life cycle - - . - - - )} -
{({ width, height }) => { - if (stringifiedDashboardJson) { + if (stringifiedDashboard) { return ( ) { variant="secondary" icon="copy" disabled={dashboardJson.loading} - getText={() => stringifiedDashboardJson ?? ''} + getText={() => stringifiedDashboard ?? ''} onClipboardCopy={() => { DashboardInteractions.exportCopyJsonClicked(); }} diff --git a/public/app/features/dashboard-scene/sharing/ExportButton/ExportMenu.tsx b/public/app/features/dashboard-scene/sharing/ExportButton/ExportMenu.tsx index 02e99fe5ff9..e96704adbba 100644 --- a/public/app/features/dashboard-scene/sharing/ExportButton/ExportMenu.tsx +++ b/public/app/features/dashboard-scene/sharing/ExportButton/ExportMenu.tsx @@ -1,7 +1,7 @@ import { useCallback } from 'react'; import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; -import { locationService } from '@grafana/runtime'; +import { config, locationService } from '@grafana/runtime'; import { IconName, Menu } from '@grafana/ui'; import { t } from 'app/core/internationalization'; import { getTrackingSource, shareDashboardType } from 'app/features/dashboard/components/ShareModal/utils'; @@ -37,11 +37,15 @@ export default function ExportMenu({ dashboard }: { dashboard: DashboardScene }) customShareDrawerItem.forEach((d) => menuItems.push(d)); + const label = config.featureToggles.kubernetesDashboards + ? t('dashboard.toolbar.new.export.tooltip.as-code', 'Export as code') + : t('share-dashboard.menu.export-json-title', 'Export as JSON'); + menuItems.push({ shareId: shareDashboardType.export, testId: newExportButtonSelector.exportAsJson, icon: 'arrow', - label: t('share-dashboard.menu.export-json-title', 'Export as JSON'), + label, renderCondition: true, onClick: () => onMenuItemClick(shareDashboardType.export), }); diff --git a/public/app/features/dashboard-scene/sharing/ExportButton/ResourceExport.tsx b/public/app/features/dashboard-scene/sharing/ExportButton/ResourceExport.tsx new file mode 100644 index 00000000000..9c91aaaa577 --- /dev/null +++ b/public/app/features/dashboard-scene/sharing/ExportButton/ResourceExport.tsx @@ -0,0 +1,113 @@ +import { AsyncState } from 'react-use/lib/useAsync'; + +import { Dashboard } from '@grafana/schema/dist/esm/index.gen'; +import { Spec as DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; +import { Alert, Label, RadioButtonGroup, Stack, Switch, TextLink } from '@grafana/ui'; +import { t, Trans } from 'app/core/internationalization'; +import { DashboardJson } from 'app/features/manage-dashboards/types'; + +import { ExportableResource } from '../ShareExportTab'; + +export enum ExportMode { + Classic = 'classic', + V1Resource = 'v1-resource', + V2Resource = 'v2-resource', +} + +interface Props { + dashboardJson: AsyncState<{ + json: Dashboard | DashboardJson | DashboardV2Spec | ExportableResource | { error: unknown }; + hasLibraryPanels?: boolean; + initialSaveModelVersion: 'v1' | 'v2'; + }>; + isSharingExternally: boolean; + exportMode: ExportMode; + isViewingYAML: boolean; + onExportModeChange: (mode: ExportMode) => void; + onShareExternallyChange: () => void; + onViewYAML: () => void; +} + +export function ResourceExport({ + dashboardJson, + isSharingExternally, + exportMode, + isViewingYAML, + onExportModeChange, + onShareExternallyChange, + onViewYAML, +}: Props) { + const hasLibraryPanels = dashboardJson.value?.hasLibraryPanels; + const initialSaveModelVersion = dashboardJson.value?.initialSaveModelVersion; + const isV2Dashboard = + dashboardJson.value?.json && 'spec' in dashboardJson.value.json && 'elements' in dashboardJson.value.json.spec; + const showV2LibPanelAlert = isV2Dashboard && isSharingExternally && hasLibraryPanels; + + const switchExportLabel = + exportMode === ExportMode.V2Resource + ? t('export.json.export-remove-ds-refs', 'Remove deployment details') + : t('share-modal.export.share-externally-label', `Export for sharing externally`); + const switchExportModeLabel = t('export.json.export-mode', 'Model'); + const switchExportFormatLabel = t('export.json.export-format', 'Format'); + + return ( + + + {initialSaveModelVersion === 'v1' && ( + + + onExportModeChange(value)} + /> + + )} + {exportMode !== ExportMode.Classic && ( + + + + + )} + {(isV2Dashboard || exportMode === ExportMode.Classic) && ( + + + + + )} + + + {showV2LibPanelAlert && ( + + + The dynamic dashboard functionality is experimental, and has not full feature parity with current dashboards + behaviour. It is based on a new schema format, that does not support library panels. This means that when + exporting the dashboard to use it in another instance, we will not include library panels. We intend to + support them as we progress in the feature{' '} + + life cycle + + . + + + )} + + ); +} diff --git a/public/app/features/dashboard-scene/sharing/ShareDrawer/ShareDrawer.tsx b/public/app/features/dashboard-scene/sharing/ShareDrawer/ShareDrawer.tsx index 317ec324e49..bbb48cc6524 100644 --- a/public/app/features/dashboard-scene/sharing/ShareDrawer/ShareDrawer.tsx +++ b/public/app/features/dashboard-scene/sharing/ShareDrawer/ShareDrawer.tsx @@ -5,7 +5,7 @@ import { Drawer } from '@grafana/ui'; import { shareDashboardType } from '../../../dashboard/components/ShareModal/utils'; import { DashboardScene } from '../../scene/DashboardScene'; import { getDashboardSceneFor } from '../../utils/utils'; -import { ExportAsJson } from '../ExportButton/ExportAsJson'; +import { ExportAsCode } from '../ExportButton/ExportAsCode'; import { ShareExternally } from '../ShareButton/share-externally/ShareExternally'; import { ShareInternally } from '../ShareButton/share-internally/ShareInternally'; import { ShareSnapshot } from '../ShareButton/share-snapshot/ShareSnapshot'; @@ -90,7 +90,7 @@ function getShareView( case shareDashboardType.snapshot: return new ShareSnapshot({ dashboardRef, panelRef, onDismiss }); case shareDashboardType.export: - return new ExportAsJson({ onDismiss }); + return new ExportAsCode({ onDismiss }); default: return new ShareInternally({ onDismiss }); } diff --git a/public/app/features/dashboard-scene/sharing/ShareExportTab.tsx b/public/app/features/dashboard-scene/sharing/ShareExportTab.tsx index ab81fb4229c..177940061eb 100644 --- a/public/app/features/dashboard-scene/sharing/ShareExportTab.tsx +++ b/public/app/features/dashboard-scene/sharing/ShareExportTab.tsx @@ -1,24 +1,48 @@ import saveAs from 'file-saver'; +import yaml from 'js-yaml'; +import { cloneDeep } from 'lodash'; import { useAsync } from 'react-use'; import AutoSizer from 'react-virtualized-auto-sizer'; +import { config } from '@grafana/runtime'; import { SceneComponentProps, SceneObjectBase } from '@grafana/scenes'; import { Dashboard } from '@grafana/schema/dist/esm/index.gen'; import { Spec as DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; -import { Alert, Button, ClipboardButton, CodeEditor, Field, Modal, Stack, Switch, TextLink } from '@grafana/ui'; +import { Button, ClipboardButton, CodeEditor, Field, Modal, Stack, Switch } from '@grafana/ui'; import { t, Trans } from 'app/core/internationalization'; +import { ObjectMeta } from 'app/features/apiserver/types'; +import { DashboardWithAccessInfo } from 'app/features/dashboard/api/types'; import { isDashboardV2Spec } from 'app/features/dashboard/api/utils'; +import { K8S_V2_DASHBOARD_API_CONFIG } from 'app/features/dashboard/api/v2'; import { shareDashboardType } from 'app/features/dashboard/components/ShareModal/utils'; +import { DashboardModel } from 'app/features/dashboard/state/DashboardModel'; import { DashboardJson } from 'app/features/manage-dashboards/types'; +import { DashboardScene } from '../scene/DashboardScene'; +import { makeExportableV1, makeExportableV2 } from '../scene/export/exporters'; +import { transformSceneToSaveModel } from '../serialization/transformSceneToSaveModel'; +import { transformSceneToSaveModelSchemaV2 } from '../serialization/transformSceneToSaveModelSchemaV2'; +import { getVariablesCompatibility } from '../utils/getVariablesCompatibility'; import { DashboardInteractions } from '../utils/interactions'; import { getDashboardSceneFor } from '../utils/utils'; +import { ExportMode, ResourceExport } from './ExportButton/ResourceExport'; import { SceneShareTabState, ShareView } from './types'; +export interface ExportableResource { + apiVersion: string; + kind: 'Dashboard'; + metadata: DashboardWithAccessInfo['metadata'] | Partial; + spec: Dashboard | DashboardModel | DashboardV2Spec | { error: unknown }; + // A placeholder for now because as code tooling expects it + status: {}; +} + export interface ShareExportTabState extends SceneShareTabState { isSharingExternally?: boolean; isViewingJSON?: boolean; + isViewingYAML?: boolean; + exportMode?: ExportMode; } export class ShareExportTab extends SceneObjectBase implements ShareView { @@ -27,9 +51,10 @@ export class ShareExportTab extends SceneObjectBase impleme constructor(state: Omit) { super({ + ...state, isSharingExternally: false, isViewingJSON: false, - ...state, + exportMode: config.featureToggles.kubernetesDashboards ? ExportMode.Classic : undefined, }); } @@ -43,46 +68,140 @@ export class ShareExportTab extends SceneObjectBase impleme }); }; + public onExportModeChange = (exportMode: ExportMode) => { + this.setState({ + exportMode, + }); + + if (exportMode === ExportMode.Classic) { + this.setState({ + isViewingYAML: false, + }); + } + }; + public onViewJSON = () => { this.setState({ isViewingJSON: !this.state.isViewingJSON, }); }; + public onViewYAML = () => { + this.setState({ + isViewingYAML: !this.state.isViewingYAML, + }); + }; + public getClipboardText() { return; } public getExportableDashboardJson = async (): Promise<{ - json: Dashboard | DashboardJson | DashboardV2Spec | { error: unknown }; + json: Dashboard | DashboardJson | DashboardV2Spec | ExportableResource | { error: unknown }; hasLibraryPanels?: boolean; + initialSaveModelVersion: 'v1' | 'v2'; }> => { - const { isSharingExternally } = this.state; + const { isSharingExternally, exportMode } = this.state; const scene = getDashboardSceneFor(this); const exportableDashboard = await scene.serializer.makeExportableExternally(scene); + const initialSaveModel = scene.getInitialSaveModel(); + const initialSaveModelVersion = initialSaveModel && isDashboardV2Spec(initialSaveModel) ? 'v2' : 'v1'; const origDashboard = scene.serializer.getSaveModel(scene); const exportable = isSharingExternally ? exportableDashboard : origDashboard; + const metadata = getMetadata(scene, Boolean(isSharingExternally)); + + if (isDashboardV2Spec(origDashboard) && 'elements' in exportable && initialSaveModelVersion === 'v2') { + this.setState({ + exportMode: ExportMode.V2Resource, + }); - if (isDashboardV2Spec(origDashboard)) { return { - json: exportable, + json: { + apiVersion: scene.serializer.apiVersion ?? '', + kind: 'Dashboard', + metadata, + spec: exportable, + status: {}, + }, + initialSaveModelVersion, hasLibraryPanels: Object.values(origDashboard.elements).some((element) => element.kind === 'LibraryPanel'), }; } + if (exportMode === ExportMode.V1Resource) { + const spec = transformSceneToSaveModel(scene); + + return { + json: { + apiVersion: scene.serializer.apiVersion ?? '', + kind: 'Dashboard', + metadata, + spec, + status: {}, + }, + initialSaveModelVersion, + hasLibraryPanels: undefined, + }; + } + + if (exportMode === ExportMode.V2Resource) { + const spec = transformSceneToSaveModelSchemaV2(scene); + const specCopy = JSON.parse(JSON.stringify(spec)); + const statelessSpec = await makeExportableV2(specCopy); + const exportableV2 = isSharingExternally ? statelessSpec : spec; + + return { + json: { + // Forcing V2 version here because in this case we have v1 serializer + apiVersion: `${K8S_V2_DASHBOARD_API_CONFIG.group}/${K8S_V2_DASHBOARD_API_CONFIG.version}`, + kind: 'Dashboard', + metadata, + spec: exportableV2, + status: {}, + }, + initialSaveModelVersion, + }; + } + + // Classic mode + // This handles a case when: + // 1. dashboardNewLayouts feature toggle is enabled + // 2. v1 dashboard is loaded + // 3. dashboard hasn't been edited yet - if it was edited, user would be forced to save it in v2 version + if ( + initialSaveModelVersion === 'v1' && + isDashboardV2Spec(origDashboard) && + initialSaveModel && + 'panels' in initialSaveModel + ) { + const oldModel = new DashboardModel(initialSaveModel, undefined, { + getVariablesFromState: () => { + return getVariablesCompatibility(window.__grafanaSceneContext); + }, + }); + const exportableV1 = isSharingExternally ? await makeExportableV1(oldModel) : initialSaveModel; + return { + json: exportableV1, + hasLibraryPanels: undefined, + initialSaveModelVersion, + }; + } + + // legacy mode or classic mode when dashboardNewLayouts is disabled return { json: exportable, hasLibraryPanels: undefined, + initialSaveModelVersion, }; }; public onSaveAsFile = async () => { const dashboard = await this.getExportableDashboardJson(); const dashboardJsonPretty = JSON.stringify(dashboard.json, null, 2); - const { isSharingExternally } = this.state; + const { isSharingExternally, isViewingYAML } = this.state; - const blob = new Blob([dashboardJsonPretty], { + const blob = new Blob([isViewingYAML ? yaml.dump(dashboard.json) : dashboardJsonPretty], { type: 'application/json;charset=utf-8', }); @@ -91,26 +210,73 @@ export class ShareExportTab extends SceneObjectBase impleme if ('title' in dashboard.json && dashboard.json.title) { title = dashboard.json.title; } - saveAs(blob, `${title}-${time}.json`); + const extension = isViewingYAML ? 'yaml' : 'json'; + saveAs(blob, `${title}-${time}.${extension}`); DashboardInteractions.exportDownloadJsonClicked({ externally: isSharingExternally, }); }; } +function getMetadata( + scene: DashboardScene, + isSharingExternally: boolean +): DashboardWithAccessInfo['metadata'] | Partial { + let result: Partial = {}; + + if (scene.serializer.metadata) { + if ('k8s' in scene.serializer.metadata) { + result = scene.serializer.metadata.k8s ? cloneDeep(scene.serializer.metadata.k8s) : {}; + } else if ('annotations' in scene.serializer.metadata) { + result = cloneDeep(scene.serializer.metadata); + } + } + + if ('managedFields' in result) { + delete result['managedFields']; + } + + if (isSharingExternally) { + // Remove fields that are not needed for sharing externally + if ('uid' in result) { + delete result['uid']; + } + delete result['resourceVersion']; + delete result['namespace']; + + // iterate over labels and delete all keys that start with grafana.app/ + for (const key in result['labels']) { + if (key.startsWith('grafana.app/')) { + // @ts-expect-error + delete result['labels'][key]; + } + } + + // iterate over annotations and delete all keys that start with grafana.app/ + for (const key in result['annotations']) { + if (key.startsWith('grafana.app/')) { + // @ts-expect-error + delete result['annotations'][key]; + } + } + } + + return result; +} + function ShareExportTabRenderer({ model }: SceneComponentProps) { - const { isSharingExternally, isViewingJSON, modalRef } = model.useState(); + const { isSharingExternally, isViewingJSON, modalRef, exportMode, isViewingYAML } = model.useState(); const dashboardJson = useAsync(async () => { const json = await model.getExportableDashboardJson(); return json; - }, [isViewingJSON, isSharingExternally]); + }, [isViewingJSON, isSharingExternally, exportMode]); const stringifiedDashboardJson = JSON.stringify(dashboardJson.value?.json, null, 2); - const hasLibraryPanels = dashboardJson.value?.hasLibraryPanels; - - const isV2Dashboard = dashboardJson.value?.json && 'elements' in dashboardJson.value.json; - const showV2LibPanelAlert = isV2Dashboard && isSharingExternally && hasLibraryPanels; + const stringifiedDashboardYAML = yaml.dump(dashboardJson.value?.json, { + skipInvalid: true, + }); + const stringifiedDashboard = isViewingYAML ? stringifiedDashboardYAML : stringifiedDashboardJson; const exportExternallyTranslation = t('share-modal.export.share-externally-label', `Export for sharing externally`); @@ -121,35 +287,27 @@ function ShareExportTabRenderer({ model }: SceneComponentProps)

Export this dashboard.

- - - - - {showV2LibPanelAlert && ( - - - The dynamic dashboard functionality is experimental, and has not full feature parity with current - dashboards behaviour. It is based on a new schema format, that does not support library panels. This - means that when exporting the dashboard to use it in another instance, we will not include library - panels. We intend to support them as we progress in the feature{' '} - - life cycle - - . - - - )} - + {config.featureToggles.kubernetesDashboards ? ( + + ) : ( + + + + + + )} - + {isViewingYAML ? ( + + ) : ( + + )} @@ -177,9 +341,9 @@ function ShareExportTabRenderer({ model }: SceneComponentProps) if (dashboardJson.value) { return ( ) variant="secondary" icon="copy" disabled={dashboardJson.loading} - getText={() => stringifiedDashboardJson ?? ''} + getText={() => stringifiedDashboard ?? ''} > Copy to Clipboard diff --git a/public/app/features/dashboard/api/v1.ts b/public/app/features/dashboard/api/v1.ts index d02a1b06502..00b3a56a7a9 100644 --- a/public/app/features/dashboard/api/v1.ts +++ b/public/app/features/dashboard/api/v1.ts @@ -21,15 +21,17 @@ import { SaveDashboardCommand } from '../components/SaveDashboard/types'; import { DashboardAPI, DashboardVersionError, DashboardWithAccessInfo } from './types'; +export const K8S_V1_DASHBOARD_API_CONFIG = { + group: 'dashboard.grafana.app', + version: 'v1beta1', + resource: 'dashboards', +}; + export class K8sDashboardAPI implements DashboardAPI { private client: ResourceClient; constructor() { - this.client = new ScopedResourceClient({ - group: 'dashboard.grafana.app', - version: 'v1beta1', - resource: 'dashboards', - }); + this.client = new ScopedResourceClient(K8S_V1_DASHBOARD_API_CONFIG); } saveDashboard(options: SaveDashboardCommand): Promise { diff --git a/public/app/features/dashboard/api/v2.ts b/public/app/features/dashboard/api/v2.ts index 0f6d1486051..efaa4c5af5e 100644 --- a/public/app/features/dashboard/api/v2.ts +++ b/public/app/features/dashboard/api/v2.ts @@ -24,17 +24,19 @@ import { SaveDashboardCommand } from '../components/SaveDashboard/types'; import { DashboardAPI, DashboardVersionError, DashboardWithAccessInfo } from './types'; +export const K8S_V2_DASHBOARD_API_CONFIG = { + group: 'dashboard.grafana.app', + version: 'v2alpha1', + resource: 'dashboards', +}; + export class K8sDashboardV2API implements DashboardAPI | DashboardDTO, DashboardV2Spec> { private client: ResourceClient; constructor() { - this.client = new ScopedResourceClient({ - group: 'dashboard.grafana.app', - version: 'v2alpha1', - resource: 'dashboards', - }); + this.client = new ScopedResourceClient(K8S_V2_DASHBOARD_API_CONFIG); } async getDashboardDTO(uid: string) { diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 57ff9764163..ae3a7156489 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -3671,7 +3671,10 @@ "export": { "arrow": "Export", "title": "Export", - "tooltip": "Export as JSON" + "tooltip": { + "as-code": "Export as code", + "json": "Export as JSON" + } }, "mark-favorite": "Mark as favorite", "more-save-options": "More save options", @@ -5109,8 +5112,11 @@ "download-button": "Download file", "download-successful_toast_message": "Your JSON has been downloaded", "export-externally-label": "Export the dashboard to use in another instance", - "info-text": "Copy or download a JSON file containing the JSON of your dashboard", - "title": "Export dashboard JSON" + "export-format": "Format", + "export-mode": "Model", + "export-remove-ds-refs": "Remove deployment details", + "info-text": "Copy or download a file containing the definition of your dashboard", + "title": "Export dashboard" }, "menu": { "export-as-json-label": "Export", @@ -8219,7 +8225,8 @@ "loading": "Loading...", "save-button": "Save to file", "share-externally-label": "Export for sharing externally", - "view-button": "View JSON" + "view-button": "View JSON", + "view-button-yaml": "View YAML" }, "library": { "info": "Create library panel." From d30b39f350b3df5a04e98b86618fc8d1f5ff367c Mon Sep 17 00:00:00 2001 From: Scott Lepper Date: Thu, 1 May 2025 15:14:36 -0400 Subject: [PATCH 079/849] Dashboard edit panel e2e (#104775) setup suite for edit panel; start adding test for custom variable edit --- .../dashboards-edit-variables.spec.ts | 60 +++++++++++++++++++ e2e/run-suite | 20 +++++++ package.json | 2 + .../src/selectors/components.ts | 27 +++++++++ .../edit-pane/DashboardEditPane.tsx | 4 +- .../edit-pane/DashboardOutline.tsx | 9 ++- .../new-toolbar/actions/ToolbarSwitch.tsx | 4 +- .../variables/VariableEditableElement.tsx | 18 +++++- .../variables/VariableSetEditableElement.tsx | 11 +++- 9 files changed, 148 insertions(+), 7 deletions(-) create mode 100644 e2e/dashboards-edit-v2-suite/dashboards-edit-variables.spec.ts diff --git a/e2e/dashboards-edit-v2-suite/dashboards-edit-variables.spec.ts b/e2e/dashboards-edit-v2-suite/dashboards-edit-variables.spec.ts new file mode 100644 index 00000000000..e6f15f8e2e5 --- /dev/null +++ b/e2e/dashboards-edit-v2-suite/dashboards-edit-variables.spec.ts @@ -0,0 +1,60 @@ +import { e2e } from '../utils'; + +const PAGE_UNDER_TEST = 'kVi2Gex7z/test-variable-output'; +const DASHBOARD_NAME = 'Test variable output'; + +describe('Dashboard edit variables', () => { + beforeEach(() => { + e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); + }); + + it('can add a new custom variable', () => { + e2e.pages.Dashboards.visit(); + + e2e.flows.openDashboard({ uid: `${PAGE_UNDER_TEST}?orgId=1` }); + cy.contains(DASHBOARD_NAME).should('be.visible'); + + const variable: Variable = { + type: 'custom', + name: 'foo', + label: 'Foo', + value: 'one,two,three', + }; + + // common steps to add a new variable + flows.newEditPaneVariableClick(); + flows.newEditPanelCommonVariableInputs(variable); + + // set the custom variable value + e2e.pages.Dashboard.Settings.Variables.Edit.CustomVariable.customValueInput().clear().type(variable.value).blur(); + + // assert the dropdown for the variable is visible and has the correct values + e2e.pages.Dashboard.SubMenu.submenuItemLabels(variable.label).should('be.visible').contains(variable.label); + const values = variable.value.split(','); + e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts(values[0]).should('be.visible'); + }); +}); + +// Common flows for adding/editing variables +// TODO: maybe move to e2e flows +const flows = { + newEditPaneVariableClick() { + e2e.components.NavToolbar.editDashboard.editButton().should('be.visible').click(); + e2e.components.PanelEditor.Outline.section().should('be.visible').click(); + e2e.components.PanelEditor.Outline.item('Variables').should('be.visible').click(); + e2e.components.PanelEditor.ElementEditPane.addVariableButton().should('be.visible').click(); + }, + newEditPanelCommonVariableInputs(variable: Variable) { + e2e.components.PanelEditor.ElementEditPane.variableType(variable.type).should('be.visible').click(); + e2e.components.PanelEditor.ElementEditPane.variableNameInput().clear().type(variable.name).blur(); + e2e.components.PanelEditor.ElementEditPane.variableLabelInput().clear().type(variable.label).blur(); + }, +}; + +type Variable = { + type: string; + name: string; + label: string; + description?: string; + value: string; +}; diff --git a/e2e/run-suite b/e2e/run-suite index 28ed2f6bd8d..c5d1f896a35 100755 --- a/e2e/run-suite +++ b/e2e/run-suite @@ -30,6 +30,7 @@ rootForEnterpriseSuite="./e2e/extensions-suite" rootForOldArch="./e2e/old-arch" rootForKubernetesDashboards="./e2e/dashboards-suite" rootForSearchDashboards="./e2e/dashboards-search-suite" +rootForEditDashboards="./e2e/dashboards-edit-v2-suite" declare -A cypressConfig=( [screenshotsFolder]=./e2e/"${args[0]}"/screenshots @@ -149,6 +150,25 @@ case "$1" in ;; esac ;; + "dashboards-edit-v2") + env[kubernetesDashboards]=true + env[dashboardNewLayouts]=true + cypressConfig[specPattern]=$rootForEditDashboards/$testFilesForSingleSuite + cypressConfig[video]=false + case "$2" in + "debug") + echo -e "Debug mode" + env[SLOWMO]=1 + PARAMS="--no-exit" + enterpriseSuite=$(basename "${args[2]}") + ;; + "dev") + echo "Dev mode" + CMD="cypress open" + enterpriseSuite=$(basename "${args[2]}") + ;; + esac + ;; "enterprise-smtp") env[SMTP_PLUGIN_ENABLED]=true cypressConfig[specPattern]=./e2e/extensions/enterprise/smtp-suite/$testFilesForSingleSuite diff --git a/package.json b/package.json index 1fb6e4e45f5..6251c0d989f 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "e2e:old-arch": "./e2e/start-and-run-suite old-arch", "e2e:schema-v2": "./e2e/start-and-run-suite dashboards-schema-v2", "e2e:dashboards-search": "./e2e/start-and-run-suite dashboards-search", + "e2e:dashboards-edit-v2": "./e2e/start-and-run-suite dashboards-edit-v2", + "e2e:dashboards-edit-v2:dev": "./e2e/start-and-run-suite dashboards-edit-v2 dev", "e2e:debug": "./e2e/start-and-run-suite debug", "e2e:dev": "./e2e/start-and-run-suite dev", "e2e:benchmark:live": "./e2e/start-and-run-suite benchmark live", diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index 1d708a77a14..df16879214a 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -532,6 +532,33 @@ export const versionedComponents = { measureButton: { '9.2.0': 'show measure tools', }, + + Outline: { + section: { + '12.0.0': 'data-testid Outline section', + }, + node: { + '12.0.0': (type: string) => `data-testid outline node ${type}`, + }, + item: { + '12.0.0': (type: string) => `data-testid outline item ${type}`, + }, + }, + + ElementEditPane: { + variableType: { + '12.0.0': (type?: string) => `data-testid variable type ${type}`, + }, + addVariableButton: { + '12.0.0': 'data-testid add variable button', + }, + variableNameInput: { + '12.0.0': 'data-testid variable name input', + }, + variableLabelInput: { + '12.0.0': 'data-testid variable label input', + }, + }, }, PanelInspector: { Data: { diff --git a/public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx b/public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx index a667b877357..490ac81bd09 100644 --- a/public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx +++ b/public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx @@ -3,6 +3,7 @@ import { Resizable } from 're-resizable'; import { useLocalStorage } from 'react-use'; import { GrafanaTheme2 } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { SceneObjectState, SceneObjectBase, SceneObject, sceneGraph, useSceneObjectState } from '@grafana/scenes'; import { ElementSelectionContextItem, @@ -272,11 +273,12 @@ export function DashboardEditPaneRenderer({ editPane, isCollapsed, onToggleColla role="button" onClick={() => setOutlineCollapsed(!outlineCollapsed)} className={styles.outlineCollapseButton} + data-testid={selectors.components.PanelEditor.Outline.section} > Outline - +
{!outlineCollapsed && (
diff --git a/public/app/features/dashboard-scene/edit-pane/DashboardOutline.tsx b/public/app/features/dashboard-scene/edit-pane/DashboardOutline.tsx index 58076081f00..725714df3d8 100644 --- a/public/app/features/dashboard-scene/edit-pane/DashboardOutline.tsx +++ b/public/app/features/dashboard-scene/edit-pane/DashboardOutline.tsx @@ -3,6 +3,7 @@ import { sortBy } from 'lodash'; import React, { useEffect, useMemo, useState } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { SceneObject } from '@grafana/scenes'; import { Box, Icon, Text, useElementSelection, useStyles2, useTheme2 } from '@grafana/ui'; import { t, Trans } from 'app/core/internationalization'; @@ -88,7 +89,12 @@ function DashboardOutlineNode({ onPointerDown={onNodeClicked} > {elementInfo.isContainer && ( - )} @@ -96,6 +102,7 @@ function DashboardOutlineNode({ role="button" className={cx(styles.nodeName, isCloned && styles.nodeNameClone)} onDoubleClick={outlineRename.onNameDoubleClicked} + data-testid={selectors.components.PanelEditor.Outline.item(instanceName)} > {outlineRename.isRenaming ? ( diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/actions/ToolbarSwitch.tsx b/public/app/features/dashboard-scene/scene/new-toolbar/actions/ToolbarSwitch.tsx index bd693d10e09..c057a50be2b 100644 --- a/public/app/features/dashboard-scene/scene/new-toolbar/actions/ToolbarSwitch.tsx +++ b/public/app/features/dashboard-scene/scene/new-toolbar/actions/ToolbarSwitch.tsx @@ -11,7 +11,7 @@ interface Props { checkedIcon?: IconName; checkedLabel?: string; disabled?: boolean; - 'data-testId'?: string; + 'data-testid'?: string; onClick: (evt: MouseEvent) => void; } @@ -23,7 +23,7 @@ export const ToolbarSwitch = ({ checkedLabel, disabled, onClick, - 'data-testId': dataTestId, + 'data-testid': dataTestId, }: Props) => { const styles = useStyles2(getStyles); diff --git a/public/app/features/dashboard-scene/settings/variables/VariableEditableElement.tsx b/public/app/features/dashboard-scene/settings/variables/VariableEditableElement.tsx index 213557fc8c4..2a639a2e5c8 100644 --- a/public/app/features/dashboard-scene/settings/variables/VariableEditableElement.tsx +++ b/public/app/features/dashboard-scene/settings/variables/VariableEditableElement.tsx @@ -1,6 +1,7 @@ import { FormEvent, useMemo, useState } from 'react'; import { VariableHide } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { locationService } from '@grafana/runtime'; import { LocalValueVariable, MultiValueVariable, SceneVariable, SceneVariableSet } from '@grafana/scenes'; import { Input, TextArea, Button, Field, Box, Stack } from '@grafana/ui'; @@ -143,14 +144,27 @@ function VariableNameInput({ variable, isNewElement }: { variable: SceneVariable return ( - + ); } function VariableLabelInput({ variable }: VariableInputProps) { const { label } = variable.useState(); - return variable.setState({ label: e.currentTarget.value })} />; + return ( + variable.setState({ label: e.currentTarget.value })} + data-testid={selectors.components.PanelEditor.ElementEditPane.variableLabelInput} + /> + ); } function VariableDescriptionTextArea({ variable }: VariableInputProps) { diff --git a/public/app/features/dashboard-scene/settings/variables/VariableSetEditableElement.tsx b/public/app/features/dashboard-scene/settings/variables/VariableSetEditableElement.tsx index 442f7c58b47..b52127ad95b 100644 --- a/public/app/features/dashboard-scene/settings/variables/VariableSetEditableElement.tsx +++ b/public/app/features/dashboard-scene/settings/variables/VariableSetEditableElement.tsx @@ -3,6 +3,7 @@ import { useMemo } from 'react'; import { useToggle } from 'react-use'; import { GrafanaTheme2 } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { SceneVariable, SceneVariableSet } from '@grafana/scenes'; import { Stack, Button, useStyles2, Text, Box, Card } from '@grafana/ui'; import { t, Trans } from 'app/core/internationalization'; @@ -87,7 +88,14 @@ function VariableList({ set }: { set: SceneVariableSet }) { ))} {canAdd && ( - @@ -115,6 +123,7 @@ function VariableTypeSelection({ onAddVariable }: VariableTypeSelectionProps) { onClick={() => onAddVariable(option.value!)} key={option.value} title={t('dashboard.edit-pane.variables.select-type-card-tooltip', 'Click to select type')} + data-testid={selectors.components.PanelEditor.ElementEditPane.variableType(option.value!)} > {option.label} {option.description} From 2d398af7fb44b702ff64739fbac2477f24d6ee89 Mon Sep 17 00:00:00 2001 From: Isabella Siu Date: Thu, 1 May 2025 15:18:36 -0400 Subject: [PATCH 080/849] Cloudwatch: Update grafana-aws-sdk to fix temp credentials single tenant (#104838) Cloudwatch: update so that temp credentials work both multi and single tenant --- go.mod | 2 +- go.sum | 3 +-- go.work.sum | 2 ++ 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5aaa373613b..2cd83b2a0d5 100644 --- a/go.mod +++ b/go.mod @@ -89,7 +89,7 @@ require ( github.com/grafana/grafana-api-golang-client v0.27.0 // @grafana/alerting-backend github.com/grafana/grafana-app-sdk v0.35.1 // @grafana/grafana-app-platform-squad github.com/grafana/grafana-app-sdk/logging v0.35.1 // @grafana/grafana-app-platform-squad - github.com/grafana/grafana-aws-sdk v0.38.0 // @grafana/aws-datasources + github.com/grafana/grafana-aws-sdk v0.38.1 // @grafana/aws-datasources github.com/grafana/grafana-azure-sdk-go/v2 v2.1.6 // @grafana/partner-datasources github.com/grafana/grafana-cloud-migration-snapshot v1.6.0 // @grafana/grafana-operator-experience-squad github.com/grafana/grafana-google-sdk-go v0.2.1 // @grafana/partner-datasources diff --git a/go.sum b/go.sum index 797bef5179e..3887f8ed5b0 100644 --- a/go.sum +++ b/go.sum @@ -1589,8 +1589,7 @@ github.com/grafana/grafana-app-sdk v0.35.1 h1:zEXubzsQrxGBOzXJJMBwhEClC/tvPi0sfK github.com/grafana/grafana-app-sdk v0.35.1/go.mod h1:Zx5MkVppYK+ElSDUAR6+fjzOVo6I/cIgk+ty+LmNOxI= github.com/grafana/grafana-app-sdk/logging v0.35.1 h1:taVpl+RoixTYl0JBJGhH+fPVmwA9wvdwdzJTZsv9buM= github.com/grafana/grafana-app-sdk/logging v0.35.1/go.mod h1:Y/bvbDhBiV/tkIle9RW49pgfSPIPSON8Q4qjx3pyqDk= -github.com/grafana/grafana-aws-sdk v0.38.0 h1:OALlZ3FvmzKHICQe0C1cuAMdyhSFkVYxHdxJsleotwc= -github.com/grafana/grafana-aws-sdk v0.38.0/go.mod h1:j3vi+cXYHEFqjhBGrI6/lw1TNM+dl0Y3f0cSnDOPy+s= +github.com/grafana/grafana-aws-sdk v0.38.1 h1:4fU28F/UIs3YYuS52bBzTOKpFIIYGJZmgM6PO7IEj90= github.com/grafana/grafana-azure-sdk-go/v2 v2.1.6 h1:OfCkitCuomzZKW1WYHrG8MxKwtMhALb7jqoj+487eTg= github.com/grafana/grafana-azure-sdk-go/v2 v2.1.6/go.mod h1:V7y2BmsWxS3A9Ohebwn4OiSfJJqi//4JQydQ8fHTduo= github.com/grafana/grafana-cloud-migration-snapshot v1.6.0 h1:S4kHwr//AqhtL9xHBtz1gqVgZQeCRGTxjgsRBAkpjKY= diff --git a/go.work.sum b/go.work.sum index 60f369552b9..88d61de884a 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1116,6 +1116,8 @@ github.com/grafana/cog v0.0.23 h1:/0CCJ24Z8XXM2DnboSd2FzoIswUroqIZzVr8oJWmMQs= github.com/grafana/cog v0.0.23/go.mod h1:jrS9indvWuDs60RHEZpLaAkmZdgyoLKMOEUT0jiB1t0= github.com/grafana/go-gelf/v2 v2.0.1 h1:BOChP0h/jLeD+7F9mL7tq10xVkDG15he3T1zHuQaWak= github.com/grafana/go-gelf/v2 v2.0.1/go.mod h1:lexHie0xzYGwCgiRGcvZ723bSNyNI8ZRD4s0CLobh90= +github.com/grafana/grafana-aws-sdk v0.38.1 h1:4fU28F/UIs3YYuS52bBzTOKpFIIYGJZmgM6PO7IEj90= +github.com/grafana/grafana-aws-sdk v0.38.1/go.mod h1:j3vi+cXYHEFqjhBGrI6/lw1TNM+dl0Y3f0cSnDOPy+s= github.com/grafana/grafana-plugin-sdk-go v0.263.0/go.mod h1:U43Cnrj/9DNYyvFcNdeUWNjMXTKNB0jcTcQGpWKd2gw= github.com/grafana/grafana-plugin-sdk-go v0.267.0/go.mod h1:OuwS4c/JYgn0rr/w5zhJBpLo4gKm/vw15RsfpYAvK9Q= github.com/grafana/grafana-plugin-sdk-go v0.269.1/go.mod h1:yv2KbO4mlr9WuDK2f+2gHAMTwwLmLuqaEnrPXTRU+OI= From a9960ed4d28a7428cb57b978f571f5fbd9390d3d Mon Sep 17 00:00:00 2001 From: Scott Lepper Date: Thu, 1 May 2025 15:37:13 -0400 Subject: [PATCH 081/849] add constant variable type to editor (#104662) add constant variable type to editor --- .../editors/ConstantVariableEditor.test.tsx | 7 ++- .../editors/ConstantVariableEditor.tsx | 43 +++++++++++++++++-- .../settings/variables/utils.ts | 3 +- public/locales/en-US/grafana.json | 1 + 4 files changed, 48 insertions(+), 6 deletions(-) diff --git a/public/app/features/dashboard-scene/settings/variables/editors/ConstantVariableEditor.test.tsx b/public/app/features/dashboard-scene/settings/variables/editors/ConstantVariableEditor.test.tsx index 94b26d3537c..0d1c158b189 100644 --- a/public/app/features/dashboard-scene/settings/variables/editors/ConstantVariableEditor.test.tsx +++ b/public/app/features/dashboard-scene/settings/variables/editors/ConstantVariableEditor.test.tsx @@ -3,7 +3,7 @@ import userEvent from '@testing-library/user-event'; import { ConstantVariable } from '@grafana/scenes'; -import { ConstantVariableEditor } from './ConstantVariableEditor'; +import { ConstantVariableEditor, getConstantVariableOptions } from './ConstantVariableEditor'; describe('ConstantVariableEditor', () => { let constantVar: ConstantVariable; @@ -36,6 +36,11 @@ describe('ConstantVariableEditor', () => { await userEvent.tab(); expect(constantVar.state.value).toBe(newValue); }); + + it('should get variable options', () => { + const options = getConstantVariableOptions(constantVar); + expect(options).toHaveLength(1); + }); }); async function buildTestScene() { diff --git a/public/app/features/dashboard-scene/settings/variables/editors/ConstantVariableEditor.tsx b/public/app/features/dashboard-scene/settings/variables/editors/ConstantVariableEditor.tsx index 10f42d992e1..0050c8a61a4 100644 --- a/public/app/features/dashboard-scene/settings/variables/editors/ConstantVariableEditor.tsx +++ b/public/app/features/dashboard-scene/settings/variables/editors/ConstantVariableEditor.tsx @@ -1,6 +1,10 @@ -import * as React from 'react'; +import { FormEvent } from 'react'; +import { lastValueFrom } from 'rxjs'; -import { ConstantVariable } from '@grafana/scenes'; +import { ConstantVariable, SceneVariable } from '@grafana/scenes'; +import { Input } from '@grafana/ui'; +import { t } from 'app/core/internationalization'; +import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor'; import { ConstantVariableForm } from '../components/ConstantVariableForm'; @@ -11,9 +15,40 @@ interface ConstantVariableEditorProps { export function ConstantVariableEditor({ variable }: ConstantVariableEditorProps) { const { value } = variable.useState(); - const onConstantValueChange = (event: React.FormEvent) => { + const onConstantValueChange = (event: FormEvent) => { variable.setState({ value: event.currentTarget.value }); }; - return ; + return ; +} + +export function getConstantVariableOptions(variable: SceneVariable): OptionsPaneItemDescriptor[] { + if (!(variable instanceof ConstantVariable)) { + console.warn('getConstantVariableOptions: variable is not a ConstantVariable'); + return []; + } + + return [ + new OptionsPaneItemDescriptor({ + title: t('dashboard-scene.constant-variable-form.label-value', 'Value'), + render: () => , + }), + ]; +} + +function ConstantValueInput({ variable }: { variable: ConstantVariable }) { + const { value } = variable.useState(); + + const onBlur = async (event: FormEvent) => { + variable.setState({ value: event.currentTarget.value }); + await lastValueFrom(variable.validateAndUpdate!()); + }; + + return ( + + ); } diff --git a/public/app/features/dashboard-scene/settings/variables/utils.ts b/public/app/features/dashboard-scene/settings/variables/utils.ts index c0ccfbc1333..d3e1c59ac20 100644 --- a/public/app/features/dashboard-scene/settings/variables/utils.ts +++ b/public/app/features/dashboard-scene/settings/variables/utils.ts @@ -25,7 +25,7 @@ import { getIntervalsQueryFromNewIntervalModel } from '../../utils/utils'; import { getCustomVariableOptions } from './components/CustomVariableForm'; import { AdHocFiltersVariableEditor } from './editors/AdHocFiltersVariableEditor'; -import { ConstantVariableEditor } from './editors/ConstantVariableEditor'; +import { ConstantVariableEditor, getConstantVariableOptions } from './editors/ConstantVariableEditor'; import { CustomVariableEditor } from './editors/CustomVariableEditor'; import { DataSourceVariableEditor } from './editors/DataSourceVariableEditor'; import { GroupByVariableEditor } from './editors/GroupByVariableEditor'; @@ -63,6 +63,7 @@ export const EDITABLE_VARIABLES: Record