mirror of
https://github.com/wassname/talk.git
synced 2026-07-03 12:54:43 +08:00
addCommentTag mutation works
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"extends": "eslint:recommended",
|
||||
"rules": {
|
||||
"indent": ["error",
|
||||
|
||||
@@ -112,11 +112,11 @@ class Comment extends React.Component {
|
||||
|
||||
// @TODO(bengo) Would be best to only create these funcs on prop change
|
||||
const addBestTag = () => addCommentTag({
|
||||
comment_id: comment.id,
|
||||
id: comment.id,
|
||||
tag: BEST_TAG,
|
||||
});
|
||||
const removeBestTag = () => removeCommentTag({
|
||||
comment_id: comment.id,
|
||||
id: comment.id,
|
||||
tag: BEST_TAG,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
mutation AddCommentTag ($comment_id: ID!, $tag: String!) {
|
||||
addCommentTag(comment_id:$comment_id, tag:$tag) {
|
||||
comment {
|
||||
tags {
|
||||
name
|
||||
}
|
||||
}
|
||||
mutation AddCommentTag ($id: ID!, $tag: String!) {
|
||||
addCommentTag(id:$id, tag:$tag) {
|
||||
comment {
|
||||
id
|
||||
tags {
|
||||
name
|
||||
}
|
||||
}
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
|
||||
@@ -127,9 +127,10 @@ export const deleteAction = graphql(DELETE_ACTION, {
|
||||
|
||||
export const addCommentTag = graphql(ADD_COMMENT_TAG, {
|
||||
props: ({mutate}) => ({
|
||||
addCommentTag: (tag) => {
|
||||
addCommentTag: ({id, tag}) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
id,
|
||||
tag
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,7 +5,10 @@ import classnames from 'classnames';
|
||||
|
||||
// tag string for best comments
|
||||
export const BEST_TAG = 'BEST';
|
||||
export const commentIsBest = ({tags} = {}) => Array.isArray(tags) && tags.some(t => t === BEST_TAG);
|
||||
export const commentIsBest = ({tags} = {}) => {
|
||||
const isBest = Array.isArray(tags) && tags.some(t => t.name === BEST_TAG);
|
||||
return isBest;
|
||||
};
|
||||
|
||||
const name = 'coral-plugin-best';
|
||||
const lang = new I18n(translations);
|
||||
@@ -38,10 +41,6 @@ export class BestButton extends Component {
|
||||
removeBest: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
state = {
|
||||
best: false
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onClickAddBest = this.onClickAddBest.bind(this);
|
||||
@@ -75,8 +74,7 @@ export class BestButton extends Component {
|
||||
// @TODO(bengo) Consider adding the comment__action classes to other buttons to add cursor:pointer and never wrap the icons
|
||||
// @TODO(bengo) Should I reuse another element like coral-ui button? Just doing what LikeButton does for now
|
||||
// Oh. I think that's styled for the admin. Don't use coral-ui button until the whole comment bottom bar does.
|
||||
let {isBest} = this.state;
|
||||
const {addBest, removeBest} = this.props;
|
||||
const {isBest, addBest, removeBest} = this.props;
|
||||
return <div className={classnames(`${name}-container`, `${name}-button`, 'comment__action-button--nowrap',
|
||||
`e2e__${isBest ? 'unset' : 'set'}-best-comment`)}>
|
||||
<button onClick={isBest ? this.onClickRemoveBest : this.onClickAddBest}
|
||||
|
||||
@@ -187,11 +187,20 @@ const setCommentStatus = ({loaders: {Comments}}, {id, status}) => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a tag to a Comment
|
||||
*/
|
||||
const addCommentTag = ({user, loaders: {Comments}}, {id, tag}) => {
|
||||
return CommentsService.addTag(id, tag, user.id)
|
||||
.then(() => CommentsService.findById( id ));
|
||||
};
|
||||
|
||||
module.exports = (context) => {
|
||||
let mutators = {
|
||||
Comment: {
|
||||
create: () => Promise.reject(errors.ErrNotAuthorized),
|
||||
setCommentStatus: () => Promise.reject(errors.ErrNotAuthorized)
|
||||
setCommentStatus: () => Promise.reject(errors.ErrNotAuthorized),
|
||||
addCommentTag: () => Promise.reject(errors.ErrNotAuthorized),
|
||||
}
|
||||
};
|
||||
|
||||
@@ -203,5 +212,9 @@ module.exports = (context) => {
|
||||
mutators.Comment.setCommentStatus = (action) => setCommentStatus(context, action);
|
||||
}
|
||||
|
||||
if (context.user && context.user.can('mutation:addCommentTag')) {
|
||||
mutators.Comment.addCommentTag = (action) => addCommentTag(context, action);
|
||||
}
|
||||
|
||||
return mutators;
|
||||
};
|
||||
|
||||
@@ -48,6 +48,9 @@ const RootMutation = {
|
||||
},
|
||||
setCommentStatus(_, {id, status}, {mutators: {Comment}}) {
|
||||
return wrapResponse(null)(Comment.setCommentStatus({id, status}));
|
||||
},
|
||||
addCommentTag(_, {id, tag}, {mutators: {Comment}}) {
|
||||
return wrapResponse('comment')(Comment.addCommentTag({id, tag}));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -631,6 +631,13 @@ type SetCommentStatusResponse implements Response {
|
||||
errors: [UserError]
|
||||
}
|
||||
|
||||
# Response to addCommentTag mutation
|
||||
type AddCommentTagResponse implements Response {
|
||||
# An array of errors relating to the mutation that occured.
|
||||
comment: Comment
|
||||
errors: [UserError]
|
||||
}
|
||||
|
||||
# All mutations for the application are defined on this object.
|
||||
type RootMutation {
|
||||
|
||||
@@ -654,6 +661,9 @@ type RootMutation {
|
||||
|
||||
# Sets Comment status. Requires the `ADMIN` role.
|
||||
setCommentStatus(id: ID!, status: COMMENT_STATUS!): SetCommentStatusResponse
|
||||
|
||||
# Add tag to comment.
|
||||
addCommentTag(id: ID!, tag: String!): AddCommentTagResponse
|
||||
}
|
||||
|
||||
################################################################################
|
||||
|
||||
+2
-1
@@ -156,7 +156,8 @@ const USER_GRAPH_OPERATIONS = [
|
||||
'mutation:deleteAction',
|
||||
'mutation:editName',
|
||||
'mutation:setUserStatus',
|
||||
'mutation:setCommentStatus'
|
||||
'mutation:setCommentStatus',
|
||||
'mutation:addCommentTag',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -103,6 +103,7 @@
|
||||
"babel-preset-es2015": "^6.18.0",
|
||||
"babel-preset-stage-0": "^6.16.0",
|
||||
"chai": "^3.5.0",
|
||||
"chai-as-promised": "^6.0.0",
|
||||
"chai-http": "^3.0.0",
|
||||
"copy-webpack-plugin": "^4.0.0",
|
||||
"css-loader": "^0.25.0",
|
||||
|
||||
+37
-16
@@ -4,7 +4,8 @@ const ActionModel = require('../models/action');
|
||||
const ActionsService = require('./actions');
|
||||
|
||||
const ALLOWED_TAGS = [
|
||||
{name: 'STAFF'}
|
||||
'STAFF',
|
||||
'BEST',
|
||||
];
|
||||
|
||||
const STATUSES = [
|
||||
@@ -44,29 +45,49 @@ module.exports = class CommentsService {
|
||||
|
||||
/**
|
||||
* Adds a tag if it doesn't already exist on the comment.
|
||||
* @param {String} id the id of the comment to tag
|
||||
* @param {String} name the name of the tag to add
|
||||
* @param {String} assigned_by the user id for the user who added the tag
|
||||
*/
|
||||
static addTag(id, name, assigned_by) {
|
||||
|
||||
if (ALLOWED_TAGS.find((t) => t.name === name) == null) {
|
||||
if (ALLOWED_TAGS.find((t) => t === name) == null) {
|
||||
return Promise.reject(new Error('tag not allowed'));
|
||||
}
|
||||
|
||||
return CommentModel.update({
|
||||
const filter = {
|
||||
id,
|
||||
tags: {
|
||||
$ne: {
|
||||
name
|
||||
tags: {$not: {$elemMatch: {name}}}
|
||||
};
|
||||
const update = {
|
||||
$push: {tags: {
|
||||
name,
|
||||
assigned_by,
|
||||
created_at: new Date()
|
||||
}}
|
||||
};
|
||||
return CommentModel.update(filter, update)
|
||||
.then(({nModified}) => {
|
||||
switch (nModified) {
|
||||
case 0:
|
||||
|
||||
// either the tag was already there, or the comment doesn't exist with that id...
|
||||
return this.findById(id)
|
||||
.then(result => {
|
||||
if ( ! result) {
|
||||
throw new Error(`Can't add tag to comment. There is no comment with id ${id}`);
|
||||
}
|
||||
throw new Error(`Can't add tag ${name} to comment. Comment already has that tag.`);
|
||||
});
|
||||
case 1:
|
||||
|
||||
// tag added
|
||||
return;
|
||||
default:
|
||||
console.warn('CommentsService#addTag modified multiple comments, but it should only have modified 1. '
|
||||
+ `You may have bad data. Check for multiple comments with id=${id}`);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
$push: {
|
||||
tags: {
|
||||
name,
|
||||
assigned_by,
|
||||
created_at: new Date()
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,6 +16,7 @@ module.exports = {
|
||||
|
||||
const setBestCommentButton = '@setBestButton';
|
||||
const unsetBestCommentButton = '@unsetBestButton';
|
||||
|
||||
embedStreamPage
|
||||
.postComment('Hi everyone. Isn\'t this the BEST comment!?')
|
||||
.waitForElementVisible(setBestCommentButton, 2000)
|
||||
@@ -25,13 +26,9 @@ module.exports = {
|
||||
});
|
||||
|
||||
// on refresh, it should still be tagged as best :)
|
||||
// client
|
||||
// .refresh(() => {
|
||||
|
||||
// })
|
||||
// .waitForElementVisible(unsetBestCommentButton, 2000)
|
||||
|
||||
// client.pause()
|
||||
client.refresh();
|
||||
client.page.embedStreamPage().ready()
|
||||
.waitForElementVisible('.e2e__unset-best-comment', 2000);
|
||||
},
|
||||
after: client => {
|
||||
client.end();
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
const expect = require('chai').expect;
|
||||
const {graphql} = require('graphql');
|
||||
|
||||
const schema = require('../../../graph/schema');
|
||||
const Context = require('../../../graph/context');
|
||||
const UserModel = require('../../../models/user');
|
||||
const SettingsService = require('../../../services/settings');
|
||||
const CommentsService = require('../../../services/comments');
|
||||
|
||||
describe('graph.mutations.addCommentTag', () => {
|
||||
let comment;
|
||||
beforeEach(async () => {
|
||||
SettingsService.init();
|
||||
comment = await CommentsService.publicCreate({body: `hello there! ${ String(Math.random()).slice(2)}`});
|
||||
});
|
||||
|
||||
const query = `
|
||||
mutation AddCommentTag ($id: ID!, $tag: String!) {
|
||||
addCommentTag(id:$id, tag:$tag) {
|
||||
comment {
|
||||
tags {
|
||||
name
|
||||
}
|
||||
}
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
it('can add tags to comments', async () => {
|
||||
const user = new UserModel({});
|
||||
const context = new Context({user});
|
||||
const response = await graphql(schema, query, {}, context, {id: comment.id, tag: 'BEST'});
|
||||
if (response.errors && response.errors.length) {
|
||||
console.error(response.errors);
|
||||
}
|
||||
expect(response.errors).to.be.empty;
|
||||
expect(response.data.addCommentTag.comment.tags).to.deep.equal([{name: 'BEST'}]);
|
||||
});
|
||||
});
|
||||
@@ -2,6 +2,7 @@ test/helpers/*.js
|
||||
test
|
||||
--compilers js:babel-core/register
|
||||
--require ignore-styles
|
||||
--require babel-polyfill
|
||||
--recursive
|
||||
--colors
|
||||
--sort
|
||||
|
||||
@@ -8,7 +8,7 @@ const CommentsService = require('../../services/comments');
|
||||
|
||||
const settings = {id: '1', moderation: 'PRE', wordlist: {banned: ['bad words'], suspect: ['suspect words']}};
|
||||
|
||||
const expect = require('chai').expect;
|
||||
const expect = require('chai').use(require('chai-as-promised')).expect;
|
||||
|
||||
describe('services.CommentsService', () => {
|
||||
const comments = [{
|
||||
@@ -68,6 +68,7 @@ describe('services.CommentsService', () => {
|
||||
}];
|
||||
|
||||
const users = [{
|
||||
id: 'u1',
|
||||
email: 'stampi@gmail.com',
|
||||
username: 'Stampi',
|
||||
password: '1Coral!!'
|
||||
@@ -218,6 +219,50 @@ describe('services.CommentsService', () => {
|
||||
|
||||
});
|
||||
|
||||
describe('#addTag', () => {
|
||||
it('adds a tag', async () => {
|
||||
const commentId = comments[0].id;
|
||||
const tagName = 'BEST';
|
||||
const userId = users[0].id;
|
||||
await CommentsService.addTag(commentId, tagName, userId);
|
||||
const updatedComment = await CommentsService.findById(commentId);
|
||||
expect(updatedComment.tags.length).to.equal(1);
|
||||
expect(updatedComment.tags[0].name).to.equal(tagName);
|
||||
expect(updatedComment.tags[0].assigned_by).to.equal(userId);
|
||||
expect(updatedComment.tags[0].created_at).to.be.an.instanceof(Date);
|
||||
});
|
||||
it('can\'t add a tag to comment id that doesn\'t exist', async () => {
|
||||
const commentId = 'fakenews';
|
||||
const tagName = 'BEST';
|
||||
const userId = users[0].id;
|
||||
await expect(CommentsService.addTag(commentId, tagName, userId)).to.be.rejected;
|
||||
});
|
||||
it('can\'t add same tag.name twice', async () => {
|
||||
const commentId = comments[0].id;
|
||||
const tagName = 'BEST';
|
||||
const userId = users[0].id;
|
||||
|
||||
// first time
|
||||
await CommentsService.addTag(commentId, tagName, userId);
|
||||
|
||||
// second time should fail
|
||||
await expect(CommentsService.addTag(commentId, tagName, userId)).to.be.rejected;
|
||||
});
|
||||
});
|
||||
|
||||
describe('#removeTag', () => {
|
||||
it.skip('removes a tag', async () => {
|
||||
const commentId = comments[0].id;
|
||||
const tagName = 'BEST';
|
||||
await CommentsService.addTag(commentId, tagName, users[0].id);
|
||||
let updatedComment = await CommentsService.findById(commentId);
|
||||
expect(updatedComment.tags.length).to.equal(1);
|
||||
|
||||
// ok now to remove it
|
||||
await CommentsService.removeTag(commentId, tagName);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#changeStatus', () => {
|
||||
|
||||
it('should change the status of a comment from no status', () => {
|
||||
|
||||
Reference in New Issue
Block a user