Skip to content

Commit d43c160

Browse files
ClementBouvierNfreddidierRTE
authored andcommitted
Added the option to send daily recap by mail (#5733)
Signed-off-by: ClementBouvierN <[email protected]>
1 parent 169b21f commit d43c160

29 files changed

+1019
-371
lines changed

node-services/cards-external-diffusion/config/default-docker.yml

+3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ operatorfabric:
2424
defaultConfig:
2525
subjectPrefix: 'Opfab card received '
2626
bodyPrefix: 'You received a card in opfab : '
27+
dailyEmailTitle: 'Cards received during the day'
28+
hourToSendDailyEmail: 7
29+
minuteToSendDailyEmail: 30
2730
opfabUrlInMailContent: http://localhost:2002
2831
windowInSecondsForCardSearch: 360
2932
secondsAfterPublicationToConsiderCardAsNotRead: 60

node-services/cards-external-diffusion/config/default.yml

+3
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ operatorfabric:
3939
4040
subjectPrefix: 'Opfab card received '
4141
bodyPrefix: 'You received a card in opfab : '
42+
dailyEmailTitle: 'Cards received during the day'
43+
hourToSendDailyEmail: 7
44+
minuteToSendDailyEmail: 30
4245
opfabUrlInMailContent: http://localhost:2002
4346
windowInSecondsForCardSearch: 360
4447
secondsAfterPublicationToConsiderCardAsNotRead: 60

node-services/cards-external-diffusion/src/cardsExternalDiffusion.ts

+13
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,19 @@ app.listen(adminPort, () => {
187187
logger.info(`Opfab card external diffusion service listening on port ${adminPort}`);
188188
});
189189

190+
app.post('/sendDailyEmail', (req, res) => {
191+
192+
authorizationService.isAdminUser(req).then(isAdmin => {
193+
if (!isAdmin)
194+
res.status(403).send();
195+
else {
196+
logger.info('Sending email with cards from the last 24 hours');
197+
cardsExternalDiffusionService.sendDailyRecap();
198+
res.send();
199+
}
200+
})
201+
});
202+
190203
async function start() {
191204
await cardsExternalDiffusionDatabaseService.connectToMongoDB();
192205
opfabServicesInterface.startListener();

node-services/cards-external-diffusion/src/domain/application/cardsDiffusionControl.ts

+26-229
Original file line numberDiff line numberDiff line change
@@ -9,40 +9,41 @@
99

1010
import SendMailService from '../server-side/sendMailService';
1111
import CardsExternalDiffusionOpfabServicesInterface from '../server-side/cardsExternalDiffusionOpfabServicesInterface';
12-
import CardsRoutingUtilities from './cardRoutingUtilities';
13-
import ConfigDTO from '../client-side/configDTO';
1412
import CardsExternalDiffusionDatabaseService from '../server-side/cardsExternaDiffusionDatabaseService';
15-
import CardsDiffusionRateLimiter from './cardsDiffusionRateLimiter';
1613
import BusinessConfigOpfabServicesInterface from '../server-side/BusinessConfigOpfabServicesInterface';
1714

1815
export default class CardsDiffusionControl {
1916

20-
opfabUrlInMailContent: any;
17+
protected opfabUrlInMailContent: any;
18+
protected cardsExternalDiffusionOpfabServicesInterface: CardsExternalDiffusionOpfabServicesInterface;
19+
protected businessConfigOpfabServicesInterface: BusinessConfigOpfabServicesInterface;
20+
protected cardsExternalDiffusionDatabaseService: CardsExternalDiffusionDatabaseService;
21+
protected logger: any;
22+
protected mailService: SendMailService;
23+
protected from: string;
2124

22-
private cardsExternalDiffusionOpfabServicesInterface: CardsExternalDiffusionOpfabServicesInterface;
23-
private businessConfigOpfabServicesInterface: BusinessConfigOpfabServicesInterface;
24-
private cardsExternalDiffusionDatabaseService: CardsExternalDiffusionDatabaseService;
25-
private logger: any;
26-
private secondsAfterPublicationToConsiderCardAsNotRead: number;
27-
private windowInSecondsForCardSearch: number;
28-
private mailService: SendMailService;
29-
private from: string;
30-
private subjectPrefix: string;
31-
private bodyPrefix: string;
32-
private activateCardsDiffusionRateLimiter: boolean;
33-
private cardsDiffusionRateLimiter: CardsDiffusionRateLimiter;
25+
public setOpfabUrlInMailContent(opfabUrlInMailContent: any) {
26+
this.opfabUrlInMailContent = opfabUrlInMailContent;
27+
return this;
28+
}
3429

35-
public setOpfabServicesInterface(cardsExternalDiffusionOpfabServicesInterface: CardsExternalDiffusionOpfabServicesInterface) {
30+
public setOpfabServicesInterface(
31+
cardsExternalDiffusionOpfabServicesInterface: CardsExternalDiffusionOpfabServicesInterface
32+
) {
3633
this.cardsExternalDiffusionOpfabServicesInterface = cardsExternalDiffusionOpfabServicesInterface;
3734
return this;
3835
}
3936

40-
public setOpfabBusinessConfigServicesInterface(businessConfigOpfabServicesInterface: BusinessConfigOpfabServicesInterface) {
37+
public setOpfabBusinessConfigServicesInterface(
38+
businessConfigOpfabServicesInterface: BusinessConfigOpfabServicesInterface
39+
) {
4140
this.businessConfigOpfabServicesInterface = businessConfigOpfabServicesInterface;
4241
return this;
4342
}
4443

45-
public setCardsExternalDiffusionDatabaseService(cardsExternalDiffusionDatabaseService: CardsExternalDiffusionDatabaseService) {
44+
public setCardsExternalDiffusionDatabaseService(
45+
cardsExternalDiffusionDatabaseService: CardsExternalDiffusionDatabaseService
46+
) {
4647
this.cardsExternalDiffusionDatabaseService = cardsExternalDiffusionDatabaseService;
4748
return this;
4849
}
@@ -62,178 +63,15 @@ export default class CardsDiffusionControl {
6263
return this;
6364
}
6465

65-
public setSubjectPrefix(subjectPrefix: string) {
66-
this.subjectPrefix = subjectPrefix;
67-
return this;
68-
}
69-
70-
public setBodyPrefix(bodyPrefix: string) {
71-
this.bodyPrefix = bodyPrefix;
72-
return this;
73-
}
74-
75-
public setOpfabUrlInMailContent(opfabUrlInMailContent: any) {
76-
this.opfabUrlInMailContent = opfabUrlInMailContent;
77-
return this;
78-
}
79-
80-
public setSecondsAfterPublicationToConsiderCardAsNotRead(secondsAfterPublicationToConsiderCardAsNotRead: number) {
81-
this.secondsAfterPublicationToConsiderCardAsNotRead = secondsAfterPublicationToConsiderCardAsNotRead;
82-
return this;
83-
}
84-
85-
public setWindowInSecondsForCardSearch(windowInSecondsForCardSearch: number) {
86-
this.windowInSecondsForCardSearch = windowInSecondsForCardSearch;
87-
return this;
88-
}
89-
90-
public setActivateCardsDiffusionRateLimiter(activate: boolean) {
91-
this.activateCardsDiffusionRateLimiter = activate;
92-
return this;
93-
}
94-
95-
public setCardsDiffusionRateLimiter( cardsDiffusionRateLimiter: CardsDiffusionRateLimiter) {
96-
this.cardsDiffusionRateLimiter = cardsDiffusionRateLimiter;
97-
}
98-
99-
public setConfiguration(updated: ConfigDTO) {
100-
this.from = updated.mailFrom;
101-
this.subjectPrefix = updated.subjectPrefix;
102-
this.bodyPrefix = updated.bodyPrefix;
103-
this.secondsAfterPublicationToConsiderCardAsNotRead = updated.secondsAfterPublicationToConsiderCardAsNotRead;
104-
this.windowInSecondsForCardSearch = updated.windowInSecondsForCardSearch;
105-
this.activateCardsDiffusionRateLimiter = updated.activateCardsDiffusionRateLimiter;
106-
if (this.activateCardsDiffusionRateLimiter) {
107-
this.cardsDiffusionRateLimiter = new CardsDiffusionRateLimiter()
108-
.setLimitPeriodInSec(updated.sendRateLimitPeriodInSec)
109-
.setSendRateLimit(updated.sendRateLimit);
110-
}
111-
}
112-
113-
public async checkUnreadCards() {
114-
const users = this.cardsExternalDiffusionOpfabServicesInterface.getUsers();
115-
const userLogins = users.map((u) => u.login);
116-
117-
const connectedResponse = await this.cardsExternalDiffusionOpfabServicesInterface.getUsersConnected();
118-
if (connectedResponse.isValid()) {
119-
const connectedUsers = connectedResponse.getData().map((u: {login: string;}) => u.login);
120-
const usersToCheck = this.removeElementsFromArray(userLogins, connectedUsers);
121-
this.logger.debug('Disconnected users ' + usersToCheck);
122-
if (usersToCheck.length > 0) {
123-
const dateFrom = Date.now() - this.windowInSecondsForCardSearch * 1000;
124-
const cards = await this.cardsExternalDiffusionDatabaseService.getCards(dateFrom);
125-
if (cards.length > 0) {
126-
this.logger.debug('Found cards: ' + cards.length);
127-
usersToCheck.forEach((login) => {
128-
this.sendCardsToUserIfNecessary(cards, login).catch(error =>
129-
this.logger.error("error during sendCardsToUserIfNecessary ", error)
130-
)
131-
});
132-
}
133-
}
134-
await this.cleanCardsAreadySent();
135-
}
136-
}
137-
138-
private async sendCardsToUserIfNecessary(cards: any[], login: string) {
139-
this.logger.debug('Check user ' + login);
140-
141-
const resp = await this.cardsExternalDiffusionOpfabServicesInterface.getUserWithPerimetersByLogin(login);
142-
if (resp.isValid()) {
143-
const userWithPerimeters = resp.getData();
144-
const emailToPlainText = this.shouldEmailBePlainText(userWithPerimeters);
145-
this.logger.debug('Got user with perimeters ' + JSON.stringify(userWithPerimeters));
146-
if (this.isEmailSettingEnabled(userWithPerimeters)) {
147-
const unreadCards = await this.getCardsForUser(cards, userWithPerimeters);
148-
for (let i = 0; i < unreadCards.length; i++) {
149-
await this.sendCardIfAllowed(unreadCards[i], userWithPerimeters.email, emailToPlainText);
150-
}
151-
}
152-
}
153-
}
154-
155-
private async sendCardIfAllowed(unreadCard: any, userEmail: string, emailToPlainText: boolean): Promise<void> {
156-
try {
157-
const alreadySent = await this.wasCardsAlreadySentToUser(unreadCard.uid, userEmail);
158-
if (!alreadySent) {
159-
if (this.isSendingAllowed(userEmail)) {
160-
await this.sendMail(unreadCard, userEmail, emailToPlainText);
161-
} else {
162-
this.logger.warn(`Send rate limit reached for ${userEmail}, not sending mail for card ${unreadCard.uid}`);
163-
await this.cardsExternalDiffusionDatabaseService.persistSentMail(unreadCard.uid, userEmail);
164-
}
165-
}
166-
} catch (error) {
167-
this.logger.error("Error occurred while sending mail: ", error);
168-
}
169-
}
170-
171-
private isSendingAllowed(email: string) {
172-
return !this.activateCardsDiffusionRateLimiter || this.cardsDiffusionRateLimiter.isNewSendingAllowed(email);
173-
}
174-
175-
private registerNewSending(destination: string) {
176-
if (this.activateCardsDiffusionRateLimiter)
177-
this.cardsDiffusionRateLimiter.registerNewSending(destination);
178-
}
179-
180-
private async getCardsForUser(cards : any[], userWithPerimeters: any) : Promise<any[]> {
181-
182-
const perimeters = userWithPerimeters.computedPerimeters;
183-
this.logger.debug('Got user perimeters' + JSON.stringify(perimeters));
184-
return cards
185-
.filter(
186-
(card: any) =>
187-
CardsRoutingUtilities.shouldUserReceiveTheCard(
188-
userWithPerimeters,
189-
card
190-
) && this.isCardUnreadForUser(card, userWithPerimeters.userData)
191-
);
192-
}
193-
194-
private wasCardsAlreadySentToUser(cardUid: string, email: string) {
195-
return this.cardsExternalDiffusionDatabaseService.getSentMail(cardUid, email);
196-
}
197-
198-
private isEmailSettingEnabled(userWithPerimeters: any): boolean {
66+
protected isEmailSettingEnabled(userWithPerimeters: any): boolean {
19967
return userWithPerimeters.sendCardsByEmail && userWithPerimeters.email;
200-
20168
}
20269

203-
private shouldEmailBePlainText(userWithPerimeters: any): boolean {
70+
protected shouldEmailBePlainText(userWithPerimeters: any): boolean {
20471
return userWithPerimeters.emailToPlainText ? userWithPerimeters.emailToPlainText : false;
20572
}
20673

207-
private isCardUnreadForUser(card: any, user: any): boolean {
208-
return (
209-
card.publishDate < Date.now() - 1000 * this.secondsAfterPublicationToConsiderCardAsNotRead &&
210-
!card.usersReads?.includes(user.login)
211-
);
212-
}
213-
214-
private async sendMail(card: any, to: string, emailToPlainText:boolean) {
215-
this.logger.info('Send Mail to ' + to + ' for card ' + card.uid);
216-
let subject =
217-
this.subjectPrefix +
218-
' - ' +
219-
card.titleTranslated +
220-
' - ' +
221-
card.summaryTranslated +
222-
' - ' +
223-
this.getFormattedDateAndTimeFromEpochDate(card.startDate);
224-
if (card.endDate) subject += ' - ' + this.getFormattedDateAndTimeFromEpochDate(card.endDate);
225-
const body = await this.processCardTemplate(card);
226-
try {
227-
await this.mailService.sendMail(subject, body, this.from, to, emailToPlainText);
228-
this.registerNewSending(to);
229-
await this.cardsExternalDiffusionDatabaseService.persistSentMail(card.uid, to);
230-
} catch (e) {
231-
this.logger.error('Error sending mail ', e);
232-
};
233-
234-
}
235-
236-
private removeElementsFromArray(arrayToFilter: string[], arrayToDelete: string[]): string[] {
74+
protected removeElementsFromArray(arrayToFilter: string[], arrayToDelete: string[]): string[] {
23775
if (arrayToDelete && arrayToDelete.length > 0) {
23876
const elementsToDeleteSet = new Set(arrayToDelete);
23977
const newArray = arrayToFilter.filter((name) => {
@@ -245,43 +83,7 @@ export default class CardsDiffusionControl {
24583
}
24684
}
24785

248-
private async processCardTemplate(card: any): Promise<string> {
249-
let cardBodyHtml =
250-
this.bodyPrefix +
251-
' <a href=" ' +
252-
this.opfabUrlInMailContent +
253-
'/#/feed/cards/' +
254-
card.id +
255-
' ">' +
256-
this.escapeHtml(card.titleTranslated) +
257-
' - ' +
258-
this.escapeHtml(card.summaryTranslated) +
259-
'</a>';
260-
try {
261-
const cardConfig = await this.businessConfigOpfabServicesInterface.fetchProcessConfig(
262-
card.process,
263-
card.processVersion
264-
);
265-
const stateName = card.state;
266-
if (cardConfig?.states?.[stateName]?.emailBodyTemplate) {
267-
const cardContentResponse = await this.cardsExternalDiffusionOpfabServicesInterface.getCard(card.id);
268-
if (cardContentResponse.isValid()) {
269-
const cardContent = cardContentResponse.getData();
270-
const templateCompiler = await this.businessConfigOpfabServicesInterface.fetchTemplate(
271-
card.process,
272-
cardConfig.states[stateName].emailBodyTemplate,
273-
card.processVersion
274-
);
275-
cardBodyHtml = cardBodyHtml + ' <br> ' + templateCompiler(cardContent);
276-
}
277-
}
278-
} catch (e) {
279-
console.warn("Couldn't parse email for : ", card.state, e);
280-
}
281-
return cardBodyHtml;
282-
}
283-
284-
private escapeHtml(text: string): string {
86+
protected escapeHtml(text: string): string {
28587
if (!text) return text;
28688
return text
28789
.replace(/&/g, '&amp;')
@@ -291,12 +93,7 @@ export default class CardsDiffusionControl {
29193
.replace(/'/g, '&#39;');
29294
}
29395

294-
private async cleanCardsAreadySent() {
295-
const dateLimit = Date.now() - this.windowInSecondsForCardSearch * 1000;
296-
await this.cardsExternalDiffusionDatabaseService.deleteMailsSentBefore(dateLimit);
297-
}
298-
299-
private getFormattedDateAndTimeFromEpochDate(epochDate: number): string {
96+
protected getFormattedDateAndTimeFromEpochDate(epochDate: number): string {
30097
if (!epochDate) return '';
30198
const date = new Date(epochDate);
30299

@@ -313,7 +110,7 @@ export default class CardsDiffusionControl {
313110
);
314111
}
315112

316-
private pad(num: number): string {
113+
protected pad(num: number): string {
317114
if (num < 10) {
318115
return '0' + num;
319116
}

0 commit comments

Comments
 (0)