Skip to content

Commit

Permalink
Merge pull request #1784 from rodekruis/fix.email-typhoon-copy
Browse files Browse the repository at this point in the history
Fix.email typhoon copy
  • Loading branch information
jannisvisser authored Nov 18, 2024
2 parents 8021518 + e3f3ad7 commit c5f4527
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 142 deletions.
8 changes: 4 additions & 4 deletions interfaces/IBF-dashboard/src/assets/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -265,10 +265,10 @@
},
"active-event-active-trigger": {
"header": "Trigger: {{ eventName }} at {{firstLeadTimeDate}}",
"header-ongoing": "Ongoing trigger: {{ eventName }} at {{firstLeadTimeDate}}",
"header-ongoing": "Ongoing trigger: {{ eventName }}",
"header-below-trigger": "Warning: {{ eventName }} at {{firstLeadTimeDate}}",
"header-ongoing-below-trigger": "Ongoing warning: {{ eventName }} at {{firstLeadTimeDate}}",
"welcome": "A trigger warning for <strong>{{ eventName }}</strong> was issued on {{ startDate }}.",
"header-ongoing-below-trigger": "Ongoing warning: {{ eventName }}",
"welcome": "A trigger for <strong>{{ eventName }}</strong> was issued on {{ startDate }}.",
"welcome-below-trigger": "A warning for <strong>{{ eventName }}</strong> was issued on {{ startDate }}, but it is currently <strong>not predicted to reach trigger thresholds</strong>.",
"upcoming-event": {
"landfall": "It is estimated to <strong>make landfall</strong> on <strong>{{ firstLeadTimeDate }}</strong>.<br><br>",
Expand All @@ -277,7 +277,7 @@
},
"ongoing-event": {
"landfall": "It has <strong>already made landfall</strong>.<br><br>",
"no-landfall": "It has already reached the point closest to land.<br><br>"
"no-landfall": "It has already reached the point closest to land. It is <strong>not predicted to make landfall</strong>.<br><br>"
}
},
"active-event": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { EapAlertClass, TriggeredArea } from '../../../shared/data.model';
import {
DisasterSpecificProperties,
EapAlertClass,
TriggeredArea,
} from '../../../shared/data.model';
import { LeadTime } from '../../admin-area-dynamic-data/enum/lead-time.enum';

