From d8886186af2dced89b74843de577e6813729e8f1 Mon Sep 17 00:00:00 2001 From: stanislavh Date: Tue, 3 Oct 2023 14:46:42 +0200 Subject: [PATCH] SONAR-20366 Migrate profiles list page --- .../components/input/SearchSelectDropdown.tsx | 19 +- .../input/SearchSelectDropdownControl.tsx | 31 ++- .../js/app/components/GlobalContainer.tsx | 3 +- .../apps/issues/components/AssigneeSelect.tsx | 1 - .../quality-gates/components/MetricSelect.tsx | 1 - ...QualityGatePermissionsAddModalRenderer.tsx | 2 - .../__tests__/QualityProfileApp-it.tsx | 6 +- .../__tests__/QualityProfilesApp-it.tsx | 4 +- .../compare/ComparisonForm.tsx | 4 - .../components/ProfileNotFound.tsx | 21 +- .../apps/quality-profiles/home/Evolution.tsx | 2 +- .../home/EvolutionDeprecated.tsx | 227 +++++++++--------- .../quality-profiles/home/EvolutionRules.tsx | 172 +++++-------- .../home/EvolutionStagnant.tsx | 58 +++-- .../quality-profiles/home/HomeContainer.tsx | 8 +- .../quality-profiles/home/LanguageSelect.tsx | 77 ++++++ .../apps/quality-profiles/home/PageHeader.tsx | 141 +++++------ .../quality-profiles/home/ProfilesList.tsx | 155 ++++++------ .../home/ProfilesListHeader.tsx | 72 ------ .../quality-profiles/home/ProfilesListRow.tsx | 79 +++--- .../main/js/apps/quality-profiles/styles.css | 78 ------ .../src/main/js/components/hoc/withRouter.tsx | 53 ++-- .../resources/org/sonar/l10n/core.properties | 20 +- 23 files changed, 562 insertions(+), 672 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/quality-profiles/home/LanguageSelect.tsx delete mode 100644 server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListHeader.tsx diff --git a/server/sonar-web/design-system/src/components/input/SearchSelectDropdown.tsx b/server/sonar-web/design-system/src/components/input/SearchSelectDropdown.tsx index ecbd8e59d55..355b232f9a3 100644 --- a/server/sonar-web/design-system/src/components/input/SearchSelectDropdown.tsx +++ b/server/sonar-web/design-system/src/components/input/SearchSelectDropdown.tsx @@ -80,7 +80,9 @@ export function SearchSelectDropdown< menuIsOpen, onChange, onInputChange, + isClearable, zLevel = PopupZLevel.Global, + placeholder = '', ...rest } = props; const [open, setOpen] = React.useState(false); @@ -94,9 +96,11 @@ export function SearchSelectDropdown< const ref = React.useRef>(null); + const computedControlLabel = controlLabel ?? (value as Option | undefined)?.label ?? null; + const toggleDropdown = React.useCallback( (value?: boolean) => { - setOpen(value === undefined ? !open : value); + setOpen(value ?? !open); }, [open], ); @@ -131,6 +135,13 @@ export function SearchSelectDropdown< [onInputChange], ); + const handleClear = () => { + onChange?.(null as OnChangeValue, { + action: 'clear', + removedValues: [], + }); + }; + React.useEffect(() => { if (open) { ref.current?.inputRef?.select(); @@ -164,7 +175,9 @@ export function SearchSelectDropdown< minLength={minLength} onChange={handleChange} onInputChange={handleInputChange} + placeholder={placeholder} selectRef={ref} + value={value} /> @@ -176,8 +189,10 @@ export function SearchSelectDropdown< ariaLabel={controlAriaLabel} className={className} disabled={isDisabled} + isClearable={isClearable && Boolean(value)} isDiscreet={isDiscreet} - label={controlLabel} + label={computedControlLabel} + onClear={handleClear} onClick={() => { toggleDropdown(true); }} diff --git a/server/sonar-web/design-system/src/components/input/SearchSelectDropdownControl.tsx b/server/sonar-web/design-system/src/components/input/SearchSelectDropdownControl.tsx index 17f36ac498f..bba4a3623a7 100644 --- a/server/sonar-web/design-system/src/components/input/SearchSelectDropdownControl.tsx +++ b/server/sonar-web/design-system/src/components/input/SearchSelectDropdownControl.tsx @@ -20,18 +20,22 @@ import styled from '@emotion/styled'; import classNames from 'classnames'; +import { useIntl } from 'react-intl'; import tw from 'twin.macro'; import { INPUT_SIZES, themeBorder, themeColor, themeContrast } from '../../helpers'; import { Key } from '../../helpers/keyboard'; import { InputSizeKeys } from '../../types/theme'; -import { ChevronDownIcon } from '../icons'; +import { InteractiveIcon } from '../InteractiveIcon'; +import { ChevronDownIcon, CloseIcon } from '../icons'; interface SearchSelectDropdownControlProps { ariaLabel?: string; className?: string; disabled?: boolean; + isClearable?: boolean; isDiscreet?: boolean; label?: React.ReactNode | string; + onClear: VoidFunction; onClick: VoidFunction; placeholder?: string; size?: InputSizeKeys; @@ -43,11 +47,16 @@ export function SearchSelectDropdownControl(props: SearchSelectDropdownControlPr disabled, placeholder, label, + isClearable, isDiscreet, + onClear, onClick, size = 'full', ariaLabel = '', } = props; + + const intl = useIntl(); + return ( - {label ?? placeholder} - + {label ?? placeholder} +
+ {isClearable && ( + { + onClear(); + }} + size="small" + /> + )} + +
); @@ -91,7 +113,7 @@ const StyledControl = styled.div` ${tw`sw-flex sw-justify-between sw-items-center`}; ${tw`sw-rounded-2`}; ${tw`sw-box-border`}; - ${tw`sw-px-3 sw-py-2`}; + ${tw`sw-px-3`}; ${tw`sw-body-sm`}; ${tw`sw-h-control`}; ${tw`sw-leading-4`}; @@ -128,6 +150,7 @@ const StyledControl = styled.div` `; const InputValue = styled.span` + height: 100%; width: 100%; color: ${themeContrast('inputBackground')}; 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 31d888d3c1d..375edebed50 100644 --- a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx @@ -45,8 +45,7 @@ const TEMP_PAGELIST_WITH_NEW_BACKGROUND = [ '/project/issues', '/project/activity', '/code', - '/profiles/show', - '/profiles/compare', + '/profiles', '/project/extension/securityreport/securityreport', '/projects', '/project/information', diff --git a/server/sonar-web/src/main/js/apps/issues/components/AssigneeSelect.tsx b/server/sonar-web/src/main/js/apps/issues/components/AssigneeSelect.tsx index 2f371153229..56df9ef1fe2 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/AssigneeSelect.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/AssigneeSelect.tsx @@ -91,7 +91,6 @@ export default function AssigneeSelect(props: AssigneeSelectProps) { size="full" controlSize="full" inputId={inputId} - isClearable defaultOptions={defaultOptions} loadOptions={handleAssigneeSearch} onChange={props.onAssigneeSelect} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/MetricSelect.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/MetricSelect.tsx index cc4f2cc0a2c..7790b657c46 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/MetricSelect.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/MetricSelect.tsx @@ -85,7 +85,6 @@ export function MetricSelect({ metric, metricsArray, metrics, onMetricChange }: size="large" controlSize="full" inputId="condition-metric" - isClearable defaultOptions={optionsWithDomains} loadOptions={handleAssigneeSearch} onChange={handleChange} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsAddModalRenderer.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsAddModalRenderer.tsx index 42c02834648..bf4200dd188 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsAddModalRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsAddModalRenderer.tsx @@ -73,8 +73,6 @@ export default function QualityGatePermissionsAddModalRenderer( controlAriaLabel={translate('quality_gates.permissions.search')} inputId={USER_SELECT_INPUT_ID} autoFocus - isClearable={false} - placeholder="" defaultOptions noOptionsMessage={() => translate('no_results')} onChange={props.onSelection} diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/__tests__/QualityProfileApp-it.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/__tests__/QualityProfileApp-it.tsx index d1ddc9772cf..88bbd3f7bc9 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/__tests__/QualityProfileApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/__tests__/QualityProfileApp-it.tsx @@ -71,7 +71,7 @@ const ui = { renameButton: byRole('menuitem', { name: 'rename' }), setAsDefaultButton: byRole('menuitem', { name: 'set_as_default' }), newNameInput: byRole('textbox', { name: /quality_profiles.new_name/ }), - qualityProfilePageLink: byRole('link', { name: 'quality_profiles.page' }), + qualityProfilePageLink: byRole('link', { name: 'quality_profiles.back_to_list' }), rulesTotalRow: byRole('row', { name: /total/ }), rulesBugsRow: byRole('row', { name: /issue.type.BUG.plural/ }), rulesVulnerabilitiesRow: byRole('row', { name: /issue.type.VULNERABILITY/ }), @@ -515,7 +515,9 @@ describe('Every Users', () => { renderQualityProfile('i-dont-exist'); await ui.waitForDataLoaded(); - expect(await screen.findByText('quality_profiles.not_found')).toBeInTheDocument(); + expect( + await screen.findByRole('heading', { name: 'quality_profiles.not_found' }), + ).toBeInTheDocument(); expect(ui.qualityProfilePageLink.get()).toBeInTheDocument(); }); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/__tests__/QualityProfilesApp-it.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/__tests__/QualityProfilesApp-it.tsx index 9afe310a39b..cf51ccf8c44 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/__tests__/QualityProfilesApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/__tests__/QualityProfilesApp-it.tsx @@ -85,7 +85,7 @@ const ui = { }), activateConfirmButton: byRole('button', { name: 'coding_rules.activate' }), namePropupInput: byRole('textbox', { name: 'quality_profiles.new_name required' }), - filterByLang: byRole('combobox', { name: 'quality_profiles.filter_by:' }), + filterByLang: byRole('combobox', { name: 'quality_profiles.select_lang' }), listLinkCQualityProfile: byRole('link', { name: 'c quality profile' }), headingNewCQualityProfile: byRole('heading', { name: 'New c quality profile' }), headingNewCQualityProfileFromCreateButton: byRole('heading', { @@ -113,7 +113,7 @@ const ui = { stagnantProfilesRegion: byRole('region', { name: 'quality_profiles.stagnant_profiles' }), recentlyAddedRulesRegion: byRole('region', { name: 'quality_profiles.latest_new_rules' }), newRuleLink: byRole('link', { name: 'Recently Added Rule' }), - seeAllNewRulesLink: byRole('link', { name: 'see_all 20 quality_profiles.latest_new_rules' }), + seeAllNewRulesLink: byRole('link', { name: 'quality_profiles.latest_new_rules.see_all_x.20' }), }; it('should list Quality Profiles and filter by language', async () => { diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonForm.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonForm.tsx index ea5c66a1e52..61380ed4cbf 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonForm.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonForm.tsx @@ -45,8 +45,6 @@ export default function ComparisonForm(props: Readonly) { .filter((p) => p.language === profile.language && p !== profile) .map((p) => ({ value: p.key, label: p.name, isDefault: p.isDefault })); - const value = options.find((o) => o.value === withKey); - const handleProfilesSearch = React.useCallback( (query: string, cb: (options: Options