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

reviewable/pr2536/r1
Sam X. Chen 6 years ago
parent a545f8c1a2
commit 2c44f83453
  1. 13
      client/components/activities/activities.jade
  2. 1430
      i18n/en.i18n.json
  3. 13
      models/activities.js
  4. 90
      models/cards.js
  5. 18
      server/notifications/email.js

@ -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

@ -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);
});

@ -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) => {

@ -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) {

Loading…
Cancel
Save