From feaad2d6ca14e3c353b820c7803bdd25c3e2ffea Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 8 Mar 2017 19:01:08 -0700 Subject: [PATCH 1/6] Added loader, graphql --- graph/loaders/metrics.js | 49 ++++++++++++++++++++++++++++++++++- graph/resolvers/asset.js | 6 ++++- graph/resolvers/root_query.js | 4 +++ graph/typeDefs.graphql | 21 ++++++++++++--- 4 files changed, 75 insertions(+), 5 deletions(-) diff --git a/graph/loaders/metrics.js b/graph/loaders/metrics.js index a6fe5afe4..d3c602de8 100644 --- a/graph/loaders/metrics.js +++ b/graph/loaders/metrics.js @@ -4,6 +4,52 @@ const {objectCacheKeyFn} = require('./util'); const ActionModel = require('../../models/action'); +/** + * Returns the assets which have had comments made within the last time period. + */ +const getCommentActivityMetrics = ({loaders: {Assets, Comments}}, {from, to, limit}) => { + let assetMetrics = []; + + return Comments.aggregate([ + {$match: { + parent_id: null, + created_at: { + $gt: from, + $lt: to + } + }}, + {$group: { + _id: '$asset_id', + commentCount: { + $sum: 1 + } + }}, + {$project: { + _id: false, + asset_id: '$_id', + commentCount: '$commentCount' + }}, + {$sort: { + commentCount: -1 + }}, + {$limit: limit} + ]) + .then((results) => { + results = assetMetrics; + + return Assets.getByID.loadMany(results.map((result) => result.asset_id)); + }) + .then((assets) => assets.map((asset, i) => { + + // We're leveraging the fact that the comments returned by the aggregation + // query are in the request order that we just made, it's what the + // Assets.getByID loader does. + asset.commentCount = assetMetrics[i].commentCount; + + return asset; + })); +}; + /** * Returns a list of assets with action metadata included on the models. */ @@ -211,7 +257,8 @@ module.exports = (context) => ({ get: ({from, to, sort, limit}) => getAssetMetrics(context, {from, to, sort, limit}) }, Comments: { - get: ({from, to, sort, limit}) => getCommentMetrics(context, {from, to, sort, limit}) + get: ({from, to, sort, limit}) => getCommentMetrics(context, {from, to, sort, limit}), + getActivity: ({from, to, limit}) => getCommentActivityMetrics(context, {from, to, limit}), } } }); diff --git a/graph/resolvers/asset.js b/graph/resolvers/asset.js index 2077b9e7a..c0cffd1ae 100644 --- a/graph/resolvers/asset.js +++ b/graph/resolvers/asset.js @@ -10,7 +10,11 @@ const Asset = { parent_id: null }); }, - commentCount({id}, _, {loaders: {Comments}}) { + commentCount({id, commentCount}, _, {loaders: {Comments}}) { + if (commentCount) { + return commentCount; + } + return Comments.countByAssetID.load(id); }, settings({settings = null}, _, {loaders: {Settings}}) { diff --git a/graph/resolvers/root_query.js b/graph/resolvers/root_query.js index bdcdcef78..083526eff 100644 --- a/graph/resolvers/root_query.js +++ b/graph/resolvers/root_query.js @@ -71,6 +71,10 @@ const RootQuery = { return null; } + if (sort === 'COMMENTS') { + return Comments.getActivity({from, to, limit}); + } + return Comments.get({from, to, sort, limit}); }, diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 6cb4cb046..2821952e4 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -476,6 +476,21 @@ enum USER_STATUS { APPROVED } +enum COMMENT_METRICS_SORT { + + # Represents a LikeAction. + LIKE + + # Represents a FlagAction. + FLAG + + # Represents a don't agree action. + DONTAGREE + + # Represents activity. + ACTIVITY +} + type RootQuery { # Site wide settings and defaults. @@ -507,7 +522,7 @@ type RootQuery { # Comment metrics related to user actions are saturated into the comments # returned. - commentMetrics(from: Date!, to: Date!, sort: ACTION_TYPE!, limit: Int = 10): [Comment!] + commentMetrics(from: Date!, to: Date!, sort: COMMENT_METRICS_SORT!, limit: Int = 10): [Comment!] } ################################################################################ @@ -646,14 +661,14 @@ type SetCommentStatusResponse implements Response { type AddCommentTagResponse implements Response { # An array of errors relating to the mutation that occured. comment: Comment - errors: [UserError] + errors: [UserError] } # Response to removeCommentTag mutation type RemoveCommentTagResponse implements Response { # An array of errors relating to the mutation that occured. comment: Comment - errors: [UserError] + errors: [UserError] } # All mutations for the application are defined on this object. From 63220cb6a721ea4b88221f420d38616c07152590 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 8 Mar 2017 19:14:02 -0700 Subject: [PATCH 2/6] Some fixes to make it work --- graph/loaders/metrics.js | 11 ++++++----- graph/resolvers/root_query.js | 10 ++++++---- graph/typeDefs.graphql | 7 ++++--- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/graph/loaders/metrics.js b/graph/loaders/metrics.js index d3c602de8..a0408ed4d 100644 --- a/graph/loaders/metrics.js +++ b/graph/loaders/metrics.js @@ -3,14 +3,15 @@ const DataLoader = require('dataloader'); const {objectCacheKeyFn} = require('./util'); const ActionModel = require('../../models/action'); +const CommentModel = require('../../models/comment'); /** * Returns the assets which have had comments made within the last time period. */ -const getCommentActivityMetrics = ({loaders: {Assets, Comments}}, {from, to, limit}) => { +const getAssetActivityMetrics = ({loaders: {Assets}}, {from, to, limit}) => { let assetMetrics = []; - return Comments.aggregate([ + return CommentModel.aggregate([ {$match: { parent_id: null, created_at: { @@ -35,7 +36,7 @@ const getCommentActivityMetrics = ({loaders: {Assets, Comments}}, {from, to, lim {$limit: limit} ]) .then((results) => { - results = assetMetrics; + assetMetrics = results; return Assets.getByID.loadMany(results.map((result) => result.asset_id)); }) @@ -254,11 +255,11 @@ module.exports = (context) => ({ cacheKeyFn: objectCacheKeyFn('from', 'to') }), Assets: { - get: ({from, to, sort, limit}) => getAssetMetrics(context, {from, to, sort, limit}) + get: ({from, to, sort, limit}) => getAssetMetrics(context, {from, to, sort, limit}), + getActivity: ({from, to, limit}) => getAssetActivityMetrics(context, {from, to, limit}), }, Comments: { get: ({from, to, sort, limit}) => getCommentMetrics(context, {from, to, sort, limit}), - getActivity: ({from, to, limit}) => getCommentActivityMetrics(context, {from, to, limit}), } } }); diff --git a/graph/resolvers/root_query.js b/graph/resolvers/root_query.js index 083526eff..98dbfb7ed 100644 --- a/graph/resolvers/root_query.js +++ b/graph/resolvers/root_query.js @@ -63,6 +63,12 @@ const RootQuery = { return null; } + console.log({from, to, sort, limit}); + + if (sort === 'ACTIVITY') { + return Assets.getActivity({from, to, limit}); + } + return Assets.get({from, to, sort, limit}); }, @@ -71,10 +77,6 @@ const RootQuery = { return null; } - if (sort === 'COMMENTS') { - return Comments.getActivity({from, to, limit}); - } - return Comments.get({from, to, sort, limit}); }, diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 2821952e4..7e0cc534d 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -476,7 +476,8 @@ enum USER_STATUS { APPROVED } -enum COMMENT_METRICS_SORT { +# Metrics for the assets. +enum ASSET_METRICS_SORT { # Represents a LikeAction. LIKE @@ -518,11 +519,11 @@ type RootQuery { # Asset metrics related to user actions are saturated into the assets # returned. - assetMetrics(from: Date!, to: Date!, sort: ACTION_TYPE!, limit: Int = 10): [Asset!] + assetMetrics(from: Date!, to: Date!, sort: ASSET_METRICS_SORT!, limit: Int = 10): [Asset!] # Comment metrics related to user actions are saturated into the comments # returned. - commentMetrics(from: Date!, to: Date!, sort: COMMENT_METRICS_SORT!, limit: Int = 10): [Comment!] + commentMetrics(from: Date!, to: Date!, sort: ACTION_TYPE!, limit: Int = 10): [Comment!] } ################################################################################ From f6af847bfbb63dc2c7b621ae968ead8cb6573ba3 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Thu, 9 Mar 2017 16:05:09 -0700 Subject: [PATCH 3/6] add Activity widget --- .../containers/Dashboard/ActivityWidget.js | 49 +++++++++++++++++++ .../src/containers/Dashboard/Dashboard.js | 4 +- .../src/containers/Dashboard/FlagWidget.js | 15 ++++-- .../src/containers/Dashboard/LikeWidget.js | 16 ++++-- .../fragments/assetMetricsView.graphql | 1 + .../src/graphql/queries/metricsQuery.graphql | 3 ++ 6 files changed, 80 insertions(+), 8 deletions(-) create mode 100644 client/coral-admin/src/containers/Dashboard/ActivityWidget.js diff --git a/client/coral-admin/src/containers/Dashboard/ActivityWidget.js b/client/coral-admin/src/containers/Dashboard/ActivityWidget.js new file mode 100644 index 000000000..e1d0efe34 --- /dev/null +++ b/client/coral-admin/src/containers/Dashboard/ActivityWidget.js @@ -0,0 +1,49 @@ +import React, {PropTypes} from 'react'; +import {Link} from 'react-router'; +import styles from './Widget.css'; +import I18n from 'coral-framework/modules/i18n/i18n'; +import translations from 'coral-admin/src/translations'; + +const lang = new I18n(translations); + +const ActivityWidget = ({assets}) => { + return ( +
+

Articles with the most comments (parent)

+
+

{lang.t('streams.article')}

+

{lang.t('dashboard.comment_count')}

+
+
+ { + assets.length + ? assets.map(asset => { + return ( +
+ Moderate +

{asset.commentCount}

+ +

{asset.title}

+ +

{asset.author} — Published: {new Date(asset.created_at).toLocaleDateString()}

+
+ ); + }) + :
{lang.t('dashboard.no_activity')}
+ } +
+
+ ); +}; + +ActivityWidget.propTypes = { + assets: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.string, + url: PropTypes.string, + commentCount: PropTypes.number, + author: PropTypes.string, + created_at: PropTypes.string + })).isRequired +}; + +export default ActivityWidget; diff --git a/client/coral-admin/src/containers/Dashboard/Dashboard.js b/client/coral-admin/src/containers/Dashboard/Dashboard.js index 5b4a20282..f1259f796 100644 --- a/client/coral-admin/src/containers/Dashboard/Dashboard.js +++ b/client/coral-admin/src/containers/Dashboard/Dashboard.js @@ -5,6 +5,7 @@ import {connect} from 'react-redux'; import {getMetrics} from 'coral-admin/src/graphql/queries'; import FlagWidget from './FlagWidget'; import LikeWidget from './LikeWidget'; +import ActivityWidget from './ActivityWidget'; import {showBanUserDialog, hideBanUserDialog} from 'coral-admin/src/actions/moderation'; import I18n from 'coral-framework/modules/i18n/i18n'; import translations from 'coral-admin/src/translations'; @@ -47,7 +48,7 @@ class Dashboard extends React.Component { return ; } - const {data: {assetsByLike, assetsByFlag}} = this.props; + const {data: {assetsByLike, assetsByFlag, assetsByActivity}} = this.props; return (
@@ -61,6 +62,7 @@ class Dashboard extends React.Component {
+
); diff --git a/client/coral-admin/src/containers/Dashboard/FlagWidget.js b/client/coral-admin/src/containers/Dashboard/FlagWidget.js index 7d2ffe17a..468875d3d 100644 --- a/client/coral-admin/src/containers/Dashboard/FlagWidget.js +++ b/client/coral-admin/src/containers/Dashboard/FlagWidget.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {PropTypes} from 'react'; import {Link} from 'react-router'; import styles from './Widget.css'; import I18n from 'coral-framework/modules/i18n/i18n'; @@ -6,8 +6,7 @@ import translations from 'coral-admin/src/translations'; const lang = new I18n(translations); -const FlagWidget = (props) => { - const {assets} = props; +const FlagWidget = ({assets}) => { return (
@@ -39,4 +38,14 @@ const FlagWidget = (props) => { ); }; +FlagWidget.propTypes = { + assets: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.string, + url: PropTypes.string, + action_summaries: PropTypes.array, + author: PropTypes.string, + created_at: PropTypes.string + })).isRequired +}; + export default FlagWidget; diff --git a/client/coral-admin/src/containers/Dashboard/LikeWidget.js b/client/coral-admin/src/containers/Dashboard/LikeWidget.js index 436fbcaf3..b878c281b 100644 --- a/client/coral-admin/src/containers/Dashboard/LikeWidget.js +++ b/client/coral-admin/src/containers/Dashboard/LikeWidget.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {PropTypes} from 'react'; import {Link} from 'react-router'; import styles from './Widget.css'; import I18n from 'coral-framework/modules/i18n/i18n'; @@ -6,9 +6,7 @@ import translations from 'coral-admin/src/translations'; const lang = new I18n(translations); -const LikeWidget = (props) => { - - const {assets} = props; +const LikeWidget = ({assets}) => { return (
@@ -40,4 +38,14 @@ const LikeWidget = (props) => { ); }; +LikeWidget.propTypes = { + assets: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.string, + url: PropTypes.string, + action_summaries: PropTypes.array, + author: PropTypes.string, + created_at: PropTypes.string + })).isRequired +}; + export default LikeWidget; diff --git a/client/coral-admin/src/graphql/fragments/assetMetricsView.graphql b/client/coral-admin/src/graphql/fragments/assetMetricsView.graphql index 37335aeaa..c77fbc32b 100644 --- a/client/coral-admin/src/graphql/fragments/assetMetricsView.graphql +++ b/client/coral-admin/src/graphql/fragments/assetMetricsView.graphql @@ -4,6 +4,7 @@ fragment metrics on Asset { url author created_at + commentCount action_summaries { type: __typename actionCount diff --git a/client/coral-admin/src/graphql/queries/metricsQuery.graphql b/client/coral-admin/src/graphql/queries/metricsQuery.graphql index 42a9fb70e..f0dff8965 100644 --- a/client/coral-admin/src/graphql/queries/metricsQuery.graphql +++ b/client/coral-admin/src/graphql/queries/metricsQuery.graphql @@ -7,4 +7,7 @@ query Metrics ($from: Date!, $to: Date!) { assetsByLike: assetMetrics(from: $from, to: $to, sort: LIKE) { ...metrics } + assetsByActivity: assetMetrics(from: $from, to: $to, sort: ACTIVITY) { + ...metrics + } } From 4a10122f3442a5a96171c01821021e7b34451527 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Fri, 10 Mar 2017 12:15:47 -0700 Subject: [PATCH 4/6] add some translations --- client/coral-admin/src/containers/Dashboard/Widget.css | 1 - client/coral-admin/src/translations.json | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/client/coral-admin/src/containers/Dashboard/Widget.css b/client/coral-admin/src/containers/Dashboard/Widget.css index 401cd1fe7..587c51ada 100644 --- a/client/coral-admin/src/containers/Dashboard/Widget.css +++ b/client/coral-admin/src/containers/Dashboard/Widget.css @@ -19,7 +19,6 @@ padding-left: 10px; font-size: 1.5rem; font-weight: bold; - background-color: #e0e0e0; } .widgetTable { diff --git a/client/coral-admin/src/translations.json b/client/coral-admin/src/translations.json index 257148837..58788e546 100644 --- a/client/coral-admin/src/translations.json +++ b/client/coral-admin/src/translations.json @@ -122,6 +122,7 @@ "no_flags": "There have been no flags in the last 5 minutes! Hooray!", "no_likes": "There have been no likes in the last 5 minutes. All quiet.", "flags": "Flags", + "no_activity": "There haven't been any comments anywhere in the last five minutes.", "comment_count": "comments" }, "streams": { @@ -242,6 +243,7 @@ "no_flags": "¡Nadie ha marcado nada en los últimos 5 minutos! ¡Bravo!", "no_likes": "A nadie le ha gustado algún comentario en los últimos 5 minutos. Todo tranquilo.", "flags": "Marcados", + "no_activity": "No hubo comentarios en los ultimos 5 minutos", "comment_count": "comentarios" }, "streams": { From 13dad0c89554058710b25671132a143c02e2a3b7 Mon Sep 17 00:00:00 2001 From: David Erwin Date: Tue, 14 Mar 2017 17:07:48 -0400 Subject: [PATCH 5/6] Language update --- client/coral-admin/src/containers/Dashboard/ActivityWidget.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-admin/src/containers/Dashboard/ActivityWidget.js b/client/coral-admin/src/containers/Dashboard/ActivityWidget.js index e1d0efe34..a6e293b82 100644 --- a/client/coral-admin/src/containers/Dashboard/ActivityWidget.js +++ b/client/coral-admin/src/containers/Dashboard/ActivityWidget.js @@ -9,7 +9,7 @@ const lang = new I18n(translations); const ActivityWidget = ({assets}) => { return (
-

Articles with the most comments (parent)

+

Articles with the most conversations

{lang.t('streams.article')}

{lang.t('dashboard.comment_count')}

From 6758a178eb9837fdfdee32cbc09878661e1dee6f Mon Sep 17 00:00:00 2001 From: David Erwin Date: Tue, 14 Mar 2017 17:10:37 -0400 Subject: [PATCH 6/6] Removing log --- graph/resolvers/root_query.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/graph/resolvers/root_query.js b/graph/resolvers/root_query.js index 98dbfb7ed..e2836c51d 100644 --- a/graph/resolvers/root_query.js +++ b/graph/resolvers/root_query.js @@ -63,8 +63,6 @@ const RootQuery = { return null; } - console.log({from, to, sort, limit}); - if (sort === 'ACTIVITY') { return Assets.getActivity({from, to, limit}); }