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 {
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';
} = 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) => {
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)}
image={
<Image
alt={translate('overview.badges', BadgeType.qualityGate, 'alt')}
- src={getBadgeUrl(BadgeType.qualityGate, fullBadgeOptions, token)}
+ src={getBadgeUrl(BadgeType.qualityGate, fullBadgeOptions, token, true)}
width="128px"
/>
}
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>
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'));
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`,
+ ),
);
});
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();
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();
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();
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();
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();
});
* 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', () => ({
);
});
+ 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',
);
});
});
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)',
+ );
+ });
});
* 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';
export enum BadgeType {
measure = 'measure',
qualityGate = 'quality_gate',
+ aiCodeAssurance = 'ai_code_assurance',
}
export function getBadgeSnippet(type: BadgeType, options: BadgeOptions, token: string) {
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';
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()}` : ''}`;
}
}
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: