You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ro...@apache.org on 2016/12/13 11:03:09 UTC
fauxton commit: updated refs/heads/master to 25630f4
Repository: couchdb-fauxton
Updated Branches:
refs/heads/master 258ec71fb -> 25630f4e4
Redux: Permissions - Use Redux for Flux flow
Integrates Redux into CouchDB Fauxton to softmigrate our stores
to Redux. Removes the Backbone models and introduces testing with
Jest.
Additional Highlights:
- Bluebird for Promises (bye jQuery deferreds)
- uses the WHATWG fetch API
- 1 file per component
Project: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/commit/25630f4e
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/tree/25630f4e
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/diff/25630f4e
Branch: refs/heads/master
Commit: 25630f4e48a55e983ea6aff095b034cc2a728251
Parents: 258ec71
Author: Robert Kowalski <ro...@apache.org>
Authored: Thu Nov 3 15:09:51 2016 +0100
Committer: Robert Kowalski <ro...@apache.org>
Committed: Mon Dec 12 17:56:52 2016 +0100
----------------------------------------------------------------------
.eslintrc | 3 +-
__mocks__/fileMock.js | 1 +
__mocks__/styleMock.js | 1 +
.../permissions/__tests__/actions-test.js | 112 +++++++++
.../permissions/__tests__/container-test.js | 57 +++++
.../permissions/__tests__/helpers-test.js | 84 +++++++
.../__tests__/permissionsScreen-test.js | 97 ++++++++
app/addons/permissions/actions.js | 132 +++++++----
app/addons/permissions/actiontypes.js | 8 +-
app/addons/permissions/base.js | 3 +
app/addons/permissions/components.react.jsx | 234 -------------------
.../permissions/components/Permissions.js | 48 ++++
.../permissions/components/PermissionsItem.js | 36 +++
.../permissions/components/PermissionsScreen.js | 85 +++++++
.../components/PermissionsSection.js | 163 +++++++++++++
.../container/PermissionsContainer.js | 40 ++++
app/addons/permissions/helpers.js | 39 ++++
app/addons/permissions/layout.js | 4 +-
app/addons/permissions/reducers.js | 67 ++++++
app/addons/permissions/resources.js | 83 -------
app/addons/permissions/routes.js | 34 +--
app/addons/permissions/stores.js | 104 ---------
app/addons/permissions/tests/actionsSpec.js | 122 ----------
.../permissions/tests/componentsSpec.react.jsx | 135 -----------
.../permissions/tests/nightwatch/permissions.js | 40 ++++
app/addons/permissions/tests/resourceSpec.js | 69 ------
app/core/base.js | 2 +
app/helpers.js | 1 +
app/main.js | 23 +-
jest-config.json | 12 +-
jest-setup.js | 18 ++
package.json | 10 +-
32 files changed, 1042 insertions(+), 825 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/.eslintrc
----------------------------------------------------------------------
diff --git a/.eslintrc b/.eslintrc
index 0b5439d..c642ec6 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -73,7 +73,8 @@
"after": true,
"define": true,
"expect": true,
- "prettyPrint": true
+ "prettyPrint": true,
+ "jest": true
}
}
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/__mocks__/fileMock.js
----------------------------------------------------------------------
diff --git a/__mocks__/fileMock.js b/__mocks__/fileMock.js
new file mode 100644
index 0000000..86059f3
--- /dev/null
+++ b/__mocks__/fileMock.js
@@ -0,0 +1 @@
+module.exports = 'test-file-stub';
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/__mocks__/styleMock.js
----------------------------------------------------------------------
diff --git a/__mocks__/styleMock.js b/__mocks__/styleMock.js
new file mode 100644
index 0000000..f053ebf
--- /dev/null
+++ b/__mocks__/styleMock.js
@@ -0,0 +1 @@
+module.exports = {};
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/app/addons/permissions/__tests__/actions-test.js
----------------------------------------------------------------------
diff --git a/app/addons/permissions/__tests__/actions-test.js b/app/addons/permissions/__tests__/actions-test.js
new file mode 100644
index 0000000..5f74223
--- /dev/null
+++ b/app/addons/permissions/__tests__/actions-test.js
@@ -0,0 +1,112 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import {
+ setPermissionOnObject,
+ deletePermissionFromObject
+} from '../actions';
+
+
+describe('Permissions Actions', () => {
+
+ describe('deleting roles', () => {
+ it('throws if a role is not in permissions', () => {
+ const p = {
+ admins: {
+ names: ['abc'],
+ roles: []
+ },
+ members: {
+ names: [],
+ roles: []
+ }
+ };
+
+ expect(() => {
+ deletePermissionFromObject(p, 'admins', 'names', 'pizza');
+ }).toThrow();
+ });
+
+ it('deletes roles', () => {
+ const p = {
+ admins: {
+ names: ['abc', 'furbie'],
+ roles: []
+ },
+ members: {
+ names: [],
+ roles: []
+ }
+ };
+
+ const res = deletePermissionFromObject(p, 'admins', 'names', 'abc');
+
+ expect(res).toEqual({
+ admins: {
+ names: ['furbie'],
+ roles: []
+ },
+ members: {
+ names: [],
+ roles: []
+ }
+ });
+ });
+
+ });
+
+ describe('adding roles', () => {
+ it('throws if a role is already in permissions', () => {
+ const p = {
+ admins: {
+ names: ['abc'],
+ roles: []
+ },
+ members: {
+ names: [],
+ roles: []
+ }
+ };
+
+ expect(() => {
+ setPermissionOnObject(p, 'admins', 'names', 'abc');
+ }).toThrow();
+ });
+
+ it('adds if not already present', () => {
+ const p = {
+ admins: {
+ names: ['abc'],
+ roles: []
+ },
+ members: {
+ names: [],
+ roles: []
+ }
+ };
+
+ const res = setPermissionOnObject(p, 'admins', 'names', 'test123');
+
+ expect(res).toEqual({
+ admins: {
+ names: ['abc', 'test123'],
+ roles: []
+ },
+ members: {
+ names: [],
+ roles: []
+ }
+ });
+ });
+
+ });
+});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/app/addons/permissions/__tests__/container-test.js
----------------------------------------------------------------------
diff --git a/app/addons/permissions/__tests__/container-test.js b/app/addons/permissions/__tests__/container-test.js
new file mode 100644
index 0000000..82ae677
--- /dev/null
+++ b/app/addons/permissions/__tests__/container-test.js
@@ -0,0 +1,57 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import { receivedPermissions } from '../actions';
+
+import React from 'react';
+import { mount } from 'enzyme';
+
+import { createStore, applyMiddleware } from 'redux';
+
+import thunk from 'redux-thunk';
+import { Provider } from 'react-redux';
+
+import reducer from '../reducers';
+import PermissionsContainer from '../container/PermissionsContainer';
+
+describe('Permissions Container', () => {
+
+ it('renders with new results', () => {
+
+ fetch.mockResponse(
+ JSON.stringify({})
+ );
+
+ const middlewares = [thunk];
+ const store = createStore(
+ reducer,
+ applyMiddleware(...middlewares)
+ );
+
+ const wrapper = mount(
+ <Provider store={store}>
+ <PermissionsContainer url="http://example.com/abc" />
+ </Provider>
+ );
+
+ store.dispatch(
+ receivedPermissions({
+ admins: { names: ['banana'], roles: [] }
+ })
+ );
+
+ const item = wrapper
+ .find('.permissions__admins .permissions__entry');
+
+ expect(item.text()).toContain('banana');
+ });
+});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/app/addons/permissions/__tests__/helpers-test.js
----------------------------------------------------------------------
diff --git a/app/addons/permissions/__tests__/helpers-test.js b/app/addons/permissions/__tests__/helpers-test.js
new file mode 100644
index 0000000..a3fd54b
--- /dev/null
+++ b/app/addons/permissions/__tests__/helpers-test.js
@@ -0,0 +1,84 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import {
+ isValueAlreadySet,
+ addValueToPermissions
+} from '../helpers';
+
+describe('Permissions - Helpers', () => {
+
+ describe('isValueAlreadySet', () => {
+
+ it('returns false if object does not have properties', () => {
+
+ let permissions = {};
+ expect(
+ isValueAlreadySet(permissions, 'admins', 'names', 'rocko')
+ ).toBe(false);
+
+ permissions = { admins: {} };
+ expect(
+ isValueAlreadySet(permissions, 'admins', 'names', 'rocko')
+ ).toBe(false);
+
+ permissions = { admins: { names: [] } };
+ expect(
+ isValueAlreadySet(permissions, 'admins', 'names', 'rocko')
+ ).toBe(false);
+ });
+
+ it('confirms existing properties', () => {
+
+ const permissions = { admins: { names: ['michelle', 'rocko', 'garren'] } };
+
+ expect(
+ isValueAlreadySet(permissions, 'admins', 'names', 'rocko')
+ ).toBe(true);
+ });
+
+ });
+
+ describe('addValueToPermissions', () => {
+
+ it('adds values, even if properties not set', () => {
+
+ const permissions = {};
+ expect(
+ addValueToPermissions(permissions, 'admins', 'names', 'rocko')
+ ).toEqual({
+ admins: {
+ names: ['rocko']
+ }
+ });
+ });
+
+ it('adds values', () => {
+
+ const permissions = {
+ admins: {
+ names: ['rocko']
+ }
+ };
+
+ expect(
+ addValueToPermissions(permissions, 'admins', 'names', 'garren')
+ ).toEqual({
+ admins: {
+ names: ['rocko', 'garren']
+ }
+ });
+ });
+
+ });
+
+});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/app/addons/permissions/__tests__/permissionsScreen-test.js
----------------------------------------------------------------------
diff --git a/app/addons/permissions/__tests__/permissionsScreen-test.js b/app/addons/permissions/__tests__/permissionsScreen-test.js
new file mode 100644
index 0000000..2283a47
--- /dev/null
+++ b/app/addons/permissions/__tests__/permissionsScreen-test.js
@@ -0,0 +1,97 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import React from 'react';
+import { mount } from 'enzyme';
+
+import PermissionsScreen from '../components/PermissionsScreen';
+
+
+describe('PermissionsScreen', () => {
+
+ it('add permississon: does not dispatch if value already exists', () => {
+
+ const security = {
+ admins: { names: ['abc'], roles: [] },
+ members: { names: [], roles: [] }
+ };
+ const stub = jest.fn();
+
+ const wrapper = mount(
+ <PermissionsScreen
+ adminRoles={security.admins.roles}
+ adminNames={security.admins.names}
+ memberRoles={security.members.roles}
+ memberNames={security.members.names}
+ security={security}
+ dispatch={stub} />
+ );
+
+ wrapper
+ .find('.permissions__admins .permissions-add-user input')
+ .simulate('change', {target: {value: 'abc'}});
+
+ wrapper
+ .find('.permissions__admins .permissions-add-user')
+ .simulate('submit');
+
+ expect(stub).not.toHaveBeenCalled();
+ });
+
+ it('add permississon: dispatches if values does not exist', () => {
+
+ const security = {
+ admins: { names: ['pineapple'], roles: [] },
+ members: { names: [], roles: [] }
+ };
+ const stub = jest.fn();
+
+ const wrapper = mount(
+ <PermissionsScreen security={security} dispatch={stub} />
+ );
+
+ wrapper
+ .find('.permissions__admins .permissions-add-user input')
+ .simulate('change', {target: {value: 'mango'}});
+
+ wrapper
+ .find('.permissions__admins .permissions-add-user')
+ .simulate('submit');
+
+ expect(stub).toHaveBeenCalled();
+ });
+
+ it('remove permississon: dispatches', () => {
+
+ const security = {
+ admins: { names: ['pineapple'], roles: [] },
+ members: { names: [], roles: [] }
+ };
+ const stub = jest.fn();
+
+ const wrapper = mount(
+ <PermissionsScreen
+ adminRoles={security.admins.roles}
+ adminNames={security.admins.names}
+ memberRoles={security.members.roles}
+ memberNames={security.members.names}
+ security={security}
+ dispatch={stub} />
+ );
+
+ wrapper
+ .find('.permissions__admins .permissions__entry button')
+ .simulate('click');
+
+ expect(stub).toHaveBeenCalled();
+ });
+});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/app/addons/permissions/actions.js
----------------------------------------------------------------------
diff --git a/app/addons/permissions/actions.js b/app/addons/permissions/actions.js
index f11ff09..0a49cc0 100644
--- a/app/addons/permissions/actions.js
+++ b/app/addons/permissions/actions.js
@@ -12,70 +12,104 @@
import FauxtonAPI from "../../core/api";
import ActionTypes from "./actiontypes";
-import Stores from "./stores";
-var permissionsStore = Stores.permissionsStore;
+import Promise from 'bluebird';
+import 'whatwg-fetch';
+import { isValueAlreadySet, addValueToPermissions } from './helpers';
-export default {
- fetchPermissions: function (database, security) {
- FauxtonAPI.dispatch({
- type: ActionTypes.PERMISSIONS_FETCHING,
- database: database,
- security: security
- });
+import {
+ PERMISSIONS_UPDATE
+} from './actiontypes';
- FauxtonAPI.when([database.fetch(), security.fetch()]).then(function () {
- this.editPermissions(database, security);
- }.bind(this));
- },
- editPermissions: function (database, security) {
- FauxtonAPI.dispatch({
- type: ActionTypes.PERMISSIONS_EDIT,
- database: database,
- security: security
- });
- },
- addItem: function (options) {
- var check = permissionsStore.getSecurity().canAddItem(options.value, options.type, options.section);
+export const receivedPermissions = json => {
+ return {
+ type: PERMISSIONS_UPDATE,
+ permissions: json
+ };
+};
- if (check.error) {
- FauxtonAPI.addNotification({
- msg: check.msg,
- type: 'error'
- });
- return;
- }
+export const fetchPermissions = url => dispatch => {
+ return fetch(url, { headers: { 'Accept': 'application/json' }})
+ .then(res => res.json())
+ .then(json => dispatch(receivedPermissions(json)));
+};
- FauxtonAPI.dispatch({
- type: ActionTypes.PERMISSIONS_ADD_ITEM,
- options: options
- });
+export const setPermissionOnObject = (p, section, type, value) => {
+ if (isValueAlreadySet(p, section, type, value)) {
+ throw new Error('Role/Name has already been added');
+ }
- this.savePermissions();
+ const res = addValueToPermissions(p, section, type, value);
- },
- removeItem: function (options) {
- FauxtonAPI.dispatch({
- type: ActionTypes.PERMISSIONS_REMOVE_ITEM,
- options: options
- });
- this.savePermissions();
- },
+ return res;
+};
+
+export const deletePermissionFromObject = (p, section, type, value) => {
+ if (!isValueAlreadySet(p, section, type, value)) {
+ throw new Error('Role/Name does not exist');
+ }
+
+ p[section][type] = p[section][type].filter((el) => {
+ return el !== value;
+ });
- savePermissions: function () {
- permissionsStore.getSecurity().save().then(function () {
+ return p;
+};
+
+export const updatePermission = (url, permissions, section, type, value) => dispatch => {
+ const res = setPermissionOnObject(permissions, section, type, value);
+
+ updatePermissionUnsafe(url, permissions, dispatch)
+ .catch((err) => {
FauxtonAPI.addNotification({
- msg: 'Database permissions has been updated.'
+ msg: err.message,
+ type: 'error'
});
- }, function (xhr) {
- if (!xhr && !xhr.responseJSON) { return;}
+ });
+};
+
+export const deletePermission = (url, permissions, section, type, value) => dispatch => {
+ const res = deletePermissionFromObject(permissions, section, type, value);
+ updatePermissionUnsafe(url, permissions, dispatch)
+ .catch((err) => {
FauxtonAPI.addNotification({
- msg: 'Could not update permissions - reason: ' + xhr.responseJSON.reason,
+ msg: err.message,
type: 'error'
});
});
- }
+};
+
+export const updatePermissionUnsafe = (url, p, dispatch) => {
+ return fetch(url, {
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json'
+ },
+ credentials: 'include',
+ method: 'PUT',
+ body: JSON.stringify(p)
+ })
+ .then((res) => res.json())
+ .then((json) => {
+ if (!json.ok) {
+ throw new Error(json.reason);
+ }
+ return json;
+ })
+ .then((json) => {
+ FauxtonAPI.addNotification({
+ msg: 'Database permissions has been updated.'
+ });
+
+ return dispatch(receivedPermissions(p));
+ })
+ .catch((error) => {
+ FauxtonAPI.addNotification({
+ msg: 'Could not update permissions - reason: ' + error,
+ type: 'error'
+ });
+ });
};
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/app/addons/permissions/actiontypes.js
----------------------------------------------------------------------
diff --git a/app/addons/permissions/actiontypes.js b/app/addons/permissions/actiontypes.js
index 132da81..f474fbd 100644
--- a/app/addons/permissions/actiontypes.js
+++ b/app/addons/permissions/actiontypes.js
@@ -10,9 +10,5 @@
// License for the specific language governing permissions and limitations under
// the License.
-export default {
- PERMISSIONS_EDIT: 'PERMISSIONS_EDIT',
- PERMISSIONS_FETCHING: 'PERMISSIONS_FETCHING',
- PERMISSIONS_ADD_ITEM: 'PERMISSIONS_ADD_ITEM',
- PERMISSIONS_REMOVE_ITEM: 'PERMISSIONS_REMOVE_ITEM'
-};
+
+export const PERMISSIONS_UPDATE = 'PERMISSIONS_UPDATE';
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/app/addons/permissions/base.js
----------------------------------------------------------------------
diff --git a/app/addons/permissions/base.js b/app/addons/permissions/base.js
index d6ddf5d..5123112 100644
--- a/app/addons/permissions/base.js
+++ b/app/addons/permissions/base.js
@@ -13,8 +13,11 @@
import app from "../../app";
import FauxtonAPI from "../../core/api";
import Permissions from "./routes";
+import reducer from './reducers';
import "./assets/less/permissions.less";
Permissions.initialize = function () {};
+FauxtonAPI.reducers.push(reducer);
+
export default Permissions;
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/app/addons/permissions/components.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/permissions/components.react.jsx b/app/addons/permissions/components.react.jsx
deleted file mode 100644
index 4b5d307..0000000
--- a/app/addons/permissions/components.react.jsx
+++ /dev/null
@@ -1,234 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import app from "../../app";
-import FauxtonAPI from "../../core/api";
-import React from "react";
-import Components from "../components/react-components.react";
-import Stores from "./stores";
-import Actions from "./actions";
-var LoadLines = Components.LoadLines;
-var permissionsStore = Stores.permissionsStore;
-var getDocUrl = app.helpers.getDocUrl;
-
-var PermissionsItem = React.createClass({
-
- removeItem: function (e) {
- this.props.removeItem({
- value: this.props.item,
- type: this.props.type,
- section: this.props.section
- });
- },
-
- render: function () {
- return (
- <li>
- <span>{this.props.item}</span>
- <button onClick={this.removeItem} type="button" className="pull-right close">�</button>
- </li>
- );
- }
-
-});
-
-var PermissionsSection = React.createClass({
- getInitialState: function () {
- return {
- newRole: '',
- newName: ''
- };
- },
-
- getHelp: function () {
- if (this.props.section === 'admins') {
- return 'Database members can access the database. If no members are defined, the database is public. ';
- }
-
- return 'Database members can access the database. If no members are defined, the database is public. ';
- },
-
- isEmptyValue: function (value, type) {
- if (!_.isEmpty(value)) {
- return false;
- }
- FauxtonAPI.addNotification({
- msg: 'Cannot add an empty value for ' + type + '.',
- type: 'warning'
- });
-
- return true;
- },
-
- addNames: function (e) {
- e.preventDefault();
- if (this.isEmptyValue(this.state.newName, 'names')) {
- return;
- }
- this.props.addItem({
- type: 'names',
- section: this.props.section,
- value: this.state.newName
- });
-
- this.setState({newName: ''});
- },
-
- addRoles: function (e) {
- e.preventDefault();
- if (this.isEmptyValue(this.state.newRole, 'roles')) {
- return;
- }
- this.props.addItem({
- type: 'roles',
- section: this.props.section,
- value: this.state.newRole
- });
-
- this.setState({newRole: ''});
- },
-
- getItems: function (items, type) {
- return _.map(items, function (item, i) {
- return <PermissionsItem key={i} item={item} section={this.props.section} type={type} removeItem={this.props.removeItem} />;
- }, this);
- },
-
- getNames: function () {
- return this.getItems(this.props.names, 'names');
- },
-
- getRoles: function () {
- return this.getItems(this.props.roles, 'roles');
- },
-
- nameChange: function (e) {
- this.setState({newName: e.target.value});
- },
-
- roleChange: function (e) {
- this.setState({newRole: e.target.value});
- },
-
- render: function () {
- return (
- <div>
- <header className="page-header">
- <h3>{this.props.section}</h3>
- <p className="help">
- {this.getHelp()}
- <a className="help-link" data-bypass="true" href={getDocUrl('DB_PERMISSION')} target="_blank">
- <i className="icon-question-sign"></i>
- </a>
- </p>
- </header>
- <div className="row-fluid">
- <div className="span6">
- <header>
- <h4>Users</h4>
- <p>Specify users who will have {this.props.section} access to this database.</p>
- </header>
- <form onSubmit={this.addNames} className="permission-item-form permissions-add-user form-inline">
- <input onChange={this.nameChange} value={this.state.newName} type="text" className="item input-small" placeholder="Add User" />
- <button type="submit" className="btn btn-success"><i className="icon fonticon-plus-circled" /> Add User</button>
- </form>
- <ul className="clearfix unstyled permission-items span10">
- {this.getNames()}
- </ul>
- </div>
- <div className="span6">
- <header>
- <h4>Roles</h4>
- <p>Users with any of the following role(s) will have {this.props.section} access.</p>
- </header>
- <form onSubmit={this.addRoles} className="permission-item-form permissions-add-role form-inline">
- <input onChange={this.roleChange} value={this.state.newRole} type="text" className="item input-small" placeholder="Add Role" />
- <button type="submit" className="btn btn-success"><i className="icon fonticon-plus-circled" /> Add Role</button>
- </form>
- <ul className="unstyled permission-items span10">
- {this.getRoles()}
- </ul>
- </div>
- </div>
- </div>
- );
- }
-
-});
-
-var PermissionsController = React.createClass({
-
- getStoreState: function () {
- return {
- isLoading: permissionsStore.isLoading(),
- adminRoles: permissionsStore.getAdminRoles(),
- adminNames: permissionsStore.getAdminNames(),
- memberRoles: permissionsStore.getMemberRoles(),
- memberNames: permissionsStore.getMemberNames(),
- };
- },
-
- getInitialState: function () {
- return this.getStoreState();
- },
-
- componentDidMount: function () {
- permissionsStore.on('change', this.onChange, this);
- },
-
- componentWillUnmount: function () {
- permissionsStore.off('change', this.onChange);
- },
-
- onChange: function () {
- this.setState(this.getStoreState());
- },
-
- addItem: function (options) {
- Actions.addItem(options);
- },
-
- removeItem: function (options) {
- Actions.removeItem(options);
- },
-
- render: function () {
- if (this.state.isLoading) {
- return <LoadLines />;
- }
-
- return (
- <div className="permissions-page flex-body">
- <div id="sections">
- <PermissionsSection roles={this.state.adminRoles}
- names={this.state.adminNames}
- addItem={this.addItem}
- removeItem={this.removeItem}
- section={'admins'} />
- <PermissionsSection
- roles={this.state.memberRoles}
- names={this.state.memberNames}
- addItem={this.addItem}
- removeItem={this.removeItem}
- section={'members'} />
- </div>
- </div>
- );
- }
-
-});
-
-export default {
- PermissionsController: PermissionsController,
- PermissionsSection: PermissionsSection,
- PermissionsItem: PermissionsItem
-};
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/app/addons/permissions/components/Permissions.js
----------------------------------------------------------------------
diff --git a/app/addons/permissions/components/Permissions.js b/app/addons/permissions/components/Permissions.js
new file mode 100644
index 0000000..49bc9c8
--- /dev/null
+++ b/app/addons/permissions/components/Permissions.js
@@ -0,0 +1,48 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import React, { Component, PropTypes } from 'react';
+
+import PermissionsScreen from './PermissionsScreen';
+
+import { fetchPermissions } from '../actions';
+import { LoadLines } from '../../components/components/loadlines';
+
+
+export default class Permissions extends Component {
+
+ constructor (props) {
+ super(props);
+ }
+
+ componentDidMount() {
+ const { dispatch, url } = this.props;
+ dispatch(fetchPermissions(url));
+ }
+
+ render () {
+ const { isLoading } = this.props;
+
+ return (
+ isLoading ? <LoadLines /> : <PermissionsScreen {...this.props} />
+ );
+ }
+};
+
+Permissions.propTypes = {
+ isLoading: PropTypes.bool.isRequired,
+ adminRoles: React.PropTypes.array.isRequired,
+ adminNames: React.PropTypes.array.isRequired,
+ memberNames: React.PropTypes.array.isRequired,
+ memberRoles: React.PropTypes.array.isRequired,
+ security: React.PropTypes.object.isRequired
+};
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/app/addons/permissions/components/PermissionsItem.js
----------------------------------------------------------------------
diff --git a/app/addons/permissions/components/PermissionsItem.js b/app/addons/permissions/components/PermissionsItem.js
new file mode 100644
index 0000000..6c03456
--- /dev/null
+++ b/app/addons/permissions/components/PermissionsItem.js
@@ -0,0 +1,36 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import React, { Component, PropTypes } from 'react';
+
+const PermissionsItem = ({removeItem, section, type, value}) =>�{
+
+ return (
+ <li className="permissions__entry">
+ <span>{value}</span>
+ <button
+ onClick={() => removeItem(section, type, value)}
+ type="button"
+ className="pull-right close"
+ >
+ �
+ </button>
+ </li>
+ );
+};
+
+PermissionsItem.propTypes = {
+ value: React.PropTypes.string.isRequired,
+ removeItem: PropTypes.func.isRequired,
+};
+
+export default PermissionsItem;
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/app/addons/permissions/components/PermissionsScreen.js
----------------------------------------------------------------------
diff --git a/app/addons/permissions/components/PermissionsScreen.js b/app/addons/permissions/components/PermissionsScreen.js
new file mode 100644
index 0000000..12bf0ad
--- /dev/null
+++ b/app/addons/permissions/components/PermissionsScreen.js
@@ -0,0 +1,85 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import FauxtonAPI from '../../../core/api';
+import React, { Component, PropTypes } from 'react';
+
+import PermissionsSection from './PermissionsSection';
+import { updatePermission, deletePermission } from '../actions';
+import { isValueAlreadySet } from '../helpers';
+
+export default class PermissionsScreen extends Component {
+
+ constructor (props) {
+ super(props);
+
+ this.addItem = this.addItem.bind(this);
+ this.removeItem = this.removeItem.bind(this);
+ }
+
+ addItem ({ section, type, value }) {
+
+ if (isValueAlreadySet(this.props.security, section, type, value)) {
+ FauxtonAPI.addNotification({
+ msg: 'Role/Name has already been added',
+ type: 'error'
+ });
+
+ return null;
+ }
+
+ this.props.dispatch(
+ updatePermission(this.props.url, this.props.security, section, type, value)
+ );
+ }
+
+ removeItem (section, type, value) {
+
+ this.props.dispatch(
+ deletePermission(this.props.url, this.props.security, section, type, value)
+ );
+ }
+
+ render () {
+ const {
+ adminRoles,
+ adminNames,
+ memberRoles,
+ memberNames
+ } = this.props;
+
+ return (
+ <div className="permissions-page flex-body">
+ <div>
+ <PermissionsSection
+ roles={adminRoles}
+ names={adminNames}
+ addItem={this.addItem}
+ removeItem={this.removeItem}
+ section="admins" />
+
+ <PermissionsSection
+ roles={memberRoles}
+ names={memberNames}
+ addItem={this.addItem}
+ removeItem={this.removeItem}
+ section="members" />
+ </div>
+ </div>
+ );
+ }
+
+};
+
+PermissionsScreen.propTypes = {
+ security: React.PropTypes.object.isRequired
+};
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/app/addons/permissions/components/PermissionsSection.js
----------------------------------------------------------------------
diff --git a/app/addons/permissions/components/PermissionsSection.js b/app/addons/permissions/components/PermissionsSection.js
new file mode 100644
index 0000000..95eb9bf
--- /dev/null
+++ b/app/addons/permissions/components/PermissionsSection.js
@@ -0,0 +1,163 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import React, { Component, PropTypes } from 'react';
+
+import FauxtonAPI from '../../../core/api';
+import app from '../../../app';
+import _ from 'lodash';
+
+import PermissionsItem from './PermissionsItem';
+
+
+const getDocUrl = app.helpers.getDocUrl;
+
+const PermissionsSection = React.createClass({
+ getInitialState: function () {
+ return {
+ newRole: '',
+ newName: ''
+ };
+ },
+
+ getDefaultProps: function () {
+ return {
+ names: [],
+ roles: []
+ };
+ },
+
+ getHelp: function () {
+ if (this.props.section === 'admins') {
+ return 'Database members can access the database. If no members are defined, the database is public. ';
+ }
+
+ return 'Database members can access the database. If no members are defined, the database is public. ';
+ },
+
+ isEmptyValue: function (value, type) {
+ if (!_.isEmpty(value)) {
+ return false;
+ }
+ FauxtonAPI.addNotification({
+ msg: 'Cannot add an empty value for ' + type + '.',
+ type: 'error'
+ });
+
+ return true;
+ },
+
+ addNames: function (e) {
+ e.preventDefault();
+ if (this.isEmptyValue(this.state.newName, 'names')) {
+ return;
+ }
+ this.props.addItem({
+ type: 'names',
+ section: this.props.section,
+ value: this.state.newName
+ });
+
+ this.setState({newName: ''});
+ },
+
+ addRoles: function (e) {
+ e.preventDefault();
+ if (this.isEmptyValue(this.state.newRole, 'roles')) {
+ return;
+ }
+ this.props.addItem({
+ type: 'roles',
+ section: this.props.section,
+ value: this.state.newRole
+ });
+
+ this.setState({newRole: ''});
+ },
+
+ getItems: function (items, type) {
+ return items.map((item, i) => {
+ return <PermissionsItem
+ key={i}
+ value={item}
+ section={this.props.section}
+ type={type}
+ removeItem={this.props.removeItem} />;
+ });
+ },
+
+ getNames: function () {
+ return this.getItems(this.props.names, 'names');
+ },
+
+ getRoles: function () {
+ return this.getItems(this.props.roles, 'roles');
+ },
+
+ nameChange: function (e) {
+ this.setState({newName: e.target.value});
+ },
+
+ roleChange: function (e) {
+ this.setState({newRole: e.target.value});
+ },
+
+ render: function () {
+
+ const { section } = this.props;
+
+ return (
+ <div className={"permissions__" + section}>
+ <header className="page-header">
+ <h3>{section}</h3>
+ <p className="help">
+ {this.getHelp()}
+ <a className="help-link" data-bypass="true" href={getDocUrl('DB_PERMISSION')} target="_blank">
+ <i className="icon-question-sign"></i>
+ </a>
+ </p>
+ </header>
+ <div className="row-fluid">
+ <div className="span6">
+ <header>
+ <h4>Users</h4>
+ <p>Specify users who will have {this.props.section} access to this database.</p>
+ </header>
+ <form onSubmit={this.addNames} className="permission-item-form permissions-add-user form-inline">
+ <input onChange={this.nameChange} value={this.state.newName} type="text" className="item input-small" placeholder="Add User" />
+ <button type="submit" className="btn btn-success"><i className="icon fonticon-plus-circled" /> Add User</button>
+ </form>
+ <ul className="unstyled permission-items span10">
+ {this.getNames()}
+ </ul>
+ </div>
+ <div className="span6">
+ <header>
+ <h4>Roles</h4>
+ <p>Users with any of the following role(s) will have {this.props.section} access.</p>
+ </header>
+ <form onSubmit={this.addRoles} className="permission-item-form permissions-add-role form-inline">
+ <input onChange={this.roleChange} value={this.state.newRole} type="text" className="item input-small" placeholder="Add Role" />
+ <button type="submit" className="btn btn-success"><i className="icon fonticon-plus-circled" /> Add Role</button>
+ </form>
+ <ul className="unstyled permission-items span10">
+ {this.getRoles()}
+ </ul>
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+});
+
+export default PermissionsSection;
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/app/addons/permissions/container/PermissionsContainer.js
----------------------------------------------------------------------
diff --git a/app/addons/permissions/container/PermissionsContainer.js b/app/addons/permissions/container/PermissionsContainer.js
new file mode 100644
index 0000000..0360ee7
--- /dev/null
+++ b/app/addons/permissions/container/PermissionsContainer.js
@@ -0,0 +1,40 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import { connect } from 'react-redux';
+
+import Permissions from '../components/Permissions';
+
+import {
+ getIsLoading,
+ getSecurity,
+ getAdminRoles,
+ getAdminNames,
+ getMemberNames,
+ getMemberRoles
+} from '../reducers';
+
+
+const mapStateToProps = (state) => {
+ return {
+ isLoading: getIsLoading(state),
+ adminRoles: getAdminRoles(state),
+ adminNames: getAdminNames(state),
+ memberNames: getMemberNames(state),
+ memberRoles: getMemberRoles(state),
+ security: getSecurity(state)
+ };
+};
+
+export default connect(
+ mapStateToProps
+)(Permissions);
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/app/addons/permissions/helpers.js
----------------------------------------------------------------------
diff --git a/app/addons/permissions/helpers.js b/app/addons/permissions/helpers.js
new file mode 100644
index 0000000..6658f8c
--- /dev/null
+++ b/app/addons/permissions/helpers.js
@@ -0,0 +1,39 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+export function isValueAlreadySet (p, section, type, value) {
+
+ if (!p[section]) {
+ return false;
+ }
+
+ if (!p[section][type]) {
+ return false;
+ }
+
+ return p[section][type].indexOf(value) !== -1;
+}
+
+export function addValueToPermissions (p, section, type, value) {
+
+ if (!p[section]) {
+ p[section] = {};
+ }
+
+ if (!p[section][type]) {
+ p[section][type] = [];
+ }
+
+ p[section][type].push(value);
+
+ return p;
+}
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/app/addons/permissions/layout.js
----------------------------------------------------------------------
diff --git a/app/addons/permissions/layout.js b/app/addons/permissions/layout.js
index 0ddeee0..84bd27c 100644
--- a/app/addons/permissions/layout.js
+++ b/app/addons/permissions/layout.js
@@ -13,7 +13,7 @@
import React from 'react';
import FauxtonAPI from "../../core/api";
import {TabsSidebarHeader} from '../documents/layouts';
-import Permissions from "./components.react";
+import PermissionsContainer from './container/PermissionsContainer';
import SidebarComponents from "../documents/sidebar/sidebar.react";
export const PermissionsLayout = ({docURL, database, endpoint, dbName, dropDownLinks}) => {
@@ -32,7 +32,7 @@ export const PermissionsLayout = ({docURL, database, endpoint, dbName, dropDownL
<SidebarComponents.SidebarController />
</aside>
<section id="dashboard-content" className="flex-layout flex-col">
- <Permissions.PermissionsController />
+ <PermissionsContainer url={endpoint} />
</section>
</div>
</div>
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/app/addons/permissions/reducers.js
----------------------------------------------------------------------
diff --git a/app/addons/permissions/reducers.js b/app/addons/permissions/reducers.js
new file mode 100644
index 0000000..3bf70b7
--- /dev/null
+++ b/app/addons/permissions/reducers.js
@@ -0,0 +1,67 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import {
+ PERMISSIONS_UPDATE
+} from './actiontypes';
+
+const initialState = {
+ isLoading: true,
+ security: {},
+ adminRoles: [],
+ adminNames: [],
+ memberNames: [],
+ memberRoles: []
+};
+
+export default function permissions (state = initialState, action) {
+ switch (action.type) {
+
+ case PERMISSIONS_UPDATE:
+ const { permissions } = action;
+ return Object.assign({}, state, {
+ isLoading: false,
+
+ security: permissions,
+ adminRoles: getRoles('admins', permissions),
+ adminNames: getNames('admins', permissions),
+ memberRoles: getRoles('members', permissions),
+ memberNames: getNames('members', permissions)
+ });
+
+ default:
+ return state;
+ }
+};
+
+function getRoles (type, permissions) {
+ if (!permissions[type]) {
+ return [];
+ }
+
+ return permissions[type].roles ? permissions[type].roles : [];
+}
+
+function getNames (type, permissions) {
+ if (!permissions[type]) {
+ return [];
+ }
+
+ return permissions[type].names ? permissions[type].names : [];
+}
+
+export const getIsLoading = state => state.isLoading;
+export const getSecurity = state => state.security;
+export const getAdminRoles = state => state.adminRoles;
+export const getAdminNames = state => state.adminNames;
+export const getMemberNames = state => state.memberNames;
+export const getMemberRoles = state => state.memberRoles;
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/app/addons/permissions/resources.js
----------------------------------------------------------------------
diff --git a/app/addons/permissions/resources.js b/app/addons/permissions/resources.js
deleted file mode 100644
index d4a4b58..0000000
--- a/app/addons/permissions/resources.js
+++ /dev/null
@@ -1,83 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import app from "../../app";
-import FauxtonAPI from "../../core/api";
-var Permissions = FauxtonAPI.addon();
-
-Permissions.Security = Backbone.Model.extend({
- defaults: {
- admins: { names: [], roles: [] },
- members: { names: [], roles: [] }
- },
-
- isNew: function () {
- return false;
- },
-
- initialize: function (attrs, options) {
- this.database = options.database;
- },
-
- url: function () {
- return window.location.origin + '/' + this.database.safeID() + '/_security';
- },
-
- documentation: FauxtonAPI.constants.DOC_URLS.DB_PERMISSION,
-
- addItem: function (value, type, section) {
- var sectionValues = this.get(section);
-
- var check = this.canAddItem(value, type, section);
- if (check.error) { return check;}
-
- sectionValues[type].push(value);
- return this.set(section, sectionValues);
- },
-
- canAddItem: function (value, type, section) {
- var sectionValues = this.get(section);
-
- if (!sectionValues || !sectionValues[type]) {
- return {
- error: true,
- msg: 'Section ' + section + ' does not exist'
- };
- }
-
- if (sectionValues[type].indexOf(value) > -1) {
- return {
- error: true,
- msg: 'Role/Name has already been added'
- };
- }
-
- return {
- error: false
- };
- },
-
- removeItem: function (value, type, section) {
- var sectionValues = this.get(section);
- var types = sectionValues[type];
- var indexOf = _.indexOf(types, value);
-
- if (indexOf === -1) { return;}
-
- types.splice(indexOf, 1);
- sectionValues[type] = types;
- return this.set(section, sectionValues);
- }
-
-});
-
-export default Permissions;
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/app/addons/permissions/routes.js
----------------------------------------------------------------------
diff --git a/app/addons/permissions/routes.js b/app/addons/permissions/routes.js
index bd87d59..5025daa 100644
--- a/app/addons/permissions/routes.js
+++ b/app/addons/permissions/routes.js
@@ -13,48 +13,48 @@
import app from "../../app";
import FauxtonAPI from "../../core/api";
import Databases from "../databases/base";
-import Resources from "./resources";
+
import Actions from "./actions";
import BaseRoute from "../documents/shared-routes";
import Layout from './layout';
import React from 'react';
const PermissionsRouteObject = BaseRoute.extend({
+
roles: ['fx_loggedIn'],
routes: {
'database/:database/permissions': 'permissions'
},
initialize: function (route, options) {
- var docOptions = app.getParams();
- docOptions.include_docs = true;
+ const docOptions = app.getParams();
- this.initViews(options[0]);
+ docOptions.include_docs = true;
},
- initViews: function (databaseName) {
- this.database = new Databases.Model({ id: databaseName });
- this.security = new Resources.Security(null, {
- database: this.database
- });
+ permissions: function (databaseId) {
+
+ // XXX magic inheritance props we need to maintain for BaseRoute
+ this.database = new Databases.Model({ id: databaseId });
+ // XXX magic methods we have to call - originating from BaseRoute.extend
this.createDesignDocsCollection();
this.addSidebar('permissions');
- },
- permissions: function () {
- Actions.fetchPermissions(this.database, this.security);
const crumbs = [
- { name: this.database.id, link: Databases.databaseUrl(this.database)},
+ { name: this.database.id, link: Databases.databaseUrl(databaseId)},
{ name: 'Permissions' }
];
+
+ const url = FauxtonAPI.urls('permissions', 'server', databaseId);
+
return <Layout
- docURL={this.security.documentation}
- endpoint={this.security.url('apiurl')}
+ docURL={FauxtonAPI.constants.DOC_URLS.DB_PERMISSION}
+ endpoint={url}
dbName={this.database.id}
dropDownLinks={crumbs}
- database={this.database}
- />;
+ database={this.database} />;
+
}
});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/app/addons/permissions/stores.js
----------------------------------------------------------------------
diff --git a/app/addons/permissions/stores.js b/app/addons/permissions/stores.js
deleted file mode 100644
index 9737cd0..0000000
--- a/app/addons/permissions/stores.js
+++ /dev/null
@@ -1,104 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import FauxtonAPI from "../../core/api";
-import ActionTypes from "./actiontypes";
-var Stores = {};
-
-Stores.PermissionsStore = FauxtonAPI.Store.extend({
- initialize: function () {
- this._isLoading = true;
- },
-
- isLoading: function () {
- return this._isLoading;
- },
-
- editPermissions: function (database, security) {
- this._database = database;
- this._security = security;
- this._isLoading = false;
- },
-
- getItem: function (section, type) {
- if (this._isLoading) {return [];}
-
- return this._security.get(section)[type];
- },
-
- getDatabase: function () {
- return this._database;
- },
-
- getSecurity: function () {
- return this._security;
- },
-
- getAdminRoles: function () {
- return this.getItem('admins', 'roles');
- },
-
- getAdminNames: function () {
- return this.getItem('admins', 'names');
- },
-
- getMemberNames: function () {
- return this.getItem('members', 'names');
- },
-
- getMemberRoles: function () {
- return this.getItem('members', 'roles');
- },
-
- addItem: function (options) {
- this._security.addItem(options.value, options.type, options.section);
- },
-
- removeItem: function (options) {
- this._security.removeItem(options.value, options.type, options.section);
- },
-
- dispatch: function (action) {
- switch (action.type) {
- case ActionTypes.PERMISSIONS_FETCHING:
- this._isLoading = true;
- this.triggerChange();
- break;
-
- case ActionTypes.PERMISSIONS_EDIT:
- this.editPermissions(action.database, action.security);
- this.triggerChange();
- break;
-
- case ActionTypes.PERMISSIONS_ADD_ITEM:
- this.addItem(action.options);
- this.triggerChange();
- break;
-
- case ActionTypes.PERMISSIONS_REMOVE_ITEM:
- this.removeItem(action.options);
- this.triggerChange();
- break;
-
- default:
- return;
- // do nothing
- }
- }
-
-});
-
-Stores.permissionsStore = new Stores.PermissionsStore();
-
-Stores.permissionsStore.dispatchToken = FauxtonAPI.dispatcher.register(Stores.permissionsStore.dispatch);
-
-export default Stores;
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/app/addons/permissions/tests/actionsSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/permissions/tests/actionsSpec.js b/app/addons/permissions/tests/actionsSpec.js
deleted file mode 100644
index 4424e04..0000000
--- a/app/addons/permissions/tests/actionsSpec.js
+++ /dev/null
@@ -1,122 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import FauxtonAPI from "../../../core/api";
-import Databases from "../../databases/base";
-import Stores from "../stores";
-import Permissions from "../resources";
-import Actions from "../actions";
-import testUtils from "../../../../test/mocha/testUtils";
-import sinon from "sinon";
-var assert = testUtils.assert;
-var restore = testUtils.restore;
-var store = Stores.permissionsStore;
-
-describe('Permissions Actions', function () {
- var getSecuritystub;
-
- beforeEach(function () {
- var databaseName = 'permissions-test';
- var database = new Databases.Model({ id: databaseName });
- Actions.editPermissions(
- database,
- new Permissions.Security(null, {
- database: database
- })
- );
-
-
- var promise = FauxtonAPI.Deferred();
- getSecuritystub = sinon.stub(store, 'getSecurity');
- getSecuritystub.returns({
- canAddItem: function () { return {error: true};},
- save: function () {
- return promise;
- }
- });
- });
-
- afterEach(function () {
- restore(store.getSecurity);
- });
-
- describe('add Item', function () {
-
- afterEach(function () {
- restore(FauxtonAPI.addNotification);
- restore(Actions.savePermissions);
- restore(store.getSecurity);
- });
-
- it('does not save item if cannot add it', function () {
- var spy = sinon.spy(FauxtonAPI, 'addNotification');
- var spy2 = sinon.spy(Actions, 'savePermissions');
-
- Actions.addItem({
- value: 'boom',
- type: 'names',
- section: 'members'
- });
-
- assert.ok(spy.calledOnce);
- assert.notOk(spy2.calledOnce);
- });
-
- it('save items', function () {
- var spy = sinon.spy(FauxtonAPI, 'addNotification');
- var spy2 = sinon.spy(Actions, 'savePermissions');
-
- var promise = FauxtonAPI.Deferred();
- getSecuritystub.returns({
- canAddItem: function () { return {error: false};},
- save: function () {
- return promise;
- }
- });
-
- Actions.addItem({
- value: 'boom',
- type: 'names',
- section: 'members'
- });
-
- assert.ok(spy2.calledOnce);
- assert.notOk(spy.calledOnce);
- });
- });
-
- describe('remove item', function () {
-
- afterEach(function () {
- restore(Actions.savePermissions);
- });
-
- it('saves item', function () {
- Actions.addItem({
- value: 'boom',
- type: 'names',
- section: 'members'
- });
-
- var spy = sinon.spy(Actions, 'savePermissions');
-
- Actions.removeItem({
- value: 'boom',
- type: 'names',
- section: 'members'
- });
-
- assert.ok(spy.calledOnce);
- });
-
- });
-});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/app/addons/permissions/tests/componentsSpec.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/permissions/tests/componentsSpec.react.jsx b/app/addons/permissions/tests/componentsSpec.react.jsx
deleted file mode 100644
index 1b90088..0000000
--- a/app/addons/permissions/tests/componentsSpec.react.jsx
+++ /dev/null
@@ -1,135 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-import FauxtonAPI from "../../../core/api";
-import Databases from "../../databases/base";
-import Permissions from "../resources";
-import Views from "../components.react";
-import Actions from "../actions";
-import utils from "../../../../test/mocha/testUtils";
-import React from "react";
-import ReactDOM from "react-dom";
-import sinon from "sinon";
-import { mount } from 'enzyme';
-var assert = utils.assert;
-var restore = utils.restore;
-
-FauxtonAPI.router = new FauxtonAPI.Router([]);
-
-describe('Permissions Components', () => {
-
- beforeEach(() => {
- var savePermissionsStub = sinon.stub(Actions, 'savePermissions');
- });
-
- afterEach(() => {
- restore(Actions.savePermissions);
- });
-
- describe('Permissions Controller', () => {
- afterEach(() => {
- restore(Actions.addItem);
- restore(Actions.removeItem);
- });
-
- it('on Add triggers add action', () => {
- var spy = sinon.spy(Actions, 'addItem');
- const el = mount(<Views.PermissionsController />);
- el.instance().addItem({});
- assert.ok(spy.calledOnce);
- });
-
- it('on Remove triggers remove action', () => {
- var spy = sinon.spy(Actions, 'removeItem');
- const el = mount(<Views.PermissionsController />);
- el.instance().removeItem({
- value: 'boom',
- type: 'names',
- section: 'members'
- });
- assert.ok(spy.calledOnce);
- });
-
- });
-
- describe('PermissionsSection', () => {
-
- it('adds user on submit', () => {
- const addSpy = sinon.spy();
- const el = mount(<Views.PermissionsSection section={'members'} roles={[]} names={[]} addItem={addSpy} />);
- el.find('.permissions-add-user input').simulate('change', {
- target: {
- value: 'newusername'
- }
- });
-
- el.find('.permissions-add-user').simulate('submit');
-
- var options = addSpy.args[0][0];
- assert.ok(addSpy.calledOnce);
- assert.equal(options.type, "names");
- assert.equal(options.section, "members");
- });
-
- it('adds role on submit', () => {
- const addSpy = sinon.spy();
- const el = mount(<Views.PermissionsSection section={'members'} roles={[]} names={[]} addItem={addSpy} />);
-
- el.find('.permissions-add-role input').simulate('change', {
- target: {
- value: 'newrole'
- }
- });
-
- el.find('.permissions-add-role').simulate('submit');
-
- var options = addSpy.args[0][0];
- assert.ok(addSpy.calledOnce);
- assert.equal(options.type, "roles");
- assert.equal(options.section, "members");
- });
-
- it('stores new name on change', () => {
- const addSpy = sinon.spy();
- var newName = 'newName';
- const el = mount(<Views.PermissionsSection section={'members'} roles={[]} names={[]} addItem={addSpy} />);
- el.find('.permissions-add-user .item').simulate('change', {
- target: {
- value: newName
- }
- });
-
- assert.equal(el.state().newName, newName);
- });
-
- it('stores new role on change', () => {
- var newRole = 'newRole';
- const addSpy = sinon.spy();
- const el = mount(<Views.PermissionsSection section={'members'} roles={[]} names={[]} addItem={addSpy} />);
- el.find('.permissions-add-role .item').simulate('change', {
- target: {
- value: newRole
- }
- });
- assert.equal(el.state().newRole, newRole);
- });
- });
-
- describe('PermissionsItem', () => {
-
- it('triggers remove on click', () => {
- const removeSpy = sinon.spy();
- const el = mount(<Views.PermissionsItem section={'members'} item={'test-item'} removeItem={removeSpy} />);
- el.find('.close').simulate('click');
- assert.ok(removeSpy.calledOnce);
- });
- });
-});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/app/addons/permissions/tests/nightwatch/permissions.js
----------------------------------------------------------------------
diff --git a/app/addons/permissions/tests/nightwatch/permissions.js b/app/addons/permissions/tests/nightwatch/permissions.js
new file mode 100644
index 0000000..3092bec
--- /dev/null
+++ b/app/addons/permissions/tests/nightwatch/permissions.js
@@ -0,0 +1,40 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+
+module.exports = {
+
+ 'CouchDB Database Permissions Test' : (client) => {
+
+ const waitTime = client.globals.maxWaitTime;
+ const newDatabaseName = client.globals.testDatabaseName;
+ const baseUrl = client.globals.test_settings.launch_url;
+
+ client
+ .loginToGUI()
+ .url(baseUrl + '/#/database/' + newDatabaseName + '/permissions')
+
+ .waitForElementVisible('.permissions__admins', waitTime, false)
+
+ .setValue('.permissions__admins [placeholder="Add User"]', 'blergie')
+ .clickWhenVisible('.permissions__admins .permissions-add-user button')
+
+ .waitForElementVisible('.permissions__admins .permissions__entry', waitTime, false)
+ .assert.containsText('.permissions__entry span', 'blergie')
+
+ .url(baseUrl + '/#/database/' + newDatabaseName + '/permissions')
+ .waitForElementVisible('.permissions__admins .permissions__entry', waitTime, false)
+ .assert.containsText('.permissions__entry span', 'blergie')
+
+ .end();
+ }
+};
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/app/addons/permissions/tests/resourceSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/permissions/tests/resourceSpec.js b/app/addons/permissions/tests/resourceSpec.js
deleted file mode 100644
index 7a77710..0000000
--- a/app/addons/permissions/tests/resourceSpec.js
+++ /dev/null
@@ -1,69 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-import FauxtonAPI from "../../../core/api";
-import Models from "../resources";
-import testUtils from "../../../../test/mocha/testUtils";
-var assert = testUtils.assert;
-
-describe('Permissions', function () {
-
- describe('#addItem', function () {
- var security;
-
- beforeEach(function () {
- security = new Models.Security(null, {database: 'fakedb'});
- });
-
- it('Should add value to section', function () {
-
- security.addItem('_user', 'names', 'admins');
- assert.equal(security.get('admins').names[0], '_user');
- });
-
- it('Should handle incorrect type', function () {
- security.addItem('_user', 'asdasd', 'admins');
- });
-
- it('Should handle incorrect section', function () {
- security.addItem('_user', 'names', 'Asdasd');
- });
-
- it('Should reject duplicates', function () {
- security.addItem('_user', 'names', 'admins');
- security.addItem('_user', 'names', 'admins');
- assert.equal(security.get('admins').names.length, 1);
- });
- });
-
- describe('#removeItem', function () {
- var security;
-
- beforeEach(function () {
- security = new Models.Security(null, {database: 'fakedb'});
- });
-
- it('removes value from section', function () {
- security.addItem('_user', 'names', 'admins');
- security.removeItem('_user', 'names', 'admins');
-
- assert.equal(security.get('admins').names.length, 0);
- });
-
- it('ignores non-existing value', function () {
- security.addItem('_user', 'names', 'admins');
- security.removeItem('wrong_user', 'names', 'admins');
- assert.equal(security.get('admins').names.length, 1);
- });
-
- });
-
-});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/app/core/base.js
----------------------------------------------------------------------
diff --git a/app/core/base.js b/app/core/base.js
index ef3d909..00e4f19 100644
--- a/app/core/base.js
+++ b/app/core/base.js
@@ -157,4 +157,6 @@ FauxtonAPI.setSession = function (newSession) {
return FauxtonAPI.session.fetchUser();
};
+FauxtonAPI.reducers = [];
+
export default FauxtonAPI;
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/app/helpers.js
----------------------------------------------------------------------
diff --git a/app/helpers.js b/app/helpers.js
index f80a3f0..9310ffc 100644
--- a/app/helpers.js
+++ b/app/helpers.js
@@ -21,6 +21,7 @@ import constants from "./constants";
import utils from "./core/utils";
import d3 from "d3";
import moment from "moment";
+import _ from 'lodash';
var Helpers = {};
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/app/main.js
----------------------------------------------------------------------
diff --git a/app/main.js b/app/main.js
index 0a1c7e0..7f092da 100644
--- a/app/main.js
+++ b/app/main.js
@@ -19,6 +19,9 @@ import Backbone from 'backbone';
import $ from 'jquery';
import AppWrapper from './addons/fauxton/appwrapper';
+import { createStore, applyMiddleware } from 'redux';
+import thunk from 'redux-thunk';
+import { Provider } from 'react-redux';
app.addons = LoadAddons;
FauxtonAPI.router = app.router = new FauxtonAPI.Router(app.addons);
@@ -54,4 +57,22 @@ $(document).on("click", "a:not([data-bypass])", function (evt) {
}
});
-ReactDOM.render(<AppWrapper router={app.router}/>, document.getElementById('app'));
+
+const reducer = FauxtonAPI.reducers.reduce((el, acc) => {
+ acc[el] = el;
+ return acc;
+}, {});
+
+const middlewares = [thunk];
+
+const store = createStore(
+ reducer,
+ applyMiddleware(...middlewares)
+);
+
+ReactDOM.render(
+ <Provider store={store}>
+ <AppWrapper router={app.router}/>
+ </Provider>,
+ document.getElementById('app')
+);
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/jest-config.json
----------------------------------------------------------------------
diff --git a/jest-config.json b/jest-config.json
index 3321768..7f817ce 100644
--- a/jest-config.json
+++ b/jest-config.json
@@ -1,3 +1,13 @@
{
- "testPathDirs": ["app"]
+ "testPathDirs": ["app"],
+
+ "setupTestFrameworkScriptFile": "jest-setup.js",
+
+ "moduleNameMapper": {
+ "bootstrap": "<rootDir>/assets/js/libs/bootstrap",
+ "underscore": "lodash",
+
+ "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|swf|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js",
+ "\\.(css|less)$": "<rootDir>/__mocks__/styleMock.js"
+ }
}
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/jest-setup.js
----------------------------------------------------------------------
diff --git a/jest-setup.js b/jest-setup.js
new file mode 100644
index 0000000..35502e6
--- /dev/null
+++ b/jest-setup.js
@@ -0,0 +1,18 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+const jest = require('jest');
+
+window.$ = window.jQuery = require('jquery');
+jest.mock('zeroclipboard', () => {});
+
+global.fetch = require('jest-fetch-mock');
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/25630f4e/package.json
----------------------------------------------------------------------
diff --git a/package.json b/package.json
index 2f141a6..bcd4fcb 100644
--- a/package.json
+++ b/package.json
@@ -20,12 +20,15 @@
"enzyme": "^2.4.1",
"es5-shim": "4.5.4",
"jest": "^17.0.3",
+ "jest-fetch-mock": "^1.0.6",
"mocha": "~3.1.2",
"mocha-loader": "^1.0.0",
"mocha-phantomjs": "git+https://github.com/garrensmith/mocha-phantomjs.git",
"nightwatch": "~0.9.0",
"phantomjs-prebuilt": "^2.1.7",
"react-addons-test-utils": "~15.4.1",
+ "redux-devtools": "^3.3.1",
+ "redux-mock-store": "^1.2.1",
"sinon": "git+https://github.com/sinonjs/sinon.git",
"url-polyfill": "github/url-polyfill"
},
@@ -41,6 +44,7 @@
"babel-register": "^6.4.3",
"backbone": "^1.1.0",
"base-64": "^0.1.0",
+ "bluebird": "^3.4.6",
"brace": "^0.7.0",
"chai": "^3.5.0",
"clean-css": "^3.4.9",
@@ -82,7 +86,10 @@
"react-addons-css-transition-group": "~15.4.1",
"react-bootstrap": "^0.30.7",
"react-dom": "~15.4.1",
+ "react-redux": "^4.4.5",
"react-select": "1.0.0-rc.2",
+ "redux": "^3.6.0",
+ "redux-thunk": "^2.1.0",
"request": "^2.54.0",
"semver": "^5.1.0",
"send": "^0.13.1",
@@ -96,6 +103,7 @@
"visualizeRevTree": "git+https://github.com/neojski/visualizeRevTree.git#gh-pages",
"webpack": "^1.12.12",
"webpack-dev-server": "^1.14.1",
+ "whatwg-fetch": "^2.0.1",
"zeroclipboard": "^2.2.0"
},
"scripts": {
@@ -104,7 +112,7 @@
"webpack:test": "webpack --debug --progress --colors --config ./webpack.config.test.js",
"webpack:release": "webpack --debug --progress --colors --config ./webpack.config.release.js",
"jest": "jest --config ./jest-config.json",
- "test": "npm run jest && grunt test",
+ "test": "grunt test && npm run jest",
"phantomjs": "./node_modules/.bin/mocha-phantomjs --debug=false --ssl-protocol=sslv2 --web-security=false --ignore-ssl-errors=true ./test/runner.html",
"couchdebug": "grunt couchdebug",
"couchdb": "grunt couchdb",