Merge master into site-wide-comments

This commit is contained in:
David Erwin
2016-11-16 10:43:35 -05:00
15 changed files with 154 additions and 123 deletions
+8 -1
View File
@@ -5,7 +5,8 @@ import {
FETCH_COMMENTERS_SUCCESS,
FETCH_COMMENTERS_FAILURE,
SORT_UPDATE,
COMMENTERS_NEW_PAGE
COMMENTERS_NEW_PAGE,
SET_ROLE
} from '../constants/community';
import {base, getInit, handleResp} from '../helpers/response';
@@ -40,3 +41,9 @@ export const newPage = () => ({
type: COMMENTERS_NEW_PAGE
});
export const setRole = (id, role) => dispatch => {
return fetch(`${base}/user/${id}/role`, getInit('POST', {role}))
.then(() => {
return dispatch({type: SET_ROLE, id, role});
});
};
@@ -3,3 +3,4 @@ export const FETCH_COMMENTERS_SUCCESS = 'FETCH_COMMENTERS_SUCCESS';
export const FETCH_COMMENTERS_FAILURE = 'FETCH_COMMENTERS_FAILURE';
export const SORT_UPDATE = 'SORT_UPDATE';
export const COMMENTERS_NEW_PAGE = 'COMMENTERS_NEW_PAGE';
export const SET_ROLE = 'SET_ROLE';
@@ -19,6 +19,10 @@ const tableHeaders = [
{
title: lang.t('community.account_creation_date'),
field: 'created_at'
},
{
title: lang.t('community.newsroom_role'),
field: 'role'
}
];
@@ -1,34 +1,66 @@
import React from 'react';
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {SelectField, Option} from 'react-mdl-selectfield';
import styles from './Community.css';
import I18n from 'coral-framework/i18n/i18n';
import translations from '../../translations';
import {setRole} from '../../actions/community';
const Table = ({headers, data, onHeaderClickHandler}) => (
<table className={`mdl-data-table ${styles.dataTable}`}>
<thead>
<tr>
{headers.map((header, i) =>(
<th
key={i}
className="mdl-data-table__cell--non-numeric"
onClick={() => onHeaderClickHandler({field: header.field})}>
{header.title}
</th>
))}
</tr>
</thead>
<tbody>
{data.map((row, i)=> (
<tr key={i}>
<td className="mdl-data-table__cell--non-numeric">
{row.displayName}
<span className={styles.email}>{row.profiles.map(({id}) => id)}</span>
</td>
<td className="mdl-data-table__cell--non-numeric">
{row.created_at}
</td>
</tr>
))}
</tbody>
</table>
);
const lang = new I18n(translations);
export default Table;
class Table extends Component {
constructor (props) {
super(props);
this.onRoleChange = this.onRoleChange.bind(this);
}
onRoleChange (id, role) {
this.props.dispatch(setRole(id, role));
}
render () {
const {headers, commenters, onHeaderClickHandler} = this.props;
return (
<table className={`mdl-data-table ${styles.dataTable}`}>
<thead>
<tr>
{headers.map((header, i) =>(
<th
key={i}
className="mdl-data-table__cell--non-numeric"
onClick={() => onHeaderClickHandler({field: header.field})}>
{header.title}
</th>
))}
</tr>
</thead>
<tbody>
{commenters.map((row, i)=> (
<tr key={i}>
<td className="mdl-data-table__cell--non-numeric">
{row.displayName}
<span className={styles.email}>{row.profiles.map(({id}) => id)}</span>
</td>
<td className="mdl-data-table__cell--non-numeric">
{row.created_at}
</td>
<td className="mdl-data-table__cell--non-numeric">
<SelectField label={'Select me'} value={row.roles[0] || ''}
label={lang.t('community.role')}
onChange={role => this.onRoleChange(row.id, role)}>
<Option value={''}>.</Option>
<Option value={'moderator'}>{lang.t('community.moderator')}</Option>
<Option value={'admin'}>{lang.t('community.admin')}</Option>
</SelectField>
</td>
</tr>
))}
</tbody>
</table>
);
}
}
export default connect(state => ({commenters: state.community.get('commenters')}))(Table);
+9 -1
View File
@@ -4,7 +4,8 @@ import {
FETCH_COMMENTERS_REQUEST,
FETCH_COMMENTERS_FAILURE,
FETCH_COMMENTERS_SUCCESS,
SORT_UPDATE
SORT_UPDATE,
SET_ROLE
} from '../constants/community';
const initialState = Map({
@@ -37,6 +38,13 @@ export default function community (state = initialState, action) {
})
.set('commenters', commenters); // Sets to normal array
}
case SET_ROLE : {
const commenters = state.get('commenters');
const idx = commenters.findIndex(el => el.id === action.id);
commenters[idx].roles[0] = action.role;
return state.set('commenters', commenters.map(id => id));
}
case SORT_UPDATE :
return state
.set('field', action.sort.field)
+10 -2
View File
@@ -2,7 +2,11 @@ export default {
en: {
'community': {
username_and_email: 'Username and Email',
account_creation_date: 'Account Creation Date'
account_creation_date: 'Account Creation Date',
newsroom_role: 'Newsroom Role',
admin: 'Administrator',
moderator: 'Moderator',
role: 'Select role...'
},
'modqueue': {
'pending': 'pending',
@@ -30,7 +34,11 @@ export default {
es: {
'community': {
username_and_email: 'Usuario y E-mail',
account_creation_date: 'Fecha de creación de la cuenta'
account_creation_date: 'Fecha de creación de la cuenta',
newsroom_role: 'Rol en la redacción',
admin: 'Administrador',
moderator: 'Moderador',
role: 'Select role...'
},
'modqueue': {
'pending': 'pendiente',
+26 -54
View File
@@ -8,6 +8,23 @@ export const ADD_ITEM = 'ADD_ITEM';
export const UPDATE_ITEM = 'UPDATE_ITEM';
export const APPEND_ITEM_ARRAY = 'APPEND_ITEM_ARRAY';
const getInit = (method, body) => {
const headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
};
const init = {method, headers};
if (method.toLowerCase() !== 'get') {
init.body = JSON.stringify(body);
}
return init;
};
const responseHandler = response => {
return response.ok ? response.json() : Promise.reject(`${response.status} ${response.statusText}`);
};
/**
* Action creators
*/
@@ -80,11 +97,7 @@ export const appendItemArray = (id, property, value, add_to_front, item_type) =>
export function getStream (assetUrl) {
return (dispatch) => {
return fetch(`/api/v1/stream?asset_url=${encodeURIComponent(assetUrl)}`)
.then(
response => {
return response.ok ? response.json() : Promise.reject(`${response.status} ${response.statusText}`);
}
)
.then(responseHandler)
.then((json) => {
/* Add items to the store */
@@ -147,13 +160,8 @@ export function getStream (assetUrl) {
export function getItemsArray (ids) {
return (dispatch) => {
return fetch(`/v1/item/${ids}`)
.then(
response => {
return response.ok ? response.json()
: Promise.reject(`${response.status } ${ response.statusText}`);
}
)
return fetch(`/v1/item/${ids}`, getInit('GET'))
.then(responseHandler)
.then((json) => {
for (let i = 0; i < json.items.length; i++) {
dispatch(addItem(json.items[i]));
@@ -182,20 +190,8 @@ export function postItem (item, type, id) {
if (id) {
item.id = id;
}
let options = {
method: 'POST',
body: JSON.stringify(item),
headers: {
'Content-Type':'application/json'
}
};
return fetch(`/api/v1/${type}`, options)
.then(
response => {
return response.ok ? response.json()
: Promise.reject(`${response.status} ${response.statusText}`);
}
)
return fetch(`/api/v1/${type}`, getInit('POST', item))
.then(responseHandler)
.then((json) => {
dispatch(addItem({...item, id:json.id}, type));
return json.id;
@@ -226,21 +222,9 @@ export function postAction (item_id, action_type, user_id, item_type) {
action_type,
user_id
};
const options = {
method: 'POST',
headers: {
'Content-Type':'application/json'
},
body: JSON.stringify(action)
};
return fetch(`/api/v1/${item_type}/${item_id}/actions`, options)
.then(
response => {
return response.ok ? response.json()
: Promise.reject(`${response.status} ${response.statusText}`);
}
);
return fetch(`/api/v1/${item_type}/${item_id}/actions`, getInit('POST', action))
.then(responseHandler);
};
}
@@ -265,20 +249,8 @@ export function deleteAction (item_id, action_type, user_id, item_type) {
action_type,
user_id
};
const options = {
method: 'DELETE',
headers: {
'Content-Type':'application/json'
},
body: JSON.stringify(action)
};
return fetch(`/api/v1/${item_type}/${item_id}/actions`, options)
.then(
response => {
return response.ok ? response.text()
: Promise.reject(`${response.status} ${response.statusText}`);
}
);
return fetch(`/api/v1/${item_type}/${item_id}/actions`, getInit('DELETE', action))
.then(responseHandler);
};
}
+4 -4
View File
@@ -194,12 +194,12 @@ CommentSchema.statics.removeById = function(id) {
* @param {String} action_type the type of the action to be removed
* @param {String} user_id the id of the user performing the action
*/
CommentSchema.statics.removeAction = function(id, user_id, action_type) {
CommentSchema.statics.removeAction = function(item_id, user_id, action_type) {
return Action.remove({
action_type: action_type,
action_type,
item_type: 'comment',
item_id: id,
user_id: user_id
item_id,
user_id
});
};
+3 -1
View File
@@ -24,7 +24,9 @@ const UserSchema = new mongoose.Schema({
required: true
}
}],
roles: [String]
roles: {
type: [{type: String, enum: ['admin', 'moderator']}]
}
}, {
timestamps: {
createdAt: 'created_at',
+7 -16
View File
@@ -5,25 +5,20 @@
"main": "app.js",
"scripts": {
"start": "./bin/www",
"build": "webpack --config webpack.config.js --bail",
"build-watch": "webpack --config webpack.config.dev.js --watch",
"build": "NODE_ENV=production webpack --config webpack.config.js --bail",
"build-watch": "NODE_ENV=development webpack --config webpack.config.dev.js --watch",
"lint": "eslint bin/* .",
"lint-fix": "eslint . --fix",
"pretest": "npm install",
"test": "mocha --compilers js:babel-core/register --recursive tests",
"test-watch": "mocha --compilers js:babel-core/register --recursive -w tests",
"embed-start": "npm run build && ./bin/www"
"embed-start": "NODE_ENV=development npm run build && ./bin/www"
},
"config": {
"pre-git": {
"commit-msg": [],
"pre-commit": [
"npm run lint",
"npm test"
],
"pre-push": [
"npm test"
],
"pre-commit": ["npm run lint", "npm test"],
"pre-push": ["npm test"],
"post-commit": [],
"post-merge": []
}
@@ -32,12 +27,7 @@
"type": "git",
"url": "git+https://github.com/coralproject/talk.git"
},
"keywords": [
"talk",
"coral",
"coralproject",
"ask"
],
"keywords": ["talk", "coral", "coralproject", "ask"],
"author": "",
"license": "Apache-2.0",
"bugs": {
@@ -103,6 +93,7 @@
"react": "15.3.2",
"react-dom": "15.3.2",
"react-mdl": "^1.7.2",
"react-mdl-selectfield": "^0.2.0",
"react-onclickoutside": "^5.7.1",
"react-redux": "^4.4.5",
"react-router": "^3.0.0",
+3 -2
View File
@@ -126,7 +126,7 @@ router.delete('/:comment_id', (req, res, next) => {
Comment
.removeById(req.params.comment_id)
.then(() => {
res.status(201).send('OK. Removed');
res.status(201).send({});
})
.catch(error => {
next(error);
@@ -134,10 +134,11 @@ router.delete('/:comment_id', (req, res, next) => {
});
router.delete('/:comment_id/actions', (req, res, next) => {
console.log(req.params);
Comment
.removeAction(req.params.comment_id, req.body.user_id, req.body.action_type)
.then(() => {
res.status(201).send('OK. Removed');
res.status(201).send({});
})
.catch(error => {
next(error);
+12 -2
View File
@@ -40,11 +40,13 @@ router.get('/', (req, res, next) => {
])
.then(([data, count]) => {
const users = data.map((user) => {
const {displayName, created_at} = user;
const {id, displayName, created_at} = user;
return {
id,
displayName,
created_at,
profiles: user.toObject().profiles
profiles: user.toObject().profiles,
roles: user.toObject().roles
};
});
@@ -60,4 +62,12 @@ router.get('/', (req, res, next) => {
.catch(next);
});
router.post('/:user_id/role', (req, res, next) => {
User.addRoleToUser(req.params.user_id, req.body.role)
.then(role => {
res.send(role);
})
.catch(next);
});
module.exports = router;
@@ -122,6 +122,7 @@ describe('itemActions', () => {
{
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type':'application/json'
},
body: JSON.stringify(item.data)
@@ -169,11 +170,11 @@ describe('itemActions', () => {
describe('deleteAction', () => {
it ('should remove an action', () => {
fetchMock.delete('*', 'Action removed.');
fetchMock.delete('*', {});
return actions.deleteAction('abc', 'flag', '123', 'comments')(store.dispatch)
.then(response => {
expect(fetchMock.calls().matched[0][0]).to.equal('/api/v1/comments/abc/actions');
expect(response).to.equal('Action removed.');
expect(response).to.deep.equal({});
});
});
+1 -1
View File
@@ -85,7 +85,7 @@ module.exports = {
}),
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': `"${'development'}"`,
'NODE_ENV': `"${process.env.NODE_ENV}"`,
'VERSION': `"${require('./package.json').version}"`
}
})
-6
View File
@@ -5,12 +5,6 @@ const devConfig = require('./webpack.config.dev');
devConfig.devtool = null;
devConfig.plugins = devConfig.plugins.concat([
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': `"${'production'}"`,
'VERSION': `"${require('./package.json').version}"`
}
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false