From 78448b85c180a67f0e64b234146b15630003f6b4 Mon Sep 17 00:00:00 2001 From: David Jay Date: Fri, 9 Dec 2016 15:11:13 -0500 Subject: [PATCH 01/32] Updating to reflect new comment api. --- client/coral-plugin-commentbox/CommentBox.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/coral-plugin-commentbox/CommentBox.js b/client/coral-plugin-commentbox/CommentBox.js index 0167d517f..501d2ecc9 100644 --- a/client/coral-plugin-commentbox/CommentBox.js +++ b/client/coral-plugin-commentbox/CommentBox.js @@ -45,8 +45,7 @@ class CommentBox extends Component { postItem(comment, 'comments') .then((postedComment) => { const commentId = postedComment.id; - const status = postedComment.status; - if (status[0] && status[0].type === 'rejected') { + if (postedComment.status === 'rejected') { addNotification('error', lang.t('comment-post-banned-word')); } else if (premod === 'pre') { addNotification('success', lang.t('comment-post-notif-premod')); From 1c2ef760eff744f93121ac5708bfbdabff006730 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Fri, 9 Dec 2016 11:46:04 -1000 Subject: [PATCH 02/32] remove cruft by pulling out some immutable.js stuff --- client/coral-admin/src/components/Comment.js | 27 +++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/client/coral-admin/src/components/Comment.js b/client/coral-admin/src/components/Comment.js index 380a001c8..be656c22d 100644 --- a/client/coral-admin/src/components/Comment.js +++ b/client/coral-admin/src/components/Comment.js @@ -14,18 +14,19 @@ const linkify = new Linkify(); // Render a single comment for the list export default props => { - const authorStatus = props.author.get('status'); - const {comment, author} = props; - const links = linkify.getMatches(comment.get('body')); + const comment = props.comment.toJS(); + const author = props.author.toJS(); + let authorStatus = author.status; + const links = linkify.getMatches(comment.body); return (
  • person - {author.get('displayName') || lang.t('comment.anon')} - {timeago().format(comment.get('createdAt') || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))} - {comment.get('flagged') ?

    {lang.t('comment.flagged')}

    : null} + {author.displayName || lang.t('comment.anon')} + {timeago().format(comment.createdAt || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))} + {comment.flagged ?

    {lang.t('comment.flagged')}

    : null}
    {links ? @@ -42,7 +43,7 @@ export default props => {
    - {comment.get('body')} + {comment.body}
    @@ -52,9 +53,11 @@ export default props => { // Get the button of the action performed over a comment if any const getActionButton = (action, i, props) => { - const status = props.comment.get('status'); - const flagged = props.comment.get('flagged'); - const banned = (props.author.get('status') === 'banned'); + const comment = props.comment.toJS(); + const author = props.author.toJS(); + const status = comment.status; + const flagged = comment.flagged; + const banned = (author.status === 'banned'); if (action === 'flag' && (status || flagged === true)) { return null; @@ -64,7 +67,7 @@ const getActionButton = (action, i, props) => { @@ -74,7 +77,7 @@ const getActionButton = (action, i, props) => { props.onClickAction(props.actionsMap[action].status, props.comment.get('id'))} + onClick={() => props.onClickAction(props.actionsMap[action].status, comment.id)} /> ); }; From a80dd953c82c73095e5661351a048de2f4d92fd3 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Fri, 9 Dec 2016 14:13:48 -1000 Subject: [PATCH 03/32] update ModerationQueue and CommentStream to not use immutable.js deep in the stack --- client/coral-admin/src/components/Comment.js | 10 ++--- .../coral-admin/src/components/CommentList.js | 38 +++++++++++-------- .../containers/CommentStream/CommentStream.js | 13 +++++-- .../ModerationQueue/ModerationQueue.js | 29 ++++---------- 4 files changed, 44 insertions(+), 46 deletions(-) diff --git a/client/coral-admin/src/components/Comment.js b/client/coral-admin/src/components/Comment.js index be656c22d..4a21b0846 100644 --- a/client/coral-admin/src/components/Comment.js +++ b/client/coral-admin/src/components/Comment.js @@ -14,8 +14,7 @@ const linkify = new Linkify(); // Render a single comment for the list export default props => { - const comment = props.comment.toJS(); - const author = props.author.toJS(); + const {comment, author} = props; let authorStatus = author.status; const links = linkify.getMatches(comment.body); @@ -53,8 +52,7 @@ export default props => { // Get the button of the action performed over a comment if any const getActionButton = (action, i, props) => { - const comment = props.comment.toJS(); - const author = props.author.toJS(); + const {comment, author} = props; const status = comment.status; const flagged = comment.flagged; const banned = (author.status === 'banned'); @@ -74,7 +72,9 @@ const getActionButton = (action, i, props) => { ); } return ( - props.onClickAction(props.actionsMap[action].status, comment.id)} diff --git a/client/coral-admin/src/components/CommentList.js b/client/coral-admin/src/components/CommentList.js index b4547335e..eab2e637c 100644 --- a/client/coral-admin/src/components/CommentList.js +++ b/client/coral-admin/src/components/CommentList.js @@ -4,6 +4,8 @@ import styles from './CommentList.css'; import key from 'keymaster'; import Hammer from 'hammerjs'; import Comment from 'components/Comment'; +import head from 'lodash/head'; +import last from 'lodash/last'; // Each action has different meaning and configuration const actions = { @@ -37,7 +39,7 @@ export default class CommentList extends React.Component { // If entering to singleview and no active, active is the first eleement componentWillReceiveProps (nextProps) { if (nextProps.singleView && !this.state.active) { - this.setState({active: nextProps.commentIds.get(0)}); + this.setState({active: nextProps.commentIds[0]}); } } @@ -81,12 +83,12 @@ export default class CommentList extends React.Component { const {commentIds} = this.props; const {active} = this.state; // check boundaries - if (active === null || !commentIds.size) { - this.setState({active: commentIds.get(0)}); - } else if (direction === 'up' && active !== commentIds.first()) { - this.setState({active: commentIds.get(commentIds.indexOf(active) - 1)}); - } else if (direction === 'down' && active !== commentIds.last()) { - this.setState({active: commentIds.get(commentIds.indexOf(active) + 1)}); + if (active === null || !commentIds.length) { + this.setState({active: head(commentIds)}); + } else if (direction === 'up' && active !== head(commentIds)) { + this.setState({active: commentIds[commentIds.indexOf(active) - 1]}); + } else if (direction === 'down' && active !== last(commentIds)) { + this.setState({active: commentIds[commentIds.indexOf(active) + 1]}); } // scroll to the position @@ -105,10 +107,10 @@ export default class CommentList extends React.Component { // activate the next comment if (id === this.state.active) { const {commentIds} = this.props; - if (commentIds.last() === this.state.active) { - this.setState({active: commentIds.get(commentIds.size - 2)}); + if (last(commentIds) === this.state.active) { + this.setState({active: commentIds[commentIds.length - 2]}); } else { - this.setState({active: commentIds.get(Math.min(commentIds.indexOf(this.state.active) + 1, commentIds.size - 1))}); + this.setState({active: commentIds[Math.min(commentIds.indexOf(this.state.active) + 1, commentIds.l - 1)]}); } } this.props.onClickAction(action, id, author_id); @@ -119,16 +121,20 @@ export default class CommentList extends React.Component { } render () { - const {singleView, commentIds, comments, users, hideActive, key} = this.props; - const {active} = this.state; + const {singleView, commentIds, hideActive, key} = this.props; + let {active} = this.state; + + const users = this.props.users.toJS(); + const comments = this.props.comments.toJS(); return (
      {commentIds.map((commentId, index) => { - const comment = comments.get(commentId); + console.log('inside the map', typeof commentId, commentId, typeof active, active); + const comment = comments[commentId]; + const author = users[comment.author_id]; return { if (el && commentId === active) { this._active = el; } }} + author={author} key={index} index={index} onClickAction={this.onClickAction} @@ -137,7 +143,7 @@ export default class CommentList extends React.Component { actionsMap={actions} isActive={commentId === active} hideActive={hideActive} />; - }).toArray()} + })}
    ); } diff --git a/client/coral-admin/src/containers/CommentStream/CommentStream.js b/client/coral-admin/src/containers/CommentStream/CommentStream.js index 113a7bc86..556e50463 100644 --- a/client/coral-admin/src/containers/CommentStream/CommentStream.js +++ b/client/coral-admin/src/containers/CommentStream/CommentStream.js @@ -46,9 +46,9 @@ class CommentStream extends React.Component { @@ -58,4 +58,9 @@ class CommentStream extends React.Component { } } -export default connect(({comments, users}) => ({comments, users}))(CommentStream); +const mapStateToProps = state => ({ + comments: state.comments.toJS(), + users: state.users.toJS() +}); + +export default connect(mapStateToProps)(CommentStream); diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js index 34b418a57..ca3465903 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js @@ -79,6 +79,11 @@ class ModerationQueue extends React.Component { const {comments, users} = this.props; const {activeTab, singleView, modalOpen} = this.state; + const c = comments.toJS(); + const premodIds = c.ids.filter(id => c.byId[id].status === 'premod'); + const rejectedIds = c.ids.filter(id => c.byId[id].status === 'rejected'); + const flaggedIds = c.ids.filter(id => c.byId[id].flagged === true); + return (
    @@ -94,14 +99,7 @@ class ModerationQueue extends React.Component { - comments - .get('byId') - .get(id) - .get('status') === 'premod') - } + commentIds={premodIds} comments={comments.get('byId')} users={users.get('byId')} onClickAction={(action, commentId) => this.onCommentAction(action, commentId)} @@ -118,15 +116,7 @@ class ModerationQueue extends React.Component { - comments - .get('byId') - .get(id) - .get('status') === 'rejected') - } + commentIds={rejectedIds} comments={comments.get('byId')} users={users.get('byId')} onClickAction={(action, id) => this.onCommentAction(action, id)} @@ -137,10 +127,7 @@ class ModerationQueue extends React.Component { { - const data = comments.get('byId').get(id); - return !data.get('status') && data.get('flagged') === true; - })} + commentIds={flaggedIds} comments={comments.get('byId')} users={users.get('byId')} onClickAction={(action, id) => this.onCommentAction(action, id)} From 7732aa4abe251ce305052c1055e21e7bbaee9e91 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Fri, 9 Dec 2016 14:20:46 -1000 Subject: [PATCH 04/32] typo --- client/coral-admin/src/components/CommentList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-admin/src/components/CommentList.js b/client/coral-admin/src/components/CommentList.js index eab2e637c..be97a6ead 100644 --- a/client/coral-admin/src/components/CommentList.js +++ b/client/coral-admin/src/components/CommentList.js @@ -110,7 +110,7 @@ export default class CommentList extends React.Component { if (last(commentIds) === this.state.active) { this.setState({active: commentIds[commentIds.length - 2]}); } else { - this.setState({active: commentIds[Math.min(commentIds.indexOf(this.state.active) + 1, commentIds.l - 1)]}); + this.setState({active: commentIds[Math.min(commentIds.indexOf(this.state.active) + 1, commentIds.length - 1)]}); } } this.props.onClickAction(action, id, author_id); From b05f4cfb16382fd09ee83c781992573dd83b9946 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Fri, 9 Dec 2016 14:25:24 -1000 Subject: [PATCH 05/32] remove logs --- client/coral-admin/src/components/CommentList.js | 1 - 1 file changed, 1 deletion(-) diff --git a/client/coral-admin/src/components/CommentList.js b/client/coral-admin/src/components/CommentList.js index be97a6ead..638a45adb 100644 --- a/client/coral-admin/src/components/CommentList.js +++ b/client/coral-admin/src/components/CommentList.js @@ -130,7 +130,6 @@ export default class CommentList extends React.Component { return (
      {commentIds.map((commentId, index) => { - console.log('inside the map', typeof commentId, commentId, typeof active, active); const comment = comments[commentId]; const author = users[comment.author_id]; return Date: Fri, 9 Dec 2016 14:38:29 -1000 Subject: [PATCH 06/32] have moderation queue use proper mapStateToProps --- .../coral-admin/src/components/CommentList.js | 5 +--- .../ModerationQueue/ModerationQueue.js | 30 +++++++++++-------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/client/coral-admin/src/components/CommentList.js b/client/coral-admin/src/components/CommentList.js index 638a45adb..55d6669d0 100644 --- a/client/coral-admin/src/components/CommentList.js +++ b/client/coral-admin/src/components/CommentList.js @@ -121,12 +121,9 @@ export default class CommentList extends React.Component { } render () { - const {singleView, commentIds, hideActive, key} = this.props; + const {singleView, commentIds, comments, users, hideActive, key} = this.props; let {active} = this.state; - const users = this.props.users.toJS(); - const comments = this.props.comments.toJS(); - return (
        {commentIds.map((commentId, index) => { diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js index ca3465903..e0e3c636f 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js @@ -79,10 +79,9 @@ class ModerationQueue extends React.Component { const {comments, users} = this.props; const {activeTab, singleView, modalOpen} = this.state; - const c = comments.toJS(); - const premodIds = c.ids.filter(id => c.byId[id].status === 'premod'); - const rejectedIds = c.ids.filter(id => c.byId[id].status === 'rejected'); - const flaggedIds = c.ids.filter(id => c.byId[id].flagged === true); + const premodIds = comments.ids.filter(id => comments.byId[id].status === 'premod'); + const rejectedIds = comments.ids.filter(id => comments.byId[id].status === 'rejected'); + const flaggedIds = comments.ids.filter(id => comments.byId[id].flagged === true); return (
        @@ -100,25 +99,25 @@ class ModerationQueue extends React.Component { isActive={activeTab === 'pending'} singleView={singleView} commentIds={premodIds} - comments={comments.get('byId')} - users={users.get('byId')} + comments={comments.byId} + users={users.byId} onClickAction={(action, commentId) => this.onCommentAction(action, commentId)} onClickShowBanDialog={(userId, userName, commentId) => this.showBanUserDialog(userId, userName, commentId)} actions={['reject', 'approve', 'ban']} loading={comments.loading} /> this.hideBanUserDialog()} onClickBanUser={(userId, commentId) => this.banUser(userId, commentId)} - user={comments.get('banUser')}/> + user={comments.banUser}/>
        this.onCommentAction(action, id)} actions={['approve']} loading={comments.loading} /> @@ -128,8 +127,8 @@ class ModerationQueue extends React.Component { isActive={activeTab === 'rejected'} singleView={singleView} commentIds={flaggedIds} - comments={comments.get('byId')} - users={users.get('byId')} + comments={comments.byId} + users={users.byId} onClickAction={(action, id) => this.onCommentAction(action, id)} actions={['reject', 'approve']} loading={comments.loading} /> @@ -142,6 +141,11 @@ class ModerationQueue extends React.Component { } } -export default connect(({comments, users}) => ({comments, users}))(ModerationQueue); +const mapStateToProps = state => ({ + comments: state.comments.toJS(), + users: state.users.toJS() +}); + +export default connect(mapStateToProps)(ModerationQueue); const lang = new I18n(translations); From 74c4d812cddfa54614504bd183f5ac3983e6d95e Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Fri, 9 Dec 2016 14:39:16 -1000 Subject: [PATCH 07/32] less changes --- client/coral-admin/src/components/CommentList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-admin/src/components/CommentList.js b/client/coral-admin/src/components/CommentList.js index 55d6669d0..8f5754182 100644 --- a/client/coral-admin/src/components/CommentList.js +++ b/client/coral-admin/src/components/CommentList.js @@ -122,7 +122,7 @@ export default class CommentList extends React.Component { render () { const {singleView, commentIds, comments, users, hideActive, key} = this.props; - let {active} = this.state; + const {active} = this.state; return (
          From 7bfd9e2e714db5ee2cfb59fd9e6734d5f3600988 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Fri, 9 Dec 2016 14:48:29 -1000 Subject: [PATCH 08/32] remove lodash implementation --- client/coral-admin/src/components/CommentList.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/client/coral-admin/src/components/CommentList.js b/client/coral-admin/src/components/CommentList.js index 8f5754182..8c6655bb5 100644 --- a/client/coral-admin/src/components/CommentList.js +++ b/client/coral-admin/src/components/CommentList.js @@ -4,8 +4,6 @@ import styles from './CommentList.css'; import key from 'keymaster'; import Hammer from 'hammerjs'; import Comment from 'components/Comment'; -import head from 'lodash/head'; -import last from 'lodash/last'; // Each action has different meaning and configuration const actions = { @@ -84,10 +82,10 @@ export default class CommentList extends React.Component { const {active} = this.state; // check boundaries if (active === null || !commentIds.length) { - this.setState({active: head(commentIds)}); - } else if (direction === 'up' && active !== head(commentIds)) { + this.setState({active: commentIds[0]}); + } else if (direction === 'up' && active !== commentIds[0]) { this.setState({active: commentIds[commentIds.indexOf(active) - 1]}); - } else if (direction === 'down' && active !== last(commentIds)) { + } else if (direction === 'down' && active !== commentIds[commentIds.length - 1]) { this.setState({active: commentIds[commentIds.indexOf(active) + 1]}); } @@ -107,7 +105,7 @@ export default class CommentList extends React.Component { // activate the next comment if (id === this.state.active) { const {commentIds} = this.props; - if (last(commentIds) === this.state.active) { + if (commentIds[commentIds.length - 1] === this.state.active) { this.setState({active: commentIds[commentIds.length - 2]}); } else { this.setState({active: commentIds[Math.min(commentIds.indexOf(this.state.active) + 1, commentIds.length - 1)]}); From 81071eacd6361f5f8f080db7918454a9ec7861be Mon Sep 17 00:00:00 2001 From: gaba Date: Sat, 10 Dec 2016 14:00:46 -1000 Subject: [PATCH 09/32] It adds a status field and change the name of the closedAt and closedMessage to record changes from the status from closed to open too. --- models/asset.js | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/models/asset.js b/models/asset.js index 3c68b7c13..c1fc59dc3 100644 --- a/models/asset.js +++ b/models/asset.js @@ -5,6 +5,12 @@ const Setting = require('./setting'); const uuid = require('uuid'); +// ASSET_STATUSES is the list of statuses that are permitted for the asset status. +const ASSET_STATUS = [ + 'open', + 'closed' +]; + const AssetSchema = new Schema({ id: { type: String, @@ -29,14 +35,15 @@ const AssetSchema = new Schema({ type: Schema.Types.Mixed, default: null }, - closedAt: { + status: { + type: String, + default: 'open' + }, + statusChangedAt: { type: Date, default: null }, - closedMessage: { - type: String, - default: null - }, + statusClosedMessage: String, title: String, description: String, image: String, @@ -68,9 +75,27 @@ AssetSchema.index({ * Returns true if the asset is closed, false else. */ AssetSchema.virtual('isClosed').get(function() { - return this.closedAt && this.closedAt.getTime() <= new Date().getTime(); + return (this.status === 'closed') && this.statusChangedAt && this.statusChangedAt.getTime() <= new Date().getTime(); }); +/** + * Close or Open the asset. + */ +AssetSchema.statics.changeOpenStatus = (id, status, closedMessage = '') => { + // Check to see if the user role is in the allowable set of roles. + if (ASSET_STATUS.indexOf(status) === -1) { + // Asset status is not supported! Error out here. + return Promise.reject(new Error(`status ${status} is not supported`)); + } + return Asset.update({id}, { + $set: { + status: status, + statusChangedAt: new Date().getTime(), + statusClosedMessage: closedMessage + } + }); +}; + /** * Finds an asset by its id. * @param {String} id identifier of the asset (uuid). From 5e21102c2fb9573ec9bb650555da1cd6ea02a971 Mon Sep 17 00:00:00 2001 From: gaba Date: Sat, 10 Dec 2016 14:01:24 -1000 Subject: [PATCH 10/32] Fix routes and tests for asset status change. --- routes/api/asset/index.js | 4 ++-- routes/api/comments/index.js | 2 +- tests/routes/api/comments/index.js | 5 +++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/routes/api/asset/index.js b/routes/api/asset/index.js index 2ddc3ea23..ebdcf038d 100644 --- a/routes/api/asset/index.js +++ b/routes/api/asset/index.js @@ -90,9 +90,9 @@ router.put('/:asset_id/settings', (req, res, next) => { }); router.put('/:asset_id/status', (req, res, next) => { - // Update the asset status + Asset - .update({id: req.params.asset_id}, {status: req.query.status}) + .changeOpenStatus(req.params.asset_id, req.query.status, req.query.closedMessage) .then(() => res.status(204).end()) .catch((err) => next(err)); }); diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js index 168b89fc3..fad9083cc 100644 --- a/routes/api/comments/index.js +++ b/routes/api/comments/index.js @@ -89,7 +89,7 @@ router.post('/', wordlist.filter('body'), (req, res, next) => { if (asset.isClosed) { // They have, ensure that we send back an error. - return Promise.reject(new Error(`asset has commenting closed because: ${asset.closedMessage}`)); + return Promise.reject(new Error(`asset has commenting closed because: ${asset.statusClosedMessage}`)); } return asset; diff --git a/tests/routes/api/comments/index.js b/tests/routes/api/comments/index.js index b3e4e2675..4787e4d58 100644 --- a/tests/routes/api/comments/index.js +++ b/tests/routes/api/comments/index.js @@ -199,8 +199,9 @@ describe('/api/v1/comments', () => { it('shouldn\'t create a comment when the asset has expired commenting', () => { return Asset.create({ - closedAt: new Date().setDate(0), - closedMessage: 'tests said expired!' + status: 'closed', + statusChangedAt: new Date().setDate(0), + statusClosedMessage: 'tests said expired!' }) .then((asset) => { return chai.request(app) From a872128a5582f906af9263788fb9399856c62d34 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Sun, 11 Dec 2016 22:52:47 -0300 Subject: [PATCH 11/32] logOut Stae --- client/coral-embed-stream/src/CommentStream.js | 4 ++-- client/coral-framework/actions/auth.js | 12 +++++++----- client/coral-framework/reducers/auth.js | 7 ++++--- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index 7ec042ea1..b53832ce5 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -91,7 +91,7 @@ class CommentStream extends Component { const rootItem = this.props.items.assets && this.props.items.assets[rootItemId]; const {actions, users, comments} = this.props.items; const {status, moderation, closedMessage} = this.props.config; - const {loggedIn, user, showSignInDialog, signInOffset} = this.props.auth; + const {loggedIn, isAdmin, user, showSignInDialog, signInOffset} = this.props.auth; const {activeTab} = this.state; const banned = (this.props.userData.status === 'banned'); @@ -105,7 +105,7 @@ class CommentStream extends Component { Settings - Configure Stream + Configure Stream {loggedIn && } diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index d027dcd9e..464ae0083 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -20,15 +20,16 @@ export const cleanState = () => ({type: actions.CLEAN_STATE}); // Sign In Actions const signInRequest = () => ({type: actions.FETCH_SIGNIN_REQUEST}); -const signInSuccess = user => ({type: actions.FETCH_SIGNIN_SUCCESS, user}); +const signInSuccess = (user, isAdmin) => ({type: actions.FETCH_SIGNIN_SUCCESS, user, isAdmin}); const signInFailure = error => ({type: actions.FETCH_SIGNIN_FAILURE, error}); export const fetchSignIn = (formData) => dispatch => { dispatch(signInRequest()); coralApi('/auth/local', {method: 'POST', body: formData}) .then(({user}) => { + const isAdmin = !!user.roles.filter(i => i === 'admin').length; + dispatch(signInSuccess(user, isAdmin)); dispatch(hideSignInDialog()); - dispatch(signInSuccess(user)); dispatch(addItem(user, 'users')); }) .catch(() => dispatch(signInFailure(lang.t('error.emailPasswordError')))); @@ -117,7 +118,7 @@ export const invalidForm = error => ({type: actions.INVALID_FORM, error}); // Check Login const checkLoginRequest = () => ({type: actions.CHECK_LOGIN_REQUEST}); -const checkLoginSuccess = user => ({type: actions.CHECK_LOGIN_SUCCESS, user}); +const checkLoginSuccess = (user, isAdmin) => ({type: actions.CHECK_LOGIN_SUCCESS, user, isAdmin}); const checkLoginFailure = error => ({type: actions.CHECK_LOGIN_FAILURE, error}); export const checkLogin = () => dispatch => { @@ -125,10 +126,11 @@ export const checkLogin = () => dispatch => { coralApi('/auth') .then(user => { if (!user) { - throw new Error('not logged in'); + throw new Error('Not logged in'); } - dispatch(checkLoginSuccess(user)); + const isAdmin = !!user.roles.filter(i => i === 'admin').length; + dispatch(checkLoginSuccess(user, isAdmin)); }) .catch(error => dispatch(checkLoginFailure(error))); }; diff --git a/client/coral-framework/reducers/auth.js b/client/coral-framework/reducers/auth.js index cf5b25921..d32956b84 100644 --- a/client/coral-framework/reducers/auth.js +++ b/client/coral-framework/reducers/auth.js @@ -4,6 +4,7 @@ import * as actions from '../constants/auth'; const initialState = Map({ isLoading: false, loggedIn: false, + isAdmin: false, user: null, showSignInDialog: false, view: 'SIGNIN', @@ -50,10 +51,12 @@ export default function auth (state = initialState, action) { case actions.CHECK_LOGIN_SUCCESS: return state .set('loggedIn', true) + .set('isAdmin', action.isAdmin) .set('user', purge(action.user)); case actions.FETCH_SIGNIN_SUCCESS: return state .set('loggedIn', true) + .set('isAdmin', action.isAdmin) .set('user', purge(action.user)); case actions.FETCH_SIGNIN_FAILURE: return state @@ -80,9 +83,7 @@ export default function auth (state = initialState, action) { .set('isLoading', false) .set('successSignUp', true); case actions.LOGOUT_SUCCESS: - return state - .set('loggedIn', false) - .set('user', null); + return initialState; case actions.INVALID_FORM: return state .set('error', action.error); From 42ff3ae4a2e55b9b032804cdc0e61a1d628d85d9 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Sun, 11 Dec 2016 23:10:25 -0300 Subject: [PATCH 12/32] filter restricted tabs --- client/coral-ui/components/TabBar.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/client/coral-ui/components/TabBar.js b/client/coral-ui/components/TabBar.js index 1ab7f47a6..2a673f78c 100644 --- a/client/coral-ui/components/TabBar.js +++ b/client/coral-ui/components/TabBar.js @@ -18,14 +18,16 @@ export class TabBar extends React.Component { return (
            - {React.Children.map(children, (child, tabId) => - React.cloneElement(child, { - tabId, - active: tabId === activeTab, - onTabClick: this.handleClickTab, - cStyle - }) - )} + {React.Children.toArray(children) + .filter(child => !child.props.restricted) + .map((child, tabId) => + React.cloneElement(child, { + tabId, + active: tabId === activeTab, + onTabClick: this.handleClickTab, + cStyle + }) + )}
          ); From cb576dca8f8355043f43fd42f62966014c2257ec Mon Sep 17 00:00:00 2001 From: David Jay Date: Mon, 12 Dec 2016 15:52:34 -0500 Subject: [PATCH 13/32] Updating signup endpoint to reflect current api. --- client/coral-framework/actions/auth.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index d027dcd9e..e64755435 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -73,7 +73,7 @@ const signUpFailure = error => ({type: actions.FETCH_SIGNUP_FAILURE, error}); export const fetchSignUp = formData => dispatch => { dispatch(signUpRequest()); - coralApi('/user', {method: 'POST', body: formData}) + coralApi('/users', {method: 'POST', body: formData}) .then(({user}) => { dispatch(signUpSuccess(user)); setTimeout(() =>{ From 3b131ce315b3d74f81508a4161c7c1166e4995eb Mon Sep 17 00:00:00 2001 From: gaba Date: Mon, 12 Dec 2016 13:02:39 -0800 Subject: [PATCH 14/32] Removes status field for Asset. Now closedAt and closedMessage are in the settings field. --- client/coral-framework/actions/config.js | 21 ++++++----- models/asset.js | 44 +++--------------------- routes/api/asset/index.js | 18 ++++++---- routes/api/comments/index.js | 3 +- tests/routes/api/comments/index.js | 10 ++++-- 5 files changed, 36 insertions(+), 60 deletions(-) diff --git a/client/coral-framework/actions/config.js b/client/coral-framework/actions/config.js index f85e609eb..eb03d63ed 100644 --- a/client/coral-framework/actions/config.js +++ b/client/coral-framework/actions/config.js @@ -6,14 +6,6 @@ import I18n from 'coral-framework/modules/i18n/i18n'; import translations from './../translations'; const lang = new I18n(translations); -export const updateOpenStatus = status => (dispatch, getState) => { - const assetId = getState().items.get('assets') - .keySeq() - .toArray()[0]; - return coralApi(`/asset/${assetId}/status?status=${status}`, {method: 'PUT'}) - .then(() => dispatch({type: status === 'open' ? actions.OPEN_COMMENTS : actions.CLOSE_COMMENTS})); -}; - const updateConfigRequest = () => ({type: actions.UPDATE_CONFIG_REQUEST}); const updateConfigSuccess = config => ({type: actions.UPDATE_CONFIG_SUCCESS, config}); const updateConfigFailure = () => ({type: actions.UPDATE_CONFIG_FAILURE}); @@ -31,3 +23,16 @@ export const updateConfiguration = newConfig => (dispatch, getState) => { }) .catch(error => dispatch(updateConfigFailure(error))); }; + +const openStream = () => ({type: actions.OPEN_COMMENTS}); +const closeStream = () => ({type: actions.CLOSE_COMMENTS}); + +export const updateOpenStatus = status => dispatch => { + if (status === 'open') { + dispatch(openStream()); + dispatch(updateConfiguration({closedAt: null})); + } else { + dispatch(closeStream()); + dispatch(updateConfiguration({closedAt: Date.getTime()})); + } +}; diff --git a/models/asset.js b/models/asset.js index c1fc59dc3..09a9f49e8 100644 --- a/models/asset.js +++ b/models/asset.js @@ -5,12 +5,6 @@ const Setting = require('./setting'); const uuid = require('uuid'); -// ASSET_STATUSES is the list of statuses that are permitted for the asset status. -const ASSET_STATUS = [ - 'open', - 'closed' -]; - const AssetSchema = new Schema({ id: { type: String, @@ -35,15 +29,6 @@ const AssetSchema = new Schema({ type: Schema.Types.Mixed, default: null }, - status: { - type: String, - default: 'open' - }, - statusChangedAt: { - type: Date, - default: null - }, - statusClosedMessage: String, title: String, description: String, image: String, @@ -71,31 +56,6 @@ AssetSchema.index({ background: true }); -/** - * Returns true if the asset is closed, false else. - */ -AssetSchema.virtual('isClosed').get(function() { - return (this.status === 'closed') && this.statusChangedAt && this.statusChangedAt.getTime() <= new Date().getTime(); -}); - -/** - * Close or Open the asset. - */ -AssetSchema.statics.changeOpenStatus = (id, status, closedMessage = '') => { - // Check to see if the user role is in the allowable set of roles. - if (ASSET_STATUS.indexOf(status) === -1) { - // Asset status is not supported! Error out here. - return Promise.reject(new Error(`status ${status} is not supported`)); - } - return Asset.update({id}, { - $set: { - status: status, - statusChangedAt: new Date().getTime(), - statusClosedMessage: closedMessage - } - }); -}; - /** * Finds an asset by its id. * @param {String} id identifier of the asset (uuid). @@ -108,6 +68,10 @@ AssetSchema.statics.findById = (id) => Asset.findOne({id}); */ AssetSchema.statics.findByUrl = (url) => Asset.findOne({url}); +AssetSchema.virtual('isClosed').get(function() { + return this.settings && this.settings.closedAt && this.settings.closedAt <= new Date().getTime(); +}); + /** * Retrieves the settings given an asset query and rectifies it against the * global settings. diff --git a/routes/api/asset/index.js b/routes/api/asset/index.js index ebdcf038d..f61851f0c 100644 --- a/routes/api/asset/index.js +++ b/routes/api/asset/index.js @@ -89,12 +89,16 @@ router.put('/:asset_id/settings', (req, res, next) => { .catch((err) => next(err)); }); -router.put('/:asset_id/status', (req, res, next) => { - - Asset - .changeOpenStatus(req.params.asset_id, req.query.status, req.query.closedMessage) - .then(() => res.status(204).end()) - .catch((err) => next(err)); -}); +// router.put('/:asset_id/status', (req, res, next) => { +// Asset +// .findOneAndUpdate(req.params.asset_id, { +// $set: { +// closedAt: req.query.closedAt, +// closedMessage: req.query.closedMessage +// } +// }) +// .then(() => res.status(204).end()) +// .catch((err) => next(err)); +// }); module.exports = router; diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js index fad9083cc..bf741ed2d 100644 --- a/routes/api/comments/index.js +++ b/routes/api/comments/index.js @@ -87,9 +87,8 @@ router.post('/', wordlist.filter('body'), (req, res, next) => { // Check to see if the asset has closed commenting... if (asset.isClosed) { - // They have, ensure that we send back an error. - return Promise.reject(new Error(`asset has commenting closed because: ${asset.statusClosedMessage}`)); + return Promise.reject(new Error(`asset has commenting closed because: ${asset.settings.closedMessage}`)); } return asset; diff --git a/tests/routes/api/comments/index.js b/tests/routes/api/comments/index.js index 4787e4d58..0f6e8442e 100644 --- a/tests/routes/api/comments/index.js +++ b/tests/routes/api/comments/index.js @@ -160,6 +160,9 @@ describe('/api/v1/comments', () => { .then((res) => { expect(res).to.have.status(201); expect(res.body).to.have.property('id'); + }) + .catch((err) => { + expect(err).to.be.null; }); }); @@ -199,9 +202,10 @@ describe('/api/v1/comments', () => { it('shouldn\'t create a comment when the asset has expired commenting', () => { return Asset.create({ - status: 'closed', - statusChangedAt: new Date().setDate(0), - statusClosedMessage: 'tests said expired!' + settings: { + closedAt: new Date().setDate(0), + closedMessage: 'tests said expired!' + } }) .then((asset) => { return chai.request(app) From 39479fd9ad3c68766b649c0e5c9ebc219421863c Mon Sep 17 00:00:00 2001 From: David Jay Date: Mon, 12 Dec 2016 17:07:43 -0500 Subject: [PATCH 15/32] Add character count field. --- .../containers/Configure/CommentSettings.js | 32 +++++++++++++++++++ .../src/containers/Configure/Configure.css | 16 ++++++++++ 2 files changed, 48 insertions(+) diff --git a/client/coral-admin/src/containers/Configure/CommentSettings.js b/client/coral-admin/src/containers/Configure/CommentSettings.js index 194af720c..429893b34 100644 --- a/client/coral-admin/src/containers/Configure/CommentSettings.js +++ b/client/coral-admin/src/containers/Configure/CommentSettings.js @@ -11,6 +11,19 @@ import { Checkbox } from 'react-mdl'; +const updateCharCountEnable = (updateSettings, charCountChecked) => () => { + const charCountEnable = !charCountChecked; + updateSettings({charCountEnable}); +}; + +const updateCharCount = (updateSettings) => (event) => { + const charCount = event.target.value; + if (charCount.match(/[^0-9]/)) { + //Show error + } + updateSettings({charCount: charCount}); +}; + const updateModeration = (updateSettings, mod) => () => { const moderation = mod === 'pre' ? 'post' : 'pre'; updateSettings({moderation}); @@ -40,6 +53,25 @@ const CommentSettings = (props) => {lang.t('configure.enable-pre-moderation')} + + + + + + Limit Content Length +

          + Comments will be limited to + + characters. +

          +
          +
          Date: Mon, 12 Dec 2016 17:35:33 -0500 Subject: [PATCH 16/32] Updating settings box style. --- .../src/containers/Configure/CommentSettings.js | 15 ++++++++++----- .../src/containers/Configure/Configure.css | 15 ++++++++++++++- client/coral-admin/src/translations.json | 2 ++ 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/client/coral-admin/src/containers/Configure/CommentSettings.js b/client/coral-admin/src/containers/Configure/CommentSettings.js index 429893b34..53f769da4 100644 --- a/client/coral-admin/src/containers/Configure/CommentSettings.js +++ b/client/coral-admin/src/containers/Configure/CommentSettings.js @@ -45,22 +45,27 @@ const updateClosedMessage = (updateSettings) => (event) => { }; const CommentSettings = (props) => - + - {lang.t('configure.enable-pre-moderation')} + +
          {lang.t('configure.enable-pre-moderation')}
          +

          + {lang.t('configure.enable-pre-moderation-text')} +

          +
          - + - Limit Content Length +
          Limit Content Length

          Comments will be limited to

          - + Date: Mon, 12 Dec 2016 17:45:20 -0500 Subject: [PATCH 17/32] Moving text to translations file. --- .../src/containers/Configure/CommentSettings.js | 6 +++--- .../coral-admin/src/containers/Configure/Configure.css | 2 +- client/coral-admin/src/translations.json | 10 ++++++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/client/coral-admin/src/containers/Configure/CommentSettings.js b/client/coral-admin/src/containers/Configure/CommentSettings.js index 53f769da4..f33b7204f 100644 --- a/client/coral-admin/src/containers/Configure/CommentSettings.js +++ b/client/coral-admin/src/containers/Configure/CommentSettings.js @@ -65,15 +65,15 @@ const CommentSettings = (props) => checked={props.settings.charCountEnable} /> -
          Limit Content Length
          +
          {lang.t('configure.comment-count-header')}

          - Comments will be limited to + {lang.t('configure.comment-count-text-pre')} - characters. + {lang.t('configure.comment-count-text-post')}

          diff --git a/client/coral-admin/src/containers/Configure/Configure.css b/client/coral-admin/src/containers/Configure/Configure.css index f1519df56..ad811a351 100644 --- a/client/coral-admin/src/containers/Configure/Configure.css +++ b/client/coral-admin/src/containers/Configure/Configure.css @@ -112,7 +112,7 @@ } .disabledSetting { - padding-left: 23px; + padding-left: 22px; } .hidden { diff --git a/client/coral-admin/src/translations.json b/client/coral-admin/src/translations.json index 7672cdfce..743380621 100644 --- a/client/coral-admin/src/translations.json +++ b/client/coral-admin/src/translations.json @@ -56,7 +56,10 @@ "configure": "Configure", "community": "Community", "closed-comments-desc": "Write a message for closed threads", - "closed-comments-label": "Write a message..." + "closed-comments-label": "Write a message...", + "comment-count-header": "Limit Content Length", + "comment-count-text-pre": "Comments will be limited to ", + "comment-count-text-post": " characters." }, "bandialog": { "ban_user": "Ban User?", @@ -112,7 +115,10 @@ "configure": "Configurar", "community": "Comunidad", "closed-comments-desc": "Escribe un mensaje para cuando los comentarios se encuentran cerrados", - "closed-comments-label": "Escribe un mensaje..." + "closed-comments-label": "Escribe un mensaje...", + "comment-count-header": "tracundeme", + "comment-count-text-pre": "tracundeme", + "comment-count-text-post": "tracundeme" }, "bandialog": { "ban_user": "Quieres suspender el Usuario?", From 4a4b92299cd9cba8ab0fa111790d0346a5a6c5f9 Mon Sep 17 00:00:00 2001 From: David Jay Date: Mon, 12 Dec 2016 18:10:47 -0500 Subject: [PATCH 18/32] Adding error message. --- .../containers/Configure/CommentSettings.js | 216 ++++++++++-------- .../src/containers/Configure/Configure.css | 11 +- client/coral-admin/src/translations.json | 3 +- 3 files changed, 134 insertions(+), 96 deletions(-) diff --git a/client/coral-admin/src/containers/Configure/CommentSettings.js b/client/coral-admin/src/containers/Configure/CommentSettings.js index f33b7204f..9e162fc30 100644 --- a/client/coral-admin/src/containers/Configure/CommentSettings.js +++ b/client/coral-admin/src/containers/Configure/CommentSettings.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {Component, PropTypes} from 'react'; import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../../translations.json'; import styles from './Configure.css'; @@ -8,108 +8,136 @@ import { ListItemContent, ListItemAction, Textfield, - Checkbox + Checkbox, + Icon } from 'react-mdl'; -const updateCharCountEnable = (updateSettings, charCountChecked) => () => { - const charCountEnable = !charCountChecked; - updateSettings({charCountEnable}); -}; +class CommentSettings extends Component { -const updateCharCount = (updateSettings) => (event) => { - const charCount = event.target.value; - if (charCount.match(/[^0-9]/)) { - //Show error + state = { + charCountError: false } - updateSettings({charCount: charCount}); -}; -const updateModeration = (updateSettings, mod) => () => { - const moderation = mod === 'pre' ? 'post' : 'pre'; - updateSettings({moderation}); -}; + static propTypes = { + updateSettings: PropTypes.func.isRequired, + settings: PropTypes.object.isRequired + } -const updateInfoBoxEnable = (updateSettings, infoBox) => () => { - const infoBoxEnable = !infoBox; - updateSettings({infoBoxEnable}); -}; + updateCharCountEnable = (updateSettings, charCountChecked) => () => { + const charCountEnable = !charCountChecked; + updateSettings({charCountEnable}); + }; -const updateInfoBoxContent = (updateSettings) => (event) => { - const infoBoxContent = event.target.value; - updateSettings({infoBoxContent}); -}; + updateCharCount = (updateSettings) => (event) => { + const charCount = event.target.value; + if (charCount.match(/[^0-9]/)) { + this.setState({charCountError: true}); + } else { + this.setState({charCountError: false}); + } + updateSettings({charCount: charCount}); + }; -const updateClosedMessage = (updateSettings) => (event) => { - const closedMessage = event.target.value; - updateSettings({closedMessage}); -}; + updateModeration = (updateSettings, mod) => () => { + const moderation = mod === 'pre' ? 'post' : 'pre'; + updateSettings({moderation}); + }; -const CommentSettings = (props) => - - - - - -
          {lang.t('configure.enable-pre-moderation')}
          -

          - {lang.t('configure.enable-pre-moderation-text')} -

          -
          -
          - - - - - -
          {lang.t('configure.comment-count-header')}
          -

          - {lang.t('configure.comment-count-text-pre')} - - {lang.t('configure.comment-count-text-post')} -

          -
          -
          - - - - - - {lang.t('configure.include-comment-stream')} -

          - {lang.t('configure.include-comment-stream-desc')} -

          -
          -
          - - - - - - - - {lang.t('configure.closed-comments-desc')} - - - -
          ; + updateInfoBoxEnable = (updateSettings, infoBox) => () => { + const infoBoxEnable = !infoBox; + updateSettings({infoBoxEnable}); + }; + + updateInfoBoxContent = (updateSettings) => (event) => { + const infoBoxContent = event.target.value; + updateSettings({infoBoxContent}); + }; + + updateClosedMessage = (updateSettings) => (event) => { + const closedMessage = event.target.value; + updateSettings({closedMessage}); + }; + + render() { + + const {updateSettings, settings} = this.props; + + return + + + + + +
          {lang.t('configure.enable-pre-moderation')}
          +

          + {lang.t('configure.enable-pre-moderation-text')} +

          +
          +
          + + + + + +
          {lang.t('configure.comment-count-header')}
          +

          + {lang.t('configure.comment-count-text-pre')} + + {lang.t('configure.comment-count-text-post')} + { + this.state.charCountError && + +
          + + {lang.t('configure.comment-count-error')} +
          + } +

          +
          +
          + + + + + + {lang.t('configure.include-comment-stream')} +

          + {lang.t('configure.include-comment-stream-desc')} +

          +
          +
          + + + + + + + + {lang.t('configure.closed-comments-desc')} + + + +
          ; + } +} export default CommentSettings; diff --git a/client/coral-admin/src/containers/Configure/Configure.css b/client/coral-admin/src/containers/Configure/Configure.css index ad811a351..4788110de 100644 --- a/client/coral-admin/src/containers/Configure/Configure.css +++ b/client/coral-admin/src/containers/Configure/Configure.css @@ -18,11 +18,20 @@ .configSetting { border: 1px solid #ccc; border-radius: 4px; - height: 90px; + height: 95px; margin-bottom: 10px; align-items: flex-start; } +.settingsError { + color: #d50000; +} + +.settingsError i { + font-size: 14px; + margin-right: 3px; +} + .settingsHeader { margin-top: 3px; margin-bottom: 10px; diff --git a/client/coral-admin/src/translations.json b/client/coral-admin/src/translations.json index 743380621..1780bc133 100644 --- a/client/coral-admin/src/translations.json +++ b/client/coral-admin/src/translations.json @@ -59,7 +59,8 @@ "closed-comments-label": "Write a message...", "comment-count-header": "Limit Content Length", "comment-count-text-pre": "Comments will be limited to ", - "comment-count-text-post": " characters." + "comment-count-text-post": " characters.", + "comment-count-error": "Please enter a valid number." }, "bandialog": { "ban_user": "Ban User?", From ba8a845741090ec7ed6efcfa61799fe7acac74e0 Mon Sep 17 00:00:00 2001 From: David Jay Date: Mon, 12 Dec 2016 18:14:03 -0500 Subject: [PATCH 19/32] Making character input field green to match designs. --- .../coral-admin/src/containers/Configure/CommentSettings.js | 2 +- client/coral-admin/src/containers/Configure/Configure.css | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/client/coral-admin/src/containers/Configure/CommentSettings.js b/client/coral-admin/src/containers/Configure/CommentSettings.js index 9e162fc30..b3ab8d62a 100644 --- a/client/coral-admin/src/containers/Configure/CommentSettings.js +++ b/client/coral-admin/src/containers/Configure/CommentSettings.js @@ -87,7 +87,7 @@ class CommentSettings extends Component {

          {lang.t('configure.comment-count-text-pre')} diff --git a/client/coral-admin/src/containers/Configure/Configure.css b/client/coral-admin/src/containers/Configure/Configure.css index 4788110de..667bb4744 100644 --- a/client/coral-admin/src/containers/Configure/Configure.css +++ b/client/coral-admin/src/containers/Configure/Configure.css @@ -71,6 +71,10 @@ border-width: 0px 0px 1px 0px; } +.charCountTexfieldEnabled { + border-color: #4caf50; +} + .charCountTexfield:focus { outline: none; } From 0648b2b1c81ff0a434ec89e6c8c2fa22af570554 Mon Sep 17 00:00:00 2001 From: gaba Date: Mon, 12 Dec 2016 15:38:28 -0800 Subject: [PATCH 20/32] More tests. --- client/coral-framework/actions/config.js | 18 ++++++++++++-- models/asset.js | 10 +++++++- routes/api/asset/index.js | 27 ++++++++++++--------- routes/api/comments/index.js | 2 +- tests/routes/api/assets/index.js | 31 +++++++++++++++++++++++- tests/routes/api/comments/index.js | 6 ++--- 6 files changed, 74 insertions(+), 20 deletions(-) diff --git a/client/coral-framework/actions/config.js b/client/coral-framework/actions/config.js index eb03d63ed..7850577dc 100644 --- a/client/coral-framework/actions/config.js +++ b/client/coral-framework/actions/config.js @@ -24,15 +24,29 @@ export const updateConfiguration = newConfig => (dispatch, getState) => { .catch(error => dispatch(updateConfigFailure(error))); }; +export const updateOpenStream = newConfig => (dispatch, getState) => { + const assetId = getState().items.get('assets') + .keySeq() + .toArray()[0]; + + dispatch(updateConfigRequest()); + coralApi(`/asset/${assetId}/status`, {method: 'PUT', body: newConfig}) + .then(() => { + dispatch(addNotification('success', lang.t('successUpdateSettings'))); + dispatch(updateConfigSuccess(newConfig)); + }) + .catch(error => dispatch(updateConfigFailure(error))); +}; + const openStream = () => ({type: actions.OPEN_COMMENTS}); const closeStream = () => ({type: actions.CLOSE_COMMENTS}); export const updateOpenStatus = status => dispatch => { if (status === 'open') { dispatch(openStream()); - dispatch(updateConfiguration({closedAt: null})); + dispatch(updateOpenStream({closedAt: null})); } else { dispatch(closeStream()); - dispatch(updateConfiguration({closedAt: Date.getTime()})); + dispatch(updateOpenStream({closedAt: Date.now(), closedMessage: ''})); } }; diff --git a/models/asset.js b/models/asset.js index 09a9f49e8..ed1bf98d0 100644 --- a/models/asset.js +++ b/models/asset.js @@ -29,6 +29,14 @@ const AssetSchema = new Schema({ type: Schema.Types.Mixed, default: null }, + closedAt: { + type: Date, + default: null + }, + closedMessage: { + type: String, + default: null + }, title: String, description: String, image: String, @@ -69,7 +77,7 @@ AssetSchema.statics.findById = (id) => Asset.findOne({id}); AssetSchema.statics.findByUrl = (url) => Asset.findOne({url}); AssetSchema.virtual('isClosed').get(function() { - return this.settings && this.settings.closedAt && this.settings.closedAt <= new Date().getTime(); + return this.closedAt && this.closedAt.getTime() <= new Date().getTime(); }); /** diff --git a/routes/api/asset/index.js b/routes/api/asset/index.js index f61851f0c..6723d3671 100644 --- a/routes/api/asset/index.js +++ b/routes/api/asset/index.js @@ -89,16 +89,21 @@ router.put('/:asset_id/settings', (req, res, next) => { .catch((err) => next(err)); }); -// router.put('/:asset_id/status', (req, res, next) => { -// Asset -// .findOneAndUpdate(req.params.asset_id, { -// $set: { -// closedAt: req.query.closedAt, -// closedMessage: req.query.closedMessage -// } -// }) -// .then(() => res.status(204).end()) -// .catch((err) => next(err)); -// }); +router.put('/:asset_id/status', (req, res, next) => { + const id = req.params.asset_id; + const { + closedAt = null, + closedMessage = null + } = req.query; + Asset + .update(id, { + closedAt: closedAt, + closedMessage: closedMessage + }) + .then((asset) => { + res.status(201).json(asset); + }) + .catch((err) => next(err)); +}); module.exports = router; diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js index bf741ed2d..b54717c78 100644 --- a/routes/api/comments/index.js +++ b/routes/api/comments/index.js @@ -88,7 +88,7 @@ router.post('/', wordlist.filter('body'), (req, res, next) => { // Check to see if the asset has closed commenting... if (asset.isClosed) { // They have, ensure that we send back an error. - return Promise.reject(new Error(`asset has commenting closed because: ${asset.settings.closedMessage}`)); + return Promise.reject(new Error(`asset has commenting closed because: ${asset.closedMessage}`)); } return asset; diff --git a/tests/routes/api/assets/index.js b/tests/routes/api/assets/index.js index c56e5b1ba..817f5b181 100644 --- a/tests/routes/api/assets/index.js +++ b/tests/routes/api/assets/index.js @@ -17,7 +17,8 @@ describe('/api/v1/assets', () => { { url: 'https://coralproject.net/news/asset1', title: 'Asset 1', - description: 'term1' + description: 'term1', + id: '1' }, { url: 'https://coralproject.net/news/asset2', @@ -82,4 +83,32 @@ describe('/api/v1/assets', () => { }); + describe('#put', () => { + it('should close the asset', function() { + + const asset = Asset.findByUrl('http://test.com') + .then((asset) => { + expect(asset).to.have.property('closedAt') + .and.to.equal(null); + }); + + const today = Date.now(); + return chai.request(app) + .put(`/api/v1/asset/${asset.id}/status`) + .set(passport.inject({roles: ['admin']})) + .send({closedAt: today}) + .then((res) => { + expect(res).to.have.status(201); + + Asset.findByUrl('http://test.com') + .then((asset) => { + expect(asset).to.have.property('isClosed') + .and.to.equal(true); + expect(asset).to.have.property('closedAt') + .and.to.not.equal(null); + }); + }); + }); + }); + }); diff --git a/tests/routes/api/comments/index.js b/tests/routes/api/comments/index.js index 0f6e8442e..59d840e1b 100644 --- a/tests/routes/api/comments/index.js +++ b/tests/routes/api/comments/index.js @@ -202,10 +202,8 @@ describe('/api/v1/comments', () => { it('shouldn\'t create a comment when the asset has expired commenting', () => { return Asset.create({ - settings: { - closedAt: new Date().setDate(0), - closedMessage: 'tests said expired!' - } + closedAt: new Date().setDate(0), + closedMessage: 'tests said expired!' }) .then((asset) => { return chai.request(app) From cd63f49a5e56706068d6c84bccb8585c4ffb8bb6 Mon Sep 17 00:00:00 2001 From: David Jay Date: Mon, 12 Dec 2016 19:08:35 -0500 Subject: [PATCH 21/32] Disabling save button on error and updating translations. --- client/coral-admin/src/actions/settings.js | 5 +- .../containers/Configure/CommentSettings.js | 217 ++++++++---------- .../src/containers/Configure/Configure.js | 21 +- client/coral-admin/src/translations.json | 9 +- models/setting.js | 10 +- 5 files changed, 136 insertions(+), 126 deletions(-) diff --git a/client/coral-admin/src/actions/settings.js b/client/coral-admin/src/actions/settings.js index 71106e1f7..1dfda51b9 100644 --- a/client/coral-admin/src/actions/settings.js +++ b/client/coral-admin/src/actions/settings.js @@ -26,7 +26,10 @@ export const updateSettings = settings => { }; export const saveSettingsToServer = () => (dispatch, getState) => { - const settings = getState().settings.toJS().settings; + let settings = getState().settings.toJS().settings; + if (settings.charCount) { + settings.charCount = parseInt(settings.charCount); + } dispatch({type: SAVE_SETTINGS_LOADING}); coralApi('/settings', {method: 'PUT', body: settings}) .then(() => { diff --git a/client/coral-admin/src/containers/Configure/CommentSettings.js b/client/coral-admin/src/containers/Configure/CommentSettings.js index b3ab8d62a..6589e115e 100644 --- a/client/coral-admin/src/containers/Configure/CommentSettings.js +++ b/client/coral-admin/src/containers/Configure/CommentSettings.js @@ -12,132 +12,115 @@ import { Icon } from 'react-mdl'; -class CommentSettings extends Component { +const updateCharCountEnable = (updateSettings, charCountChecked) => () => { + const charCountEnable = !charCountChecked; + updateSettings({charCountEnable}); +}; - state = { - charCountError: false +const updateCharCount = (updateSettings, settingsError) => (event) => { + const charCount = event.target.value; + if (charCount.match(/[^0-9]/)) { + settingsError('charCount', true); + } else { + settingsError('charCount', false); } + updateSettings({charCount: charCount}); +}; - static propTypes = { - updateSettings: PropTypes.func.isRequired, - settings: PropTypes.object.isRequired - } +const updateModeration = (updateSettings, mod) => () => { + const moderation = mod === 'pre' ? 'post' : 'pre'; + updateSettings({moderation}); +}; - updateCharCountEnable = (updateSettings, charCountChecked) => () => { - const charCountEnable = !charCountChecked; - updateSettings({charCountEnable}); - }; +const updateInfoBoxEnable = (updateSettings, infoBox) => () => { + const infoBoxEnable = !infoBox; + updateSettings({infoBoxEnable}); +}; - updateCharCount = (updateSettings) => (event) => { - const charCount = event.target.value; - if (charCount.match(/[^0-9]/)) { - this.setState({charCountError: true}); - } else { - this.setState({charCountError: false}); - } - updateSettings({charCount: charCount}); - }; +const updateInfoBoxContent = (updateSettings) => (event) => { + const infoBoxContent = event.target.value; + updateSettings({infoBoxContent}); +}; - updateModeration = (updateSettings, mod) => () => { - const moderation = mod === 'pre' ? 'post' : 'pre'; - updateSettings({moderation}); - }; +const updateClosedMessage = (updateSettings) => (event) => { + const closedMessage = event.target.value; + updateSettings({closedMessage}); +}; - updateInfoBoxEnable = (updateSettings, infoBox) => () => { - const infoBoxEnable = !infoBox; - updateSettings({infoBoxEnable}); - }; - - updateInfoBoxContent = (updateSettings) => (event) => { - const infoBoxContent = event.target.value; - updateSettings({infoBoxContent}); - }; - - updateClosedMessage = (updateSettings) => (event) => { - const closedMessage = event.target.value; - updateSettings({closedMessage}); - }; - - render() { - - const {updateSettings, settings} = this.props; - - return - - - - - -

          {lang.t('configure.enable-pre-moderation')}
          -

          - {lang.t('configure.enable-pre-moderation-text')} +const CommentSettings = ({updateSettings, settingsError, settings, errors}) => + + + + + +

          {lang.t('configure.enable-pre-moderation')}
          +

          + {lang.t('configure.enable-pre-moderation-text')} +

          + +
          + + + + + +
          {lang.t('configure.comment-count-header')}
          +

          + {lang.t('configure.comment-count-text-pre')} + + {lang.t('configure.comment-count-text-post')} + { + errors.charCount && + +
          + + {lang.t('configure.comment-count-error')} +
          + }

          -
          - - - - - -
          {lang.t('configure.comment-count-header')}
          -

          - {lang.t('configure.comment-count-text-pre')} - - {lang.t('configure.comment-count-text-post')} - { - this.state.charCountError && - -
          - - {lang.t('configure.comment-count-error')} -
          - } -

          -
          -
          - - - - - - {lang.t('configure.include-comment-stream')} -

          - {lang.t('configure.include-comment-stream-desc')} -

          -
          -
          - - - - - - - - {lang.t('configure.closed-comments-desc')} - - - -
          ; - } -} +
          + + + + + + {lang.t('configure.include-comment-stream')} +

          + {lang.t('configure.include-comment-stream-desc')} +

          +
          +
          + + + + + + + + {lang.t('configure.closed-comments-desc')} + + + +
          ; export default CommentSettings; diff --git a/client/coral-admin/src/containers/Configure/Configure.js b/client/coral-admin/src/containers/Configure/Configure.js index db3bf6f20..8dd106cd0 100644 --- a/client/coral-admin/src/containers/Configure/Configure.js +++ b/client/coral-admin/src/containers/Configure/Configure.js @@ -22,7 +22,8 @@ class Configure extends React.Component { this.state = { activeSection: 'comments', wordlist: [], - changed: false + changed: false, + errors: {} }; } @@ -64,12 +65,23 @@ class Configure extends React.Component { this.props.dispatch(updateSettings(setting)); } + // Sets an arbitrary error string and a boolean state. + // This allows the system to track multiple errors. + onSettingError = (error, state) => { + this.setState((prevState) => { + prevState.errors[error] = state; + return prevState; + }); + } + getSection = (section) => { switch(section){ case 'comments': return ; + updateSettings={this.onSettingUpdate} + errors={this.state.errors} + settingsError={this.onSettingError}/>; case 'embed': return ; case 'wordlist': @@ -94,6 +106,9 @@ class Configure extends React.Component { let pageTitle = this.getPageTitle(this.state.activeSection); const section = this.getSection(this.state.activeSection); + const showSave = Object.keys(this.state.errors).reduce( + (bool, error) => this.state.errors[error] ? false : bool, this.state.changed); + if (this.props.fetchingSettings) { pageTitle += ' - Loading...'; } @@ -119,7 +134,7 @@ class Configure extends React.Component { { - this.state.changed ? + showSave ?