From 72bd306ed82ab8746a0c306b48b48a7ebe811f11 Mon Sep 17 00:00:00 2001 From: Ismail Cherri Date: Fri, 25 Oct 2024 16:34:47 +0200 Subject: [PATCH] SONAR-23299 Add Quality Gate update icon when condition for other mode exist --- .../js/api/mocks/QualityGatesServiceMock.ts | 4 + .../components/AIGeneratedIcon.tsx | 18 ++- .../js/apps/quality-gates/components/List.tsx | 133 +++++++++++------- .../components/QGRecommendedIcon.tsx | 18 ++- .../components/__tests__/QualityGate-it.tsx | 28 +++- server/sonar-web/src/main/js/types/types.ts | 2 + .../resources/org/sonar/l10n/core.properties | 1 + 7 files changed, 141 insertions(+), 63 deletions(-) diff --git a/server/sonar-web/src/main/js/api/mocks/QualityGatesServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/QualityGatesServiceMock.ts index 4046cc56883..93e97f60996 100644 --- a/server/sonar-web/src/main/js/api/mocks/QualityGatesServiceMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/QualityGatesServiceMock.ts @@ -183,6 +183,8 @@ export class QualityGatesServiceMock { isDefault: false, isBuiltIn: true, caycStatus: CaycStatus.Compliant, + hasStandardConditions: true, + hasMQRConditions: false, }), mockQualityGate({ name: 'Non Cayc QG', @@ -194,6 +196,8 @@ export class QualityGatesServiceMock { isDefault: false, isBuiltIn: false, caycStatus: CaycStatus.NonCompliant, + hasStandardConditions: false, + hasMQRConditions: true, }), mockQualityGate({ name: 'Non Cayc Compliant QG', diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/AIGeneratedIcon.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/AIGeneratedIcon.tsx index a1dae6141b4..139595c87dc 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/AIGeneratedIcon.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/AIGeneratedIcon.tsx @@ -18,12 +18,18 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import styled from '@emotion/styled'; import { IconSparkle } from '@sonarsource/echoes-react'; -import { themeColor } from '~design-system'; -const AIGeneratedIcon = styled(IconSparkle)` - color: ${themeColor('primary')}; -`; +interface Props { + className?: string; + isDisabled?: boolean; +} -export default AIGeneratedIcon; +export default function AIGeneratedIcon({ isDisabled = false, className }: Readonly) { + return ( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/List.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/List.tsx index 715a5375c2b..d1b3cff60fa 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/List.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/List.tsx @@ -18,12 +18,13 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Tooltip } from '@sonarsource/echoes-react'; +import { IconRefresh, Spinner, Tooltip } from '@sonarsource/echoes-react'; import { useNavigate } from 'react-router-dom'; import { Badge, BareButton, SubnavigationGroup, SubnavigationItem } from '~design-system'; import { useAvailableFeatures } from '../../../app/components/available-features/withAvailableFeatures'; import { translate } from '../../../helpers/l10n'; import { getQualityGateUrl } from '../../../helpers/urls'; +import { useStandardExperienceMode } from '../../../queries/settings'; import { Feature } from '../../../types/features'; import { CaycStatus, QualityGate } from '../../../types/types'; import AIGeneratedIcon from './AIGeneratedIcon'; @@ -35,62 +36,96 @@ interface Props { qualityGates: QualityGate[]; } -export default function List({ qualityGates, currentQualityGate }: Props) { +export default function List({ qualityGates, currentQualityGate }: Readonly) { const navigateTo = useNavigate(); const { hasFeature } = useAvailableFeatures(); + const { data: isStandard, isLoading } = useStandardExperienceMode(); return ( - {qualityGates.map(({ isDefault, isBuiltIn, name, caycStatus }) => { - const isDefaultTitle = isDefault ? ` ${translate('default')}` : ''; - const isBuiltInTitle = isBuiltIn ? ` ${translate('quality_gates.built_in')}` : ''; - const isAICodeAssuranceQualityGate = - hasFeature(Feature.AiCodeAssurance) && isBuiltIn && name === 'Sonar way'; + {qualityGates.map( + ({ + isDefault, + isBuiltIn, + name, + caycStatus, + hasMQRConditions, + hasStandardConditions, + actions, + }) => { + const isDefaultTitle = isDefault ? ` ${translate('default')}` : ''; + const isBuiltInTitle = isBuiltIn ? ` ${translate('quality_gates.built_in')}` : ''; + const isAICodeAssuranceQualityGate = + hasFeature(Feature.AiCodeAssurance) && isBuiltIn && name === 'Sonar way'; + const shouldShowQualityGateUpdateIcon = + actions?.manageConditions === true && + ((isStandard && hasMQRConditions === true) || + (!isStandard && hasStandardConditions === true)); - return ( - { - navigateTo(getQualityGateUrl(name)); - }} - > -
- - {name} - + return ( + { + navigateTo(getQualityGateUrl(name)); + }} + > +
+ + {name} + - {(isDefault || isBuiltIn) && ( -
- {isDefault && {translate('default')}} - {isBuiltIn && } + {(isDefault || isBuiltIn) && ( +
+ {isDefault && {translate('default')}} + {isBuiltIn && } +
+ )} +
+ +
+ {shouldShowQualityGateUpdateIcon && ( + + + + + + )} + + {isAICodeAssuranceQualityGate && ( + + + + + + )} + {caycStatus !== CaycStatus.NonCompliant && ( + + + + + + )}
- )} -
-
- {isAICodeAssuranceQualityGate && ( - - - - - - )} - {caycStatus !== CaycStatus.NonCompliant && ( - - - - - - )} -
-
- ); - })} + + + ); + }, + )} ); } diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/QGRecommendedIcon.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/QGRecommendedIcon.tsx index 4d7153e0865..9451400630c 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/QGRecommendedIcon.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/QGRecommendedIcon.tsx @@ -18,12 +18,18 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import styled from '@emotion/styled'; import { IconRecommended } from '@sonarsource/echoes-react'; -import { themeColor } from '~design-system'; -const QGRecommendedIcon = styled(IconRecommended)` - color: ${themeColor('primary')}; -`; +interface Props { + className?: string; + isDisabled?: boolean; +} -export default QGRecommendedIcon; +export default function QGRecommendedIcon({ isDisabled = false, className }: Readonly) { + return ( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx index e4791e4ca7c..1798c704ddc 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx @@ -22,27 +22,32 @@ import { screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { byLabelText, byRole, byTestId } from '~sonar-aligned/helpers/testSelector'; import { QualityGatesServiceMock } from '../../../../api/mocks/QualityGatesServiceMock'; +import SettingsServiceMock from '../../../../api/mocks/SettingsServiceMock'; import UsersServiceMock from '../../../../api/mocks/UsersServiceMock'; import { searchProjects, searchUsers } from '../../../../api/quality-gates'; import { dismissNotice } from '../../../../api/users'; import { mockLoggedInUser } from '../../../../helpers/testMocks'; -import { RenderContext, renderAppRoutes } from '../../../../helpers/testReactTestingUtils'; +import { renderAppRoutes, RenderContext } from '../../../../helpers/testReactTestingUtils'; import { Feature } from '../../../../types/features'; +import { SettingsKey } from '../../../../types/settings'; import { CaycStatus } from '../../../../types/types'; import { NoticeType } from '../../../../types/users'; import routes from '../../routes'; let qualityGateHandler: QualityGatesServiceMock; let usersHandler: UsersServiceMock; +let settingsHandler: SettingsServiceMock; beforeAll(() => { qualityGateHandler = new QualityGatesServiceMock(); usersHandler = new UsersServiceMock(); + settingsHandler = new SettingsServiceMock(); }); afterEach(() => { qualityGateHandler.reset(); usersHandler.reset(); + settingsHandler.reset(); }); it('should open the default quality gates', async () => { @@ -73,6 +78,25 @@ it('should list all quality gates', async () => { ).toBeInTheDocument(); }); +it('should show MQR mode update icon if standard mode conditions are present', async () => { + qualityGateHandler.setIsAdmin(true); + renderQualityGateApp(); + + expect( + await screen.findByTestId('quality-gates-mqr-standard-mode-update-indicator'), + ).toBeInTheDocument(); +}); + +it('should show Standard mode update icon if MQR mode conditions are present', async () => { + settingsHandler.set(SettingsKey.MQRMode, 'false'); + qualityGateHandler.setIsAdmin(true); + renderQualityGateApp(); + + expect( + await screen.findByTestId('quality-gates-mqr-standard-mode-update-indicator'), + ).toBeInTheDocument(); +}); + it('should render the built-in quality gate properly', async () => { const user = userEvent.setup(); renderQualityGateApp(); @@ -869,7 +893,7 @@ describe('The Permissions section', () => { }); it('should handle searchUser service failure', async () => { - (searchUsers as jest.Mock).mockRejectedValue('error'); + jest.mocked(searchUsers).mockRejectedValue('error'); const user = userEvent.setup(); qualityGateHandler.setIsAdmin(true); diff --git a/server/sonar-web/src/main/js/types/types.ts b/server/sonar-web/src/main/js/types/types.ts index bd13aa9c219..36cd407a64b 100644 --- a/server/sonar-web/src/main/js/types/types.ts +++ b/server/sonar-web/src/main/js/types/types.ts @@ -482,6 +482,8 @@ export interface QualityGate extends QualityGatePreview { }; caycStatus?: CaycStatus; conditions?: Condition[]; + hasMQRConditions?: boolean; + hasStandardConditions?: boolean; isBuiltIn?: boolean; } 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 f3a4d75f383..e0abaec5dc3 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -2553,6 +2553,7 @@ quality_gates.cayc.review_update_modal.modify_condition.header= {0} condition(s) quality_gates.ai_generated.tootltip.message=Sonar way ensures clean AI-generated code quality_gates.ai_generated.description=Sonar way ensures {link} quality_gates.ai_generated.description.clean_ai_generated_code=clean AI-generated code +quality_gates.mqr_mode_update.tooltip.message=Update the metrics of this quality gate #------------------------------------------------------------------------------ # -- 2.39.5