From e875fbe32a1cf60e6d6598f80c2b89da9fa11117 Mon Sep 17 00:00:00 2001 From: Revanshu Paliwal Date: Thu, 19 Oct 2023 16:57:34 +0200 Subject: [PATCH] SONAR-20814 Migrating project new code branch list to new UI --- .../src/components/FlagMessage.tsx | 39 +++++++++- .../components/__tests__/FlagMessage-test.tsx | 29 ++++++- .../design-system/src/components/index.ts | 2 +- .../projectNewCode/components/BranchList.tsx | 45 +++++------ .../components/BranchListRow.tsx | 77 +++++++++++-------- .../ProjectNewCodeDefinitionApp.tsx | 9 ++- .../ProjectNewCodeDefinitionApp-it.tsx | 38 +++++---- .../BranchNCDAutoUpdateMessage.tsx | 39 +++++----- 8 files changed, 182 insertions(+), 96 deletions(-) diff --git a/server/sonar-web/design-system/src/components/FlagMessage.tsx b/server/sonar-web/design-system/src/components/FlagMessage.tsx index 16a120119d0..d395d2e8e7a 100644 --- a/server/sonar-web/design-system/src/components/FlagMessage.tsx +++ b/server/sonar-web/design-system/src/components/FlagMessage.tsx @@ -20,10 +20,12 @@ import styled from '@emotion/styled'; import classNames from 'classnames'; import * as React from 'react'; +import { useIntl } from 'react-intl'; import tw from 'twin.macro'; import { themeBorder, themeColor, themeContrast } from '../helpers/theme'; import { ThemeColors } from '../types/theme'; -import { FlagErrorIcon, FlagInfoIcon, FlagSuccessIcon, FlagWarningIcon } from './icons'; +import { InteractiveIcon } from './InteractiveIcon'; +import { CloseIcon, FlagErrorIcon, FlagInfoIcon, FlagSuccessIcon, FlagWarningIcon } from './icons'; export type Variant = 'error' | 'warning' | 'success' | 'info'; @@ -79,6 +81,31 @@ export function FlagMessage(props: Props & React.HTMLAttributes) FlagMessage.displayName = 'FlagMessage'; // so that tests don't see the obfuscated production name +interface DismissableFlagMessageProps extends Props { + onDismiss: () => void; +} + +export function DismissableFlagMessage( + props: DismissableFlagMessageProps & React.HTMLAttributes, +) { + const { onDismiss, children, ...flagMessageProps } = props; + const intl = useIntl(); + return ( + + {children} + + + ); +} + +DismissableFlagMessage.displayName = 'DismissableFlagMessage'; // so that tests don't see the obfuscated production name + export const StyledFlag = styled.div<{ backGroundColor: ThemeColors; borderColor: ThemeColors; @@ -111,3 +138,13 @@ export const StyledFlag = styled.div<{ color: ${themeContrast('flagMessageBackground')}; } `; + +export const DismissIcon = styled(InteractiveIcon)` + --background: ${themeColor('productNews')}; + --backgroundHover: ${themeColor('productNewsHover')}; + --color: ${themeContrast('productNews')}; + --colorHover: ${themeContrast('productNewsHover')}; + --focus: ${themeColor('interactiveIconFocus', 0.2)}; + + height: 28px; +`; diff --git a/server/sonar-web/design-system/src/components/__tests__/FlagMessage-test.tsx b/server/sonar-web/design-system/src/components/__tests__/FlagMessage-test.tsx index 0d1e0dadede..3c8ae5f31d1 100644 --- a/server/sonar-web/design-system/src/components/__tests__/FlagMessage-test.tsx +++ b/server/sonar-web/design-system/src/components/__tests__/FlagMessage-test.tsx @@ -18,9 +18,22 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { screen } from '@testing-library/react'; +import { IntlShape } from 'react-intl'; import { render } from '../../helpers/testUtils'; import { FCProps } from '../../types/misc'; -import { FlagMessage, Variant } from '../FlagMessage'; +import { DismissableFlagMessage, FlagMessage, Variant } from '../FlagMessage'; + +jest.mock( + 'react-intl', + () => + ({ + ...jest.requireActual('react-intl'), + useIntl: () => ({ + formatMessage: ({ id }: { id: string }, values = {}) => + [id, ...Object.values(values)].join('.'), + }), + }) as IntlShape, +); it.each([ ['error', '1px solid rgb(249,112,102)'], @@ -35,10 +48,22 @@ it.each([ expect(item).toHaveStyle({ border: color }); }); +it('should render Dismissable flag message properly', () => { + const dismissFunc = jest.fn(); + render(); + const item = screen.getByRole('status'); + expect(item).toBeInTheDocument(); + expect(item).toHaveStyle({ border: '1px solid rgb(249,112,102)' }); + const dismissButton = screen.getByRole('button'); + expect(dismissButton).toBeInTheDocument(); + dismissButton.click(); + expect(dismissFunc).toHaveBeenCalled(); +}); + function renderFlagMessage(props: Partial> = {}) { return render( This is an error! - + , ); } diff --git a/server/sonar-web/design-system/src/components/index.ts b/server/sonar-web/design-system/src/components/index.ts index 16e93fbc755..9064185c0b4 100644 --- a/server/sonar-web/design-system/src/components/index.ts +++ b/server/sonar-web/design-system/src/components/index.ts @@ -39,7 +39,7 @@ export * from './FacetBox'; export * from './FacetItem'; export { FailedQGConditionLink } from './FailedQGConditionLink'; export * from './FavoriteButton'; -export { FlagMessage } from './FlagMessage'; +export { DismissableFlagMessage, FlagMessage } from './FlagMessage'; export * from './FlowStep'; export * from './HighlightedSection'; export { Histogram } from './Histogram'; diff --git a/server/sonar-web/src/main/js/apps/projectNewCode/components/BranchList.tsx b/server/sonar-web/src/main/js/apps/projectNewCode/components/BranchList.tsx index c13ca443188..5e4e4662785 100644 --- a/server/sonar-web/src/main/js/apps/projectNewCode/components/BranchList.tsx +++ b/server/sonar-web/src/main/js/apps/projectNewCode/components/BranchList.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 { ActionCell, ContentCell, Spinner, Table, TableRow } from 'design-system'; import * as React from 'react'; import { listBranchesNewCodeDefinition, @@ -27,7 +28,6 @@ import { PreviouslyNonCompliantBranchNCD, isPreviouslyNonCompliantDaysNCD, } from '../../../components/new-code-definition/utils'; -import Spinner from '../../../components/ui/Spinner'; import { isBranch, sortBranches } from '../../../helpers/branch-like'; import { translate } from '../../../helpers/l10n'; import { DEFAULT_NEW_CODE_DEFINITION_TYPE } from '../../../helpers/new-code-definition'; @@ -164,6 +164,14 @@ export default class BranchList extends React.PureComponent { return ; } + const header = ( + + {translate('branch_list.branch')} + {translate('branch_list.current_setting')} + {translate('branch_list.actions')} + + ); + return (
{previouslyNonCompliantBranchNCDs && ( @@ -172,29 +180,18 @@ export default class BranchList extends React.PureComponent { previouslyNonCompliantBranchNCDs={previouslyNonCompliantBranchNCDs} /> )} - - - - - - - - - - {branches.map((branch) => ( - b.name)} - inheritedSetting={inheritedSetting} - key={branch.name} - onOpenEditModal={this.openEditModal} - onResetToDefault={this.resetToDefault} - /> - ))} - -
{translate('branch_list.branch')} - {translate('branch_list.current_setting')} - {translate('branch_list.actions')}
+ + {branches.map((branch) => ( + b.name)} + inheritedSetting={inheritedSetting} + key={branch.name} + onOpenEditModal={this.openEditModal} + onResetToDefault={this.resetToDefault} + /> + ))} +
{editedBranch && ( - - + + + {branch.name} - {branch.isMain && ( -
{translate('branches.main_branch')}
- )} - - + {branch.isMain && {translate('branches.main_branch')}} +
+ - {settingWarning !== undefined && } + {settingWarning !== undefined && } {branch.newCodePeriod ? renderNewCodePeriodSetting(branch.newCodePeriod) : translate('branch_list.default_setting')} - - - - props.onOpenEditModal(branch)}> - {translate('edit')} - - {branch.newCodePeriod && ( - props.onResetToDefault(branch.name)} - tooltipOverlay={ + + + {!branch.newCodePeriod && ( + props.onOpenEditModal(branch)} + className="sw-mr-2" + size="small" + /> + )} + {branch.newCodePeriod && ( + + - {translate('reset_to_default')} - - )} - - - + props.onResetToDefault(branch.name)} + > + {translate('reset_to_default')} + + + props.onOpenEditModal(branch)}> + {translate('edit')} + + + )} + +
); } diff --git a/server/sonar-web/src/main/js/apps/projectNewCode/components/ProjectNewCodeDefinitionApp.tsx b/server/sonar-web/src/main/js/apps/projectNewCode/components/ProjectNewCodeDefinitionApp.tsx index c97144e92cf..448f454a305 100644 --- a/server/sonar-web/src/main/js/apps/projectNewCode/components/ProjectNewCodeDefinitionApp.tsx +++ b/server/sonar-web/src/main/js/apps/projectNewCode/components/ProjectNewCodeDefinitionApp.tsx @@ -17,6 +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 { HeadingDark } from 'design-system'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Helmet } from 'react-helmet-async'; import withAppStateContext from '../../../app/components/app-state/withAppStateContext'; @@ -218,9 +220,10 @@ function ProjectNewCodeDefinitionApp(props: Readonly -
-

{translate('project_baseline.configure_branches')}

+
+ + {translate('project_baseline.configure_branches')} + { }); await ui.appIsLoaded(); - await ui.openBranchSettingModal('main'); + await ui.openBranchSettingModal('main branches.main_branch branch_list.default_setting'); expect(ui.specificAnalysisRadio.query()).not.toBeInTheDocument(); }); @@ -188,13 +188,13 @@ it('can set a previous version setting for branch', async () => { featureList: [Feature.BranchSupport], }); await ui.appIsLoaded(); - await ui.setBranchPreviousVersionSetting('main'); + await ui.setBranchPreviousVersionSetting('main branches.main_branch branch_list.default_setting'); expect( within(byRole('table').get()).getByText('new_code_definition.previous_version'), ).toBeInTheDocument(); - await user.click(await ui.branchActionsButton('main').find()); + await user.click(await ui.branchActionsButton().find()); expect(ui.resetToDefaultButton.get()).toBeInTheDocument(); await user.click(ui.resetToDefaultButton.get()); @@ -211,7 +211,10 @@ it('can set a number of days setting for branch', async () => { }); await ui.appIsLoaded(); - await ui.setBranchNumberOfDaysSetting('main', '15'); + await ui.setBranchNumberOfDaysSetting( + 'main branches.main_branch branch_list.default_setting', + '15', + ); expect( within(byRole('table').get()).getByText('new_code_definition.number_days: 15'), @@ -219,7 +222,7 @@ it('can set a number of days setting for branch', async () => { }); it('cannot set a specific analysis setting for branch', async () => { - const { ui } = getPageObjects(); + const { ui, user } = getPageObjects(); newCodeDefinitionMock.setListBranchesNewCode([ mockNewCodePeriodBranch({ branchKey: 'main', @@ -232,8 +235,12 @@ it('cannot set a specific analysis setting for branch', async () => { }); await ui.appIsLoaded(); - await ui.openBranchSettingModal('main'); - + await user.click( + await byRole('row', { name: /main branches.main_branch/ }) + .byLabelText('menu') + .find(), + ); + await user.click(await byRole('menuitem', { name: 'edit' }).find()); expect(ui.specificAnalysisRadio.get()).toBeChecked(); expect(ui.specificAnalysisRadio.get()).toHaveClass('disabled'); expect(ui.specificAnalysisWarning.get()).toBeInTheDocument(); @@ -251,7 +258,10 @@ it('can set a reference branch setting for branch', async () => { }); await ui.appIsLoaded(); - await ui.setBranchReferenceToBranchSetting('main', 'normal-branch'); + await ui.setBranchReferenceToBranchSetting( + 'main branches.main_branch branch_list.default_setting', + 'normal-branch', + ); expect( byRole('table').byText('baseline.reference_branch: normal-branch').get(), @@ -396,12 +406,11 @@ function getPageObjects() { analysisListItem: byRole('radio', { name: /baseline.branch_analyses.analysis_for_x/ }), saveButton: byRole('button', { name: 'save' }), cancelButton: byRole('button', { name: 'cancel' }), - branchActionsButton: (branch: string) => - byRole('button', { name: `branch_list.show_actions_for_x.${branch}` }), + branchActionsButton: () => byRole('button', { name: `menu` }), editButton: byRole('button', { name: 'edit' }), - resetToDefaultButton: byRole('button', { name: 'reset_to_default' }), + resetToDefaultButton: byRole('menuitem', { name: 'reset_to_default' }), branchNCDsBanner: byText(/new_code_definition.auto_update.branch.message/), - dismissButton: byLabelText('alert.dismiss'), + dismissButton: byLabelText('dismiss'), }; async function appIsLoaded() { @@ -448,8 +457,9 @@ function getPageObjects() { } async function openBranchSettingModal(branch: string) { - await user.click(await ui.branchActionsButton(branch).find()); - await user.click(ui.editButton.get()); + await user.click( + await byRole('row', { name: branch, exact: false }).byLabelText('edit').find(), + ); } return { diff --git a/server/sonar-web/src/main/js/components/new-code-definition/BranchNCDAutoUpdateMessage.tsx b/server/sonar-web/src/main/js/components/new-code-definition/BranchNCDAutoUpdateMessage.tsx index 197ef8d285b..be25ad81f2f 100644 --- a/server/sonar-web/src/main/js/components/new-code-definition/BranchNCDAutoUpdateMessage.tsx +++ b/server/sonar-web/src/main/js/components/new-code-definition/BranchNCDAutoUpdateMessage.tsx @@ -17,12 +17,13 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +import { DismissableFlagMessage, Link } from 'design-system'; import React, { useCallback, useEffect, useState } from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; import { MessageTypes, checkMessageDismissed, setMessageDismissed } from '../../api/messages'; +import { useDocUrl } from '../../helpers/docs'; import { Component } from '../../types/types'; -import DocLink from '../common/DocLink'; -import DismissableAlertComponent from '../ui/DismissableAlertComponent'; import { PreviouslyNonCompliantBranchNCD } from './utils'; interface NCDAutoUpdateMessageProps { @@ -33,6 +34,9 @@ interface NCDAutoUpdateMessageProps { export default function NCDAutoUpdateMessage(props: NCDAutoUpdateMessageProps) { const { component, previouslyNonCompliantBranchNCDs } = props; const intl = useIntl(); + const toUrl = useDocUrl( + '/project-administration/clean-as-you-code-settings/defining-new-code/#new-code-definition-options', + ); const [dismissed, setDismissed] = useState(true); @@ -79,24 +83,17 @@ export default function NCDAutoUpdateMessage(props: NCDAutoUpdateMessageProps) { ); return ( - - - {intl.formatMessage({ id: 'learn_more' })} - - ), - }} - /> - + +
+ {intl.formatMessage({ id: 'learn_more' })}, + }} + /> +
+
); } -- 2.39.5