aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/apps
diff options
context:
space:
mode:
authorStas Vilchik <vilchiks@gmail.com>2016-07-26 16:21:19 +0200
committerGitHub <noreply@github.com>2016-07-26 16:21:19 +0200
commit940c3eb71b6c8fd6cb34c4ff93a1e661ec19a4c5 (patch)
tree195de2c237f89ada52d0d467dd9bd138581bc19c /server/sonar-web/src/main/js/apps
parentfcba3fa1650b1afc794094e20764191ae8bca910 (diff)
downloadsonarqube-940c3eb71b6c8fd6cb34c4ff93a1e661ec19a4c5.tar.gz
sonarqube-940c3eb71b6c8fd6cb34c4ff93a1e661ec19a4c5.zip
SONAR-7922 Rewrite "Quality Profiles" project page (#1118)
Diffstat (limited to 'server/sonar-web/src/main/js/apps')
-rw-r--r--server/sonar-web/src/main/js/apps/project-admin/app.js19
-rw-r--r--server/sonar-web/src/main/js/apps/project-admin/deletion/Form.js69
-rw-r--r--server/sonar-web/src/main/js/apps/project-admin/quality-profiles/Header.js36
-rw-r--r--server/sonar-web/src/main/js/apps/project-admin/quality-profiles/ProfileRow.js101
-rw-r--r--server/sonar-web/src/main/js/apps/project-admin/quality-profiles/QualityProfiles.js75
-rw-r--r--server/sonar-web/src/main/js/apps/project-admin/quality-profiles/Table.js71
-rw-r--r--server/sonar-web/src/main/js/apps/project-admin/store/actions.js70
-rw-r--r--server/sonar-web/src/main/js/apps/project-admin/store/profiles.js39
-rw-r--r--server/sonar-web/src/main/js/apps/project-admin/store/profilesByProject.js44
-rw-r--r--server/sonar-web/src/main/js/apps/project-admin/store/rootReducer.js42
10 files changed, 555 insertions, 11 deletions
diff --git a/server/sonar-web/src/main/js/apps/project-admin/app.js b/server/sonar-web/src/main/js/apps/project-admin/app.js
index ee3493544d3..7bcb91f9e1c 100644
--- a/server/sonar-web/src/main/js/apps/project-admin/app.js
+++ b/server/sonar-web/src/main/js/apps/project-admin/app.js
@@ -19,9 +19,13 @@
*/
import React from 'react';
import { render } from 'react-dom';
+import { Provider } from 'react-redux';
import { Router, Route, useRouterHistory } from 'react-router';
import { createHistory } from 'history';
import Deletion from './deletion/Deletion';
+import QualityProfiles from './quality-profiles/QualityProfiles';
+import rootReducer from './store/rootReducer';
+import configureStore from '../../components/store/configureStore';
window.sonarqube.appStarted.then(options => {
const el = document.querySelector(options.el);
@@ -30,12 +34,21 @@ window.sonarqube.appStarted.then(options => {
basename: window.baseUrl + '/project'
});
+ const store = configureStore(rootReducer);
+
const withComponent = ComposedComponent => props =>
<ComposedComponent {...props} component={options.component}/>;
render((
- <Router history={history}>
- <Route path="/deletion" component={withComponent(Deletion)}/>
- </Router>
+ <Provider store={store}>
+ <Router history={history}>
+ <Route
+ path="/deletion"
+ component={withComponent(Deletion)}/>
+ <Route
+ path="/quality_profiles"
+ component={withComponent(QualityProfiles)}/>
+ </Router>
+ </Provider>
), el);
});
diff --git a/server/sonar-web/src/main/js/apps/project-admin/deletion/Form.js b/server/sonar-web/src/main/js/apps/project-admin/deletion/Form.js
index 81d52fb3ba2..354935418fa 100644
--- a/server/sonar-web/src/main/js/apps/project-admin/deletion/Form.js
+++ b/server/sonar-web/src/main/js/apps/project-admin/deletion/Form.js
@@ -18,24 +18,39 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
-import ConfirmationModal from './ConfirmationModal';
-import { translate } from '../../../helpers/l10n';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { deleteProject } from '../../../api/components';
export default class Form extends React.Component {
static propTypes = {
component: React.PropTypes.object.isRequired
};
+ state = {
+ confirmation: false,
+ loading: false
+ };
+
handleDelete (e) {
e.preventDefault();
- new ConfirmationModal({ project: this.props.component })
- .on('done', () => {
- window.location = window.baseUrl + '/';
- })
- .render();
+ this.setState({ confirmation: true });
}
- render () {
+ confirmDeleteClick (e) {
+ e.preventDefault();
+ this.setState({ loading: true });
+ deleteProject(this.props.component.key).then(() => {
+ window.location = window.baseUrl + '/';
+ });
+ }
+
+ cancelDeleteClick (e) {
+ e.preventDefault();
+ e.target.blur();
+ this.setState({ confirmation: false });
+ }
+
+ renderInitial () {
return (
<form onSubmit={this.handleDelete.bind(this)}>
<button id="delete-project" className="button-red">
@@ -44,4 +59,42 @@ export default class Form extends React.Component {
</form>
);
}
+
+ renderConfirmation () {
+ return (
+ <form className="panel panel-warning"
+ onSubmit={this.confirmDeleteClick.bind(this)}>
+ <div className="big-spacer-bottom">
+ {translateWithParameters(
+ 'project_deletion.delete_resource_confirmation',
+ this.props.component.name)}
+ </div>
+
+ <div>
+ <button
+ id="confirm-project-deletion"
+ className="button-red"
+ disabled={this.state.loading}>
+ {translate('delete')}
+ </button>
+
+ {this.state.loading ? (
+ <i className="spinner big-spacer-left"/>
+ ) : (
+ <a href="#"
+ className="big-spacer-left"
+ onClick={this.cancelDeleteClick.bind(this)}>
+ {translate('cancel')}
+ </a>
+ )}
+ </div>
+ </form>
+ );
+ }
+
+ render () {
+ return this.state.confirmation ?
+ this.renderConfirmation() :
+ this.renderInitial();
+ }
}
diff --git a/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/Header.js b/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/Header.js
new file mode 100644
index 00000000000..f93fb46e926
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/Header.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 React from 'react';
+import { translate } from '../../../helpers/l10n';
+
+export default class Header extends React.Component {
+ render () {
+ return (
+ <header className="page-header">
+ <h1 className="page-title">
+ {translate('project_quality_profiles.page')}
+ </h1>
+ <div className="page-description">
+ {translate('project_quality_profiles.page.description')}
+ </div>
+ </header>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/ProfileRow.js b/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/ProfileRow.js
new file mode 100644
index 00000000000..50227fa19a9
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/ProfileRow.js
@@ -0,0 +1,101 @@
+/*
+ * 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 shallowCompare from 'react-addons-shallow-compare';
+import Select from 'react-select';
+import { translate } from '../../../helpers/l10n';
+
+export default class ProfileRow extends React.Component {
+ static propTypes = {
+ profile: React.PropTypes.object.isRequired,
+ possibleProfiles: React.PropTypes.array.isRequired,
+ onChangeProfile: React.PropTypes.func.isRequired
+ };
+
+ state = {
+ loading: false
+ };
+
+ shouldComponentUpdate (nextProps, nextState) {
+ return shallowCompare(this, nextProps, nextState);
+ }
+
+ componentWillUpdate (nextProps) {
+ if (nextProps.profile !== this.props.profile) {
+ this.setState({ loading: false });
+ }
+ }
+
+ handleChange (option) {
+ if (this.props.profile.key !== option.value) {
+ this.setState({ loading: true });
+ this.props.onChangeProfile(this.props.profile.key, option.value);
+ }
+ }
+
+ renderProfileName (profileOption) {
+ if (profileOption.isDefault) {
+ return (
+ <span>
+ <strong>{translate('default')}</strong>
+ {': '}
+ {profileOption.label}
+ </span>
+ );
+ }
+
+ return profileOption.label;
+ }
+
+ renderProfileSelect () {
+ const { profile, possibleProfiles } = this.props;
+
+ const options = possibleProfiles.map(profile => ({
+ value: profile.key,
+ label: profile.name,
+ isDefault: profile.isDefault
+ }));
+
+ return (
+ <Select
+ options={options}
+ valueRenderer={this.renderProfileName}
+ optionRenderer={this.renderProfileName}
+ value={profile.key}
+ clearable={false}
+ style={{ width: 300 }}
+ disabled={this.state.loading}
+ onChange={this.handleChange.bind(this)}/>
+ );
+ }
+
+ render () {
+ const { profile } = this.props;
+
+ return (
+ <tr data-key={profile.language}>
+ <td className="thin nowrap">{profile.languageName}</td>
+ <td className="thin nowrap">{this.renderProfileSelect()}</td>
+ <td>{this.state.loading && <i className="spinner"/>}
+ </td>
+ </tr>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/QualityProfiles.js b/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/QualityProfiles.js
new file mode 100644
index 00000000000..766e4bf5dae
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/QualityProfiles.js
@@ -0,0 +1,75 @@
+/*
+ * 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 { connect } from 'react-redux';
+import shallowCompare from 'react-addons-shallow-compare';
+import Header from './Header';
+import Table from './Table';
+import { fetchProjectProfiles, setProjectProfile } from '../store/actions';
+import { getProjectProfiles, getAllProfiles } from '../store/rootReducer';
+
+class QualityProfiles extends React.Component {
+ static propTypes = {
+ component: React.PropTypes.object.isRequired,
+ allProfiles: React.PropTypes.array,
+ profiles: React.PropTypes.array
+ };
+
+ componentDidMount () {
+ this.props.fetchProjectProfiles(this.props.component.key);
+ }
+
+ shouldComponentUpdate (nextProps, nextState) {
+ return shallowCompare(this, nextProps, nextState);
+ }
+
+ handleChangeProfile (oldKey, newKey) {
+ this.props.setProjectProfile(this.props.component.key, oldKey, newKey);
+ }
+
+ render () {
+ const { allProfiles, profiles } = this.props;
+
+ return (
+ <div className="page page-limited">
+ <Header/>
+
+ {profiles.length > 0 ? (
+ <Table
+ allProfiles={allProfiles}
+ profiles={profiles}
+ onChangeProfile={this.handleChangeProfile.bind(this)}/>
+ ) : (
+ <i className="spinner"/>
+ )}
+ </div>
+ );
+ }
+}
+
+const mapStateToProps = (state, ownProps) => ({
+ allProfiles: getAllProfiles(state),
+ profiles: getProjectProfiles(state, ownProps.component.key)
+});
+
+export default connect(
+ mapStateToProps,
+ { fetchProjectProfiles, setProjectProfile }
+)(QualityProfiles);
diff --git a/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/Table.js b/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/Table.js
new file mode 100644
index 00000000000..3e0eaae5b32
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/Table.js
@@ -0,0 +1,71 @@
+/*
+ * 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 shallowCompare from 'react-addons-shallow-compare';
+import groupBy from 'lodash/groupBy';
+import orderBy from 'lodash/orderBy';
+import ProfileRow from './ProfileRow';
+import { translate } from '../../../helpers/l10n';
+
+export default class Table extends React.Component {
+ static propTypes = {
+ allProfiles: React.PropTypes.array.isRequired,
+ profiles: React.PropTypes.array.isRequired,
+ onChangeProfile: React.PropTypes.func.isRequired
+ };
+
+ shouldComponentUpdate (nextProps, nextState) {
+ return shallowCompare(this, nextProps, nextState);
+ }
+
+ renderHeader () {
+ // keep one empty cell for the spinner
+ return (
+ <thead>
+ <tr>
+ <th className="thin nowrap">{translate('language')}</th>
+ <th className="thin nowrap">{translate('quality_profile')}</th>
+ <th>&nbsp;</th>
+ </tr>
+ </thead>
+ );
+ }
+
+ render () {
+ const profilesByLanguage = groupBy(this.props.allProfiles, 'language');
+ const orderedProfiles = orderBy(this.props.profiles, 'languageName');
+
+ // set key to language to avoid destroying of component
+ const profileRows = orderedProfiles.map(profile => (
+ <ProfileRow
+ key={profile.language}
+ profile={profile}
+ possibleProfiles={profilesByLanguage[profile.language]}
+ onChangeProfile={this.props.onChangeProfile}/>
+ ));
+
+ return (
+ <table className="data zebra">
+ {this.renderHeader()}
+ <tbody>{profileRows}</tbody>
+ </table>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/project-admin/store/actions.js b/server/sonar-web/src/main/js/apps/project-admin/store/actions.js
new file mode 100644
index 00000000000..cad25a70e7a
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/project-admin/store/actions.js
@@ -0,0 +1,70 @@
+/*
+ * 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 {
+ getQualityProfiles,
+ associateProject,
+ dissociateProject
+} from '../../../api/quality-profiles';
+import { getProfileByKey } from './rootReducer';
+
+export const RECEIVE_PROFILES = 'RECEIVE_PROFILES';
+export const receiveProfiles = profiles => ({
+ type: RECEIVE_PROFILES,
+ profiles
+});
+
+export const RECEIVE_PROJECT_PROFILES = 'RECEIVE_PROJECT_PROFILES';
+export const receiveProjectProfiles = (projectKey, profiles) => ({
+ type: RECEIVE_PROJECT_PROFILES,
+ projectKey,
+ profiles
+});
+
+export const fetchProjectProfiles = projectKey => dispatch => {
+ Promise.all([
+ getQualityProfiles(),
+ getQualityProfiles({ projectKey })
+ ]).then(responses => {
+ const [allProfiles, projectProfiles] = responses;
+ dispatch(receiveProfiles(allProfiles));
+ dispatch(receiveProjectProfiles(projectKey, projectProfiles));
+ });
+};
+
+export const SET_PROJECT_PROFILE = 'SET_PROJECT_PROFILE';
+const setProjectProfileAction = (projectKey, oldProfileKey, newProfileKey) => ({
+ type: SET_PROJECT_PROFILE,
+ projectKey,
+ oldProfileKey,
+ newProfileKey
+});
+
+export const setProjectProfile = (projectKey, oldKey, newKey) =>
+ (dispatch, getState) => {
+ const state = getState();
+ const newProfile = getProfileByKey(state, newKey);
+ const request = newProfile.isDefault ?
+ dissociateProject(oldKey, projectKey) :
+ associateProject(newKey, projectKey);
+
+ request.then(() => {
+ dispatch(setProjectProfileAction(projectKey, oldKey, newKey));
+ });
+ };
diff --git a/server/sonar-web/src/main/js/apps/project-admin/store/profiles.js b/server/sonar-web/src/main/js/apps/project-admin/store/profiles.js
new file mode 100644
index 00000000000..d3f93cc257a
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/project-admin/store/profiles.js
@@ -0,0 +1,39 @@
+/*
+ * 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 keyBy from 'lodash/keyBy';
+import values from 'lodash/values';
+import { RECEIVE_PROFILES } from './actions';
+
+const profiles = (state = {}, action = {}) => {
+ if (action.type === RECEIVE_PROFILES) {
+ const newProfilesByKey = keyBy(action.profiles, 'key');
+ return { ...state, ...newProfilesByKey };
+ }
+
+ return state;
+};
+
+export default profiles;
+
+export const getAllProfiles = state =>
+ values(state);
+
+export const getProfile = (state, key) =>
+ state[key];
diff --git a/server/sonar-web/src/main/js/apps/project-admin/store/profilesByProject.js b/server/sonar-web/src/main/js/apps/project-admin/store/profilesByProject.js
new file mode 100644
index 00000000000..90f8be6c2cd
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/project-admin/store/profilesByProject.js
@@ -0,0 +1,44 @@
+/*
+ * 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 without from 'lodash/without';
+import { RECEIVE_PROJECT_PROFILES, SET_PROJECT_PROFILE } from './actions';
+
+const profilesByProject = (state = {}, action = {}) => {
+ if (action.type === RECEIVE_PROJECT_PROFILES) {
+ const profileKeys = action.profiles.map(profile => profile.key);
+ return { ...state, [action.projectKey]: profileKeys };
+ }
+
+ if (action.type === SET_PROJECT_PROFILE) {
+ const profileKeys = state[action.projectKey];
+ const nextProfileKeys = [
+ ...without(profileKeys, action.oldProfileKey),
+ action.newProfileKey
+ ];
+ return { ...state, [action.projectKey]: nextProfileKeys };
+ }
+
+ return state;
+};
+
+export default profilesByProject;
+
+export const getProfiles = (state, projectKey) =>
+ state[projectKey] || [];
diff --git a/server/sonar-web/src/main/js/apps/project-admin/store/rootReducer.js b/server/sonar-web/src/main/js/apps/project-admin/store/rootReducer.js
new file mode 100644
index 00000000000..5f5dc3d7899
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/project-admin/store/rootReducer.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 { combineReducers } from 'redux';
+import profiles, {
+ getProfile,
+ getAllProfiles as nextGetAllProfiles
+} from './profiles';
+import profilesByProject, { getProfiles } from './profilesByProject';
+
+const rootReducer = combineReducers({
+ profiles,
+ profilesByProject
+});
+
+export default rootReducer;
+
+export const getProfileByKey = (state, profileKey) =>
+ getProfile(state.profiles, profileKey);
+
+export const getAllProfiles = state =>
+ nextGetAllProfiles(state.profiles);
+
+export const getProjectProfiles = (state, projectKey) =>
+ getProfiles(state.profilesByProject, projectKey)
+ .map(profileKey => getProfileByKey(state, profileKey));