From: Viktor Vorona Date: Mon, 28 Oct 2024 15:31:30 +0000 (+0100) Subject: SONAR-23299 Quality Gate page shows conditions from other modes X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=9c8223cb2a2ec9c312c1c96c332de3fca08a143b;p=sonarqube.git SONAR-23299 Quality Gate page shows conditions from other modes --- diff --git a/server/sonar-web/__mocks__/react-intl.tsx b/server/sonar-web/__mocks__/react-intl.tsx index f8a2a34cc47..f68d0c36c3f 100644 --- a/server/sonar-web/__mocks__/react-intl.tsx +++ b/server/sonar-web/__mocks__/react-intl.tsx @@ -29,7 +29,9 @@ module.exports = { <> {id} {Object.entries(values).map(([key, value]) => ( - {value} + + {typeof value === 'function' ? value() : value} + ))} ); @@ -38,13 +40,21 @@ module.exports = { }, formatDate: jest.fn().mockReturnValue(''), }), - FormattedMessage: ({ id, values }: { id: string; values?: { [x: string]: React.ReactNode } }) => { + FormattedMessage: ({ + id, + values, + }: { + id: string; + values?: { [x: string]: React.ReactNode | (() => React.ReactNode) }; + }) => { return ( <> {id} {values !== undefined && Object.entries(values).map(([key, value]) => ( - {value} + + {typeof value === 'function' ? value() : value} + ))} ); 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 93e97f60996..d32c988dd19 100644 --- a/server/sonar-web/src/main/js/api/mocks/QualityGatesServiceMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/QualityGatesServiceMock.ts @@ -21,6 +21,7 @@ import { cloneDeep, flatten, omit, remove } from 'lodash'; import { MetricKey } from '~sonar-aligned/types/metrics'; import { Project } from '../../apps/quality-gates/components/Projects'; +import { MQR_CONDITIONS_MAP, STANDARD_CONDITIONS_MAP } from '../../apps/quality-gates/utils'; import { mockQualityGate, mockQualityGateApplicationStatus, @@ -119,6 +120,8 @@ export class QualityGatesServiceMock { error: '3', }, ], + hasStandardConditions: true, + hasMQRConditions: false, isDefault: true, isBuiltIn: false, caycStatus: CaycStatus.Compliant, @@ -140,19 +143,31 @@ export class QualityGatesServiceMock { error: '0', isCaycCondition: true, }, - { id: 'AXJMbIUHPAOIsUIE3eNs', metric: 'new_security_rating', op: 'GT', error: '1' }, - { id: 'AXJMbIUHPAOIsUIE3eNy', metric: 'new_security_rating', op: 'GT', error: '0' }, + { + id: 'AXJMbIUHPAOIsUIE3eOl', + metric: MetricKey.new_software_quality_security_rating, + op: 'GT', + error: '1', + }, + { + id: 'AXJMbIUHPAOIsUIE3eOd', + metric: MetricKey.new_software_quality_security_rating, + op: 'GT', + error: '0', + }, { id: 'deprecated', metric: 'function_complexity', op: 'LT', error: '1' }, ], isDefault: false, isBuiltIn: false, + hasStandardConditions: false, + hasMQRConditions: true, caycStatus: CaycStatus.NonCompliant, }), mockQualityGate({ name: 'Sonar way', conditions: [ { - id: 'AXJMbIUHPAOIsUIE3eNs', + id: 'AXJMbIUHPAOIsUIE3eQQ', metric: 'new_violations', op: 'GT', error: '0', @@ -182,16 +197,26 @@ export class QualityGatesServiceMock { ], isDefault: false, isBuiltIn: true, - caycStatus: CaycStatus.Compliant, - hasStandardConditions: true, + hasStandardConditions: false, hasMQRConditions: false, + caycStatus: CaycStatus.Compliant, }), mockQualityGate({ name: 'Non Cayc QG', conditions: [ - { id: 'AXJMbIUHPAOIsUIE3eNs', metric: 'new_security_rating', op: 'GT', error: '1' }, - { id: 'AXJMbIUHPAOIsUIE3eOD', metric: 'new_reliability_rating', op: 'GT', error: '1' }, - { id: 'AXJMbIUHPAOIsUIE3eOF', metric: 'new_coverage', op: 'LT', error: '80' }, + { + id: 'AXJMbIUHPAOIsUIE3eCC', + metric: MetricKey.new_software_quality_security_rating, + op: 'LT', + error: '80', + }, + { + id: 'AXJMbIUHPAOIsUIE3eOD', + metric: MetricKey.new_software_quality_reliability_rating, + op: 'LT', + error: '80', + }, + { id: 'AXJMbIUHPAOIsUIE3eOA', metric: MetricKey.new_coverage, op: 'LT', error: '80' }, ], isDefault: false, isBuiltIn: false, @@ -202,12 +227,24 @@ export class QualityGatesServiceMock { mockQualityGate({ name: 'Non Cayc Compliant QG', conditions: [ - { id: 'AXJMbIUHPAOIsUIE3eNs', metric: 'new_security_rating', op: 'GT', error: '1' }, - { id: 'AXJMbIUHPAOIsUIE3eOD', metric: 'new_reliability_rating', op: 'GT', error: '1' }, - { id: 'AXJMbIUHPAOIsUIE3eOF', metric: 'new_coverage', op: 'LT', error: '80' }, + { + id: 'AXJMbIUHPAOIsUIE3eDD', + metric: MetricKey.new_software_quality_security_rating, + op: 'GT', + error: '1', + }, + { + id: 'AXJMbIUHPAOIsUIE3eDA', + metric: MetricKey.new_software_quality_reliability_rating, + op: 'GT', + error: '1', + }, + { id: 'AXJMbIUHPAOIsUIE3eDK', metric: MetricKey.new_coverage, op: 'LT', error: '80' }, ], isDefault: false, isBuiltIn: false, + hasStandardConditions: false, + hasMQRConditions: true, caycStatus: CaycStatus.Compliant, }), mockQualityGate({ @@ -239,6 +276,8 @@ export class QualityGatesServiceMock { ], isDefault: false, isBuiltIn: false, + hasStandardConditions: true, + hasMQRConditions: false, caycStatus: CaycStatus.OverCompliant, }), mockQualityGate({ @@ -246,15 +285,53 @@ export class QualityGatesServiceMock { conditions: [], isDefault: false, isBuiltIn: false, + hasStandardConditions: false, + hasMQRConditions: false, caycStatus: CaycStatus.NonCompliant, }), mockQualityGate({ name: 'QG without new code conditions', conditions: [ - { id: 'AXJMbIUHPAOIsUIE3eNs', metric: 'security_rating', op: 'GT', error: '1' }, + { id: 'AXJMbIUHPAOIsUIE3eAA', metric: 'security_rating', op: 'GT', error: '1' }, ], isDefault: false, isBuiltIn: false, + hasStandardConditions: true, + hasMQRConditions: false, + caycStatus: CaycStatus.NonCompliant, + }), + mockQualityGate({ + name: 'QG with MQR conditions', + conditions: [ + { + id: 'AXJMbIUHPAOIsUIE3eWW', + metric: MetricKey.software_quality_security_rating, + op: 'GT', + error: '1', + }, + { + id: 'AXJMbIUHPAOIsUIE3eW1', + metric: MetricKey.new_software_quality_blocker_issues, + op: 'GT', + error: '1', + }, + { + id: 'AXJMbIUHPAOIsUIE3eW2', + metric: MetricKey.new_software_quality_high_issues, + op: 'GT', + error: '1', + }, + { + id: 'AXJMbIUHPAOIsUIE3eW3', + metric: MetricKey.high_impact_accepted_issues, + op: 'GT', + error: '1', + }, + ], + isDefault: false, + isBuiltIn: false, + hasStandardConditions: false, + hasMQRConditions: true, caycStatus: CaycStatus.NonCompliant, }), ]; @@ -469,6 +546,10 @@ export class QualityGatesServiceMock { conditions.push(newCondition); qg.conditions = conditions; + qg.hasMQRConditions = + qg.hasMQRConditions || MQR_CONDITIONS_MAP[metric as MetricKey] !== undefined; + qg.hasStandardConditions = + qg.hasStandardConditions || STANDARD_CONDITIONS_MAP[metric as MetricKey] !== undefined; return this.reply(newCondition); }; @@ -483,6 +564,17 @@ export class QualityGatesServiceMock { condition.error = error; condition.isCaycCondition = isCaycCondition; + const qg = this.list.find((qg) => qg.conditions?.find((c) => c.id === id)); + + if (qg) { + qg.hasMQRConditions = + qg.conditions?.some((c) => MQR_CONDITIONS_MAP[c.metric as MetricKey] !== undefined) || + false; + qg.hasStandardConditions = + qg.conditions?.some((c) => STANDARD_CONDITIONS_MAP[c.metric as MetricKey] !== undefined) || + false; + } + return this.reply(condition); }; @@ -490,6 +582,18 @@ export class QualityGatesServiceMock { this.list.forEach((q) => { remove(q.conditions || [], (c) => c.id === id); }); + + const qg = this.list.find((qg) => qg.conditions?.find((c) => c.id === id)); + + if (qg) { + qg.hasMQRConditions = + qg.conditions?.some((c) => MQR_CONDITIONS_MAP[c.metric as MetricKey] !== undefined) || + false; + qg.hasStandardConditions = + qg.conditions?.some((c) => STANDARD_CONDITIONS_MAP[c.metric as MetricKey] !== undefined) || + false; + } + return Promise.resolve(); }; diff --git a/server/sonar-web/src/main/js/apps/component-measures/__tests__/ComponentMeasures-it.tsx b/server/sonar-web/src/main/js/apps/component-measures/__tests__/ComponentMeasures-it.tsx index 03805f6a7ea..6e7255c6f67 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/__tests__/ComponentMeasures-it.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/__tests__/ComponentMeasures-it.tsx @@ -94,14 +94,14 @@ describe('rendering', () => { await user.click(ui.maintainabilityDomainBtn.get()); [ 'component_measures.metric.new_maintainability_issues.name 5', - 'Software Quality Maintainability Remediation Effort on new code work_duration.x_minutes.1', - 'Software Quality Technical Debt Ratio on New Code 1.0%', - 'Software Quality Maintainability Rating on New Code metric.has_rating_X.D metric.software_quality_maintainability_rating.tooltip.D.0.0%', + 'Added Technical Debt work_duration.x_minutes.1', + 'Technical Debt Ratio on New Code 1.0%', + 'Maintainability Rating on New Code metric.has_rating_X.D metric.software_quality_maintainability_rating.tooltip.D.0.0%', 'component_measures.metric.maintainability_issues.name 2', - 'Software Quality Maintainability Remediation Effort work_duration.x_minutes.1', - 'Software Quality Technical Debt Ratio 1.0%', - 'Software Quality Maintainability Rating metric.has_rating_X.D metric.software_quality_maintainability_rating.tooltip.D.0.0%', - 'Software Quality Effort to Reach Maintainability Rating A work_duration.x_minutes.1', + 'Technical Debt work_duration.x_minutes.1', + 'Technical Debt Ratio 1.0%', + 'Maintainability Rating metric.has_rating_X.D metric.software_quality_maintainability_rating.tooltip.D.0.0%', + 'Effort to Reach Maintainability Rating A work_duration.x_minutes.1', ].forEach((measure) => { expect(ui.measureLink(measure).get()).toBeInTheDocument(); }); @@ -539,7 +539,7 @@ describe('navigation', () => { await user.click( ui .measureLink( - 'Software Quality Maintainability Rating metric.has_rating_X.D metric.software_quality_maintainability_rating.tooltip.D.0.0%', + 'Maintainability Rating metric.has_rating_X.D metric.software_quality_maintainability_rating.tooltip.D.0.0%', ) .get(), ); diff --git a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/ProjectLinksApp-it.tsx b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/ProjectLinksApp-it.tsx index b3b3fbf9c80..2bd8326b835 100644 --- a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/ProjectLinksApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/ProjectLinksApp-it.tsx @@ -41,7 +41,7 @@ it('renders project links app and can do CRUD operations', async () => { renderProjectLinksApp(); await ui.appIsLoaded(); - expect(ui.noResultsTable.get()).toBeInTheDocument(); + expect(await ui.noResultsTable.find()).toBeInTheDocument(); // Create link await ui.createLink(newLinkName1, 'https://link.com'); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/Condition.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/Condition.tsx index 0716b3bbde6..1ce1c2a71d6 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/Condition.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/Condition.tsx @@ -24,22 +24,37 @@ import { ButtonSize, ButtonVariety, IconDelete, + IconRefresh, ModalAlert, } from '@sonarsource/echoes-react'; -import { ActionCell, ContentCell, NumericalCell, TableRow, TextError } from '~design-system'; +import { useIntl } from 'react-intl'; +import { + ActionCell, + ContentCell, + NumericalCell, + Pill, + PillHighlight, + PillVariant, + TableRow, + TextError, +} from '~design-system'; import { useMetrics } from '../../../app/components/metrics/withMetricsContext'; import { getLocalizedMetricName, translate, translateWithParameters } from '../../../helpers/l10n'; import { getOperatorLabel } from '../../../helpers/qualityGates'; import { useDeleteConditionMutation } from '../../../queries/quality-gates'; +import { useStandardExperienceMode } from '../../../queries/settings'; import { MetricKey } from '../../../sonar-aligned/types/metrics'; import { CaycStatus, Condition as ConditionType, Metric, QualityGate } from '../../../types/types'; import { getLocalizedMetricNameNoDiffMetric, isConditionWithFixedValue, isNonEditableMetric, + MQR_CONDITIONS_MAP, + STANDARD_CONDITIONS_MAP, } from '../utils'; import ConditionValue from './ConditionValue'; import EditConditionModal from './EditConditionModal'; +import UpdateConditionsFromOtherModeModal from './UpdateConditionsFromOtherModeModal'; export enum ConditionChange { Added = 'added', @@ -66,14 +81,26 @@ export default function ConditionComponent({ }: Readonly) { const { mutateAsync: deleteCondition } = useDeleteConditionMutation(qualityGate.name); const metrics = useMetrics(); + const intl = useIntl(); + const { data: isStandard } = useStandardExperienceMode(); const { op = 'GT' } = condition; const isCaycCompliantAndOverCompliant = qualityGate.caycStatus !== CaycStatus.NonCompliant; + const isMetricFromOtherMode = isStandard + ? MQR_CONDITIONS_MAP[condition.metric as MetricKey] !== undefined + : STANDARD_CONDITIONS_MAP[condition.metric as MetricKey] !== undefined; return ( {getLocalizedMetricNameNoDiffMetric(metric, metrics)} + {isMetricFromOtherMode && canEdit && ( + + {intl.formatMessage({ + id: `quality_gates.metric.${isStandard ? 'mqr' : 'standard'}_mode_short`, + })} + + )} {metric.hidden && } @@ -90,10 +117,32 @@ export default function ConditionComponent({ {!isCaycModal && canEdit && ( <> + {isMetricFromOtherMode && ( + + + + )} {(!isCaycCompliantAndOverCompliant || !isConditionWithFixedValue(condition) || (isCaycCompliantAndOverCompliant && showEdit)) && - !isNonEditableMetric(condition.metric as MetricKey) && ( + !isNonEditableMetric(condition.metric as MetricKey) && + !isMetricFromOtherMode && ( ) ); const conditionsToOtherModeMap = isStandardMode ? MQR_CONDITIONS_MAP : STANDARD_CONDITIONS_MAP; + const hasConditionsFromOtherMode = + qualityGate[isStandardMode ? 'hasMQRConditions' : 'hasStandardConditions']; return ( @@ -166,33 +168,27 @@ export default function Conditions({ qualityGate, isFetching }: Readonly) )} - {isCompliantCustomQualityGate && !isOptimizing && } - {isCompliantCustomQualityGate && isOptimizing && canEdit && ( + {(!hasConditionsFromOtherMode || !canEdit) && + isCompliantCustomQualityGate && + !isOptimizing && } + {!hasConditionsFromOtherMode && isCompliantCustomQualityGate && isOptimizing && canEdit && ( )} - {caycStatus === CaycStatus.NonCompliant && canEdit && ( + {!hasConditionsFromOtherMode && caycStatus === CaycStatus.NonCompliant && canEdit && ( )} - conditionsToOtherModeMap[c.metric as MetricKey] !== undefined, - )} - overallCodeConditions={overallCodeConditions.filter( - (c) => conditionsToOtherModeMap[c.metric as MetricKey] !== undefined, - )} - > - {/* TODO test example */} - - - c.metric.includes('rating'))!} - > - {/* TODO test example */} - - + {hasConditionsFromOtherMode && canEdit && ( + conditionsToOtherModeMap[c.metric as MetricKey] !== undefined, + )} + overallCodeConditions={overallCodeConditions.filter( + (c) => conditionsToOtherModeMap[c.metric as MetricKey] !== undefined, + )} + /> + )} +
@@ -270,9 +266,16 @@ export default function Conditions({ qualityGate, isFetching }: Readonly) {newCodeConditions.length > 0 && (
- - {translate('quality_gates.conditions.new_code', 'long')} - +
+ + {translate('quality_gates.conditions.new_code', 'long')} + + {hasFeature(Feature.BranchSupport) && ( + + {translate('quality_gates.conditions.new_code', 'description')} + + )} +
) showEdit={editing} scope="new" /> - - {hasFeature(Feature.BranchSupport) && ( - - {translate('quality_gates.conditions.new_code', 'description')} - - )}
)} {overallCodeConditions.length > 0 && (
- - {translate('quality_gates.conditions.overall_code', 'long')} - +
+ + {translate('quality_gates.conditions.overall_code', 'long')} + + {hasFeature(Feature.BranchSupport) && ( + + {translate('quality_gates.conditions.overall_code', 'description')} + + )} +
) conditions={overallCodeConditions} scope="overall" /> - - {hasFeature(Feature.BranchSupport) && ( - - {translate('quality_gates.conditions.overall_code', 'description')} - - )}
)}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionsTable.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionsTable.tsx index b0d5bb9565e..72b521d0614 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionsTable.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionsTable.tsx @@ -68,7 +68,7 @@ export default function ConditionsTable({ return ( } data-test={`quality-gates__conditions-${scope}`} 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 d1b3cff60fa..e5707e03241 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 @@ -57,6 +57,7 @@ export default function List({ qualityGates, currentQualityGate }: Readonly) { + const { data: isStandard } = useStandardExperienceMode(); + const intl = useIntl(); + return ( + + + {intl.formatMessage( + { id: 'quality_gates.mode_banner.title' }, + { + mode: intl.formatMessage({ + id: `settings.mode.${isStandard ? 'standard' : 'mqr'}.name`, + }), + }, + )} + +
+ + {intl.formatMessage({ + id: `settings.mode.${isStandard ? 'standard' : 'mqr'}.name`, + })} + + ), + otherMode: intl.formatMessage({ + id: `settings.mode.${isStandard ? 'mqr' : 'standard'}.name`, + }), + }} + /> +
+ + + +
+ ); +} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/UpdateConditionsFromOtherModeModal.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/UpdateConditionsFromOtherModeModal.tsx index 50775c76dda..5d876ba848a 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/UpdateConditionsFromOtherModeModal.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/UpdateConditionsFromOtherModeModal.tsx @@ -50,13 +50,11 @@ import ConditionValue from './ConditionValue'; type Props = React.PropsWithChildren & { qualityGateName: string } & ( | { condition?: never; - isSingleMetric?: false; newCodeConditions: Condition[]; overallCodeConditions: Condition[]; } | { condition: Condition; - isSingleMetric: true; newCodeConditions?: never; overallCodeConditions?: never; } @@ -66,7 +64,6 @@ export default function UpdateConditionsFromOtherModeModal({ newCodeConditions, overallCodeConditions, qualityGateName, - isSingleMetric, condition, children, }: Readonly) { @@ -75,6 +72,7 @@ export default function UpdateConditionsFromOtherModeModal({ const [error, setError] = React.useState(false); const intl = useIntl(); const mapper = isStandard ? MQR_CONDITIONS_MAP : STANDARD_CONDITIONS_MAP; + const isSingleMetric = !!condition; const { mutate: updateConditions, isPending } = useUpdateOrDeleteConditionsMutation( qualityGateName, isSingleMetric, @@ -174,6 +172,7 @@ export default function UpdateConditionsFromOtherModeModal({ {intl.formatMessage({ id: 'overview.new_code' })}
) {
{intl.formatMessage({ - id: `quality_gates.update_conditions.${isStandard ? 'mqr' : 'standard'}_mode_header`, + id: `quality_gates.metric.${isStandard ? 'mqr' : 'standard'}_mode_long`, })} @@ -238,7 +238,7 @@ function SingleMetric({ condition }: Readonly<{ condition: Condition }>) { <> {intl.formatMessage({ - id: `quality_gates.update_conditions.${isStandard ? 'standard' : 'mqr'}_mode_header`, + id: `quality_gates.metric.${isStandard ? 'standard' : 'mqr'}_mode_long`, })} @@ -246,7 +246,7 @@ function SingleMetric({ condition }: Readonly<{ condition: Condition }>) { ) : ( - + {intl.formatMessage({ id: 'quality_gates.update_conditions.removed' })} )} @@ -264,7 +264,7 @@ function Header() { {intl.formatMessage({ - id: `quality_gates.update_conditions.${isStandard ? 'mqr' : 'standard'}_mode_header`, + id: `quality_gates.metric.${isStandard ? 'mqr' : 'standard'}_mode_long`, })} @@ -272,7 +272,7 @@ function Header() { {intl.formatMessage({ - id: `quality_gates.update_conditions.${isStandard ? 'standard' : 'mqr'}_mode_header`, + id: `quality_gates.metric.${isStandard ? 'standard' : 'mqr'}_mode_long`, })} 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 1798c704ddc..c30f631dd71 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 @@ -20,7 +20,7 @@ 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 { byLabelText, byRole, byTestId, byText } from '~sonar-aligned/helpers/testSelector'; import { QualityGatesServiceMock } from '../../../../api/mocks/QualityGatesServiceMock'; import SettingsServiceMock from '../../../../api/mocks/SettingsServiceMock'; import UsersServiceMock from '../../../../api/mocks/UsersServiceMock'; @@ -34,6 +34,26 @@ import { CaycStatus } from '../../../../types/types'; import { NoticeType } from '../../../../types/users'; import routes from '../../routes'; +const ui = { + batchUpdate: byRole('button', { name: 'quality_gates.mode_banner.button' }), + singleUpdate: byRole('button', { + name: /quality_gates.mqr_mode_update.single_metric.tooltip.message/, + }), + removeCondition: byRole('button', { name: /quality_gates.condition.delete/ }), + listItem: byTestId('js-subnavigation-item'), + requiresUpdateIndicator: byTestId('quality-gates-mqr-standard-mode-update-indicator'), + qualityGateListItem: (qualityGateName: string) => byRole('link', { name: qualityGateName }), + newConditionRow: byTestId('quality-gates__conditions-new').byRole('row'), + overallConditionRow: byTestId('quality-gates__conditions-overall').byRole('row'), + batchDialog: byRole('dialog', { name: /quality_gates.update_conditions.header/ }), + singleDialog: byRole('dialog', { name: /quality_gates.update_conditions.header.single_metric/ }), + updateMetricsBtn: byRole('button', { name: 'quality_gates.update_conditions.update_metrics' }), + updateSingleBtn: byRole('button', { name: 'update_verb' }), + cancelBtn: byRole('button', { name: 'cancel' }), + standardBadge: byText('quality_gates.metric.standard_mode_short'), + mqrBadge: byText('quality_gates.metric.mqr_mode_short'), +}; + let qualityGateHandler: QualityGatesServiceMock; let usersHandler: UsersServiceMock; let settingsHandler: SettingsServiceMock; @@ -82,9 +102,7 @@ it('should show MQR mode update icon if standard mode conditions are present', a qualityGateHandler.setIsAdmin(true); renderQualityGateApp(); - expect( - await screen.findByTestId('quality-gates-mqr-standard-mode-update-indicator'), - ).toBeInTheDocument(); + expect(await ui.requiresUpdateIndicator.findAll()).toHaveLength(3); }); it('should show Standard mode update icon if MQR mode conditions are present', async () => { @@ -92,9 +110,7 @@ it('should show Standard mode update icon if MQR mode conditions are present', a qualityGateHandler.setIsAdmin(true); renderQualityGateApp(); - expect( - await screen.findByTestId('quality-gates-mqr-standard-mode-update-indicator'), - ).toBeInTheDocument(); + expect(await ui.requiresUpdateIndicator.findAll()).toHaveLength(4); }); it('should render the built-in quality gate properly', async () => { @@ -271,8 +287,8 @@ it('should be able to add a condition on overall code', async () => { ); // In real app there are no metrics with selectable condition operator - // so we manually changed direction for Info Issues to 0 to test this behavior - await user.click(await dialog.byRole('option', { name: 'Info Issues' }).find()); + // so we manually changed direction for Cognitive Complexity to 0 to test this behavior + await user.click(await dialog.byRole('option', { name: 'Cognitive Complexity' }).find()); await user.click(await dialog.byLabelText('quality_gates.conditions.operator').find()); @@ -284,7 +300,7 @@ it('should be able to add a condition on overall code', async () => { const overallConditions = byTestId('quality-gates__conditions-overall'); expect( - await overallConditions.byRole('cell', { name: 'Info Issues' }).find(), + await overallConditions.byRole('cell', { name: 'Cognitive Complexity' }).find(), ).toBeInTheDocument(); expect(await overallConditions.byRole('cell', { name: '42' }).find()).toBeInTheDocument(); }); @@ -314,7 +330,7 @@ it('should be able to select a rating', async () => { const overallConditions = byTestId('quality-gates__conditions-overall'); expect( - await overallConditions.byRole('cell', { name: 'Maintainability Rating' }).find(), + await overallConditions.byRole('cell', { name: /Maintainability Rating/ }).find(), ).toBeInTheDocument(); expect(await overallConditions.byRole('cell', { name: 'B' }).find()).toBeInTheDocument(); }); @@ -913,6 +929,171 @@ describe('The Permissions section', () => { }); }); +describe('Mode transition', () => { + describe('MQR mode', () => { + it('should not see that quality gates require updates if not an admin', async () => { + const user = userEvent.setup(); + renderQualityGateApp(); + + expect(await ui.listItem.findAll()).toHaveLength(9); + expect(ui.requiresUpdateIndicator.query()).not.toBeInTheDocument(); + await user.click(ui.qualityGateListItem('SonarSource way default').get()); + expect(byText('quality_gates.cayc.banner.title').get()).toBeInTheDocument(); + expect(ui.batchUpdate.query()).not.toBeInTheDocument(); + expect(ui.singleUpdate.query()).not.toBeInTheDocument(); + expect(ui.standardBadge.query()).not.toBeInTheDocument(); + }); + + it('should see that quality gates require updates if an admin', async () => { + const user = userEvent.setup(); + qualityGateHandler.setIsAdmin(true); + renderQualityGateApp(); + + expect(await ui.listItem.findAll()).toHaveLength(9); + expect( + ui.qualityGateListItem('SonarSource way default').by(ui.requiresUpdateIndicator).get(), + ).toBeInTheDocument(); + await user.click(ui.qualityGateListItem('SonarSource way default').get()); + expect(byText('quality_gates.cayc.banner.title').query()).not.toBeInTheDocument(); + expect(ui.batchUpdate.get()).toBeInTheDocument(); + expect(ui.singleUpdate.getAll()).toHaveLength(5); + expect(ui.standardBadge.getAll()).toHaveLength(5); + }); + + it('should update conditions to MQR mode', async () => { + const user = userEvent.setup(); + qualityGateHandler.setIsAdmin(true); + renderQualityGateApp(); + + expect(await ui.listItem.findAll()).toHaveLength(9); + await user.click(ui.qualityGateListItem('SonarSource way default').get()); + + await user.click(ui.batchUpdate.get()); + expect(ui.batchDialog.get()).toBeInTheDocument(); + // + 1 for headers + expect(ui.batchDialog.by(ui.newConditionRow).getAll()).toHaveLength(4); + expect(ui.batchDialog.by(ui.overallConditionRow).getAll()).toHaveLength(3); + await user.click(ui.batchDialog.by(ui.cancelBtn).get()); + + expect(ui.newConditionRow.getAt(7)).toHaveTextContent( + 'Reliability Ratingquality_gates.metric.standard_mode_short', + ); + expect(ui.singleUpdate.get(ui.newConditionRow.getAt(7))).toBeInTheDocument(); + await user.click(ui.singleUpdate.get(ui.newConditionRow.getAt(7))); + expect(ui.singleDialog.get()).toBeInTheDocument(); + expect(ui.singleDialog.get()).toHaveTextContent( + 'quality_gates.metric.standard_mode_longReliability Ratingquality_gates.metric.mqr_mode_longReliability Rating', + ); + await user.click(ui.updateSingleBtn.get()); + + expect(ui.singleUpdate.getAll()).toHaveLength(4); + expect(ui.standardBadge.getAll()).toHaveLength(4); + + await user.click(ui.batchUpdate.get()); + expect(ui.batchDialog.get()).toBeInTheDocument(); + // + 1 for headers + expect(ui.batchDialog.by(ui.newConditionRow).getAll()).toHaveLength(3); + expect(ui.batchDialog.by(ui.overallConditionRow).getAll()).toHaveLength(3); + expect(ui.batchDialog.by(ui.newConditionRow).getAt(1)).toHaveTextContent( + 'Maintainability RatingMaintainability Rating', + ); + expect(ui.batchDialog.by(ui.overallConditionRow).getAt(1)).toHaveTextContent( + 'Reliability RatingReliability Rating', + ); + await user.click(ui.batchDialog.by(ui.updateMetricsBtn).get()); + + expect(byText('quality_gates.cayc.banner.title').get()).toBeInTheDocument(); + expect(ui.batchUpdate.query()).not.toBeInTheDocument(); + expect(ui.singleUpdate.query()).not.toBeInTheDocument(); + expect(ui.standardBadge.query()).not.toBeInTheDocument(); + }); + }); + + describe('Standard mode', () => { + beforeEach(() => { + settingsHandler.set(SettingsKey.MQRMode, 'false'); + }); + + it('should not see that quality gates require updates if not an admin', async () => { + const user = userEvent.setup(); + renderQualityGateApp(); + + expect(await ui.listItem.findAll()).toHaveLength(9); + expect(ui.requiresUpdateIndicator.query()).not.toBeInTheDocument(); + await user.click(ui.qualityGateListItem('QG with MQR conditions').get()); + expect(ui.batchUpdate.query()).not.toBeInTheDocument(); + expect(ui.singleUpdate.query()).not.toBeInTheDocument(); + expect(ui.mqrBadge.query()).not.toBeInTheDocument(); + }); + + it('should see that quality gates require updates if an admin', async () => { + const user = userEvent.setup(); + qualityGateHandler.setIsAdmin(true); + renderQualityGateApp(); + + expect(await ui.listItem.findAll()).toHaveLength(9); + expect( + ui.qualityGateListItem('QG with MQR conditions').by(ui.requiresUpdateIndicator).get(), + ).toBeInTheDocument(); + await user.click(ui.qualityGateListItem('QG with MQR conditions').get()); + expect(ui.batchUpdate.get()).toBeInTheDocument(); + expect(ui.singleUpdate.getAll()).toHaveLength(4); + expect(ui.mqrBadge.getAll()).toHaveLength(4); + }); + + it('should update conditions to Standard mode', async () => { + const user = userEvent.setup(); + qualityGateHandler.setIsAdmin(true); + renderQualityGateApp(); + + expect(await ui.listItem.findAll()).toHaveLength(9); + await user.click(ui.qualityGateListItem('QG with MQR conditions').get()); + + await user.click(ui.batchUpdate.get()); + expect(ui.batchDialog.get()).toBeInTheDocument(); + // + 1 for headers + expect(ui.batchDialog.by(ui.newConditionRow).getAll()).toHaveLength(3); + expect(ui.batchDialog.by(ui.overallConditionRow).getAll()).toHaveLength(3); + await user.click(ui.batchDialog.by(ui.cancelBtn).get()); + + expect(ui.newConditionRow.getAt(1)).toHaveTextContent( + 'Blocker Severity Issuesquality_gates.metric.mqr_mode_short', + ); + expect(ui.singleUpdate.get(ui.newConditionRow.getAt(1))).toBeInTheDocument(); + await user.click(ui.singleUpdate.get(ui.newConditionRow.getAt(1))); + expect(ui.singleDialog.get()).toBeInTheDocument(); + expect(ui.singleDialog.get()).toHaveTextContent( + 'quality_gates.metric.mqr_mode_longBlocker Severity Issuesquality_gates.metric.standard_mode_longBlocker Issues', + ); + await user.click(ui.updateSingleBtn.get()); + + expect(ui.singleUpdate.getAll()).toHaveLength(3); + expect(ui.mqrBadge.getAll()).toHaveLength(3); + + await user.click(ui.batchUpdate.get()); + expect(ui.batchDialog.get()).toBeInTheDocument(); + // + 1 for headers + expect(ui.batchDialog.by(ui.newConditionRow).getAll()).toHaveLength(2); + expect(ui.batchDialog.by(ui.overallConditionRow).getAll()).toHaveLength(3); + expect(ui.batchDialog.by(ui.newConditionRow).getAt(1)).toHaveTextContent( + 'High Severity IssuesCritical Issues', + ); + expect(ui.batchDialog.by(ui.overallConditionRow).getAt(1)).toHaveTextContent( + 'Blocker and High Severity Accepted Issuesquality_gates.update_conditions.removed', + ); + expect(ui.batchDialog.by(ui.overallConditionRow).getAt(2)).toHaveTextContent( + 'Security RatingSecurity Rating', + ); + await user.click(ui.batchDialog.by(ui.updateMetricsBtn).get()); + + expect(byText('quality_gates.cayc_missing.banner.title').get()).toBeInTheDocument(); + expect(ui.batchUpdate.query()).not.toBeInTheDocument(); + expect(ui.singleUpdate.query()).not.toBeInTheDocument(); + expect(ui.mqrBadge.query()).not.toBeInTheDocument(); + }); + }); +}); + function renderQualityGateApp(context?: RenderContext) { return renderAppRoutes('quality_gates', routes, context); } diff --git a/server/sonar-web/src/main/js/helpers/mocks/metrics.ts b/server/sonar-web/src/main/js/helpers/mocks/metrics.ts index 58438bbfaad..0701da72488 100644 --- a/server/sonar-web/src/main/js/helpers/mocks/metrics.ts +++ b/server/sonar-web/src/main/js/helpers/mocks/metrics.ts @@ -43,6 +43,18 @@ export const DEFAULT_METRICS: Dict = { qualitative: true, hidden: false, }, + new_software_quality_maintainability_remediation_effort: { + id: 'bf182476-9397-4471-812d-7e40568ef1b0', + key: 'new_software_quality_maintainability_remediation_effort', + type: 'WORK_DUR', + name: 'Added Technical Debt', + description: + 'Total effort (in minutes) to fix all the maintainability issues on new code on the component and therefore to comply to all the requirements.', + domain: 'Maintainability', + direction: -1, + qualitative: true, + hidden: false, + }, analysis_from_sonarqube_9_4: { id: 'AX_iDGfBRf9uEywNDdeh', key: 'analysis_from_sonarqube_9_4', @@ -55,6 +67,17 @@ export const DEFAULT_METRICS: Dict = { qualitative: false, hidden: true, }, + high_impact_accepted_issues: { + id: 'AY0aC41wVDhd53-OniNc', + key: 'high_impact_accepted_issues', + type: 'INT', + name: 'Blocker and High Severity Accepted Issues', + description: 'Accepted issues with blocker or high impact', + domain: 'Issues', + direction: -1, + qualitative: false, + hidden: false, + }, blocker_violations: { id: 'AXJMbIl_PAOIsUIE3gtt', key: 'blocker_violations', @@ -66,6 +89,17 @@ export const DEFAULT_METRICS: Dict = { qualitative: true, hidden: false, }, + software_quality_blocker_issues: { + id: '9a1650ae-056f-4d40-b761-0ae06b9f15b2', + key: 'software_quality_blocker_issues', + type: 'INT', + name: 'Blocker Severity Issues', + description: 'Blocker Severity issues', + domain: 'Issues', + direction: -1, + qualitative: true, + hidden: false, + }, bugs: { id: 'AXJMbIl_PAOIsUIE3gt_', key: 'bugs', @@ -106,7 +140,7 @@ export const DEFAULT_METRICS: Dict = { name: 'Cognitive Complexity', description: 'Cognitive complexity', domain: 'Complexity', - direction: -1, + direction: 0, // manually changed direction to test quality gate condition operator qualitative: false, hidden: false, }, @@ -427,6 +461,17 @@ export const DEFAULT_METRICS: Dict = { qualitative: false, hidden: false, }, + effort_to_reach_software_quality_maintainability_rating_a: { + id: '0f195cdf-5d79-4be0-90e4-c6e0afb58551', + key: 'effort_to_reach_software_quality_maintainability_rating_a', + type: 'WORK_DUR', + name: 'Effort to Reach Maintainability Rating A', + description: 'Effort to reach maintainability rating A', + domain: 'Maintainability', + direction: -1, + qualitative: true, + hidden: false, + }, effort_to_reach_maintainability_rating_a: { id: 'AXJMbIl_PAOIsUIE3guM', key: 'effort_to_reach_maintainability_rating_a', @@ -525,15 +570,15 @@ export const DEFAULT_METRICS: Dict = { qualitative: false, hidden: false, }, - high_impact_accepted_issues: { - id: 'AY0aC41wVDhd53-OniNc', - key: 'high_impact_accepted_issues', + software_quality_high_issues: { + id: '50f6240a-85c5-4aaf-a928-657c7f03b6ef', + key: 'software_quality_high_issues', type: 'INT', - name: 'Blocker and High Severity Accepted Issues', - description: 'Accepted issues with blocker or high impact', + name: 'High Severity Issues', + description: 'High Severity issues', domain: 'Issues', direction: -1, - qualitative: false, + qualitative: true, hidden: false, }, info_violations: { @@ -543,7 +588,18 @@ export const DEFAULT_METRICS: Dict = { name: 'Info Issues', description: 'Info issues', domain: 'Issues', - direction: 0, // manually changed direction to test quality gate condition operator + direction: -1, + qualitative: true, + hidden: false, + }, + software_quality_info_issues: { + id: 'cfc48499-476f-43fb-999e-43bb33e7c93a', + key: 'software_quality_info_issues', + type: 'INT', + name: 'Info Severity Issues', + description: 'Info Severity issues', + domain: 'Issues', + direction: -1, qualitative: true, hidden: false, }, @@ -629,16 +685,6 @@ export const DEFAULT_METRICS: Dict = { qualitative: false, hidden: true, }, - last_change_on_software_quality_releasability_rating: { - id: 'a2aebcc4-366d-49b3-852b-c7b36f43e1c7', - key: 'last_change_on_software_quality_releasability_rating', - type: 'DATA', - name: 'Last Change on Software Quality Releasability Rating', - domain: 'Releasability', - direction: 0, - qualitative: false, - hidden: true, - }, last_change_on_software_quality_reliability_rating: { id: '42889539-14b7-45a5-a383-8c4d4a5e48a5', key: 'last_change_on_software_quality_reliability_rating', @@ -659,16 +705,6 @@ export const DEFAULT_METRICS: Dict = { qualitative: false, hidden: true, }, - last_change_on_software_quality_security_review_rating: { - id: '0f4f143d-b76b-40c9-8769-7f959f4a49ea', - key: 'last_change_on_software_quality_security_review_rating', - type: 'DATA', - name: 'Last Change on SoftwareQuality Security Review Rating', - domain: 'Security', - direction: 0, - qualitative: false, - hidden: true, - }, line_coverage: { id: 'AXJMbIl_PAOIsUIE3gtl', key: 'line_coverage', @@ -757,6 +793,17 @@ export const DEFAULT_METRICS: Dict = { qualitative: false, hidden: true, }, + software_quality_low_issues: { + id: '452a1f9d-c0c6-4001-b5ce-921b748423e2', + key: 'software_quality_low_issues', + type: 'INT', + name: 'Low Severity Issues', + description: 'Low Severity issues', + domain: 'Issues', + direction: -1, + qualitative: true, + hidden: false, + }, maintainability_issues: { id: 'acc8fd75-3acf-499f-809c-f104af89d16d', key: 'maintainability_issues', @@ -768,6 +815,28 @@ export const DEFAULT_METRICS: Dict = { qualitative: false, hidden: false, }, + software_quality_maintainability_issues: { + id: '163c2107-797a-46c8-a1a9-912b00dae7df', + key: 'software_quality_maintainability_issues', + type: 'INT', + name: 'Maintainability Issues', + description: 'Maintainability Issues', + domain: 'Maintainability', + direction: -1, + qualitative: false, + hidden: false, + }, + software_quality_maintainability_rating: { + id: '9fc76baf-f660-4f65-a271-b2ae7f849239', + key: 'software_quality_maintainability_rating', + type: 'RATING', + name: 'Maintainability Rating', + description: 'Maintainability rating', + domain: 'Maintainability', + direction: -1, + qualitative: true, + hidden: false, + }, sqale_rating: { id: 'AXJMbIl_PAOIsUIE3guF', key: 'sqale_rating', @@ -801,6 +870,17 @@ export const DEFAULT_METRICS: Dict = { qualitative: true, hidden: true, }, + new_software_quality_maintainability_rating: { + id: 'c5d12cc4-e712-4701-a395-c9113ce13c3e', + key: 'new_software_quality_maintainability_rating', + type: 'RATING', + name: 'Maintainability Rating on New Code', + description: 'Maintainability rating on new code', + domain: 'Maintainability', + direction: -1, + qualitative: true, + hidden: false, + }, new_maintainability_rating: { id: 'AXJMbIl_PAOIsUIE3guH', key: 'new_maintainability_rating', @@ -823,6 +903,17 @@ export const DEFAULT_METRICS: Dict = { qualitative: true, hidden: false, }, + software_quality_medium_issues: { + id: '5c85ee75-753a-44db-b357-5ea54cd2d88b', + key: 'software_quality_medium_issues', + type: 'INT', + name: 'Medium Severity Issues', + description: 'Medium Severity issues', + domain: 'Issues', + direction: -1, + qualitative: true, + hidden: false, + }, minor_violations: { id: 'AXJMbIl_PAOIsUIE3gtw', key: 'minor_violations', @@ -866,6 +957,17 @@ export const DEFAULT_METRICS: Dict = { qualitative: true, hidden: false, }, + new_software_quality_blocker_issues: { + id: '24492b83-d35f-4cbd-b12f-0b1168cb7c9a', + key: 'new_software_quality_blocker_issues', + type: 'INT', + name: 'New Blocker Severity Issues', + description: 'New Blocker Severity issues', + domain: 'Issues', + direction: -1, + qualitative: true, + hidden: false, + }, new_bugs: { id: 'AXJMbIl_PAOIsUIE3guA', key: 'new_bugs', @@ -899,6 +1001,17 @@ export const DEFAULT_METRICS: Dict = { qualitative: true, hidden: false, }, + new_software_quality_high_issues: { + id: '1f2e2f4d-4069-4998-885b-287cb87939b6', + key: 'new_software_quality_high_issues', + type: 'INT', + name: 'New High Severity Issues', + description: 'New High Severity issues', + domain: 'Issues', + direction: -1, + qualitative: true, + hidden: false, + }, new_info_violations: { id: 'AXJMbIl_PAOIsUIE3gt3', key: 'new_info_violations', @@ -910,6 +1023,17 @@ export const DEFAULT_METRICS: Dict = { qualitative: true, hidden: false, }, + new_software_quality_info_issues: { + id: '6857dd13-4d48-4a6f-b985-889930ee3508', + key: 'new_software_quality_info_issues', + type: 'INT', + name: 'New Info Severity Issues', + description: 'New Info Severity issues', + domain: 'Issues', + direction: -1, + qualitative: true, + hidden: false, + }, new_violations: { id: 'AXJMbIl_PAOIsUIE3gty', key: 'new_violations', @@ -932,6 +1056,17 @@ export const DEFAULT_METRICS: Dict = { qualitative: false, hidden: false, }, + new_software_quality_low_issues: { + id: '8ecc3d65-72e7-4a36-9d1b-84344ba017f3', + key: 'new_software_quality_low_issues', + type: 'INT', + name: 'New Low Severity Issues', + description: 'New Low Severity issues', + domain: 'Issues', + direction: -1, + qualitative: true, + hidden: false, + }, new_maintainability_issues: { id: '9385d0e6-8991-40b1-b94a-5a260e6146f0', key: 'new_maintainability_issues', @@ -943,6 +1078,17 @@ export const DEFAULT_METRICS: Dict = { qualitative: false, hidden: false, }, + new_software_quality_maintainability_issues: { + id: 'e0216417-de54-4beb-8948-f61bd53987ed', + key: 'new_software_quality_maintainability_issues', + type: 'INT', + name: 'New Maintainability Issues', + description: 'New Maintainability Issues', + domain: 'Maintainability', + direction: -1, + qualitative: true, + hidden: false, + }, new_major_violations: { id: 'AXJMbIl_PAOIsUIE3gt1', key: 'new_major_violations', @@ -954,6 +1100,17 @@ export const DEFAULT_METRICS: Dict = { qualitative: true, hidden: false, }, + new_software_quality_medium_issues: { + id: '9d634fca-caee-4bee-8e23-37744d014194', + key: 'new_software_quality_medium_issues', + type: 'INT', + name: 'New Medium Severity Issues', + description: 'New Medium Severity issues', + domain: 'Issues', + direction: -1, + qualitative: true, + hidden: false, + }, new_minor_violations: { id: 'AXJMbIl_PAOIsUIE3gt2', key: 'new_minor_violations', @@ -976,6 +1133,17 @@ export const DEFAULT_METRICS: Dict = { qualitative: false, hidden: false, }, + new_software_quality_reliability_issues: { + id: 'bfcac26d-2ce7-4fbf-b5c6-ea4f6556825d', + key: 'new_software_quality_reliability_issues', + type: 'INT', + name: 'New Reliability Issues', + description: 'New Reliability Issues', + domain: 'Reliability', + direction: -1, + qualitative: true, + hidden: false, + }, new_security_hotspots: { id: 'AXJMbIl9PAOIsUIE3gsw', key: 'new_security_hotspots', @@ -987,6 +1155,17 @@ export const DEFAULT_METRICS: Dict = { qualitative: true, hidden: false, }, + new_software_quality_security_issues: { + id: 'ecf1983d-ea7e-4df6-b377-d16f4a5a59e6', + key: 'new_software_quality_security_issues', + type: 'INT', + name: 'New Security Issues', + description: 'New Security Issues', + domain: 'Security', + direction: -1, + qualitative: true, + hidden: false, + }, new_security_issues: { id: '6887ef7d-ee21-449c-b1ff-f3f7930ba27f', key: 'new_security_issues', @@ -1161,6 +1340,17 @@ export const DEFAULT_METRICS: Dict = { qualitative: false, hidden: false, }, + software_quality_reliability_issues: { + id: '765f3905-47df-4d9c-b568-9184f341a737', + key: 'software_quality_reliability_issues', + type: 'INT', + name: 'Reliability Issues', + description: 'Reliability Issues', + domain: 'Reliability', + direction: -1, + qualitative: false, + hidden: false, + }, reliability_rating: { id: 'AXJMbIl_PAOIsUIE3guP', key: 'reliability_rating', @@ -1172,6 +1362,17 @@ export const DEFAULT_METRICS: Dict = { qualitative: true, hidden: false, }, + software_quality_reliability_rating: { + id: '6548ffa4-8a5e-4445-a28d-e2fd9fdbba78', + key: 'software_quality_reliability_rating', + type: 'RATING', + name: 'Reliability Rating', + description: 'Reliability rating', + domain: 'Reliability', + direction: -1, + qualitative: true, + hidden: false, + }, reliability_rating_distribution: { id: 'AX3sJDjuJHBehddvNyhO', key: 'reliability_rating_distribution', @@ -1205,6 +1406,17 @@ export const DEFAULT_METRICS: Dict = { qualitative: true, hidden: false, }, + new_software_quality_reliability_rating: { + id: 'ab82dcac-cf81-4780-965d-1384ce9e8983', + key: 'new_software_quality_reliability_rating', + type: 'RATING', + name: 'Reliability Rating on New Code', + description: 'Reliability rating on new code', + domain: 'Reliability', + direction: -1, + qualitative: true, + hidden: false, + }, reliability_remediation_effort: { id: 'AXJMbIl_PAOIsUIE3guN', key: 'reliability_remediation_effort', @@ -1216,6 +1428,28 @@ export const DEFAULT_METRICS: Dict = { qualitative: true, hidden: false, }, + software_quality_reliability_remediation_effort: { + id: 'f2094b93-e08c-4350-ad8f-f265974278a8', + key: 'software_quality_reliability_remediation_effort', + type: 'WORK_DUR', + name: 'Reliability Remediation Effort', + description: 'Reliability remediation effort', + domain: 'Reliability', + direction: -1, + qualitative: true, + hidden: false, + }, + new_software_quality_reliability_remediation_effort: { + id: '45f9a292-5f6e-459e-8d81-134d5aaedef9', + key: 'new_software_quality_reliability_remediation_effort', + type: 'WORK_DUR', + name: 'Reliability Remediation Effort on New Code', + description: 'Reliability remediation effort on new code', + domain: 'Reliability', + direction: -1, + qualitative: true, + hidden: false, + }, new_reliability_remediation_effort: { id: 'AXJMbIl_PAOIsUIE3guO', key: 'new_reliability_remediation_effort', @@ -1284,6 +1518,17 @@ export const DEFAULT_METRICS: Dict = { qualitative: false, hidden: false, }, + software_quality_security_issues: { + id: '7133e418-aadb-4cac-b31f-b13bf036a7ff', + key: 'software_quality_security_issues', + type: 'INT', + name: 'Security Issues', + description: 'Security Issues', + domain: 'Security', + direction: -1, + qualitative: false, + hidden: false, + }, security_rating: { id: 'AXJMbIl_PAOIsUIE3guS', key: 'security_rating', @@ -1295,6 +1540,17 @@ export const DEFAULT_METRICS: Dict = { qualitative: true, hidden: false, }, + software_quality_security_rating: { + id: 'db22dacd-a3fd-41d2-8617-0cb7cfc86429', + key: 'software_quality_security_rating', + type: 'RATING', + name: 'Security Rating', + description: 'Security rating', + domain: 'Security', + direction: -1, + qualitative: true, + hidden: false, + }, security_rating_distribution: { id: 'AX3sJDjuJHBehddvNyhP', key: 'security_rating_distribution', @@ -1317,6 +1573,17 @@ export const DEFAULT_METRICS: Dict = { qualitative: true, hidden: true, }, + new_software_quality_security_rating: { + id: '228b9a04-09a2-418e-9ea4-3584a57a95ba', + key: 'new_software_quality_security_rating', + type: 'RATING', + name: 'Security Rating on New Code', + description: 'Security rating on new code', + domain: 'Security', + direction: -1, + qualitative: true, + hidden: false, + }, new_security_rating: { id: 'AXJMbImPPAOIsUIE3guT', key: 'new_security_rating', @@ -1328,6 +1595,17 @@ export const DEFAULT_METRICS: Dict = { qualitative: true, hidden: false, }, + software_quality_security_remediation_effort: { + id: '43652a84-3ca7-4506-9c09-00ea2b6c6e71', + key: 'software_quality_security_remediation_effort', + type: 'WORK_DUR', + name: 'Security Remediation Effort', + description: 'Security remediation effort', + domain: 'Security', + direction: -1, + qualitative: true, + hidden: false, + }, security_remediation_effort: { id: 'AXJMbIl_PAOIsUIE3guG', key: 'security_remediation_effort', @@ -1350,6 +1628,17 @@ export const DEFAULT_METRICS: Dict = { qualitative: true, hidden: false, }, + new_software_quality_security_remediation_effort: { + id: 'd5d0020a-419c-4387-b00f-523ad8a6a3e4', + key: 'new_software_quality_security_remediation_effort', + type: 'WORK_DUR', + name: 'Security Remediation Effort on New Code', + description: 'Security remediation effort on new code', + domain: 'Security', + direction: -1, + qualitative: true, + hidden: false, + }, security_review_rating: { id: 'AXJMbIl9PAOIsUIE3gsx', key: 'security_review_rating', @@ -1449,28 +1738,6 @@ export const DEFAULT_METRICS: Dict = { qualitative: true, hidden: false, }, - effort_to_reach_software_quality_maintainability_rating_a: { - id: '0f195cdf-5d79-4be0-90e4-c6e0afb58551', - key: 'effort_to_reach_software_quality_maintainability_rating_a', - type: 'WORK_DUR', - name: 'Software Quality Effort to Reach Maintainability Rating A', - description: 'Software quality effort to reach maintainability rating A', - domain: 'Maintainability', - direction: -1, - qualitative: true, - hidden: false, - }, - software_quality_maintainability_rating: { - id: '9fc76baf-f660-4f65-a271-b2ae7f849239', - key: 'software_quality_maintainability_rating', - type: 'RATING', - name: 'Software Quality Maintainability Rating', - description: 'Software quality maintainability rating', - domain: 'Maintainability', - direction: -1, - qualitative: true, - hidden: false, - }, software_quality_maintainability_rating_distribution: { id: 'b39b797b-216d-4800-810e-2277012ee096', key: 'software_quality_maintainability_rating_distribution', @@ -1503,73 +1770,6 @@ export const DEFAULT_METRICS: Dict = { qualitative: false, hidden: true, }, - new_software_quality_maintainability_rating: { - id: 'c5d12cc4-e712-4701-a395-c9113ce13c3e', - key: 'new_software_quality_maintainability_rating', - type: 'RATING', - name: 'Software Quality Maintainability Rating on New Code', - description: 'Software quality maintainability rating on new code', - domain: 'Maintainability', - direction: -1, - qualitative: true, - hidden: false, - }, - software_quality_maintainability_remediation_effort: { - id: '24edda40-db1c-4acd-9c9b-66d5bcca8486', - key: 'software_quality_maintainability_remediation_effort', - type: 'WORK_DUR', - name: 'Software Quality Maintainability Remediation Effort', - description: - 'Software quality total effort (in minutes) to fix all the maintainability issues on the component and therefore to comply to all the requirements.', - domain: 'Maintainability', - direction: -1, - qualitative: true, - hidden: false, - }, - new_software_quality_maintainability_remediation_effort: { - id: 'bf182476-9397-4471-812d-7e40568ef1b0', - key: 'new_software_quality_maintainability_remediation_effort', - type: 'WORK_DUR', - name: 'Software Quality Maintainability Remediation Effort on new code', - description: - 'Software quality total effort (in minutes) to fix all the maintainability issues on new code on the component and therefore to comply to all the requirements.', - domain: 'Maintainability', - direction: -1, - qualitative: true, - hidden: false, - }, - software_quality_releasability_rating: { - id: '1fb38855-84b8-41b2-88a0-50c3dceda102', - key: 'software_quality_releasability_rating', - type: 'RATING', - name: 'Software Quality Releasability rating', - domain: 'Releasability', - direction: -1, - qualitative: true, - hidden: false, - }, - software_quality_releasability_rating_distribution: { - id: 'a34a08a2-29b5-4efb-bd2a-eebe3dc10dab', - key: 'software_quality_releasability_rating_distribution', - type: 'DATA', - name: 'Software Quality Releasability Rating Distribution', - description: 'Software Quality Releasability rating distribution', - domain: 'Releasability', - direction: -1, - qualitative: true, - hidden: true, - }, - software_quality_reliability_rating: { - id: '6548ffa4-8a5e-4445-a28d-e2fd9fdbba78', - key: 'software_quality_reliability_rating', - type: 'RATING', - name: 'Software Quality Reliability Rating', - description: 'Software quality reliability rating', - domain: 'Reliability', - direction: -1, - qualitative: true, - hidden: false, - }, software_quality_reliability_rating_distribution: { id: '571de2d7-d1ef-460b-8f99-e29e0aa6218c', key: 'software_quality_reliability_rating_distribution', @@ -1602,50 +1802,6 @@ export const DEFAULT_METRICS: Dict = { qualitative: false, hidden: true, }, - new_software_quality_reliability_rating: { - id: 'ab82dcac-cf81-4780-965d-1384ce9e8983', - key: 'new_software_quality_reliability_rating', - type: 'RATING', - name: 'Software Quality Reliability Rating on New Code', - description: 'Software quality reliability rating on new code', - domain: 'Reliability', - direction: -1, - qualitative: true, - hidden: false, - }, - software_quality_reliability_remediation_effort: { - id: 'f2094b93-e08c-4350-ad8f-f265974278a8', - key: 'software_quality_reliability_remediation_effort', - type: 'WORK_DUR', - name: 'Software Quality Reliability Remediation Effort', - description: 'Software quality reliability remediation effort', - domain: 'Reliability', - direction: -1, - qualitative: true, - hidden: false, - }, - new_software_quality_reliability_remediation_effort: { - id: '45f9a292-5f6e-459e-8d81-134d5aaedef9', - key: 'new_software_quality_reliability_remediation_effort', - type: 'WORK_DUR', - name: 'Software Quality Reliability Remediation Effort on New Code', - description: 'Software quality reliability remediation effort on new code', - domain: 'Reliability', - direction: -1, - qualitative: true, - hidden: false, - }, - software_quality_security_rating: { - id: 'db22dacd-a3fd-41d2-8617-0cb7cfc86429', - key: 'software_quality_security_rating', - type: 'RATING', - name: 'Software Quality Security Rating', - description: 'Software quality security rating', - domain: 'Security', - direction: -1, - qualitative: true, - hidden: false, - }, software_quality_security_rating_distribution: { id: 'f9a76abe-7663-47b3-a27c-1dea7e6b4861', key: 'software_quality_security_rating_distribution', @@ -1678,89 +1834,37 @@ export const DEFAULT_METRICS: Dict = { qualitative: false, hidden: true, }, - new_software_quality_security_rating: { - id: '228b9a04-09a2-418e-9ea4-3584a57a95ba', - key: 'new_software_quality_security_rating', - type: 'RATING', - name: 'Software Quality Security Rating on New Code', - description: 'Software quality security rating on new code', - domain: 'Security', + statements: { + id: 'AXJMbImPPAOIsUIE3gum', + key: 'statements', + type: 'INT', + name: 'Statements', + description: 'Number of statements', + domain: 'Size', direction: -1, - qualitative: true, + qualitative: false, hidden: false, }, - software_quality_security_remediation_effort: { - id: '43652a84-3ca7-4506-9c09-00ea2b6c6e71', - key: 'software_quality_security_remediation_effort', + software_quality_maintainability_remediation_effort: { + id: '24edda40-db1c-4acd-9c9b-66d5bcca8486', + key: 'software_quality_maintainability_remediation_effort', type: 'WORK_DUR', - name: 'Software Quality Security Remediation Effort', - description: 'Software quality security remediation effort', - domain: 'Security', + name: 'Technical Debt', + description: + 'Total effort (in minutes) to fix all the maintainability issues on the component and therefore to comply to all the requirements.', + domain: 'Maintainability', direction: -1, qualitative: true, hidden: false, }, - new_software_quality_security_remediation_effort: { - id: 'd5d0020a-419c-4387-b00f-523ad8a6a3e4', - key: 'new_software_quality_security_remediation_effort', + sqale_index: { + id: 'AXJMbIl_PAOIsUIE3guD', + key: 'sqale_index', type: 'WORK_DUR', - name: 'Software Quality Security Remediation Effort on New Code', - description: 'Software quality security remediation effort on new code', - domain: 'Security', - direction: -1, - qualitative: true, - hidden: false, - }, - software_quality_security_review_rating: { - id: '6e09531b-dee8-4c23-9a76-dc335cf79019', - key: 'software_quality_security_review_rating', - type: 'RATING', - name: 'Software Quality Security Review Rating', - description: 'Software quality security review rating', - domain: 'SecurityReview', - direction: -1, - qualitative: true, - hidden: false, - }, - software_quality_security_review_rating_distribution: { - id: '3b9d046c-a319-4cbf-99c4-15c206e71401', - key: 'software_quality_security_review_rating_distribution', - type: 'DATA', - name: 'software Quality Security Review Rating Distribution', - description: 'Software Quality Security review rating distribution', - domain: 'Security', - direction: -1, - qualitative: true, - hidden: true, - }, - new_software_quality_security_review_rating_distribution: { - id: 'b6565b9b-3676-405f-8149-a37cb0bd78b9', - key: 'new_software_quality_security_review_rating_distribution', - type: 'DATA', - name: 'Software Quality Security Review Rating Distribution on New Code', - description: 'Software Quality Security review rating distribution on new code', - domain: 'Security', - direction: -1, - qualitative: true, - hidden: true, - }, - software_quality_security_review_rating_effort: { - id: '8d6d023e-6764-42ee-9fb7-bc1f17bda205', - key: 'software_quality_security_review_rating_effort', - type: 'DATA', - name: 'Software Quality Security Review Rating Effort', - domain: 'Security', - direction: 0, - qualitative: false, - hidden: true, - }, - new_software_quality_security_review_rating: { - id: '4d4b1d18-da7e-403c-a3f0-99951c58b050', - key: 'new_software_quality_security_review_rating', - type: 'RATING', - name: 'Software Quality Security Review Rating on New Code', - description: 'Software quality security review rating on new code', - domain: 'SecurityReview', + name: 'Technical Debt', + description: + 'Total effort (in minutes) to fix all the issues on the component and therefore to comply to all the requirements.', + domain: 'Maintainability', direction: -1, qualitative: true, hidden: false, @@ -1769,57 +1873,34 @@ export const DEFAULT_METRICS: Dict = { id: '8a7a5279-9dfe-4fdf-9886-9203ae50be5c', key: 'software_quality_maintainability_debt_ratio', type: 'PERCENT', - name: 'Software Quality Technical Debt Ratio', + name: 'Technical Debt Ratio', description: - 'Software quality ratio of the actual technical debt compared to the estimated cost to develop the whole source code from scratch', + 'Ratio of the actual technical debt compared to the estimated cost to develop the whole source code from scratch', domain: 'Maintainability', direction: -1, qualitative: true, hidden: false, decimalScale: 1, }, - new_software_quality_maintainability_debt_ratio: { - id: '23330467-a21e-4254-a76a-2eb99aab6f0f', - key: 'new_software_quality_maintainability_debt_ratio', + sqale_debt_ratio: { + id: 'AXJMbIl_PAOIsUIE3guK', + key: 'sqale_debt_ratio', type: 'PERCENT', - name: 'Software Quality Technical Debt Ratio on New Code', - description: 'Software quality technical debt ratio software quality of new/changed code.', - domain: 'Maintainability', - direction: -1, - qualitative: true, - hidden: false, - decimalScale: 1, - }, - statements: { - id: 'AXJMbImPPAOIsUIE3gum', - key: 'statements', - type: 'INT', - name: 'Statements', - description: 'Number of statements', - domain: 'Size', - direction: -1, - qualitative: false, - hidden: false, - }, - sqale_index: { - id: 'AXJMbIl_PAOIsUIE3guD', - key: 'sqale_index', - type: 'WORK_DUR', - name: 'Technical Debt', + name: 'Technical Debt Ratio', description: - 'Total effort (in minutes) to fix all the issues on the component and therefore to comply to all the requirements.', + 'Ratio of the actual technical debt compared to the estimated cost to develop the whole source code from scratch', domain: 'Maintainability', direction: -1, qualitative: true, hidden: false, + decimalScale: 1, }, - sqale_debt_ratio: { - id: 'AXJMbIl_PAOIsUIE3guK', - key: 'sqale_debt_ratio', + new_software_quality_maintainability_debt_ratio: { + id: '23330467-a21e-4254-a76a-2eb99aab6f0f', + key: 'new_software_quality_maintainability_debt_ratio', type: 'PERCENT', - name: 'Technical Debt Ratio', - description: - 'Ratio of the actual technical debt compared to the estimated cost to develop the whole source code from scratch', + name: 'Technical Debt Ratio on New Code', + description: 'Technical Debt Ratio on New Code', domain: 'Maintainability', direction: -1, qualitative: true, diff --git a/server/sonar-web/src/main/js/queries/quality-gates.ts b/server/sonar-web/src/main/js/queries/quality-gates.ts index acc880b1e73..d8f6a4e8842 100644 --- a/server/sonar-web/src/main/js/queries/quality-gates.ts +++ b/server/sonar-web/src/main/js/queries/quality-gates.ts @@ -239,7 +239,7 @@ export function useUpdateConditionMutation(gateName: string) { }); } -export function useUpdateOrDeleteConditionsMutation(gateName: string, isSingleMetric?: boolean) { +export function useUpdateOrDeleteConditionsMutation(gateName: string, isSingleMetric = false) { const queryClient = useQueryClient(); const intl = useIntl(); 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 eb50f757551..ea963b436ed 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -2555,6 +2555,7 @@ quality_gates.ai_generated.tootltip.message=Sonar way ensures clean AI-generated 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 +quality_gates.mqr_mode_update.single_metric.tooltip.message=Update {metric} metric to {mode} quality_gates.update_conditions.update_metrics=Update metrics quality_gates.update_conditions.header=Update all metrics of “{qualityGate}” gate quality_gates.update_conditions.header.single_metric=Update this metric @@ -2563,10 +2564,16 @@ quality_gates.update_conditions.description.line2=They will be calculated differ quality_gates.update_conditions.description.line3=Note that the update to {mode} might cause your quality gate to fail. {link} quality_gates.update_conditions.description.link=For more information, refer to the documentation. quality_gates.update_conditions.standard_mode_header=Standard Experience Metric -quality_gates.update_conditions.mqr_mode_header=MQR Mode Metric +quality_gates.metric.standard_mode_short=Standard Experience +quality_gates.metric.standard_mode_long=Standard Experience Metric +quality_gates.metric.mqr_mode_short=MQR Mode +quality_gates.metric.mqr_mode_long=MQR Mode Metric quality_gates.update_conditions.operator_and_value_header=Operator and Value quality_gates.update_conditions.removed=Condition will be removed quality_gates.update_conditions.error=Failed to update some conditions +quality_gates.mode_banner.title=This quality gate is using conditions with metrics that belong to the {mode} +quality_gates.mode_banner.description=This instance is currently in the {link}. We recommend you update the metrics of your quality gate conditions to ensure accurate categorization and ranking of your issues in this mode. Conditions that use {otherMode} metrics can’t be edited until they are updated. +quality_gates.mode_banner.button=Review and update metrics #------------------------------------------------------------------------------