From 175c2eaef5237a6371adc9eda13b23cae1886229 Mon Sep 17 00:00:00 2001 From: Kevin Silva Date: Sat, 18 Nov 2023 19:12:46 +0100 Subject: [PATCH] SONAR-21019 - Project Settings - Quality Profile adopts MIUI --- .../js/app/components/GlobalContainer.tsx | 1 + .../ProjectQualityProfilesApp.tsx | 2 +- .../ProjectQualityProfilesAppRenderer.tsx | 234 +++++++++--------- .../projectQualityProfilesApp-it.tsx | 26 +- .../components/AddLanguageModal.tsx | 138 +++++------ .../LanguageProfileSelectOption.tsx | 2 +- .../components/SetQualityProfileModal.tsx | 171 ++++++------- 7 files changed, 277 insertions(+), 297 deletions(-) diff --git a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx index aa2277eb8dd..e1fd1f5f38c 100644 --- a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx @@ -64,6 +64,7 @@ const TEMP_PAGELIST_WITH_NEW_BACKGROUND_WHITE = [ '/project/links', '/project/import_export', '/project/quality_gate', + '/project/quality_profiles', ]; export default function GlobalContainer() { diff --git a/server/sonar-web/src/main/js/apps/projectQualityProfiles/ProjectQualityProfilesApp.tsx b/server/sonar-web/src/main/js/apps/projectQualityProfiles/ProjectQualityProfilesApp.tsx index b527f81c962..4f9247d5a90 100644 --- a/server/sonar-web/src/main/js/apps/projectQualityProfiles/ProjectQualityProfilesApp.tsx +++ b/server/sonar-web/src/main/js/apps/projectQualityProfiles/ProjectQualityProfilesApp.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { addGlobalSuccessMessage } from 'design-system'; import { differenceBy } from 'lodash'; import * as React from 'react'; import { @@ -28,7 +29,6 @@ import { } from '../../api/quality-profiles'; import withComponentContext from '../../app/components/componentContext/withComponentContext'; import handleRequiredAuthorization from '../../app/utils/handleRequiredAuthorization'; -import { addGlobalSuccessMessage } from '../../helpers/globalMessages'; import { translateWithParameters } from '../../helpers/l10n'; import { isDefined } from '../../helpers/types'; import { Component } from '../../types/types'; diff --git a/server/sonar-web/src/main/js/apps/projectQualityProfiles/ProjectQualityProfilesAppRenderer.tsx b/server/sonar-web/src/main/js/apps/projectQualityProfiles/ProjectQualityProfilesAppRenderer.tsx index 3af9417a960..ce39f100233 100644 --- a/server/sonar-web/src/main/js/apps/projectQualityProfiles/ProjectQualityProfilesAppRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/projectQualityProfiles/ProjectQualityProfilesAppRenderer.tsx @@ -17,17 +17,29 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { + ActionCell, + ButtonPrimary, + ContentCell, + HelperHintIcon, + InteractiveIcon, + LargeCenteredLayout, + Link, + PageContentFontWrapper, + PencilIcon, + Spinner, + Table, + TableRow, + TableRowInteractive, + Title, +} from 'design-system'; import { groupBy, orderBy } from 'lodash'; import * as React from 'react'; import { Helmet } from 'react-helmet-async'; import { Profile } from '../../api/quality-profiles'; import A11ySkipTarget from '../../components/a11y/A11ySkipTarget'; -import Link from '../../components/common/Link'; import HelpTooltip from '../../components/controls/HelpTooltip'; -import { Button } from '../../components/controls/buttons'; import Suggestions from '../../components/embed-docs-modal/Suggestions'; -import EditIcon from '../../components/icons/EditIcon'; -import PlusCircleIcon from '../../components/icons/PlusCircleIcon'; import { translate } from '../../helpers/l10n'; import { getRulesUrl } from '../../helpers/urls'; import { Component } from '../../types/types'; @@ -65,124 +77,124 @@ export default function ProjectQualityProfilesAppRenderer( const profilesByLanguage = groupBy(allProfiles, 'language'); const orderedProfiles = orderBy(projectProfiles, (p) => p.profile.languageName); + const COLUMN_WIDTHS_WITH_PURGE_SETTING = ['auto', 'auto', 'auto', '5%']; + + const header = ( + + {translate('language')} + {translate('project_quality_profile.current')} + {translate('coding_rules.filters.activation.active_rules')} + {translate('actions')} + + ); + return ( -
- - - + + + + + -
-
-

{translate('project_quality_profiles.page')}

+
+ {translate('project_quality_profiles.page')} - {translate('quality_profiles.list.projects.help')} -
- } - /> -
- + className="sw-ml-2 sw-mb-4" + overlay={translate('quality_profiles.list.projects.help')} + > + + + -
-

{translate('project_quality_profile.subtitle')}

+
+

{translate('project_quality_profiles.page.description')}

+
+ + {!loading && orderedProfiles.length > 0 && ( + + {orderedProfiles.map((projectProfile) => { + const { profile, selected } = projectProfile; -
-

- {translate('project_quality_profiles.page.description')} -

+ return ( + + + {profile.languageName} + + + + {!selected && profile.isDefault ? ( + {translate('project_quality_profile.instance_default')} + ) : ( + <> + {profile.name} + {profile.isBuiltIn && ( + + )} + + )} + + + + + {profile.activeRuleCount} + + - {loading && } + + { + props.onOpenSetProfileModal(projectProfile); + }} + size="small" + stopPropagation={false} + /> + + + ); + })} +
+ )} - {!loading && orderedProfiles.length > 0 && ( - - - - - - - - - - {orderedProfiles.map((projectProfile) => { - const { profile, selected } = projectProfile; - return ( - - - - - - - ); - })} - -
{translate('language')}{translate('project_quality_profile.current')} - {translate('coding_rules.filters.activation.active_rules')} - -
{profile.languageName} - - {!selected && profile.isDefault ? ( - {translate('project_quality_profile.instance_default')} - ) : ( - <> - {profile.name} - {profile.isBuiltIn && ( - - )} - - )} - - - - {profile.activeRuleCount} - - - -
- )} +
+
+ {translate('project_quality_profile.add_language.description')} +
-
-

{translate('project_quality_profile.add_language.title')}

+ + {translate('project_quality_profile.add_language.action')} + +
-

- {translate('project_quality_profile.add_language.description')} -

+ {showProjectProfileInModal && ( + + )} - + {showAddLanguageModal && projectProfiles && ( + p.profile.language)} + /> + )} +
- - {showProjectProfileInModal && ( - - )} - - {showAddLanguageModal && projectProfiles && ( - p.profile.language)} - /> - )}
-
-
+ + ); } diff --git a/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/projectQualityProfilesApp-it.tsx b/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/projectQualityProfilesApp-it.tsx index 0ff50093a04..205b0220482 100644 --- a/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/projectQualityProfilesApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/projectQualityProfilesApp-it.tsx @@ -17,8 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ - import userEvent from '@testing-library/user-event'; +import { addGlobalSuccessMessage } from 'design-system'; import selectEvent from 'react-select-event'; import { ProfileProject, @@ -27,7 +27,6 @@ import { searchQualityProfiles, } from '../../../api/quality-profiles'; import handleRequiredAuthorization from '../../../app/utils/handleRequiredAuthorization'; -import { addGlobalSuccessMessage } from '../../../helpers/globalMessages'; import { mockComponent } from '../../../helpers/mocks/component'; import { RenderContext, @@ -100,13 +99,10 @@ jest.mock('../../../api/quality-profiles', () => { }; }); -jest.mock('../../../helpers/globalMessages', () => { - const globalMessages = jest.requireActual('../../../helpers/globalMessages'); - return { - ...globalMessages, - addGlobalSuccessMessage: jest.fn(), - }; -}); +jest.mock('design-system', () => ({ + ...jest.requireActual('design-system'), + addGlobalSuccessMessage: jest.fn(), +})); jest.mock('../../../app/utils/handleRequiredAuthorization', () => jest.fn()); @@ -116,7 +112,7 @@ const ui = { pageTitle: byText('project_quality_profiles.page'), pageSubTitle: byText('project_quality_profile.subtitle'), pageDescription: byText('project_quality_profiles.page.description'), - helpTooltip: byLabelText('help'), + helpTooltip: byLabelText('help-tooltip'), profileRows: byRole('row'), addLanguageButton: byRole('button', { name: 'project_quality_profile.add_language.action' }), modalAddLanguageTitle: byText('project_quality_profile.add_language_modal.title'), @@ -158,9 +154,8 @@ it('should be able to add and change profile for languages', async () => { }); expect(ui.pageTitle.get()).toBeInTheDocument(); - expect(ui.pageSubTitle.get()).toBeInTheDocument(); expect(ui.pageDescription.get()).toBeInTheDocument(); - expect(ui.addLanguageButton.get()).toBeInTheDocument(); + expect(await ui.addLanguageButton.find()).toBeInTheDocument(); await expect(ui.helpTooltip.get()).toHaveATooltipWithContent( 'quality_profiles.list.projects.help', ); @@ -190,7 +185,7 @@ it('should be able to add and change profile for languages', async () => { // Updates the page after API call const htmlRow = byRole('row', { - name: 'HTML html profile 10 project_quality_profile.change_profile', + name: 'HTML html profile 10', }); expect(ui.htmlLanguage.get()).toBeInTheDocument(); @@ -231,15 +226,14 @@ it('should call authorization api when permissions is not proper', () => { expect(handleRequiredAuthorization).toHaveBeenCalled(); }); -it('should still show page with add language button when api fails', () => { +it('should still show page with add language button when api fails', async () => { jest.mocked(searchQualityProfiles).mockRejectedValueOnce(null); jest.mocked(getProfileProjects).mockRejectedValueOnce(null); renderProjectQualityProfilesApp(); expect(ui.pageTitle.get()).toBeInTheDocument(); - expect(ui.pageSubTitle.get()).toBeInTheDocument(); expect(ui.pageDescription.get()).toBeInTheDocument(); - expect(ui.addLanguageButton.get()).toBeInTheDocument(); + expect(await ui.addLanguageButton.find()).toBeInTheDocument(); }); function renderProjectQualityProfilesApp( diff --git a/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/AddLanguageModal.tsx b/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/AddLanguageModal.tsx index d09f755fc04..887e4a35bf6 100644 --- a/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/AddLanguageModal.tsx +++ b/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/AddLanguageModal.tsx @@ -17,13 +17,12 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { ButtonPrimary, FormField, InputSelect, Modal } from 'design-system'; import { difference } from 'lodash'; import * as React from 'react'; import { Profile } from '../../../api/quality-profiles'; import withLanguagesContext from '../../../app/components/languages/withLanguagesContext'; -import Select, { LabelValueSelectOption } from '../../../components/controls/Select'; -import SimpleModal from '../../../components/controls/SimpleModal'; -import { ButtonLink, SubmitButton } from '../../../components/controls/buttons'; +import { LabelValueSelectOption } from '../../../components/controls/Select'; import { translate } from '../../../helpers/l10n'; import { Languages } from '../../../types/languages'; import { Dict } from '../../../types/types'; @@ -62,80 +61,71 @@ export function AddLanguageModal(props: AddLanguageModalProps) { })) : []; - return ( - { - if (language && key) { - props.onSubmit(key); - } - }} - > - {({ onCloseClick, onFormSubmit, submitting }) => ( - <> -
-

{header}

-
+ const onFormSubmit = (event: React.FormEvent) => { + event.preventDefault(); + if (language && key) { + props.onSubmit(key); + } + }; -
-
-
-
- -
- setSelected({ language, key: value })} - options={profileOptions} - components={{ - Option: LanguageProfileSelectOption, - }} - value={profileOptions.find((o) => o.value === key) ?? null} - /> -
-
+ + setSelected({ language, key: value })} + options={profileOptions} + components={{ + Option: LanguageProfileSelectOption, + }} + value={profileOptions.find((o) => o.value === key) ?? null} + /> + + +
+ ); -
- {submitting && } - - {translate('save')} - - - {translate('cancel')} - -
- - - )} -
+ return ( + + {translate('save')} + + } + secondaryButtonLabel={translate('cancel')} + /> ); } diff --git a/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/LanguageProfileSelectOption.tsx b/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/LanguageProfileSelectOption.tsx index 6e83258278e..572e5f96016 100644 --- a/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/LanguageProfileSelectOption.tsx +++ b/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/LanguageProfileSelectOption.tsx @@ -17,10 +17,10 @@ * 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 } from 'design-system'; import * as React from 'react'; import { components, OptionProps } from 'react-select'; import DisableableSelectOption from '../../../components/common/DisableableSelectOption'; -import Link from '../../../components/common/Link'; import { LabelValueSelectOption } from '../../../components/controls/Select'; import { translate } from '../../../helpers/l10n'; import { getQualityProfileUrl } from '../../../helpers/urls'; diff --git a/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/SetQualityProfileModal.tsx b/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/SetQualityProfileModal.tsx index b79bc7c10fc..de4b10b22b2 100644 --- a/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/SetQualityProfileModal.tsx +++ b/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/SetQualityProfileModal.tsx @@ -17,13 +17,16 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { + ButtonPrimary, + FlagMessage, + InputSelect, + LightLabel, + Modal, + RadioButton, +} from 'design-system'; import * as React from 'react'; import { Profile } from '../../../api/quality-profiles'; -import Radio from '../../../components/controls/Radio'; -import Select from '../../../components/controls/Select'; -import SimpleModal from '../../../components/controls/SimpleModal'; -import { ButtonLink, SubmitButton } from '../../../components/controls/buttons'; -import { Alert } from '../../../components/ui/Alert'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { Component } from '../../../types/types'; import BuiltInQualityProfileBadge from '../../quality-profiles/components/BuiltInQualityProfileBadge'; @@ -68,99 +71,79 @@ export default function SetQualityProfileModal(props: SetQualityProfileModalProp hasSelectedSysDefault ? p.key === defaultProfile.key : p.key === selected, ); - return ( - - props.onSubmit(hasSelectedSysDefault ? undefined : selected, currentProfile.key) - } - > - {({ onCloseClick, onFormSubmit, submitting }) => ( - <> -
-

{header}

+ const handleFormSubmit = (event: React.SyntheticEvent) => { + event.preventDefault(); + props.onSubmit(hasSelectedSysDefault ? undefined : selected, currentProfile.key); + }; + + const renderForm = ( +
+
+ setSelected(USE_SYSTEM_DEFAULT)} + value={USE_SYSTEM_DEFAULT} + > +
+
{translate('project_quality_profile.always_use_default')}
+ + + {translate('current_noun')}: {defaultProfile?.name} + + {defaultProfile?.isBuiltIn && } +
+
- -
-
- setSelected(USE_SYSTEM_DEFAULT)} - value={USE_SYSTEM_DEFAULT} - > -
-
- {translate('project_quality_profile.always_use_default')} -
-
- {translate('current_noun')}: - {defaultProfile.name} - {defaultProfile.isBuiltIn && ( - - )} -
-
-
-
+ { + if (hasSelectedSysDefault) { + setSelected(value); + } + }} + value={currentProfile.key} + > +
{translate('project_quality_profile.always_use_specific')}
+
-
- { - if (hasSelectedSysDefault) { - setSelected(value); - } - }} - value={currentProfile.key} - > -
-
- {translate('project_quality_profile.always_use_specific')} -
-
-