diff options
9 files changed, 373 insertions, 107 deletions
diff --git a/server/sonar-web/src/main/js/app/components/notifications/NotificationsSidebar.tsx b/server/sonar-web/src/main/js/app/components/notifications/NotificationsSidebar.tsx index f267574cde8..8eeb985837a 100644 --- a/server/sonar-web/src/main/js/app/components/notifications/NotificationsSidebar.tsx +++ b/server/sonar-web/src/main/js/app/components/notifications/NotificationsSidebar.tsx @@ -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} diff --git a/server/sonar-web/src/main/js/app/components/notifications/__tests__/__snapshots__/NotificationsSidebar-test.tsx.snap b/server/sonar-web/src/main/js/app/components/notifications/__tests__/__snapshots__/NotificationsSidebar-test.tsx.snap index a832b1e93c1..e309fd23ec8 100644 --- a/server/sonar-web/src/main/js/app/components/notifications/__tests__/__snapshots__/NotificationsSidebar-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/notifications/__tests__/__snapshots__/NotificationsSidebar-test.tsx.snap @@ -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" diff --git a/server/sonar-web/src/main/js/apps/account/notifications/GlobalNotifications.tsx b/server/sonar-web/src/main/js/apps/account/notifications/GlobalNotifications.tsx index 5ca04b775f8..3e330e5e696 100644 --- a/server/sonar-web/src/main/js/apps/account/notifications/GlobalNotifications.tsx +++ b/server/sonar-web/src/main/js/apps/account/notifications/GlobalNotifications.tsx @@ -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 index 00000000000..175f7233f22 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/account/notifications/SonarCloudNotifications.tsx @@ -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); diff --git a/server/sonar-web/src/main/js/apps/account/notifications/__tests__/GlobalNotifications-test.tsx b/server/sonar-web/src/main/js/apps/account/notifications/__tests__/GlobalNotifications-test.tsx index 7f5cb7287bd..f25506fa1a2 100644 --- a/server/sonar-web/src/main/js/apps/account/notifications/__tests__/GlobalNotifications-test.tsx +++ b/server/sonar-web/src/main/js/apps/account/notifications/__tests__/GlobalNotifications-test.tsx @@ -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} + /> + ); +} 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 index 00000000000..e67695d028d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/account/notifications/__tests__/SonarCloudNotifications-test.tsx @@ -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} + /> + ); +} diff --git a/server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/GlobalNotifications-test.tsx.snap b/server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/GlobalNotifications-test.tsx.snap index 3d21dbbdf2a..ecd10ebc9fe 100644 --- a/server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/GlobalNotifications-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/GlobalNotifications-test.tsx.snap @@ -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> `; 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 index 00000000000..571bf1aaca8 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/SonarCloudNotifications-test.tsx.snap @@ -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> +`; diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index eae4d016b97..909ad4dc927 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -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 |