From 7d5b79c6a03414d172f238d3e8fc790396a2e016 Mon Sep 17 00:00:00 2001 From: gaba Date: Thu, 10 Nov 2016 14:59:56 -0800 Subject: [PATCH 1/6] Moved the moderation queue to its own folder and change the route to /queue/comments/pending --- routes/api/comments/index.js | 36 +++++----- routes/api/index.js | 1 + routes/api/queue/index.js | 30 +++++++++ tests/routes/api/comments/index.js | 25 ++----- tests/routes/api/queue/index.js | 105 +++++++++++++++++++++++++++++ transfer.py | 49 ++++++++++++++ 6 files changed, 206 insertions(+), 40 deletions(-) create mode 100644 routes/api/queue/index.js create mode 100644 tests/routes/api/queue/index.js create mode 100644 transfer.py diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js index a2e77b417..ad8e0654d 100644 --- a/routes/api/comments/index.js +++ b/routes/api/comments/index.js @@ -1,8 +1,6 @@ const express = require('express'); const Comment = require('../../../models/comment'); -const Setting = require('../../../models/setting'); - const router = express.Router(); //============================================================================== @@ -25,11 +23,7 @@ router.get('/:comment_id', (req, res, next) => { }); }); -//============================================================================== -// Moderation Queues Routes -//============================================================================== - -// Get all the comments that have that action_type over them. +// Get all the comments that have an 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); @@ -47,19 +41,19 @@ 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) => { - 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); - }); +// Get all the comments that were accepted. +router.get('/status/accepted', (req, res, next) => { + Comment.findByStatus('accepted').then((comments) => { + res.status(200).json(comments); + }).catch(error => { + next(error); + }); +}); + +// Get all the not moderated comments. +router.get('/status/new', (req, res, next) => { + Comment.findByStatus('').then((comments) => { + res.status(200).json(comments); }).catch(error => { next(error); }); @@ -94,7 +88,7 @@ router.post('/:comment_id', (req, res, next) => { }); router.post('/:comment_id/status', (req, res, next) => { - + Comment .changeStatus(req.params.comment_id, req.body.status) .then(comment => res.status(200).send(comment)) diff --git a/routes/api/index.js b/routes/api/index.js index d161cf622..acd007dae 100644 --- a/routes/api/index.js +++ b/routes/api/index.js @@ -4,6 +4,7 @@ const router = express.Router(); router.use('/asset', require('./asset')); router.use('/comments', require('./comments')); +router.use('/queue', require('./queue')); router.use('/settings', require('./settings')); router.use('/stream', require('./stream')); diff --git a/routes/api/queue/index.js b/routes/api/queue/index.js new file mode 100644 index 000000000..4c87d6593 --- /dev/null +++ b/routes/api/queue/index.js @@ -0,0 +1,30 @@ +const express = require('express'); +const Comment = require('../../../models/comment'); + +const Setting = require('../../../models/setting'); + +const router = express.Router(); + +//============================================================================== +// Get Routes +//============================================================================== + +// 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('/comments/pending', (req, res, next) => { + 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); + }); +}); + +module.exports = router; diff --git a/tests/routes/api/comments/index.js b/tests/routes/api/comments/index.js index e635e988c..cde4b9efb 100644 --- a/tests/routes/api/comments/index.js +++ b/tests/routes/api/comments/index.js @@ -75,7 +75,7 @@ describe('Get /comments', () => { }); }); -describe('Get moderation queues rejected, pending, flags', () => { +describe('Get comments by status and action', () => { const comments = [{ id: 'abc', body: 'comment 10', @@ -133,21 +133,20 @@ describe('Get moderation queues rejected, pending, flags', () => { }); }); - it('should return all the pending comments', function(done){ + it('should return all the approved comments', function(done){ chai.request(app) - .get('/api/v1/comments/status/pending') + .get('/api/v1/comments/status/accepted') .end(function(err, res){ expect(err).to.be.null; expect(res).to.have.status(200); - expect(res.body[0]).to.have.property('id', 'def'); + expect(res.body[0]).to.have.property('id', 'hij'); done(); }); }); - it('should return all the pending comments as pre moderated', function(done){ + it('should return all the new comments', function(done){ chai.request(app) - .get('/api/v1/comments/status/pending') - .query({'moderation': 'pre'}) + .get('/api/v1/comments/status/new') .end(function(err, res){ expect(err).to.be.null; expect(res).to.have.status(200); @@ -156,18 +155,6 @@ describe('Get moderation queues rejected, pending, flags', () => { }); }); - 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') diff --git a/tests/routes/api/queue/index.js b/tests/routes/api/queue/index.js new file mode 100644 index 000000000..e339926cd --- /dev/null +++ b/tests/routes/api/queue/index.js @@ -0,0 +1,105 @@ +process.env.NODE_ENV = 'test'; + +require('../../../utils/mongoose'); + +const app = require('../../../../app'); +const chai = require('chai'); +const expect = chai.expect; + +// Setup chai. +chai.should(); +chai.use(require('chai-http')); + +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 moderation queues rejected, pending, flags', () => { + const comments = [{ + id: 'abc', + body: 'comment 10', + asset_id: 'asset', + author_id: '123', + status: 'rejected' + }, { + id: 'def', + body: 'comment 20', + asset_id: 'asset', + author_id: '456' + }, { + id: 'hij', + body: 'comment 30', + asset_id: '456', + status: 'accepted' + }]; + + const users = [{ + displayName: 'Ana', + email: 'ana@gmail.com', + password: '123' + }, { + displayName: 'Maria', + email: 'maria@gmail.com', + password: '123' + }]; + + const actions = [{ + action_type: 'flag', + item_id: 'abc', + item_type: 'comment' + }, { + action_type: 'like', + item_id: 'hij', + item_type: 'comment' + }]; + + beforeEach(() => { + return Promise.all([ + Comment.create(comments), + User.createLocalUsers(users), + Action.create(actions) + ]); + }); + + it('should return all the pending comments', function(done){ + chai.request(app) + .get('/api/v1/queue/comments/pending') + .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 pre moderated', function(done){ + chai.request(app) + .get('/api/v1/queue/comments/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/queue/comments/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(); + }); + }); +}); diff --git a/transfer.py b/transfer.py new file mode 100644 index 000000000..97a73a429 --- /dev/null +++ b/transfer.py @@ -0,0 +1,49 @@ +from pymongo import MongoClient +import requests + +url = 'http://localhost:3000/api/v1/' +url_asset = '%sasset' % url +url_comment = '%scomments' % url + +def main(): + client = MongoClient('localhost', 27017) + db_wapo = client.original_wapo + wcomments = db_wapo.comments + + for comment in wcomments.find(): + ncomment = {} + nasset = {} + status = comment['object']['status'] + if (status == 'Untouched'): + ncomment['status'] = '' + elif (status == 'CommunityFlagged'): + ncomment['status'] = '' + elif (status == 'ModeratorApproved'): + ncomment['status'] = 'accepted' + elif (status == 'ModeratorDeleted'): + ncomment['status'] = 'rejected' + ncomment['body'] = comment['object']['content'] + nasset['url'] = comment['targets'][0]['conversationID'] + ncomment['asset_id'] = post_asset(nasset) + ncomment['author_id'] = '1' + post_comment(ncomment) + +def post_asset(nasset): + print 'Posting %s into %s.' % (nasset, url_asset) + try: + r = requests.put(url_asset, json=nasset) + if 'id' in r.json(): + return r.json()['id'] + else: + ra = requests.get(url_asset, { url: nasset['url']}) + return ra.json()['id'] + except: + print('Error when getting asset.') + return 'asset' + +def post_comment(ncomment): + print 'Posting %s into %s.' % (ncomment, url_comment) + r = requests.post(url_comment, json=ncomment) + +if __name__ == '__main__': + main() From ad01bc813f309df256a6b2a5218b18e018b416a6 Mon Sep 17 00:00:00 2001 From: gaba Date: Fri, 11 Nov 2016 08:38:51 -0800 Subject: [PATCH 2/6] Removes script and change according to suggestion. --- routes/api/queue/index.js | 2 +- transfer.py | 49 --------------------------------------- 2 files changed, 1 insertion(+), 50 deletions(-) delete mode 100644 transfer.py diff --git a/routes/api/queue/index.js b/routes/api/queue/index.js index 4c87d6593..82147853f 100644 --- a/routes/api/queue/index.js +++ b/routes/api/queue/index.js @@ -16,7 +16,7 @@ const router = express.Router(); router.get('/comments/pending', (req, res, next) => { Setting.getModerationSetting().then(function({moderation}){ let moderationValue = req.query.moderation; - if (typeof moderationValue === 'undefined' || moderationValue === undefined) { + if (typeof moderationValue === 'undefined' || !moderationValue) { moderationValue = moderation; } Comment.moderationQueue(moderationValue).then((comments) => { diff --git a/transfer.py b/transfer.py deleted file mode 100644 index 97a73a429..000000000 --- a/transfer.py +++ /dev/null @@ -1,49 +0,0 @@ -from pymongo import MongoClient -import requests - -url = 'http://localhost:3000/api/v1/' -url_asset = '%sasset' % url -url_comment = '%scomments' % url - -def main(): - client = MongoClient('localhost', 27017) - db_wapo = client.original_wapo - wcomments = db_wapo.comments - - for comment in wcomments.find(): - ncomment = {} - nasset = {} - status = comment['object']['status'] - if (status == 'Untouched'): - ncomment['status'] = '' - elif (status == 'CommunityFlagged'): - ncomment['status'] = '' - elif (status == 'ModeratorApproved'): - ncomment['status'] = 'accepted' - elif (status == 'ModeratorDeleted'): - ncomment['status'] = 'rejected' - ncomment['body'] = comment['object']['content'] - nasset['url'] = comment['targets'][0]['conversationID'] - ncomment['asset_id'] = post_asset(nasset) - ncomment['author_id'] = '1' - post_comment(ncomment) - -def post_asset(nasset): - print 'Posting %s into %s.' % (nasset, url_asset) - try: - r = requests.put(url_asset, json=nasset) - if 'id' in r.json(): - return r.json()['id'] - else: - ra = requests.get(url_asset, { url: nasset['url']}) - return ra.json()['id'] - except: - print('Error when getting asset.') - return 'asset' - -def post_comment(ncomment): - print 'Posting %s into %s.' % (ncomment, url_comment) - r = requests.post(url_comment, json=ncomment) - -if __name__ == '__main__': - main() From e2735759a6f38607ae249d45bf43f13a9db72df9 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Fri, 11 Nov 2016 11:27:39 -0700 Subject: [PATCH 3/6] first pass at permalink box --- .../coral-embed-stream/src/CommentStream.js | 8 ++ .../PermalinkButton.js | 90 +++++++++++++++++++ package.json | 1 + 3 files changed, 99 insertions(+) create mode 100644 client/coral-plugin-permalinks/PermalinkButton.js diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index 28b70841f..32db3b5d6 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -14,6 +14,7 @@ import AuthorName from '../../coral-plugin-author-name/AuthorName'; import {ReplyBox, ReplyButton} from '../../coral-plugin-replies'; import Pym from 'pym.js'; import FlagButton from '../../coral-plugin-flags/FlagButton'; +import PermalinkButton from '../../coral-plugin-permalinks/PermalinkButton'; const {addItem, updateItem, postItem, getStream, postAction, appendItemArray} = itemActions; const {addNotification, clearNotification} = notificationActions; @@ -130,6 +131,9 @@ class CommentStream extends Component { + + ; }) diff --git a/client/coral-plugin-permalinks/PermalinkButton.js b/client/coral-plugin-permalinks/PermalinkButton.js new file mode 100644 index 000000000..85629452b --- /dev/null +++ b/client/coral-plugin-permalinks/PermalinkButton.js @@ -0,0 +1,90 @@ +import React, {PropTypes} from 'react'; +import onClickOutside from 'react-onclickoutside'; +const name = 'coral-plugin-permalinks'; + +class PermalinkButton extends React.Component { + + static propTypes = { + asset_id: PropTypes.string.isRequired, + comment_id: PropTypes.string.isRequired + } + + constructor (props) { + super(props); + this.state = {popoverOpen: false, copySuccessful: null, copyFailure: null}; + this.toggle = this.toggle.bind(this); + this.copyPermalink = this.copyPermalink.bind(this); + } + + toggle () { + this.setState({popoverOpen: !this.state.popoverOpen}); + } + + handleClickOutside () { + this.setState({popoverOpen: false}); + } + + copyPermalink () { + this.permalinkInput.select(); + try { + document.execCommand('copy'); + this.setState({copySuccessful: true}); + } catch (err) { + this.setState({copyFailure: true}); + } + + setTimeout(() => { + this.setState({copyFailure: null, copySuccessful: null}); + }, 4500); + } + + render () { + const publisherUrl = 'http://nytimes.com/'; + + return ( +
+ +
+ this.permalinkInput = input} + value={`${publisherUrl}${this.props.asset_id}#${this.props.comment_id}`} + onChange={() => {}} /> + + { + this.state.copySuccessful ?

copied to clipboard

: null + } + { + this.state.copyFailure + ?

copying to clipboard not supported in this browser. Use Cmd + C.

+ : null + } +
+
+ ); + } +} + +export default onClickOutside(PermalinkButton); + +const styles = { + position: 'relative', + + popover: active => { + return { + display: active ? 'block' : 'none', + backgroundColor: 'white', + border: '1px solid black', + minWidth: 400, + position: 'absolute', + top: 30, + right: 0, + padding: 5 + }; + } +}; diff --git a/package.json b/package.json index 207724d61..cbdaad174 100644 --- a/package.json +++ b/package.json @@ -103,6 +103,7 @@ "react": "15.3.2", "react-dom": "15.3.2", "react-mdl": "^1.7.2", + "react-onclickoutside": "^5.7.1", "react-redux": "^4.4.5", "react-router": "^3.0.0", "redux": "^3.6.0", From 4b6bf58d43d9eea8148e0b96210e9134d1ee2614 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Fri, 11 Nov 2016 13:17:18 -0700 Subject: [PATCH 4/6] now with translations and page hostname --- client/coral-plugin-permalinks/PermalinkButton.js | 8 ++++++-- client/coral-plugin-permalinks/translations.js | 12 ++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 client/coral-plugin-permalinks/translations.js diff --git a/client/coral-plugin-permalinks/PermalinkButton.js b/client/coral-plugin-permalinks/PermalinkButton.js index 85629452b..978a6dd03 100644 --- a/client/coral-plugin-permalinks/PermalinkButton.js +++ b/client/coral-plugin-permalinks/PermalinkButton.js @@ -1,7 +1,11 @@ import React, {PropTypes} from 'react'; +import I18n from 'coral-framework/i18n/i18n'; +import translations from './translations'; import onClickOutside from 'react-onclickoutside'; const name = 'coral-plugin-permalinks'; +const lang = new I18n(translations); + class PermalinkButton extends React.Component { static propTypes = { @@ -39,13 +43,13 @@ class PermalinkButton extends React.Component { } render () { - const publisherUrl = 'http://nytimes.com/'; + const publisherUrl = `${location.protocol}//${location.host}/`; return (
Date: Fri, 11 Nov 2016 13:25:46 -0700 Subject: [PATCH 5/6] rename translations file to json --- client/coral-plugin-permalinks/translations.js | 12 ------------ client/coral-plugin-permalinks/translations.json | 12 ++++++++++++ 2 files changed, 12 insertions(+), 12 deletions(-) delete mode 100644 client/coral-plugin-permalinks/translations.js create mode 100644 client/coral-plugin-permalinks/translations.json diff --git a/client/coral-plugin-permalinks/translations.js b/client/coral-plugin-permalinks/translations.js deleted file mode 100644 index 1ded7deee..000000000 --- a/client/coral-plugin-permalinks/translations.js +++ /dev/null @@ -1,12 +0,0 @@ -export default { - en: { - permalink: { - permalink: 'Permalink' - } - }, - es: { - permalink: { - permalink: 'Enlace permanente' - } - } -}; diff --git a/client/coral-plugin-permalinks/translations.json b/client/coral-plugin-permalinks/translations.json new file mode 100644 index 000000000..af5c19b0a --- /dev/null +++ b/client/coral-plugin-permalinks/translations.json @@ -0,0 +1,12 @@ +{ + "en": { + "permalink": { + "permalink": "Permalink" + } + }, + "es": { + "permalink": { + "permalink": "Enlace permanente" + } + } +} From c6198b21bc4534636085ba7030b5bc685cd453e0 Mon Sep 17 00:00:00 2001 From: gaba Date: Fri, 11 Nov 2016 12:59:24 -0800 Subject: [PATCH 6/6] It changes the endpoint for the moderation queue. --- client/coral-admin/src/services/talk-adapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-admin/src/services/talk-adapter.js b/client/coral-admin/src/services/talk-adapter.js index 9f4dd4715..1457601ed 100644 --- a/client/coral-admin/src/services/talk-adapter.js +++ b/client/coral-admin/src/services/talk-adapter.js @@ -34,7 +34,7 @@ export default store => next => action => { // Get comments to fill each of the three lists on the mod queue const fetchModerationQueueComments = store => -Promise.all([fetch('/api/v1/comments/status/pending'), fetch('/api/v1/comments/status/rejected'), fetch('/api/v1/comments/action/flag')]) +Promise.all([fetch('/api/v1/queue/comments/pending'), fetch('/api/v1/comments/status/rejected'), fetch('/api/v1/comments/action/flag')]) .then(res => Promise.all(res.map(r => r.json()))) .then(res => { res[2] = res[2].map(comment => { comment.flagged = true; return comment; });