diff options
author | Stas Vilchik <vilchiks@gmail.com> | 2016-01-26 14:34:51 +0100 |
---|---|---|
committer | Stas Vilchik <vilchiks@gmail.com> | 2016-01-28 16:14:24 +0100 |
commit | de0b07fffd01cc2552c9a4a50ebbc994fb684400 (patch) | |
tree | c66be7f9c8ae4761ef7b4cc64f96f7e51cd954b4 /server/sonar-web/src/main/js/apps/account | |
parent | 2a981676b2f034933c01b7fd7b745a61263690ac (diff) | |
download | sonarqube-de0b07fffd01cc2552c9a4a50ebbc994fb684400.tar.gz sonarqube-de0b07fffd01cc2552c9a4a50ebbc994fb684400.zip |
SONAR-7230 Move user notifications into separate page of "My Account" space
Diffstat (limited to 'server/sonar-web/src/main/js/apps/account')
10 files changed, 435 insertions, 11 deletions
diff --git a/server/sonar-web/src/main/js/apps/account/app.js b/server/sonar-web/src/main/js/apps/account/app.js index 5af38f5b6c3..4a78a6a04fb 100644 --- a/server/sonar-web/src/main/js/apps/account/app.js +++ b/server/sonar-web/src/main/js/apps/account/app.js @@ -21,10 +21,12 @@ import React from 'react'; import { render } from 'react-dom'; import { Router, Route, IndexRoute, Redirect } from 'react-router'; import { createHistory, useBasename } from 'history'; +import { Provider } from 'react-redux'; +import configureStore from './store/configureStore'; import AccountApp from './containers/AccountApp'; import Home from './components/Home'; -import Notifications from './components/Notifications'; +import NotificationsContainer from './containers/NotificationsContainer'; window.sonarqube.appStarted.then(options => { const el = document.querySelector(options.el); @@ -33,16 +35,21 @@ window.sonarqube.appStarted.then(options => { basename: window.baseUrl + '/account' }); + const store = configureStore(); + document.querySelector('html').classList.add('dashboard-page'); document.querySelector('#container').classList.add('page-wrapper-context'); render(( - <Router history={history}> - <Route path="/" component={AccountApp}> - <IndexRoute component={Home}/> + <Provider store={store}> + <Router history={history}> + <Route path="/" component={AccountApp}> + <IndexRoute component={Home}/> + <Route path="notifications" component={NotificationsContainer}/> - <Redirect from="/index" to="/"/> - </Route> - </Router> + <Redirect from="/index" to="/"/> + </Route> + </Router> + </Provider> ), el); }); diff --git a/server/sonar-web/src/main/js/apps/account/components/GlobalNotifications.js b/server/sonar-web/src/main/js/apps/account/components/GlobalNotifications.js new file mode 100644 index 00000000000..5f5de5de9b4 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/account/components/GlobalNotifications.js @@ -0,0 +1,53 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 React from 'react'; + +import NotificationsList from './NotificationsList'; +import { translate } from '../../../helpers/l10n'; + +export default function GlobalNotifications ({ notifications, channels }) { + return ( + <div> + <header className="page-header"> + <h2 className="page-title"> + {translate('my_profile.overall_notifications.title')} + </h2> + </header> + + <table className="form"> + <thead> + <tr> + <th></th> + {channels.map(channel => ( + <th key={channel} className="text-center"> + <h4>{translate('notification.channel', channel)}</h4> + </th> + ))} + </tr> + </thead> + + <NotificationsList + notifications={notifications} + checkboxId={(d, c) => `global_notifs_${d}_${c}`} + checkboxName={(d, c) => `global_notifs[${d}.${c}]`}/> + </table> + </div> + ); +} diff --git a/server/sonar-web/src/main/js/apps/account/components/Nav.js b/server/sonar-web/src/main/js/apps/account/components/Nav.js index 3a85c7da307..e19213d3062 100644 --- a/server/sonar-web/src/main/js/apps/account/components/Nav.js +++ b/server/sonar-web/src/main/js/apps/account/components/Nav.js @@ -38,6 +38,11 @@ const Nav = () => ( <i className="icon-home"/> </IndexLink> </li> + <li> + <IndexLink to="notifications" activeClassName="active"> + {translate('my_account.notifications')} + </IndexLink> + </li> </ul> </div> </nav> diff --git a/server/sonar-web/src/main/js/apps/account/components/Notifications.js b/server/sonar-web/src/main/js/apps/account/components/Notifications.js index 195a134c15c..45a9d5839eb 100644 --- a/server/sonar-web/src/main/js/apps/account/components/Notifications.js +++ b/server/sonar-web/src/main/js/apps/account/components/Notifications.js @@ -19,8 +19,41 @@ */ import React from 'react'; -const Notifications = () => ( - <h1>Notifications</h1> -); +import GlobalNotifications from './GlobalNotifications'; +import ProjectNotifications from './ProjectNotifications'; +import { translate } from '../../../helpers/l10n'; -export default Notifications; +export default function Notifications ({ globalNotifications, projectNotifications, onAddProject, onRemoveProject }) { + const channels = globalNotifications[0].channels.map(c => c.id); + + return ( + <div> + <p className="big-spacer-bottom"> + {translate('notification.dispatcher.information')} + </p> + <form id="notif_form" method="post" action={`${window.baseUrl}/account/update_notifications`}> + <div className="columns"> + <div className="column-half"> + <GlobalNotifications + notifications={globalNotifications} + channels={channels}/> + </div> + + <div className="column-half"> + <ProjectNotifications + notifications={projectNotifications} + channels={channels} + onAddProject={onAddProject} + onRemoveProject={onRemoveProject}/> + </div> + </div> + + <p className="big-spacer-top panel panel-vertical bordered-top text-right"> + <button id="submit-notifications" type="submit"> + {translate('my_profile.notifications.submit')} + </button> + </p> + </form> + </div> + ); +} diff --git a/server/sonar-web/src/main/js/apps/account/components/NotificationsList.js b/server/sonar-web/src/main/js/apps/account/components/NotificationsList.js new file mode 100644 index 00000000000..a8044b4b142 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/account/components/NotificationsList.js @@ -0,0 +1,42 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 React from 'react'; + +import { translate } from '../../../helpers/l10n'; + +export default function NotificationsList ({ notifications, checkboxName, checkboxId }) { + return ( + <tbody> + {notifications.map(notification => ( + <tr key={notification.dispatcher}> + <td>{translate('notification.dispatcher', notification.dispatcher)}</td> + {notification.channels.map(channel => ( + <td key={channel.id} className="text-center"> + <input defaultChecked={channel.checked} + id={checkboxId(notification.dispatcher, channel.id)} + name={checkboxName(notification.dispatcher, channel.id)} + type="checkbox"/> + </td> + ))} + </tr> + ))} + </tbody> + ); +} diff --git a/server/sonar-web/src/main/js/apps/account/components/ProjectNotifications.js b/server/sonar-web/src/main/js/apps/account/components/ProjectNotifications.js new file mode 100644 index 00000000000..5683e50e1d8 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/account/components/ProjectNotifications.js @@ -0,0 +1,102 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 React from 'react'; +import Select from 'react-select'; + +import NotificationsList from './NotificationsList'; +import { translate } from '../../../helpers/l10n'; +import { getProjectsWithInternalId } from '../../../api/components'; + +export default function ProjectNotifications ({ notifications, channels, onAddProject, onRemoveProject }) { + const loadOptions = (query) => { + return getProjectsWithInternalId(query) + .then(results => results.map(r => { + return { + value: r.id, + label: r.text + }; + })) + .then(options => { + return { options }; + }); + }; + + const handleAddProject = (selected) => { + const project = { + internalId: selected.value, + name: selected.label + }; + onAddProject(project); + }; + + const handleRemoveProject = (project) => ( + (e) => { + e.preventDefault; + onRemoveProject(project); + } + ); + + return ( + <div> + <header className="page-header"> + <h2 className="page-title"> + {translate('my_profile.per_project_notifications.title')} + </h2> + <div className="pull-right"> + <Select.Async + name="new_project" + style={{ width: '150px' }} + loadOptions={loadOptions} + onChange={handleAddProject} + placeholder="Add Project"/> + </div> + </header> + + {!notifications.length && ( + <div className="note"> + {translate('my_account.no_project_notifications')} + </div> + )} + + {notifications.map(p => ( + <table key={p.project.internalId} className="form spacer-bottom"> + <thead> + <tr> + <th> + <a onClick={handleRemoveProject(p.project)} + className="spacer-right icon-delete js-delete-project" href="#"></a> + <h3 className="display-inline-block">{p.project.name}</h3> + </th> + {channels.map(channel => ( + <th key={channel} className="text-center"> + <h4>{translate('notification.channel', channel)}</h4> + </th> + ))} + </tr> + </thead> + <NotificationsList + notifications={p.notifications} + checkboxId={(d, c) => `project_notifs_${p.project.internalId}_${d}_${c}`} + checkboxName={(d, c) => `project_notifs[${p.project.internalId}][${d}][${c}]`}/> + </table> + ))} + </div> + ); +} diff --git a/server/sonar-web/src/main/js/apps/account/containers/NotificationsContainer.js b/server/sonar-web/src/main/js/apps/account/containers/NotificationsContainer.js new file mode 100644 index 00000000000..84328295905 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/account/containers/NotificationsContainer.js @@ -0,0 +1,42 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { connect } from 'react-redux'; + +import { addProjectNotifications, removeProjectNotifications } from '../store/actions'; +import Notifications from './../components/Notifications'; + +function mapStateToProps (state) { + return { + globalNotifications: window.sonarqube.notifications.global, + projectNotifications: state.projectNotifications + }; +} + +function mapDispatchToProps (dispatch) { + return { + onAddProject: (project) => dispatch(addProjectNotifications(project)), + onRemoveProject: (project) => dispatch(removeProjectNotifications(project)) + }; +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(Notifications); diff --git a/server/sonar-web/src/main/js/apps/account/store/actions.js b/server/sonar-web/src/main/js/apps/account/store/actions.js new file mode 100644 index 00000000000..bee94192a5c --- /dev/null +++ b/server/sonar-web/src/main/js/apps/account/store/actions.js @@ -0,0 +1,35 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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. + */ +export const ADD_PROJECT_NOTIFICATIONS = 'ADD_PROJECT_NOTIFICATIONS'; +export const REMOVE_PROJECT_NOTIFICATIONS = 'REMOVE_PROJECT_NOTIFICATIONS'; + +export function addProjectNotifications (project) { + return { + type: ADD_PROJECT_NOTIFICATIONS, + project + }; +} + +export function removeProjectNotifications (project) { + return { + type: REMOVE_PROJECT_NOTIFICATIONS, + project + }; +} diff --git a/server/sonar-web/src/main/js/apps/account/store/configureStore.js b/server/sonar-web/src/main/js/apps/account/store/configureStore.js new file mode 100644 index 00000000000..e412913dc76 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/account/store/configureStore.js @@ -0,0 +1,36 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { createStore, applyMiddleware } from 'redux'; +import thunk from 'redux-thunk'; +import createLogger from 'redux-logger'; +import rootReducer from './reducers'; + +const logger = createLogger({ + predicate: () => process.env.NODE_ENV !== 'production' +}); + +const createStoreWithMiddleware = applyMiddleware( + thunk, + logger +)(createStore); + +export default function configureStore () { + return createStoreWithMiddleware(rootReducer); +} diff --git a/server/sonar-web/src/main/js/apps/account/store/reducers.js b/server/sonar-web/src/main/js/apps/account/store/reducers.js new file mode 100644 index 00000000000..23921840d8b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/account/store/reducers.js @@ -0,0 +1,69 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { ADD_PROJECT_NOTIFICATIONS, REMOVE_PROJECT_NOTIFICATIONS } from './actions'; + +function addProjectNotifications (state, project) { + const found = state.find(notification => { + return notification.project.internalId === project.internalId; + }); + + if (found) { + return state; + } + + const newProjectNotification = { + project, + notifications: window.sonarqube.notifications.projectDispatchers.map(dispatcher => { + const channels = window.sonarqube.notifications.channels.map(channel => { + return { id: channel, checked: false }; + }); + return { dispatcher, channels }; + }) + }; + + return [...state, newProjectNotification]; +} + +function removeProjectNotifications (state, project) { + return state.filter(notification => { + return notification.project.internalId !== project.internalId; + }); +} + +export const initialState = { + projectNotifications: window.sonarqube.notifications.project +}; + +export default function (state = initialState, action) { + switch (action.type) { + case ADD_PROJECT_NOTIFICATIONS: + return { + ...state, + projectNotifications: addProjectNotifications(state.projectNotifications, action.project) + }; + case REMOVE_PROJECT_NOTIFICATIONS: + return { + ...state, + projectNotifications: removeProjectNotifications(state.projectNotifications, action.project) + }; + default: + return state; + } +} |