From 026a0003041192499c3533ee5901d9faa513b00a Mon Sep 17 00:00:00 2001 From: Alex Khomenko Date: Fri, 28 Nov 2025 13:32:25 +0200 Subject: [PATCH] Provisioning: Prevent duplicate source links (#114577) --- public/app/features/dashboard/api/v1.ts | 5 +++-- public/app/features/dashboard/api/v2.ts | 7 ++++--- .../app/features/provisioning/utils/sourceLink.ts | 14 ++++++++++++++ 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/public/app/features/dashboard/api/v1.ts b/public/app/features/dashboard/api/v1.ts index 9414d3531a6..c69093258cb 100644 --- a/public/app/features/dashboard/api/v1.ts +++ b/public/app/features/dashboard/api/v1.ts @@ -21,7 +21,7 @@ import { } from 'app/features/apiserver/types'; import { getDashboardUrl } from 'app/features/dashboard-scene/utils/getDashboardUrl'; import { DeleteDashboardResponse } from 'app/features/manage-dashboards/types'; -import { buildSourceLink } from 'app/features/provisioning/utils/sourceLink'; +import { buildSourceLink, removeExistingSourceLinks } from 'app/features/provisioning/utils/sourceLink'; import { DashboardDataDTO, DashboardDTO, SaveDashboardResponseDTO } from 'app/types/dashboard'; import { SaveDashboardCommand } from '../components/SaveDashboard/types'; @@ -164,7 +164,8 @@ export class K8sDashboardAPI implements DashboardAPI { // Inject source link for repo-managed dashboards const sourceLink = await buildSourceLink(annotations); if (sourceLink) { - result.dashboard.links = [sourceLink, ...(result.dashboard.links || [])]; + const linksWithoutSource = removeExistingSourceLinks(result.dashboard.links); + result.dashboard.links = [sourceLink, ...linksWithoutSource]; } if (dash.metadata.labels?.[DeprecatedInternalId]) { diff --git a/public/app/features/dashboard/api/v2.ts b/public/app/features/dashboard/api/v2.ts index ddf85da7338..35c5e1a7a0d 100644 --- a/public/app/features/dashboard/api/v2.ts +++ b/public/app/features/dashboard/api/v2.ts @@ -9,8 +9,8 @@ import { AnnoKeyFolder, AnnoKeyFolderTitle, AnnoKeyFolderUrl, - AnnoKeyMessage, AnnoKeyGrantPermissions, + AnnoKeyMessage, DeprecatedInternalId, Resource, ResourceClient, @@ -18,7 +18,7 @@ import { } from 'app/features/apiserver/types'; import { getDashboardUrl } from 'app/features/dashboard-scene/utils/getDashboardUrl'; import { DeleteDashboardResponse } from 'app/features/manage-dashboards/types'; -import { buildSourceLink } from 'app/features/provisioning/utils/sourceLink'; +import { buildSourceLink, removeExistingSourceLinks } from 'app/features/provisioning/utils/sourceLink'; import { DashboardDTO, SaveDashboardResponseDTO } from 'app/types/dashboard'; import { SaveDashboardCommand } from '../components/SaveDashboard/types'; @@ -79,7 +79,8 @@ export class K8sDashboardV2API // Inject source link for repo-managed dashboards const sourceLink = await buildSourceLink(dashboard.metadata.annotations); if (sourceLink) { - dashboard.spec.links = [sourceLink, ...(dashboard.spec.links || [])]; + const linksWithoutSource = removeExistingSourceLinks(dashboard.spec.links); + dashboard.spec.links = [sourceLink, ...linksWithoutSource]; } return dashboard; diff --git a/public/app/features/provisioning/utils/sourceLink.ts b/public/app/features/provisioning/utils/sourceLink.ts index 6018f463eab..cb8269fe715 100644 --- a/public/app/features/provisioning/utils/sourceLink.ts +++ b/public/app/features/provisioning/utils/sourceLink.ts @@ -16,6 +16,20 @@ import { isValidRepoType } from '../guards'; import { getHasTokenInstructions, getRepoFileUrl } from './git'; +/** + * Find and remove existing source links from the links array. + * A source link is identified by its tooltip matching the source link tooltip translation. + * Returns the links array with source links removed. + */ +export function removeExistingSourceLinks(links: DashboardLink[] | undefined): DashboardLink[] { + if (!links) { + return []; + } + // TODO This is a pretty hacky way to match the source links, needs a better alternative + const sourceLinkTooltip = t('dashboard.source-link.tooltip', 'View source file in repository'); + return links.filter((link) => link.tooltip !== sourceLinkTooltip); +} + /** * Build a source link for a repo-managed dashboard. * Returns undefined if the dashboard is not repo-managed or if the repository is not a git provider.