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';
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<HTMLDivElement>,
+) {
+ const { onDismiss, children, ...flagMessageProps } = props;
+ const intl = useIntl();
+ return (
+ <FlagMessage {...flagMessageProps}>
+ {children}
+ <DismissIcon
+ Icon={CloseIcon}
+ aria-label={intl.formatMessage({ id: 'dismiss' })}
+ className="sw-ml-3"
+ onClick={onDismiss}
+ size="small"
+ />
+ </FlagMessage>
+ );
+}
+
+DismissableFlagMessage.displayName = 'DismissableFlagMessage'; // so that tests don't see the obfuscated production name
+
export const StyledFlag = styled.div<{
backGroundColor: ThemeColors;
borderColor: ThemeColors;
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;
+`;
* 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)'],
expect(item).toHaveStyle({ border: color });
});
+it('should render Dismissable flag message properly', () => {
+ const dismissFunc = jest.fn();
+ render(<DismissableFlagMessage onDismiss={dismissFunc} role="status" variant="error" />);
+ 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<FCProps<typeof FlagMessage>> = {}) {
return render(
<FlagMessage role="status" variant="error" {...props}>
This is an error!
- </FlagMessage>
+ </FlagMessage>,
);
}
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';
* 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,
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';
return <Spinner />;
}
+ const header = (
+ <TableRow>
+ <ContentCell>{translate('branch_list.branch')}</ContentCell>
+ <ContentCell>{translate('branch_list.current_setting')}</ContentCell>
+ <ActionCell>{translate('branch_list.actions')}</ActionCell>
+ </TableRow>
+ );
+
return (
<div>
{previouslyNonCompliantBranchNCDs && (
previouslyNonCompliantBranchNCDs={previouslyNonCompliantBranchNCDs}
/>
)}
- <table className="data zebra">
- <thead>
- <tr>
- <th>{translate('branch_list.branch')}</th>
- <th className="nowrap huge-spacer-right">
- {translate('branch_list.current_setting')}
- </th>
- <th className="thin nowrap">{translate('branch_list.actions')}</th>
- </tr>
- </thead>
- <tbody>
- {branches.map((branch) => (
- <BranchListRow
- branch={branch}
- existingBranches={branchList.map((b) => b.name)}
- inheritedSetting={inheritedSetting}
- key={branch.name}
- onOpenEditModal={this.openEditModal}
- onResetToDefault={this.resetToDefault}
- />
- ))}
- </tbody>
- </table>
+ <Table columnCount={3} header={header}>
+ {branches.map((branch) => (
+ <BranchListRow
+ branch={branch}
+ existingBranches={branchList.map((b) => b.name)}
+ inheritedSetting={inheritedSetting}
+ key={branch.name}
+ onOpenEditModal={this.openEditModal}
+ onResetToDefault={this.resetToDefault}
+ />
+ ))}
+ </Table>
{editedBranch && (
<BranchNewCodeDefinitionSettingModal
branch={editedBranch}
* 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,
+ ActionsDropdown,
+ Badge,
+ ContentCell,
+ FlagWarningIcon,
+ InteractiveIcon,
+ ItemButton,
+ PencilIcon,
+ TableRowInteractive,
+} from 'design-system';
import * as React from 'react';
-import ActionsDropdown, { ActionsDropdownItem } from '../../../components/controls/ActionsDropdown';
import Tooltip from '../../../components/controls/Tooltip';
import BranchLikeIcon from '../../../components/icons/BranchLikeIcon';
-import WarningIcon from '../../../components/icons/WarningIcon';
import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { isNewCodeDefinitionCompliant } from '../../../helpers/new-code-definition';
const isCompliant = isNewCodeDefinitionCompliant(inheritedSetting);
return (
- <tr className={settingWarning ? 'branch-setting-warning' : ''}>
- <td className="nowrap">
- <BranchLikeIcon branchLike={branch} className="little-spacer-right" />
+ <TableRowInteractive>
+ <ContentCell>
+ <BranchLikeIcon branchLike={branch} className="sw-mr-1" />
{branch.name}
- {branch.isMain && (
- <div className="badge spacer-left">{translate('branches.main_branch')}</div>
- )}
- </td>
- <td className="huge-spacer-right nowrap">
+ {branch.isMain && <Badge className="sw-ml-1">{translate('branches.main_branch')}</Badge>}
+ </ContentCell>
+ <ContentCell>
<Tooltip overlay={settingWarning}>
<span>
- {settingWarning !== undefined && <WarningIcon className="little-spacer-right" />}
+ {settingWarning !== undefined && <FlagWarningIcon className="sw-mr-1" />}
{branch.newCodePeriod
? renderNewCodePeriodSetting(branch.newCodePeriod)
: translate('branch_list.default_setting')}
</span>
</Tooltip>
- </td>
- <td className="text-right">
- <ActionsDropdown
- label={translateWithParameters('branch_list.show_actions_for_x', branch.name)}
- >
- <ActionsDropdownItem onClick={() => props.onOpenEditModal(branch)}>
- {translate('edit')}
- </ActionsDropdownItem>
- {branch.newCodePeriod && (
- <ActionsDropdownItem
- disabled={!isCompliant}
- onClick={() => props.onResetToDefault(branch.name)}
- tooltipOverlay={
+ </ContentCell>
+ <ActionCell>
+ {!branch.newCodePeriod && (
+ <InteractiveIcon
+ Icon={PencilIcon}
+ aria-label={translate('edit')}
+ onClick={() => props.onOpenEditModal(branch)}
+ className="sw-mr-2"
+ size="small"
+ />
+ )}
+ {branch.newCodePeriod && (
+ <ActionsDropdown allowResizing id="new-code-action">
+ <Tooltip
+ overlay={
isCompliant ? null : translate('project_baseline.compliance.warning.title.project')
}
>
- {translate('reset_to_default')}
- </ActionsDropdownItem>
- )}
- </ActionsDropdown>
- </td>
- </tr>
+ <ItemButton
+ disabled={!isCompliant}
+ onClick={() => props.onResetToDefault(branch.name)}
+ >
+ {translate('reset_to_default')}
+ </ItemButton>
+ </Tooltip>
+ <ItemButton onClick={() => props.onOpenEditModal(branch)}>
+ {translate('edit')}
+ </ItemButton>
+ </ActionsDropdown>
+ )}
+ </ActionCell>
+ </TableRowInteractive>
);
}
* 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';
)}
{globalNewCodeDefinition && branchSupportEnabled && (
- <div className="huge-spacer-top branch-baseline-selector">
- <hr />
- <h2>{translate('project_baseline.configure_branches')}</h2>
+ <div className="sw-mt-6">
+ <HeadingDark className="sw-mb-4">
+ {translate('project_baseline.configure_branches')}
+ </HeadingDark>
<BranchList
branchList={branchList}
component={component}
});
await ui.appIsLoaded();
- await ui.openBranchSettingModal('main');
+ await ui.openBranchSettingModal('main branches.main_branch branch_list.default_setting');
expect(ui.specificAnalysisRadio.query()).not.toBeInTheDocument();
});
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());
});
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'),
});
it('cannot set a specific analysis setting for branch', async () => {
- const { ui } = getPageObjects();
+ const { ui, user } = getPageObjects();
newCodeDefinitionMock.setListBranchesNewCode([
mockNewCodePeriodBranch({
branchKey: 'main',
});
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();
});
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(),
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() {
}
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 {
* 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 {
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);
);
return (
- <DismissableAlertComponent
- className="sw-my-4"
- onDismiss={handleBannerDismiss}
- variant="info"
- display="banner"
- >
- <FormattedMessage
- id="new_code_definition.auto_update.branch.message"
- values={{
- date: new Date(previouslyNonCompliantBranchNCDs[0].updatedAt).toLocaleDateString(),
- branchesList,
- link: (
- <DocLink to="/project-administration/clean-as-you-code-settings/defining-new-code/#new-code-definition-options">
- {intl.formatMessage({ id: 'learn_more' })}
- </DocLink>
- ),
- }}
- />
- </DismissableAlertComponent>
+ <DismissableFlagMessage className="sw-my-4" onDismiss={handleBannerDismiss} variant="info">
+ <div>
+ <FormattedMessage
+ id="new_code_definition.auto_update.branch.message"
+ values={{
+ date: new Date(previouslyNonCompliantBranchNCDs[0].updatedAt).toLocaleDateString(),
+ branchesList,
+ link: <Link to={toUrl}>{intl.formatMessage({ id: 'learn_more' })}</Link>,
+ }}
+ />
+ </div>
+ </DismissableFlagMessage>
);
}