Skip to content

Commit e17a71c

Browse files
authored
Merge pull request #4 from hisptz/feature/mailing-util
Feature/mailing util
2 parents 620d09d + 828e0a5 commit e17a71c

File tree

10 files changed

+1812
-612
lines changed

10 files changed

+1812
-612
lines changed

.env.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1+
# DHIS2 Configuration
12
DHIS2_BASE_URL=
23
DHIS2_USERNAME=
34
DHIS2_PASSWORD=
45
PREDICTOR_GROUPS=
6+
7+
# Email Configuration
8+
EMAIL_SENDER=
9+
EMAIL_SENDER_PASSWORD=
10+
EMAIL_RECIPIENTS=

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,16 @@ yarn install
4545
Environment variables can be set by creating `.env` file with contents similar as `.env.example` Or as shown below:
4646

4747
```
48+
# DHIS2 Configuration
4849
DHIS2_BASE_URL=<url-for-dhis2-instance>
4950
DHIS2_USERNAME=<dhis2-username>
5051
DHIS2_PASSWORD=<dhis2-password>
5152
PREDICTOR_GROUPS=<comma-separated-uids>
53+
54+
# Email Configuration
55+
EMAIL_SENDER=<email-notification-sender>
56+
EMAIL_SENDER_PASSWORD=<sender-password>
57+
EMAIL_RECIPIENTS=<comma-separated-recipients>
5258
```
5359

5460
### Running the application

build-readme.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,16 @@ yarn install
2727
Environment variables can be set by creating `.env` file with contents similar as `.env.example` Or as shown below:
2828

2929
```
30+
# DHIS2 Configuration
3031
DHIS2_BASE_URL=<url-for-dhis2-instance>
3132
DHIS2_USERNAME=<dhis2-username>
3233
DHIS2_PASSWORD=<dhis2-password>
3334
PREDICTOR_GROUPS=<comma-separated-uids>
35+
36+
# Email Configuration
37+
EMAIL_SENDER=<email-notification-sender>
38+
EMAIL_SENDER_PASSWORD=<sender-password>
39+
EMAIL_RECIPIENTS=<comma-separated-recipients>
3440
```
3541

3642
### 3. Running the application

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "somalia-lmis-predictors-script",
33
"description": "A custom script that evaluates the predictors from the specified DHIS2 instance",
4-
"version": "1.0.1",
4+
"version": "1.0.2",
55
"main": "index.js",
66
"license": "MIT",
77
"dependencies": {
@@ -11,6 +11,7 @@
1111
"dotenv": "^16.0.3",
1212
"lodash": "^4.17.21",
1313
"luxon": "^3.3.0",
14+
"nodemailer": "^7.0.11",
1415
"winston": "^3.8.2"
1516
},
1617
"scripts": {
@@ -24,6 +25,7 @@
2425
"@types/lodash": "^4.17.13",
2526
"@types/luxon": "^3.3.1",
2627
"@types/node": "^20.2.4",
28+
"@types/nodemailer": "^7.0.4",
2729
"bestzip": "^2.2.1",
2830
"rimraf": "^5.0.1",
2931
"ts-loader": "^9.4.3",

src/commands/index.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { Command } from "commander";
2-
import appDetails from "../../package.json";
32
import logger from "../logging";
43
import { printDHIS2Info } from "../clients/sysInfo";
54
import {
@@ -35,9 +34,32 @@ program
3534
endDate
3635
);
3736
}
37+
38+
const evaluationResults: string[] = [];
39+
3840
for (const { startDate, endDate } of dateIntervals) {
39-
await initiatePredictorEvaluationProcess(startDate, endDate);
41+
try {
42+
const result = await initiatePredictorEvaluationProcess(
43+
startDate,
44+
endDate
45+
);
46+
result && evaluationResults.push(result);
47+
} catch (error) {
48+
logger.error(
49+
`Failed to run predictor evaluation for period ${startDate} to ${endDate}`
50+
);
51+
logger.error(error);
52+
}
4053
}
54+
55+
logger.info("Predictor data generation process completed.");
56+
57+
// send notification emails
58+
await import("../utils/predictor-notifications").then(
59+
async ({ sendNotifications }) => {
60+
await sendNotifications(evaluationResults);
61+
}
62+
);
4163
});
4264

4365
program

