* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { LinkStandalone } from '@sonarsource/echoes-react';
import {
ButtonPrimary,
FlagMessage,
import { OptionProps, components } from 'react-select';
import A11ySkipTarget from '~sonar-aligned/components/a11y/A11ySkipTarget';
import HelpTooltip from '~sonar-aligned/components/controls/HelpTooltip';
+import withAvailableFeatures, {
+ WithAvailableFeaturesProps,
+} from '../../app/components/available-features/withAvailableFeatures';
import DisableableSelectOption from '../../components/common/DisableableSelectOption';
+import DocumentationLink from '../../components/common/DocumentationLink';
import Suggestions from '../../components/embed-docs-modal/Suggestions';
import { DocLink } from '../../helpers/doc-links';
import { translate } from '../../helpers/l10n';
import { isDiffMetric } from '../../helpers/measures';
import { LabelValueSelectOption } from '../../helpers/search';
import { getQualityGateUrl } from '../../helpers/urls';
-import { QualityGate } from '../../types/types';
+import { useProjectAiCodeAssuredQuery } from '../../queries/ai-code-assurance';
+import { useLocation } from '../../sonar-aligned/components/hoc/withRouter';
+import { queryToSearchString } from '../../sonar-aligned/helpers/urls';
+import { ComponentQualifier } from '../../sonar-aligned/types/component';
+import { Feature } from '../../types/features';
+import { Component, QualityGate } from '../../types/types';
import BuiltInQualityGateBadge from '../quality-gates/components/BuiltInQualityGateBadge';
import { USE_SYSTEM_DEFAULT } from './constants';
-export interface ProjectQualityGateAppRendererProps {
+export interface ProjectQualityGateAppRendererProps extends WithAvailableFeaturesProps {
allQualityGates?: QualityGate[];
+ component: Component;
currentQualityGate?: QualityGate;
loading: boolean;
onSelect: (id: string) => void;
);
}
-export default function ProjectQualityGateAppRenderer(props: ProjectQualityGateAppRendererProps) {
- const { allQualityGates, currentQualityGate, loading, selectedQualityGateName, submitting } =
- props;
+function ProjectQualityGateAppRenderer(props: Readonly<ProjectQualityGateAppRendererProps>) {
+ const {
+ allQualityGates,
+ component,
+ currentQualityGate,
+ loading,
+ selectedQualityGateName,
+ submitting,
+ } = props;
const defaultQualityGate = allQualityGates?.find((g) => g.isDefault);
+ const location = useLocation();
+
+ const { data: isAiAssured } = useProjectAiCodeAssuredQuery(
+ { project: component.key },
+ {
+ enabled:
+ component.qualifier === ComponentQualifier.Project &&
+ props.hasFeature(Feature.AiCodeAssurance),
+ },
+ );
+
if (loading) {
return <Spinner />;
}
<RadioButton
className="it__project-quality-default sw-items-start"
checked={usesDefault}
- disabled={submitting}
+ disabled={submitting || isAiAssured}
onCheck={() => props.onSelect(USE_SYSTEM_DEFAULT)}
value={USE_SYSTEM_DEFAULT}
>
<RadioButton
className="it__project-quality-specific sw-items-start sw-mt-1"
checked={!usesDefault}
- disabled={submitting}
+ disabled={submitting || isAiAssured}
onCheck={(value: string) => {
if (usesDefault) {
props.onSelect(value);
Option: renderQualitygateOption,
}}
isClearable={usesDefault}
- isDisabled={submitting || usesDefault}
+ isDisabled={submitting || usesDefault || isAiAssured}
onChange={({ value }: QualityGateOption) => {
props.onSelect(value);
}}
/>
</div>
+ {isAiAssured && (
+ <>
+ <p className="sw-w-abs-400 sw-mt-6">
+ <FormattedMessage
+ id="project_quality_gate.ai_assured.message1"
+ defaultMessage={translate('project_quality_gate.ai_assured.message1')}
+ values={{
+ link: (
+ <DocumentationLink to={DocLink.AiCodeAssurance}>
+ {translate('project_quality_gate.ai_assured.message1.link')}
+ </DocumentationLink>
+ ),
+ }}
+ />
+ </p>
+ <p className="sw-w-abs-400 sw-mt-6">
+ <FormattedMessage
+ id="project_quality_gate.ai_assured.message2"
+ defaultMessage={translate('project_quality_gate.ai_assured.message2')}
+ values={{
+ link: (
+ <LinkStandalone
+ className="sw-shrink-0"
+ to={{
+ pathname:
+ '/project/admin/extension/developer-server/ai-project-settings',
+ search: queryToSearchString({
+ ...location.query,
+ qualifier: ComponentQualifier.Project,
+ }),
+ }}
+ >
+ {translate('project_quality_gate.ai_assured.message2.link')}
+ </LinkStandalone>
+ ),
+ value: <b>{translate('false')}</b>,
+ }}
+ />
+ </p>
+ </>
+ )}
+
{selectedQualityGate && !hasConditionOnNewCode(selectedQualityGate) && (
<FlagMessage variant="warning">
<FormattedMessage
</div>
<div>
- <ButtonPrimary form="project_quality_gate" disabled={submitting} type="submit">
+ <ButtonPrimary
+ form="project_quality_gate"
+ disabled={submitting || isAiAssured}
+ type="submit"
+ >
{translate('save')}
</ButtonPrimary>
<Spinner loading={submitting} />
</LargeCenteredLayout>
);
}
+
+export default withAvailableFeatures(ProjectQualityGateAppRenderer);
RenderContext,
renderAppWithComponentContext,
} from '../../../helpers/testReactTestingUtils';
+import { Feature } from '../../../types/features';
import { Component } from '../../../types/types';
import routes from '../routes';
addGlobalSuccessMessage: jest.fn(),
}));
+jest.mock('../../../api/ai-code-assurance', () => ({
+ isProjectAiCodeAssured: jest.fn().mockResolvedValue(true),
+}));
+
let handler: QualityGatesServiceMock;
const ui = {
saveButton: byRole('button', { name: 'save' }),
noConditionsNewCodeWarning: byText('project_quality_gate.no_condition_on_new_code'),
+ aiCodeAssuranceMessage1: byText('project_quality_gate.ai_assured.message1'),
+ aiCodeAssuranceMessage2: byText('project_quality_gate.ai_assured.message2'),
};
beforeAll(() => {
expect(ui.noConditionsNewCodeWarning.get()).toBeInTheDocument();
});
+it('disable the QG selection if project is AI assured', async () => {
+ renderProjectQualityGateApp({ featureList: [Feature.AiCodeAssurance] });
+
+ expect(await ui.aiCodeAssuranceMessage1.find()).toBeInTheDocument();
+ expect(ui.aiCodeAssuranceMessage2.get()).toBeInTheDocument();
+ expect(ui.specificRadioQualityGate.get()).toBeDisabled();
+ expect(ui.defaultRadioQualityGate.get()).toBeDisabled();
+ expect(ui.qualityGatesSelect.get()).toBeDisabled();
+ expect(ui.saveButton.get()).toBeDisabled();
+});
+
it('renders nothing and shows alert when any API fails', async () => {
handler.setThrowOnGetGateForProject(true);
renderProjectQualityGateApp();