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}
className="feature"
>
<ul
- className="categories"
+ className="categories spacer-bottom"
>
<li
key="BitBucket"
className="feature"
>
<ul
- className="categories"
+ className="categories spacer-bottom"
>
<li
key="Java"
*/
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;
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 />}
+ </>
);
}
--- /dev/null
+/*
+ * 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);
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 = [
{ 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}
+ />
+ );
+}
--- /dev/null
+/*
+ * 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}
+ />
+ );
+}
// 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>
`;
--- /dev/null
+// 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>
+`;
action=Action
actions=Actions
active=Active
+activate=Activate
add_verb=Add
admin=Admin
apply=Apply
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