expect(screen.getByText(name)).toBeInTheDocument();
- expect(screen.getByText('my_account.profile')).toBeInTheDocument();
- expect(screen.getByText('my_account.security')).toBeInTheDocument();
- expect(screen.getByText('my_account.notifications')).toBeInTheDocument();
- expect(screen.getByText('my_account.projects')).toBeInTheDocument();
+ const topMenuNavigationItems = [
+ 'my_account.profile',
+ 'my_account.security',
+ 'my_account.notifications',
+ 'my_account.projects',
+ ];
+ topMenuNavigationItems.forEach((itemName) => {
+ expect(byRole('navigation').byRole('link', { name: itemName }).get()).toBeInTheDocument();
+ });
});
describe('profile page', () => {
addButton: byRole('button', { name: 'my_profile.per_project_notifications.add' }),
addModalButton: byRole('button', { name: 'add_verb' }),
searchInput: byRole('searchbox', { name: 'search.placeholder' }),
- sonarQubeProject: byRole('heading', { name: 'SonarQube' }),
+ sonarQubeProject: byRole('link', { name: 'SonarQube' }),
checkbox: (type: NotificationProjectType) =>
byRole('checkbox', {
- name: `notification.dispatcher.descrption_x.notification.dispatcher.${type}.project`,
+ name: `notification.dispatcher.description_x.notification.dispatcher.${type}.project`,
}),
};
noNotificationForProject: byText('my_account.no_project_notifications'),
checkbox: (type: NotificationGlobalType) =>
byRole('checkbox', {
- name: `notification.dispatcher.descrption_x.notification.dispatcher.${type}`,
+ name: `notification.dispatcher.description_x.notification.dispatcher.${type}`,
}),
};
await user.click(await projectUI.addButton.find());
expect(projectUI.addModalButton.get()).toBeDisabled();
- await user.type(projectUI.searchInput.get(), 'sonar');
+
+ await user.keyboard('sonar');
// navigate within the two results, choose the first:
await user.keyboard('[ArrowDown][ArrowDown][ArrowUp][Enter]');
await user.click(projectUI.addModalButton.get());
renderAccountApp(mockLoggedInUser(), notificationsPagePath);
- await user.click(
- await screen.findByRole('button', { name: 'my_profile.per_project_notifications.add' }),
- );
- expect(screen.getByLabelText('search.placeholder', { selector: 'input' })).toBeInTheDocument();
+ await user.click(await projectUI.addButton.find());
+
+ expect(screen.getByLabelText('my_account.set_notifications_for.title')).toBeInTheDocument();
+
await user.keyboard('sonarqube');
await user.click(screen.getByText('SonarQube'));
await user.click(screen.getByRole('searchbox'));
await user.keyboard('bla');
- expect(screen.queryByRole('heading', { name: 'SonarQube' })).not.toBeInTheDocument();
+ expect(projectUI.sonarQubeProject.query()).not.toBeInTheDocument();
await user.keyboard('[Backspace>3/]');
- expect(await screen.findByRole('heading', { name: 'SonarQube' })).toBeInTheDocument();
+ expect(await projectUI.sonarQubeProject.find()).toBeInTheDocument();
});
});
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
+import { PageTitle, Table } from 'design-system';
import * as React from 'react';
import { translate } from '../../../helpers/l10n';
import { Notification, NotificationGlobalType } from '../../../types/notifications';
interface Props {
addNotification: (n: Notification) => void;
channels: string[];
+ header?: React.JSX.Element;
notifications: Notification[];
removeNotification: (n: Notification) => void;
types: NotificationGlobalType[];
}
-export default function GlobalNotifications(props: Props) {
+export default function GlobalNotifications(props: Readonly<Props>) {
return (
- <section className="boxed-group">
- <h2>{translate('my_profile.overall_notifications.title')}</h2>
+ <>
+ <PageTitle className="sw-mb-4" text={translate('my_profile.overall_notifications.title')} />
- <div className="boxed-group-inner">
- <table className="data zebra">
- <thead>
- <tr>
- <th>{translate('notification.notification')}</th>
- {props.channels.map((channel) => (
- <th className="text-center" key={channel}>
- <h4>{translate('notification.channel', channel)}</h4>
- </th>
- ))}
- </tr>
- </thead>
+ {!props.header && (
+ <div className="sw-body-sm-highlight sw-mb-2">{translate('notifications.send_email')}</div>
+ )}
- <NotificationsList
- channels={props.channels}
- checkboxId={getCheckboxId}
- notifications={props.notifications}
- onAdd={props.addNotification}
- onRemove={props.removeNotification}
- types={props.types}
- />
- </table>
- </div>
- </section>
+ <Table className="sw-w-full" columnCount={2} header={props.header ?? null}>
+ <NotificationsList
+ channels={props.channels}
+ checkboxId={getCheckboxId}
+ notifications={props.notifications}
+ onAdd={props.addNotification}
+ onRemove={props.removeNotification}
+ types={props.types}
+ />
+ </Table>
+ </>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
+import { FlagMessage, GreySeparator, Spinner, Title } from 'design-system';
import { partition } from 'lodash';
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
import {
- withNotifications,
WithNotificationsProps,
+ withNotifications,
} from '../../../components/hoc/withNotifications';
-import { Alert } from '../../../components/ui/Alert';
-import Spinner from '../../../components/ui/Spinner';
import { translate } from '../../../helpers/l10n';
import GlobalNotifications from './GlobalNotifications';
import Projects from './Projects';
-export function Notifications(props: WithNotificationsProps) {
- const {
- addNotification,
- channels,
- globalTypes,
- loading,
- notifications,
- perProjectTypes,
- removeNotification,
- } = props;
-
+export function Notifications({
+ addNotification,
+ channels,
+ globalTypes,
+ loading,
+ notifications,
+ perProjectTypes,
+ removeNotification,
+}: Readonly<WithNotificationsProps>) {
const [globalNotifications, projectNotifications] = partition(notifications, (n) => !n.project);
+ const emailOnly = channels.length === 1 && channels[0] === 'EmailNotificationChannel';
+
+ const header = emailOnly ? undefined : (
+ <tr>
+ <th className="sw-body-sm-highlight">{translate('events')}</th>
+
+ {channels.map((channel) => (
+ <th className="sw-body-sm-highlight sw-text-right" key={channel}>
+ {translate('notification.channel', channel)}
+ </th>
+ ))}
+ </tr>
+ );
+
return (
- <div className="account-body account-container">
+ <div className="it__account-body">
<Helmet defer={false} title={translate('my_account.notifications')} />
- <Alert variant="info">{translate('notification.dispatcher.information')}</Alert>
+
+ <Title>{translate('my_account.notifications')}</Title>
+
+ <FlagMessage className="sw-my-2" variant="info">
+ {translate('notification.dispatcher.information')}
+ </FlagMessage>
+
<Spinner loading={loading}>
{notifications && (
<>
+ <GreySeparator className="sw-mb-4 sw-mt-6" />
+
<GlobalNotifications
addNotification={addNotification}
channels={channels}
+ header={header}
notifications={globalNotifications}
removeNotification={removeNotification}
types={globalTypes}
/>
+
+ <GreySeparator className="sw-mb-4 sw-mt-6" />
+
<Projects
addNotification={addNotification}
channels={channels}
+ header={header}
notifications={projectNotifications}
removeNotification={removeNotification}
types={perProjectTypes}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
+import { CellComponent, Checkbox, TableRowInteractive } from 'design-system';
import * as React from 'react';
-import Checkbox from '../../../components/controls/Checkbox';
import { hasMessage, translate, translateWithParameters } from '../../../helpers/l10n';
import {
Notification,
} from '../../../types/notifications';
interface Props {
- onAdd: (n: Notification) => void;
- onRemove: (n: Notification) => void;
channels: string[];
checkboxId: (type: string, channel: string) => string;
+ notifications: Notification[];
+ onAdd: (n: Notification) => void;
+ onRemove: (n: Notification) => void;
project?: boolean;
types: (NotificationGlobalType | NotificationProjectType)[];
- notifications: Notification[];
}
-export default class NotificationsList extends React.PureComponent<Props> {
- isEnabled(type: string, channel: string) {
- return !!this.props.notifications.find(
+export default function NotificationsList({
+ channels,
+ checkboxId,
+ notifications,
+ onAdd,
+ onRemove,
+ project,
+ types,
+}: Readonly<Props>) {
+ const isEnabled = (type: string, channel: string) =>
+ !!notifications.find(
(notification) => notification.type === type && notification.channel === channel,
);
- }
- handleCheck(type: string, channel: string, checked: boolean) {
+ const handleCheck = (type: string, channel: string, checked: boolean) => {
if (checked) {
- this.props.onAdd({ type, channel });
+ onAdd({ type, channel });
} else {
- this.props.onRemove({ type, channel });
+ onRemove({ type, channel });
}
- }
+ };
- getDispatcherLabel(dispatcher: string) {
+ const getDispatcherLabel = (dispatcher: string) => {
const globalMessageKey = ['notification.dispatcher', dispatcher];
const projectMessageKey = [...globalMessageKey, 'project'];
- const shouldUseProjectMessage = this.props.project && hasMessage(...projectMessageKey);
+ const shouldUseProjectMessage = project && hasMessage(...projectMessageKey);
+
return shouldUseProjectMessage
? translate(...projectMessageKey)
: translate(...globalMessageKey);
- }
+ };
- render() {
- const { channels, checkboxId, types } = this.props;
+ return types.map((type) => (
+ <TableRowInteractive className="sw-h-9" key={type}>
+ <CellComponent className="sw-py-0 sw-border-0">{getDispatcherLabel(type)}</CellComponent>
- return (
- <tbody>
- {types.map((type) => (
- <tr key={type}>
- <td>{this.getDispatcherLabel(type)}</td>
- {channels.map((channel) => (
- <td className="text-center" key={channel}>
- <Checkbox
- label={translateWithParameters(
- 'notification.dispatcher.descrption_x',
- this.getDispatcherLabel(type),
- )}
- checked={this.isEnabled(type, channel)}
- id={checkboxId(type, channel)}
- onCheck={(checked) => this.handleCheck(type, channel, checked)}
- />
- </td>
- ))}
- </tr>
- ))}
- </tbody>
- );
- }
+ {channels.map((channel) => (
+ <CellComponent className="sw-py-0 sw-border-0" key={channel}>
+ <div className="sw-justify-end sw-flex sw-items-center">
+ <Checkbox
+ checked={isEnabled(type, channel)}
+ id={checkboxId(type, channel)}
+ label={translateWithParameters(
+ 'notification.dispatcher.description_x',
+ getDispatcherLabel(type),
+ )}
+ onCheck={(checked) => handleCheck(type, channel, checked)}
+ />
+ </div>
+ </CellComponent>
+ ))}
+ </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 classNames from 'classnames';
+
+import {
+ ButtonPrimary,
+ DropdownMenu,
+ InputSearch,
+ ItemButton,
+ Modal,
+ Popup,
+ PopupPlacement,
+ PopupZLevel,
+ Spinner,
+} from 'design-system';
import * as React from 'react';
import { getSuggestions } from '../../../api/components';
-import { ResetButtonLink, SubmitButton } from '../../../components/controls/buttons';
-import { DropdownOverlay } from '../../../components/controls/Dropdown';
-import SearchBox from '../../../components/controls/SearchBox';
-import SimpleModal from '../../../components/controls/SimpleModal';
import { KeyboardKeys } from '../../../helpers/keycodes';
import { translate } from '../../../helpers/l10n';
+import { ComponentQualifier } from '../../../types/component';
import { NotificationProject } from '../../../types/notifications';
interface Props {
highlighted?: NotificationProject;
loading?: boolean;
query?: string;
- open?: boolean;
selectedProject?: NotificationProject;
suggestions?: NotificationProject[];
}
event.preventDefault();
this.handleSelectHighlighted();
break;
+
case KeyboardKeys.UpArrow:
event.preventDefault();
this.handleHighlightPrevious();
break;
+
case KeyboardKeys.DownArrow:
event.preventDefault();
this.handleHighlightNext();
getCurrentIndex = () => {
const { highlighted, suggestions } = this.state;
+
return highlighted && suggestions
? suggestions.findIndex((suggestion) => suggestion.project === highlighted.project)
: -1;
highlightIndex = (index: number) => {
const { suggestions } = this.state;
+
if (suggestions && suggestions.length > 0) {
if (index < 0) {
index = suggestions.length - 1;
} else if (index >= suggestions.length) {
index = 0;
}
+
this.setState({
highlighted: suggestions[index],
});
};
handleSelectHighlighted = () => {
- const { highlighted, selectedProject } = this.state;
+ const { highlighted } = this.state;
+
if (highlighted !== undefined) {
- if (selectedProject !== undefined && highlighted.project === selectedProject.project) {
- this.handleSubmit();
- } else {
- this.handleSelect(highlighted);
- }
+ this.handleSelect(highlighted);
}
};
const { addedProjects } = this.props;
if (query.length < 2) {
- this.setState({ open: false, query });
+ this.setState({ query, selectedProject: undefined, suggestions: undefined });
+
return;
}
- this.setState({ loading: true, query });
- getSuggestions(query).then(
+ this.setState({ loading: true, query, selectedProject: undefined });
+
+ getSuggestions(query, undefined, ComponentQualifier.Project).then(
(r) => {
if (this.mounted) {
let suggestions = undefined;
- const projects = r.results.find((domain) => domain.q === 'TRK');
+
+ const projects = r.results.find((domain) => domain.q === ComponentQualifier.Project);
+
if (projects && projects.items.length > 0) {
suggestions = projects.items
.filter((item) => !addedProjects.find((p) => p.project === item.key))
projectName: item.name,
}));
}
- this.setState({ loading: false, open: true, suggestions });
+
+ this.setState({ loading: false, suggestions });
}
},
() => {
if (this.mounted) {
- this.setState({ loading: false, open: false });
+ this.setState({ loading: false });
}
},
);
handleSelect = (selectedProject: NotificationProject) => {
this.setState({
- open: false,
query: selectedProject.projectName,
selectedProject,
+ suggestions: undefined,
});
};
handleSubmit = () => {
const { selectedProject } = this.state;
+
if (selectedProject) {
this.props.onSubmit(selectedProject);
}
render() {
const { closeModal } = this.props;
- const { highlighted, loading, query, open, selectedProject, suggestions } = this.state;
- const header = translate('my_account.set_notifications_for.title');
+ const { highlighted, loading, query, selectedProject, suggestions } = this.state;
+
+ const projectSuggestion = (suggestion: NotificationProject) => (
+ <ItemButton
+ className="sw-my-1"
+ key={suggestion.project}
+ onClick={() => this.handleSelect(suggestion)}
+ selected={
+ highlighted?.project === suggestion.project ||
+ selectedProject?.project === suggestion.project
+ }
+ >
+ {suggestion.projectName}
+ </ItemButton>
+ );
+
+ const isSearching = query?.length && !selectedProject;
+
+ const noResults = isSearching ? (
+ <div className="sw-mx-5 sw-my-3">{translate('no_results')}</div>
+ ) : undefined;
+
return (
- <SimpleModal header={header} onClose={closeModal} onSubmit={this.handleSubmit}>
- {({ onCloseClick, onFormSubmit }) => (
- <form onSubmit={onFormSubmit}>
- <header className="modal-head">
- <h2>{header}</h2>
- </header>
- <div className="modal-body">
- <div className="modal-field abs-width-400">
- <label>{translate('my_account.set_notifications_for')}</label>
- <SearchBox
- autoFocus
- onChange={this.handleSearch}
- onKeyDown={this.handleKeyDown}
- placeholder={translate('search.placeholder')}
- value={query}
- />
-
- {loading && <i className="spinner spacer-left" />}
-
- {!loading && open && (
- <div className="position-relative">
- <DropdownOverlay className="abs-width-400" noPadding>
+ <Modal
+ body={
+ <form id="project-notifications-modal-form" onSubmit={this.handleSubmit}>
+ <Popup
+ allowResizing
+ overlay={
+ isSearching ? (
+ <DropdownMenu
+ className="sw-overflow-x-hidden sw-min-w-abs-350"
+ maxHeight="38rem"
+ size="auto"
+ >
+ <Spinner className="sw-mx-5 sw-my-3" loading={!!loading}>
{suggestions && suggestions.length > 0 ? (
- <ul className="notifications-add-project-search-results">
- {suggestions.map((suggestion) => (
- <li
- className={classNames({
- active: highlighted && highlighted.project === suggestion.project,
- })}
- key={suggestion.project}
- onClick={() => this.handleSelect(suggestion)}
- >
- {suggestion.projectName}
- </li>
- ))}
+ <ul className="sw-py-2">
+ {suggestions.map((suggestion) => projectSuggestion(suggestion))}
</ul>
) : (
- <div className="notifications-add-project-no-search-results">
- {translate('no_results')}
- </div>
+ noResults
)}
- </DropdownOverlay>
- </div>
- )}
- </div>
- </div>
- <footer className="modal-foot">
- <div>
- <SubmitButton disabled={selectedProject === undefined}>
- {translate('add_verb')}
- </SubmitButton>
- <ResetButtonLink onClick={onCloseClick}>{translate('cancel')}</ResetButtonLink>
- </div>
- </footer>
+ </Spinner>
+ </DropdownMenu>
+ ) : undefined
+ }
+ placement={PopupPlacement.BottomLeft}
+ zLevel={PopupZLevel.Global}
+ >
+ <InputSearch
+ autoFocus
+ className="sw-my-2"
+ onChange={this.handleSearch}
+ onKeyDown={this.handleKeyDown}
+ placeholder={translate('my_account.set_notifications_for')}
+ searchInputAriaLabel={translate('search_verb')}
+ size="full"
+ value={query}
+ />
+ </Popup>
</form>
- )}
- </SimpleModal>
+ }
+ headerTitle={translate('my_account.set_notifications_for.title')}
+ onClose={closeModal}
+ primaryButton={
+ <ButtonPrimary
+ disabled={selectedProject === undefined}
+ form="project-notifications-modal-form"
+ type="submit"
+ >
+ {translate('add_verb')}
+ </ButtonPrimary>
+ }
+ secondaryButtonLabel={translate('cancel')}
+ />
);
}
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import classNames from 'classnames';
+import { Link, Table } from 'design-system';
import * as React from 'react';
-import BoxedGroupAccordion from '../../../components/controls/BoxedGroupAccordion';
import { translate } from '../../../helpers/l10n';
+import { getProjectUrl } from '../../../helpers/urls';
import {
Notification,
NotificationProject,
interface Props {
addNotification: (n: Notification) => void;
channels: string[];
- collapsed: boolean;
+ header?: React.JSX.Element;
notifications: Notification[];
project: NotificationProject;
removeNotification: (n: Notification) => void;
types: NotificationProjectType[];
}
-export default function ProjectNotifications(props: Props) {
- const { collapsed, project, channels } = props;
- const [isCollapsed, setCollapsed] = React.useState<boolean>(collapsed);
-
+export default function ProjectNotifications({
+ addNotification,
+ channels,
+ header,
+ notifications,
+ project,
+ removeNotification,
+ types,
+}: Readonly<Props>) {
const getCheckboxId = (type: string, channel: string) => {
- return `project-notification-${props.project.project}-${type}-${channel}`;
+ return `project-notification-${project.project}-${type}-${channel}`;
};
const handleAddNotification = ({ channel, type }: { channel: string; type: string }) => {
- props.addNotification({ ...props.project, channel, type });
+ addNotification({ ...project, channel, type });
};
const handleRemoveNotification = ({ channel, type }: { channel: string; type: string }) => {
- props.removeNotification({
- ...props.project,
+ removeNotification({
+ ...project,
channel,
type,
});
};
- const toggleExpanded = () => setCollapsed(!isCollapsed);
-
return (
- <BoxedGroupAccordion
- onClick={toggleExpanded}
- open={!isCollapsed}
- title={<h4 className="display-inline-block">{project.projectName}</h4>}
- >
- <table className="data zebra notifications-table" key={project.project}>
- <thead>
- <tr>
- <th aria-label={translate('project')} />
- {channels.map((channel) => (
- <th className="text-center" key={channel}>
- <h4>{translate('notification.channel', channel)}</h4>
- </th>
- ))}
- </tr>
- </thead>
+ <div className="sw-my-6">
+ <div className="sw-mb-4">
+ <Link to={getProjectUrl(project.project)}>{project.projectName}</Link>
+ </div>
+ {!header && (
+ <div className="sw-body-sm-highlight sw-mb-2">{translate('notifications.send_email')}</div>
+ )}
+ <Table
+ className={classNames('sw-w-full', { 'sw-mt-4': header })}
+ columnCount={2}
+ header={header ?? null}
+ >
<NotificationsList
- channels={props.channels}
+ channels={channels}
checkboxId={getCheckboxId}
- notifications={props.notifications}
+ notifications={notifications}
onAdd={handleAddNotification}
onRemove={handleRemoveNotification}
project
- types={props.types}
+ types={types}
/>
- </table>
- </BoxedGroupAccordion>
+ </Table>
+ </div>
);
}
* 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, InputSearch, Note } from 'design-system';
import { groupBy, sortBy, uniqBy } from 'lodash';
import * as React from 'react';
-import { Button } from '../../../components/controls/buttons';
-import SearchBox from '../../../components/controls/SearchBox';
import { translate } from '../../../helpers/l10n';
import {
Notification,
export interface Props {
addNotification: (n: Notification) => void;
channels: string[];
+ header?: React.JSX.Element;
notifications: Notification[];
removeNotification: (n: Notification) => void;
types: NotificationProjectType[];
}
-const THRESHOLD_COLLAPSED = 3;
-
interface State {
addedProjects: NotificationProject[];
search: string;
};
filterSearch = (project: NotificationProject, search: string) => {
- return project.projectName && project.projectName.toLowerCase().includes(search);
+ return project.projectName?.toLowerCase().includes(search);
};
handleAddProject = (project: NotificationProject) => {
removeNotification = (removed: Notification, allProjects: NotificationProject[]) => {
const projectToRemove = allProjects.find((p) => p.project === removed.project);
+
if (projectToRemove) {
this.handleAddProject(projectToRemove);
}
const projects = uniqBy(notifications, ({ project }) => project).filter(
isNotificationProject,
) as NotificationProject[];
+
const notificationsByProject = groupBy(notifications, (n) => n.project);
const allProjects = uniqBy([...addedProjects, ...projects], (project) => project.project);
+
const filteredProjects = sortBy(allProjects, 'projectName').filter((p) =>
this.filterSearch(p, search),
);
- const shouldBeCollapsed = Object.keys(notificationsByProject).length > THRESHOLD_COLLAPSED;
return (
- <section className="boxed-group" data-test="account__project-notifications">
- <div className="boxed-group-inner">
- <div className="page-actions">
- <Button onClick={this.openModal}>
- <span data-test="account__add-project-notification">
- {translate('my_profile.per_project_notifications.add')}
- </span>
- </Button>
- </div>
-
- <h2>{translate('my_profile.per_project_notifications.title')}</h2>
+ <section data-test="account__project-notifications">
+ <div className="sw-flex sw-justify-between">
+ <h2 className="sw-body-md-highlight sw-mb-4">
+ {translate('my_profile.per_project_notifications.title')}
+ </h2>
+
+ <ButtonPrimary onClick={this.openModal}>
+ <span data-test="account__add-project-notification">
+ {translate('my_profile.per_project_notifications.add')}
+ </span>
+ </ButtonPrimary>
</div>
{this.state.showModal && (
/>
)}
- <div className="boxed-group-inner">
+ <div>
{allProjects.length === 0 && (
- <div className="note">{translate('my_account.no_project_notifications')}</div>
+ <Note>{translate('my_account.no_project_notifications')}</Note>
)}
{allProjects.length > 0 && (
- <div className="big-spacer-bottom">
- <SearchBox
+ <div className="sw-mb-4">
+ <InputSearch
onChange={this.handleSearch}
placeholder={translate('search.search_for_projects')}
/>
</div>
)}
- {filteredProjects.map((project) => {
- const collapsed = addedProjects.find((p) => p.project === project.project)
- ? false
- : shouldBeCollapsed;
- return (
- <ProjectNotifications
- addNotification={this.props.addNotification}
- channels={this.props.channels}
- collapsed={collapsed}
- key={project.project}
- notifications={notificationsByProject[project.project] || []}
- project={project}
- removeNotification={(n) => this.removeNotification(n, allProjects)}
- types={this.props.types}
- />
- );
- })}
+ {filteredProjects.map((project) => (
+ <ProjectNotifications
+ addNotification={this.props.addNotification}
+ channels={this.props.channels}
+ header={this.props.header}
+ key={project.project}
+ notifications={notificationsByProject[project.project] || []}
+ project={project}
+ removeNotification={(n) => this.removeNotification(n, allProjects)}
+ types={this.props.types}
+ />
+ ))}
</div>
</section>
);
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { PageTitle, SubHeading } from 'design-system';
+import { PageTitle } from 'design-system';
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
import { useCurrentLoginUser } from '../../../app/components/current-user/CurrentUserContext';
<Tokens login={currentUser.login} />
{currentUser.local && (
- <SubHeading as="section">
+ <>
<PageTitle
className="sw-heading-md sw-my-6"
text={translate('my_profile.password.title')}
/>
<ResetPasswordForm user={currentUser} />
- </SubHeading>
+ </>
)}
</>
);
<Spinner className="sw-mt-6" loading={loading}>
<h3 id="notifications-update-title" className="sw-mt-6">
- {translate('project_information.project_notifications.title')}
+ {translate('notifications.send_email')}
</h3>
<ul className="sw-list-none sw-mt-4 sw-pl-0">
{perProjectTypes.map((type) => (
right
className="sw-flex sw-justify-between"
label={translateWithParameters(
- 'notification.dispatcher.descrption_x',
+ 'notification.dispatcher.description_x',
getDispatcherLabel(type),
)}
checked={isEnabled(type, emailChannel)}
const user = userEvent.setup();
renderProjectNotifications();
- expect(
- await screen.findByText('project_information.project_notifications.title'),
- ).toBeInTheDocument();
+ expect(await screen.findByText('notifications.send_email')).toBeInTheDocument();
expect(
screen.getByLabelText(
- 'notification.dispatcher.descrption_x.notification.dispatcher.NewAlerts.project',
+ 'notification.dispatcher.description_x.notification.dispatcher.NewAlerts.project',
),
).toBeChecked();
expect(
screen.getByLabelText(
- 'notification.dispatcher.descrption_x.notification.dispatcher.NewIssues.project',
+ 'notification.dispatcher.description_x.notification.dispatcher.NewIssues.project',
),
).not.toBeChecked();
// Toggle New Alerts
await user.click(
screen.getByLabelText(
- 'notification.dispatcher.descrption_x.notification.dispatcher.NewAlerts.project',
+ 'notification.dispatcher.description_x.notification.dispatcher.NewAlerts.project',
),
);
expect(
screen.getByLabelText(
- 'notification.dispatcher.descrption_x.notification.dispatcher.NewAlerts.project',
+ 'notification.dispatcher.description_x.notification.dispatcher.NewAlerts.project',
),
).not.toBeChecked();
// Toggle New Issues
await user.click(
screen.getByLabelText(
- 'notification.dispatcher.descrption_x.notification.dispatcher.NewIssues.project',
+ 'notification.dispatcher.description_x.notification.dispatcher.NewIssues.project',
),
);
expect(
screen.getByLabelText(
- 'notification.dispatcher.descrption_x.notification.dispatcher.NewIssues.project',
+ 'notification.dispatcher.description_x.notification.dispatcher.NewIssues.project',
),
).toBeChecked();
});
notification.dispatcher.SQ-MyNewIssues=My new issues
notification.dispatcher.CeReportTaskFailure=Background tasks in failure on my administered projects
notification.dispatcher.CeReportTaskFailure.project=Background tasks in failure
-notification.dispatcher.descrption_x=Check to receive notification for {0}
+notification.dispatcher.description_x=Check to receive notification for {0}
#------------------------------------------------------------------------------
#
my_account.preferences.keyboard_shortcuts.enabled=Keyboard shortcuts are enabled
my_account.preferences.keyboard_shortcuts.disabled=Keyboard shortcuts are disabled
+notifications.send_email=Send me an email for:
+
#------------------------------------------------------------------------------
#
# PROJECT PROVISIONING
project_dump.import_form_description=A dump has been found on the file system for this project. You can import it by clicking on the button below.
project_dump.import_form_description_disabled=Projects cannot be imported. This feature is only available starting from Enterprise Edition.
-#------------------------------------------------------------------------------
-#
-# Project Information
-#
-#------------------------------------------------------------------------------
-project_information.project_notifications.title=Send me an email when:
-
#------------------------------------------------------------------------------
#
# SYSTEM