--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+import { throwGlobalError } from '~sonar-aligned/helpers/error';
+import { getJSON } from '~sonar-aligned/helpers/request';
+
+export function isProjectAiCodeAssured(project: string): Promise<boolean> {
+ return getJSON('/api/projects/get_ai_code_assurance', { project })
+ .then((response) => response.aiCodeAssurance)
+ .catch(throwGlobalError);
+}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { isProjectAiCodeAssured } from '../ai-code-assurance';
import { getProjectBadgesToken, renewProjectBadgesToken } from '../project-badges';
jest.mock('../project-badges');
jest.mock('../project-badges');
+jest.mock('../ai-code-assurance');
const defaultToken = 'sqb_2b5052cef8eac91a921ac71be9227a27f6b6b38b';
jest.mocked(getProjectBadgesToken).mockImplementation(this.handleGetProjectBadgesToken);
jest.mocked(renewProjectBadgesToken).mockImplementation(this.handleRenewProjectBadgesToken);
+ jest.mocked(isProjectAiCodeAssured).mockImplementation(this.handleProjectAiGeneratedCode);
}
handleGetProjectBadgesToken = () => {
return Promise.resolve(this.token);
};
+ handleProjectAiGeneratedCode = (project: string) => {
+ if (project === 'no-ai') {
+ return Promise.resolve(false);
+ }
+ return Promise.resolve(true);
+ };
+
handleRenewProjectBadgesToken = () => {
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
this.token =
import ProjectLinksServiceMock from '../../../api/mocks/ProjectLinksServiceMock';
import { mockComponent } from '../../../helpers/mocks/component';
import { mockCurrentUser, mockLoggedInUser, mockMeasure } from '../../../helpers/testMocks';
-import { renderAppWithComponentContext } from '../../../helpers/testReactTestingUtils';
+import {
+ renderAppWithComponentContext,
+ RenderContext,
+} from '../../../helpers/testReactTestingUtils';
+import { Feature } from '../../../types/features';
import { Component } from '../../../types/types';
-import { CurrentUser } from '../../../types/users';
import routes from '../routes';
jest.mock('../../../api/rules');
description: 'Test description',
tags: ['bar'],
},
- mockLoggedInUser(),
+ { currentUser: mockLoggedInUser(), featureList: [Feature.AiCodeAssurance] },
);
expect(await ui.projectPageTitle.find()).toBeInTheDocument();
expect(ui.qualityGateList.get()).toBeInTheDocument();
expect(ui.link.getAll(ui.qualityGateList.get())).toHaveLength(1);
expect(ui.link.getAll(ui.qualityProfilesList.get())).toHaveLength(1);
expect(ui.link.getAll(ui.externalLinksList.get())).toHaveLength(1);
+ expect(screen.getByText('project.info.ai_code_assurance.title')).toBeInTheDocument();
expect(screen.getByText('Test description')).toBeInTheDocument();
expect(screen.getByText('my-project')).toBeInTheDocument();
expect(screen.getByText('visibility.private')).toBeInTheDocument();
description: 'Test description',
tags: ['bar'],
},
- mockLoggedInUser(),
+ { currentUser: mockLoggedInUser() },
);
expect(await ui.applicationPageTitle.find()).toBeInTheDocument();
expect(ui.qualityGateList.query()).not.toBeInTheDocument();
});
it('should hide some fields for application', async () => {
- renderProjectInformationApp({
- qualifier: ComponentQualifier.Application,
- });
+ renderProjectInformationApp(
+ {
+ qualifier: ComponentQualifier.Application,
+ },
+ { featureList: [Feature.AiCodeAssurance] },
+ );
expect(await ui.applicationPageTitle.find()).toBeInTheDocument();
expect(screen.getByText('application.info.empty_description')).toBeInTheDocument();
+ expect(screen.queryByText('project.info.ai_code_assurance.title')).not.toBeInTheDocument();
expect(screen.getByText('visibility.public')).toBeInTheDocument();
expect(ui.tags.get()).toHaveTextContent('no_tags');
});
+it('should not display ai code assurence', async () => {
+ renderProjectInformationApp(
+ {
+ key: 'no-ai',
+ },
+ { featureList: [Feature.AiCodeAssurance] },
+ );
+ expect(await ui.projectPageTitle.find()).toBeInTheDocument();
+ expect(screen.queryByText('project.info.ai_code_assurance.title')).not.toBeInTheDocument();
+});
+
it('should not show field that is not configured', async () => {
renderProjectInformationApp({
qualityGate: undefined,
function renderProjectInformationApp(
overrides: Partial<Component> = {},
- currentUser: CurrentUser = mockCurrentUser(),
+ context: RenderContext = { currentUser: mockCurrentUser() },
) {
const component = mockComponent(overrides);
componentsMock.registerComponent(component, [componentsMock.components[0].component]);
return renderAppWithComponentContext(
'project/information',
routes,
- { currentUser },
+ { ...context },
{ component },
);
}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import classNames from 'classnames';
-import { BasicSeparator, SubTitle } from 'design-system';
+import { BasicSeparator, SubHeading, SubTitle } from 'design-system';
import React, { PropsWithChildren, useEffect, useState } from 'react';
+import { FormattedMessage } from 'react-intl';
import { ComponentQualifier, Visibility } from '~sonar-aligned/types/component';
import { getProjectLinks } from '../../../api/projectLinks';
+import { useAvailableFeatures } from '../../../app/components/available-features/withAvailableFeatures';
import { translate } from '../../../helpers/l10n';
+import { useProjectAiCodeAssuredQuery } from '../../../queries/ai-code-assurance';
+import { Feature } from '../../../types/features';
import { Component, Measure, ProjectLink } from '../../../types/types';
import MetaDescription from './components/MetaDescription';
import MetaKey from './components/MetaKey';
export default function AboutProject(props: AboutProjectProps) {
const { component, measures = [] } = props;
+ const { hasFeature } = useAvailableFeatures();
const isApp = component.qualifier === ComponentQualifier.Application;
const [links, setLinks] = useState<ProjectLink[] | undefined>(undefined);
+ const { data: isAiAssured } = useProjectAiCodeAssuredQuery(
+ { project: component.key },
+ {
+ enabled:
+ component.qualifier === ComponentQualifier.Project && hasFeature(Feature.AiCodeAssurance),
+ },
+ );
useEffect(() => {
if (!isApp) {
(component.qualityGate ||
(component.qualityProfiles && component.qualityProfiles.length > 0)) && (
<ProjectInformationSection className="sw-pt-0 sw-flex sw-flex-col sw-gap-4">
- {component.qualityGate && <MetaQualityGate qualityGate={component.qualityGate} />}
+ {component.qualityGate && (
+ <MetaQualityGate qualityGate={component.qualityGate} isAiAssured={isAiAssured} />
+ )}
{component.qualityProfiles && component.qualityProfiles.length > 0 && (
<MetaQualityProfiles profiles={component.qualityProfiles} />
</ProjectInformationSection>
)}
+ {isAiAssured === true && (
+ <ProjectInformationSection>
+ <SubHeading>{translate('project.info.ai_code_assurance.title')}</SubHeading>
+ <span>
+ <FormattedMessage id="projects.ai_code.content" />
+ </span>
+ </ProjectInformationSection>
+ )}
+
<ProjectInformationSection>
<MetaKey componentKey={component.key} qualifier={component.qualifier} />
</ProjectInformationSection>
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { Link, Note, SubHeading } from 'design-system';
+import { Link, Note, StyledMutedText, SubHeading } from 'design-system';
import * as React from 'react';
+import { FormattedMessage } from 'react-intl';
import { translate } from '../../../../helpers/l10n';
import { getQualityGateUrl } from '../../../../helpers/urls';
interface Props {
+ isAiAssured?: boolean;
qualityGate: { isDefault?: boolean; name: string };
}
-export default function MetaQualityGate({ qualityGate }: Props) {
+export default function MetaQualityGate({ qualityGate, isAiAssured }: Props) {
return (
<div>
<SubHeading id="quality-gate-header">{translate('project.info.quality_gate')}</SubHeading>
<Link to={getQualityGateUrl(qualityGate.name)}>{qualityGate.name}</Link>
</li>
</ul>
+ {isAiAssured === true && (
+ <StyledMutedText className="sw-text-wrap sw-mt-2">
+ <FormattedMessage id="project.info.quality_gate.ai_code_assurance.description" />
+ </StyledMutedText>
+ )}
</div>
);
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { queryOptions } from '@tanstack/react-query';
+import { isProjectAiCodeAssured } from '../api/ai-code-assurance';
+import { createQueryHook } from './common';
+
+export const AI_CODE_ASSURANCE_QUERY_PREFIX = 'ai-code-assurance';
+
+export const useProjectAiCodeAssuredQuery = createQueryHook(({ project }: { project: string }) => {
+ return queryOptions({
+ queryKey: [AI_CODE_ASSURANCE_QUERY_PREFIX, project], // - or _ ?
+ queryFn: ({ queryKey: [_, project] }) => isProjectAiCodeAssured(project),
+ enabled: project !== undefined,
+ });
+});
admin=Admin
after=After
ai_code=AI Code
+ai_code_assurance=AI CODE ASSURANCE
apply=Apply
all=All
and=And
application.info.make_home.tooltip=This means you'll be redirected to this application whenever you log in to SonarQube or click on the top-left SonarQube logo.
overview.project_key.tooltip.TRK=Your project key is a unique identifier for your project. If you are using Maven, make sure the key matches the "groupId:artifactId" format.
overview.project_key.tooltip.APP=Your application key is a unique identifier for your application.
+project.info.ai_code_assurance.title=AI Code Assurance
+project.info.quality_gate.ai_code_assurance.description=This project contains AI-generated code. It must use Sonar way Quality Gate to benefit from Sonar’s AI Code Assurance.
+
#------------------------------------------------------------------------------
#