diff options
author | Viktor Vorona <viktor.vorona@sonarsource.com> | 2024-10-07 14:36:18 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2024-10-16 20:03:01 +0000 |
commit | 98ec043c4f45a6168735b694351b2e6c86f5239f (patch) | |
tree | 96872046d718ea883a6b5807ec94174c474d3139 /server/sonar-web/src | |
parent | d53784c7e606db9c985bd7747076d0f066bb339d (diff) | |
download | sonarqube-98ec043c4f45a6168735b694351b2e6c86f5239f.tar.gz sonarqube-98ec043c4f45a6168735b694351b2e6c86f5239f.zip |
SONAR-23263 Redesign Quality Profile select in activation modal
Diffstat (limited to 'server/sonar-web/src')
3 files changed, 77 insertions, 59 deletions
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRuleDetails-it.ts b/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRuleDetails-it.ts index 805a4fcb5c7..a5010470c86 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRuleDetails-it.ts +++ b/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRuleDetails-it.ts @@ -177,6 +177,15 @@ it('can activate/change/deactivate rule in quality profile', async () => { renderCodingRulesApp(mockLoggedInUser(), 'coding_rules?open=rule1', [Feature.PrioritizedRules]); expect(await ui.qpLink('QP Foo').find()).toBeInTheDocument(); + // Activate profile with inherited ones java rule + await user.click(ui.activateButton.get()); + await user.click(ui.qualityProfileSelect.get()); + await user.click(byRole('option', { name: 'QP FooBarBaz' }).get()); + await user.type(ui.paramInput('1').get(), 'paramInput'); + await user.click(ui.activateButton.get(ui.activateQPDialog.get())); + expect(ui.qpLink('QP FooBarBaz').get()).toBeInTheDocument(); + expect(ui.qpLink('QP FooBaz').get()).toBeInTheDocument(); + // Activate rule in quality profile expect(ui.prioritizedRuleCell.query()).not.toBeInTheDocument(); await user.click(ui.activateButton.get()); @@ -194,13 +203,6 @@ it('can activate/change/deactivate rule in quality profile', async () => { 'coding_rules.impact_customized.detailsoftware_quality.MAINTAINABILITYseverity_impact.MEDIUMseverity_impact.LOW', ); - // Activate last java rule - await user.click(ui.activateButton.get()); - await user.type(ui.paramInput('1').get(), 'paramInput'); - await user.click(ui.activateButton.get(ui.activateQPDialog.get())); - expect(ui.qpLink('QP FooBarBaz').get()).toBeInTheDocument(); - expect(ui.qpLink('QP FooBaz').get()).toBeInTheDocument(); - // Rule is activated in all quality profiles - show notification in dialog await user.click(ui.activateButton.get(screen.getByRole('main'))); expect(ui.activaInAllQPs.get()).toBeInTheDocument(); @@ -215,10 +217,10 @@ it('can activate/change/deactivate rule in quality profile', async () => { await user.click(ui.newSeveritySelect(SoftwareQuality.Maintainability).get()); await user.click(byRole('option', { name: 'severity_impact.BLOCKER' }).get()); await user.click(ui.saveButton.get(ui.changeQPDialog.get())); - expect(await ui.qualityProfileRow.findAt(5)).toHaveTextContent('QP FooBaz'); - expect(ui.qualityProfileRow.getAt(5)).toHaveTextContent('New'); + expect(await ui.qualityProfileRow.findAt(4)).toHaveTextContent('QP FooBaz'); + expect(ui.qualityProfileRow.getAt(4)).toHaveTextContent('New'); await expect( - ui.newSeverityCustomizedCell.get(ui.qualityProfileRow.getAt(5)), + ui.newSeverityCustomizedCell.get(ui.qualityProfileRow.getAt(4)), ).toHaveATooltipWithContent( 'coding_rules.impact_customized.detailsoftware_quality.MAINTAINABILITYseverity_impact.MEDIUMseverity_impact.BLOCKER', ); @@ -226,9 +228,9 @@ it('can activate/change/deactivate rule in quality profile', async () => { // Revert rule details in quality profile await user.click(ui.revertToParentDefinitionButton.get()); await user.click(ui.yesButton.get()); - expect(await ui.qualityProfileRow.findAt(5)).toHaveTextContent('QP FooBaz'); - expect(await ui.qualityProfileRow.findAt(5)).not.toHaveTextContent('New'); - expect(ui.newSeverityCustomizedCell.query(ui.qualityProfileRow.getAt(5))).not.toBeInTheDocument(); + expect(await ui.qualityProfileRow.findAt(4)).toHaveTextContent('QP FooBaz'); + expect(await ui.qualityProfileRow.findAt(4)).not.toHaveTextContent('New'); + expect(ui.newSeverityCustomizedCell.query(ui.qualityProfileRow.getAt(4))).not.toBeInTheDocument(); // Deactivate rule in quality profile await user.click(ui.deactivateInQPButton('QP FooBar').get()); diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/ActivationFormModal.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/ActivationFormModal.tsx index a06bb85d86d..6c439093d7c 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/ActivationFormModal.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/ActivationFormModal.tsx @@ -18,14 +18,12 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Button, ButtonVariety, Checkbox, Modal, Text } from '@sonarsource/echoes-react'; +import { Button, ButtonVariety, Checkbox, Modal, Select, Text } from '@sonarsource/echoes-react'; import { FlagMessage, FormField, InputField, - InputSelect, InputTextArea, - LabelValueSelectOption, Note, SafeHTMLInjection, SanitizeLevel, @@ -74,9 +72,7 @@ export default function ActivationFormModal(props: Readonly<Props>) { const [changedPrioritizedRule, setChangedPrioritizedRule] = React.useState<boolean | undefined>( undefined, ); - const [changedProfile, setChangedProfile] = React.useState<ProfileWithDepth | undefined>( - undefined, - ); + const [changedProfile, setChangedProfile] = React.useState<string | undefined>(undefined); const [changedParams, setChangedParams] = React.useState<Record<string, string> | undefined>( undefined, ); @@ -94,7 +90,7 @@ export default function ActivationFormModal(props: Readonly<Props>) { const prioritizedRule = changedPrioritizedRule ?? (activation ? activation.prioritizedRule : false); - const profile = changedProfile ?? profilesWithDepth[0]; + const profile = profiles.find((p) => p.key === changedProfile) ?? profilesWithDepth[0]; const params = changedParams ?? getRuleParams({ activation, rule }); const severity = changedSeverity ?? ((activation ? activation.severity : rule.severity) as IssueSeverity); @@ -105,7 +101,11 @@ export default function ActivationFormModal(props: Readonly<Props>) { .map((impact) => [impact.softwareQuality, impact.severity] as const) ?? []), ...changedImpactSeveritiesMap, ]); - const profileOptions = profilesWithDepth.map((p) => ({ label: p.name, value: p })); + const profileOptions = profilesWithDepth.map((p) => ({ + label: p.name, + value: p.key, + prefix: ' '.repeat(p.depth), + })); const isCustomRule = !!(rule as RuleDetails).templateKey; const activeInAllProfiles = profilesWithDepth.length <= 0; const isUpdateMode = !!activation; @@ -167,48 +167,56 @@ export default function ActivationFormModal(props: Readonly<Props>) { } content={ <form className="sw-pb-10" id={FORM_ID} onSubmit={handleFormSubmit}> + <Text as="div"> + <FormattedMessage + id="coding_rules.rule_name.title" + values={{ + name: <Text isSubdued>{rule.name}</Text>, + }} + /> + </Text> + {!isUpdateMode && activeInAllProfiles && ( - <FlagMessage className="sw-mb-2" variant="info"> + <FlagMessage className="sw-mt-4 sw-mb-6" variant="info"> {intl.formatMessage({ id: 'coding_rules.active_in_all_profiles' })} </FlagMessage> )} - <FormField - ariaLabel={intl.formatMessage({ id: 'coding_rules.quality_profile' })} - label={intl.formatMessage({ id: 'coding_rules.quality_profile' })} - htmlFor="coding-rules-quality-profile-select-input" - > - <InputSelect - id="coding-rules-quality-profile-select" - inputId="coding-rules-quality-profile-select-input" - isClearable={false} - isDisabled={submitting || profilesWithDepth.length < MIN_PROFILES_TO_ENABLE_SELECT} - onChange={({ value }: LabelValueSelectOption<ProfileWithDepth>) => { - setChangedProfile(value); - }} - getOptionLabel={({ value }: LabelValueSelectOption<ProfileWithDepth>) => - ' '.repeat(value.depth) + value.name - } - options={profileOptions} - value={profileOptions.find(({ value }) => value.key === profile?.key)} - /> - </FormField> + {profilesWithDepth.length >= MIN_PROFILES_TO_ENABLE_SELECT ? ( + <FormField + className="sw-mt-4" + ariaLabel={intl.formatMessage({ id: 'coding_rules.quality_profile' })} + label={intl.formatMessage({ id: 'coding_rules.quality_profile' })} + htmlFor="coding-rules-quality-profile-select" + > + <Select + id="coding-rules-quality-profile-select" + isNotClearable + isDisabled={submitting} + onChange={(value) => setChangedProfile(value ?? undefined)} + data={profileOptions} + value={profile?.key} + /> + </FormField> + ) : ( + <> + {(isUpdateMode || !activeInAllProfiles) && ( + <Text as="div" className="sw-mb-6"> + <FormattedMessage + id="coding_rules.quality_profile.title" + values={{ + name: <Text isSubdued>{profile?.name}</Text>, + }} + /> + </Text> + )} + </> + )} {hasFeature(Feature.PrioritizedRules) && ( <FormField ariaLabel={intl.formatMessage({ id: 'coding_rules.prioritized_rule.title' })} label={intl.formatMessage({ id: 'coding_rules.prioritized_rule.title' })} - description={ - <div className="sw-text-xs"> - {intl.formatMessage({ id: 'coding_rules.prioritized_rule.note' })} - <DocumentationLink - className="sw-ml-2 sw-whitespace-nowrap" - to={DocLink.InstanceAdminQualityProfilesPrioritizingRules} - > - {intl.formatMessage({ id: 'learn_more' })} - </DocumentationLink> - </div> - } > <Checkbox onCheck={(checked) => setChangedPrioritizedRule(!!checked)} @@ -216,6 +224,17 @@ export default function ActivationFormModal(props: Readonly<Props>) { id="coding-rules-prioritized-rule" checked={prioritizedRule} /> + {prioritizedRule && ( + <FlagMessage className="sw-mt-2" variant="info"> + {intl.formatMessage({ id: 'coding_rules.prioritized_rule.note' })} + <DocumentationLink + className="sw-ml-2 sw-whitespace-nowrap" + to={DocLink.InstanceAdminQualityProfilesPrioritizingRules} + > + {intl.formatMessage({ id: 'learn_more' })} + </DocumentationLink> + </FlagMessage> + )} </FormField> )} @@ -360,10 +379,7 @@ export default function ActivationFormModal(props: Readonly<Props>) { ); } -function getQualityProfilesWithDepth( - profiles: Profile[] = [], - ruleLang?: string, -): ProfileWithDepth[] { +function getQualityProfilesWithDepth(profiles: Profile[], ruleLang?: string): ProfileWithDepth[] { return sortProfiles( profiles.filter( (profile) => @@ -387,7 +403,7 @@ function getRuleParams({ rule: RuleDetails | Rule; }) { const params: Record<string, string> = {}; - if (rule?.params) { + if (rule.params) { for (const param of rule.params) { params[param.key] = param.defaultValue ?? ''; } diff --git a/server/sonar-web/src/main/js/apps/coding-rules/utils-tests.tsx b/server/sonar-web/src/main/js/apps/coding-rules/utils-tests.tsx index ce8bbefbd61..6ae58cfd19a 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/utils-tests.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/utils-tests.tsx @@ -161,7 +161,7 @@ const selectors = { name: /coding_rules.deactivate_in_quality_profile/, hidden: true, }), - qualityProfileSelect: byLabelText('coding_rules.quality_profile'), + qualityProfileSelect: byRole('combobox', { name: 'coding_rules.quality_profile' }), oldSeveritySelect: byRole('combobox', { name: 'coding_rules.custom_severity.choose_severity' }), mqrSwitch: byRole('switch'), newSeveritySelect: (quality: SoftwareQuality) => |