src/services/index.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,45 @@ import { getPredictorGroups, runPredictorGroup } from "../utils/predictors";
88
export async function initiatePredictorEvaluationProcess(
99
startDate?: string,
1010
endDate?: string
11-
) {
11+
): Promise<string | undefined> {
12+
const results: any[] = [];
1213
startDate = startDate ?? getDefaultStartDateForPredictorEvaluation();
1314
endDate = endDate ?? getDefaultLastDateForPredictorEvaluation();
1415
logger.info(`Started running the predictors from ${startDate} to ${endDate}`);
1516

1617
try {
1718
const predictorGroups = await getPredictorGroups();
1819
for (let predictorGroup of predictorGroups) {
19-
await runPredictorGroup(predictorGroup, startDate, endDate);
20+
const result = await runPredictorGroup(
21+
predictorGroup,
22+
startDate,
23+
endDate
24+
);
25+
results.push({
26+
...predictorGroup,
27+
result,
28+
});
2029
}
30+
let htmlTable = `<h3>Predictions Results from ${startDate} to ${endDate}</h3><table border="1" cellpadding="5" cellspacing="0">
31+
<tr>
32+
<th>ID</th>
33+
<th>Name</th>
34+
<th>Result</th>
35+
</tr>`;
36+
results.forEach((res) => {
37+
htmlTable += `<tr>
38+
<td>${res.id}</td>
39+
<td>${res.name}</td>
40+
<td>${res.result}</td>
41+
</tr>`;
42+
});
43+
44+
htmlTable += `</table>`;
45+
46+
return htmlTable;
2147
} catch (error) {
2248
logger.error(`Failed to evaluate Predictors! check the error below.`);
2349
logger.error(`${JSON.stringify(error)}`);
50+
throw error;
2451
}
2552
}

src/utils/email-notifications.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { config } from "dotenv";
2+
import nodemailer from "nodemailer";
3+
import logger from "../logging";
4+
5+
config();
6+
7+
const emailSender = {
8+
email: process.env.EMAIL_SENDER,
9+
password: process.env.EMAIL_SENDER_PASSWORD,
10+
};
11+
12+
const transporter = nodemailer.createTransport({
13+
service: "gmail",
14+
auth: {
15+
type: "login",
16+
user: emailSender.email || "",
17+
pass: emailSender.password || "",
18+
},
19+
});
20+
21+
type EmailNotificationParams = {
22+
to: string;
23+
subject: string;
24+
htmlContent?: string;
25+
textContent?: string;
26+
attachments?: Array<{
27+
filename: string;
28+
path: string;
29+
}>;
30+
};
31+
32+
export const sendEmailNotification = async ({
33+
to,
34+
subject,
35+
htmlContent = "",
36+
textContent = "",
37+
attachments = [],
38+
}: EmailNotificationParams): Promise<void> => {
39+
const mailOptions = {
40+
from: emailSender.email,
41+
to,
42+
subject,
43+
text: textContent,
44+
html: htmlContent,
45+
attachments,
46+
};
47+
48+
try {
49+
await transporter.sendMail(mailOptions);
50+
logger.info(`Email sent to ${to} with subject: ${subject}`);
51+
} catch (error) {
52+
logger.error(`Failed to send email to ${to}`);
53+
logger.error(error);
54+
}
55+
};
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { config } from "dotenv";
2+
import logger from "../logging";
3+
4+
config();
5+
6+
export async function sendNotifications(
7+
evaluationResults: string[]
8+
): Promise<void> {
9+
const emailRecipients = process.env.EMAIL_RECIPIENTS;
10+
try {
11+
if (evaluationResults.length) {
12+
const finalHtml =
13+
"<p>The predictor evaluation results are as follows:</p>" +
14+
evaluationResults.join("<br/>");
15+
if (emailRecipients) {
16+
await import("../utils/email-notifications").then(
17+
async ({ sendEmailNotification }) => {
18+
await sendEmailNotification({
19+
to: emailRecipients,
20+
subject: `Predictor Evaluation Results`,
21+
htmlContent: finalHtml,
22+
});
23+
}
24+
);
25+
logger.info(`Prediction results email sent to: ${emailRecipients}`);
26+
}
27+
} else {
28+
logger.info("No predictor evaluation results to display.");
29+
if (emailRecipients) {
30+
await import("../utils/email-notifications").then(
31+
async ({ sendEmailNotification }) => {
32+
await sendEmailNotification({
33+
to: emailRecipients,
34+
subject: `ERROR: Predictor Evaluation Results`,
35+
textContent:
36+
"There were no predictor evaluation results. To see more details, please check the server logs.",
37+
});
38+
}
39+
);
40+
logger.info(`Notification email sent to: ${emailRecipients}`);
41+
}
42+
}
43+
} catch (error) {
44+
logger.error(`Failed to send notification emails! check the error below.`);
45+
logger.error(error);
46+
}
47+
}

src/utils/predictors.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export async function runPredictorGroup(
5353
predictorGroup: PredictorGroup,
5454
startDate: string,
5555
endDate: string
56-
): Promise<void> {
56+
): Promise<string> {
5757
logger.info(
5858
`Generating predictions for ${predictorGroup.name} Group from ${startDate} to ${endDate}`
5959
);
@@ -65,16 +65,17 @@ export async function runPredictorGroup(
6565
logger.error(
6666
`Failed to generate the predictions! ${response?.data?.message ?? ""}`
6767
);
68-
return;
68+
return "Failed to generate predictions";
6969
}
70-
7170
const message =
7271
response?.data?.message ?? "Successfully generated the predictions";
7372
logger.info(message);
73+
return message;
7474
} catch (error) {
7575
logger.error(
7676
`Failed to run ${predictorGroup.name} group! See the error below.`
7777
);
7878
logger.error(`${JSON.stringify(error)}`);
79+
return "Failed to generate predictions";
7980
}
8081
}

0 commit comments

Comments
 (0)