From fe2487dea56a51b9f2fffb1ad33d7714c6a05941 Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 14 Dec 2016 15:02:26 -0500 Subject: [PATCH 01/30] Add streams option and sidebar. --- client/coral-admin/src/AppRouter.js | 2 + .../coral-admin/src/components/ui/Header.js | 2 + .../src/containers/Streams/Streams.css | 40 +++++++++++++++++++ .../src/containers/Streams/Streams.js | 40 +++++++++++++++++++ client/coral-admin/src/translations.json | 1 + 5 files changed, 85 insertions(+) create mode 100644 client/coral-admin/src/containers/Streams/Streams.css create mode 100644 client/coral-admin/src/containers/Streams/Streams.js diff --git a/client/coral-admin/src/AppRouter.js b/client/coral-admin/src/AppRouter.js index 6d55d7b18..bc9747f34 100644 --- a/client/coral-admin/src/AppRouter.js +++ b/client/coral-admin/src/AppRouter.js @@ -4,6 +4,7 @@ import {Router, Route, IndexRoute, browserHistory} from 'react-router'; import ModerationQueue from 'containers/ModerationQueue/ModerationQueue'; import CommentStream from 'containers/CommentStream/CommentStream'; import Configure from 'containers/Configure/Configure'; +import Streams from 'containers/Streams/Streams'; import CommunityContainer from 'containers/Community/CommunityContainer'; import LayoutContainer from 'containers/LayoutContainer'; @@ -13,6 +14,7 @@ const routes = ( + ); diff --git a/client/coral-admin/src/components/ui/Header.js b/client/coral-admin/src/components/ui/Header.js index e4d151d30..4c76424cc 100644 --- a/client/coral-admin/src/components/ui/Header.js +++ b/client/coral-admin/src/components/ui/Header.js @@ -16,6 +16,8 @@ export default ({handleLogout}) => ( activeClassName={styles.active}>{lang.t('configure.community')} {lang.t('configure.configure')} + {lang.t('configure.streams')}
    diff --git a/client/coral-admin/src/containers/Streams/Streams.css b/client/coral-admin/src/containers/Streams/Streams.css new file mode 100644 index 000000000..56d757863 --- /dev/null +++ b/client/coral-admin/src/containers/Streams/Streams.css @@ -0,0 +1,40 @@ +.container { + padding: 10px; + display: flex; +} + +.leftColumn { + width: 200px; +} + +.mainContent { + width: calc(70% - 300px) +} + +.searchIcon { + vertical-align: middle; + font-size: 18px; + color: #ccc; +} + +.searchBox { + padding: 3px; + border: 1px solid #ccc; + border-radius: 3px; + width: 90%; + display: flex; +} + +.searchBoxInput { + border: none; + flex: 1; + font-size: 14px; +} + +.searchBoxInput:focus { + outline: none; +} + +.hidden { + display: none; +} diff --git a/client/coral-admin/src/containers/Streams/Streams.js b/client/coral-admin/src/containers/Streams/Streams.js new file mode 100644 index 000000000..0e3e0d3f1 --- /dev/null +++ b/client/coral-admin/src/containers/Streams/Streams.js @@ -0,0 +1,40 @@ +import React from 'react'; +import styles from './Streams.css'; +import I18n from 'coral-framework/modules/i18n/i18n'; +import translations from '../../translations.json'; +import { + RadioGroup, + Radio, + Icon +} from 'react-mdl'; + +const Streams = () =>
    +
    +
    + + {}} + placeholder="Search"/> +
    +
    {lang.t('streams.filter-streams')}
    +
    {lang.t('streams.stream-status')}
    + + {lang.t('streams.all')} + {lang.t('streams.open')} + {lang.t('streams.closed')} + +
    {lang.t('streams.sort-by')}
    + + {lang.t('streams.newest')} + {lang.t('streams.oldest')} + +
    +
    +
    +
    ; + +export default Streams; + +const lang = new I18n(translations); diff --git a/client/coral-admin/src/translations.json b/client/coral-admin/src/translations.json index e462f7567..4d3525dd9 100644 --- a/client/coral-admin/src/translations.json +++ b/client/coral-admin/src/translations.json @@ -55,6 +55,7 @@ "moderate": "Moderate", "configure": "Configure", "community": "Community", + "streams": "Streams", "closed-comments-desc": "Write a message for closed threads", "closed-comments-label": "Write a message...", "comment-count-header": "Limit Comment Length", From dad46cc8defa45288d50041a0350d09b00be2562 Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 14 Dec 2016 15:15:13 -0500 Subject: [PATCH 02/30] Add style and language to left nav. --- .../src/containers/Streams/Streams.css | 15 +++++++++++++ .../src/containers/Streams/Streams.js | 10 ++++----- client/coral-admin/src/translations.json | 22 +++++++++++++++++++ 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/client/coral-admin/src/containers/Streams/Streams.css b/client/coral-admin/src/containers/Streams/Streams.css index 56d757863..07724baa8 100644 --- a/client/coral-admin/src/containers/Streams/Streams.css +++ b/client/coral-admin/src/containers/Streams/Streams.css @@ -35,6 +35,21 @@ outline: none; } +.optionHeader { + font-size: 16px; + font-weight: 900; + margin-top: 15px; +} + +.optionDetail { + font-size: 16px; + margin-top: 3px; +} + +.radio { + display: block; +} + .hidden { display: none; } diff --git a/client/coral-admin/src/containers/Streams/Streams.js b/client/coral-admin/src/containers/Streams/Streams.js index 0e3e0d3f1..ee5d205ae 100644 --- a/client/coral-admin/src/containers/Streams/Streams.js +++ b/client/coral-admin/src/containers/Streams/Streams.js @@ -16,16 +16,16 @@ const Streams = () =>
    type='text' className={styles.searchBoxInput} onChange={() => {}} - placeholder="Search"/> + placeholder={lang.t('streams.search')}/>
    -
    {lang.t('streams.filter-streams')}
    -
    {lang.t('streams.stream-status')}
    - +
    {lang.t('streams.filter-streams')}
    +
    {lang.t('streams.stream-status')}
    + }> {lang.t('streams.all')} {lang.t('streams.open')} {lang.t('streams.closed')} -
    {lang.t('streams.sort-by')}
    +
    {lang.t('streams.sort-by')}
    {lang.t('streams.newest')} {lang.t('streams.oldest')} diff --git a/client/coral-admin/src/translations.json b/client/coral-admin/src/translations.json index 4d3525dd9..da859c49c 100644 --- a/client/coral-admin/src/translations.json +++ b/client/coral-admin/src/translations.json @@ -69,6 +69,17 @@ "note": "Note: Banning this user will also place this comment in the Rejected queue.", "cancel": "Cancel", "yes_ban_user": "Yes, Ban User" + }, + "streams": { + "search": "Search", + "filter-streams": "Filter Streams", + "stream-status": "Stream Status", + "all": "All", + "open": "Open", + "closed": "Closed", + "newest": "Newest", + "oldest": "Oldest", + "sort-by": "Sort By" } }, "es": { @@ -129,6 +140,17 @@ "note": "Nota: Suspender este usuario también va a colocar este comentario en la cola de Rechazados.", "cancel": "Cancelar", "yes_ban_user": "Si, Suspendan el usuario" + }, + "streams": { + "search": "", + "filter-streams": "", + "stream-status": "", + "all": "", + "open": "", + "closed": "", + "newest": "", + "oldest": "", + "sort-by": "" } } } From 7c6299243f6ecd77f4eb49806736fc1d494925d7 Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 14 Dec 2016 15:29:56 -0500 Subject: [PATCH 03/30] Connecting sorting options to state. --- .../src/containers/Streams/Streams.js | 80 ++++++++++++------- 1 file changed, 53 insertions(+), 27 deletions(-) diff --git a/client/coral-admin/src/containers/Streams/Streams.js b/client/coral-admin/src/containers/Streams/Streams.js index ee5d205ae..c6c9feeb3 100644 --- a/client/coral-admin/src/containers/Streams/Streams.js +++ b/client/coral-admin/src/containers/Streams/Streams.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {Component} from 'react'; import styles from './Streams.css'; import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../../translations.json'; @@ -8,32 +8,58 @@ import { Icon } from 'react-mdl'; -const Streams = () =>
    -
    -
    - - {}} - placeholder={lang.t('streams.search')}/> -
    -
    {lang.t('streams.filter-streams')}
    -
    {lang.t('streams.stream-status')}
    - }> - {lang.t('streams.all')} - {lang.t('streams.open')} - {lang.t('streams.closed')} - -
    {lang.t('streams.sort-by')}
    - - {lang.t('streams.newest')} - {lang.t('streams.oldest')} - -
    -
    -
    -
    ; +class Streams extends Component { + + state = { + searchTerm: '', + sortBy: 'newest', + statusFilter: 'all' + } + + onSettingChange = (setting) => (e) => { + this.setState({[setting]: e.target.value}); + } + + render () { + const {searchTerm, sortBy, statusFilter} = this.state; + + return
    +
    +
    + + +
    +
    {lang.t('streams.filter-streams')}
    +
    {lang.t('streams.stream-status')}
    + + {lang.t('streams.all')} + {lang.t('streams.open')} + {lang.t('streams.closed')} + +
    {lang.t('streams.sort-by')}
    + + {lang.t('streams.newest')} + {lang.t('streams.oldest')} + +
    +
    +
    +
    ; + } +} export default Streams; From 65b308a0612789aeda3da097ad51942394938146 Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 14 Dec 2016 16:01:43 -0500 Subject: [PATCH 04/30] Adding initial stream list. --- .../src/containers/Streams/Streams.css | 2 +- .../src/containers/Streams/Streams.js | 89 ++++++++++++------- 2 files changed, 60 insertions(+), 31 deletions(-) diff --git a/client/coral-admin/src/containers/Streams/Streams.css b/client/coral-admin/src/containers/Streams/Streams.css index 07724baa8..7ab1b5199 100644 --- a/client/coral-admin/src/containers/Streams/Streams.css +++ b/client/coral-admin/src/containers/Streams/Streams.css @@ -8,7 +8,7 @@ } .mainContent { - width: calc(70% - 300px) + width: calc(90% - 200px); } .searchIcon { diff --git a/client/coral-admin/src/containers/Streams/Streams.js b/client/coral-admin/src/containers/Streams/Streams.js index c6c9feeb3..bec61e995 100644 --- a/client/coral-admin/src/containers/Streams/Streams.js +++ b/client/coral-admin/src/containers/Streams/Streams.js @@ -5,7 +5,9 @@ import translations from '../../translations.json'; import { RadioGroup, Radio, - Icon + Icon, + DataTable, + TableHeader } from 'react-mdl'; class Streams extends Component { @@ -20,42 +22,69 @@ class Streams extends Component { this.setState({[setting]: e.target.value}); } + renderDate = (date) => { + const d = new Date(date); + return `${d.getMonth()}/${d.getDate()}/${d.getYear()}`; + } + + renderStatus = (status) => { + return
    {status}
    ; + } + render () { const {searchTerm, sortBy, statusFilter} = this.state; return
    -
    - - -
    -
    {lang.t('streams.filter-streams')}
    -
    {lang.t('streams.stream-status')}
    - - {lang.t('streams.all')} - {lang.t('streams.open')} - {lang.t('streams.closed')} - -
    {lang.t('streams.sort-by')}
    - - {lang.t('streams.newest')} - {lang.t('streams.oldest')} - + +
    + + +
    + +
    {lang.t('streams.filter-streams')}
    +
    {lang.t('streams.stream-status')}
    + + {lang.t('streams.all')} + {lang.t('streams.open')} + {lang.t('streams.closed')} + +
    {lang.t('streams.sort-by')}
    + + {lang.t('streams.newest')} + {lang.t('streams.oldest')} +
    +
    + + {lang.t('streams.article')} + + {lang.t('streams.pubdate')} + + + {lang.t('streams.status')} + +
    ; } From b98201afa3269c1bae428367effd03a33f4d6b08 Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 14 Dec 2016 19:03:07 -0500 Subject: [PATCH 05/30] Adding and testing function to fetch assets in talk reducer. --- client/coral-admin/src/actions/assets.js | 0 client/coral-admin/src/constants/comments.js | 2 +- client/coral-admin/src/reducers/comments.js | 2 +- .../coral-admin/src/services/talk-adapter.js | 34 ++++++--- .../coral-admin/services/talk-adapter.js | 75 +++++++++++++++++++ .../{itemActions.spec.js => itemActions.js} | 0 .../{itemReducer.spec.js => itemReducer.js} | 0 7 files changed, 100 insertions(+), 13 deletions(-) create mode 100644 client/coral-admin/src/actions/assets.js create mode 100644 tests/client/coral-admin/services/talk-adapter.js rename tests/client/coral-framework/store/{itemActions.spec.js => itemActions.js} (100%) rename tests/client/coral-framework/store/{itemReducer.spec.js => itemReducer.js} (100%) diff --git a/client/coral-admin/src/actions/assets.js b/client/coral-admin/src/actions/assets.js new file mode 100644 index 000000000..e69de29bb diff --git a/client/coral-admin/src/constants/comments.js b/client/coral-admin/src/constants/comments.js index 856f619d0..16d72e889 100644 --- a/client/coral-admin/src/constants/comments.js +++ b/client/coral-admin/src/constants/comments.js @@ -1,3 +1,3 @@ export const SHOW_BANUSER_DIALOG = 'SHOW_BANUSER_DIALOG'; export const HIDE_BANUSER_DIALOG = 'HIDE_BANUSER_DIALOG'; -export const USER_BAN_SUCESS = 'USER_BAN_SUCESS'; +export const USER_BAN_SUCCESS = 'USER_BAN_SUCCESS'; diff --git a/client/coral-admin/src/reducers/comments.js b/client/coral-admin/src/reducers/comments.js index dca726b5e..d5fd85ab6 100644 --- a/client/coral-admin/src/reducers/comments.js +++ b/client/coral-admin/src/reducers/comments.js @@ -32,7 +32,7 @@ export default (state = initialState, action) => { case '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_SUCESS: return setBanUser(state, false, action); + case actions.USER_BAN_SUCCESS: return setBanUser(state, false, action); default: return state; } }; diff --git a/client/coral-admin/src/services/talk-adapter.js b/client/coral-admin/src/services/talk-adapter.js index ab542b4a0..6dc4d5827 100644 --- a/client/coral-admin/src/services/talk-adapter.js +++ b/client/coral-admin/src/services/talk-adapter.js @@ -11,22 +11,20 @@ import coralApi from '../../../coral-framework/helpers/response'; // Intercept redux actions and act over the ones we are interested export default store => next => action => { + next(action); + switch (action.type) { case 'COMMENTS_MODERATION_QUEUE_FETCH': - fetchModerationQueueComments(store); - break; + return fetchModerationQueueComments(store); case 'COMMENT_UPDATE': - updateComment(store, action.comment); - break; + return updateComment(store, action.comment); case 'COMMENT_CREATE': - createComment(store, action.name, action.body); - break; + return createComment(store, action.name, action.body); case 'USER_BAN': - userStatusUpdate(store, action.status, action.userId, action.commentId); - break; + return userStatusUpdate(store, action.status, action.userId, action.commentId); + case 'ASSETS_FETCH': + return fetchAssets(store, action); } - - next(action); }; // Get comments to fill each of the three lists on the mod queue @@ -88,6 +86,20 @@ const createComment = (store, name, comment) => { // Ban a user const userStatusUpdate = (store, status, userId, commentId) => { return coralApi(`/users/${userId}/status`, {method: 'POST', body: {status: status, comment_id: commentId}}) - .then(res => store.dispatch({type: 'USER_BAN_SUCESS', res})) + .then(res => store.dispatch({type: 'USER_BAN_SUCCESS', res})) .catch(error => store.dispatch({type: 'USER_BAN_FAILED', error})); }; + +// Fetch a page of assets +// Get comments to fill each of the three lists on the mod queue +const fetchAssets = (store, action) => { + const {skip, limit, search} = action; + return coralApi(`/assets?skip=${skip}&limit=${limit}&search=${search}`) + .then(({result, count}) => + /* Post comments and users to redux store. Actions will be posted when they are needed. */ + store.dispatch({type: 'ASSETS_FETCH_SUCCESS', + assets: result, + count + })) + .catch(error => store.dispatch({type: 'ASSETS_FETCH_FAILED', error})); +}; diff --git a/tests/client/coral-admin/services/talk-adapter.js b/tests/client/coral-admin/services/talk-adapter.js new file mode 100644 index 000000000..27ae8efcb --- /dev/null +++ b/tests/client/coral-admin/services/talk-adapter.js @@ -0,0 +1,75 @@ +import 'react'; +import 'redux'; +import {expect} from 'chai'; +import fetchMock from 'fetch-mock'; +import adapter from '../../../../client/coral-admin/src/services/talk-adapter'; +import {Map} from 'immutable'; + +import configureStore from 'redux-mock-store'; + +const mockStore = configureStore(); + +describe('talk-adapter.js', () => { + let store; + + beforeEach(() => { + store = mockStore(new Map({})); + fetchMock.restore(); + }); + + describe('ASSETS_FETCH', () => { + + const assets = [ + { + url: 'http://test.com', + id: '123', + status: 'closed' + }, + { + url: 'http://test.org', + id: '456', + status: 'open' + } + ]; + + it('should fetch a list of assets', () => { + + const action = { + type: 'ASSETS_FETCH', + skip: 2, + limit: 20, + search: '' + }; + + fetchMock.get('*', JSON.stringify({ + result: assets, + count: 2 + })); + + return adapter(store)(()=>{})(action) + .then(() => { + expect(store.getActions()[0]).to.have.property('type', 'ASSETS_FETCH_SUCCESS'); + expect(store.getActions()[0]).to.have.property('count', 2); + expect(store.getActions()[0]).to.have.property('assets'). + and.to.deep.equal(assets); + }); + }); + + it('should return an error appropriatly', () => { + + const action = { + type: 'ASSETS_FETCH', + skip: 2, + limit: 20, + search: '' + }; + + fetchMock.get('*', 404); + + return adapter(store)(()=>{})(action) + .then(() => { + expect(store.getActions()[0]).to.have.property('type', 'ASSETS_FETCH_FAILED'); + }); + }); + }); +}); diff --git a/tests/client/coral-framework/store/itemActions.spec.js b/tests/client/coral-framework/store/itemActions.js similarity index 100% rename from tests/client/coral-framework/store/itemActions.spec.js rename to tests/client/coral-framework/store/itemActions.js diff --git a/tests/client/coral-framework/store/itemReducer.spec.js b/tests/client/coral-framework/store/itemReducer.js similarity index 100% rename from tests/client/coral-framework/store/itemReducer.spec.js rename to tests/client/coral-framework/store/itemReducer.js From 78c81aeaf4f538ae2419226490a5108036eab15f Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 14 Dec 2016 19:30:59 -0500 Subject: [PATCH 06/30] Adding asset reducer. --- client/coral-admin/src/actions/assets.js | 0 client/coral-admin/src/reducers/assets.js | 20 +++++++++++ tests/client/coral-admin/reducers/assets.js | 39 +++++++++++++++++++++ 3 files changed, 59 insertions(+) delete mode 100644 client/coral-admin/src/actions/assets.js create mode 100644 client/coral-admin/src/reducers/assets.js create mode 100644 tests/client/coral-admin/reducers/assets.js diff --git a/client/coral-admin/src/actions/assets.js b/client/coral-admin/src/actions/assets.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/client/coral-admin/src/reducers/assets.js b/client/coral-admin/src/reducers/assets.js new file mode 100644 index 000000000..bdd1bcaf9 --- /dev/null +++ b/client/coral-admin/src/reducers/assets.js @@ -0,0 +1,20 @@ +import {Map, List, fromJS} from 'immutable'; + +const initialState = Map({ + byId: Map(), + ids: List() +}); + +export default (state = initialState, action) => { + switch (action.type) { + case 'ASSETS_FETCH_SUCCESS': return replaceAssets(action, state); + default: return state; + } +}; + +const replaceAssets = (action, state) => { + const assets = fromJS(action.assets.reduce((prev, curr) => { prev[curr.id] = curr; return prev; }, {})); + return state.set('byId', assets) + .set('count', action.count) + .set('ids', List(assets.keys())); +}; diff --git a/tests/client/coral-admin/reducers/assets.js b/tests/client/coral-admin/reducers/assets.js new file mode 100644 index 000000000..cf3e0dac4 --- /dev/null +++ b/tests/client/coral-admin/reducers/assets.js @@ -0,0 +1,39 @@ +import {Map} from 'immutable'; +import {expect} from 'chai'; +import assetsReducer from '../../../../client/coral-admin/src/reducers/assets'; + +describe ('assetsReducer', () => { + describe('ASSETS_FETCH_SUCCESS', () => { + it('should replace the existing assets', () => { + const action = { + type: 'ASSETS_FETCH_SUCCESS', + count: 200, + assets: [ + { + id: '123', + url: 'http://test.com', + closedAt: 'tomorrow' + }, + { + id: '456', + url: 'http://test2.com', + closedAt: 'thursday' + }, + ] + }; + const store = new Map({}); + const result = assetsReducer(store, action); + console.log(result.getIn(['byId', '123']).toJS()); + expect(result.getIn(['byId', '123']).toJS()).to.deep.equal({ + url: 'http://test.com', + closedAt: 'tomorrow', + id: '123' + }); + expect(result.getIn(['ids']).toJS()).to.deep.equal([ + '123', + '456' + ]); + expect(result.getIn(['count'])).to.equal(200); + }); + }); +}); From faafbfa78cf3142b35430abb198002985910b173 Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 14 Dec 2016 19:58:04 -0500 Subject: [PATCH 07/30] Loading assets and adding to store. --- client/coral-admin/src/constants/assets.js | 1 + .../src/containers/Streams/Streams.js | 23 ++++++++++++++++++- client/coral-admin/src/reducers/index.js | 4 +++- routes/api/assets/index.js | 4 ++-- 4 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 client/coral-admin/src/constants/assets.js diff --git a/client/coral-admin/src/constants/assets.js b/client/coral-admin/src/constants/assets.js new file mode 100644 index 000000000..59b76390f --- /dev/null +++ b/client/coral-admin/src/constants/assets.js @@ -0,0 +1 @@ +export const ASSETS_FETCH = 'ASSETS_FETCH'; diff --git a/client/coral-admin/src/containers/Streams/Streams.js b/client/coral-admin/src/containers/Streams/Streams.js index bec61e995..ea365d0d1 100644 --- a/client/coral-admin/src/containers/Streams/Streams.js +++ b/client/coral-admin/src/containers/Streams/Streams.js @@ -1,6 +1,8 @@ import React, {Component} from 'react'; import styles from './Streams.css'; +import {connect} from 'react-redux'; import I18n from 'coral-framework/modules/i18n/i18n'; +import {ASSETS_FETCH} from '../../constants/assets'; import translations from '../../translations.json'; import { RadioGroup, @@ -18,6 +20,10 @@ class Streams extends Component { statusFilter: 'all' } + componentDidMount () { + this.props.fetchAssets(0, 25, '', 'desc'); + } + onSettingChange = (setting) => (e) => { this.setState({[setting]: e.target.value}); } @@ -90,6 +96,21 @@ class Streams extends Component { } } -export default Streams; +const mapStateToProps = () => {}; +const mapDispatchToProps = (dispatch) => { + return { + fetchAssets: (skip, limit, search, sort) => { + dispatch({ + type: ASSETS_FETCH, + skip, + limit, + search, + sort + }); + } + }; +}; + +export default connect(mapStateToProps, mapDispatchToProps)(Streams); const lang = new I18n(translations); diff --git a/client/coral-admin/src/reducers/index.js b/client/coral-admin/src/reducers/index.js index 1f1b444fc..fca4694d8 100644 --- a/client/coral-admin/src/reducers/index.js +++ b/client/coral-admin/src/reducers/index.js @@ -4,6 +4,7 @@ import settings from 'reducers/settings'; import community from 'reducers/community'; import users from 'reducers/users'; import auth from 'reducers/auth'; +import assets from 'reducers/assets'; // Combine all reducers into a main one export default combineReducers({ @@ -11,5 +12,6 @@ export default combineReducers({ comments, community, auth, - users + users, + assets }); diff --git a/routes/api/assets/index.js b/routes/api/assets/index.js index 5aa9b8cc6..a602d7fd0 100644 --- a/routes/api/assets/index.js +++ b/routes/api/assets/index.js @@ -20,8 +20,8 @@ router.get('/', (req, res, next) => { Asset .search(search) .sort({[field]: (sort === 'asc') ? 1 : -1}) - .skip(skip) - .limit(limit), + .skip(parseInt(skip)) + .limit(parseInt(limit)), Asset .search(search) .count() From d5268989627c9e2f1e1bd2e1778e275c60716217 Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 14 Dec 2016 20:19:27 -0500 Subject: [PATCH 08/30] Loading assets into Streams component. --- .../src/containers/Streams/Streams.css | 4 +++ .../src/containers/Streams/Streams.js | 26 ++++++++++--------- client/coral-admin/src/translations.json | 14 ++++++++-- 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/client/coral-admin/src/containers/Streams/Streams.css b/client/coral-admin/src/containers/Streams/Streams.css index 7ab1b5199..5ad875b35 100644 --- a/client/coral-admin/src/containers/Streams/Streams.css +++ b/client/coral-admin/src/containers/Streams/Streams.css @@ -46,6 +46,10 @@ margin-top: 3px; } +.streamsTable { + width: 100%; +} + .radio { display: block; } diff --git a/client/coral-admin/src/containers/Streams/Streams.js b/client/coral-admin/src/containers/Streams/Streams.js index ea365d0d1..7b1778012 100644 --- a/client/coral-admin/src/containers/Streams/Streams.js +++ b/client/coral-admin/src/containers/Streams/Streams.js @@ -30,15 +30,17 @@ class Streams extends Component { renderDate = (date) => { const d = new Date(date); - return `${d.getMonth()}/${d.getDate()}/${d.getYear()}`; + return `${d.getMonth() + 1}/${d.getDate()}/${d.getFullYear()}`; } - renderStatus = (status) => { + renderStatus = (closedAt) => { + const status = closedAt && new Date(closedAt) < Date.now ? lang.t('streams.closed') : lang.t('streams.open'); return
    {status}
    ; } render () { const {searchTerm, sortBy, statusFilter} = this.state; + const {assets} = this.props; return
    @@ -77,17 +79,13 @@ class Streams extends Component {
    - {lang.t('streams.article')} - + className={styles.streamsTable} + rows={assets.ids.map((id) => assets.byId[id])}> + {lang.t('streams.article')} + {lang.t('streams.pubdate')} - + {lang.t('streams.status')} @@ -96,7 +94,11 @@ class Streams extends Component { } } -const mapStateToProps = () => {}; +const mapStateToProps = ({assets}) => { + return { + assets: assets.toJS() + }; +}; const mapDispatchToProps = (dispatch) => { return { fetchAssets: (skip, limit, search, sort) => { diff --git a/client/coral-admin/src/translations.json b/client/coral-admin/src/translations.json index da859c49c..c093f7ae0 100644 --- a/client/coral-admin/src/translations.json +++ b/client/coral-admin/src/translations.json @@ -79,7 +79,12 @@ "closed": "Closed", "newest": "Newest", "oldest": "Oldest", - "sort-by": "Sort By" + "sort-by": "Sort By", + "open": "Open", + "closed": "Closed", + "article": "Article", + "pubdate": "Publication Date", + "status": "Status" } }, "es": { @@ -150,7 +155,12 @@ "closed": "", "newest": "", "oldest": "", - "sort-by": "" + "sort-by": "", + "open": "", + "closed": "", + "article": "", + "pubdate": "", + "status": "" } } } From 86eef68b163e9412a24a478a0e2dd3644cebf0b4 Mon Sep 17 00:00:00 2001 From: David Jay Date: Thu, 15 Dec 2016 12:25:43 -0500 Subject: [PATCH 09/30] Adding status menu. --- client/coral-admin/src/actions/assets.js | 13 +++++ client/coral-admin/src/constants/assets.js | 5 +- .../src/containers/Streams/Streams.css | 24 ++++++++ .../src/containers/Streams/Streams.js | 55 ++++++++++++++----- client/coral-admin/src/reducers/assets.js | 3 +- .../coral-admin/src/services/talk-adapter.js | 6 +- 6 files changed, 88 insertions(+), 18 deletions(-) create mode 100644 client/coral-admin/src/actions/assets.js diff --git a/client/coral-admin/src/actions/assets.js b/client/coral-admin/src/actions/assets.js new file mode 100644 index 000000000..1e5f2716d --- /dev/null +++ b/client/coral-admin/src/actions/assets.js @@ -0,0 +1,13 @@ +import {FETCH_ASSETS, UPDATE_ASSET} from '../constants/assets'; + +/** + * Action disptacher related to assets + */ + +export const updateAsset = (id, property, value) => (dispatch) => { + dispatch({type: UPDATE_ASSET, id, property, value}); +}; + +export const fetchAssets = (skip, limit, search, sort) => (dispatch) => { + dispatch({type: FETCH_ASSETS, skip, limit, search, sort}); +}; diff --git a/client/coral-admin/src/constants/assets.js b/client/coral-admin/src/constants/assets.js index 59b76390f..b13534726 100644 --- a/client/coral-admin/src/constants/assets.js +++ b/client/coral-admin/src/constants/assets.js @@ -1 +1,4 @@ -export const ASSETS_FETCH = 'ASSETS_FETCH'; +export const FETCH_ASSETS = 'FETCH_ASSETS'; +export const FETCH_ASSETS_SUCCESS = 'FETCH_ASSETS_SUCCESS'; +export const FETCH_ASSETS_FAILED = 'FETCH_ASSETS_FAILED'; +export const UPDATE_ASSET = 'UPDATE_ASSET'; diff --git a/client/coral-admin/src/containers/Streams/Streams.css b/client/coral-admin/src/containers/Streams/Streams.css index 5ad875b35..c64de7974 100644 --- a/client/coral-admin/src/containers/Streams/Streams.css +++ b/client/coral-admin/src/containers/Streams/Streams.css @@ -54,6 +54,30 @@ display: block; } +.statusMenu { + border-radius: 3px; + width: 10em; + text-align: center; + float: right; + border: 1px solid #ccc; + color: #fff; + cursor: pointer; +} + +.statusMenuOpen { + padding: 10px; + background-color: #4caf50; +} + +.statusMenuIcon { + float: right; +} + +.statusMenuClosed { + padding: 10px; + background-color: #000; +} + .hidden { display: none; } diff --git a/client/coral-admin/src/containers/Streams/Streams.js b/client/coral-admin/src/containers/Streams/Streams.js index 7b1778012..078f7c98a 100644 --- a/client/coral-admin/src/containers/Streams/Streams.js +++ b/client/coral-admin/src/containers/Streams/Streams.js @@ -2,7 +2,7 @@ import React, {Component} from 'react'; import styles from './Streams.css'; import {connect} from 'react-redux'; import I18n from 'coral-framework/modules/i18n/i18n'; -import {ASSETS_FETCH} from '../../constants/assets'; +import {fetchAssets, updateAsset} from '../../actions/assets'; import translations from '../../translations.json'; import { RadioGroup, @@ -17,7 +17,8 @@ class Streams extends Component { state = { searchTerm: '', sortBy: 'newest', - statusFilter: 'all' + statusFilter: 'all', + statusMenus: {} } componentDidMount () { @@ -33,9 +34,40 @@ class Streams extends Component { return `${d.getMonth() + 1}/${d.getDate()}/${d.getFullYear()}`; } - renderStatus = (closedAt) => { - const status = closedAt && new Date(closedAt) < Date.now ? lang.t('streams.closed') : lang.t('streams.open'); - return
    {status}
    ; + onStatusClick = (closeStream, id, statusMenuOpen) => () => { + if (statusMenuOpen) { + this.setState(prev => { + prev.statusMenus[id] = false; + return prev; + }); + this.props.updateAsset(id, 'closedAt', closeStream ? Date.now() : null); + } else { + this.setState(prev => { + prev.statusMenus[id] = true; + return prev; + }); + } + } + + renderStatus = (closedAt, id) => { + const closed = closedAt && new Date(closedAt) < Date.now; + const statusMenuOpen = this.state.statusMenus[id]; + return
    +
    + {closed ? lang.t('streams.closed') : lang.t('streams.open')} + {!statusMenuOpen && } +
    + { + statusMenuOpen && +
    + {!closed ? lang.t('streams.closed') : lang.t('streams.open')} +
    + } +
    ; } render () { @@ -101,14 +133,11 @@ const mapStateToProps = ({assets}) => { }; const mapDispatchToProps = (dispatch) => { return { - fetchAssets: (skip, limit, search, sort) => { - dispatch({ - type: ASSETS_FETCH, - skip, - limit, - search, - sort - }); + fetchAssets: (...args) => { + dispatch(fetchAssets.apply(this, args)); + }, + updateAsset: (...args) => { + dispatch(updateAsset.apply(this, args)); } }; }; diff --git a/client/coral-admin/src/reducers/assets.js b/client/coral-admin/src/reducers/assets.js index bdd1bcaf9..a1ca4c831 100644 --- a/client/coral-admin/src/reducers/assets.js +++ b/client/coral-admin/src/reducers/assets.js @@ -1,4 +1,5 @@ import {Map, List, fromJS} from 'immutable'; +import {FETCH_ASSETS_SUCCESS} from '../constants/assets'; const initialState = Map({ byId: Map(), @@ -7,7 +8,7 @@ const initialState = Map({ export default (state = initialState, action) => { switch (action.type) { - case 'ASSETS_FETCH_SUCCESS': return replaceAssets(action, state); + case FETCH_ASSETS_SUCCESS: return replaceAssets(action, state); default: return state; } }; diff --git a/client/coral-admin/src/services/talk-adapter.js b/client/coral-admin/src/services/talk-adapter.js index 6dc4d5827..1b824c13e 100644 --- a/client/coral-admin/src/services/talk-adapter.js +++ b/client/coral-admin/src/services/talk-adapter.js @@ -22,7 +22,7 @@ export default store => next => action => { return createComment(store, action.name, action.body); case 'USER_BAN': return userStatusUpdate(store, action.status, action.userId, action.commentId); - case 'ASSETS_FETCH': + case 'FETCH_ASSETS': return fetchAssets(store, action); } }; @@ -97,9 +97,9 @@ const fetchAssets = (store, action) => { return coralApi(`/assets?skip=${skip}&limit=${limit}&search=${search}`) .then(({result, count}) => /* Post comments and users to redux store. Actions will be posted when they are needed. */ - store.dispatch({type: 'ASSETS_FETCH_SUCCESS', + store.dispatch({type: 'FETCH_ASSETS_SUCCESS', assets: result, count })) - .catch(error => store.dispatch({type: 'ASSETS_FETCH_FAILED', error})); + .catch(error => store.dispatch({type: 'FETCH_ASSETS_FAILED', error})); }; From c7b232f0b3e89aafbe64531826138a323479581e Mon Sep 17 00:00:00 2001 From: David Jay Date: Thu, 15 Dec 2016 13:10:23 -0500 Subject: [PATCH 10/30] Adding action to upate asset state. --- client/coral-admin/src/actions/assets.js | 6 +- client/coral-admin/src/constants/assets.js | 4 +- .../coral-admin/src/services/talk-adapter.js | 28 ++++++- tests/client/coral-admin/reducers/assets.js | 4 +- .../coral-admin/services/talk-adapter.js | 73 ++++++++++++++----- 5 files changed, 87 insertions(+), 28 deletions(-) diff --git a/client/coral-admin/src/actions/assets.js b/client/coral-admin/src/actions/assets.js index 1e5f2716d..5918d1df8 100644 --- a/client/coral-admin/src/actions/assets.js +++ b/client/coral-admin/src/actions/assets.js @@ -1,11 +1,11 @@ -import {FETCH_ASSETS, UPDATE_ASSET} from '../constants/assets'; +import {FETCH_ASSETS, UPDATE_ASSET_STATE} from '../constants/assets'; /** * Action disptacher related to assets */ -export const updateAsset = (id, property, value) => (dispatch) => { - dispatch({type: UPDATE_ASSET, id, property, value}); +export const updateAssetState = (id, property, value) => (dispatch) => { + dispatch({type: UPDATE_ASSET_STATE, id, property, value}); }; export const fetchAssets = (skip, limit, search, sort) => (dispatch) => { diff --git a/client/coral-admin/src/constants/assets.js b/client/coral-admin/src/constants/assets.js index b13534726..8927b351d 100644 --- a/client/coral-admin/src/constants/assets.js +++ b/client/coral-admin/src/constants/assets.js @@ -1,4 +1,6 @@ export const FETCH_ASSETS = 'FETCH_ASSETS'; export const FETCH_ASSETS_SUCCESS = 'FETCH_ASSETS_SUCCESS'; export const FETCH_ASSETS_FAILED = 'FETCH_ASSETS_FAILED'; -export const UPDATE_ASSET = 'UPDATE_ASSET'; +export const UPDATE_ASSET_STATE = 'UPDATE_ASSET_STATE'; +export const UPDATE_ASSET_STATE_SUCCESS = 'UPDATE_ASSET_STATE_SUCCESS'; +export const UPDATE_ASSET_STATE_FAILED = 'UPDATE_ASSET_STATE_FAILED'; diff --git a/client/coral-admin/src/services/talk-adapter.js b/client/coral-admin/src/services/talk-adapter.js index 1b824c13e..dea4d7cb2 100644 --- a/client/coral-admin/src/services/talk-adapter.js +++ b/client/coral-admin/src/services/talk-adapter.js @@ -1,5 +1,12 @@ import coralApi from '../../../coral-framework/helpers/response'; - +import { + FETCH_ASSETS, + FETCH_ASSETS_FAILED, + FETCH_ASSETS_SUCCESS, + UPDATE_ASSET_STATE, + UPDATE_ASSET_STATE_SUCCESS, + UPDATE_ASSET_STATE_FAILED, +} from '../constants/assets'; /** * The adapter is a redux middleware that interecepts the actions that need * to interface with the backend, do the job and return the results. @@ -22,8 +29,10 @@ export default store => next => action => { return createComment(store, action.name, action.body); case 'USER_BAN': return userStatusUpdate(store, action.status, action.userId, action.commentId); - case 'FETCH_ASSETS': + case FETCH_ASSETS: return fetchAssets(store, action); + case UPDATE_ASSET_STATE: + return updateAssetState(store, action); } }; @@ -97,9 +106,20 @@ const fetchAssets = (store, action) => { return coralApi(`/assets?skip=${skip}&limit=${limit}&search=${search}`) .then(({result, count}) => /* Post comments and users to redux store. Actions will be posted when they are needed. */ - store.dispatch({type: 'FETCH_ASSETS_SUCCESS', + store.dispatch({type: FETCH_ASSETS_SUCCESS, assets: result, count })) - .catch(error => store.dispatch({type: 'FETCH_ASSETS_FAILED', error})); + .catch(error => store.dispatch({type: FETCH_ASSETS_FAILED, error})); +}; + +// Update an asset state +// Get comments to fill each of the three lists on the mod queue +const updateAssetState = (store, action) => { + const {id, closedAt} = action; + return coralApi(`/assets/${id}/status`, {method: 'PUT', body: {closedAt}}) + .then(() => + /* Post comments and users to redux store. Actions will be posted when they are needed. */ + store.dispatch({type: UPDATE_ASSET_STATE_SUCCESS, closedAt})) + .catch(error => store.dispatch({type: UPDATE_ASSET_STATE_FAILED, error})); }; diff --git a/tests/client/coral-admin/reducers/assets.js b/tests/client/coral-admin/reducers/assets.js index cf3e0dac4..2a3c15b45 100644 --- a/tests/client/coral-admin/reducers/assets.js +++ b/tests/client/coral-admin/reducers/assets.js @@ -3,10 +3,10 @@ import {expect} from 'chai'; import assetsReducer from '../../../../client/coral-admin/src/reducers/assets'; describe ('assetsReducer', () => { - describe('ASSETS_FETCH_SUCCESS', () => { + describe('FETCH_ASSETS_SUCCESS', () => { it('should replace the existing assets', () => { const action = { - type: 'ASSETS_FETCH_SUCCESS', + type: 'FETCH_ASSETS_SUCCESS', count: 200, assets: [ { diff --git a/tests/client/coral-admin/services/talk-adapter.js b/tests/client/coral-admin/services/talk-adapter.js index 27ae8efcb..b30ae3905 100644 --- a/tests/client/coral-admin/services/talk-adapter.js +++ b/tests/client/coral-admin/services/talk-adapter.js @@ -12,30 +12,30 @@ const mockStore = configureStore(); describe('talk-adapter.js', () => { let store; + const assets = [ + { + url: 'http://test.com', + id: '123', + status: 'closed' + }, + { + url: 'http://test.org', + id: '456', + status: 'open' + } + ]; + beforeEach(() => { store = mockStore(new Map({})); fetchMock.restore(); }); - describe('ASSETS_FETCH', () => { - - const assets = [ - { - url: 'http://test.com', - id: '123', - status: 'closed' - }, - { - url: 'http://test.org', - id: '456', - status: 'open' - } - ]; + describe('FETCH_ASSETS', () => { it('should fetch a list of assets', () => { const action = { - type: 'ASSETS_FETCH', + type: 'FETCH_ASSETS', skip: 2, limit: 20, search: '' @@ -48,7 +48,7 @@ describe('talk-adapter.js', () => { return adapter(store)(()=>{})(action) .then(() => { - expect(store.getActions()[0]).to.have.property('type', 'ASSETS_FETCH_SUCCESS'); + expect(store.getActions()[0]).to.have.property('type', 'FETCH_ASSETS_SUCCESS'); expect(store.getActions()[0]).to.have.property('count', 2); expect(store.getActions()[0]).to.have.property('assets'). and.to.deep.equal(assets); @@ -58,7 +58,7 @@ describe('talk-adapter.js', () => { it('should return an error appropriatly', () => { const action = { - type: 'ASSETS_FETCH', + type: 'FETCH_ASSETS', skip: 2, limit: 20, search: '' @@ -68,7 +68,44 @@ describe('talk-adapter.js', () => { return adapter(store)(()=>{})(action) .then(() => { - expect(store.getActions()[0]).to.have.property('type', 'ASSETS_FETCH_FAILED'); + expect(store.getActions()[0]).to.have.property('type', 'FETCH_ASSETS_FAILED'); + }); + }); + }); + + describe('UPDATE_ASSET_STATE', () => { + + it('should update an asset', () => { + const action = { + type: 'UPDATE_ASSET_STATE', + id: '123', + property: 'closedAt', + value: Date.now() + }; + + fetchMock.put('*', JSON.stringify(assets[0])); + + return adapter(store)(()=>{})(action) + .then(() => { + expect(store.getActions()[0]).to.have.property('type', 'UPDATE_ASSET_STATE_SUCCESS'); + }); + + }); + + it('should return an error appropriately', () => { + + const action = { + type: 'UPDATE_ASSET_STATE', + id: '123', + property: 'closedAt', + value: Date.now() + }; + + fetchMock.put('*', 404); + + return adapter(store)(()=>{})(action) + .then(() => { + expect(store.getActions()[0]).to.have.property('type', 'UPDATE_ASSET_STATE_FAILED'); }); }); }); From 328eaad8e521a35e7fce4dfa1207f6e6adbf8937 Mon Sep 17 00:00:00 2001 From: David Jay Date: Thu, 15 Dec 2016 14:05:10 -0500 Subject: [PATCH 11/30] Adding update state to reducer. --- client/coral-admin/src/reducers/assets.js | 7 +++-- tests/client/coral-admin/reducers/assets.js | 29 +++++++++++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/client/coral-admin/src/reducers/assets.js b/client/coral-admin/src/reducers/assets.js index a1ca4c831..f18aaec33 100644 --- a/client/coral-admin/src/reducers/assets.js +++ b/client/coral-admin/src/reducers/assets.js @@ -1,5 +1,5 @@ import {Map, List, fromJS} from 'immutable'; -import {FETCH_ASSETS_SUCCESS} from '../constants/assets'; +import {FETCH_ASSETS_SUCCESS, UPDATE_ASSET_STATE} from '../constants/assets'; const initialState = Map({ byId: Map(), @@ -8,7 +8,10 @@ const initialState = Map({ export default (state = initialState, action) => { switch (action.type) { - case FETCH_ASSETS_SUCCESS: return replaceAssets(action, state); + case FETCH_ASSETS_SUCCESS: + return replaceAssets(action, state); + case UPDATE_ASSET_STATE: + return state.setIn(['byId', action.id, 'closedAt'], action.closedAt); default: return state; } }; diff --git a/tests/client/coral-admin/reducers/assets.js b/tests/client/coral-admin/reducers/assets.js index 2a3c15b45..60972f47a 100644 --- a/tests/client/coral-admin/reducers/assets.js +++ b/tests/client/coral-admin/reducers/assets.js @@ -1,4 +1,4 @@ -import {Map} from 'immutable'; +import {Map, fromJS} from 'immutable'; import {expect} from 'chai'; import assetsReducer from '../../../../client/coral-admin/src/reducers/assets'; @@ -23,7 +23,6 @@ describe ('assetsReducer', () => { }; const store = new Map({}); const result = assetsReducer(store, action); - console.log(result.getIn(['byId', '123']).toJS()); expect(result.getIn(['byId', '123']).toJS()).to.deep.equal({ url: 'http://test.com', closedAt: 'tomorrow', @@ -36,4 +35,30 @@ describe ('assetsReducer', () => { expect(result.getIn(['count'])).to.equal(200); }); }); + + describe('UPDATE_ASSET_STATE', () => { + it('should update the state of a particular asset', () => { + const action = { + type: 'UPDATE_ASSET_STATE', + id: '123', + closedAt: null + }; + const store = new fromJS({ + byId: { + '123': { + id: '123', + url: 'http://test.com', + closedAt: Date.now() + }, + '456': { + id: '456', + url: 'http://test2.com', + closedAt: 'thursday' + } + } + }); + const result = assetsReducer(store, action); + expect(result.getIn(['byId', '123', 'closedAt'])).to.equal.null; + }); + }); }); From 1154b65c9166b86370e5e070faeb859cad606fd0 Mon Sep 17 00:00:00 2001 From: David Jay Date: Thu, 15 Dec 2016 14:19:44 -0500 Subject: [PATCH 12/30] Updating state from menu. --- client/coral-admin/src/actions/assets.js | 4 ++-- client/coral-admin/src/containers/Streams/Streams.js | 12 ++++++------ client/coral-admin/src/services/talk-adapter.js | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/client/coral-admin/src/actions/assets.js b/client/coral-admin/src/actions/assets.js index 5918d1df8..b13454a21 100644 --- a/client/coral-admin/src/actions/assets.js +++ b/client/coral-admin/src/actions/assets.js @@ -4,8 +4,8 @@ import {FETCH_ASSETS, UPDATE_ASSET_STATE} from '../constants/assets'; * Action disptacher related to assets */ -export const updateAssetState = (id, property, value) => (dispatch) => { - dispatch({type: UPDATE_ASSET_STATE, id, property, value}); +export const updateAssetState = (id, closedAt) => (dispatch) => { + dispatch({type: UPDATE_ASSET_STATE, id, closedAt}); }; export const fetchAssets = (skip, limit, search, sort) => (dispatch) => { diff --git a/client/coral-admin/src/containers/Streams/Streams.js b/client/coral-admin/src/containers/Streams/Streams.js index 078f7c98a..53db75c89 100644 --- a/client/coral-admin/src/containers/Streams/Streams.js +++ b/client/coral-admin/src/containers/Streams/Streams.js @@ -2,7 +2,7 @@ import React, {Component} from 'react'; import styles from './Streams.css'; import {connect} from 'react-redux'; import I18n from 'coral-framework/modules/i18n/i18n'; -import {fetchAssets, updateAsset} from '../../actions/assets'; +import {fetchAssets, updateAssetState} from '../../actions/assets'; import translations from '../../translations.json'; import { RadioGroup, @@ -40,7 +40,7 @@ class Streams extends Component { prev.statusMenus[id] = false; return prev; }); - this.props.updateAsset(id, 'closedAt', closeStream ? Date.now() : null); + this.props.updateAssetState(id, closeStream ? Date.now() : null); } else { this.setState(prev => { prev.statusMenus[id] = true; @@ -49,8 +49,8 @@ class Streams extends Component { } } - renderStatus = (closedAt, id) => { - const closed = closedAt && new Date(closedAt) < Date.now; + renderStatus = (closedAt, {id}) => { + const closed = closedAt && new Date(closedAt).getTime() < Date.now(); const statusMenuOpen = this.state.statusMenus[id]; return
    { fetchAssets: (...args) => { dispatch(fetchAssets.apply(this, args)); }, - updateAsset: (...args) => { - dispatch(updateAsset.apply(this, args)); + updateAssetState: (...args) => { + dispatch(updateAssetState.apply(this, args)); } }; }; diff --git a/client/coral-admin/src/services/talk-adapter.js b/client/coral-admin/src/services/talk-adapter.js index dea4d7cb2..67835e30d 100644 --- a/client/coral-admin/src/services/talk-adapter.js +++ b/client/coral-admin/src/services/talk-adapter.js @@ -120,6 +120,6 @@ const updateAssetState = (store, action) => { return coralApi(`/assets/${id}/status`, {method: 'PUT', body: {closedAt}}) .then(() => /* Post comments and users to redux store. Actions will be posted when they are needed. */ - store.dispatch({type: UPDATE_ASSET_STATE_SUCCESS, closedAt})) + store.dispatch({type: UPDATE_ASSET_STATE_SUCCESS})) .catch(error => store.dispatch({type: UPDATE_ASSET_STATE_FAILED, error})); }; From d4880ff183423306cf925e12d8f9816f69a7f00c Mon Sep 17 00:00:00 2001 From: David Jay Date: Fri, 16 Dec 2016 15:35:15 -0500 Subject: [PATCH 13/30] Adding asset sorting to endpoint. --- .../src/containers/Streams/Streams.js | 13 ++++--- routes/api/assets/index.js | 28 +++++++++++--- tests/routes/api/assets/index.js | 38 ++++++++++++++++++- 3 files changed, 67 insertions(+), 12 deletions(-) diff --git a/client/coral-admin/src/containers/Streams/Streams.js b/client/coral-admin/src/containers/Streams/Streams.js index 53db75c89..5d8508f33 100644 --- a/client/coral-admin/src/containers/Streams/Streams.js +++ b/client/coral-admin/src/containers/Streams/Streams.js @@ -12,21 +12,24 @@ import { TableHeader } from 'react-mdl'; +const limit = 25; + class Streams extends Component { state = { - searchTerm: '', - sortBy: 'newest', + search: '', + sort: 'desc', statusFilter: 'all', statusMenus: {} } componentDidMount () { - this.props.fetchAssets(0, 25, '', 'desc'); + this.props.fetchAssets(0, limit, '', this.state.sortBy); } onSettingChange = (setting) => (e) => { this.setState({[setting]: e.target.value}); + this.props.fetchAssets(0, limit, this.state.search, this.state.sort); } renderDate = (date) => { @@ -104,8 +107,8 @@ class Streams extends Component { value={sortBy} childContainer='div' onChange={this.onSettingChange('sortBy')}> - {lang.t('streams.newest')} - {lang.t('streams.oldest')} + {lang.t('streams.newest')} + {lang.t('streams.oldest')}
    diff --git a/routes/api/assets/index.js b/routes/api/assets/index.js index a602d7fd0..a661d64e8 100644 --- a/routes/api/assets/index.js +++ b/routes/api/assets/index.js @@ -12,22 +12,40 @@ router.get('/', (req, res, next) => { skip = 0, sort = 'asc', field = 'created_at', + filter = 'all', search = '' } = req.query; + const assets = (filter, search) => { + switch(filter) { + case 'open': + return Asset.search(search) + .find({ + $or: [ + {closedAt: null}, + {closedAt: {$gt: Date.now()}} + ] + }); + case 'closed': + return Asset.search(search) + .find({ + closedAt: {$lt: Date.now()} + }); + default: + return Asset.search(search); + } + }; + // Find all the assets. Promise.all([ - Asset - .search(search) + assets(filter, search) .sort({[field]: (sort === 'asc') ? 1 : -1}) .skip(parseInt(skip)) .limit(parseInt(limit)), - Asset - .search(search) + assets(filter, search) .count() ]) .then(([result, count]) => { - // Send back the asset data. res.json({ result, diff --git a/tests/routes/api/assets/index.js b/tests/routes/api/assets/index.js index 8e94c81fc..da011bf7b 100644 --- a/tests/routes/api/assets/index.js +++ b/tests/routes/api/assets/index.js @@ -18,12 +18,14 @@ describe('/api/v1/assets', () => { url: 'https://coralproject.net/news/asset1', title: 'Asset 1', description: 'term1', - id: '1' + id: '1', + closedAt: Date.now() }, { url: 'https://coralproject.net/news/asset2', title: 'Asset 2', - description: 'term2' + description: 'term2', + closedAt: null } ]); }); @@ -81,6 +83,38 @@ describe('/api/v1/assets', () => { }); }); + it('should return only closed assets', () => { + return chai.request(app) + .get('/api/v1/assets?filter=closed') + .set(passport.inject({roles: ['admin']})) + .then((res) => { + const body = res.body; + + expect(body).to.have.property('count', 1); + expect(body).to.have.property('result'); + + const assets = body.result; + + expect(assets[0]).to.have.property('title', 'Asset 1'); + }); + }); + + it('should return only opened assets', () => { + return chai.request(app) + .get('/api/v1/assets?filter=open') + .set(passport.inject({roles: ['admin']})) + .then((res) => { + const body = res.body; + + expect(body).to.have.property('count', 1); + expect(body).to.have.property('result'); + + const assets = body.result; + + expect(assets[0]).to.have.property('title', 'Asset 2'); + }); + }); + }); describe('#put', () => { From 979b54166bbb3060b3944d612f4bf66e332a6ebc Mon Sep 17 00:00:00 2001 From: David Jay Date: Fri, 16 Dec 2016 16:24:32 -0500 Subject: [PATCH 14/30] Updating search with delay. --- client/coral-admin/src/actions/assets.js | 4 +-- .../src/containers/Streams/Streams.js | 35 +++++++++++++------ .../coral-admin/src/services/talk-adapter.js | 4 +-- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/client/coral-admin/src/actions/assets.js b/client/coral-admin/src/actions/assets.js index b13454a21..df2e1bd60 100644 --- a/client/coral-admin/src/actions/assets.js +++ b/client/coral-admin/src/actions/assets.js @@ -8,6 +8,6 @@ export const updateAssetState = (id, closedAt) => (dispatch) => { dispatch({type: UPDATE_ASSET_STATE, id, closedAt}); }; -export const fetchAssets = (skip, limit, search, sort) => (dispatch) => { - dispatch({type: FETCH_ASSETS, skip, limit, search, sort}); +export const fetchAssets = (skip, limit, search, sort, filter) => (dispatch) => { + dispatch({type: FETCH_ASSETS, skip, limit, search, sort, filter}); }; diff --git a/client/coral-admin/src/containers/Streams/Streams.js b/client/coral-admin/src/containers/Streams/Streams.js index 5d8508f33..58c1c9b61 100644 --- a/client/coral-admin/src/containers/Streams/Streams.js +++ b/client/coral-admin/src/containers/Streams/Streams.js @@ -19,8 +19,9 @@ class Streams extends Component { state = { search: '', sort: 'desc', - statusFilter: 'all', - statusMenus: {} + filter: 'all', + statusMenus: {}, + timer: null } componentDidMount () { @@ -28,8 +29,22 @@ class Streams extends Component { } onSettingChange = (setting) => (e) => { + let options = this.state; this.setState({[setting]: e.target.value}); - this.props.fetchAssets(0, limit, this.state.search, this.state.sort); + options[setting] = e.target.value; + this.props.fetchAssets(0, limit, options.search, options.sort, options.filter); + } + + onSearchChange = (e) => { + this.setState({search: e.target.value}); + this.setState((prevState) => { + clearTimeout(prevState.timer); + const fetchAssets = this.props.fetchAssets; + prevState.timer = setTimeout(() => { + fetchAssets(0, limit, this.state.search, this.state.sort, this.state.filter); + }, 350); + return prevState; + }); } renderDate = (date) => { @@ -74,7 +89,7 @@ class Streams extends Component { } render () { - const {searchTerm, sortBy, statusFilter} = this.state; + const {search, sort, filter} = this.state; const {assets} = this.props; return
    @@ -84,9 +99,9 @@ class Streams extends Component {
    @@ -94,9 +109,9 @@ class Streams extends Component {
    {lang.t('streams.stream-status')}
    + onChange={this.onSettingChange('filter')}> {lang.t('streams.all')} {lang.t('streams.open')} {lang.t('streams.closed')} @@ -104,9 +119,9 @@ class Streams extends Component {
    {lang.t('streams.sort-by')}
    + onChange={this.onSettingChange('sort')}> {lang.t('streams.newest')} {lang.t('streams.oldest')} diff --git a/client/coral-admin/src/services/talk-adapter.js b/client/coral-admin/src/services/talk-adapter.js index 67835e30d..fcc334953 100644 --- a/client/coral-admin/src/services/talk-adapter.js +++ b/client/coral-admin/src/services/talk-adapter.js @@ -102,8 +102,8 @@ const userStatusUpdate = (store, status, userId, commentId) => { // Fetch a page of assets // Get comments to fill each of the three lists on the mod queue const fetchAssets = (store, action) => { - const {skip, limit, search} = action; - return coralApi(`/assets?skip=${skip}&limit=${limit}&search=${search}`) + const {skip, limit, sort, search, filter} = action; + return coralApi(`/assets?skip=${skip}&limit=${limit}&sort=${sort}&search=${search}&filter=${filter}`) .then(({result, count}) => /* Post comments and users to redux store. Actions will be posted when they are needed. */ store.dispatch({type: FETCH_ASSETS_SUCCESS, From 0d2d6b52a5d0468d020f672745844dfacb474ed8 Mon Sep 17 00:00:00 2001 From: David Jay Date: Fri, 16 Dec 2016 17:07:59 -0500 Subject: [PATCH 15/30] Adding pagination. --- .../src/containers/Community/Community.js | 2 +- .../src/containers/Streams/Streams.js | 19 +++++++++++++++++-- .../components}/Pager.css | 0 .../components}/Pager.js | 0 4 files changed, 18 insertions(+), 3 deletions(-) rename client/{coral-admin/src/containers/Community => coral-ui/components}/Pager.css (100%) rename client/{coral-admin/src/containers/Community => coral-ui/components}/Pager.js (100%) diff --git a/client/coral-admin/src/containers/Community/Community.js b/client/coral-admin/src/containers/Community/Community.js index e798266f0..286a7f364 100644 --- a/client/coral-admin/src/containers/Community/Community.js +++ b/client/coral-admin/src/containers/Community/Community.js @@ -7,7 +7,7 @@ import styles from './Community.css'; import Table from './Table'; import Loading from './Loading'; import NoResults from './NoResults'; -import Pager from './Pager'; +import Pager from 'coral-ui/components/Pager'; const lang = new I18n(translations); diff --git a/client/coral-admin/src/containers/Streams/Streams.js b/client/coral-admin/src/containers/Streams/Streams.js index 58c1c9b61..4841d2a1b 100644 --- a/client/coral-admin/src/containers/Streams/Streams.js +++ b/client/coral-admin/src/containers/Streams/Streams.js @@ -11,8 +11,9 @@ import { DataTable, TableHeader } from 'react-mdl'; +import Pager from 'coral-ui/components/Pager'; -const limit = 25; +const limit = 10; class Streams extends Component { @@ -21,7 +22,8 @@ class Streams extends Component { sort: 'desc', filter: 'all', statusMenus: {}, - timer: null + timer: null, + page: 0 } componentDidMount () { @@ -88,10 +90,18 @@ class Streams extends Component {
    ; } + onPageClick = (page) => { + this.setState({page}); + const {search, sort, filter} = this.state; + this.props.fetchAssets((page - 1) * limit, limit, search, sort, filter); + } + render () { const {search, sort, filter} = this.state; const {assets} = this.props; + console.log(assets); + return
    @@ -139,6 +149,11 @@ class Streams extends Component { {lang.t('streams.status')} +
    ; } diff --git a/client/coral-admin/src/containers/Community/Pager.css b/client/coral-ui/components/Pager.css similarity index 100% rename from client/coral-admin/src/containers/Community/Pager.css rename to client/coral-ui/components/Pager.css diff --git a/client/coral-admin/src/containers/Community/Pager.js b/client/coral-ui/components/Pager.js similarity index 100% rename from client/coral-admin/src/containers/Community/Pager.js rename to client/coral-ui/components/Pager.js From c34bf5036e10348796485af8643a52004a176437 Mon Sep 17 00:00:00 2001 From: David Jay Date: Fri, 16 Dec 2016 17:20:31 -0500 Subject: [PATCH 16/30] Updating page count. --- client/coral-admin/src/containers/Streams/Streams.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-admin/src/containers/Streams/Streams.js b/client/coral-admin/src/containers/Streams/Streams.js index 4841d2a1b..55fe10fc2 100644 --- a/client/coral-admin/src/containers/Streams/Streams.js +++ b/client/coral-admin/src/containers/Streams/Streams.js @@ -13,7 +13,7 @@ import { } from 'react-mdl'; import Pager from 'coral-ui/components/Pager'; -const limit = 10; +const limit = 25; class Streams extends Component { From 871836f636bcba97805b302b3034394a384c729c Mon Sep 17 00:00:00 2001 From: David Jay Date: Fri, 16 Dec 2016 18:19:05 -0500 Subject: [PATCH 17/30] Adding description field to flags. --- client/coral-embed-stream/style/default.css | 2 ++ client/coral-plugin-flags/FlagButton.js | 27 ++++++++++----------- client/coral-plugin-flags/FlagComment.js | 3 ++- client/coral-plugin-flags/translations.json | 6 +++-- models/action.js | 1 + 5 files changed, 22 insertions(+), 17 deletions(-) diff --git a/client/coral-embed-stream/style/default.css b/client/coral-embed-stream/style/default.css index 463c8dc68..bc5ce27bc 100644 --- a/client/coral-embed-stream/style/default.css +++ b/client/coral-embed-stream/style/default.css @@ -241,6 +241,7 @@ hr { .coral-plugin-flags-popup-radio-label { margin:5px; font-size: 14px; + font-weight: 700; } .coral-plugin-flags-popup-counter { @@ -256,6 +257,7 @@ hr { .coral-plugin-flags-other-text { margin-left: 20px; + margin-top: 5px; width: 75%; } diff --git a/client/coral-plugin-flags/FlagButton.js b/client/coral-plugin-flags/FlagButton.js index d0b9cb6b4..57b3d1946 100644 --- a/client/coral-plugin-flags/FlagButton.js +++ b/client/coral-plugin-flags/FlagButton.js @@ -10,10 +10,9 @@ class FlagButton extends Component { state = { showMenu: false, - showOther: false, itemType: '', detail: '', - otherText: '', + description: '', step: 0, posted: false } @@ -30,7 +29,7 @@ class FlagButton extends Component { onPopupContinue = () => { const {postAction, addItem, updateItem, flag, id, author_id} = this.props; - const {itemType, field, detail, step, otherText, posted} = this.state; + const {itemType, field, detail, step, description, posted} = this.state; //Proceed to the next step or close the menu if we've reached the end if (step + 1 >= this.props.getPopupMenu.length) { @@ -42,7 +41,6 @@ class FlagButton extends Component { // If itemType and detail are both set, post the action if (itemType && detail && !posted) { // Set the text from the "other" field if it exists. - const updatedDetail = otherText || detail; let item_id; switch(itemType) { case 'comments': @@ -55,7 +53,8 @@ class FlagButton extends Component { const action = { action_type: 'flag', field, - detail: updatedDetail + detail, + description }; postAction(item_id, itemType, action) .then((action) => { @@ -92,7 +91,7 @@ class FlagButton extends Component { } onOtherTextChange = (e) => { - this.setState({otherText: e.target.value}); + this.setState({description: e.target.value}); } handleClickOutside () { @@ -141,16 +140,16 @@ class FlagButton extends Component { ) } { - this.state.showOther &&
    - +
    +