From 9149b737f5bd06f7e10994110c8a1213fcbf9cce Mon Sep 17 00:00:00 2001 From: David Jay Date: Tue, 21 Feb 2017 17:23:09 -0500 Subject: [PATCH] Retrieving counts and displaying new comments to user. --- client/coral-embed-stream/src/Comment.js | 2 +- client/coral-embed-stream/src/Embed.js | 8 +- client/coral-embed-stream/src/NewCount.js | 18 ++++ client/coral-embed-stream/src/Stream.js | 22 ++++- client/coral-framework/actions/asset.js | 1 + client/coral-framework/constants/asset.js | 1 + .../coral-framework/graphql/queries/index.js | 93 ++++++++++++------- client/coral-framework/reducers/asset.js | 3 + .../reducers/assetReducer.spec.js | 19 ++++ .../reducers/notificationReducer.spec.js | 35 +++++++ 10 files changed, 165 insertions(+), 37 deletions(-) create mode 100644 client/coral-embed-stream/src/NewCount.js create mode 100644 test/client/coral-framework/reducers/assetReducer.spec.js create mode 100644 test/client/coral-framework/reducers/notificationReducer.spec.js diff --git a/client/coral-embed-stream/src/Comment.js b/client/coral-embed-stream/src/Comment.js index 9d679473e..8371cec45 100644 --- a/client/coral-embed-stream/src/Comment.js +++ b/client/coral-embed-stream/src/Comment.js @@ -177,7 +177,7 @@ class Comment extends React.Component { comment.replies &&
comment.replies.length} diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index a7c4dcb38..6ff153a52 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -12,6 +12,7 @@ const {fetchAssetSuccess} = assetActions; import {queryStream} from 'coral-framework/graphql/queries'; import {postComment, postFlag, postLike, deleteAction} from 'coral-framework/graphql/mutations'; import {editName} from 'coral-framework/actions/user'; +import {updateCountCache} from 'coral-framework/actions/asset'; import {Notification, notificationActions, authActions, assetActions, pym} from 'coral-framework'; import Stream from './Stream'; @@ -28,6 +29,7 @@ import SettingsContainer from 'coral-settings/containers/SettingsContainer'; import RestrictedContent from 'coral-framework/components/RestrictedContent'; import ConfigureStreamContainer from 'coral-configure/containers/ConfigureStreamContainer'; import LoadMore from './LoadMore'; +import NewCount from './NewCount'; class Embed extends Component { @@ -82,7 +84,7 @@ class Embed extends Component { render () { const {activeTab} = this.state; - const {closedAt} = this.props.asset; + const {closedAt, countCache = {}} = this.props.asset; const {loading, asset, refetch} = this.props.data; const {loggedIn, isAdmin, user, showSignInDialog, signInOffset} = this.props.auth; @@ -147,6 +149,7 @@ class Embed extends Component { } {!loggedIn && } {loggedIn && user && } + ({ clearNotification: () => dispatch(clearNotification()), editName: (username) => dispatch(editName(username)), showSignInDialog: (offset) => dispatch(showSignInDialog(offset)), + updateCountCache: (id, count) => dispatch(updateCountCache(id, count)), logout: () => dispatch(logout()), dispatch: d => dispatch(d) }); diff --git a/client/coral-embed-stream/src/NewCount.js b/client/coral-embed-stream/src/NewCount.js new file mode 100644 index 000000000..712c36be3 --- /dev/null +++ b/client/coral-embed-stream/src/NewCount.js @@ -0,0 +1,18 @@ +import React, {PropTypes} from 'react'; + +const NewCount = ({commentCount, countCache}) => +
+ { + countCache && commentCount - countCache > 0 && +
+ Load {commentCount - countCache} More Comments +
+ } +
; + +NewCount.propTypes = { + commentCount: PropTypes.number.isRequired, + countCache: PropTypes.number +}; + +export default NewCount; diff --git a/client/coral-embed-stream/src/Stream.js b/client/coral-embed-stream/src/Stream.js index 2f0f4b01b..d59521484 100644 --- a/client/coral-embed-stream/src/Stream.js +++ b/client/coral-embed-stream/src/Stream.js @@ -17,10 +17,30 @@ class Stream extends React.Component { constructor(props) { super(props); - this.state = {activeReplyBox: ''}; + this.state = {activeReplyBox: '', countPoll: null}; this.setActiveReplyBox = this.setActiveReplyBox.bind(this); } + componentDidMount() { + const {asset, getCounts, updateCountCache} = this.props; + + updateCountCache(asset.id, asset.comments.length); + + // Note: Apollo's built-in polling doesn't work with fetchMore queries, so a + // setInterval is being used instead. + this.setState({ + countPoll: setInterval(() => getCounts({ + asset_id: asset.id, + limit: asset.comments.length, + sort: 'REVERSE_CHRONOLOGICAL' + }), 5000), + }); + } + + componentWillUnmount() { + clearInterval(this.state.countPoll); + } + setActiveReplyBox (reactKey) { if (!this.props.currentUser) { const offset = document.getElementById(`c_${reactKey}`).getBoundingClientRect().top - 75; diff --git a/client/coral-framework/actions/asset.js b/client/coral-framework/actions/asset.js index d5db289c3..02e72ea98 100644 --- a/client/coral-framework/actions/asset.js +++ b/client/coral-framework/actions/asset.js @@ -38,6 +38,7 @@ export const updateOpenStream = closedBody => (dispatch, getState) => { const openStream = () => ({type: actions.OPEN_COMMENTS}); const closeStream = () => ({type: actions.CLOSE_COMMENTS}); +export const updateCountCache = (id, count) => ({type: actions.UPDATE_COUNT_CACHE, id, count}); export const updateOpenStatus = status => dispatch => { if (status === 'open') { diff --git a/client/coral-framework/constants/asset.js b/client/coral-framework/constants/asset.js index 40f746706..234095d9d 100644 --- a/client/coral-framework/constants/asset.js +++ b/client/coral-framework/constants/asset.js @@ -8,3 +8,4 @@ export const UPDATE_ASSET_SETTINGS_FAILURE = 'UPDATE_ASSET_SETTINGS_FAILURE'; export const OPEN_COMMENTS = 'OPEN_COMMENTS'; export const CLOSE_COMMENTS = 'CLOSE_COMMENTS'; +export const UPDATE_COUNT_CACHE = 'UPDATE_COUNT_CACHE'; diff --git a/client/coral-framework/graphql/queries/index.js b/client/coral-framework/graphql/queries/index.js index 09b3159d0..494ffa30e 100644 --- a/client/coral-framework/graphql/queries/index.js +++ b/client/coral-framework/graphql/queries/index.js @@ -1,6 +1,7 @@ import {graphql} from 'react-apollo'; import STREAM_QUERY from './streamQuery.graphql'; import LOAD_MORE from './loadMore.graphql'; +import GET_COUNTS from './getCounts.graphql'; import MY_COMMENT_HISTORY from './myCommentHistory.graphql'; function getQueryVariable(variable) { @@ -17,6 +18,62 @@ function getQueryVariable(variable) { return 'http://localhost/default/stream'; } +export const getCounts = (data) => ({asset_id, limit, sort}) => { + return data.fetchMore({ + query: GET_COUNTS, + variables: { + asset_id, + limit, + sort + }, + updateQuery: (oldData, {fetchMoreResult:{data}}) => { + + return { + ...oldData, + asset: { + ...oldData.asset, + commentCount: data.asset.commentCount + } + }; + } + }); +}; + +export const loadMore = (data) => ({limit, cursor, parent_id, asset_id, sort}) => { + return data.fetchMore({ + query: LOAD_MORE, + variables: { + limit, + cursor, + parent_id, + asset_id, + sort + }, + updateQuery: (oldData, {fetchMoreResult:{data:{new_top_level_comments}}}) => + + // If loading more replies + parent_id ? { + ...oldData, + asset: { + ...oldData.asset, + comments: oldData.asset.comments.map((comment) => + comment.id === parent_id + ? {...comment, replies: [...comment.replies, ...new_top_level_comments]} + : comment) + } + } + + // If loading more top-level comments + : { + ...oldData, + asset: { + ...oldData.asset, + comments: [...oldData.asset.comments, ...new_top_level_comments] + } + } + }); +}; + export const queryStream = graphql(STREAM_QUERY, { options: () => ({ variables: { @@ -25,40 +82,8 @@ export const queryStream = graphql(STREAM_QUERY, { }), props: ({data}) => ({ data, - loadMore: ({limit, cursor, parent_id, asset_id, sort}) => { - return data.fetchMore({ - query: LOAD_MORE, - variables: { - limit, - cursor, - parent_id, - asset_id, - sort - }, - updateQuery: (oldData, {fetchMoreResult:{data:{new_top_level_comments}}}) => - - // If loading more replies - parent_id ? { - ...oldData, - asset: { - ...oldData.asset, - comments: oldData.asset.comments.map((comment) => - comment.id === parent_id - ? {...comment, replies: [...comment.replies, ...new_top_level_comments]} - : comment) - } - } - - // If loading more top-level comments - : { - ...oldData, - asset: { - ...oldData.asset, - comments: [...oldData.asset.comments, ...new_top_level_comments] - } - } - }); - } + loadMore: loadMore(data), + getCounts: getCounts(data), }) }); diff --git a/client/coral-framework/reducers/asset.js b/client/coral-framework/reducers/asset.js index f9d0a55e3..067edfc5b 100644 --- a/client/coral-framework/reducers/asset.js +++ b/client/coral-framework/reducers/asset.js @@ -19,6 +19,9 @@ export default function asset (state = initialState, action) { case actions.UPDATE_ASSET_SETTINGS_SUCCESS: return state .setIn(['settings'], action.settings); + case actions.UPDATE_COUNT_CACHE: + return state + .setIn(['countCache', action.id], action.count); default: return state; } diff --git a/test/client/coral-framework/reducers/assetReducer.spec.js b/test/client/coral-framework/reducers/assetReducer.spec.js new file mode 100644 index 000000000..c54f51737 --- /dev/null +++ b/test/client/coral-framework/reducers/assetReducer.spec.js @@ -0,0 +1,19 @@ +import {Map} from 'immutable'; +import {expect} from 'chai'; +import assetReducer from '../../../../client/coral-framework/reducers/asset'; +import * as actions from '../../../../client/coral-framework/constants/asset'; + +describe ('coral-embed-stream assetReducer', () => { + describe('UPDATE_COUNT_CACHE', () => { + it('should update the count cache', () => { + const action = { + type: actions.UPDATE_COUNT_CACHE, + id: '123', + count: 456 + }; + const store = new Map({}); + const result = assetReducer(store, action); + expect(result.getIn(['countCache', '123'])).to.equal(456); + }); + }); +}); diff --git a/test/client/coral-framework/reducers/notificationReducer.spec.js b/test/client/coral-framework/reducers/notificationReducer.spec.js new file mode 100644 index 000000000..40c50d11a --- /dev/null +++ b/test/client/coral-framework/reducers/notificationReducer.spec.js @@ -0,0 +1,35 @@ +import {Map} from 'immutable'; +import {expect} from 'chai'; +import notificationReducer from '../../../../client/coral-framework/reducers/notification'; +import * as actions from '../../../../client/coral-framework/actions/notification'; + +describe ('notificationsReducer', () => { + describe('ADD_NOTIFICATION', () => { + it('should add a notification', () => { + const action = { + type: actions.ADD_NOTIFICATION, + text: 'Test notification', + notifType: 'test' + }; + const store = new Map({}); + const result = notificationReducer(store, action); + expect(result.get('text')).to.equal(action.text); + expect(result.get('type')).to.equal(action.notifType); + }); + }); + + describe('CLEAR_NOTIFICATION', () => { + it('should clear a notification', () => { + const action = { + type: actions.CLEAR_NOTIFICATION + }; + const store = new Map({ + text: 'Test notification', + type: 'test' + }); + const result = notificationReducer(store, action); + expect(result.get('text')).to.equal(''); + expect(result.get('type')).to.equal(''); + }); + }); +});