Add Feature: system timelines will be showing any modification for duat startat endat receivedat, also notification to the watchers and if card is due, watchers will be notified

This commit is contained in:
Sam X. Chen 2019-07-09 16:36:50 -04:00
parent a545f8c1a2
commit 2c44f83453
5 changed files with 848 additions and 716 deletions

View file

@ -201,6 +201,19 @@ template(name="cardActivities")
.activity-checklist(href="{{ card.absoluteUrl }}")
+viewer
= checklistItem.title
if(currentData.timeKey)
| {{{_ activityType }}}
= ' '
i(title=currentData.timeValue).activity-meta {{ moment currentData.timeValue 'LLL' }}
if (currentData.timeOldValue)
= ' '
| {{{_ "previous_as" }}}
= ' '
i(title=currentData.timeOldValue).activity-meta {{ moment currentData.timeOldValue 'LLL' }}
= ' @'
else if(currentData.timeValue)
| {{{_ activityType currentData.timeValue}}}
if($eq activityType 'addComment')
+inlinedForm(classNames='js-edit-comment')

File diff suppressed because it is too large Load diff

View file

@ -197,6 +197,18 @@ if (Meteor.isServer) {
// params.label = label.name;
// params.labelId = activity.labelId;
//}
if (
(!activity.timeKey || activity.timeKey === 'dueAt') &&
activity.timeValue
) {
// due time reminder
title = 'act-withDue';
}
['timeValue', 'timeOldValue'].forEach(key => {
// copy time related keys & values to params
const value = activity[key];
if (value) params[key] = value;
});
if (board) {
const watchingUsers = _.pluck(
_.where(board.watchers, { level: 'watching' }),
@ -212,7 +224,6 @@ if (Meteor.isServer) {
_.intersection(participants, trackingUsers),
);
}
Notifications.getUsers(watchers).forEach(user => {
Notifications.notify(user, title, description, params);
});

View file

@ -1553,6 +1553,60 @@ function cardRemover(userId, doc) {
});
}
const findDueCards = days => {
const seekDue = ($from, $to, activityType) => {
Cards.find({
dueAt: { $gte: $from, $lt: $to },
}).forEach(card => {
const username = Users.findOne(card.userId).username;
const activity = {
userId: card.userId,
username,
activityType,
boardId: card.boardId,
cardId: card._id,
cardTitle: card.title,
listId: card.listId,
timeValue: card.dueAt,
swimlaneId: card.swimlaneId,
};
Activities.insert(activity);
});
};
const now = new Date(),
aday = 3600 * 24 * 1e3,
then = day => new Date(now.setHours(0, 0, 0, 0) + day * aday);
seekDue(then(1), then(days), 'almostdue');
seekDue(then(0), then(1), 'duenow');
seekDue(then(-days), now, 'pastdue');
};
const addCronJob = _.debounce(
Meteor.bindEnvironment(function findDueCardsDebounced() {
const notifydays = parseInt(process.env.NOTIFY_DUE_DAYS, 10) || 2; // default as 2 days b4 and after
if (!(notifydays > 0 && notifydays < 15)) {
// notifying due is disabled
return;
}
const notifyitvl = process.env.NOTIFY_DUE_ITVL; //passed in the itvl has to be a number standing for the hour of current time
const defaultitvl = 8; // default every morning at 8am, if the passed env variable has parsing error use default
const itvl = parseInt(notifyitvl, 10) || defaultitvl;
const scheduler = (job => () => {
const now = new Date();
const hour = 3600 * 1e3;
if (now.getHours() === itvl) {
if (typeof job === 'function') {
job();
}
}
Meteor.setTimeout(scheduler, hour);
})(() => {
findDueCards(notifydays);
});
scheduler();
}),
500,
);
if (Meteor.isServer) {
// Cards are often fetched within a board, so we create an index to make these
// queries more efficient.
@ -1565,12 +1619,17 @@ if (Meteor.isServer) {
// With a huge database, this result in a very slow app and high CPU on the mongodb side.
// To correct it, add Index to parentId:
Cards._collection._ensureIndex({ parentId: 1 });
/*let notifydays = parseInt(process.env.NOTIFY_DUE_DAYS) || 2; // default as 2 days b4 and after
let notifyitvl = parseInt(process.env.NOTIFY_DUE_ITVL) || 3600 * 24 * 1e3; // default interval as one day
Meteor.call("findDueCards",notifydays,notifyitvl);*/
Meteor.defer(() => {
addCronJob();
});
});
Cards.after.insert((userId, doc) => {
cardCreation(userId, doc);
});
// New activity for card (un)archivage
Cards.after.update((userId, doc, fieldNames) => {
cardState(userId, doc, fieldNames);
@ -1600,6 +1659,35 @@ if (Meteor.isServer) {
cardCustomFields(userId, doc, fieldNames, modifier);
});
// Add a new activity if modify time related field like dueAt startAt etc
Cards.before.update((userId, doc, fieldNames, modifier) => {
const dla = 'dateLastActivity';
const fields = fieldNames.filter(name => name !== dla);
const timingaction = ['receivedAt', 'dueAt', 'startAt', 'endAt'];
const action = fields[0];
if (fields.length > 0 && _.contains(timingaction, action)) {
// add activities for user change these attributes
const value = modifier.$set[action];
const oldvalue = doc[action] || '';
const activityType = `a-${action}`;
const card = Cards.findOne(doc._id);
const username = Users.findOne(userId).username;
const activity = {
userId,
username,
activityType,
boardId: doc.boardId,
cardId: doc._id,
cardTitle: doc.title,
timeKey: action,
timeValue: value,
timeOldValue: oldvalue,
listId: card.listId,
swimlaneId: card.swimlaneId,
};
Activities.insert(activity);
}
});
// Remove all activities associated with a card if we remove the card
// Remove also card_comments / checklists / attachments
Cards.before.remove((userId, doc) => {

View file

@ -6,12 +6,17 @@ Meteor.startup(() => {
['card', 'list', 'oldList', 'board', 'comment'].forEach(key => {
if (quoteParams[key]) quoteParams[key] = `"${params[key]}"`;
});
['timeValue', 'timeOldValue'].forEach(key => {
if (quoteParams[key]) quoteParams[key] = `${params[key]}`;
});
const lan = user.getLanguage();
const subject = TAPi18n.__(title, params, lan); // the original function has a fault, i believe the title should be used according to original author
const existing = user.getEmailBuffer().length > 0;
const text = `${existing ? `\n${subject}\n` : ''}${
params.user
} ${TAPi18n.__(description, quoteParams, lan)}\n${params.url}`;
const text = `${params.user} ${TAPi18n.__(
description,
quoteParams,
user.getLanguage(),
)}\n${params.url}`;
user.addEmailBuffer(text);
// unlike setTimeout(func, delay, args),
@ -29,12 +34,11 @@ Meteor.startup(() => {
// merge the cached content into single email and flush
const text = texts.join('\n\n');
user.clearEmailBuffer();
try {
Email.send({
to: user.emails[0].address.toLowerCase(),
from: Accounts.emailTemplates.from,
subject: TAPi18n.__('act-activity-notify', {}, user.getLanguage()),
subject,
text,
});
} catch (e) {