]> source.dussan.org Git - sonarqube.git/commitdiff
SONARCLOUD-272 Update user notifications settings page
authorWouter Admiraal <wouter.admiraal@sonarsource.com>
Fri, 14 Dec 2018 15:25:25 +0000 (16:25 +0100)
committersonartech <sonartech@sonarsource.com>
Thu, 20 Dec 2018 10:41:51 +0000 (11:41 +0100)
server/sonar-web/src/main/js/app/components/notifications/NotificationsSidebar.tsx
server/sonar-web/src/main/js/app/components/notifications/__tests__/__snapshots__/NotificationsSidebar-test.tsx.snap
server/sonar-web/src/main/js/apps/account/notifications/GlobalNotifications.tsx
server/sonar-web/src/main/js/apps/account/notifications/SonarCloudNotifications.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/account/notifications/__tests__/GlobalNotifications-test.tsx
server/sonar-web/src/main/js/apps/account/notifications/__tests__/SonarCloudNotifications-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/GlobalNotifications-test.tsx.snap
server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/SonarCloudNotifications-test.tsx.snap [new file with mode: 0644]
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index f267574cde84b5eb8f3217f08e3c5a2cdb063b61..8eeb985837a00b18fd6de06ed8966f307c5d4e24 100644 (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}
index a832b1e93c15607ad0f0a548ac22bcacae9b9dc9..e309fd23ec83dcca0a37d4c2db2cc75a851589a0 100644 (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"
index 5ca04b775f836e8ce0f0ffc66919c54c1f7bab25..3e330e5e696a9ae60f1c5c01a6dcb113db1d874f 100644 (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 />}
+    </>
   );
 }
 
diff --git a/server/sonar-web/src/main/js/apps/account/notifications/SonarCloudNotifications.tsx b/server/sonar-web/src/main/js/apps/account/notifications/SonarCloudNotifications.tsx
new file mode 100644 (file)
index 0000000..175f723
--- /dev/null
@@ -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);
index 7f5cb7287bd783004b94e2cc2d65a2d26ee6c258..f25506fa1a2c550794cf95b9b657c87e0004f629 100644 (file)
 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}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/account/notifications/__tests__/SonarCloudNotifications-test.tsx b/server/sonar-web/src/main/js/apps/account/notifications/__tests__/SonarCloudNotifications-test.tsx
new file mode 100644 (file)
index 0000000..e67695d
--- /dev/null
@@ -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}
+    />
+  );
+}
index 3d21dbbdf2a50f1ba31f2faa297d74aaecb60285..ecd10ebc9feb5014662bf77540d1690c1e4efbe3 100644 (file)
 // 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>
 `;
diff --git a/server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/SonarCloudNotifications-test.tsx.snap b/server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/SonarCloudNotifications-test.tsx.snap
new file mode 100644 (file)
index 0000000..571bf1a
--- /dev/null
@@ -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>
+`;
index eae4d016b97e115dd93bf7fc5ce978339a88203b..909ad4dc927f8a37dfe401ec333be5c4478fe467 100644 (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