From 2823232c12528f51ef299381d6c8e9b1b9aadbc7 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Tue, 8 Nov 2016 15:25:48 -0700 Subject: [PATCH 01/15] add actions and reducers for settings --- .eslintrc.json | 4 ++ client/coral-admin/package.json | 3 +- client/coral-admin/src/actions/settings.js | 67 +++++++++++++++++++ .../coral-admin/src/containers/Configure.css | 1 + .../coral-admin/src/containers/Configure.js | 32 ++++++++- .../src/containers/ModerationQueue.js | 3 - client/coral-admin/src/reducers/index.js | 2 + client/coral-admin/src/reducers/settings.js | 42 ++++++++++++ 8 files changed, 147 insertions(+), 7 deletions(-) create mode 100644 client/coral-admin/src/actions/settings.js create mode 100644 client/coral-admin/src/reducers/settings.js diff --git a/.eslintrc.json b/.eslintrc.json index 5df3444a6..d15dc3367 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,4 +1,8 @@ { + "globals": { + "fetch": true, + "Headers": true + }, "env": { "es6": true, "node": true diff --git a/client/coral-admin/package.json b/client/coral-admin/package.json index 048359526..dcd26d06a 100644 --- a/client/coral-admin/package.json +++ b/client/coral-admin/package.json @@ -5,7 +5,8 @@ "main": "index.js", "scripts": { "build": "./node_modules/.bin/webpack --config webpack.config.js", - "start": "./node_modules/.bin/webpack-dev-server --config webpack.config.dev.js --inline --hot --content-base public --port 3142" + "start": "./node_modules/.bin/webpack-dev-server --config webpack.config.dev.js --inline --hot --content-base public --port 3142", + "dev": "./node_modules/.bin/webpack -w" }, "keywords": [], "author": "", diff --git a/client/coral-admin/src/actions/settings.js b/client/coral-admin/src/actions/settings.js new file mode 100644 index 000000000..73e7d9510 --- /dev/null +++ b/client/coral-admin/src/actions/settings.js @@ -0,0 +1,67 @@ +export const SETTINGS_LOADING = 'SETTINGS_LOADING' +export const SETTINGS_RECEIVED = 'SETTINGS_RECEIVED' +export const SETTINGS_FETCH_ERROR = 'SETTINGS_FETCH_ERROR' + +export const SETTINGS_UPDATED = 'SETTINGS_UPDATED' + +export const SAVE_SETTINGS_LOADING = 'SAVE_SETTINGS_LOADING' +export const SAVE_SETTINGS_SUCCESS = 'SAVE_SETTINGS_SUCCESS' +export const SAVE_SETTINGS_FAILED = 'SAVE_SETTINGS_FAILED' + +const base = '/api/v1' + +const getInit = (method, body) => { + const headers = new Headers({ + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }) + + const init = {method, headers} + if (method.toLowerCase() !== 'get') { + init.body = JSON.stringify(body) + } + + return init +} + +const handleResp = res => { + if (res.status === 401) { + throw new Error('Not Authorized to make this request') + } else if (res.status > 399) { + throw new Error('Error! Status ' + res.status) + } else if (res.status === 204) { + return res.text() + } else { + return res.json() + } +} + +export const fetchSettings = () => dispatch => { + dispatch({type: SETTINGS_LOADING}) + fetch(`${base}/settings`, getInit('GET')) + .then(handleResp) + .then(settings => { + dispatch({type: SETTINGS_RECEIVED, settings}) + }) + .catch(error => { + dispatch({type: SETTINGS_FETCH_ERROR, error}) + }) +} + +export const updateSettings = settings => { + console.log('updated settings', settings) + return {type: SETTINGS_UPDATED, settings} +} + +export const saveSettingsToServer = (dispatch, getState) => { + const settings = getState().settings.settings + dispatch({type: SAVE_SETTINGS_LOADING}) + fetch(`${base}/settings`, getInit('PUT', settings)) + .then(handleResp) + .then(() => { + dispatch({type: SAVE_SETTINGS_SUCCESS, settings}) + }) + .catch(error => { + dispatch({type: SAVE_SETTINGS_FAILED, error}) + }) +} diff --git a/client/coral-admin/src/containers/Configure.css b/client/coral-admin/src/containers/Configure.css index 2c65fd5bc..b5ec3735b 100644 --- a/client/coral-admin/src/containers/Configure.css +++ b/client/coral-admin/src/containers/Configure.css @@ -20,6 +20,7 @@ border-radius: 4px; height: 90px; margin-bottom: 10px; + cursor: pointer; } .configSettingEmbed { diff --git a/client/coral-admin/src/containers/Configure.js b/client/coral-admin/src/containers/Configure.js index 4a36e56e7..70c357cc5 100644 --- a/client/coral-admin/src/containers/Configure.js +++ b/client/coral-admin/src/containers/Configure.js @@ -1,5 +1,6 @@ import React from 'react' import {connect} from 'react-redux' +import {fetchSettings, updateSettings, saveSettingsToServer} from '../actions/settings' import { List, ListItem, @@ -17,12 +18,31 @@ class Configure extends React.Component { constructor (props) { super(props) this.state = {activeSection: 'comments'} + this.updateModeration = this.updateModeration.bind(this) + this.saveSettings = this.saveSettings.bind(this) + } + + componentWillMount () { + this.props.dispatch(fetchSettings()) + } + + updateModeration () { + const moderation = this.props.settings.moderation === 'pre' ? 'post' : 'pre' + this.props.dispatch(updateSettings({moderation})) + } + + saveSettings () { + this.props.dispatch(saveSettingsToServer()) } getCommentSettings () { return - + + + Enable pre-moderation @@ -75,7 +95,7 @@ class Configure extends React.Component { icon='code'>Embed Comment Stream - @@ -93,4 +113,10 @@ class Configure extends React.Component { } } -export default connect(x => x)(Configure) +const mapStateToProps = state => { + return { + settings: state.settings.toJS() + } +} + +export default connect(mapStateToProps)(Configure) diff --git a/client/coral-admin/src/containers/ModerationQueue.js b/client/coral-admin/src/containers/ModerationQueue.js index 061786543..3f13b8bf4 100644 --- a/client/coral-admin/src/containers/ModerationQueue.js +++ b/client/coral-admin/src/containers/ModerationQueue.js @@ -20,8 +20,6 @@ class ModerationQueue extends React.Component { constructor (props) { super(props) - console.log('ModerationQueue', props) - this.state = { activeTab: 'pending', singleView: false, modalOpen: false } } @@ -60,7 +58,6 @@ class ModerationQueue extends React.Component { render () { const { comments } = this.props const { activeTab, singleView, modalOpen } = this.state - console.log('moderation queue', styles) return ( diff --git a/client/coral-admin/src/reducers/index.js b/client/coral-admin/src/reducers/index.js index 5c99e3bb2..0c95fb566 100644 --- a/client/coral-admin/src/reducers/index.js +++ b/client/coral-admin/src/reducers/index.js @@ -1,8 +1,10 @@ import { combineReducers } from 'redux' import comments from 'reducers/comments' +import settings from 'reducers/settings' // Combine all reducers into a main one export default combineReducers({ + settings, comments }) diff --git a/client/coral-admin/src/reducers/settings.js b/client/coral-admin/src/reducers/settings.js new file mode 100644 index 000000000..9d877a36d --- /dev/null +++ b/client/coral-admin/src/reducers/settings.js @@ -0,0 +1,42 @@ +import { Map } from 'immutable' +import * as types from '../actions/settings' + +const initialState = Map({ + settings: Map(), + saveSettingsError: null, + fetchSettingsError: null, + fetchingSettings: false +}) + +// Handle the comment actions +export default (state = initialState, action) => { + switch (action.type) { + case types.SETTINGS_LOADING: return state.set('fetchingSettings', true).set('fetchSettingsError', null) + case types.SETTINGS_RECEIVED: return receivedSettings(state, action) + case types.SETTINGS_FETCH_ERROR: return settingsFetchFailed(state, action) + case types.SETTINGS_UPDAETD: return state.get('settings').merge(action.settings) + case types.SAVE_SETTINGS_LOADING: return state.set('fetchingSettings', true).set('saveSettingsError', null) + case types.SAVE_SETTINGS_SUCCESS: return saveComplete(state, action) + case types.SAVE_SETTINGS_FAILED: return settingsSaveFailed(state, action) + default: return state + } +} + +const receivedSettings = (state, action) => { + state.set('fetchingSettings', false).set('fetchSettingsError', null) + return state.get('settings').merge(action.settings) +} + +const saveComplete = (state, action) => { + state.set('fetchingSettings', false).set('saveSettingsError', null) + console.log('saveComplete', action.settings) + return state.get('settings').merge(action.settings) +} + +const settingsFetchFailed = (state, action) => { + return state.set('fetchingSettings', false).set('fetchSettingsError', action.error) +} + +const settingsSaveFailed = (state, action) => { + return state.set('fetchingSettings', false).set('fetchSettingsError', action.error) +} From ff52714f272aa69f0c090e9c0630d36de37ca528 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Tue, 8 Nov 2016 16:16:27 -0700 Subject: [PATCH 02/15] update state correctly. save to server correctly --- client/coral-admin/src/actions/settings.js | 5 ++--- client/coral-admin/src/containers/Configure.js | 6 +----- client/coral-admin/src/reducers/settings.js | 17 +++++++++-------- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/client/coral-admin/src/actions/settings.js b/client/coral-admin/src/actions/settings.js index 73e7d9510..488764c17 100644 --- a/client/coral-admin/src/actions/settings.js +++ b/client/coral-admin/src/actions/settings.js @@ -49,12 +49,11 @@ export const fetchSettings = () => dispatch => { } export const updateSettings = settings => { - console.log('updated settings', settings) return {type: SETTINGS_UPDATED, settings} } -export const saveSettingsToServer = (dispatch, getState) => { - const settings = getState().settings.settings +export const saveSettingsToServer = () => (dispatch, getState) => { + const settings = getState().settings.toJS().settings dispatch({type: SAVE_SETTINGS_LOADING}) fetch(`${base}/settings`, getInit('PUT', settings)) .then(handleResp) diff --git a/client/coral-admin/src/containers/Configure.js b/client/coral-admin/src/containers/Configure.js index 70c357cc5..12a47613c 100644 --- a/client/coral-admin/src/containers/Configure.js +++ b/client/coral-admin/src/containers/Configure.js @@ -113,10 +113,6 @@ class Configure extends React.Component { } } -const mapStateToProps = state => { - return { - settings: state.settings.toJS() - } -} +const mapStateToProps = state => state.settings.toJS() export default connect(mapStateToProps)(Configure) diff --git a/client/coral-admin/src/reducers/settings.js b/client/coral-admin/src/reducers/settings.js index 9d877a36d..60dcd05af 100644 --- a/client/coral-admin/src/reducers/settings.js +++ b/client/coral-admin/src/reducers/settings.js @@ -12,9 +12,9 @@ const initialState = Map({ export default (state = initialState, action) => { switch (action.type) { case types.SETTINGS_LOADING: return state.set('fetchingSettings', true).set('fetchSettingsError', null) - case types.SETTINGS_RECEIVED: return receivedSettings(state, action) + case types.SETTINGS_RECEIVED: return updateSettings(state, action) case types.SETTINGS_FETCH_ERROR: return settingsFetchFailed(state, action) - case types.SETTINGS_UPDAETD: return state.get('settings').merge(action.settings) + case types.SETTINGS_UPDATED: return updateSettings(state, action) case types.SAVE_SETTINGS_LOADING: return state.set('fetchingSettings', true).set('saveSettingsError', null) case types.SAVE_SETTINGS_SUCCESS: return saveComplete(state, action) case types.SAVE_SETTINGS_FAILED: return settingsSaveFailed(state, action) @@ -22,15 +22,16 @@ export default (state = initialState, action) => { } } -const receivedSettings = (state, action) => { - state.set('fetchingSettings', false).set('fetchSettingsError', null) - return state.get('settings').merge(action.settings) +const updateSettings = (state, action) => { + const s = state.set('fetchingSettings', false).set('fetchSettingsError', null) + const settings = s.get('settings').merge(action.settings) + return s.set('settings', settings) } const saveComplete = (state, action) => { - state.set('fetchingSettings', false).set('saveSettingsError', null) - console.log('saveComplete', action.settings) - return state.get('settings').merge(action.settings) + const s = state.set('fetchingSettings', false).set('saveSettingsError', null) + const settings = s.get('settings').merge(action.settings) + return s.set('settings', settings) } const settingsFetchFailed = (state, action) => { From c6188913d884fc70dee60f815e2f675a4bb14be2 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Tue, 8 Nov 2016 16:19:40 -0700 Subject: [PATCH 03/15] display a simple error text --- client/coral-admin/src/containers/Configure.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/coral-admin/src/containers/Configure.js b/client/coral-admin/src/containers/Configure.js index 12a47613c..364703934 100644 --- a/client/coral-admin/src/containers/Configure.js +++ b/client/coral-admin/src/containers/Configure.js @@ -75,10 +75,12 @@ class Configure extends React.Component { } render () { - const pageTitle = this.state.activeSection === 'comments' + let pageTitle = this.state.activeSection === 'comments' ? 'Comment Settings' : 'Embed Comment Stream' + if (this.props.fetchingSettings) pageTitle += ' - Loading...' + return (
@@ -101,6 +103,8 @@ class Configure extends React.Component {

{pageTitle}

+ { this.props.saveFetchingError } + { this.props.fetchSettingsError } { this.state.activeSection === 'comments' ? this.getCommentSettings() From 0968b02c3a1859abadb4f9b828b84d314f82ae70 Mon Sep 17 00:00:00 2001 From: gaba Date: Tue, 8 Nov 2016 15:26:05 -0800 Subject: [PATCH 04/15] Moderation queue depending on moderation settings. --- models/comment.js | 41 +++++++++++++- models/setting.js | 9 +++- routes/api/comments/index.js | 8 ++- tests/models/comment.js | 87 +++++++++++++++++++++++------- tests/routes/api/comments/index.js | 7 +++ 5 files changed, 129 insertions(+), 23 deletions(-) diff --git a/models/comment.js b/models/comment.js index 3aad5d978..0edbcb34e 100644 --- a/models/comment.js +++ b/models/comment.js @@ -1,6 +1,7 @@ const mongoose = require('../mongoose'); const uuid = require('uuid'); const Action = require('./action'); +const Setting = require('./setting'); const Schema = mongoose.Schema; @@ -74,14 +75,52 @@ CommentSchema.statics.findByActionType = function(action_type) { }); }; +/** + * Find not moderated comments by an action that was performed on them. + * @param {String} action_type the type of action that was performed on the comment + * @param {String} status the status of the comment to search for +*/ +CommentSchema.statics.findByStatusByActionType = function(status, action_type) { + return Action.findCommentsIdByActionType(action_type, 'comment').then((actions) => { + return Comment.find({'status': status, 'id': {'$in': actions.map(function(a){return a.item_id;})}}); + }); +}; + /** * Find comments by their status. - * @param {String} status the status to search for + * @param {String} status the status of the comment to search for */ CommentSchema.statics.findByStatus = function(status) { return Comment.find({'status': status}); }; +/** + * Find comments that need to be moderated (aka moderation queue). + * @param {String} moderationValue pre or post moderation setting. If it is undefined then look at the settings. +*/ +CommentSchema.statics.moderationQueue = function(moderationValue) { + + return Setting.getModerationSetting().then(function({moderation}){ + if (typeof moderationValue === 'undefined' || moderationValue === undefined) { + moderationValue = moderation; + } + switch(moderationValue){ + // Pre-moderation: New comments are shown in the moderator queues immediately. + case 'pre': + return Comment.findByStatus('').then((comments) => { + return comments; + }); + // Post-moderation: New comments do not appear in moderation queues unless they are flagged by other users. + case 'post': + return Comment.findByStatusByActionType('', 'flag').then((comments) => { + return comments; + }); + default: + throw new Error('Moderation setting not found.'); + } + }); +}; + //============================================================================== // Update Statics //============================================================================== diff --git a/models/setting.js b/models/setting.js index b47b6e9e2..15c457307 100644 --- a/models/setting.js +++ b/models/setting.js @@ -5,7 +5,6 @@ const SettingSchema = new Schema({ id: {type: String, default: '1'}, moderation: {type: String, enum: ['pre', 'post'], default: 'pre'} }, { - _id: false, timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' @@ -20,6 +19,14 @@ SettingSchema.statics.getSettings = function () { return this.findOne({id: '1'}); }; +/** + * gets the moderation settings and sends it back + * @return {Promise} moderation the settings for how to moderate comments + */ +SettingSchema.statics.getModerationSetting = function () { + return this.findOne({id: '1'}).select('moderation'); +}; + /** * this will update the settings object with whatever you pass in * @param {object} setting a hash of whatever settings you want to update diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js index b3e2bdee7..c51db9f26 100644 --- a/routes/api/comments/index.js +++ b/routes/api/comments/index.js @@ -27,6 +27,7 @@ router.get('/:comment_id', (req, res, next) => { // Moderation Queues Routes //============================================================================== +// Get all the comments that have that action_type over them. router.get('/action/:action_type', (req, res, next) => { Comment.findByActionType(req.params.action_type).then((comments) => { res.status(200).json(comments); @@ -35,6 +36,7 @@ router.get('/action/:action_type', (req, res, next) => { }); }); +// Get all the comments that were rejected. router.get('/status/rejected', (req, res, next) => { Comment.findByStatus('rejected').then((comments) => { res.status(200).json(comments); @@ -43,8 +45,12 @@ router.get('/status/rejected', (req, res, next) => { }); }); +// Returns back all the comments that are in the moderation queue. The moderation queue is pre or post moderated, +// depending on the settings. The :moderation overwrites this settings. +// Pre-moderation: New comments are shown in the moderator queues immediately. +// Post-moderation: New comments do not appear in moderation queues unless they are flagged by other users. router.get('/status/pending', (req, res, next) => { - Comment.findByStatus('').then((comments) => { + Comment.moderationQueue(req.query.moderation).then((comments) => { res.status(200).json(comments); }).catch(error => { next(error); diff --git a/tests/models/comment.js b/tests/models/comment.js index b3f0c0f30..20f501da1 100644 --- a/tests/models/comment.js +++ b/tests/models/comment.js @@ -1,30 +1,73 @@ require('../utils/mongoose'); const Comment = require('../../models/comment'); +const User = require('../../models/user'); +const Action = require('../../models/action'); +const Setting = require('../../models/setting'); + +const settings = {id: '1', moderation: 'pre'}; + const expect = require('chai').expect; describe('Comment: models', () => { - let mockComments; + const comments = [{ + body: 'comment 10', + asset_id: '123', + status: '', + parent_id: '', + author_id: '123', + id: '1' + }, { + body: 'comment 20', + asset_id: '123', + status: 'accepted', + parent_id: '', + author_id: '123', + id: '2' + }, { + body: 'comment 30', + asset_id: '456', + status: 'rejected', + parent_id: '', + author_id: '456', + id: '3' + }]; + + const users = [{ + id: '123', + display_name: 'Ana', + }, { + id: '456', + display_name: 'Maria', + }]; + + const actions = [{ + action_type: 'flag', + item_id: comments[0].id, + item_type: 'comment', + user_id: '123' + }, { + action_type: 'like', + item_id: comments[1].id, + item_type: 'comment', + user_id: '456' + }]; + beforeEach(() => { - return Comment.create([{ - body: 'comment 10', - asset_id: '123' - }, { - body: 'comment 20', - asset_id: '123' - }, { - body: 'comment 30', - asset_id: '456' - }]).then((comments) => { - mockComments = comments; + return Setting.create(settings).then(() => { + return Comment.create(comments); + }).then(() => { + return User.create(users); + }).then(() => { + return Action.create(actions); }); }); describe('#findById()', () => { it('should find a comment by id', () => { - return Comment.findById(mockComments[0].id).then((result) => { - expect(result).to.have.property('body') - .and.to.equal('comment 10'); + return Comment.findById('1').then((result) => { + expect(result).to.not.be.null; + expect(result).to.have.property('body', 'comment 10'); }); }); }); @@ -37,13 +80,17 @@ describe('Comment: models', () => { if (a.body < b.body) {return -1;} else {return 1;} }); - expect(result[0]).to.have.property('body') - .and.to.equal('comment 10'); - expect(result[1]).to.have.property('body') - .and.to.equal('comment 20'); + expect(result[0]).to.have.property('body', 'comment 10'); + expect(result[1]).to.have.property('body', 'comment 20'); }); }); }); - // }); + describe('#moderationQueue()', () => { + it('should find an array of new comments to moderate when pre-moderation'); + it('should find an array of new comments to moderate when post-moderation'); + it('should find an array of new comments to moderate when pre-moderation in settings'); + it('should find an array of new comments to moderate when post-moderation in settings'); + it('should fail when the moderation is not pre or post'); + }); }); diff --git a/tests/routes/api/comments/index.js b/tests/routes/api/comments/index.js index 378860bcb..5dc6d2d0b 100644 --- a/tests/routes/api/comments/index.js +++ b/tests/routes/api/comments/index.js @@ -14,6 +14,13 @@ const Comment = require('../../../../models/comment'); const Action = require('../../../../models/action'); const User = require('../../../../models/user'); +const Setting = require('../../../../models/setting'); +const settings = {id: '1', moderation: 'pre'}; + +beforeEach(() => { + return Setting.create(settings); +}); + describe('Get /comments', () => { const comments = [{ id: 'abc', From 3d17dc4c1aed1d19eacebff3d82d78df76b64d5a Mon Sep 17 00:00:00 2001 From: gaba Date: Tue, 8 Nov 2016 15:26:05 -0800 Subject: [PATCH 05/15] Moderation queue depending on moderation settings. From b19f936e64e4bb1e6a6bd9aca233c70cb65df2f3 Mon Sep 17 00:00:00 2001 From: gaba Date: Tue, 8 Nov 2016 15:59:35 -0800 Subject: [PATCH 06/15] Adds tests --- models/comment.js | 2 +- tests/models/comment.js | 43 +++++++++++++++++++++++++++++++++-------- tests/models/setting.js | 7 +++++++ 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/models/comment.js b/models/comment.js index 0edbcb34e..caefaa65f 100644 --- a/models/comment.js +++ b/models/comment.js @@ -116,7 +116,7 @@ CommentSchema.statics.moderationQueue = function(moderationValue) { return comments; }); default: - throw new Error('Moderation setting not found.'); + return Error('Moderation setting not found.') ; } }); }; diff --git a/tests/models/comment.js b/tests/models/comment.js index 20f501da1..e46345e39 100644 --- a/tests/models/comment.js +++ b/tests/models/comment.js @@ -27,7 +27,7 @@ describe('Comment: models', () => { }, { body: 'comment 30', asset_id: '456', - status: 'rejected', + status: '', parent_id: '', author_id: '456', id: '3' @@ -43,12 +43,12 @@ describe('Comment: models', () => { const actions = [{ action_type: 'flag', - item_id: comments[0].id, + item_id: '3', item_type: 'comment', user_id: '123' }, { action_type: 'like', - item_id: comments[1].id, + item_id: '1', item_type: 'comment', user_id: '456' }]; @@ -87,10 +87,37 @@ describe('Comment: models', () => { }); describe('#moderationQueue()', () => { - it('should find an array of new comments to moderate when pre-moderation'); - it('should find an array of new comments to moderate when post-moderation'); - it('should find an array of new comments to moderate when pre-moderation in settings'); - it('should find an array of new comments to moderate when post-moderation in settings'); - it('should fail when the moderation is not pre or post'); + it('should find an array of new comments to moderate when pre-moderation', () => { + return Comment.moderationQueue('pre').then((result) => { + expect(result).to.not.be.null; + expect(result).to.have.lengthOf(2); + }); + }); + it('should find an array of new comments to moderate when post-moderation', () => { + return Comment.moderationQueue('post').then((result) => { + expect(result).to.not.be.null; + expect(result).to.have.lengthOf(1); + expect(result[0]).to.have.property('body', 'comment 30'); + }); + }); + it('should find an array of new comments to moderate when pre-moderation in settings', () => { + return Comment.moderationQueue().then((result) => { + expect(result).to.not.be.null; + expect(result).to.have.lengthOf(2); + }); + }); + it('should find an array of new comments to moderate when post-moderation in settings', () => { + return Comment.moderationQueue('post').then((result) => { + expect(result).to.not.be.null; + expect(result).to.have.lengthOf(1); + expect(result[0]).to.have.property('body', 'comment 30'); + }); + }); + it('should fail when the moderation is not pre or post', () => { + return Comment.moderationQueue('any').then((error, result) => { + expect(error).to.not.be.null; + expect(result).to.be.null; + }); + }); }); }); diff --git a/tests/models/setting.js b/tests/models/setting.js index e2600fb0f..f50110635 100644 --- a/tests/models/setting.js +++ b/tests/models/setting.js @@ -29,4 +29,11 @@ describe('Setting: model', () => { }); }); + describe('#getModerationSetting', () => { + it('should return the moderation settings', () => { + return Setting.getModerationSetting().then(({moderation}) => { + expect(moderation).not.to.be.null; + }); + }); + }); }); From b5769f429c32a030cfeb19902eda6bb3525a1299 Mon Sep 17 00:00:00 2001 From: gaba Date: Tue, 8 Nov 2016 16:01:57 -0800 Subject: [PATCH 07/15] One test to do. --- tests/models/comment.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tests/models/comment.js b/tests/models/comment.js index e46345e39..d76277d6e 100644 --- a/tests/models/comment.js +++ b/tests/models/comment.js @@ -113,11 +113,6 @@ describe('Comment: models', () => { expect(result[0]).to.have.property('body', 'comment 30'); }); }); - it('should fail when the moderation is not pre or post', () => { - return Comment.moderationQueue('any').then((error, result) => { - expect(error).to.not.be.null; - expect(result).to.be.null; - }); - }); + it('should fail when the moderation is not pre or post'); }); }); From ab68b81f21762d89d0912c67026b810e47f21e3a Mon Sep 17 00:00:00 2001 From: gaba Date: Wed, 9 Nov 2016 09:33:15 -0800 Subject: [PATCH 08/15] Add tests to stream on moderated comments. --- models/comment.js | 14 ++++++++++++-- routes/api/stream/index.js | 4 ++++ tests/models/comment.js | 14 ++++++++++++-- tests/routes/api/comments/index.js | 24 ++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 4 deletions(-) diff --git a/models/comment.js b/models/comment.js index caefaa65f..1e89a3f92 100644 --- a/models/comment.js +++ b/models/comment.js @@ -58,13 +58,23 @@ CommentSchema.statics.findById = function(id) { }; /** - * Finds a comment by the asset_id. + * Finds ALL the comments by the asset_id. * @param {String} asset_id identifier of the asset which owns this comment (uuid) */ -CommentSchema.statics.findByAssetId = function(asset_id) { +CommentSchema.statics.findAllByAssetId = function(asset_id) { return Comment.find({asset_id}); }; +/** + * Finds the comments by the asset_id. + * get the comments that are approved. + * if post: get the comments that new and not flagged. + * @param {String} asset_id identifier of the asset which owns the comments (uuid) +*/ +CommentSchema.statics.findByAssetId = function(asset_id) { + return Comment.find({asset_id: asset_id, status:'accepted'}); +}; + /** * Find comments by an action that was performed on them. * @param {String} action_type the type of action that was performed on the comment diff --git a/routes/api/stream/index.js b/routes/api/stream/index.js index 67216a252..221a14c4d 100644 --- a/routes/api/stream/index.js +++ b/routes/api/stream/index.js @@ -8,8 +8,12 @@ const router = express.Router(); router.get('/', (req, res, next) => { + // find all the comments by a specific asset_id. + // - get the comments that are approved. + // - if post: get the comments that new and not flagged. const commentsPromise = Comment.findByAssetId(req.query.asset_id); + // get all the users and actions for those comments. commentsPromise.then(comments => { return Promise.all([ comments, diff --git a/tests/models/comment.js b/tests/models/comment.js index d76277d6e..6f051af39 100644 --- a/tests/models/comment.js +++ b/tests/models/comment.js @@ -73,8 +73,8 @@ describe('Comment: models', () => { }); describe('#findByAssetId()', () => { - it('should find an array of comments by asset id', () => { - return Comment.findByAssetId('123').then((result) => { + it('should find an array of all comments by asset id', () => { + return Comment.findAllByAssetId('123').then((result) => { expect(result).to.have.length(2); result.sort((a, b) => { if (a.body < b.body) {return -1;} @@ -84,6 +84,16 @@ describe('Comment: models', () => { expect(result[1]).to.have.property('body', 'comment 20'); }); }); + it('should find an array of approved comments by asset id', () => { + return Comment.findByAssetId('123').then((result) => { + expect(result).to.have.length(1); + result.sort((a, b) => { + if (a.body < b.body) {return -1;} + else {return 1;} + }); + expect(result[0]).to.have.property('body', 'comment 20'); + }); + }); }); describe('#moderationQueue()', () => { diff --git a/tests/routes/api/comments/index.js b/tests/routes/api/comments/index.js index 5dc6d2d0b..35959a182 100644 --- a/tests/routes/api/comments/index.js +++ b/tests/routes/api/comments/index.js @@ -140,6 +140,30 @@ describe('Get moderation queues rejected, pending, flags', () => { }); }); + it('should return all the pending comments as pre moderated', function(done){ + chai.request(app) + .get('/api/v1/comments/status/pending') + .query({'moderation': 'pre'}) + .end(function(err, res){ + expect(err).to.be.null; + expect(res).to.have.status(200); + expect(res.body[0]).to.have.property('id', 'def'); + done(); + }); + }); + + it('should return all the pending comments as post moderated', function(done){ + chai.request(app) + .get('/api/v1/comments/status/pending') + .query({'moderation': 'post'}) + .end(function(err, res){ + expect(err).to.be.null; + expect(res).to.have.status(200); + expect(res.body).to.have.lengthOf(0); + done(); + }); + }); + it('should return all the flagged comments', function(done){ chai.request(app) .get('/api/v1/comments/action/flag') From 9ff2613ab45e978f09cf552e93c38a29b621b5a0 Mon Sep 17 00:00:00 2001 From: gaba Date: Wed, 9 Nov 2016 11:40:57 -0800 Subject: [PATCH 09/15] Comment stream with new and approved when post-moderated. --- models/comment.js | 19 +++++++++++------ routes/api/stream/index.js | 24 ++++++++++++++------- tests/models/comment.js | 26 +++++++++++++++++++---- tests/routes/api/stream/index.js | 36 +++++++++++++++++++++++++------- 4 files changed, 80 insertions(+), 25 deletions(-) diff --git a/models/comment.js b/models/comment.js index 1e89a3f92..a84219043 100644 --- a/models/comment.js +++ b/models/comment.js @@ -61,20 +61,27 @@ CommentSchema.statics.findById = function(id) { * Finds ALL the comments by the asset_id. * @param {String} asset_id identifier of the asset which owns this comment (uuid) */ -CommentSchema.statics.findAllByAssetId = function(asset_id) { +CommentSchema.statics.findByAssetId = function(asset_id) { return Comment.find({asset_id}); }; /** - * Finds the comments by the asset_id. - * get the comments that are approved. - * if post: get the comments that new and not flagged. + * Finds the accepted comments by the asset_id. + * get the comments that are accepted. * @param {String} asset_id identifier of the asset which owns the comments (uuid) */ -CommentSchema.statics.findByAssetId = function(asset_id) { +CommentSchema.statics.findAcceptedByAssetId = function(asset_id) { return Comment.find({asset_id: asset_id, status:'accepted'}); }; +/** + * Finds the new and accepted comments by the asset_id. + * @param {String} asset_id identifier of the asset which owns the comments (uuid) +*/ +CommentSchema.statics.findNewByAssetId = function(asset_id) { + return Comment.find({asset_id: asset_id, status: {'$in': ['accepted', '']}}); +}; + /** * Find comments by an action that was performed on them. * @param {String} action_type the type of action that was performed on the comment @@ -126,7 +133,7 @@ CommentSchema.statics.moderationQueue = function(moderationValue) { return comments; }); default: - return Error('Moderation setting not found.') ; + throw new Error('Moderation setting not found.'); } }); }; diff --git a/routes/api/stream/index.js b/routes/api/stream/index.js index 221a14c4d..9fe7ac62a 100644 --- a/routes/api/stream/index.js +++ b/routes/api/stream/index.js @@ -4,16 +4,26 @@ const Comment = require('../../../models/comment'); const User = require('../../../models/user'); const Action = require('../../../models/action'); +const Setting = require('../../../models/setting'); + const router = express.Router(); +// Find all the comments by a specific asset_id. +// . if pre: get the comments that are accepted. +// . if post: get the comments that are new and accepted. router.get('/', (req, res, next) => { + const commentsPromise = Setting.getModerationSetting().then(({moderation}) => { + switch(moderation){ + case 'pre': + return Comment.findAcceptedByAssetId(req.query.asset_id); + case 'post': + return Comment.findNewByAssetId(req.query.asset_id); + default: + throw new Error('Moderation setting not found.'); + } + }); - // find all the comments by a specific asset_id. - // - get the comments that are approved. - // - if post: get the comments that new and not flagged. - const commentsPromise = Comment.findByAssetId(req.query.asset_id); - - // get all the users and actions for those comments. + // Get all the users and actions for those comments. commentsPromise.then(comments => { return Promise.all([ comments, @@ -21,7 +31,7 @@ router.get('/', (req, res, next) => { Action.findByItemIdArray(comments.map((comment) => comment.id)) ]); }).then(([comments, users, actions]) => { - res.json([...comments, ...users, ...actions]); + res.status(200).json([...comments, ...users, ...actions]); }).catch(error => { next(error); }); diff --git a/tests/models/comment.js b/tests/models/comment.js index 6f051af39..a76cea23e 100644 --- a/tests/models/comment.js +++ b/tests/models/comment.js @@ -31,6 +31,13 @@ describe('Comment: models', () => { parent_id: '', author_id: '456', id: '3' + }, { + body: 'comment 40', + asset_id: '123', + status: 'rejected', + parent_id: '', + author_id: '456', + id: '4' }]; const users = [{ @@ -74,18 +81,19 @@ describe('Comment: models', () => { describe('#findByAssetId()', () => { it('should find an array of all comments by asset id', () => { - return Comment.findAllByAssetId('123').then((result) => { - expect(result).to.have.length(2); + return Comment.findByAssetId('123').then((result) => { + expect(result).to.have.length(3); result.sort((a, b) => { if (a.body < b.body) {return -1;} else {return 1;} }); expect(result[0]).to.have.property('body', 'comment 10'); expect(result[1]).to.have.property('body', 'comment 20'); + expect(result[2]).to.have.property('body', 'comment 40'); }); }); - it('should find an array of approved comments by asset id', () => { - return Comment.findByAssetId('123').then((result) => { + it('should find an array of accepted comments by asset id', () => { + return Comment.findAcceptedByAssetId('123').then((result) => { expect(result).to.have.length(1); result.sort((a, b) => { if (a.body < b.body) {return -1;} @@ -94,6 +102,16 @@ describe('Comment: models', () => { expect(result[0]).to.have.property('body', 'comment 20'); }); }); + it('should find an array of new and accepted comments by asset id', () => { + return Comment.findNewByAssetId('123').then((result) => { + expect(result).to.have.length(2); + result.sort((a, b) => { + if (a.body < b.body) {return -1;} + else {return 1;} + }); + expect(result[0]).to.have.property('body', 'comment 10'); + }); + }); }); describe('#moderationQueue()', () => { diff --git a/tests/routes/api/stream/index.js b/tests/routes/api/stream/index.js index b1348e88a..97d9b3af9 100644 --- a/tests/routes/api/stream/index.js +++ b/tests/routes/api/stream/index.js @@ -12,21 +12,38 @@ const Action = require('../../../../models/action'); const User = require('../../../../models/user'); const Comment = require('../../../../models/comment'); +const Setting = require('../../../../models/setting'); + describe('api/stream: routes', () => { + + const settings = {id: '1', moderation: 'pre'}; + const comments = [{ id: 'abc', body: 'comment 10', asset_id: 'asset', - author_id: '123' + author_id: '123', + parent_id: '', + status: 'accepted' }, { id: 'def', body: 'comment 20', asset_id: 'asset', - author_id: '456' + author_id: '456', + parent_id: '', + status: '' + }, { + id: 'uio', + body: 'comment 30', + asset_id: 'asset', + author_id: '456', + parent_id: '', + status: '' }, { id: 'hij', - body: 'comment 30', - asset_id: '456' + body: 'comment 40', + asset_id: '456', + status: 'rejected' }]; const users = [{ @@ -46,10 +63,12 @@ describe('api/stream: routes', () => { }]; beforeEach(() => { - return Comment.create(comments).then(() => { - return User.create(users); - }).then(() => { - return Action.create(actions); + return Setting.create(settings).then(() => { + return Comment.create(comments).then(() => { + return User.create(users); + }).then(() => { + return Action.create(actions); + }); }); }); @@ -60,6 +79,7 @@ describe('api/stream: routes', () => { .end(function(err, res){ expect(err).to.be.null; expect(res).to.have.status(200); + expect(res.body.length).to.equal(3); done(); }); }); From eae2a0285ba24b0b74bd8d4992cfc28bd52cd13a Mon Sep 17 00:00:00 2001 From: gaba Date: Wed, 9 Nov 2016 12:35:59 -0800 Subject: [PATCH 10/15] More tests and move settings stuff to routes and out of models. --- models/comment.js | 37 +++++++++++++++--------------------- routes/api/comments/index.js | 19 +++++++++--------- tests/models/comment.js | 19 +++++------------- 3 files changed, 30 insertions(+), 45 deletions(-) diff --git a/models/comment.js b/models/comment.js index a84219043..03f3363be 100644 --- a/models/comment.js +++ b/models/comment.js @@ -1,7 +1,6 @@ const mongoose = require('../mongoose'); const uuid = require('uuid'); const Action = require('./action'); -const Setting = require('./setting'); const Schema = mongoose.Schema; @@ -115,27 +114,21 @@ CommentSchema.statics.findByStatus = function(status) { * Find comments that need to be moderated (aka moderation queue). * @param {String} moderationValue pre or post moderation setting. If it is undefined then look at the settings. */ -CommentSchema.statics.moderationQueue = function(moderationValue) { - - return Setting.getModerationSetting().then(function({moderation}){ - if (typeof moderationValue === 'undefined' || moderationValue === undefined) { - moderationValue = moderation; - } - switch(moderationValue){ - // Pre-moderation: New comments are shown in the moderator queues immediately. - case 'pre': - return Comment.findByStatus('').then((comments) => { - return comments; - }); - // Post-moderation: New comments do not appear in moderation queues unless they are flagged by other users. - case 'post': - return Comment.findByStatusByActionType('', 'flag').then((comments) => { - return comments; - }); - default: - throw new Error('Moderation setting not found.'); - } - }); +CommentSchema.statics.moderationQueue = function(moderation) { + switch(moderation){ + // Pre-moderation: New comments are shown in the moderator queues immediately. + case 'pre': + return Comment.findByStatus('').then((comments) => { + return comments; + }); + // Post-moderation: New comments do not appear in moderation queues unless they are flagged by other users. + case 'post': + return Comment.findByStatusByActionType('', 'flag').then((comments) => { + return comments; + }); + default: + throw new Error('Moderation setting not found.'); + } }; //============================================================================== diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js index c51db9f26..73ae3dea3 100644 --- a/routes/api/comments/index.js +++ b/routes/api/comments/index.js @@ -1,6 +1,8 @@ const express = require('express'); const Comment = require('../../../models/comment'); +const Setting = require('../../../models/setting'); + const router = express.Router(); //============================================================================== @@ -50,8 +52,14 @@ router.get('/status/rejected', (req, res, next) => { // Pre-moderation: New comments are shown in the moderator queues immediately. // Post-moderation: New comments do not appear in moderation queues unless they are flagged by other users. router.get('/status/pending', (req, res, next) => { - Comment.moderationQueue(req.query.moderation).then((comments) => { - res.status(200).json(comments); + Setting.getModerationSetting().then(function({moderation}){ + let moderationValue = req.query.moderation; + if (typeof moderationValue === 'undefined' || moderationValue === undefined) { + moderationValue = moderation; + } + Comment.moderationQueue(moderationValue).then((comments) => { + res.status(200).json(comments); + }); }).catch(error => { next(error); }); @@ -68,13 +76,6 @@ router.post('/', (req, res, next) => { }).catch(error => { next(error); }); - - // let comment = new Comment({body, author_id, asset_id, parent_id, status, username}); - // comment.save().then(({id}) => { - // res.status(200).send({'id': id}); - // }).catch(error => { - // next(error); - // }); }); router.post('/:comment_id', (req, res, next) => { diff --git a/tests/models/comment.js b/tests/models/comment.js index a76cea23e..0efd0c960 100644 --- a/tests/models/comment.js +++ b/tests/models/comment.js @@ -128,19 +128,10 @@ describe('Comment: models', () => { expect(result[0]).to.have.property('body', 'comment 30'); }); }); - it('should find an array of new comments to moderate when pre-moderation in settings', () => { - return Comment.moderationQueue().then((result) => { - expect(result).to.not.be.null; - expect(result).to.have.lengthOf(2); - }); - }); - it('should find an array of new comments to moderate when post-moderation in settings', () => { - return Comment.moderationQueue('post').then((result) => { - expect(result).to.not.be.null; - expect(result).to.have.lengthOf(1); - expect(result[0]).to.have.property('body', 'comment 30'); - }); - }); - it('should fail when the moderation is not pre or post'); + // it('should fail when the moderation is not pre or post', () => { + // return Comment.moderationQueue('any').catch(function(error) { + // expect(error).to.not.be.null; + // }); + // }); }); }); From d7ea18cba9b874b1f6bf893aa75bf8813a787e3f Mon Sep 17 00:00:00 2001 From: gaba Date: Wed, 9 Nov 2016 13:52:47 -0800 Subject: [PATCH 11/15] Fix linting problems. Remove comment of code. Add package. --- client/coral-admin/src/actions/settings.js | 2 +- package.json | 1 + tests/models/comment.js | 2 -- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/client/coral-admin/src/actions/settings.js b/client/coral-admin/src/actions/settings.js index e4df4adfe..f71730663 100644 --- a/client/coral-admin/src/actions/settings.js +++ b/client/coral-admin/src/actions/settings.js @@ -28,7 +28,7 @@ const handleResp = res => { if (res.status === 401) { throw new Error('Not Authorized to make this request'); } else if (res.status > 399) { - throw new Error('Error! Status ' + res.status); + throw new Error('Error! Status ', res.status); } else if (res.status === 204) { return res.text(); } else { diff --git a/package.json b/package.json index a3656ddba..7b6b66fe5 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "body-parser": "^1.15.2", "debug": "^2.2.0", "ejs": "^2.5.2", + "eslint-config-postcss": "^2.0.2", "express": "^4.14.0", "mongoose": "^4.6.5", "morgan": "^1.7.0", diff --git a/tests/models/comment.js b/tests/models/comment.js index b3f0c0f30..4ebcbbc64 100644 --- a/tests/models/comment.js +++ b/tests/models/comment.js @@ -44,6 +44,4 @@ describe('Comment: models', () => { }); }); }); - - // }); }); From decb6747fd5634f43d4eda6448ccaf91fef61f24 Mon Sep 17 00:00:00 2001 From: David Erwin Date: Wed, 9 Nov 2016 17:12:52 -0500 Subject: [PATCH 12/15] Comment out inactive Settings options --- client/coral-admin/src/containers/Configure.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/coral-admin/src/containers/Configure.js b/client/coral-admin/src/containers/Configure.js index 9f0b32dcf..0d4cf58d6 100644 --- a/client/coral-admin/src/containers/Configure.js +++ b/client/coral-admin/src/containers/Configure.js @@ -7,7 +7,7 @@ import { ListItem, ListItemContent, ListItemAction, - Textfield, + //Textfield, Checkbox, Button, Icon @@ -50,6 +50,7 @@ class Configure extends React.Component { Enable pre-moderation + {/* Include Comment Stream Description for Readers @@ -62,6 +63,7 @@ class Configure extends React.Component { error='Input is not a number!' label='Maximum Characters' /> + */} ; } From 39654f34f23f9715858d59f40c010fb9b5fdb83d Mon Sep 17 00:00:00 2001 From: David Erwin Date: Wed, 9 Nov 2016 17:15:39 -0500 Subject: [PATCH 13/15] Updating eslint as per @wyattjoh's request --- .eslintrc.json | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index ac9157879..98585fbc2 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,11 +1,8 @@ { - "globals": { - "fetch": true, - "Headers": true - }, "env": { "es6": true, - "node": true + "node": true, + "browser": true }, "extends": "eslint:recommended", "rules": { From f6473972d849d6316f4759fbb5780c791fac86a9 Mon Sep 17 00:00:00 2001 From: David Erwin Date: Wed, 9 Nov 2016 17:19:44 -0500 Subject: [PATCH 14/15] Remove browser: true in favor of client's eslint file --- .eslintrc.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 98585fbc2..23bdea410 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,8 +1,7 @@ { "env": { "es6": true, - "node": true, - "browser": true + "node": true }, "extends": "eslint:recommended", "rules": { From b6a0a99d913697e40fd28e61f9c7b2d1182fc10d Mon Sep 17 00:00:00 2001 From: David Erwin Date: Thu, 10 Nov 2016 10:10:55 -0500 Subject: [PATCH 15/15] Update method name for clarity --- models/comment.js | 2 +- routes/api/stream/index.js | 2 +- tests/models/comment.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/models/comment.js b/models/comment.js index 03f3363be..3f99bcb6a 100644 --- a/models/comment.js +++ b/models/comment.js @@ -77,7 +77,7 @@ CommentSchema.statics.findAcceptedByAssetId = function(asset_id) { * Finds the new and accepted comments by the asset_id. * @param {String} asset_id identifier of the asset which owns the comments (uuid) */ -CommentSchema.statics.findNewByAssetId = function(asset_id) { +CommentSchema.statics.findAcceptedAndNewByAssetId = function(asset_id) { return Comment.find({asset_id: asset_id, status: {'$in': ['accepted', '']}}); }; diff --git a/routes/api/stream/index.js b/routes/api/stream/index.js index 9fe7ac62a..4fe266294 100644 --- a/routes/api/stream/index.js +++ b/routes/api/stream/index.js @@ -17,7 +17,7 @@ router.get('/', (req, res, next) => { case 'pre': return Comment.findAcceptedByAssetId(req.query.asset_id); case 'post': - return Comment.findNewByAssetId(req.query.asset_id); + return Comment.findAcceptedAndNewByAssetId(req.query.asset_id); default: throw new Error('Moderation setting not found.'); } diff --git a/tests/models/comment.js b/tests/models/comment.js index f81cb7609..2a8225c72 100644 --- a/tests/models/comment.js +++ b/tests/models/comment.js @@ -103,7 +103,7 @@ describe('Comment: models', () => { }); }); it('should find an array of new and accepted comments by asset id', () => { - return Comment.findNewByAssetId('123').then((result) => { + return Comment.findAcceptedAndNewByAssetId('123').then((result) => { expect(result).to.have.length(2); result.sort((a, b) => { if (a.body < b.body) {return -1;}