From 8664a16bfc5e4603f675e4c7e7ff19b771895989 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Fri, 3 Feb 2017 16:05:27 -0300 Subject: [PATCH 01/18] modQueue Query --- .../src/graphql/queries/modQueueQuery.graphql | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 client/coral-admin/src/graphql/queries/modQueueQuery.graphql diff --git a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql new file mode 100644 index 000000000..e26e440f8 --- /dev/null +++ b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql @@ -0,0 +1,20 @@ +query ModQueue { + flagged_comments: comments(query: { + action_type: FLAG, + asset_id: $asset_id + }) { + id + } + accepted_comments: comments(query: { + statuses: [ACCEPTED], + asset_id: $asset_id + }) { + id + } + rejected_comments: comments(query: { + statuses: [REJECTED], + asset_id: $asset_id + }) { + id + } +} From c837e89b380fe03cbfd53a0480895e0bb6941420 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Tue, 7 Feb 2017 13:43:17 -0300 Subject: [PATCH 02/18] wip --- client/coral-admin/src/actions/moderation.js | 5 + .../coral-admin/src/constants/moderation.js | 3 + .../ModerationQueue/ModerationContainer.js | 97 ++++++++----------- client/coral-admin/src/reducers/index.js | 26 ++--- client/coral-admin/src/reducers/moderation.js | 24 +++++ 5 files changed, 89 insertions(+), 66 deletions(-) create mode 100644 client/coral-admin/src/actions/moderation.js create mode 100644 client/coral-admin/src/constants/moderation.js create mode 100644 client/coral-admin/src/reducers/moderation.js diff --git a/client/coral-admin/src/actions/moderation.js b/client/coral-admin/src/actions/moderation.js new file mode 100644 index 000000000..25a9aed73 --- /dev/null +++ b/client/coral-admin/src/actions/moderation.js @@ -0,0 +1,5 @@ +import * as actions from 'constants/moderation'; + +export const setActiveTab = activeTab => ({type: actions.SET_ACTIVE_TAB, activeTab}); +export const toggleModal = open => ({type: actions.TOGGLE_MODAL, open}); +export const singleView = open => ({type: actions.SINGLE_VIEW}); diff --git a/client/coral-admin/src/constants/moderation.js b/client/coral-admin/src/constants/moderation.js new file mode 100644 index 000000000..1ef27eddd --- /dev/null +++ b/client/coral-admin/src/constants/moderation.js @@ -0,0 +1,3 @@ +export const SET_ACTIVE_TAB = 'SET_ACTIVE_TAB'; +export const TOGGLE_MODAL = 'TOGGLE_MODAL'; +export const SINGLE_VIEW = 'SINGLE_VIEW' diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index d1d1afd53..095321e42 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -11,32 +11,23 @@ import { fetchFlaggedQueue, fetchModerationQueueComments, } from 'actions/comments'; -import {userStatusUpdate, sendNotificationEmail} from 'actions/users'; + import {fetchSettings} from 'actions/settings'; +import {userStatusUpdate, sendNotificationEmail} from 'actions/users'; +import {setActiveTab, toggleModal, singleView} from 'actions/moderation'; import ModerationQueue from './ModerationQueue'; class ModerationContainer extends React.Component { - - constructor(props) { - super(props); - - this.state = { - activeTab: 'all', - singleView: false, - modalOpen: false - }; - - this.onClose = this.onClose.bind(this); - this.onTabClick = this.onTabClick.bind(this); - } - componentWillMount() { + const {toggleModal, singleView} = this.props; + this.props.fetchModerationQueueComments(); this.props.fetchSettings(); - key('s', () => this.setState({singleView: !this.state.singleView})); - key('shift+/', () => this.setState({modalOpen: true})); - key('esc', () => this.setState({modalOpen: false})); + + key('s', () => singleView()); + key('shift+/', () => toggleModal(true)); + key('esc', () => toggleModal(false)); } componentWillUnmount() { @@ -45,18 +36,9 @@ class ModerationContainer extends React.Component { key.unbind('esc'); } - componentDidMount() { - - // Hack for dynamic mdl tabs - if (typeof componentHandler !== 'undefined') { - - // FIXME: fix this hack - componentHandler.upgradeAllRegistered(); // eslint-disable-line no-undef - } - } - - onTabClick(activeTab) { - this.setState({activeTab}); + onTabClick = (activeTab) => { + const {setActiveTab} = this.props; + setActiveTab(activeTab); if (activeTab === 'premod') { this.props.fetchPremodQueue(); @@ -69,12 +51,15 @@ class ModerationContainer extends React.Component { } } - onClose() { - this.setState({modalOpen: false}); + onClose = () => { + const {toggleModal} = this.props; + toggleModal(false); } render () { - const {comments, actions, settings} = this.props; + const {comments, actions, settings, moderation} = this.props; + + // Remove all this filters 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 => @@ -97,37 +82,41 @@ class ModerationContainer extends React.Component { rejectedIds={rejectedIds} flaggedIds={flaggedIds} {...this.props} - {...this.state} + {...moderation} /> ); } } const mapStateToProps = state => ({ + moderation: state.moderation.toJS(), comments: state.comments.toJS(), settings: state.settings.toJS(), users: state.users.toJS(), - actions: state.actions.toJS(), + actions: state.actions.toJS() }); -const mapDispatchToProps = dispatch => { - return { - fetchSettings: () => dispatch(fetchSettings()), - fetchModerationQueueComments: () => dispatch(fetchModerationQueueComments()), - fetchPremodQueue: () => dispatch(fetchPremodQueue()), - fetchRejectedQueue: () => dispatch(fetchRejectedQueue()), - fetchFlaggedQueue: () => dispatch(fetchFlaggedQueue()), - showBanUserDialog: (userId, userName, commentId) => dispatch(showBanUserDialog(userId, userName, commentId)), - hideBanUserDialog: () => dispatch(hideBanUserDialog(false)), - userStatusUpdate: (status, userId, commentId) => dispatch(userStatusUpdate(status, userId, commentId)).then(() => { - dispatch(fetchModerationQueueComments()); - }), - suspendUser: (userId, subject, text) => dispatch(userStatusUpdate('suspended', userId)) - .then(() => dispatch(sendNotificationEmail(userId, subject, text))) - .then(() => dispatch(fetchModerationQueueComments())) - , - updateStatus: (action, comment) => dispatch(updateStatus(action, comment)) - }; -}; +const mapDispatchToProps = dispatch => ({ + setActiveTab: tab => dispatch(setActiveTab(tab)), + toggleModal: open => dispatch(toggleModal(open)), + singleView: () => dispatch(singleView()), + + + fetchSettings: () => dispatch(fetchSettings()), + fetchModerationQueueComments: () => dispatch(fetchModerationQueueComments()), + fetchPremodQueue: () => dispatch(fetchPremodQueue()), + fetchRejectedQueue: () => dispatch(fetchRejectedQueue()), + fetchFlaggedQueue: () => dispatch(fetchFlaggedQueue()), + showBanUserDialog: (userId, userName, commentId) => dispatch(showBanUserDialog(userId, userName, commentId)), + hideBanUserDialog: () => dispatch(hideBanUserDialog(false)), + userStatusUpdate: (status, userId, commentId) => dispatch(userStatusUpdate(status, userId, commentId)).then(() => { + dispatch(fetchModerationQueueComments()); + }), + suspendUser: (userId, subject, text) => dispatch(userStatusUpdate('suspended', userId)) + .then(() => dispatch(sendNotificationEmail(userId, subject, text))) + .then(() => dispatch(fetchModerationQueueComments())) + , + updateStatus: (action, comment) => dispatch(updateStatus(action, comment)) +}); export default connect(mapStateToProps, mapDispatchToProps)(ModerationContainer); diff --git a/client/coral-admin/src/reducers/index.js b/client/coral-admin/src/reducers/index.js index a19a5a087..415d0f581 100644 --- a/client/coral-admin/src/reducers/index.js +++ b/client/coral-admin/src/reducers/index.js @@ -1,19 +1,21 @@ import {combineReducers} from 'redux'; -import comments from 'reducers/comments'; -import settings from 'reducers/settings'; -import community from 'reducers/community'; -import users from 'reducers/users'; -import auth from 'reducers/auth'; -import actions from 'reducers/actions'; -import assets from 'reducers/assets'; -// Combine all reducers into a main one +import auth from './auth'; +import users from './users'; +import assets from './assets'; +import actions from './actions'; +import comments from './comments'; +import settings from './settings'; +import community from './community'; +import moderation from './moderation'; + export default combineReducers({ + auth, + users, + assets, + actions, settings, comments, community, - auth, - actions, - assets, - users + moderation }); diff --git a/client/coral-admin/src/reducers/moderation.js b/client/coral-admin/src/reducers/moderation.js new file mode 100644 index 000000000..3f04abfc6 --- /dev/null +++ b/client/coral-admin/src/reducers/moderation.js @@ -0,0 +1,24 @@ +import {Map} from 'immutable'; +import * as actions from '../constants/moderation'; + +const initialState = Map({ + activeTab: 'all', + singleView: false, + modalOpen: false +}); + +export default function moderation (state = initialState, action) { + switch (action.type) { + case actions.SET_ACTIVE_TAB: + return state + .set('activeTab', action.activeTab); + case actions.TOGGLE_MODAL: + return state + .set('modalOpen', action.open); + case actions.SINGLE_VIEW: + return state + .set('singleView', !state.get('singleView')); + default : + return state; + } +} From 3fc7fbd01e51f4084490256e1ad1e6e27a614f51 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Tue, 7 Feb 2017 16:06:32 -0300 Subject: [PATCH 03/18] =?UTF-8?q?=C3=81dmin=20running=20on=20Apollo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/coral-admin/src/actions/moderation.js | 2 +- .../coral-admin/src/constants/moderation.js | 2 +- .../ModerationQueue/ModerationContainer.js | 16 ++++++++-- .../coral-admin/src/graphql/queries/index.js | 10 +++++++ client/coral-admin/src/index.js | 15 ++++++++-- client/coral-admin/src/reducers/index.js | 6 ++-- .../src/services}/PymConnection.js | 0 .../src/services}/client.js | 0 client/coral-admin/src/services/store.js | 30 +++++++++++-------- .../src/services}/subscriptions.js | 0 .../src/services}/transport.js | 0 .../coral-framework/services/PymConnection.js | 9 ++++++ client/coral-framework/services/client.js | 14 +++++++++ .../coral-framework/{ => services}/store.js | 0 .../coral-framework/services/subscriptions.js | 14 +++++++++ client/coral-framework/services/transport.js | 11 +++++++ 16 files changed, 105 insertions(+), 24 deletions(-) create mode 100644 client/coral-admin/src/graphql/queries/index.js rename client/{coral-framework => coral-admin/src/services}/PymConnection.js (100%) rename client/{coral-framework => coral-admin/src/services}/client.js (100%) rename client/{coral-framework => coral-admin/src/services}/subscriptions.js (100%) rename client/{coral-framework => coral-admin/src/services}/transport.js (100%) create mode 100644 client/coral-framework/services/PymConnection.js create mode 100644 client/coral-framework/services/client.js rename client/coral-framework/{ => services}/store.js (100%) create mode 100644 client/coral-framework/services/subscriptions.js create mode 100644 client/coral-framework/services/transport.js diff --git a/client/coral-admin/src/actions/moderation.js b/client/coral-admin/src/actions/moderation.js index 25a9aed73..425628abe 100644 --- a/client/coral-admin/src/actions/moderation.js +++ b/client/coral-admin/src/actions/moderation.js @@ -2,4 +2,4 @@ import * as actions from 'constants/moderation'; export const setActiveTab = activeTab => ({type: actions.SET_ACTIVE_TAB, activeTab}); export const toggleModal = open => ({type: actions.TOGGLE_MODAL, open}); -export const singleView = open => ({type: actions.SINGLE_VIEW}); +export const singleView = () => ({type: actions.SINGLE_VIEW}); diff --git a/client/coral-admin/src/constants/moderation.js b/client/coral-admin/src/constants/moderation.js index 1ef27eddd..0fa51dc7b 100644 --- a/client/coral-admin/src/constants/moderation.js +++ b/client/coral-admin/src/constants/moderation.js @@ -1,3 +1,3 @@ export const SET_ACTIVE_TAB = 'SET_ACTIVE_TAB'; export const TOGGLE_MODAL = 'TOGGLE_MODAL'; -export const SINGLE_VIEW = 'SINGLE_VIEW' +export const SINGLE_VIEW = 'SINGLE_VIEW'; diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index 095321e42..074d071e5 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -1,6 +1,9 @@ import React from 'react'; -import {connect} from 'react-redux'; import key from 'keymaster'; +import {connect} from 'react-redux'; +import {compose} from 'react-apollo'; + +import {modQueueQuery} from '../../graphql/queries'; import { updateStatus, @@ -19,6 +22,7 @@ import {setActiveTab, toggleModal, singleView} from 'actions/moderation'; import ModerationQueue from './ModerationQueue'; class ModerationContainer extends React.Component { + componentWillMount() { const {toggleModal, singleView} = this.props; @@ -88,6 +92,10 @@ class ModerationContainer extends React.Component { } } +ModerationContainer.contextTypes = { + router: React.PropTypes.func.isRequired +}; + const mapStateToProps = state => ({ moderation: state.moderation.toJS(), comments: state.comments.toJS(), @@ -101,7 +109,6 @@ const mapDispatchToProps = dispatch => ({ toggleModal: open => dispatch(toggleModal(open)), singleView: () => dispatch(singleView()), - fetchSettings: () => dispatch(fetchSettings()), fetchModerationQueueComments: () => dispatch(fetchModerationQueueComments()), fetchPremodQueue: () => dispatch(fetchPremodQueue()), @@ -119,4 +126,7 @@ const mapDispatchToProps = dispatch => ({ updateStatus: (action, comment) => dispatch(updateStatus(action, comment)) }); -export default connect(mapStateToProps, mapDispatchToProps)(ModerationContainer); +export default compose( + connect(mapStateToProps, mapDispatchToProps), + modQueueQuery +)(ModerationContainer); diff --git a/client/coral-admin/src/graphql/queries/index.js b/client/coral-admin/src/graphql/queries/index.js new file mode 100644 index 000000000..20cf1af8f --- /dev/null +++ b/client/coral-admin/src/graphql/queries/index.js @@ -0,0 +1,10 @@ +import {graphql} from 'react-apollo'; +import MOD_QUEUE_QUERY from './modQueueQuery.graphql'; + +export const modQueueQuery = graphql(MOD_QUEUE_QUERY, { + options: () => ({ + variables: { + asset_url: 'id' + } + }) +}); diff --git a/client/coral-admin/src/index.js b/client/coral-admin/src/index.js index 84d1b84ad..38b441992 100644 --- a/client/coral-admin/src/index.js +++ b/client/coral-admin/src/index.js @@ -1,6 +1,15 @@ import React from 'react'; -import ReactDOM from 'react-dom'; +import {render} from 'react-dom'; +import {ApolloProvider} from 'react-apollo'; + +import {client} from 'services/client'; +import store from 'services/store'; + import App from './components/App'; -// Render the application into the DOM -ReactDOM.render(, document.querySelector('#root')); +render( + + + + , document.querySelector('#root') +); diff --git a/client/coral-admin/src/reducers/index.js b/client/coral-admin/src/reducers/index.js index 415d0f581..53256fac5 100644 --- a/client/coral-admin/src/reducers/index.js +++ b/client/coral-admin/src/reducers/index.js @@ -1,5 +1,3 @@ -import {combineReducers} from 'redux'; - import auth from './auth'; import users from './users'; import assets from './assets'; @@ -9,7 +7,7 @@ import settings from './settings'; import community from './community'; import moderation from './moderation'; -export default combineReducers({ +export default { auth, users, assets, @@ -18,4 +16,4 @@ export default combineReducers({ comments, community, moderation -}); +}; diff --git a/client/coral-framework/PymConnection.js b/client/coral-admin/src/services/PymConnection.js similarity index 100% rename from client/coral-framework/PymConnection.js rename to client/coral-admin/src/services/PymConnection.js diff --git a/client/coral-framework/client.js b/client/coral-admin/src/services/client.js similarity index 100% rename from client/coral-framework/client.js rename to client/coral-admin/src/services/client.js diff --git a/client/coral-admin/src/services/store.js b/client/coral-admin/src/services/store.js index 0700dc579..8b9529e82 100644 --- a/client/coral-admin/src/services/store.js +++ b/client/coral-admin/src/services/store.js @@ -1,17 +1,23 @@ - -import {createStore, applyMiddleware} from 'redux'; +import {createStore, combineReducers, applyMiddleware, compose} from 'redux'; import thunk from 'redux-thunk'; -import mainReducer from 'reducers'; +import mainReducer from '../reducers'; +import {client} from './client'; -/** - * Create the store by merging the app reducers with - * the talk adapter. The talk adapter is the wire between - * this client and the coral backend. The idea is we can - * write different adapters for other platforms if we want - */ +const middlewares = [ + applyMiddleware(client.middleware()), + applyMiddleware(thunk) +]; + +if (window.devToolsExtension) { + + // we can't have the last argument of compose() be undefined + middlewares.push(window.devToolsExtension()); +} export default createStore( - mainReducer, - window.devToolsExtension && window.devToolsExtension(), - applyMiddleware(thunk) + combineReducers({ + ...mainReducer, + apollo: client.reducer() + }), + compose(...middlewares) ); diff --git a/client/coral-framework/subscriptions.js b/client/coral-admin/src/services/subscriptions.js similarity index 100% rename from client/coral-framework/subscriptions.js rename to client/coral-admin/src/services/subscriptions.js diff --git a/client/coral-framework/transport.js b/client/coral-admin/src/services/transport.js similarity index 100% rename from client/coral-framework/transport.js rename to client/coral-admin/src/services/transport.js diff --git a/client/coral-framework/services/PymConnection.js b/client/coral-framework/services/PymConnection.js new file mode 100644 index 000000000..ca592b824 --- /dev/null +++ b/client/coral-framework/services/PymConnection.js @@ -0,0 +1,9 @@ +import Pym from '../../node_modules/pym.js'; + +const pym = new Pym.Child({polling: 100}); +export default pym; + +export const link = (url) => (e) => { + e.preventDefault(); + pym.sendMessage('navigate', url); +}; diff --git a/client/coral-framework/services/client.js b/client/coral-framework/services/client.js new file mode 100644 index 000000000..b4a7a38df --- /dev/null +++ b/client/coral-framework/services/client.js @@ -0,0 +1,14 @@ +import ApolloClient, {addTypename} from 'apollo-client'; +import getNetworkInterface from './transport'; + +export const client = new ApolloClient({ + connectToDevTools: true, + queryTransformer: addTypename, + dataIdFromObject: (result) => { + if (result.id && result.__typename) { // eslint-disable-line no-underscore-dangle + return result.__typename + result.id; // eslint-disable-line no-underscore-dangle + } + return null; + }, + networkInterface: getNetworkInterface() +}); diff --git a/client/coral-framework/store.js b/client/coral-framework/services/store.js similarity index 100% rename from client/coral-framework/store.js rename to client/coral-framework/services/store.js diff --git a/client/coral-framework/services/subscriptions.js b/client/coral-framework/services/subscriptions.js new file mode 100644 index 000000000..f514ea18b --- /dev/null +++ b/client/coral-framework/services/subscriptions.js @@ -0,0 +1,14 @@ +import {print} from 'graphql-tag/printer'; + +// quick way to add the subscribe and unsubscribe functions to the network interface +const addGraphQLSubscriptions = (networkInterface, wsClient) => Object.assign(networkInterface, { + subscribe: (request, handler) => wsClient.subscribe({ + query: print(request.query), + variables: request.variables, + }, handler), + unsubscribe: (id) => { + wsClient.unsubscribe(id); + }, +}); + +export default addGraphQLSubscriptions; diff --git a/client/coral-framework/services/transport.js b/client/coral-framework/services/transport.js new file mode 100644 index 000000000..2bd6ac636 --- /dev/null +++ b/client/coral-framework/services/transport.js @@ -0,0 +1,11 @@ +import {createNetworkInterface} from 'apollo-client'; + +export default function getNetworkInterface(apiUrl = '/api/v1/graph/ql', headers = {}) { + return new createNetworkInterface({ + uri: apiUrl, + opts: { + credentials: 'same-origin', + headers, + }, + }); +} From f8d83ffbde38c49c7cd81d9b5e12c6778ba4083a Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Tue, 7 Feb 2017 16:15:53 -0300 Subject: [PATCH 04/18] Rest of the embed side config --- client/coral-embed-stream/src/index.js | 4 ++-- client/coral-framework/index.js | 4 ++-- client/coral-framework/services/PymConnection.js | 2 +- client/coral-framework/services/store.js | 2 +- client/coral-settings/containers/SettingsContainer.js | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/coral-embed-stream/src/index.js b/client/coral-embed-stream/src/index.js index f75146f0a..3dc69400c 100644 --- a/client/coral-embed-stream/src/index.js +++ b/client/coral-embed-stream/src/index.js @@ -2,8 +2,8 @@ import React from 'react'; import {render} from 'react-dom'; import {ApolloProvider} from 'react-apollo'; -import {client} from 'coral-framework/client'; -import store from 'coral-framework/store'; +import {client} from 'coral-framework/services/client'; +import store from 'coral-framework/services/store'; import Embed from './Embed'; diff --git a/client/coral-framework/index.js b/client/coral-framework/index.js index d2cd4197b..56a2e522c 100644 --- a/client/coral-framework/index.js +++ b/client/coral-framework/index.js @@ -1,5 +1,5 @@ -import store from './store'; -import pym from './PymConnection'; +import store from './services/store'; +import pym from './services/PymConnection'; import I18n from './modules/i18n/i18n'; import * as authActions from './actions/auth'; import * as assetActions from './actions/asset'; diff --git a/client/coral-framework/services/PymConnection.js b/client/coral-framework/services/PymConnection.js index ca592b824..6c492f2b8 100644 --- a/client/coral-framework/services/PymConnection.js +++ b/client/coral-framework/services/PymConnection.js @@ -1,4 +1,4 @@ -import Pym from '../../node_modules/pym.js'; +import Pym from '../../../node_modules/pym.js'; const pym = new Pym.Child({polling: 100}); export default pym; diff --git a/client/coral-framework/services/store.js b/client/coral-framework/services/store.js index 439569ee1..bf2735e45 100644 --- a/client/coral-framework/services/store.js +++ b/client/coral-framework/services/store.js @@ -1,6 +1,6 @@ import {createStore, combineReducers, applyMiddleware, compose} from 'redux'; import thunk from 'redux-thunk'; -import mainReducer from './reducers'; +import mainReducer from '../reducers'; import {client} from './client'; const middlewares = [ diff --git a/client/coral-settings/containers/SettingsContainer.js b/client/coral-settings/containers/SettingsContainer.js index f02badd80..cda0c657d 100644 --- a/client/coral-settings/containers/SettingsContainer.js +++ b/client/coral-settings/containers/SettingsContainer.js @@ -7,7 +7,7 @@ import {myCommentHistory} from 'coral-framework/graphql/queries'; import {saveBio} from 'coral-framework/actions/user'; import BioContainer from './BioContainer'; -import {link} from 'coral-framework/PymConnection'; +import {link} from 'coral-framework/services/PymConnection'; import NotLoggedIn from '../components/NotLoggedIn'; import {TabBar, Tab, TabContent, Spinner} from 'coral-ui'; import SettingsHeader from '../components/SettingsHeader'; From fab0c7e74d9ef3c821869338d437480cee7d41e0 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Wed, 8 Feb 2017 12:39:35 -0300 Subject: [PATCH 05/18] Mod Queue Working --- .../src/graphql/queries/modQueueQuery.graphql | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql index fda130510..5d69f6324 100644 --- a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql +++ b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql @@ -1,21 +1,27 @@ #import "../fragments/commentView.graphql" query ($asset_id: ID!) { - premod: comments(query: { - statuses: [PREMOD], + all: comments(query: { + statuses: [ACCEPTED, REJECTED, PREMOD], asset_id: $asset_id }) { ...commentView } + premod: comments(query: { + statuses: [PREMOD], + asset_id: $asset_id + }) { + ...commentView + } flagged: comments(query: { - action_type: FLAG, - asset_id: $asset_id + action_type: FLAG, + asset_id: $asset_id }) { ...commentView } - all: comments(query: { - statuses: [ACCEPTED], - asset_id: $asset_id + rejected: comments(query: { + statuses: [REJECTED], + asset_id: $asset_id }) { ...commentView } @@ -25,10 +31,4 @@ query ($asset_id: ID!) { }) { ...commentView } - rejected: comments(query: { - statuses: [REJECTED], - asset_id: $asset_id - }) { - ...commentView - } } From e234fa7e0bf76d02d5dc98a10fc23995954dc6c4 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Thu, 9 Feb 2017 07:00:39 -0300 Subject: [PATCH 06/18] Reducers refactor, working Moderate Queue --- .../ModerationQueue/ModerationContainer.js | 18 +- .../ModerationQueue/ModerationQueue.js | 33 ++-- .../ModerationQueue/components/Comment.js | 76 ++++++++ .../components/ModerationList.js | 0 .../ModerationQueue/components/styles.css | 184 ++++++++++++++++++ client/coral-admin/src/reducers/actions.js | 27 --- client/coral-admin/src/reducers/comments.js | 77 -------- client/coral-admin/src/reducers/index.js | 6 - client/coral-admin/src/reducers/settings.js | 81 ++++---- client/coral-admin/src/reducers/users.js | 28 --- client/coral-admin/src/services/client.js | 1 - 11 files changed, 325 insertions(+), 206 deletions(-) create mode 100644 client/coral-admin/src/containers/ModerationQueue/components/Comment.js create mode 100644 client/coral-admin/src/containers/ModerationQueue/components/ModerationList.js create mode 100644 client/coral-admin/src/containers/ModerationQueue/components/styles.css delete mode 100644 client/coral-admin/src/reducers/actions.js delete mode 100644 client/coral-admin/src/reducers/comments.js delete mode 100644 client/coral-admin/src/reducers/users.js diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index cd1a3e59f..9968f25d3 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -29,7 +29,6 @@ class ModerationContainer extends Component { componentWillMount() { const {toggleModal, singleView} = this.props; - this.props.fetchModerationQueueComments(); this.props.fetchSettings(); key('s', () => singleView()); @@ -54,7 +53,7 @@ class ModerationContainer extends Component { } render () { - const {data, moderation} = this.props; + const {data, moderation, settings} = this.props; if (data.loading) { return
; @@ -66,27 +65,16 @@ class ModerationContainer extends Component { ); } } -// ModerationContainer.contextTypes = { -// router: React.PropTypes.func.isRequired -// }; - const mapStateToProps = state => ({ moderation: state.moderation.toJS(), - comments: state.comments.toJS(), - settings: state.settings.toJS(), - users: state.users.toJS(), - actions: state.actions.toJS() + settings: state.settings.toJS() }); const mapDispatchToProps = dispatch => ({ diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js index 961642e84..f1f3514ea 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js @@ -1,22 +1,29 @@ -import React from 'react'; +import React, {PropTypes} from 'react'; -// import Comment from '../../components/Comment'; +import Comment from './components/Comment'; -export default (props) => { +const ModerationQueue = props => { return (
- {/* */} +
    { - props.data[props.activeTab].map((comment, i) => -
    - {comment.body} -
    - ) + props.data[props.activeTab].map((comment, i) => { + console.log(comment); + return ; + }) } +
); }; + +ModerationQueue.propTypes = { + data: PropTypes.object.isRequired +}; + +export default ModerationQueue; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js new file mode 100644 index 000000000..a06f052e4 --- /dev/null +++ b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js @@ -0,0 +1,76 @@ +import React, {PropTypes} from 'react'; +import timeago from 'timeago.js'; +import Linkify from 'react-linkify'; +import Highlighter from 'react-highlight-words'; + +import styles from './styles.css'; + +import {Icon} from 'coral-ui'; + +// import ActionButton from '../components/ActionButton'; + +import I18n from 'coral-framework/modules/i18n/i18n'; +import translations from '../../../translations.json'; + +const Comment = props => { + const links = linkify.getMatches(props.body); + + return ( +
  • +
    +
    + {props.user.name} + + {timeago().format(props.created_at || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))} + + {props.flagged ?

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

    : null} +
    +
    + {links ? Contains Link : null} +
    + {/* {props.modActions.map(*/} + {/* (action, i) =>*/} + {/* */} + {/* )}*/} +
    + {props.user.banned === 'banned' ? + + + {lang.t('comment.banned_user')} + + : null} +
    +
    +
    + + + + + +
    +
  • + ); +}; + +const linkStyles = { + backgroundColor: 'rgb(255, 219, 135)', + padding: '1px 2px' +}; + +const linkify = new Linkify(); +const lang = new I18n(translations); + +Comment.propTypes = { + user: PropTypes.object.isRequired +}; + +export default Comment; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/ModerationList.js b/client/coral-admin/src/containers/ModerationQueue/components/ModerationList.js new file mode 100644 index 000000000..e69de29bb diff --git a/client/coral-admin/src/containers/ModerationQueue/components/styles.css b/client/coral-admin/src/containers/ModerationQueue/components/styles.css new file mode 100644 index 000000000..fbcbce7d7 --- /dev/null +++ b/client/coral-admin/src/containers/ModerationQueue/components/styles.css @@ -0,0 +1,184 @@ + +@custom-media --big-viewport (min-width: 780px); + +.list { + padding: 8px 0; + list-style: none; + display: block; + + &.singleView .listItem { + display: none; + } + + &.singleView .listItem.activeItem { + display: block; + height: 100%; + font-size: 1.5em; + line-height: 1.5em; + border: none; + + .actions { + position: fixed; + bottom: 60px; + left: 25%; + margin: 0 auto; + display: flex; + justify-content: space-around; + width: 50%; + margin: 0; + } + + .actionButton { + transform: scale(1.4); + } + } +} + +.listItem { + border-bottom: 1px solid #e0e0e0; + font-size: 16px; + width: 100%; + max-width: 660px; + min-width: 400px; + margin: 0 auto; + padding: 16px 14px; + position: relative; + transition: box-shadow 200ms; + + + &:hover { + box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); + } + + &:last-child { + border-bottom: none; + } + + .sideActions { + position: absolute; + right: 0; + height: 100%; + top: 0; + padding: 40px 18px; + box-sizing: border-box; + } + + .itemHeader { + display: flex; + align-items: center; + justify-content: space-between; + + .author { + min-width: 230px; + display: flex; + align-items: center; + } + } + + .itemBody { + display: flex; + justify-content: space-between; + } + + .avatar { + margin-right: 16px; + height: 40px; + width: 40px; + border-radius: 50%; + background-color: #757575; + font-size: 40px; + color: #fff; + } + + .created { + color: #666; + font-size: 13px; + margin-left: 40px; + } + + .actionButton { + transform: scale(.8); + margin: 0; + } + + .body { + margin-top: 20px; + flex: 1; + font-size: 0.88em; + color: black; + } + + .flagged { + color: rgba(255, 0, 0, .5); + padding-top: 15px; + padding-left: 10px; + } + + .flagCount{ + font-size: 12px; + color: #d32f2f; + } + +} + +.empty { + color: #444; + margin-top: 50px; + text-align: center; +} + + +@media (--big-viewport) { + .listItem { + border: 1px solid #e0e0e0; + margin-bottom: 30px; + + &:last-child { + border-bottom: 1px solid #e0e0e0; + } + + &.activeItem { + border: 2px solid #333; + } + } + +} + +.hasLinks { + color: #f00; + text-align: right; + display: flex; + align-items: center; + + i { + margin-right: 5px; + } +} + +.banned { + color: #f00; + text-align: left; + display: flex; + align-items: center; + + i { + margin-right: 5px; + } +} + +.ban { + display: block; + text-align: center; + margin-top: 5px; +} + +.banButton { + width: 114px; + letter-spacing: 1px; + + i { + vertical-align: middle; + margin-right: 10px; + font-size: 14px; + } +} diff --git a/client/coral-admin/src/reducers/actions.js b/client/coral-admin/src/reducers/actions.js deleted file mode 100644 index 284e41c72..000000000 --- a/client/coral-admin/src/reducers/actions.js +++ /dev/null @@ -1,27 +0,0 @@ -import {Map, Set, fromJS} from 'immutable'; -import * as types from '../constants/actions'; - -const initialState = Map({ - ids: Set(), - byId: Map() -}); - -export default (state = initialState, action) => { - switch (action.type) { - case types.ACTIONS_MODERATION_QUEUE_FETCH_SUCCESS: return addActions(state, action); - default: - return state; - } -}; - -const addActions = (state, action) => { - - // Make ids that are unique by item_id and by action type - const typeId = (action) => `${action.action_type}_${action.item_id}`; - const ids = action.actions.map(action => typeId(action)); - const map = action.actions.reduce((memo, action) => { - memo[typeId(action)] = action; - return memo; - }, {}); - return state.set('byId', fromJS(map)).set('ids', new Set(ids)); -}; diff --git a/client/coral-admin/src/reducers/comments.js b/client/coral-admin/src/reducers/comments.js deleted file mode 100644 index 034016088..000000000 --- a/client/coral-admin/src/reducers/comments.js +++ /dev/null @@ -1,77 +0,0 @@ -import * as actions from '../constants/comments'; -import * as userActions from '../constants/users'; -import {Map, List, fromJS} from 'immutable'; - -/** - * Comments state is stored using 2 structures: - * - byId is a Map holding the comments using the item_id property as keys - * - ids is a List of item_id, this allows us to order and iterate easily - * since maps are unordered and some times we just need a list of things - */ - -const initialState = Map({ - byId: Map(), - ids: List(), - loading: false, - showBanUserDialog: false, - banUser: { - 'userName': '', - 'userId': '', - 'commentId': '' - } -}); - -// Handle the comment actions -export default (state = initialState, action) => { - switch (action.type) { - case actions.COMMENTS_MODERATION_QUEUE_FETCH_REQUEST: return state.set('loading', true); - case actions.COMMENTS_MODERATION_QUEUE_FETCH_SUCCESS: return replaceComments(action, state); - case actions.COMMENTS_MODERATION_QUEUE_FAILED: return state.set('loading', false); - case actions.COMMENT_STATUS_UPDATE_REQUEST: return updateStatus(state, action); - case actions.COMMENT_FLAG: return flag(state, action); - case actions.COMMENT_CREATE_SUCCESS: return addComment(state, action); - case actions.COMMENT_STREAM_FETCH_SUCCESS: return replaceComments(action, state); - case actions.SHOW_BANUSER_DIALOG: return setBanUser(state, true, action); - case actions.HIDE_BANUSER_DIALOG: return setBanUser(state, false, action); - case actions.USER_BAN_SUCCESS: return setBanUser(state, false, action); - case userActions.UPDATE_STATUS_SUCCESS: return setBanUser(state, false, action); - default: return state; - } -}; - -// hide or show the UI for the dialog confirming the ban -// set the user that is going to set and the comment that is the reason -const setBanUser = (state, showBanUser, action) => { - const banUser = {'userName': action.userName, 'userId': action.userId, 'commentId': action.commentId}; - return state.set('showBanUserDialog', showBanUser) - .set('banUser', banUser); -}; - -// Update a comment status -const updateStatus = (state, action) => { - const byId = state.get('byId'); - const data = byId.get(action.id).set('status', action.status.toLowerCase()); - return state.set('byId', byId.set(action.id, data)); -}; - -// Flag a comment -const flag = (state, action) => { - const byId = state.get('byId'); - const data = byId.get(action.id).set('flagged', true); - const comment = byId.get(action.id).set('data', data); - return state.set('byId', byId.set(action.id, comment)); -}; - -// Replace the comment list with a new one -const replaceComments = (action, state) => { - const comments = fromJS(action.comments.reduce((prev, curr) => { prev[curr.id] = curr; return prev; }, {})); - return state.set('byId', comments).set('loading', false) - .set('ids', List(comments.keys())); -}; - -// Add a new comment -const addComment = (state, action) => { - const comment = fromJS(action.comment); - return state.set('byId', state.get('byId').set(comment.get('item_id'), comment)) - .set('ids', state.get('ids').unshift(comment.get('item_id'))); -}; diff --git a/client/coral-admin/src/reducers/index.js b/client/coral-admin/src/reducers/index.js index 53256fac5..e58f5bf5a 100644 --- a/client/coral-admin/src/reducers/index.js +++ b/client/coral-admin/src/reducers/index.js @@ -1,19 +1,13 @@ import auth from './auth'; -import users from './users'; import assets from './assets'; -import actions from './actions'; -import comments from './comments'; import settings from './settings'; import community from './community'; import moderation from './moderation'; export default { auth, - users, assets, - actions, settings, - comments, community, moderation }; diff --git a/client/coral-admin/src/reducers/settings.js b/client/coral-admin/src/reducers/settings.js index 12b16d9ad..fa728b3ed 100644 --- a/client/coral-admin/src/reducers/settings.js +++ b/client/coral-admin/src/reducers/settings.js @@ -1,5 +1,5 @@ import {Map, List} from 'immutable'; -import * as types from '../actions/settings'; +import * as actions from '../actions/settings'; const initialState = Map({ settings: Map({ @@ -13,43 +13,46 @@ const initialState = Map({ fetchingSettings: false }); -// Handle the comment actions -export default (state = initialState, action) => { +export default function settings (state = initialState, action) { switch (action.type) { - case types.SETTINGS_LOADING: return state.set('fetchingSettings', true).set('fetchSettingsError', null); - case types.SETTINGS_RECEIVED: return updateSettings(state, action); - case types.SETTINGS_FETCH_ERROR: return settingsFetchFailed(state, action); - case types.SETTINGS_UPDATED: return updateSettings(state, action); - case types.SAVE_SETTINGS_LOADING: return state.set('fetchingSettings', true).set('saveSettingsError', null); - case types.SAVE_SETTINGS_SUCCESS: return saveComplete(state, action); - case types.SAVE_SETTINGS_FAILED: return settingsSaveFailed(state, action); - case types.WORDLIST_UPDATED: return updateWordlist(state, action); - default: return state; + case actions.SETTINGS_LOADING: + return state + .set('fetchingSettings', true) + .set('fetchSettingsError', null); + case actions.SETTINGS_RECEIVED: + return state.merge({ + fetchingSettings: null, + fetchSettingsError: null, + ...action.settings + }); + case actions.SETTINGS_FETCH_ERROR: + return state + .set('fetchingSettings', false) + .set('fetchSettingsError', action.error); + case actions.SETTINGS_UPDATED: + return state.merge({ + fetchingSettings: null, + fetchSettingsError: null, + ...action.settings + }); + case actions.SAVE_SETTINGS_LOADING: + return state + .set('fetchingSettings', true) + .set('saveSettingsError', null); + case actions.SAVE_SETTINGS_SUCCESS: + return state.merge({ + fetchingSettings: false, + fetchSettingsError: null, + ...action.settings + }); + case actions.SAVE_SETTINGS_FAILED: + return state + .set('fetchingSettings', false) + .set('fetchSettingsError', action.error); + case actions.WORDLIST_UPDATED: + return state + .setIn(['settings', 'wordlist', action.listName], action.list); + default: + return state; } -}; - -// only for updating top-level settings -const updateSettings = (state, action) => { - const s = state.set('fetchingSettings', false).set('fetchSettingsError', null); - const settings = s.get('settings').merge(action.settings); - return s.set('settings', settings); -}; - -// any nested settings must have a specialized setter -const updateWordlist = (state, action) => { - return state.setIn(['settings', 'wordlist', action.listName], action.list); -}; - -const saveComplete = (state, action) => { - const s = state.set('fetchingSettings', false).set('saveSettingsError', null); - const settings = s.get('settings').merge(action.settings); - return s.set('settings', settings); -}; - -const settingsFetchFailed = (state, action) => { - return state.set('fetchingSettings', false).set('fetchSettingsError', action.error); -}; - -const settingsSaveFailed = (state, action) => { - return state.set('fetchingSettings', false).set('fetchSettingsError', action.error); -}; +} diff --git a/client/coral-admin/src/reducers/users.js b/client/coral-admin/src/reducers/users.js deleted file mode 100644 index ef589c155..000000000 --- a/client/coral-admin/src/reducers/users.js +++ /dev/null @@ -1,28 +0,0 @@ -import {Map, List, fromJS} from 'immutable'; - -const initialState = Map({ - byId: Map(), - ids: List() -}); - -export default (state = initialState, action) => { - switch (action.type) { - case 'USERS_MODERATION_QUEUE_FETCH_SUCCESS': return replaceUsers(action, state); - case 'USER_STATUS_UPDATE': return updateUserStatus(state, action); - default: return state; - } -}; - -// Replace the comment list with a new one -const replaceUsers = (action, state) => { - const users = fromJS(action.users.reduce((prev, curr) => { prev[curr.id] = curr; return prev; }, {})); - return state.set('byId', users) - .set('ids', List(users.keys())); -}; - -// Update a user status -const updateUserStatus = (state, action) => { - const byId = state.get('byId'); - const data = byId.get(action.author_id).set('status', action.status.toLowerCase()); - return state.set('byId', byId.set(action.author_id, data)); -}; diff --git a/client/coral-admin/src/services/client.js b/client/coral-admin/src/services/client.js index b4a7a38df..40a539634 100644 --- a/client/coral-admin/src/services/client.js +++ b/client/coral-admin/src/services/client.js @@ -2,7 +2,6 @@ import ApolloClient, {addTypename} from 'apollo-client'; import getNetworkInterface from './transport'; export const client = new ApolloClient({ - connectToDevTools: true, queryTransformer: addTypename, dataIdFromObject: (result) => { if (result.id && result.__typename) { // eslint-disable-line no-underscore-dangle From e6087dfa2569f52aa52f0536541bcf2ee00cf3dc Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Thu, 9 Feb 2017 13:53:23 -0300 Subject: [PATCH 07/18] Go to Moderate from Stream --- client/coral-admin/src/components/ActionButton.js | 2 +- .../components/ModerationList.js => graphql/mutations/index.js} | 0 client/coral-plugin-moderation/ModerationLink.js | 0 client/coral-plugin-moderation/index.js | 0 client/coral-plugin-moderation/styles.css | 0 client/coral-plugin-moderation/translations.json | 0 6 files changed, 1 insertion(+), 1 deletion(-) rename client/coral-admin/src/{containers/ModerationQueue/components/ModerationList.js => graphql/mutations/index.js} (100%) create mode 100644 client/coral-plugin-moderation/ModerationLink.js create mode 100644 client/coral-plugin-moderation/index.js create mode 100644 client/coral-plugin-moderation/styles.css create mode 100644 client/coral-plugin-moderation/translations.json diff --git a/client/coral-admin/src/components/ActionButton.js b/client/coral-admin/src/components/ActionButton.js index 61de1c669..25c7772dc 100644 --- a/client/coral-admin/src/components/ActionButton.js +++ b/client/coral-admin/src/components/ActionButton.js @@ -1,5 +1,5 @@ import React from 'react'; -import styles from './ModerationList.css'; +import styles from './styles.css'; import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../translations.json'; import {FabButton, Button, Icon} from 'coral-ui'; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/ModerationList.js b/client/coral-admin/src/graphql/mutations/index.js similarity index 100% rename from client/coral-admin/src/containers/ModerationQueue/components/ModerationList.js rename to client/coral-admin/src/graphql/mutations/index.js diff --git a/client/coral-plugin-moderation/ModerationLink.js b/client/coral-plugin-moderation/ModerationLink.js new file mode 100644 index 000000000..e69de29bb diff --git a/client/coral-plugin-moderation/index.js b/client/coral-plugin-moderation/index.js new file mode 100644 index 000000000..e69de29bb diff --git a/client/coral-plugin-moderation/styles.css b/client/coral-plugin-moderation/styles.css new file mode 100644 index 000000000..e69de29bb diff --git a/client/coral-plugin-moderation/translations.json b/client/coral-plugin-moderation/translations.json new file mode 100644 index 000000000..e69de29bb From bc14b908ebcde65a5bf44d53dc9809e86f816b0b Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Fri, 10 Feb 2017 17:15:26 -0300 Subject: [PATCH 08/18] banUsers and moderate separate streams, not found assets etc --- client/coral-admin/src/actions/assets.js | 8 +- client/coral-admin/src/actions/comments.js | 9 --- client/coral-admin/src/actions/moderation.js | 9 +++ .../coral-admin/src/components/ui/Header.css | 1 + client/coral-admin/src/constants/assets.js | 3 + .../coral-admin/src/constants/moderation.js | 2 + .../ModerationQueue/ModerationContainer.js | 81 ++++++++----------- .../ModerationQueue/components/Comment.js | 17 +++- .../components/ModerationHeader.js | 25 ++++++ ...rationQueueHeader.js => ModerationMenu.js} | 14 +--- .../components/NotFoundAsset.js | 14 ++++ .../ModerationQueue/components/styles.css | 59 ++++++++++++++ .../src/containers/Streams/Streams.css | 6 ++ .../src/containers/Streams/Streams.js | 36 ++++----- .../src/containers/Streams/StreamsTable.js | 39 +++++++++ .../src/graphql/queries/assetsQuery.graphql | 6 ++ .../src/graphql/queries/modQueueQuery.graphql | 10 +-- client/coral-admin/src/reducers/assets.js | 22 +++-- graph/mutators/index.js | 2 + graph/mutators/user.js | 34 ++++++++ graph/resolvers/root_mutation.js | 3 + graph/typeDefs.graphql | 12 +++ models/user.js | 3 +- 23 files changed, 310 insertions(+), 105 deletions(-) create mode 100644 client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js rename client/coral-admin/src/containers/ModerationQueue/components/{ModerationQueueHeader.js => ModerationMenu.js} (80%) create mode 100644 client/coral-admin/src/containers/ModerationQueue/components/NotFoundAsset.js create mode 100644 client/coral-admin/src/containers/Streams/StreamsTable.js create mode 100644 client/coral-admin/src/graphql/queries/assetsQuery.graphql create mode 100644 graph/mutators/user.js diff --git a/client/coral-admin/src/actions/assets.js b/client/coral-admin/src/actions/assets.js index f431f1ad6..a4a3513a8 100644 --- a/client/coral-admin/src/actions/assets.js +++ b/client/coral-admin/src/actions/assets.js @@ -4,8 +4,10 @@ import { FETCH_ASSETS_FAILURE, UPDATE_ASSET_STATE_REQUEST, UPDATE_ASSET_STATE_SUCCESS, - UPDATE_ASSET_STATE_FAILURE + UPDATE_ASSET_STATE_FAILURE, + UPDATE_ASSETS } from '../constants/assets'; + import coralApi from '../../../coral-framework/helpers/response'; /** @@ -34,3 +36,7 @@ export const updateAssetState = (id, closedAt) => (dispatch) => { dispatch({type: UPDATE_ASSET_STATE_SUCCESS})) .catch(error => dispatch({type: UPDATE_ASSET_STATE_FAILURE, error})); }; + +export const updateAssets = assets => dispatch => { + dispatch({type: UPDATE_ASSETS, assets}); +}; diff --git a/client/coral-admin/src/actions/comments.js b/client/coral-admin/src/actions/comments.js index ac7af12fd..14f33bf36 100644 --- a/client/coral-admin/src/actions/comments.js +++ b/client/coral-admin/src/actions/comments.js @@ -101,12 +101,3 @@ export const flagComment = id => (dispatch, getState) => { dispatch({type: commentTypes.COMMENT_FLAG, id}); dispatch({type: 'COMMENT_UPDATE', comment: getState().comments.get('byId').get(id)}); }; - -// Dialog Actions -export const showBanUserDialog = (userId, userName, commentId) => { - return {type: commentTypes.SHOW_BANUSER_DIALOG, userId, userName, commentId}; -}; - -export const hideBanUserDialog = (showDialog) => { - return {type: commentTypes.HIDE_BANUSER_DIALOG, showDialog}; -}; diff --git a/client/coral-admin/src/actions/moderation.js b/client/coral-admin/src/actions/moderation.js index 425628abe..7d34a14ed 100644 --- a/client/coral-admin/src/actions/moderation.js +++ b/client/coral-admin/src/actions/moderation.js @@ -3,3 +3,12 @@ import * as actions from 'constants/moderation'; export const setActiveTab = activeTab => ({type: actions.SET_ACTIVE_TAB, activeTab}); export const toggleModal = open => ({type: actions.TOGGLE_MODAL, open}); export const singleView = () => ({type: actions.SINGLE_VIEW}); + +// Ban User Dialog +export const showBanUserDialog = (userId, userName, commentId) => { + return {type: actions.SHOW_BANUSER_DIALOG, userId, userName, commentId}; +}; + +export const hideBanUserDialog = (showDialog) => { + return {type: actions.HIDE_BANUSER_DIALOG, showDialog}; +}; diff --git a/client/coral-admin/src/components/ui/Header.css b/client/coral-admin/src/components/ui/Header.css index 8fbdedc0a..e6cf85d23 100644 --- a/client/coral-admin/src/components/ui/Header.css +++ b/client/coral-admin/src/components/ui/Header.css @@ -16,6 +16,7 @@ .rightPanel { position: absolute; + top: 0; right: 0; width: 170px; height: 100%; diff --git a/client/coral-admin/src/constants/assets.js b/client/coral-admin/src/constants/assets.js index 0a2ecf73c..20ec0a9c3 100644 --- a/client/coral-admin/src/constants/assets.js +++ b/client/coral-admin/src/constants/assets.js @@ -1,6 +1,9 @@ export const FETCH_ASSETS_REQUEST = 'FETCH_ASSETS_REQUEST'; export const FETCH_ASSETS_SUCCESS = 'FETCH_ASSETS_SUCCESS'; export const FETCH_ASSETS_FAILURE = 'FETCH_ASSETS_FAILURE'; + export const UPDATE_ASSET_STATE_REQUEST = 'UPDATE_ASSET_STATE_REQUEST'; export const UPDATE_ASSET_STATE_SUCCESS = 'UPDATE_ASSET_STATE_SUCCESS'; export const UPDATE_ASSET_STATE_FAILURE = 'UPDATE_ASSET_STATE_FAILURE'; + +export const UPDATE_ASSETS = 'UPDATE_ASSETS'; diff --git a/client/coral-admin/src/constants/moderation.js b/client/coral-admin/src/constants/moderation.js index 0fa51dc7b..10c6a7c4c 100644 --- a/client/coral-admin/src/constants/moderation.js +++ b/client/coral-admin/src/constants/moderation.js @@ -1,3 +1,5 @@ export const SET_ACTIVE_TAB = 'SET_ACTIVE_TAB'; export const TOGGLE_MODAL = 'TOGGLE_MODAL'; export const SINGLE_VIEW = 'SINGLE_VIEW'; +export const SHOW_BANUSER_DIALOG = 'SHOW_BANUSER_DIALOG'; +export const HIDE_BANUSER_DIALOG = 'HIDE_BANUSER_DIALOG'; diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index 277a0344b..ae78d1b8e 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -1,28 +1,20 @@ import React, {Component} from 'react'; -import key from 'keymaster'; import {connect} from 'react-redux'; import {compose} from 'react-apollo'; -import {Spinner} from 'coral-ui'; -import {withRouter} from 'react-router'; +import key from 'keymaster'; +import isEqual from 'lodash/isEqual'; import {modQueueQuery} from '../../graphql/queries'; -import { - updateStatus, - showBanUserDialog, - hideBanUserDialog, - fetchPremodQueue, - fetchRejectedQueue, - fetchFlaggedQueue, - fetchModerationQueueComments, -} from 'actions/comments'; - import {fetchSettings} from 'actions/settings'; -import {userStatusUpdate, sendNotificationEmail} from 'actions/users'; +import {updateAssets} from 'actions/assets'; import {setActiveTab, toggleModal, singleView} from 'actions/moderation'; +import {Spinner} from 'coral-ui'; import ModerationQueue from './ModerationQueue'; -import ModerationQueueHeader from './components/ModerationQueueHeader'; +import ModerationMenu from './components/ModerationMenu'; +import ModerationHeader from './components/ModerationHeader'; +import NotFoundAsset from './components/NotFoundAsset'; class ModerationContainer extends Component { @@ -42,29 +34,37 @@ class ModerationContainer extends Component { key.unbind('esc'); } - onTabClick = (activeTab) => { - const {setActiveTab} = this.props; - setActiveTab(activeTab); - } - - onClose = () => { - const {toggleModal} = this.props; - toggleModal(false); + componentWillReceiveProps(nextProps) { + const {updateAssets} = this.props; + if(!isEqual(nextProps.data.assets, this.props.data.assets)) { + updateAssets(nextProps.data.assets); + } } render () { - const {data, moderation, settings} = this.props; + const {data, moderation, settings, assets} = this.props; + const providedAssetId = this.props.params.id; + let asset; if (data.loading) { return
    ; } - const enablePremodTab = data.premod.length; + if (providedAssetId) { + asset = assets.find(asset => asset.id === this.props.params.id); + + if (!asset) { + return ; + } + } + + const enablePremodTab = !!data.premod.length; return (
    - + ({ moderation: state.moderation.toJS(), - settings: state.settings.toJS() + settings: state.settings.toJS(), + assets: state.assets.get('assets') }); const mapDispatchToProps = dispatch => ({ - setActiveTab: tab => dispatch(setActiveTab(tab)), - toggleModal: open => dispatch(toggleModal(open)), + onTabClick: activeTab => dispatch(setActiveTab(activeTab)), + toggleModal: toggle => dispatch(toggleModal(toggle)), + onClose: () => dispatch(toggleModal(false)), singleView: () => dispatch(singleView()), - - fetchSettings: () => dispatch(fetchSettings()), - fetchModerationQueueComments: () => dispatch(fetchModerationQueueComments()), - fetchPremodQueue: () => dispatch(fetchPremodQueue()), - fetchRejectedQueue: () => dispatch(fetchRejectedQueue()), - fetchFlaggedQueue: () => dispatch(fetchFlaggedQueue()), - showBanUserDialog: (userId, userName, commentId) => dispatch(showBanUserDialog(userId, userName, commentId)), - hideBanUserDialog: () => dispatch(hideBanUserDialog(false)), - userStatusUpdate: (status, userId, commentId) => dispatch(userStatusUpdate(status, userId, commentId)).then(() => { - dispatch(fetchModerationQueueComments()); - }), - suspendUser: (userId, subject, text) => dispatch(userStatusUpdate('suspended', userId)) - .then(() => dispatch(sendNotificationEmail(userId, subject, text))) - .then(() => dispatch(fetchModerationQueueComments())) - , - updateStatus: (action, comment) => dispatch(updateStatus(action, comment)) + updateAssets: assets => dispatch(updateAssets(assets)), + fetchSettings: () => dispatch(fetchSettings()) }); export default compose( connect(mapStateToProps, mapDispatchToProps), - withRouter, modQueueQuery )(ModerationContainer); diff --git a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js index 771d6fe55..881675d6a 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js @@ -14,7 +14,6 @@ import translations from '../../../translations.json'; const Comment = props => { const links = linkify.getMatches(props.body); - return (
  • @@ -50,14 +49,24 @@ const Comment = props => { : null}
  • + + {/*
    */} + {/* Article title */} + {/* Moderate this Article */} + {/*
    */} +
    - +

    - +

    - View context + + {/* */} + {/* View context */} + {/* */} + ); }; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js b/client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js new file mode 100644 index 000000000..474d8fd27 --- /dev/null +++ b/client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js @@ -0,0 +1,25 @@ +import React from 'react'; +import {Link} from 'react-router'; +import styles from './styles.css'; + +const ModerationHeader = props => ( +
    +
    + { + props.asset ? +
    + All Streams + {props.asset.title} + Select Stream +
    + : +
    + + All Streams + Select Stream +
    + } +
    +
    +); +export default ModerationHeader; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/ModerationQueueHeader.js b/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js similarity index 80% rename from client/coral-admin/src/containers/ModerationQueue/components/ModerationQueueHeader.js rename to client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js index 06540537b..803f62ecd 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/ModerationQueueHeader.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js @@ -5,7 +5,7 @@ import translations from '../../../translations.json'; const lang = new I18n(translations); -const ModerationQueueHeader = (props) => ( +const ModerationMenu = (props) => (
    ); -ModerationQueueHeader.propTypes = { +ModerationMenu.propTypes = { activeTab: PropTypes.string.isRequired, enablePremodTab: PropTypes.bool }; -export default ModerationQueueHeader; +export default ModerationMenu; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/NotFoundAsset.js b/client/coral-admin/src/containers/ModerationQueue/components/NotFoundAsset.js new file mode 100644 index 000000000..ffa1adfcd --- /dev/null +++ b/client/coral-admin/src/containers/ModerationQueue/components/NotFoundAsset.js @@ -0,0 +1,14 @@ +import React from 'react'; +import {Link} from 'react-router'; +import styles from './styles.css'; + +const NotFound = props => ( +
    +

    + The provided asset id {props.assetId} does not exist. + Go to Streams +

    +
    +); + +export default NotFound; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/styles.css b/client/coral-admin/src/containers/ModerationQueue/components/styles.css index a1e918459..cc40b03a3 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/styles.css +++ b/client/coral-admin/src/containers/ModerationQueue/components/styles.css @@ -1,3 +1,62 @@ +.notFound { + position: relative; + margin: 20px auto; + text-align: center; + padding: 68px 45px; + vertical-align: middle; + min-width: 500px; + + a { + color: rgb(244, 126, 107); + font-weight: 500; + + &.goToStreams { + position: absolute; + right: 10px; + bottom: 10px; + } + } +} + +.header { + background-color: #3949AB; + color: white; + margin-bottom: -1px; + + .moderateAsset { + a { + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; + color: white; + text-transform: capitalize; + font-weight: 500; + font-size: 15px; + letter-spacing: 1px; + transition: opacity 200ms; + opacity: 1; + + &:hover { + opacity: .8; + cursor: pointer; + } + + &:first-child { + text-align: left; + } + + &:nth-child(2) { + text-align: center; + } + + &:last-child { + text-align: right; + } + } + } +} + + @custom-media --big-viewport (min-width: 780px); .list { diff --git a/client/coral-admin/src/containers/Streams/Streams.css b/client/coral-admin/src/containers/Streams/Streams.css index 26fb4f74f..d8a75a4ce 100644 --- a/client/coral-admin/src/containers/Streams/Streams.css +++ b/client/coral-admin/src/containers/Streams/Streams.css @@ -55,6 +55,12 @@ border-left: none; border-right: none; + a { + color: rgb(44, 44, 44); + font-weight: 500; + text-decoration: none; + } + th { font-size: 1.1em; } diff --git a/client/coral-admin/src/containers/Streams/Streams.js b/client/coral-admin/src/containers/Streams/Streams.js index 1937f82c0..62f2f009e 100644 --- a/client/coral-admin/src/containers/Streams/Streams.js +++ b/client/coral-admin/src/containers/Streams/Streams.js @@ -4,14 +4,10 @@ import {connect} from 'react-redux'; import I18n from 'coral-framework/modules/i18n/i18n'; import {fetchAssets, updateAssetState} from '../../actions/assets'; import translations from '../../translations.json'; -import { - RadioGroup, - Radio, - Icon, - DataTable, - TableHeader -} from 'react-mdl'; -import Pager from 'coral-ui/components/Pager'; +import {Link} from 'react-router'; + +import {Pager, Icon} from 'coral-ui'; +import {DataTable, TableHeader, RadioGroup, Radio} from 'react-mdl'; const limit = 25; @@ -74,6 +70,8 @@ class Streams extends Component { } } + renderTitle = (title, {id}) => {title} + renderStatus = (closedAt, {id}) => { const closed = closedAt && new Date(closedAt).getTime() < Date.now(); const statusMenuOpen = this.state.statusMenus[id]; @@ -104,6 +102,9 @@ class Streams extends Component { render () { const {search, sort, filter} = this.state; const {assets} = this.props; + + const assetsIds = assets.ids.map((id) => assets.byId[id]); + return (
    @@ -142,16 +143,14 @@ class Streams extends Component {
    - assets.byId[id])}> - {lang.t('streams.article')} - - {lang.t('streams.pubdate')} - - - {lang.t('streams.status')} - + + {lang.t('streams.article')} + + {lang.t('streams.pubdate')} + + + {lang.t('streams.status')} + { assets: assets.toJS() }; }; + const mapDispatchToProps = (dispatch) => { return { fetchAssets: (...args) => { diff --git a/client/coral-admin/src/containers/Streams/StreamsTable.js b/client/coral-admin/src/containers/Streams/StreamsTable.js new file mode 100644 index 000000000..a801a3f7b --- /dev/null +++ b/client/coral-admin/src/containers/Streams/StreamsTable.js @@ -0,0 +1,39 @@ +import React from 'react'; +import translations from '../../translations.json'; +import I18n from 'coral-framework/modules/i18n/i18n'; +const lang = new I18n(translations); + +const StreamsTable = props => ( + + + + + + + + + + {props.rows.map((row, i)=> ( + + + + + + ))} + +
    + {lang.t('streams.article')} + + {lang.t('streams.pubdate')} + + {lang.t('streams.status')} +
    + {row.title} + + {row.publication_date} + + {lang.t('streams.status')} +
    +); + +export default StreamsTable; diff --git a/client/coral-admin/src/graphql/queries/assetsQuery.graphql b/client/coral-admin/src/graphql/queries/assetsQuery.graphql new file mode 100644 index 000000000..37950692d --- /dev/null +++ b/client/coral-admin/src/graphql/queries/assetsQuery.graphql @@ -0,0 +1,6 @@ +query Assets { + assets { + id + title + } +} diff --git a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql index 5d69f6324..a22a6c808 100644 --- a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql +++ b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql @@ -1,6 +1,6 @@ #import "../fragments/commentView.graphql" -query ($asset_id: ID!) { +query ModQueue ($asset_id: ID!) { all: comments(query: { statuses: [ACCEPTED, REJECTED, PREMOD], asset_id: $asset_id @@ -25,10 +25,8 @@ query ($asset_id: ID!) { }) { ...commentView } - account: comments(query: { - statuses: [ACCEPTED], - asset_id: $asset_id - }) { - ...commentView + assets: assets { + id + title } } diff --git a/client/coral-admin/src/reducers/assets.js b/client/coral-admin/src/reducers/assets.js index 77d0bf081..c9a82f1c5 100644 --- a/client/coral-admin/src/reducers/assets.js +++ b/client/coral-admin/src/reducers/assets.js @@ -1,20 +1,26 @@ import {Map, List, fromJS} from 'immutable'; -import {FETCH_ASSETS_SUCCESS, UPDATE_ASSET_STATE_REQUEST} from '../constants/assets'; +import * as actions from '../constants/assets'; const initialState = Map({ byId: Map(), - ids: List() + ids: List(), + assets: List() }); -export default (state = initialState, action) => { +export default function assets (state = initialState, action) { switch (action.type) { - case FETCH_ASSETS_SUCCESS: + case actions.FETCH_ASSETS_SUCCESS: return replaceAssets(action, state); - case UPDATE_ASSET_STATE_REQUEST: - return state.setIn(['byId', action.id, 'closedAt'], action.closedAt); - default: return state; + case actions.UPDATE_ASSET_STATE_REQUEST: + return state + .setIn(['byId', action.id, 'closedAt'], action.closedAt); + case actions.UPDATE_ASSETS: + return state + .set('assets', List(action.assets)); + default: + return state; } -}; +} const replaceAssets = (action, state) => { const assets = fromJS(action.assets.reduce((prev, curr) => { prev[curr.id] = curr; return prev; }, {})); diff --git a/graph/mutators/index.js b/graph/mutators/index.js index 58d0ed62c..b799cf83d 100644 --- a/graph/mutators/index.js +++ b/graph/mutators/index.js @@ -2,6 +2,7 @@ const _ = require('lodash'); const Comment = require('./comment'); const Action = require('./action'); +const User = require('./user'); module.exports = (context) => { @@ -9,6 +10,7 @@ module.exports = (context) => { return _.merge(...[ Comment, Action, + User, ].map((mutators) => { // Each set of mutators is a function which takes the context. diff --git a/graph/mutators/user.js b/graph/mutators/user.js new file mode 100644 index 000000000..f693195d6 --- /dev/null +++ b/graph/mutators/user.js @@ -0,0 +1,34 @@ +const UsersService = require('../../services/users'); + +const setUserStatus = ({user}, {id, status}) => { + console.log('------as-d-asd-a-sads-a-sad-dsa-----'); + console.log('user', user); + console.log('id', id); + console.log('status', status); + + return UsersService.setStatus(id, status) + .then((user) => { + console.log('result', user); + return user; + }); +}; + +module.exports = (context) => { + + // TODO: refactor to something that'll return an error in the event an attempt + // is made to mutate state while not logged in. There's got to be a better way + // to do this. + if (context.user && context.user.can('mutation:setUserStatus')) { + return { + User: { + setUserStatus: (action) => setUserStatus(context, action) + } + }; + } + + return { + User: { + setUserStatus: () => {}, + } + }; +}; diff --git a/graph/resolvers/root_mutation.js b/graph/resolvers/root_mutation.js index ef46b839a..a1d25189b 100644 --- a/graph/resolvers/root_mutation.js +++ b/graph/resolvers/root_mutation.js @@ -8,6 +8,9 @@ const RootMutation = { deleteAction(_, {id}, {mutators: {Action}}) { return Action.delete({id}); }, + setUserStatus(_, {id, status}, {mutators: {User}}) { + return User.setUserStatus({id, status}); + } }; module.exports = RootMutation; diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 8369b93c6..a655a77f3 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -61,6 +61,9 @@ type User { # returns all comments based on a query. comments(query: CommentsQuery): [Comment] + + # returns user status + status: USER_STATUS } type Comment { @@ -177,6 +180,13 @@ enum COMMENT_STATUS { PREMOD } +enum USER_STATUS { + ACTIVE + BANNED + PENDING + APPROVED +} + type RootQuery { # retrieves site wide settings and defaults. @@ -216,6 +226,8 @@ type RootMutation { # delete an action based on the action id. deleteAction(id: ID!): Boolean + # sets user status + setUserStatus(id: ID!, status: USER_STATUS!): Boolean } schema { diff --git a/models/user.js b/models/user.js index 06dc3c7d1..0194ba5f3 100644 --- a/models/user.js +++ b/models/user.js @@ -147,7 +147,8 @@ const USER_GRAPH_OPERATIONS = [ 'mutation:createComment', 'mutation:createAction', 'mutation:deleteAction', - 'mutation:editName' + 'mutation:editName', + 'mutation:setUserStatus' ]; /** From 894fec9fb6412a006411d7b51e076018e544bb47 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Fri, 10 Feb 2017 17:55:51 -0300 Subject: [PATCH 09/18] adding context for moderation --- .../src/components/ActionButton.js | 38 ++++++++---------- .../src/components/ModerationList.css | 11 +++--- .../ModerationQueue/ModerationContainer.js | 5 +-- .../ModerationQueue/ModerationQueue.js | 2 + .../ModerationQueue/components/Comment.js | 38 +++++++----------- .../ModerationQueue/components/styles.css | 22 +++++++++++ .../src/containers/Streams/StreamsTable.js | 39 ------------------- .../src/graphql/fragments/commentView.graphql | 5 +++ graph/mutators/user.js | 10 +---- 9 files changed, 69 insertions(+), 101 deletions(-) delete mode 100644 client/coral-admin/src/containers/Streams/StreamsTable.js diff --git a/client/coral-admin/src/components/ActionButton.js b/client/coral-admin/src/components/ActionButton.js index 61de1c669..09abdd266 100644 --- a/client/coral-admin/src/components/ActionButton.js +++ b/client/coral-admin/src/components/ActionButton.js @@ -4,43 +4,39 @@ import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../translations.json'; import {FabButton, Button, Icon} from 'coral-ui'; -const ActionButton = ({option, type, comment = {}, user, menuOptionsMap, onClickAction, onClickShowBanDialog}) => -{ - const banned = user.status === 'BANNED'; +const ActionButton = ({type, user}) => { + const menuOptionsMap = { + 'reject': {status: 'REJECTED', icon: 'close', key: 'r'}, + 'approve': {status: 'ACCEPTED', icon: 'done', key: 't'}, + 'flag': {status: 'FLAGGED', icon: 'flag', filter: 'Untouched'}, + 'ban': {status: 'BANNED', icon: 'not interested'} + }; - if (option === 'flag' && (type === 'USERS' || comment.status || comment.flagged === true)) { - return null; - } - if (option === 'ban') { + if (type === 'ban') { return (
    ); } - const menuOption = menuOptionsMap[option]; - const action = { - item_type: type, - item_id: type === 'COMMENTS' ? comment.id : user.id - }; + return ( onClickAction(menuOption.status, type === 'COMMENTS' ? comment : user, action)} + className={`${type} ${styles.actionButton}`} + cStyle={type} + icon={menuOptionsMap[type].icon} + onClick={() => {}} /> ); + }; export default ActionButton; diff --git a/client/coral-admin/src/components/ModerationList.css b/client/coral-admin/src/components/ModerationList.css index fbcbce7d7..140f4bc8f 100644 --- a/client/coral-admin/src/components/ModerationList.css +++ b/client/coral-admin/src/components/ModerationList.css @@ -96,11 +96,6 @@ margin-left: 40px; } - .actionButton { - transform: scale(.8); - margin: 0; - } - .body { margin-top: 20px; flex: 1; @@ -182,3 +177,9 @@ font-size: 14px; } } + + +.actionButton { + transform: scale(.8); + margin: 0; +} diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index ae78d1b8e..6b0ec8827 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -59,7 +59,6 @@ class ModerationContainer extends Component { } const enablePremodTab = !!data.premod.length; - return (
    @@ -68,9 +67,9 @@ class ModerationContainer extends Component { enablePremodTab={enablePremodTab} {...moderation} />
    diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js index ed1bc4dce..47d3d6745 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js @@ -7,11 +7,13 @@ const actionsMap = { }; const ModerationQueue = props => { + console.log(props); return (
      { props.data[props.activeTab].map((comment, i) => { + console.log(props.asset); return { const links = linkify.getMatches(props.body); return (
    • + className={`mdl-card mdl-shadow--2dp ${styles.Comment} ${styles.listItem} ${props.isActive && !props.hideActive ? styles.activeItem : ''}`}>
    • ); }; @@ -80,7 +69,8 @@ const linkify = new Linkify(); const lang = new I18n(translations); Comment.propTypes = { - user: PropTypes.object.isRequired + user: PropTypes.object.isRequired, + asset: PropTypes.object.isRequired }; export default Comment; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/styles.css b/client/coral-admin/src/containers/ModerationQueue/components/styles.css index cc40b03a3..f87efec24 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/styles.css +++ b/client/coral-admin/src/containers/ModerationQueue/components/styles.css @@ -248,3 +248,25 @@ font-size: 14px; } } + + +.Comment { + .moderateArticle { + padding: 10px 0px; + a { + display: block; + color: #679af3; + text-decoration: none; + font-size: 1em; + font-weight: 500; + letter-spacing: .5px; + font-size: .8em; + + &:hover { + text-decoration: underline; + opacity: .9; + cursor: pointer; + } + } + } +} diff --git a/client/coral-admin/src/containers/Streams/StreamsTable.js b/client/coral-admin/src/containers/Streams/StreamsTable.js deleted file mode 100644 index a801a3f7b..000000000 --- a/client/coral-admin/src/containers/Streams/StreamsTable.js +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; -import translations from '../../translations.json'; -import I18n from 'coral-framework/modules/i18n/i18n'; -const lang = new I18n(translations); - -const StreamsTable = props => ( - - - - - - - - - - {props.rows.map((row, i)=> ( - - - - - - ))} - -
      - {lang.t('streams.article')} - - {lang.t('streams.pubdate')} - - {lang.t('streams.status')} -
      - {row.title} - - {row.publication_date} - - {lang.t('streams.status')} -
      -); - -export default StreamsTable; diff --git a/client/coral-admin/src/graphql/fragments/commentView.graphql b/client/coral-admin/src/graphql/fragments/commentView.graphql index 29f9b3bfe..ced2f89fc 100644 --- a/client/coral-admin/src/graphql/fragments/commentView.graphql +++ b/client/coral-admin/src/graphql/fragments/commentView.graphql @@ -6,6 +6,11 @@ fragment commentView on Comment { user { id name: displayName + status + } + asset { + id + title } actions { type: action_type diff --git a/graph/mutators/user.js b/graph/mutators/user.js index f693195d6..7c48744d2 100644 --- a/graph/mutators/user.js +++ b/graph/mutators/user.js @@ -1,16 +1,8 @@ const UsersService = require('../../services/users'); const setUserStatus = ({user}, {id, status}) => { - console.log('------as-d-asd-a-sads-a-sad-dsa-----'); - console.log('user', user); - console.log('id', id); - console.log('status', status); - return UsersService.setStatus(id, status) - .then((user) => { - console.log('result', user); - return user; - }); + .then(res => res); }; module.exports = (context) => { From e2ebac71760534ca8db3e06db9460f4f70230473 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Mon, 13 Feb 2017 14:32:48 -0300 Subject: [PATCH 10/18] adds new mutations: setCommentStatus --- .../src/components/ActionButton.js | 3 +-- .../ModerationQueue/ModerationContainer.js | 11 ++++++-- .../ModerationQueue/ModerationQueue.js | 2 ++ .../ModerationQueue/components/Comment.js | 10 ++++--- .../src/graphql/mutations/index.js | 24 +++++++++++++++++ .../mutations/setCommentStatus.graphql | 3 +++ .../coral-admin/src/graphql/queries/index.js | 3 +-- .../src/graphql/queries/modQueueQuery.graphql | 2 +- graph/mutators/comment.js | 20 +++++++++++--- graph/resolvers/root_mutation.js | 3 +++ graph/typeDefs.graphql | 5 +++- models/user.js | 3 ++- services/comments.js | 27 +++++++++++++++++++ 13 files changed, 101 insertions(+), 15 deletions(-) create mode 100644 client/coral-admin/src/graphql/mutations/setCommentStatus.graphql diff --git a/client/coral-admin/src/components/ActionButton.js b/client/coral-admin/src/components/ActionButton.js index 0415b2529..1d1d4d9d0 100644 --- a/client/coral-admin/src/components/ActionButton.js +++ b/client/coral-admin/src/components/ActionButton.js @@ -14,10 +14,9 @@ const ActionButton = ({type = '', user, ...props}) => { className={`${type.toLowerCase()} ${styles.actionButton}`} cStyle={type.toLowerCase()} icon={menuActionsMap[type].icon} - onClick={() => {}} + onClick={type === 'APPROVE' ? props.acceptComment : props.rejectComment} /> ); }; export default ActionButton; - diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index 36836ea0d..44d64219e 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -5,7 +5,7 @@ import key from 'keymaster'; import isEqual from 'lodash/isEqual'; import {modQueueQuery} from '../../graphql/queries'; -import {banUser} from '../../graphql/mutations'; +import {banUser, setCommentStatus} from '../../graphql/mutations'; import {fetchSettings} from 'actions/settings'; import {updateAssets} from 'actions/assets'; @@ -52,6 +52,11 @@ class ModerationContainer extends Component { return
      ; } + if (data.error) { + console.log(data); + return
      Error
      ; + } + if (providedAssetId) { asset = assets.find(asset => asset.id === this.props.params.id); @@ -61,7 +66,6 @@ class ModerationContainer extends Component { } const enablePremodTab = !!data.premod.length; - console.log(props.banUser); return (
      @@ -75,6 +79,8 @@ class ModerationContainer extends Component { enablePremodTab={enablePremodTab} suspectWords={settings.wordlist.suspect} showBanUserDialog={props.showBanUserDialog} + acceptComment={props.acceptComment} + rejectComment={props.rejectComment} /> ({ export default compose( connect(mapStateToProps, mapDispatchToProps), + setCommentStatus, modQueueQuery, banUser )(ModerationContainer); diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js index 41f0f80bd..322452489 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js @@ -15,6 +15,8 @@ const ModerationQueue = props => { suspectWords={props.suspectWords} actions={actionsMap[comment.status]} showBanUserDialog={props.showBanUserDialog} + acceptComment={props.acceptComment} + rejectComment={props.rejectComment} {...comment} />; }) diff --git a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js index 7467aa677..eae2f60b2 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js @@ -13,7 +13,7 @@ const lang = new I18n(translations); import I18n from 'coral-framework/modules/i18n/i18n'; import translations from 'coral-admin/src/translations.json'; -const Comment = props => { +const Comment = ({actions = [], ...props}) => { const links = linkify.getMatches(props.body); return ( @@ -30,8 +30,12 @@ const Comment = props => {
      {links ? Contains Link : null}
      - {props.actions.map((action, i) => - + props.acceptComment({commentId: props.id})} + rejectComment={() => props.rejectComment({commentId: props.id})} showBanUserDialog={() => props.showBanUserDialog(props.user, props.id)} /> )} diff --git a/client/coral-admin/src/graphql/mutations/index.js b/client/coral-admin/src/graphql/mutations/index.js index 372f87e56..5e1503062 100644 --- a/client/coral-admin/src/graphql/mutations/index.js +++ b/client/coral-admin/src/graphql/mutations/index.js @@ -2,6 +2,7 @@ // rejectComment import {graphql} from 'react-apollo'; import SET_USER_STATUS from './setUserStatus.graphql'; +import SET_COMMENT_STATUS from './setCommentStatus.graphql'; export const banUser = graphql(SET_USER_STATUS, { props: ({mutate}) => ({ @@ -14,3 +15,26 @@ export const banUser = graphql(SET_USER_STATUS, { }); }}), }); + +export const setCommentStatus = graphql(SET_COMMENT_STATUS, { + props: ({mutate}) => ({ + acceptComment: ({commentId}) => { + return mutate({ + variables: { + commentId, + status: 'ACCEPTED' + }, + refetchQueries: ['ModQueue'] + }); + }, + rejectComment: ({commentId}) => { + return mutate({ + variables: { + commentId, + status: 'REJECTED' + }, + refetchQueries: ['ModQueue'] + }); + } + }) +}); diff --git a/client/coral-admin/src/graphql/mutations/setCommentStatus.graphql b/client/coral-admin/src/graphql/mutations/setCommentStatus.graphql new file mode 100644 index 000000000..cfcc76421 --- /dev/null +++ b/client/coral-admin/src/graphql/mutations/setCommentStatus.graphql @@ -0,0 +1,3 @@ +mutation setCommentStatus($commentId: ID!, $status: COMMENT_STATUS!){ + setCommentStatus(id: $commentId, status: $status) +} diff --git a/client/coral-admin/src/graphql/queries/index.js b/client/coral-admin/src/graphql/queries/index.js index ab7d61b05..fc59f8f84 100644 --- a/client/coral-admin/src/graphql/queries/index.js +++ b/client/coral-admin/src/graphql/queries/index.js @@ -2,8 +2,7 @@ import {graphql} from 'react-apollo'; import MOD_QUEUE_QUERY from './modQueueQuery.graphql'; export const modQueueQuery = graphql(MOD_QUEUE_QUERY, { - options: (props) => { - const {id = ''} = props.params; + options: ({params: {id = ''}}) => { return { variables: { asset_id: id diff --git a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql index a22a6c808..38ce9af23 100644 --- a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql +++ b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql @@ -2,7 +2,7 @@ query ModQueue ($asset_id: ID!) { all: comments(query: { - statuses: [ACCEPTED, REJECTED, PREMOD], + statuses: [REJECTED, PREMOD], asset_id: $asset_id }) { ...commentView diff --git a/graph/mutators/comment.js b/graph/mutators/comment.js index 6214c9df8..844b47d3c 100644 --- a/graph/mutators/comment.js +++ b/graph/mutators/comment.js @@ -162,22 +162,36 @@ const createPublicComment = (context, commentInput) => { })); }; +/** + * Sets the status of a comment + * @param {String} comment comment in graphql context + * @param {String} id identifier of the comment (uuid) + * @param {String} status the new status of the comment + */ + +const setCommentStatus = ({comment}, {id, status}) => { + return CommentsService.setStatus(id, status) + .then(res => res); +}; + module.exports = (context) => { // TODO: refactor to something that'll return an error in the event an attempt // is made to mutate state while not logged in. There's got to be a better way // to do this. - if (context.user && context.user.can('mutation:createComment')) { + if (context.user && context.user.can('mutation:createComment', 'mutation:setUserStatus')) { return { Comment: { - create: (comment) => createPublicComment(context, comment) + create: (comment) => createPublicComment(context, comment), + setCommentStatus: (action) => setCommentStatus(context, action) } }; } return { Comment: { - create: () => Promise.reject(errors.ErrNotAuthorized) + create: () => Promise.reject(errors.ErrNotAuthorized), + setCommentStatus: () => Promise.reject(errors.ErrNotAuthorized) } }; }; diff --git a/graph/resolvers/root_mutation.js b/graph/resolvers/root_mutation.js index dba537879..1f10d51d9 100644 --- a/graph/resolvers/root_mutation.js +++ b/graph/resolvers/root_mutation.js @@ -29,6 +29,9 @@ const RootMutation = { }, setUserStatus(_, {id, status}, {mutators: {User}}) { return User.setUserStatus({id, status}); + }, + setCommentStatus(_, {id, status}, {mutators: {Comment}}) { + return Comment.setCommentStatus({id, status}); } }; diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index d91dbbdd7..325fe3b5d 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -491,8 +491,11 @@ type RootMutation { # Delete an action based on the action id. deleteAction(id: ID!): DeleteActionResponse - # Sets user status + # Sets User status setUserStatus(id: ID!, status: USER_STATUS!): Boolean + + # Sets Comment status + setCommentStatus(id: ID!, status: COMMENT_STATUS!): Boolean } ################################################################################ diff --git a/models/user.js b/models/user.js index 0194ba5f3..bd18aa4c5 100644 --- a/models/user.js +++ b/models/user.js @@ -148,7 +148,8 @@ const USER_GRAPH_OPERATIONS = [ 'mutation:createAction', 'mutation:deleteAction', 'mutation:editName', - 'mutation:setUserStatus' + 'mutation:setUserStatus', + 'mutation:setCommentStatus' ]; /** diff --git a/services/comments.js b/services/comments.js index fc05341cb..bcc534c51 100644 --- a/services/comments.js +++ b/services/comments.js @@ -7,6 +7,12 @@ const ALLOWED_TAGS = [ {name: 'STAFF'} ]; +const STATUSES = [ + 'ACCEPTED', + 'REJECTED', + 'PREMOD', +]; + module.exports = class CommentsService { /** @@ -249,4 +255,25 @@ module.exports = class CommentsService { return CommentModel.find(query); } + + /** + * Sets Comment Status + * @param {String} id identifier of the comment (uuid) + * @param {String} status the new status of the comment + * @return {Promise} + */ + + static setStatus(id, status) { + + // Check to see if the comment status is in the allowable set of statuses. + if (STATUSES.indexOf(status) === -1) { + + // Comment status is not supported! Error out here. + return Promise.reject(new Error(`status ${status} is not supported`)); + } + + return CommentModel.update({id}, { + $set: {status} + }); + } }; From 594b87208aff208a10e9048ac9bdcb1c4411ef73 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Mon, 13 Feb 2017 16:00:55 -0300 Subject: [PATCH 11/18] GraphQL Review and Responses --- .../ModerationQueue/ModerationContainer.js | 3 ++- .../ModerationQueue/ModerationQueue.js | 2 +- .../ModerationQueue/components/Comment.js | 27 ++++++++----------- graph/mutators/comment.js | 21 ++++++++------- graph/mutators/user.js | 3 ++- graph/resolvers/root_mutation.js | 4 +-- graph/typeDefs.graphql | 20 ++++++++++++-- models/user.js | 4 +++ 8 files changed, 51 insertions(+), 33 deletions(-) diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index 44d64219e..1f359a1f7 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -72,7 +72,8 @@ class ModerationContainer extends Component { + activeTab={moderation.activeTab} + /> { return ; }) } diff --git a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js index eae2f60b2..7a7967aaf 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js @@ -1,4 +1,4 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; import timeago from 'timeago.js'; import Linkify from 'react-linkify'; import Highlighter from 'react-highlight-words'; @@ -14,16 +14,16 @@ import I18n from 'coral-framework/modules/i18n/i18n'; import translations from 'coral-admin/src/translations.json'; const Comment = ({actions = [], ...props}) => { - const links = linkify.getMatches(props.body); + const links = linkify.getMatches(props.comment.body); return (
    • - {props.user.name} + {props.comment.user.name} - {timeago().format(props.created_at || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))} + {timeago().format(props.comment.created_at || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))} {props.flagged ?

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

      : null}
      @@ -33,14 +33,14 @@ const Comment = ({actions = [], ...props}) => { {actions.map((action, i) => props.acceptComment({commentId: props.id})} - rejectComment={() => props.rejectComment({commentId: props.id})} - showBanUserDialog={() => props.showBanUserDialog(props.user, props.id)} + user={props.comment.user} + acceptComment={() => props.acceptComment({commentId: props.comment.id})} + rejectComment={() => props.rejectComment({commentId: props.comment.id})} + showBanUserDialog={() => props.showBanUserDialog(props.comment.user, props.comment.id)} /> )}
      - {props.user.banned === 'banned' ? + {props.comment.user.banned === 'banned' ? {lang.t('comment.banned_user')} @@ -50,13 +50,13 @@ const Comment = ({actions = [], ...props}) => {
    • - {props.asset.title} Moderate Article + {props.comment.asset.title} Moderate Article

      - +

      @@ -74,9 +74,4 @@ const linkStyles = { padding: '1px 2px' }; -Comment.propTypes = { - user: PropTypes.object.isRequired, - asset: PropTypes.object.isRequired -}; - export default Comment; diff --git a/graph/mutators/comment.js b/graph/mutators/comment.js index 844b47d3c..98891953c 100644 --- a/graph/mutators/comment.js +++ b/graph/mutators/comment.js @@ -179,19 +179,20 @@ module.exports = (context) => { // TODO: refactor to something that'll return an error in the event an attempt // is made to mutate state while not logged in. There's got to be a better way // to do this. - if (context.user && context.user.can('mutation:createComment', 'mutation:setUserStatus')) { - return { - Comment: { - create: (comment) => createPublicComment(context, comment), - setCommentStatus: (action) => setCommentStatus(context, action) - } - }; - } - - return { + let mutators = { Comment: { create: () => Promise.reject(errors.ErrNotAuthorized), setCommentStatus: () => Promise.reject(errors.ErrNotAuthorized) } }; + + if (context.user && context.user.can('mutation:createComment')) { + mutators.Comment.create = (comment) => createPublicComment(context, comment); + } + + if (context.user && context.user.can('mutation:setCommentStatus')) { + mutators.Comment.setCommentStatus = (action) => setCommentStatus(context, action); + } + + return mutators; }; diff --git a/graph/mutators/user.js b/graph/mutators/user.js index 7c48744d2..2c43f11be 100644 --- a/graph/mutators/user.js +++ b/graph/mutators/user.js @@ -1,3 +1,4 @@ +const errors = require('../../errors'); const UsersService = require('../../services/users'); const setUserStatus = ({user}, {id, status}) => { @@ -20,7 +21,7 @@ module.exports = (context) => { return { User: { - setUserStatus: () => {}, + setUserStatus: () => Promise.reject(errors.ErrNotAuthorized) } }; }; diff --git a/graph/resolvers/root_mutation.js b/graph/resolvers/root_mutation.js index 1f10d51d9..4285f900d 100644 --- a/graph/resolvers/root_mutation.js +++ b/graph/resolvers/root_mutation.js @@ -28,10 +28,10 @@ const RootMutation = { return wrapResponse(null)(Action.delete({id})); }, setUserStatus(_, {id, status}, {mutators: {User}}) { - return User.setUserStatus({id, status}); + return wrapResponse(null)(User.setUserStatus({id, status})); }, setCommentStatus(_, {id, status}, {mutators: {Comment}}) { - return Comment.setCommentStatus({id, status}); + return wrapResponse(null)(Comment.setCommentStatus({id, status})); } }; diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 325fe3b5d..b5c8539ad 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -476,6 +476,22 @@ type DeleteActionResponse implements Response { errors: [UserError] } +# SetUserStatusResponse is the response returned with possibly some errors +# relating to the delete action attempt. +type SetUserStatusResponse implements Response { + + # An array of errors relating to the mutation that occured. + errors: [UserError] +} + +# SetCommentStatusResponse is the response returned with possibly some errors +# relating to the delete action attempt. +type SetCommentStatusResponse implements Response { + + # An array of errors relating to the mutation that occured. + errors: [UserError] +} + # All mutations for the application are defined on this object. type RootMutation { @@ -492,10 +508,10 @@ type RootMutation { deleteAction(id: ID!): DeleteActionResponse # Sets User status - setUserStatus(id: ID!, status: USER_STATUS!): Boolean + setUserStatus(id: ID!, status: USER_STATUS!): SetUserStatusResponse # Sets Comment status - setCommentStatus(id: ID!, status: COMMENT_STATUS!): Boolean + setCommentStatus(id: ID!, status: COMMENT_STATUS!): SetCommentStatusResponse } ################################################################################ diff --git a/models/user.js b/models/user.js index bd18aa4c5..7e04386cd 100644 --- a/models/user.js +++ b/models/user.js @@ -165,6 +165,10 @@ UserSchema.method('can', function(...actions) { return false; } + if (actions.some((action) => action === 'mutation:setUserStatus' || action === 'mutation:setCommentStatus') && !this.hasRoles('ADMIN')) { + return false; + } + return true; }); From 0403c46c6ce4eb96caf1e8dca2d92f57e2eed011 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Mon, 13 Feb 2017 16:13:03 -0300 Subject: [PATCH 12/18] Adding errors response --- client/coral-admin/src/graphql/mutations/index.js | 2 -- .../src/graphql/mutations/setCommentStatus.graphql | 6 +++++- .../coral-admin/src/graphql/mutations/setUserStatus.graphql | 6 +++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/client/coral-admin/src/graphql/mutations/index.js b/client/coral-admin/src/graphql/mutations/index.js index 5e1503062..fe3a1faf9 100644 --- a/client/coral-admin/src/graphql/mutations/index.js +++ b/client/coral-admin/src/graphql/mutations/index.js @@ -1,5 +1,3 @@ -// acceptComment -// rejectComment import {graphql} from 'react-apollo'; import SET_USER_STATUS from './setUserStatus.graphql'; import SET_COMMENT_STATUS from './setCommentStatus.graphql'; diff --git a/client/coral-admin/src/graphql/mutations/setCommentStatus.graphql b/client/coral-admin/src/graphql/mutations/setCommentStatus.graphql index cfcc76421..7ff6173a8 100644 --- a/client/coral-admin/src/graphql/mutations/setCommentStatus.graphql +++ b/client/coral-admin/src/graphql/mutations/setCommentStatus.graphql @@ -1,3 +1,7 @@ mutation setCommentStatus($commentId: ID!, $status: COMMENT_STATUS!){ - setCommentStatus(id: $commentId, status: $status) + setCommentStatus(id: $commentId, status: $status) { + errors { + translation_key + } + } } diff --git a/client/coral-admin/src/graphql/mutations/setUserStatus.graphql b/client/coral-admin/src/graphql/mutations/setUserStatus.graphql index 0ebaf54fd..32fcf7e20 100644 --- a/client/coral-admin/src/graphql/mutations/setUserStatus.graphql +++ b/client/coral-admin/src/graphql/mutations/setUserStatus.graphql @@ -1,3 +1,7 @@ mutation setUserStatus($userId: ID!, $status: USER_STATUS!) { - setUserStatus(id: $userId, status: $status) + setUserStatus(id: $userId, status: $status) { + errors { + translation_key + } + } } From 732ef9b8bd4889ccd19a4a55883f7f73ee550fb1 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Mon, 13 Feb 2017 16:24:12 -0300 Subject: [PATCH 13/18] Adding moderationList for the tests --- .../src/containers/ModerationQueue/ModerationQueue.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js index 1cd478b63..ca3b5e540 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js @@ -5,7 +5,7 @@ import {actionsMap} from './helpers/moderationQueueActionsMap'; const ModerationQueue = props => { return ( -
      +
        { props.data[props.activeTab].map((comment, i) => { From 519298b980cbdeadfb7c772067fe8055dc78025e Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Mon, 13 Feb 2017 18:12:37 -0300 Subject: [PATCH 14/18] Design Pass --- client/coral-admin/src/components/ui/Header.js | 10 +++++----- .../ModerationQueue/ModerationContainer.js | 1 + .../ModerationQueue/ModerationQueue.js | 1 + .../ModerationQueue/components/Comment.js | 11 +++++------ .../ModerationQueue/components/styles.css | 17 ++++++++++------- 5 files changed, 22 insertions(+), 18 deletions(-) diff --git a/client/coral-admin/src/components/ui/Header.js b/client/coral-admin/src/components/ui/Header.js index 3e6598d6e..e92e3c84f 100644 --- a/client/coral-admin/src/components/ui/Header.js +++ b/client/coral-admin/src/components/ui/Header.js @@ -13,10 +13,14 @@ export default ({handleLogout, restricted = false}) => ( !restricted ?
        - {lang.t('configure.moderate')} + + {lang.t('configure.streams')} + {lang.t('configure.community')} @@ -25,10 +29,6 @@ export default ({handleLogout, restricted = false}) => ( activeClassName={styles.active}> {lang.t('configure.configure')} - - {lang.t('configure.streams')} -
          diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index 1f359a1f7..5a11ef425 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -76,6 +76,7 @@ class ModerationContainer extends Component { /> { showBanUserDialog={props.showBanUserDialog} acceptComment={props.acceptComment} rejectComment={props.rejectComment} + currentAsset={props.currentAsset} />; }) } diff --git a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js index 7a7967aaf..0f9202a51 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js @@ -15,7 +15,6 @@ import translations from 'coral-admin/src/translations.json'; const Comment = ({actions = [], ...props}) => { const links = linkify.getMatches(props.comment.body); - return (
        • @@ -48,11 +47,11 @@ const Comment = ({actions = [], ...props}) => { : null}
        - -
        - {props.comment.asset.title} Moderate Article -
        - + {!props.currentAsset && ( +
        + Article: {props.comment.asset.title} Moderate Article +
        + )}

        diff --git a/client/coral-admin/src/containers/ModerationQueue/components/styles.css b/client/coral-admin/src/containers/ModerationQueue/components/styles.css index 3290b6e1f..e11c20394 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/styles.css +++ b/client/coral-admin/src/containers/ModerationQueue/components/styles.css @@ -6,7 +6,7 @@ } .tabBar { - background: #262626; + background-color: rgba(44, 44, 44, 0.89); z-index: 5; } @@ -73,7 +73,7 @@ span { } .header { - background-color: #3949AB; + background-color: #2c2c2c; color: white; margin-bottom: -1px; @@ -156,6 +156,7 @@ span { padding: 16px 14px; position: relative; transition: box-shadow 200ms; + margin-top: 0; &:hover { @@ -222,10 +223,11 @@ span { } .body { - margin-top: 20px; + margin-top: 0px; flex: 1; font-size: 0.88em; color: black; + max-width: 500px; } .flagged { @@ -294,15 +296,16 @@ span { .Comment { .moderateArticle { - padding: 10px 0px; + font-size: 12px; a { - display: block; + display: inline-block; color: #679af3; text-decoration: none; font-size: 1em; - font-weight: 500; + font-weight: 400; letter-spacing: .5px; - font-size: .8em; + font-size: 12px; + margin-left: 10px; &:hover { text-decoration: underline; From 37ba14f3b5b8ec8f2d0b672564c429210f4bbc58 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Mon, 13 Feb 2017 18:27:26 -0300 Subject: [PATCH 15/18] breaking big words --- .../src/containers/ModerationQueue/components/styles.css | 1 + 1 file changed, 1 insertion(+) diff --git a/client/coral-admin/src/containers/ModerationQueue/components/styles.css b/client/coral-admin/src/containers/ModerationQueue/components/styles.css index e11c20394..0be87efd7 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/styles.css +++ b/client/coral-admin/src/containers/ModerationQueue/components/styles.css @@ -228,6 +228,7 @@ span { font-size: 0.88em; color: black; max-width: 500px; + word-wrap: break-word; } .flagged { From 51be47b62e9f0ebc1777aa201ab6149cd601a273 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Mon, 13 Feb 2017 19:09:26 -0300 Subject: [PATCH 16/18] added flag functionality --- .../ModerationQueue/components/Comment.js | 9 ++++++--- .../ModerationQueue/components/FlagBox.js | 19 +++++++++++++++++++ .../ModerationQueue/components/styles.css | 9 ++++++++- .../src/graphql/queries/modQueueQuery.graphql | 6 ++++++ graph/resolvers/root_query.js | 7 +------ 5 files changed, 40 insertions(+), 10 deletions(-) create mode 100644 client/coral-admin/src/containers/ModerationQueue/components/FlagBox.js diff --git a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js index 0f9202a51..ae70b303a 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js @@ -7,14 +7,17 @@ import {Link} from 'react-router'; import styles from './styles.css'; import {Icon} from 'coral-ui'; import ActionButton from '../../../components/ActionButton'; +import FlagBox from './FlagBox'; + const linkify = new Linkify(); -const lang = new I18n(translations); import I18n from 'coral-framework/modules/i18n/i18n'; import translations from 'coral-admin/src/translations.json'; +const lang = new I18n(translations); const Comment = ({actions = [], ...props}) => { const links = linkify.getMatches(props.comment.body); + const actionSumaries = props.comment.action_summaries; return (

      • @@ -24,7 +27,7 @@ const Comment = ({actions = [], ...props}) => { {timeago().format(props.comment.created_at || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))} - {props.flagged ?

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

        : null} + {props.comment.action_summaries ?

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

        : null}
      • {links ? Contains Link : null} @@ -59,11 +62,11 @@ const Comment = ({actions = [], ...props}) => {

        + {actionSumaries && } {/* */} {/* View context*/} {/* */} - ); }; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/FlagBox.js b/client/coral-admin/src/containers/ModerationQueue/components/FlagBox.js new file mode 100644 index 000000000..bf5a34f29 --- /dev/null +++ b/client/coral-admin/src/containers/ModerationQueue/components/FlagBox.js @@ -0,0 +1,19 @@ +import React, {PropTypes} from 'react'; +import styles from './styles.css'; + +const FlagBox = props => ( +
        +

        Flags:

        +
          + {props.actionSumaries.map((action, i) => +
        • {!action.reason ? No reason provided : action.reason} ({action.count})
        • + )} +
        +
        +); + +FlagBox.propTypes = { + actionSumaries: PropTypes.array.isRequired +}; + +export default FlagBox; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/styles.css b/client/coral-admin/src/containers/ModerationQueue/components/styles.css index 0be87efd7..658c9a059 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/styles.css +++ b/client/coral-admin/src/containers/ModerationQueue/components/styles.css @@ -317,4 +317,11 @@ span { } } - +.flagBox { + border-top: 1px solid rgba(66, 66, 66, 0.12); + h3 { + font-size: 14px; + margin: 0; + font-weight: 500; + } +} diff --git a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql index 38ce9af23..9eba7a971 100644 --- a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql +++ b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql @@ -18,6 +18,12 @@ query ModQueue ($asset_id: ID!) { asset_id: $asset_id }) { ...commentView + action_summaries { + count + ... on FlagActionSummary { + reason + } + } } rejected: comments(query: { statuses: [REJECTED], diff --git a/graph/resolvers/root_query.js b/graph/resolvers/root_query.js index 8aa14d5c6..ec3ac5b08 100644 --- a/graph/resolvers/root_query.js +++ b/graph/resolvers/root_query.js @@ -29,12 +29,7 @@ const RootQuery = { if (user != null && user.hasRoles('ADMIN') && action_type) { return Actions.getByTypes({action_type, item_type: 'COMMENTS'}) - .then((actions) => { - - // Map the actions from the items referenced byt this query. The actions - // returned by this query are explicitly going to be distinct by their - // `item_id`'s. - let ids = actions.map((action) => action.item_id); + .then((ids) => { // Perform the query using the available resolver. return Comments.getByQuery({ids, statuses, asset_id, parent_id, limit, cursor, sort}); From 51cbf79f3074316d7c1b6190e2ba595e28fa4f86 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 13 Feb 2017 15:33:10 -0700 Subject: [PATCH 17/18] Changed fragment --- client/coral-admin/src/graphql/fragments/commentView.graphql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-admin/src/graphql/fragments/commentView.graphql b/client/coral-admin/src/graphql/fragments/commentView.graphql index c31e67fb1..e78c28a28 100644 --- a/client/coral-admin/src/graphql/fragments/commentView.graphql +++ b/client/coral-admin/src/graphql/fragments/commentView.graphql @@ -5,7 +5,7 @@ fragment commentView on Comment { status user { id - name: displayName + name: username status } asset { From f07900b294cf80aeb1ef3660d396fcca66fbdd76 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Tue, 14 Feb 2017 11:44:22 -0300 Subject: [PATCH 18/18] Missing reducer --- client/coral-admin/src/reducers/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/coral-admin/src/reducers/index.js b/client/coral-admin/src/reducers/index.js index e58f5bf5a..837ad507d 100644 --- a/client/coral-admin/src/reducers/index.js +++ b/client/coral-admin/src/reducers/index.js @@ -3,11 +3,13 @@ import assets from './assets'; import settings from './settings'; import community from './community'; import moderation from './moderation'; +import install from './install'; export default { auth, assets, settings, community, - moderation + moderation, + install };