export class NotificationDataPerEventDto {
triggerStatusLabel: TriggerStatusLabelEnum;
eventName: string;
disasterSpecificCopy: DisasterSpecificCopy;
disasterSpecificProperties: DisasterSpecificProperties;

/**
* The day that the event starts.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export class EmailService {
if (isApiTest) {
// NOTE: use this to test the email output instead of using Mailchimp
// fs.writeFileSync(
// `email-${country.countryCodeISO3}-${disasterType}.html`,
// `email-${country.countryCodeISO3}-${disasterType}-${new Date()}.html`,
// emailHtml,
// );
return emailHtml;
Expand Down
88 changes: 71 additions & 17 deletions services/API-service/src/api/notification/email/mjml/body-event.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { LeadTime } from '../../../admin-area-dynamic-data/enum/lead-time.enum';
import { DisasterType } from '../../../disaster/disaster-type.enum';
import { ContentEventEmail } from '../../dto/content-trigger-email.dto';
import { TriggerStatusLabelEnum } from '../../dto/notification-date-per-event.dto';
import {
NotificationDataPerEventDto,
TriggerStatusLabelEnum,
} from '../../dto/notification-date-per-event.dto';
import {
dateObjectToDateTimeString,
getDisasterIssuedLabel,
Expand Down Expand Up @@ -28,6 +33,7 @@ const getMjmlBodyEvent = ({
triangleIcon,
eapLink,
triggerStatusLabel,
disasterSpecificCopy,
}: {
color: string;
defaultAdminAreaLabel: string;
Expand All @@ -44,6 +50,7 @@ const getMjmlBodyEvent = ({
triangleIcon: string;
eapLink: string;
triggerStatusLabel: string;
disasterSpecificCopy: string;
}): object => {
const icon = getInlineImage({ src: triangleIcon, size: 16 });

Expand All @@ -54,23 +61,29 @@ const getMjmlBodyEvent = ({

const contentContent = [];

if (firstTriggerLeadTimeFromNow) {
// Trigger event
if (firstLeadTimeString !== firstTriggerLeadTimeString) {
// Warning-to-trigger event: show start of warning first
contentContent.push(
`<strong>${disasterTypeLabel}:</strong> Expected to start on ${firstLeadTimeString}, ${firstLeadTimeFromNow}.`,
);
}
// Either way, show start of trigger next
if (disasterSpecificCopy) {
contentContent.push(
`<strong>${disasterIssuedLabel}:</strong> Expected to trigger on ${firstTriggerLeadTimeString}, ${firstTriggerLeadTimeFromNow}.`,
`<strong>${disasterIssuedLabel}:</strong> ${disasterSpecificCopy}`,
);
} else {
// Warning event
contentContent.push(
`<strong>${disasterIssuedLabel}:</strong> Expected to start on ${firstLeadTimeString}, ${firstLeadTimeFromNow}.`,
);
if (triggerStatusLabel === TriggerStatusLabelEnum.Trigger) {
// Trigger event
if (firstLeadTimeString !== firstTriggerLeadTimeString) {
// Warning-to-trigger event: show start of warning first
contentContent.push(
`<strong>${disasterTypeLabel}:</strong> Expected to start on ${firstLeadTimeString}, ${firstLeadTimeFromNow}.`,
);
}
// Either way, show start of trigger next, the above line is optionally extra
contentContent.push(
`<strong>${disasterIssuedLabel}:</strong> Expected to start on ${firstTriggerLeadTimeString}, ${firstTriggerLeadTimeFromNow}.`,
);
} else {
// Warning event
contentContent.push(
`<strong>${disasterIssuedLabel}:</strong> Expected to start on ${firstLeadTimeString}, ${firstLeadTimeFromNow}.`,
);
}
}

contentContent.push(
Expand All @@ -88,7 +101,7 @@ const getMjmlBodyEvent = ({
});

const closingElement = getTextElement({
content: `This ${triggerStatusLabel} was first issued by IBF on ${issuedDate} (${timeZone})`,
content: `This ${triggerStatusLabel.toLowerCase()} was first issued by IBF on ${issuedDate} (${timeZone})`,
attributes: {
'padding-top': '8px',
'font-size': '14px',
Expand All @@ -101,10 +114,48 @@ const getMjmlBodyEvent = ({
});
};

const getTyphoonSpecificCopy = (event: NotificationDataPerEventDto): string => {
let disasterSpecificCopy: string;
if (event.firstLeadTime === LeadTime.hour0) {
if (event.disasterSpecificProperties.typhoonLandfall) {
disasterSpecificCopy = 'Has already made landfall.';
} else {
disasterSpecificCopy =
'Has already reached the point closest to land. Not predicted to make landfall.';
}
} else {
if (event.disasterSpecificProperties.typhoonLandfall) {
disasterSpecificCopy = `Expected to make landfall on ${
event.triggerStatusLabel === TriggerStatusLabelEnum.Trigger
? event.firstTriggerLeadTimeString
: event.firstLeadTimeString
}.`;
} else if (event.disasterSpecificProperties.typhoonNoLandfallYet) {
disasterSpecificCopy =
'The landfall time prediction cannot be determined yet. Keep monitoring the event.';
} else {
disasterSpecificCopy = `Expected to reach the point closest to land on ${
event.triggerStatusLabel === TriggerStatusLabelEnum.Trigger
? event.firstTriggerLeadTimeString
: event.firstLeadTimeString
}. Not predicted to make landfall.`;
}
}
if (event.triggerStatusLabel === TriggerStatusLabelEnum.Warning) {
disasterSpecificCopy += ' Not predicted to reach trigger thresholds.';
}
return disasterSpecificCopy;
};

export const getMjmlEventListBody = (emailContent: ContentEventEmail) => {
const eventList = [];

for (const event of emailContent.dataPerEvent) {
let disasterSpecificCopy: string;
if (emailContent.disasterType === DisasterType.Typhoon) {
disasterSpecificCopy = getTyphoonSpecificCopy(event);
}

eventList.push(
getMjmlBodyEvent({
eventName: event.eventName,
Expand Down Expand Up @@ -137,12 +188,15 @@ export const getMjmlEventListBody = (emailContent: ContentEventEmail) => {

disasterIssuedLabel: getDisasterIssuedLabel(
event.eapAlertClass?.label,
emailContent.disasterTypeLabel,
event.triggerStatusLabel,
),
color: getIbfHexColor(
event.eapAlertClass?.color,
event.triggerStatusLabel,
),

// Disaster-specific copy
disasterSpecificCopy,
}),
);
}
Expand Down
16 changes: 9 additions & 7 deletions services/API-service/src/api/notification/helpers/mjml.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,11 +268,13 @@ export const getTimeFromNow = (leadTime: LeadTime) => {

const leadTimeQuantity = parseInt(leadTime.split('-')[0]);

return [LeadTime.day0, LeadTime.month0, LeadTime.hour0].includes(leadTime)
? 'ongoing'
: `${leadTime.replace('-', ' ')}${
leadTimeQuantity === 1 ? '' : 's'
} from now`;
if (leadTimeQuantity === 0) {
return 'ongoing';
}

return `${leadTime.replace('-', ' ')}${
leadTimeQuantity === 1 ? '' : 's'
} from now`;
};

export const getTriangleIcon = (
Expand Down Expand Up @@ -321,9 +323,9 @@ export const getPngImageAsDataURL = (relativePath: string) => {

export const getDisasterIssuedLabel = (
eapLabel: string,
disasterTypeLabel: string,
triggerStatusLabel: TriggerStatusLabelEnum,
) => {
return eapLabel || disasterTypeLabel;
return eapLabel || triggerStatusLabel;
};

export const getIbfHexColor = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export class NotificationContentService {
});
}

public async getDisaster(
private async getDisaster(
disasterType: DisasterType,
): Promise<DisasterEntity> {
return await this.disasterRepository.findOne({
Expand Down Expand Up @@ -165,11 +165,7 @@ export class NotificationContentService {
: TriggerStatusLabelEnum.Warning;

data.eventName = await this.getFormattedEventName(event, disasterType);
data.disasterSpecificCopy = await this.getDisasterSpecificCopy(
disasterType,
event.firstLeadTime,
event,
);
data.disasterSpecificProperties = event.disasterSpecificProperties;
data.firstLeadTime = event.firstLeadTime;
data.firstTriggerLeadTime = event.firstTriggerLeadTime;
data.triggeredAreas = await this.getSortedTriggeredAreas(
Expand All @@ -178,7 +174,7 @@ export class NotificationContentService {
event,
);
data.nrOfTriggeredAreas = data.triggeredAreas.length;
// This looks weird, but as far as I understand the startDate of the event is the day it was first issued

data.issuedDate = new Date(event.startDate);
data.firstLeadTimeString = await this.getFirstLeadTimeString(
event,
Expand Down Expand Up @@ -348,109 +344,6 @@ export class NotificationContentService {
);
}

private async getDisasterSpecificCopy(
disasterType: DisasterType,
leadTime: LeadTime,
event: EventSummaryCountry,
): Promise<{
eventStatus: string;
extraInfo: string;
leadTimeString?: string;
timestamp?: string;
}> {
switch (disasterType) {
case DisasterType.HeavyRain:
return this.getHeavyRainCopy();
case DisasterType.Typhoon:
return await this.getTyphoonCopy(leadTime, event);
case DisasterType.FlashFloods:
return await this.getFlashFloodsCopy(leadTime, event);
default:
return { eventStatus: '', extraInfo: '' };
}
}

private getHeavyRainCopy(): {
eventStatus: string;
extraInfo: string;
} {
return {
eventStatus: 'Estimated',
extraInfo: '',
};
}

private async getTyphoonCopy(
leadTime: LeadTime,
event: EventSummaryCountry,
): Promise<{
eventStatus: string;
extraInfo: string;
leadTimeString: string;
timestamp: string;
}> {
const { typhoonLandfall, typhoonNoLandfallYet } =
event.disasterSpecificProperties;
let eventStatus = '';
let extraInfo = '';
let leadTimeString = null;

if (leadTime === LeadTime.hour0) {
if (typhoonLandfall) {
eventStatus = 'Has <strong>already made landfall</strong>';
leadTimeString = 'Already made landfall';
} else {
eventStatus = 'Has already reached the point closest to land';
leadTimeString = 'reached the point closest to land';
}
} else {
if (typhoonNoLandfallYet) {
eventStatus =
'<strong>Landfall time prediction cannot be determined yet</strong>';
extraInfo = 'Keep monitoring the event.';
leadTimeString = 'Undetermined landfall';
} else if (typhoonLandfall) {
eventStatus = 'Estimated to <strong>make landfall</strong>';
} else {
eventStatus =
'<strong>Not predicted to make landfall</strong>. It is estimated to reach the point closest to land';
}
}

const timestampString = await this.getLeadTimeTimestamp(
leadTime,
event.countryCodeISO3,
DisasterType.Typhoon,
);

return {
eventStatus: eventStatus,
extraInfo: extraInfo,
leadTimeString,
timestamp: timestampString,
};
}

private async getFlashFloodsCopy(
leadTime: LeadTime,
event: EventSummaryCountry,
): Promise<{
eventStatus: string;
extraInfo: string;
timestamp: string;
}> {
const timestampString = await this.getLeadTimeTimestamp(
leadTime,
event.countryCodeISO3,
DisasterType.FlashFloods,
);
return {
eventStatus: 'The flash flood is forecasted: ',
extraInfo: '',
timestamp: timestampString,
};
}

private async getLeadTimeTimestamp(
leadTime: LeadTime,
countryCodeISO3: string,
Expand Down
Loading

0 comments on commit c5f4527

Please sign in to comment.