From ef200a85cbec4f4a8a04619409bbd1aae727f281 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 30 Nov 2017 17:16:32 -0700 Subject: [PATCH] removed backend for metrics --- graph/loaders/assets.js | 6 - graph/loaders/index.js | 2 - graph/loaders/metrics.js | 257 --------------------------- graph/resolvers/root_query.js | 22 --- graph/resolvers/user.js | 3 +- graph/typeDefs.graphql | 21 --- perms/constants.js | 1 - perms/queryReducer.js | 2 - test/server/graph/loaders/metrics.js | 133 -------------- 9 files changed, 1 insertion(+), 446 deletions(-) delete mode 100644 graph/loaders/metrics.js delete mode 100644 test/server/graph/loaders/metrics.js diff --git a/graph/loaders/assets.js b/graph/loaders/assets.js index b2e315ec5..832b7b473 100644 --- a/graph/loaders/assets.js +++ b/graph/loaders/assets.js @@ -79,11 +79,6 @@ const findOrCreateAssetByURL = async (context, asset_url) => { return asset; }; -const getAssetsForMetrics = async ({loaders: {Comments}}) => { - return Comments.getByQuery({action_type: 'FLAG'}) - .then((connection) => connection.nodes); -}; - const findByUrl = async (context, asset_url) => { // Verify that the asset_url is parsable. @@ -111,7 +106,6 @@ module.exports = (context) => ({ findByUrl: (url) => findByUrl(context, url), getByQuery: (query) => getAssetsByQuery(context, query), getByID: new DataLoader((ids) => genAssetsByID(context, ids)), - getForMetrics: () => getAssetsForMetrics(context), getAll: new util.SingletonResolver(() => AssetModel.find({})) } }); diff --git a/graph/loaders/index.js b/graph/loaders/index.js index 8bc170c6d..0ef09940f 100644 --- a/graph/loaders/index.js +++ b/graph/loaders/index.js @@ -4,7 +4,6 @@ const debug = require('debug')('talk:graph:loaders'); const Actions = require('./actions'); const Assets = require('./assets'); const Comments = require('./comments'); -const Metrics = require('./metrics'); const Settings = require('./settings'); const Tags = require('./tags'); const Users = require('./users'); @@ -17,7 +16,6 @@ let loaders = [ Actions, Assets, Comments, - Metrics, Settings, Tags, Users, diff --git a/graph/loaders/metrics.js b/graph/loaders/metrics.js deleted file mode 100644 index 28efe2963..000000000 --- a/graph/loaders/metrics.js +++ /dev/null @@ -1,257 +0,0 @@ -const _ = require('lodash'); -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 getAssetActivityMetrics = ({loaders: {Assets}}, {from, to, limit}) => { - let assetMetrics = []; - - return CommentModel.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) => { - assetMetrics = results; - - 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. - */ -const getAssetMetrics = async ({loaders: {Metrics, Assets, Comments}}, {from, to, sortBy, limit}) => { - - // Get the recent actions. - let actionSummaries = await Metrics.getRecentActions.load({from, to}); - - let commentMetrics = actionSummaries.reduce((acc, {item_id, action_type, count}) => { - if (action_type !== sortBy) { - return acc; - } - - if (!(item_id in acc)) { - acc[item_id] = []; - } - - acc[item_id].push({action_type, count}); - - return acc; - }, {}); - - // Collect just the comment id's. - let commentIDs = Object.keys(commentMetrics); - - // Find those comments. - let comments = await Comments.get.loadMany(commentIDs); - - let commentResults = _.groupBy(comments, 'asset_id'); - - let assetMetrics = Object.keys(commentResults) - .map((asset_id) => { - let ids = commentResults[asset_id].map((comment) => comment.id); - let summaries = _.groupBy(_.flatten(ids.map((id) => commentMetrics[id])), 'action_type'); - - let action_summaries = Object.keys(summaries).map((action_type) => ({ - action_type, - actionCount: summaries[action_type].reduce((acc, {count}) => acc + count, 0), - actionableItemCount: summaries[action_type].length - })); - - return {action_summaries, id: asset_id}; - }) - - .filter((asset) => { - let contextActionSummary = asset.action_summaries.find((({action_type}) => action_type === sortBy)); - if (contextActionSummary === null || contextActionSummary.actionCount === 0) { - return false; - } - - return true; - }) - - // Sort these metrics by the predefined sort order. This will ensure that - // if the action summary does not exist on the object, that it is less - // prefered over the one that does have it. - .sort((a, b) => { - let aActionSummary = a.action_summaries.find((({action_type}) => action_type === sortBy)); - let bActionSummary = b.action_summaries.find((({action_type}) => action_type === sortBy)); - - // Both of them had an actionCount, hence we can determine that we could - // compare the actual values directly. - return bActionSummary.actionCount - aActionSummary.actionCount; - }); - - // Only keep the top `limit`. - assetMetrics = assetMetrics.slice(0, limit); - - // Determine the assets that we need to return. - let assets = await Assets.getByID.loadMany(assetMetrics.map((asset) => asset.id)); - - // 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, action_summaries}) => { - if (id in groupedAssets) { - let asset = groupedAssets[id][0]; - - // Add the action summaries to the asset. - asset.action_summaries = action_summaries; - - return asset; - } - - return null; - }).filter((asset) => asset != null); -}; - -/** - * Returns a list of comments that are retrieved based on most activity within - * the indicated time range. - */ -const getCommentMetrics = async ({loaders: {Metrics, Comments}}, {from, to, sortBy, limit}) => { - - let commentActionSummaries = {}; - - let actionSummaries = await Metrics.getRecentActions.load({from, to}); - - actionSummaries.sort((a, b) => { - let aActionSummary = a.action_type === sortBy ? a : null; - let bActionSummary = b.action_type === sortBy ? b : null; - - // If either a or b don't have this action type, then one of them will - // automatically win. - if (aActionSummary == null || bActionSummary == null) { - if (bActionSummary != null) { - return 1; - } - - if (aActionSummary != null) { - return -1; - } - - return 0; - } - - // Both of them had an actionCount, hence we can determine that we could - // compare the actual values directly. - return bActionSummary.count - aActionSummary.count; - }); - - commentActionSummaries = _.groupBy(actionSummaries, 'item_id'); - - // Grab the comment id's for comment where they have at least one of the - // actions being sorted by. - let commentIDs = Object.keys(commentActionSummaries).filter((item_id) => { - let contextActionSummary = commentActionSummaries[item_id].find(({action_type}) => action_type === sortBy); - if (contextActionSummary == null) { - return false; - } - - return true; - }); - - // Only keep the top `limit`. - commentIDs = commentIDs.slice(0, limit); - - // If there are no comment's to get, then just continue with an empty - // array. - if (commentIDs.length === 0) { - return []; - } - - // Find those comments, this is the final stage, so let's get all the - // fields. - let comments = await Comments.get.loadMany(commentIDs); - - return comments.map((comment) => { - - // Add in the action summaries genrerated. - comment.action_summaries = commentActionSummaries[comment.id]; - - return comment; - }); -}; - -const getRecentActions = (context, {from, to}) => { - return ActionModel.aggregate([ - - // Find all actions that were created in the time range. - {$match: { - item_type: 'COMMENTS', - created_at: { - $gt: from, - $lt: to - } - }}, - - // Count all those items. - {$group: { - _id: { - item_id: '$item_id', - action_type: '$action_type' - }, - count: { - $sum: 1 - } - }}, - - // Project the count to a better field. - {$project: { - item_id: '$_id.item_id', - action_type: '$_id.action_type', - count: '$count' - }} - ]); -}; - -module.exports = (context) => ({ - Metrics: { - getRecentActions: new DataLoader(([{from, to}]) => getRecentActions(context, {from, to}).then((as) => [as]), { - batch: false, - cacheKeyFn: objectCacheKeyFn('from', 'to') - }), - Assets: { - get: ({from, to, sortBy, limit}) => getAssetMetrics(context, {from, to, sortBy, limit}), - getActivity: ({from, to, limit}) => getAssetActivityMetrics(context, {from, to, limit}), - }, - Comments: { - get: ({from, to, sortBy, limit}) => getCommentMetrics(context, {from, to, sortBy, limit}), - } - } -}); diff --git a/graph/resolvers/root_query.js b/graph/resolvers/root_query.js index e4701c901..aa8b90f20 100644 --- a/graph/resolvers/root_query.js +++ b/graph/resolvers/root_query.js @@ -1,7 +1,6 @@ const { SEARCH_ASSETS, SEARCH_OTHERS_COMMENTS, - SEARCH_COMMENT_METRICS, SEARCH_OTHER_USERS } = require('../../perms/constants'); @@ -58,27 +57,6 @@ const RootQuery = { return Users.getCountByQuery(query); }, - assetMetrics(_, query, {user, loaders: {Metrics: {Assets}}}) { - if (user == null || !user.can(SEARCH_ASSETS)) { - return null; - } - - const {sortBy} = query; - if (sortBy === 'ACTIVITY') { - return Assets.getActivity(query); - } - - return Assets.get(query); - }, - - commentMetrics(_, query, {user, loaders: {Metrics: {Comments}}}) { - if (user == null || !user.can(SEARCH_COMMENT_METRICS)) { - return null; - } - - return Comments.get(query); - }, - // This returns the current user, ensure that if we aren't logged in, we // return null. me(_, args, {user}) { diff --git a/graph/resolvers/user.js b/graph/resolvers/user.js index e5c5746ca..d95ae53f6 100644 --- a/graph/resolvers/user.js +++ b/graph/resolvers/user.js @@ -5,7 +5,6 @@ const { SEARCH_OTHER_USERS, SEARCH_OTHERS_COMMENTS, UPDATE_USER_ROLES, - SEARCH_COMMENT_METRICS, VIEW_SUSPENSION_INFO, LIST_OWN_TOKENS } = require('../../perms/constants'); @@ -79,7 +78,7 @@ const User = { // Extract the reliability from the user metadata if they have permission. reliable(user, _, {user: requestingUser}) { - if (requestingUser && requestingUser.can(SEARCH_COMMENT_METRICS)) { + if (requestingUser && requestingUser.can(SEARCH_ACTIONS)) { return KarmaService.model(user); } }, diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 44b906e24..182504abb 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -818,19 +818,6 @@ enum USER_STATUS { APPROVED } -# Metrics for the assets. -enum ASSET_METRICS_SORT { - - # Represents a FlagAction. - FLAG - - # Represents a don't agree action. - DONTAGREE - - # Represents activity. - ACTIVITY -} - type RootQuery { # Site wide settings and defaults. @@ -865,14 +852,6 @@ type RootQuery { # a single User by id user(id: ID!): User - - # Asset metrics related to user actions are saturated into the assets - # returned. Parameters `from` and `to` are related to the action created_at field. - assetMetrics(from: Date!, to: Date!, sortBy: ASSET_METRICS_SORT!, limit: Int = 10): [Asset!] - - # Comment metrics related to user actions are saturated into the comments - # returned. Parameters `from` and `to` are related to the action created_at field. - commentMetrics(from: Date!, to: Date!, sortBy: ACTION_TYPE!, limit: Int = 10): [Comment!] } ################################################################################ diff --git a/perms/constants.js b/perms/constants.js index 9cda7442b..7841e9711 100644 --- a/perms/constants.js +++ b/perms/constants.js @@ -26,7 +26,6 @@ module.exports = { SEARCH_ACTIONS: 'SEARCH_ACTIONS', SEARCH_NON_NULL_OR_ACCEPTED_COMMENTS: 'SEARCH_NON_NULL_OR_ACCEPTED_COMMENTS', SEARCH_OTHERS_COMMENTS: 'SEARCH_OTHERS_COMMENTS', - SEARCH_COMMENT_METRICS: 'SEARCH_COMMENT_METRICS', LIST_OWN_TOKENS: 'LIST_OWN_TOKENS', SEARCH_COMMENT_STATUS_HISTORY: 'SEARCH_COMMENT_STATUS_HISTORY', VIEW_SUSPENSION_INFO: 'VIEW_SUSPENSION_INFO', diff --git a/perms/queryReducer.js b/perms/queryReducer.js index b71c3d964..2cc5b95eb 100644 --- a/perms/queryReducer.js +++ b/perms/queryReducer.js @@ -13,8 +13,6 @@ module.exports = (user, perm) => { return check(user, ['ADMIN', 'MODERATOR']); case types.SEARCH_OTHERS_COMMENTS: return check(user, ['ADMIN', 'MODERATOR']); - case types.SEARCH_COMMENT_METRICS: - return check(user, ['ADMIN', 'MODERATOR']); case types.LIST_OWN_TOKENS: return check(user, ['ADMIN']); case types.SEARCH_COMMENT_STATUS_HISTORY: diff --git a/test/server/graph/loaders/metrics.js b/test/server/graph/loaders/metrics.js deleted file mode 100644 index 4414428bd..000000000 --- a/test/server/graph/loaders/metrics.js +++ /dev/null @@ -1,133 +0,0 @@ -const {graphql} = require('graphql'); - -const schema = require('../../../../graph/schema'); -const Context = require('../../../../graph/context'); -const UserModel = require('../../../../models/user'); -const AssetModel = require('../../../../models/asset'); -const SettingsService = require('../../../../services/settings'); -const ActionModel = require('../../../../models/action'); -const CommentModel = require('../../../../models/comment'); - -const {expect} = require('chai'); - -describe('graph.loaders.Metrics', () => { - beforeEach(() => SettingsService.init()); - - describe('#Comments', () => { - const query = ` - query CommentMetrics($from: Date!, $to: Date!) { - flagged: commentMetrics(from: $from, to: $to, sortBy: FLAG) { - id - } - } - `; - - describe('different comment states', () => { - - beforeEach(() => CommentModel.create([ - {id: '1', body: 'a new comment!'}, - {id: '2', body: 'a new comment!'}, - {id: '3', body: 'a new comment!'} - ])); - - [ - {flagged: 0, actions: []}, - {flagged: 1, actions: [{action_type: 'FLAG', item_id: '1', item_type: 'COMMENTS'}]}, - {flagged: 1, actions: [ - {action_type: 'FLAG', item_id: '1', item_type: 'COMMENTS'}, - ]}, - {flagged: 1, actions: [ - {action_type: 'FLAG', item_id: '3', item_type: 'COMMENTS'} - ]} - ].forEach(({flagged, actions}) => { - - describe(`with actions=${actions.length}`, () => { - - beforeEach(() => ActionModel.create(actions)); - - it(`returns the correct amount of metrics flagged=${flagged}`, () => { - const context = new Context({user: new UserModel({roles: ['ADMIN']})}); - - return graphql(schema, query, {}, context, { - from: (new Date()).setMinutes((new Date()).getMinutes() - 5), - to: (new Date()).setMinutes((new Date()).getMinutes() + 5) - }) - .then(({data, errors}) => { - expect(errors).to.be.undefined; - expect(data.flagged).to.have.length(flagged); - }); - }); - - }); - - }); - - }); - }); - - describe('#Assets', () => { - const query = ` - fragment metrics on Asset { - id - action_summaries { - actionCount - actionableItemCount - } - } - - query Metrics($from: Date!, $to: Date!) { - assetsByFlag: assetMetrics(from: $from, to: $to, sortBy: FLAG) { - ...metrics - } - } - `; - - describe('different comment states', () => { - - beforeEach(() => Promise.all([ - AssetModel.create([ - {id: 'a1', url: 'http://localhost:3030/article/1'}, - {id: 'a2', url: 'http://localhost:3030/article/2'} - ]), - CommentModel.create([ - {id: 'c1', asset_id: 'a1', body: 'a new comment!'}, - {id: 'c2', asset_id: 'a1', body: 'a new comment!'}, - {id: 'c3', asset_id: 'a1', body: 'a new comment!'} - ]) - ])); - - [ - {flagged: 0, actions: []}, - {flagged: 1, actions: [{action_type: 'FLAG', item_id: 'c1', item_type: 'COMMENTS'}]}, - {flagged: 1, actions: [ - {action_type: 'FLAG', item_id: 'c1', item_type: 'COMMENTS'}, - ]}, - {flagged: 1, actions: [ - {action_type: 'FLAG', item_id: 'c3', item_type: 'COMMENTS'} - ]} - ].forEach(({flagged, actions}) => { - - describe(`with actions=${actions.length}`, () => { - - beforeEach(() => ActionModel.create(actions)); - - it(`returns the correct amount of metrics flagged=${flagged}`, () => { - const context = new Context({user: new UserModel({roles: ['ADMIN']})}); - - return graphql(schema, query, {}, context, { - from: (new Date()).setMinutes((new Date()).getMinutes() - 5), - to: (new Date()).setMinutes((new Date()).getMinutes() + 5) - }) - .then(({data, errors}) => { - expect(errors).to.be.undefined; - expect(data.assetsByFlag).to.have.length(flagged); - }); - }); - - }); - - }); - - }); - }); -});