mirror of
https://github.com/wekan/wekan.git
synced 2025-04-23 13:37:09 -04:00
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:
parent
a545f8c1a2
commit
2c44f83453
5 changed files with 848 additions and 716 deletions
|
@ -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')
|
||||
|
|
1430
i18n/en.i18n.json
1430
i18n/en.i18n.json
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…
Add table
Add a link
Reference in a new issue