Unverified Commit 64fecf28 authored by Paul Kaplan's avatar Paul Kaplan Committed by GitHub

Merge pull request #5237 from paulkaplan/studio-comment-more-actions

Hook up remaining comment actions for studio comments
parents 8fa0b977 7c4871c1
......@@ -31,8 +31,14 @@ const {
selectStudioId
} = require('./studio');
const getReplies = (studioId, commentIds, offset, isAdmin, token) => (dispatch => {
const getReplies = (commentIds, offset) => ((dispatch, getState) => {
if (!Array.isArray(commentIds)) commentIds = [commentIds];
dispatch(setFetchStatus('replies', Status.FETCHING));
const state = getState();
const studioId = selectStudioId(state);
const isAdmin = selectIsAdmin(state);
const token = selectToken(state);
const fetchedReplies = {};
eachLimit(commentIds, 10, (parentId, callback) => {
api({
......@@ -84,7 +90,7 @@ const getTopLevelComments = () => ((dispatch, getState) => {
}
dispatch(setFetchStatus('comments', Status.FETCHED));
dispatch(setComments(body));
dispatch(getReplies(id, body.map(comment => comment.id), 0, isAdmin, token));
dispatch(getReplies(body.map(comment => comment.id), 0));
// If we loaded a full page of comments, assume there are more to load.
// This will be wrong (1 / COMMENT_LIMIT) of the time, but does not require
......@@ -95,7 +101,11 @@ const getTopLevelComments = () => ((dispatch, getState) => {
});
});
const getCommentById = (studioId, commentId, isAdmin, token) => (dispatch => {
const getCommentById = commentId => ((dispatch, getState) => {
const state = getState();
const studioId = selectStudioId(state);
const isAdmin = selectIsAdmin(state);
const token = selectToken(state);
dispatch(setFetchStatus('comments', Status.FETCHING));
api({
uri: `${isAdmin ? '/admin' : ''}/studios/${studioId}/comments/${commentId}`,
......@@ -114,18 +124,20 @@ const getCommentById = (studioId, commentId, isAdmin, token) => (dispatch => {
if (body.parent_id) {
// If the comment is a reply, load the parent
return dispatch(getCommentById(studioId, body.parent_id, isAdmin, token));
return dispatch(getCommentById(body.parent_id));
}
// If the comment is not a reply, show it as top level and load replies
dispatch(setFetchStatus('comments', Status.FETCHED));
dispatch(setComments([body]));
dispatch(getReplies(studioId, [body.id], 0, isAdmin, token));
dispatch(getReplies(body.id, 0));
});
});
const deleteComment = (studioId, commentId, topLevelCommentId, token) => (dispatch => {
/* TODO fetching/fetched/error states updates for comment deleting */
const deleteComment = (commentId, topLevelCommentId) => ((dispatch, getState) => {
const state = getState();
const studioId = selectStudioId(state);
const token = selectToken(state);
api({
uri: `/proxy/comments/studio/${studioId}/comment/${commentId}`,
authentication: token,
......@@ -144,7 +156,10 @@ const deleteComment = (studioId, commentId, topLevelCommentId, token) => (dispat
});
});
const reportComment = (studioId, commentId, topLevelCommentId, token) => (dispatch => {
const reportComment = (commentId, topLevelCommentId) => ((dispatch, getState) => {
const state = getState();
const studioId = selectStudioId(state);
const token = selectToken(state);
api({
uri: `/proxy/studio/${studioId}/comment/${commentId}/report`,
authentication: token,
......@@ -161,7 +176,10 @@ const reportComment = (studioId, commentId, topLevelCommentId, token) => (dispat
});
});
const restoreComment = (studioId, commentId, topLevelCommentId, token) => (dispatch => {
const restoreComment = (commentId, topLevelCommentId) => ((dispatch, getState) => {
const state = getState();
const studioId = selectStudioId(state);
const token = selectToken(state);
api({
uri: `/proxy/admin/studio/${studioId}/comment/${commentId}/undelete`,
authentication: token,
......
......@@ -91,8 +91,14 @@ const selectCanAddProjects = state =>
isCurator(state) ||
(selectIsSocial(state) && state.studio.openToAll);
// This isn't "canComment" since they could be muted, but comment composer handles that
const selectShowCommentComposer = state => selectIsSocial(state);
const selectCanReportComment = state => selectIsSocial(state);
const selectCanRestoreComment = state => selectIsAdmin(state);
// On the project page, project owners can delete comments with a confirmation,
// and admins can delete comments without a confirmation. For now, only admins
// can delete studio comments, so the following two are the same.
const selectCanDeleteComment = state => selectIsAdmin(state);
const selectCanDeleteCommentWithoutConfirm = state => selectIsAdmin(state);
// Data selectors
const selectStudioId = state => state.studio.id;
......@@ -157,5 +163,9 @@ module.exports = {
selectStudioId,
selectCanEditInfo,
selectCanAddProjects,
selectShowCommentComposer
selectShowCommentComposer,
selectCanDeleteComment,
selectCanDeleteCommentWithoutConfirm,
selectCanReportComment,
selectCanRestoreComment
};
import React, {useEffect} from 'react';
import PropTypes from 'prop-types';
import {useParams} from 'react-router-dom';
import {connect} from 'react-redux';
import {FormattedMessage} from 'react-intl';
......@@ -9,7 +8,13 @@ import ComposeComment from '../preview/comment/compose-comment.jsx';
import TopLevelComment from '../preview/comment/top-level-comment.jsx';
import studioCommentActions from '../../redux/studio-comment-actions.js';
import {selectShowCommentComposer} from '../../redux/studio.js';
import {
selectShowCommentComposer,
selectCanDeleteComment,
selectCanDeleteCommentWithoutConfirm,
selectCanReportComment,
selectCanRestoreComment
} from '../../redux/studio.js';
const StudioComments = ({
comments,
......@@ -17,10 +22,17 @@ const StudioComments = ({
handleNewComment,
moreCommentsToLoad,
replies,
shouldShowCommentComposer
postURI,
shouldShowCommentComposer,
canDeleteComment,
canDeleteCommentWithoutConfirm,
canReportComment,
canRestoreComment,
handleDeleteComment,
handleRestoreComment,
handleReportComment,
handleLoadMoreReplies
}) => {
const {studioId} = useParams();
useEffect(() => {
if (comments.length === 0) handleLoadMoreComments();
}, []); // Only runs once after the first render
......@@ -31,24 +43,32 @@ const StudioComments = ({
<div>
{shouldShowCommentComposer &&
<ComposeComment
postURI={`/proxy/comments/studio/${studioId}`}
postURI={postURI}
onAddComment={handleNewComment}
/>
}
{comments.map(comment => (
<TopLevelComment
author={comment.author}
canDelete={canDeleteComment}
canDeleteWithoutConfirm={canDeleteCommentWithoutConfirm}
canReply={shouldShowCommentComposer}
canReport={canReportComment}
canRestore={canRestoreComment}
content={comment.content}
datetimeCreated={comment.datetime_created}
id={comment.id}
key={comment.id}
moreRepliesToLoad={comment.moreRepliesToLoad}
parentId={comment.parent_id}
postURI={`/proxy/comments/studio/${studioId}`}
postURI={postURI}
replies={replies && replies[comment.id] ? replies[comment.id] : []}
visibility={comment.visibility}
onAddComment={handleNewComment}
onDelete={handleDeleteComment}
onRestore={handleRestoreComment}
onReport={handleReportComment}
onLoadMoreReplies={handleLoadMoreReplies}
/>
))}
{moreCommentsToLoad &&
......@@ -70,19 +90,37 @@ StudioComments.propTypes = {
handleNewComment: PropTypes.func,
moreCommentsToLoad: PropTypes.bool,
replies: PropTypes.shape({}),
shouldShowCommentComposer: PropTypes.bool
shouldShowCommentComposer: PropTypes.bool,
canDeleteComment: PropTypes.bool,
canDeleteCommentWithoutConfirm: PropTypes.bool,
canReportComment: PropTypes.bool,
canRestoreComment: PropTypes.bool,
handleDeleteComment: PropTypes.func,
handleRestoreComment: PropTypes.func,
handleReportComment: PropTypes.func,
handleLoadMoreReplies: PropTypes.func,
postURI: PropTypes.string
};
export default connect(
state => ({
comments: state.comments.comments,
moreCommentsToLoad: state.comments.moreCommentsToLoad,
replies: state.comments.replies,
shouldShowCommentComposer: selectShowCommentComposer(state)
shouldShowCommentComposer: selectShowCommentComposer(state),
canDeleteComment: selectCanDeleteComment(state),
canDeleteCommentWithoutConfirm: selectCanDeleteCommentWithoutConfirm(state),
canReportComment: selectCanReportComment(state),
canRestoreComment: selectCanRestoreComment(state),
postURI: `/proxy/comments/studio/${state.studio.id}`
}),
{
handleLoadMoreComments: studioCommentActions.getTopLevelComments,
handleNewComment: studioCommentActions.addNewComment
handleNewComment: studioCommentActions.addNewComment,
handleDeleteComment: studioCommentActions.deleteComment,
handleRestoreComment: studioCommentActions.restoreComment,
handleReportComment: studioCommentActions.reportComment,
handleLoadMoreReplies: studioCommentActions.getReplies
}
)(StudioComments);
......@@ -2,7 +2,11 @@ import {
getInitialState as getInitialStudioState,
selectCanEditInfo,
selectCanAddProjects,
selectShowCommentComposer
selectShowCommentComposer,
selectCanDeleteComment,
selectCanDeleteCommentWithoutConfirm,
selectCanReportComment,
selectCanRestoreComment
} from '../../../src/redux/studio';
import {
......@@ -11,79 +15,156 @@ import {
import {sessions, studios} from '../../helpers/state-fixtures.json';
describe('studio selectors', () => {
let state;
let state;
beforeEach(() => {
state = {
session: getInitialSessionState(),
studio: getInitialStudioState()
};
});
const setStateByRole = (role) => {
switch (role) {
case 'admin':
state.session = sessions.user1Admin;
break;
case 'curator':
state.studio = studios.isCurator;
state.session = sessions.user1Social;
break;
case 'manager':
state.studio = studios.isManager;
state.session = sessions.user1Social;
break;
case 'creator':
state.studio = studios.creator1;
state.session = sessions.user1Social;
break;
case 'logged in':
state.session = sessions.user1Social;
break;
case 'unconfirmed':
state.session = sessions.user1;
break;
case 'logged out': // Default state set in beforeEach
break;
default:
throw new Error('Unknown user role in test: ' + role);
}
};
describe('studio info', () => {
test('is editable by admin', () => {
state.session = sessions.user1Admin;
expect(selectCanEditInfo(state)).toBe(true);
});
test('is editable by managers and studio creator', () => {
state.studio = studios.isManager;
expect(selectCanEditInfo(state)).toBe(true);
beforeEach(() => {
state = {
session: getInitialSessionState(),
studio: getInitialStudioState()
};
});
state.studio = studios.creator1;
state.session = sessions.user1;
expect(selectCanEditInfo(state)).toBe(true);
});
test('is not editable by curators', () => {
state.studio = studios.isCurator;
state.session = sessions.user1;
expect(selectCanEditInfo(state)).toBe(false);
describe('studio info', () => {
describe('can edit studio info', () => {
test.each([
['admin', true],
['curator', false],
['manager', true],
['creator', true],
['logged in', false],
['unconfirmed', false],
['logged out', false]
])('%s: %s', (role, expected) => {
setStateByRole(role);
expect(selectCanEditInfo(state)).toBe(expected);
});
test('is not editable by other logged in users', () => {
state.session = sessions.user1;
expect(selectCanEditInfo(state)).toBe(false);
});
test('is not editable by logged out users', () => {
expect(selectCanEditInfo(state)).toBe(false);
});
});
describe('studio projects', () => {
describe('can add project, not open to all', () => {
test.each([
['admin', false],
['curator', true],
['manager', true],
['creator', true],
['logged in', false],
['unconfirmed', false],
['logged out', false]
])('%s: %s', (role, expected) => {
setStateByRole(role);
expect(selectCanAddProjects(state)).toBe(expected);
});
});
describe('studio projects', () => {
test('cannot be added by admin', () => {
state.session = sessions.user1Admin;
expect(selectCanAddProjects(state)).toBe(false);
describe('can add project, open to all', () => {
test.each([
['logged in', true],
['unconfirmed', false],
['logged out', false]
])('%s: %s', (role, expected) => {
setStateByRole(role);
state.studio.openToAll = true;
expect(selectCanAddProjects(state)).toBe(expected);
});
test('can be added by managers and studio creator', () => {
state.studio = studios.isManager;
expect(selectCanAddProjects(state)).toBe(true);
});
});
state.studio = studios.creator1;
state.session = sessions.user1;
expect(selectCanAddProjects(state)).toBe(true);
describe('studio comments', () => {
describe('showing comment composer', () => {
test.each([
['logged in', true],
['unconfirmed', false],
['logged out', false]
])('%s: %s', (role, expected) => {
setStateByRole(role);
expect(selectShowCommentComposer(state)).toBe(expected);
});
test('can be added by curators', () => {
state.studio = studios.isCurator;
state.session = sessions.user1;
expect(selectCanAddProjects(state)).toBe(true);
});
describe('can report comment', () => {
test.each([
['logged in', true],
['unconfirmed', false],
['logged out', false]
])('%s: %s', (role, expected) => {
setStateByRole(role);
expect(selectCanReportComment(state)).toBe(expected);
});
test('can be added by social users if studio is openToAll', () => {
state.studio = studios.openToAll;
state.session = sessions.user1Social;
expect(selectCanAddProjects(state)).toBe(true);
});
describe('can delete comment', () => {
test.each([
['admin', true],
['curator', false],
['manager', false],
['creator', false],
['logged in', false],
['unconfirmed', false],
['logged out', false]
])('%s: %s', (role, expected) => {
setStateByRole(role);
expect(selectCanDeleteComment(state)).toBe(expected);
});
test('cannot be added by social users if not openToAll', () => {
state.session = sessions.user1Social;
expect(selectCanAddProjects(state)).toBe(false);
});
describe('can delete comment without confirmation', () => {
test.each([
['admin', true],
['curator', false],
['manager', false],
['creator', false],
['logged in', false],
['unconfirmed', false],
['logged out', false]
])('%s: %s', (role, expected) => {
setStateByRole(role);
expect(selectCanDeleteCommentWithoutConfirm(state)).toBe(expected);
});
});
describe('studio comments', () => {
test('show comment composer only for social users', () => {
expect(selectShowCommentComposer(state)).toBe(false);
state.session = sessions.user1;
expect(selectShowCommentComposer(state)).toBe(false);
state.session = sessions.user1Social;
expect(selectShowCommentComposer(state)).toBe(true);
describe('can restore a comment', () => {
test.each([
['admin', true],
['curator', false],
['manager', false],
['creator', false],
['logged in', false],
['unconfirmed', false],
['logged out', false]
])('%s: %s', (role, expected) => {
setStateByRole(role);
expect(selectCanRestoreComment(state)).toBe(expected);
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment