aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorguillaume-peoch-sonarsource <guillaume.peoch@sonarsource.com>2024-09-23 11:10:38 +0200
committersonartech <sonartech@sonarsource.com>2024-09-25 20:02:54 +0000
commit163036952152e5b4d90ff105cf8f4d81dd70a04b (patch)
tree8b8f6e8815e17c02a64afcc0595783873abe7606
parent8bd0aa22f3198179249796c3a634a77a489520b3 (diff)
downloadsonarqube-163036952152e5b4d90ff105cf8f4d81dd70a04b.tar.gz
sonarqube-163036952152e5b4d90ff105cf8f4d81dd70a04b.zip
SONAR-23008 Add the new AI code assurance badge when the feature is in the feature list
-rw-r--r--server/sonar-web/src/main/js/apps/projectInformation/badges/ProjectBadges.tsx27
-rw-r--r--server/sonar-web/src/main/js/apps/projectInformation/badges/__tests__/ProjectBadges-test.tsx27
-rw-r--r--server/sonar-web/src/main/js/apps/projectInformation/badges/__tests__/utils-test.ts27
-rw-r--r--server/sonar-web/src/main/js/apps/projectInformation/badges/utils.ts16
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties5
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={
<Image
alt={translate('overview.badges', BadgeType.measure, 'alt')}
- src={getBadgeUrl(BadgeType.measure, fullBadgeOptions, token)}
+ src={getBadgeUrl(BadgeType.measure, fullBadgeOptions, token, true)}
/>
}
description={translate('overview.badges', BadgeType.measure, 'description', qualifier)}
@@ -119,7 +123,7 @@ export default function ProjectBadges(props: ProjectBadgesProps) {
image={
<Image
alt={translate('overview.badges', BadgeType.qualityGate, 'alt')}
- src={getBadgeUrl(BadgeType.qualityGate, fullBadgeOptions, token)}
+ src={getBadgeUrl(BadgeType.qualityGate, fullBadgeOptions, token, true)}
width="128px"
/>
}
@@ -130,6 +134,25 @@ export default function ProjectBadges(props: ProjectBadgesProps) {
qualifier,
)}
/>
+ {hasFeature(Feature.AiCodeAssurance) && isProject(qualifier) && (
+ <IllustratedSelectionCard
+ className="sw-w-abs-300 it__badge-button"
+ onClick={() => handleSelectType(BadgeType.aiCodeAssurance)}
+ selected={BadgeType.aiCodeAssurance === selectedType}
+ image={
+ <Image
+ alt={translate('overview.badges', BadgeType.aiCodeAssurance, 'alt')}
+ src={getBadgeUrl(BadgeType.aiCodeAssurance, fullBadgeOptions, token, true)}
+ />
+ }
+ description={translate(
+ 'overview.badges',
+ BadgeType.aiCodeAssurance,
+ 'description',
+ qualifier,
+ )}
+ />
+ )}
</div>
</Spinner>
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: