Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: listing opportunity email content updates #1021

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/prisma/seed-helpers/translation-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ const translations = (jurisdictionName?: string, language?: LanguagesEnum) => {
rentalOpportunity: {
subject: 'New rental opportunity',
intro: 'Rental opportunity at',
viewListingNotice: 'Please view listing for the most updated information',
applicationsDue: 'Applications Due',
community: 'Community',
address: 'Address',
Expand Down
15 changes: 15 additions & 0 deletions api/src/controllers/script-runner.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,4 +248,19 @@ export class ScirptRunnerController {
): Promise<SuccessDTO> {
return await this.scriptRunnerService.removeWorkAddresses(req);
}

@Put('addNoticeToListingOpportunityEmail')
@ApiOperation({
summary:
'A script that adds notice translations for the listing opportunity email',
operationId: 'addNoticeToListingOpportunityEmail',
})
@ApiOkResponse({ type: SuccessDTO })
async addNoticeToListingOpportunityEmail(
@Request() req: ExpressRequest,
): Promise<SuccessDTO> {
return await this.scriptRunnerService.addNoticeToListingOpportunityEmail(
req,
);
}
}
5 changes: 3 additions & 2 deletions api/src/services/email.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,8 @@ export class EmailService {
label: this.polyglot.t('rentalOpportunity.applicationsDue'),
value: dayjs(listing.applicationDueDate)
.tz(process.env.TIME_ZONE)
.format('MMMM D, YYYY'),
.format('MMMM D, YYYY [at] h:mma PST'),
bolded: true,
});
}
tableRows.push({
Expand Down Expand Up @@ -577,7 +578,7 @@ export class EmailService {
label: this.polyglot.t('rentalOpportunity.lottery'),
value: dayjs(lotteryEvent.startDate)
.tz(process.env.TIME_ZONE)
.format('MMMM D, YYYY'),
.format('MMMM D, YYYY [at] h:mma PST'),
});
}
}
Expand Down
51 changes: 51 additions & 0 deletions api/src/services/script-runner.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1379,6 +1379,57 @@ export class ScriptRunnerService {
return { success: true };
}

/**
*
* @param req incoming request object
* @returns successDTO
* @description updates single use code translations to show extended expiration time
*/
async addNoticeToListingOpportunityEmail(
req: ExpressRequest,
): Promise<SuccessDTO> {
const requestingUser = mapTo(User, req['user']);
await this.markScriptAsRunStart(
'add notice translation for listing opportunity email',
requestingUser,
);

await this.updateTranslationsForLanguage(LanguagesEnum.en, {
rentalOpportunity: {
viewListingNotice:
'Please view listing for the most updated information',
},
});
await this.updateTranslationsForLanguage(LanguagesEnum.es, {
rentalOpportunity: {
viewListingNotice:
'Consulte el listado para obtener la información más actualizada',
},
});
await this.updateTranslationsForLanguage(LanguagesEnum.tl, {
rentalOpportunity: {
viewListingNotice:
'Mangyaring tingnan ang listahan para sa pinakabagong impormasyon',
},
});
await this.updateTranslationsForLanguage(LanguagesEnum.vi, {
rentalOpportunity: {
viewListingNotice: 'Vui lòng xem danh sách để biết thông tin mới nhất',
},
});
await this.updateTranslationsForLanguage(LanguagesEnum.zh, {
rentalOpportunity: {
viewListingNotice: '请查看列表以获取最新信息',
},
});

await this.markScriptAsComplete(
'add notice translation for listing opportunity email',
requestingUser,
);
return { success: true };
}

/**
this is simply an example
*/
Expand Down
5 changes: 3 additions & 2 deletions api/src/views/listing-opportunity.hbs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{{#> layout_default }}

<h1><span class="intro"> {{t "rentalOpportunity.intro"}}</span> <br />{{{listingName}}}</h1>
<h1><span class="intro">{{t "rentalOpportunity.intro"}}</span> <br />{{{listingName}}}</h1>
<p align="center"><em>{{t "rentalOpportunity.viewListingNotice"}}</em></p>
<table class="inset fit-content" role="presentation" border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
Expand All @@ -12,7 +13,7 @@
<td class="bold">
{{label}}
</td>
<td>
<td{{#if bolded}} class="bold"{{/if}}>
{{value}}
</td>
</tr>
Expand Down
60 changes: 60 additions & 0 deletions api/test/unit/services/email.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing';
import { ConfigModule } from '@nestjs/config';
import {
LanguagesEnum,
ListingEventsTypeEnum,
ListingsStatusEnum,
ReviewOrderTypeEnum,
} from '@prisma/client';
Expand All @@ -16,8 +17,10 @@ import { User } from '../../../src/dtos/users/user.dto';
import { ApplicationCreate } from '../../../src/dtos/applications/application-create.dto';
import { of } from 'rxjs';
import { HttpService } from '@nestjs/axios';
import dayjs from 'dayjs';

let sendMock;
let govSendMock;
const translationServiceMock = {
getMergedTranslations: () => {
return translationFactory().translations;
Expand Down Expand Up @@ -64,8 +67,10 @@ describe('Testing email service', () => {
beforeEach(async () => {
jest.useFakeTimers();
sendMock = jest.fn();
govSendMock = jest.fn();
service = await module.resolve(EmailService);
service.sendSES = sendMock;
service.govSend = govSendMock;
});

const user = {
Expand Down Expand Up @@ -443,5 +448,60 @@ describe('Testing email service', () => {
expect(emailMock.html).toMatch('Thank you,');
expect(emailMock.html).toMatch('Bloom Housing Portal');
});

it('should notify people of listing opportunity', async () => {
const listing = {
id: 'listingId',
name: 'test listing',
reviewOrderType: ReviewOrderTypeEnum.firstComeFirstServe,
applicationDueDate: new Date(),
status: ListingsStatusEnum.active,
jurisdictions: { name: 'test jurisdiction', id: 'jurisdictionId' },
displayWaitlistSize: false,
showWaitlist: false,
applicationMethods: [],
assets: [],
listingEvents: [
{
type: ListingEventsTypeEnum.publicLottery,
startDate: new Date(),
},
],
units: [],
referralApplication: undefined,
createdAt: new Date(),
updatedAt: new Date(),
listingsBuildingAddress: {
...yellowstoneAddress,
id: 'addressId',
createdAt: new Date(),
updatedAt: new Date(),
},
};
const service = await module.resolve(EmailService);
await service.listingOpportunity(listing);

const emailMock = govSendMock.mock.calls[0][0];
expect(emailMock).toMatch(
'<span class="intro">Rental opportunity at</span> <br />test listing',
);
expect(emailMock).toMatch(
'Please view listing for the most updated information',
);
expect(emailMock).toMatch(
/<td class="bold">\s*Applications Due\s*<\/td>/,
);
expect(emailMock).toMatch(
new RegExp(
`<td class="bold">\\s*${dayjs(listing.applicationDueDate)
.tz(process.env.TIME_ZONE)
.format('MMMM D, YYYY [at] h:mma PST')}\\s*</td>`,
),
);
expect(emailMock).toMatch(/<td class="bold">\s*Address\s*<\/td>/);
expect(emailMock).toMatch(
/<td>\s*3200 Old Faithful Inn Rd, Yellowstone National Park WY 82190\s*<\/td>/,
);
});
});
});
42 changes: 42 additions & 0 deletions api/test/unit/services/script-runner.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,48 @@ describe('Testing script runner service', () => {
expect(prisma.translations.create).toHaveBeenCalled();
});

it('should add translations for lottery opportunity email', async () => {
prisma.scriptRuns.findUnique = jest.fn().mockResolvedValue(null);
prisma.scriptRuns.create = jest.fn().mockResolvedValue(null);
prisma.scriptRuns.update = jest.fn().mockResolvedValue(null);
prisma.translations.findMany = jest
.fn()
.mockResolvedValue([{ id: randomUUID(), translations: {} }]);
prisma.translations.update = jest.fn().mockResolvedValue(null);

const id = randomUUID();
const scriptName = 'add notice translation for listing opportunity email';

const res = await service.addNoticeToListingOpportunityEmail({
user: {
id,
} as unknown as User,
} as unknown as ExpressRequest);

expect(res.success).toBe(true);

expect(prisma.scriptRuns.findUnique).toHaveBeenCalledWith({
where: {
scriptName,
},
});
expect(prisma.scriptRuns.create).toHaveBeenCalledWith({
data: {
scriptName,
triggeringUser: id,
},
});
expect(prisma.scriptRuns.update).toHaveBeenCalledWith({
data: {
didScriptRun: true,
triggeringUser: id,
},
where: {
scriptName,
},
});
});

describe('transferJurisdictionListingData', () => {
it('should transfer listings', async () => {
console.log = jest.fn();
Expand Down
16 changes: 16 additions & 0 deletions shared-helpers/src/types/backend-swagger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2497,6 +2497,22 @@ export class ScriptRunnerService {

configs.data = data

axios(configs, resolve, reject)
})
}
/**
* A script that adds notice translations for the listing opportunity email
*/
addNoticeToListingOpportunityEmail(options: IRequestOptions = {}): Promise<SuccessDTO> {
return new Promise((resolve, reject) => {
let url = basePath + "/scriptRunner/addNoticeToListingOpportunityEmail"

const configs: IRequestConfig = getConfigs("put", "application/json", url, options)

let data = null

configs.data = data

axios(configs, resolve, reject)
})
}
Expand Down
Loading