diff --git a/client/coral-admin/src/components/FlagWidget.js b/client/coral-admin/src/components/FlagWidget.js index be167de8a..324b76c10 100644 --- a/client/coral-admin/src/components/FlagWidget.js +++ b/client/coral-admin/src/components/FlagWidget.js @@ -1,7 +1,6 @@ import React, {PropTypes} from 'react'; -import styles from './FlagWidget.css'; -const FlagWidget = props => { +const FlagWidget = () => { return ( diff --git a/graph/loaders/index.js b/graph/loaders/index.js index 536e40fa9..5b1894b65 100644 --- a/graph/loaders/index.js +++ b/graph/loaders/index.js @@ -3,6 +3,7 @@ const _ = require('lodash'); const Actions = require('./actions'); const Assets = require('./assets'); const Comments = require('./comments'); +const Metrics = require('./metrics'); const Settings = require('./settings'); const Users = require('./users'); @@ -18,6 +19,7 @@ module.exports = (context) => { Actions, Assets, Comments, + Metrics, Settings, Users ].map((loaders) => { diff --git a/graph/loaders/metrics.js b/graph/loaders/metrics.js new file mode 100644 index 000000000..a827ddeea --- /dev/null +++ b/graph/loaders/metrics.js @@ -0,0 +1,137 @@ +const _ = require('lodash'); +const CommentModel = require('../../models/comment'); +const AssetModel = require('../../models/asset'); +const ActionModel = require('../../models/action'); + +const getMetrics = (context, {from, to}) => { + + let commentMetrics = {}; + let assetMetrics = []; + + return ActionModel.aggregate([ + + // Find all actions that were created in the time range. + {$match: { + action_type: 'FLAG', + item_type: 'COMMENTS', + created_at: { + $gt: from, + $lt: to + } + }}, + + // Count all those items. + {$group: { + _id: '$item_id', + count: { + $sum: 1 + } + }}, + + // Project the count to a better field. + {$project: { + item_id: '$_id', + count: '$count' + }} + ]).then((actionSummaries) => { + + // Collect all the action summaries into a dictionary. + actionSummaries.forEach((actionSummary) => { + commentMetrics[actionSummary.item_id] = actionSummary.count; + }); + + // Collect just the comment id's. + let commentIDs = actionSummaries.map((as) => as.item_id); + + // Find those comments. + return CommentModel.aggregate([ + + // Get only those comments. + {$match: { + id: { + $in: commentIDs + } + }}, + + // Group by their asset id and push in the comment id. + {$group: { + _id: { + asset_id: '$asset_id' + }, + ids: { + $addToSet: '$id' + } + }}, + + // Project that data only as better fields. + {$project: { + asset_id: '$_id.asset_id', + ids: '$ids' + }} + ]); + }) + .then((commentResults) => { + + // Compute all the action summaries for the assets based on the time slice + // that you requested. + commentResults.forEach((result) => { + let actionCount = 0; + + result.ids.forEach((id) => { + actionCount += commentMetrics[id]; + }); + + assetMetrics.push({ + id: result.asset_id, + actionCount, + actionableItemCount: result.ids.length + }); + }); + + // Sort the assets by flag count. + assetMetrics.sort((a, b) => { + return b.flags - a.flags; + }); + + // Only keep the top 10. + assetMetrics = assetMetrics.slice(0, 10); + + // Determine the assets that we need to return. + return AssetModel.find({ + id: { + $in: assetMetrics.map((asset) => asset.id) + } + }); + }) + .then((assets) => { + + // Join up the assets that are returned by their id. + let groupedAssets = _.groupBy(assets, 'id'); + + // Return from the sorted asset metrics and return their assetes. + return assetMetrics.map(({id, actionCount, actionableItemCount}) => { + if (id in groupedAssets) { + let asset = groupedAssets[id][0]; + + let flagAssetActionSummary = { + action_type: 'FLAG', + actionCount, + actionableItemCount + }; + + // Add the action summaries to the asset. + asset.action_summaries = [flagAssetActionSummary]; + + return asset; + } + + return null; + }).filter((asset) => asset != null); + }); +}; + +module.exports = (context) => ({ + Metrics: { + get: ({from, to}) => getMetrics(context, {from, to}) + } +}); diff --git a/graph/resolvers/asset_action_summary.js b/graph/resolvers/asset_action_summary.js new file mode 100644 index 000000000..99483f9bb --- /dev/null +++ b/graph/resolvers/asset_action_summary.js @@ -0,0 +1,10 @@ +const AssetActionSummary = { + __resolveType({action_type}) { + switch (action_type) { + case 'FLAG': + return 'FlagAssetActionSummary'; + } + } +}; + +module.exports = AssetActionSummary; diff --git a/graph/resolvers/index.js b/graph/resolvers/index.js index 84dd10fdc..65461dc76 100644 --- a/graph/resolvers/index.js +++ b/graph/resolvers/index.js @@ -1,5 +1,6 @@ const ActionSummary = require('./action_summary'); const Action = require('./action'); +const AssetActionSummary = require('./asset_action_summary'); const Asset = require('./asset'); const Comment = require('./comment'); const Date = require('./date'); @@ -17,6 +18,7 @@ const ValidationUserError = require('./validation_user_error'); module.exports = { ActionSummary, Action, + AssetActionSummary, Asset, Comment, Date, diff --git a/graph/resolvers/root_query.js b/graph/resolvers/root_query.js index ff81e4c0f..432ad126f 100644 --- a/graph/resolvers/root_query.js +++ b/graph/resolvers/root_query.js @@ -39,12 +39,12 @@ const RootQuery = { return Comments.getByQuery(query); }, - metric(_, args, {user, loaders: {Assets}}) { + metrics(_, {from, to}, {user, loaders: {Metrics}}) { if (user == null || !user.hasRoles('ADMIN')) { return null; } - return Assets.getForMetrics(); + return Metrics.get({from, to}); }, // This returns the current user, ensure that if we aren't logged in, we diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 0ba307046..5d9262612 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -198,16 +198,6 @@ interface AssetActionSummary { actionableItemCount: Int } -# A summary of counts related to all the Likes on an Asset. -type LikeAssetActionSummary implements AssetActionSummary { - - # Number of actions associated with actionable types on this this Asset. - actionCount: Int - - # Number of unique actionable types that are referenced by the actions. - actionableItemCount: Int -} - # A summary of counts related to all the Flags on an Asset. type FlagAssetActionSummary implements AssetActionSummary { @@ -343,15 +333,12 @@ type Asset { # The date that the asset was closed at. closedAt: Date - # The date that the asset was created. - created_at: Date - # Summary of all Actions against all entities associated with the Asset. # (likes, flags, etc.) action_summaries: [AssetActionSummary] - # Unique users that have commented on this Asset. - authorCount: Int + # The date that the asset was created. + created_at: Date } ################################################################################ @@ -386,7 +373,7 @@ type ValidationUserError implements UserError { } ################################################################################ -## Queries +## Queries; ################################################################################ # Establishes the ordering of the content by their created_at time stamp. @@ -424,8 +411,8 @@ type RootQuery { # The currently logged in user based on the request. me: User - # metrics - metric: [Asset] + # Metrics related to user actions are saturated into the assets returned. + metrics(from: Date!, to: Date!): [Asset] } ################################################################################