mirror of
https://github.com/wassname/talk.git
synced 2026-07-23 13:10:20 +08:00
Optimization pass
This commit is contained in:
+123
-105
@@ -1,13 +1,104 @@
|
||||
const _ = require('lodash');
|
||||
const DataLoader = require('dataloader');
|
||||
const util = require('./util');
|
||||
const objectCacheKeyFn = util.objectCacheKeyFn;
|
||||
const arrayCacheKeyFn = util.arrayCacheKeyFn;
|
||||
|
||||
const CommentModel = require('../../models/comment');
|
||||
const AssetModel = require('../../models/asset');
|
||||
const ActionModel = require('../../models/action');
|
||||
|
||||
const getMetrics = (context, {from, to, sort, limit}) => {
|
||||
const getMetrics = ({loaders: {Metrics, Assets}}, {from, to, sort, limit}) => {
|
||||
|
||||
let commentMetrics = {};
|
||||
let assetMetrics = [];
|
||||
|
||||
return Metrics.getRecentActions.load({from, to})
|
||||
.then((actionSummaries) => {
|
||||
|
||||
commentMetrics = actionSummaries.reduce((acc, {item_id, action_type, count}) => {
|
||||
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 = _.uniq(actionSummaries.map((as) => as.item_id));
|
||||
|
||||
// Find those comments.
|
||||
return Metrics.getSpecificComments.load(commentIDs);
|
||||
})
|
||||
.then((commentResults) => {
|
||||
|
||||
assetMetrics = commentResults.map(({ids, asset_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};
|
||||
});
|
||||
|
||||
// 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.
|
||||
assetMetrics.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));
|
||||
|
||||
// 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.actionCount - aActionSummary.actionCount;
|
||||
});
|
||||
|
||||
// Only keep the top `limit`.
|
||||
assetMetrics = assetMetrics.slice(0, limit);
|
||||
|
||||
// Determine the assets that we need to return.
|
||||
return Assets.getByID.loadMany(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, 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);
|
||||
});
|
||||
};
|
||||
|
||||
const getRecentActions = (context, {from, to}) => {
|
||||
return ActionModel.aggregate([
|
||||
|
||||
// Find all actions that were created in the time range.
|
||||
@@ -36,120 +127,47 @@ const getMetrics = (context, {from, to, sort, limit}) => {
|
||||
action_type: '$_id.action_type',
|
||||
count: '$count'
|
||||
}}
|
||||
]).then((actionSummaries) => {
|
||||
]);
|
||||
};
|
||||
|
||||
// Collect all the action summaries into a dictionary.
|
||||
actionSummaries.forEach(({item_id, action_type, count}) => {
|
||||
if (!(item_id in commentMetrics)) {
|
||||
commentMetrics[item_id] = [];
|
||||
}
|
||||
const getSpecificComments = (context, ids) => {
|
||||
return CommentModel.aggregate([
|
||||
|
||||
commentMetrics[item_id].push({action_type, count});
|
||||
});
|
||||
|
||||
// Collect just the comment id's.
|
||||
let commentIDs = _.uniq(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) => {
|
||||
|
||||
assetMetrics = commentResults.map(({ids, asset_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};
|
||||
});
|
||||
|
||||
// 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.
|
||||
assetMetrics.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));
|
||||
|
||||
// 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.actionCount - aActionSummary.actionCount;
|
||||
});
|
||||
|
||||
// Only keep the top `limit`.
|
||||
assetMetrics = assetMetrics.slice(0, limit);
|
||||
|
||||
// Determine the assets that we need to return.
|
||||
return AssetModel.find({
|
||||
// Get only those comments.
|
||||
{$match: {
|
||||
id: {
|
||||
$in: assetMetrics.map((asset) => asset.id)
|
||||
$in: ids
|
||||
}
|
||||
});
|
||||
})
|
||||
.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, 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;
|
||||
// Group by their asset id and push in the comment id.
|
||||
{$group: {
|
||||
_id: {
|
||||
asset_id: '$asset_id'
|
||||
},
|
||||
ids: {
|
||||
$addToSet: '$id'
|
||||
}
|
||||
}},
|
||||
|
||||
return null;
|
||||
}).filter((asset) => asset != null);
|
||||
});
|
||||
// Project that data only as better fields.
|
||||
{$project: {
|
||||
asset_id: '$_id.asset_id',
|
||||
ids: '$ids'
|
||||
}}
|
||||
]);
|
||||
};
|
||||
|
||||
module.exports = (context) => ({
|
||||
Metrics: {
|
||||
getSpecificComments: new DataLoader(([ids]) => getSpecificComments(context, ids).then((c) => [c]), {
|
||||
batch: false,
|
||||
cacheKeyFn: arrayCacheKeyFn
|
||||
}),
|
||||
getRecentActions: new DataLoader(([{from, to}]) => getRecentActions(context, {from, to}).then((as) => [as]), {
|
||||
batch: false,
|
||||
cacheKeyFn: objectCacheKeyFn('from', 'to')
|
||||
}),
|
||||
get: ({from, to, sort, limit}) => getMetrics(context, {from, to, sort, limit})
|
||||
}
|
||||
});
|
||||
|
||||
@@ -130,10 +130,20 @@ const objectCacheKeyFn = (...paths) => (obj) => {
|
||||
return paths.map((path) => obj[path]).join(':');
|
||||
};
|
||||
|
||||
/**
|
||||
* Maps an object's paths to a string that can be used as a cache key.
|
||||
* @param {Array} paths paths on the object to be used to generate the cache
|
||||
* key
|
||||
*/
|
||||
const arrayCacheKeyFn = (arr) => {
|
||||
return arr.sort().join(':');
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
singleJoinBy,
|
||||
arrayJoinBy,
|
||||
objectCacheKeyFn,
|
||||
arrayCacheKeyFn,
|
||||
SingletonResolver,
|
||||
SharedCacheDataLoader
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user