Browse Source

SONARCLOUD-272 Update user notifications settings page

tags/7.6
Wouter Admiraal 5 years ago
parent
commit
66e365f711

+ 1
- 1
server/sonar-web/src/main/js/app/components/notifications/NotificationsSidebar.tsx View File

@@ -114,7 +114,7 @@ interface FeatureProps {
export function Feature({ feature }: FeatureProps) {
return (
<div className="feature">
<ul className="categories">
<ul className="categories spacer-bottom">
{feature.categories.map(category => (
<li key={category.name} style={{ backgroundColor: category.color }}>
{category.name}

+ 2
- 2
server/sonar-web/src/main/js/app/components/notifications/__tests__/__snapshots__/NotificationsSidebar-test.tsx.snap View File

@@ -5,7 +5,7 @@ exports[`#Feature should render correctly 1`] = `
className="feature"
>
<ul
className="categories"
className="categories spacer-bottom"
>
<li
key="BitBucket"
@@ -37,7 +37,7 @@ exports[`#Feature should render correctly 2`] = `
className="feature"
>
<ul
className="categories"
className="categories spacer-bottom"
>
<li
key="Java"

+ 30
- 25
server/sonar-web/src/main/js/apps/account/notifications/GlobalNotifications.tsx View File

@@ -19,7 +19,9 @@
*/
import * as React from 'react';
import NotificationsList from './NotificationsList';
import SonarCloudNotifications from './SonarCloudNotifications';
import { translate } from '../../../helpers/l10n';
import { isSonarCloud } from '../../../helpers/system';

interface Props {
addNotification: (n: T.Notification) => void;
@@ -31,33 +33,36 @@ interface Props {

export default function GlobalNotifications(props: Props) {
return (
<section className="boxed-group">
<h2>{translate('my_profile.overall_notifications.title')}</h2>
<>
<section className="boxed-group">
<h2>{translate('my_profile.overall_notifications.title')}</h2>

<div className="boxed-group-inner">
<table className="form">
<thead>
<tr>
<th />
{props.channels.map(channel => (
<th className="text-center" key={channel}>
<h4>{translate('notification.channel', channel)}</h4>
</th>
))}
</tr>
</thead>
<div className="boxed-group-inner">
<table className="form">
<thead>
<tr>
<th />
{props.channels.map(channel => (
<th className="text-center" key={channel}>
<h4>{translate('notification.channel', channel)}</h4>
</th>
))}
</tr>
</thead>

<NotificationsList
channels={props.channels}
checkboxId={getCheckboxId}
notifications={props.notifications}
onAdd={props.addNotification}
onRemove={props.removeNotification}
types={props.types}
/>
</table>
</div>
</section>
<NotificationsList
channels={props.channels}
checkboxId={getCheckboxId}
notifications={props.notifications}
onAdd={props.addNotification}
onRemove={props.removeNotification}
types={props.types}
/>
</table>
</div>
</section>
{isSonarCloud() && <SonarCloudNotifications />}
</>
);
}


+ 87
- 0
server/sonar-web/src/main/js/apps/account/notifications/SonarCloudNotifications.tsx View File

@@ -0,0 +1,87 @@
/*
* SonarQube
* Copyright (C) 2009-2018 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { connect } from 'react-redux';
import Checkbox from '../../../components/controls/Checkbox';
import { translate } from '../../../helpers/l10n';
import { getCurrentUserSetting, Store } from '../../../store/rootReducer';
import { setCurrentUserSetting } from '../../../store/users';

interface Props {
notificationsOptOut?: boolean;
setCurrentUserSetting: (setting: T.CurrentUserSetting) => void;
}

export class SonarCloudNotifications extends React.PureComponent<Props> {
handleCheckOptOut = (checked: boolean) => {
this.props.setCurrentUserSetting({
key: 'notifications.optOut',
value: checked ? 'false' : 'true'
});
};

render() {
return (
<section className="boxed-group">
<h2>{translate('my_profile.sonarcloud_feature_notifications.title')}</h2>
<div className="boxed-group-inner">
<table className="form">
<thead>
<tr>
<th />
<th className="text-center">
<h4>{translate('activate')}</h4>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>{translate('my_profile.sonarcloud_feature_notifications.description')}</td>
<td className="text-center">
<Checkbox
checked={!this.props.notificationsOptOut}
onCheck={this.handleCheckOptOut}
/>
</td>
</tr>
</tbody>
</table>
</div>
</section>
);
}
}

const mapStateToProps = (state: Store) => {
const notificationsOptOut = getCurrentUserSetting(state, 'notifications.optOut') === 'true';

return {
notificationsOptOut
};
};

const mapDispatchToProps = {
setCurrentUserSetting
};

export default connect(
mapStateToProps,
mapDispatchToProps
)(SonarCloudNotifications);

+ 23
- 12
server/sonar-web/src/main/js/apps/account/notifications/__tests__/GlobalNotifications-test.tsx View File

@@ -20,8 +20,20 @@
import * as React from 'react';
import { shallow } from 'enzyme';
import GlobalNotifications from '../GlobalNotifications';
import { isSonarCloud } from '../../../../helpers/system';

jest.mock('../../../../helpers/system', () => ({ isSonarCloud: jest.fn() }));

it('should match snapshot', () => {
expect(shallowRender()).toMatchSnapshot();
});

it('should show SonarCloud options if in SC context', () => {
(isSonarCloud as jest.Mock).mockImplementation(() => true);
expect(shallowRender()).toMatchSnapshot();
});

function shallowRender(props = {}) {
const channels = ['channel1', 'channel2'];
const types = ['type1', 'type2'];
const notifications = [
@@ -30,15 +42,14 @@ it('should match snapshot', () => {
{ channel: 'channel2', type: 'type2' }
];

expect(
shallow(
<GlobalNotifications
addNotification={jest.fn()}
channels={channels}
notifications={notifications}
removeNotification={jest.fn()}
types={types}
/>
)
).toMatchSnapshot();
});
return shallow(
<GlobalNotifications
addNotification={jest.fn()}
channels={channels}
notifications={notifications}
removeNotification={jest.fn()}
types={types}
{...props}
/>
);
}

+ 36
- 0
server/sonar-web/src/main/js/apps/account/notifications/__tests__/SonarCloudNotifications-test.tsx View File

@@ -0,0 +1,36 @@
/*
* SonarQube
* Copyright (C) 2009-2018 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { shallow } from 'enzyme';
import { SonarCloudNotifications } from '../SonarCloudNotifications';

it('should match snapshot', () => {
expect(shallowRender()).toMatchSnapshot();
});

function shallowRender(props: Partial<SonarCloudNotifications['props']> = {}) {
return shallow(
<SonarCloudNotifications
notificationsOptOut={true}
setCurrentUserSetting={jest.fn()}
{...props}
/>
);
}

+ 144
- 67
server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/GlobalNotifications-test.tsx.snap View File

@@ -1,73 +1,150 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should match snapshot 1`] = `
<section
className="boxed-group"
>
<h2>
my_profile.overall_notifications.title
</h2>
<div
className="boxed-group-inner"
<Fragment>
<section
className="boxed-group"
>
<table
className="form"
<h2>
my_profile.overall_notifications.title
</h2>
<div
className="boxed-group-inner"
>
<thead>
<tr>
<th />
<th
className="text-center"
key="channel1"
>
<h4>
notification.channel.channel1
</h4>
</th>
<th
className="text-center"
key="channel2"
>
<h4>
notification.channel.channel2
</h4>
</th>
</tr>
</thead>
<NotificationsList
channels={
Array [
"channel1",
"channel2",
]
}
checkboxId={[Function]}
notifications={
Array [
Object {
"channel": "channel1",
"type": "type1",
},
Object {
"channel": "channel1",
"type": "type2",
},
Object {
"channel": "channel2",
"type": "type2",
},
]
}
onAdd={[MockFunction]}
onRemove={[MockFunction]}
types={
Array [
"type1",
"type2",
]
}
/>
</table>
</div>
</section>
<table
className="form"
>
<thead>
<tr>
<th />
<th
className="text-center"
key="channel1"
>
<h4>
notification.channel.channel1
</h4>
</th>
<th
className="text-center"
key="channel2"
>
<h4>
notification.channel.channel2
</h4>
</th>
</tr>
</thead>
<NotificationsList
channels={
Array [
"channel1",
"channel2",
]
}
checkboxId={[Function]}
notifications={
Array [
Object {
"channel": "channel1",
"type": "type1",
},
Object {
"channel": "channel1",
"type": "type2",
},
Object {
"channel": "channel2",
"type": "type2",
},
]
}
onAdd={[MockFunction]}
onRemove={[MockFunction]}
types={
Array [
"type1",
"type2",
]
}
/>
</table>
</div>
</section>
</Fragment>
`;

exports[`should show SonarCloud options if in SC context 1`] = `
<Fragment>
<section
className="boxed-group"
>
<h2>
my_profile.overall_notifications.title
</h2>
<div
className="boxed-group-inner"
>
<table
className="form"
>
<thead>
<tr>
<th />
<th
className="text-center"
key="channel1"
>
<h4>
notification.channel.channel1
</h4>
</th>
<th
className="text-center"
key="channel2"
>
<h4>
notification.channel.channel2
</h4>
</th>
</tr>
</thead>
<NotificationsList
channels={
Array [
"channel1",
"channel2",
]
}
checkboxId={[Function]}
notifications={
Array [
Object {
"channel": "channel1",
"type": "type1",
},
Object {
"channel": "channel1",
"type": "type2",
},
Object {
"channel": "channel2",
"type": "type2",
},
]
}
onAdd={[MockFunction]}
onRemove={[MockFunction]}
types={
Array [
"type1",
"type2",
]
}
/>
</table>
</div>
</section>
<Connect(SonarCloudNotifications) />
</Fragment>
`;

+ 47
- 0
server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/SonarCloudNotifications-test.tsx.snap View File

@@ -0,0 +1,47 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should match snapshot 1`] = `
<section
className="boxed-group"
>
<h2>
my_profile.sonarcloud_feature_notifications.title
</h2>
<div
className="boxed-group-inner"
>
<table
className="form"
>
<thead>
<tr>
<th />
<th
className="text-center"
>
<h4>
activate
</h4>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
my_profile.sonarcloud_feature_notifications.description
</td>
<td
className="text-center"
>
<Checkbox
checked={false}
onCheck={[Function]}
thirdState={false}
/>
</td>
</tr>
</tbody>
</table>
</div>
</section>
`;

+ 3
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -7,6 +7,7 @@
action=Action
actions=Actions
active=Active
activate=Activate
add_verb=Add
admin=Admin
apply=Apply
@@ -1500,6 +1501,8 @@ my_profile.password.submit=Change password
my_profile.password.changed=The password has been changed!
my_profile.notifications.submit=Save changes
my_profile.overall_notifications.title=Overall notifications
my_profile.sonarcloud_feature_notifications.title=SonarCloud new feature notifications
my_profile.sonarcloud_feature_notifications.description=Display a notification in the header when new features are deployed
my_profile.per_project_notifications.title=Notifications per project

my_account.page=My Account

Loading…
Cancel
Save