From 163036952152e5b4d90ff105cf8f4d81dd70a04b Mon Sep 17 00:00:00 2001 From: guillaume-peoch-sonarsource Date: Mon, 23 Sep 2024 11:10:38 +0200 Subject: [PATCH] SONAR-23008 Add the new AI code assurance badge when the feature is in the feature list --- .../badges/ProjectBadges.tsx | 27 +++++++++++++++++-- .../badges/__tests__/ProjectBadges-test.tsx | 27 +++++++++++++------ .../badges/__tests__/utils-test.ts | 27 ++++++++++++++++--- .../apps/projectInformation/badges/utils.ts | 16 ++++++++--- .../resources/org/sonar/l10n/core.properties | 5 ++++ 5 files changed, 86 insertions(+), 16 deletions(-) diff --git a/server/sonar-web/src/main/js/apps/projectInformation/badges/ProjectBadges.tsx b/server/sonar-web/src/main/js/apps/projectInformation/badges/ProjectBadges.tsx index 9a11599bfe9..da0c3962add 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/badges/ProjectBadges.tsx +++ b/server/sonar-web/src/main/js/apps/projectInformation/badges/ProjectBadges.tsx @@ -36,6 +36,7 @@ import { useState } from 'react'; import { Image } from '~sonar-aligned/components/common/Image'; import { getBranchLikeQuery } from '~sonar-aligned/helpers/branch-like'; import { MetricKey } from '~sonar-aligned/types/metrics'; +import { useAvailableFeatures } from '../../../app/components/available-features/withAvailableFeatures'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { localizeMetric } from '../../../helpers/measures'; import { @@ -45,6 +46,8 @@ import { useRenewBagdeTokenMutation, } from '../../../queries/badges'; import { BranchLike } from '../../../types/branch-like'; +import { isProject } from '../../../types/component'; +import { Feature } from '../../../types/features'; import { Component } from '../../../types/types'; import { BadgeFormats, BadgeOptions, BadgeType, getBadgeSnippet, getBadgeUrl } from './utils'; @@ -68,6 +71,7 @@ export default function ProjectBadges(props: ProjectBadgesProps) { } = useBadgeTokenQuery(project); const { data: metricOptions, isLoading: isLoadingMetrics } = useBadgeMetricsQuery(); const { mutate: renewToken, isPending: isRenewing } = useRenewBagdeTokenMutation(); + const { hasFeature } = useAvailableFeatures(); const isLoading = isLoadingMetrics || isLoadingToken || isRenewing; const handleSelectType = (selectedType: BadgeType) => { @@ -107,7 +111,7 @@ export default function ProjectBadges(props: ProjectBadgesProps) { image={ {translate('overview.badges', } description={translate('overview.badges', BadgeType.measure, 'description', qualifier)} @@ -119,7 +123,7 @@ export default function ProjectBadges(props: ProjectBadgesProps) { image={ {translate('overview.badges', } @@ -130,6 +134,25 @@ export default function ProjectBadges(props: ProjectBadgesProps) { qualifier, )} /> + {hasFeature(Feature.AiCodeAssurance) && isProject(qualifier) && ( + handleSelectType(BadgeType.aiCodeAssurance)} + selected={BadgeType.aiCodeAssurance === selectedType} + image={ + {translate('overview.badges', + } + description={translate( + 'overview.badges', + BadgeType.aiCodeAssurance, + 'description', + qualifier, + )} + /> + )} diff --git a/server/sonar-web/src/main/js/apps/projectInformation/badges/__tests__/ProjectBadges-test.tsx b/server/sonar-web/src/main/js/apps/projectInformation/badges/__tests__/ProjectBadges-test.tsx index 1ff688da6f8..2354d1f4950 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/badges/__tests__/ProjectBadges-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectInformation/badges/__tests__/ProjectBadges-test.tsx @@ -65,7 +65,9 @@ it('should renew token', async () => { expect(screen.getByAltText(`overview.badges.${BadgeType.measure}.alt`)).toHaveAttribute( 'src', - `host/api/project_badges/measure?branch=branch-6.7&project=my-project&metric=${MetricKey.alert_status}&token=foo`, + expect.stringContaining( + `host/api/project_badges/measure?branch=branch-6.7&project=my-project&metric=${MetricKey.alert_status}&token=foo`, + ), ); await user.click(screen.getByText('overview.badges.renew')); @@ -74,12 +76,16 @@ it('should renew token', async () => { await screen.findByAltText(`overview.badges.${BadgeType.qualityGate}.alt`), ).toHaveAttribute( 'src', - 'host/api/project_badges/quality_gate?branch=branch-6.7&project=my-project&token=bar', + expect.stringContaining( + 'host/api/project_badges/quality_gate?branch=branch-6.7&project=my-project&token=bar', + ), ); expect(screen.getByAltText(`overview.badges.${BadgeType.measure}.alt`)).toHaveAttribute( 'src', - `host/api/project_badges/measure?branch=branch-6.7&project=my-project&metric=${MetricKey.alert_status}&token=bar`, + expect.stringContaining( + `host/api/project_badges/measure?branch=branch-6.7&project=my-project&metric=${MetricKey.alert_status}&token=bar`, + ), ); }); @@ -90,7 +96,8 @@ it('should update params', async () => { expect( screen.getByText( - `[![${MetricKey.alert_status}](host/api/project_badges/measure?branch=branch-6.7&project=my-project&metric=${MetricKey.alert_status}&token=foo)](/dashboard)`, + `[![${MetricKey.alert_status}](host/api/project_badges/measure?branch=branch-6.7&project=my-project&metric=${MetricKey.alert_status}&token=foo`, + { exact: false }, ), ).toBeInTheDocument(); @@ -98,7 +105,8 @@ it('should update params', async () => { expect( screen.getByText( - `host/api/project_badges/measure?branch=branch-6.7&project=my-project&metric=${MetricKey.alert_status}&token=foo`, + 'host/api/project_badges/measure?branch=branch-6.7&project=my-project&metric=alert_status&token=foo', + { exact: false }, ), ).toBeInTheDocument(); @@ -107,7 +115,8 @@ it('should update params', async () => { expect( screen.getByText( - `host/api/project_badges/measure?branch=branch-6.7&project=my-project&metric=${MetricKey.coverage}&token=foo`, + 'host/api/project_badges/measure?branch=branch-6.7&project=my-project&metric=coverage&token=foo', + { exact: false }, ), ).toBeInTheDocument(); @@ -119,7 +128,8 @@ it('should update params', async () => { expect( screen.getByText( - `host/api/project_badges/quality_gate?branch=branch-6.7&project=my-project&token=foo`, + 'host/api/project_badges/quality_gate?branch=branch-6.7&project=my-project&token=foo', + { exact: false }, ), ).toBeInTheDocument(); @@ -131,7 +141,8 @@ it('should update params', async () => { expect( screen.getByText( - `host/api/project_badges/measure?branch=branch-6.7&project=my-project&metric=${MetricKey.coverage}&token=foo`, + 'host/api/project_badges/measure?branch=branch-6.7&project=my-project&metric=coverage&token=foo', + { exact: false }, ), ).toBeInTheDocument(); }); diff --git a/server/sonar-web/src/main/js/apps/projectInformation/badges/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/projectInformation/badges/__tests__/utils-test.ts index 54e6db99d16..06710bbd7e1 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/badges/__tests__/utils-test.ts +++ b/server/sonar-web/src/main/js/apps/projectInformation/badges/__tests__/utils-test.ts @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { Location } from '~sonar-aligned/types/router'; +import { MetricKey } from '../../../../sonar-aligned/types/metrics'; import { BadgeOptions, BadgeType, getBadgeSnippet, getBadgeUrl } from '../utils'; jest.mock('../../../../helpers/urls', () => ({ @@ -45,8 +46,20 @@ describe('#getBadgeUrl', () => { ); }); + it('should generate correct ai code assurance badge links', () => { + expect(getBadgeUrl(BadgeType.aiCodeAssurance, options, 'foo')).toBe( + 'host/api/project_badges/ai_code_assurance?branch=master&project=foo&token=foo', + ); + }); + + it('should generate correct ai code assurance badge links with timestamp', () => { + expect(getBadgeUrl(BadgeType.aiCodeAssurance, options, 'foo', true)).toContain( + 'host/api/project_badges/ai_code_assurance?branch=master&project=foo&token=foo', + ); + }); + it('should ignore undefined parameters', () => { - expect(getBadgeUrl(BadgeType.measure, { metric: 'alert_status' }, 'foo')).toBe( + expect(getBadgeUrl(BadgeType.measure, { metric: MetricKey.alert_status }, 'foo')).toBe( 'host/api/project_badges/measure?metric=alert_status&token=foo', ); }); @@ -59,9 +72,17 @@ describe('#getBadgeUrl', () => { }); describe('#getBadgeSnippet', () => { - it('should generate a correct markdown image', () => { - expect(getBadgeSnippet(BadgeType.measure, { ...options, format: 'md' }, 'foo')).toBe( + it('should generate a correct markdown image for measure', () => { + const snippet = getBadgeSnippet(BadgeType.measure, options, 'foo'); + expect(snippet).toBe( '[![alert_status](host/api/project_badges/measure?branch=master&project=foo&metric=alert_status&token=foo)](host/dashboard?id=foo&branch=master)', ); }); + + it('should generate a correct markdown image for ai code assurance', () => { + const snippet = getBadgeSnippet(BadgeType.aiCodeAssurance, options, 'foo'); + expect(snippet).toBe( + '[![overview.badges.ai_code_assurance](host/api/project_badges/ai_code_assurance?branch=master&project=foo&token=foo)](host/dashboard?id=foo&branch=master)', + ); + }); }); diff --git a/server/sonar-web/src/main/js/apps/projectInformation/badges/utils.ts b/server/sonar-web/src/main/js/apps/projectInformation/badges/utils.ts index b1c3435804b..904c2357c4b 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/badges/utils.ts +++ b/server/sonar-web/src/main/js/apps/projectInformation/badges/utils.ts @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { getLocalizedMetricName } from '../../../helpers/l10n'; +import { getLocalizedMetricName, translate } from '../../../helpers/l10n'; import { omitNil } from '../../../helpers/request'; import { getHostUrl, getPathUrlAsString, getProjectUrl } from '../../../helpers/urls'; @@ -35,6 +35,7 @@ export interface BadgeOptions { export enum BadgeType { measure = 'measure', qualityGate = 'quality_gate', + aiCodeAssurance = 'ai_code_assurance', } export function getBadgeSnippet(type: BadgeType, options: BadgeOptions, token: string) { @@ -52,6 +53,9 @@ export function getBadgeSnippet(type: BadgeType, options: BadgeOptions, token: s case BadgeType.measure: label = getLocalizedMetricName({ key: metric }); break; + case BadgeType.aiCodeAssurance: + label = translate('overview.badges.ai_code_assurance'); + break; case BadgeType.qualityGate: default: label = 'Quality gate'; @@ -70,16 +74,22 @@ export function getBadgeUrl( type: BadgeType, { branch, project, metric = 'alert_status', pullRequest }: BadgeOptions, token: string, + disableBrowserCache: boolean = false, ) { switch (type) { case BadgeType.qualityGate: return `${getHostUrl()}/api/project_badges/quality_gate?${new URLSearchParams( omitNil({ branch, project, pullRequest, token }), - ).toString()}`; + ).toString()}${disableBrowserCache ? `&${new Date().getTime()}` : ''}`; + case BadgeType.aiCodeAssurance: + return `${getHostUrl()}/api/project_badges/ai_code_assurance?${new URLSearchParams( + omitNil({ branch, project, pullRequest, token }), + ).toString()}${disableBrowserCache ? `&${new Date().getTime()}` : ''}`; + case BadgeType.measure: default: return `${getHostUrl()}/api/project_badges/measure?${new URLSearchParams( omitNil({ branch, project, metric, pullRequest, token }), - ).toString()}`; + ).toString()}${disableBrowserCache ? `&${new Date().getTime()}` : ''}`; } } diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index a72896a47f2..0a43a4c953c 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -4360,11 +4360,16 @@ overview.badges.measure.alt=Standard badge overview.badges.measure.description.TRK=Displays the current status of one metric of your project. overview.badges.measure.description.VW=Displays the current status of one metric of your portfolio. overview.badges.measure.description.APP=Displays the current status of one metric of your application. +overview.badges.quality_gate=Quality Gate overview.badges.quality_gate.alt=Quality Gate badge overview.badges.quality_gate.description=Displays the current quality gate status of your project. overview.badges.quality_gate.description.APP=Displays the current quality gate status of your application. overview.badges.quality_gate.description.TRK=Displays the current quality gate status of your project. overview.badges.quality_gate.description.VW=Displays the current quality gate status of your portfolio. +overview.badges.ai_code_assurance=AI Code Assurance +overview.badges.ai_code_assurance.alt=AI Code Assurance badge +overview.badges.ai_code_assurance.description=Displays the current status of Sonar's AI Code Assurance. +overview.badges.ai_code_assurance.description.TRK=Displays the current status of Sonar's AI Code Assurance of your project. overview.badges.leak_warning=Project badges can expose your security rating and other measures. Only use project badges in trusted environments. overview.badges.renew=Renew Token overview.badges.renew.description=If your project badge security token has leaked to an unsafe environment, you can renew it: -- 2.39.5