mirror of
https://github.com/wassname/talk.git
synced 2026-06-29 00:40:27 +08:00
258 lines
7.2 KiB
JavaScript
258 lines
7.2 KiB
JavaScript
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, sort, 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 !== sort) {
|
|
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 === sort));
|
|
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 === sort));
|
|
let bActionSummary = b.action_summaries.find((({action_type}) => action_type === sort));
|
|
|
|
// 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, sort, limit}) => {
|
|
|
|
let commentActionSummaries = {};
|
|
|
|
let actionSummaries = await Metrics.getRecentActions.load({from, to});
|
|
|
|
actionSummaries.sort((a, b) => {
|
|
let aActionSummary = a.action_type === sort ? a : null;
|
|
let bActionSummary = b.action_type === sort ? 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 === sort);
|
|
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, 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}),
|
|
}
|
|
}
|
|
});
|