From 5c2e1d1c27614ebd4ba5a55f465ce9248d725ba5 Mon Sep 17 00:00:00 2001 From: ColinBuyck <53269332+ColinBuyck@users.noreply.github.com> Date: Thu, 13 Jun 2024 10:09:32 -0700 Subject: [PATCH] fix: fallback and refresh translations (#4105) * fix: fallback and refresh translations * fix: commenting * fix: remove logging * fix: updated translation logic * fix: add contentUpdatedAt field * fix: seeding updates * fix: add testing coverage * fix: testing tweaks * fix: context for new field * fix: pr review cleanup --- .../12_add_content_updated_at/migration.sql | 2 ++ api/prisma/schema.prisma | 1 + api/prisma/seed-staging.ts | 6 ++++ api/src/dtos/listings/listing.dto.ts | 8 +++++ api/src/services/listing.service.ts | 2 ++ api/src/services/translation.service.ts | 36 +++++++++++++++++-- .../unit/services/listing.service.spec.ts | 4 +++ .../unit/services/translation.service.spec.ts | 28 +++++++++++++++ shared-helpers/src/types/backend-swagger.ts | 9 +++++ 9 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 api/prisma/migrations/12_add_content_updated_at/migration.sql diff --git a/api/prisma/migrations/12_add_content_updated_at/migration.sql b/api/prisma/migrations/12_add_content_updated_at/migration.sql new file mode 100644 index 0000000000..ee26dcf55d --- /dev/null +++ b/api/prisma/migrations/12_add_content_updated_at/migration.sql @@ -0,0 +1,2 @@ +-- Add field to capture most recent listing content update time +ALTER TABLE "listings" ADD COLUMN "content_updated_at" TIMESTAMPTZ(6); diff --git a/api/prisma/schema.prisma b/api/prisma/schema.prisma index e6b9efb69b..31b8dc98cc 100644 --- a/api/prisma/schema.prisma +++ b/api/prisma/schema.prisma @@ -508,6 +508,7 @@ model Listings { isWaitlistOpen Boolean? @map("is_waitlist_open") waitlistOpenSpots Int? @map("waitlist_open_spots") customMapPin Boolean? @map("custom_map_pin") + contentUpdatedAt DateTime? @map("content_updated_at") @db.Timestamptz(6) publishedAt DateTime? @map("published_at") @db.Timestamptz(6) closedAt DateTime? @map("closed_at") @db.Timestamptz(6) afsLastRunAt DateTime? @default(dbgenerated("'1970-01-01 00:00:00-07'::timestamp with time zone")) @map("afs_last_run_at") @db.Timestamptz(6) diff --git a/api/prisma/seed-staging.ts b/api/prisma/seed-staging.ts index 5627ccddfe..58a77861d7 100644 --- a/api/prisma/seed-staging.ts +++ b/api/prisma/seed-staging.ts @@ -285,6 +285,7 @@ export const stagingSeed = async ( isWaitlistOpen: false, waitlistOpenSpots: null, customMapPin: false, + contentUpdatedAt: new Date(), publishedAt: new Date(), listingsBuildingAddress: { create: whiteHouse, @@ -414,6 +415,7 @@ export const stagingSeed = async ( isWaitlistOpen: false, waitlistOpenSpots: null, customMapPin: false, + contentUpdatedAt: new Date(), publishedAt: new Date(), listingsApplicationPickUpAddress: undefined, listingsApplicationDropOffAddress: undefined, @@ -561,6 +563,7 @@ export const stagingSeed = async ( isWaitlistOpen: false, waitlistOpenSpots: null, customMapPin: false, + contentUpdatedAt: new Date(), publishedAt: new Date(), listingsBuildingAddress: { create: whiteHouse, @@ -678,6 +681,7 @@ export const stagingSeed = async ( isWaitlistOpen: false, waitlistOpenSpots: null, customMapPin: false, + contentUpdatedAt: dayjs(new Date()).subtract(1, 'days').toDate(), publishedAt: dayjs(new Date()).subtract(3, 'days').toDate(), closedAt: dayjs(new Date()).subtract(1, 'days').toDate(), listingsApplicationPickUpAddress: undefined, @@ -781,6 +785,7 @@ export const stagingSeed = async ( isWaitlistOpen: true, waitlistOpenSpots: 6, customMapPin: false, + contentUpdatedAt: new Date(), publishedAt: new Date(), listingsApplicationPickUpAddress: undefined, listingsApplicationDropOffAddress: undefined, @@ -870,6 +875,7 @@ export const stagingSeed = async ( isWaitlistOpen: false, waitlistOpenSpots: null, customMapPin: false, + contentUpdatedAt: new Date(), publishedAt: new Date(), listingsApplicationPickUpAddress: undefined, listingsApplicationDropOffAddress: undefined, diff --git a/api/src/dtos/listings/listing.dto.ts b/api/src/dtos/listings/listing.dto.ts index 9c593bd32d..59adb3fc20 100644 --- a/api/src/dtos/listings/listing.dto.ts +++ b/api/src/dtos/listings/listing.dto.ts @@ -380,6 +380,14 @@ class Listing extends AbstractDTO { @ApiPropertyOptional() customMapPin?: boolean; + //Used to refresh translations and communicate recent changes to admin users + //should be revisited after translations refactoring to see if its still useful + @Expose() + @IsDate({ groups: [ValidationsGroupsEnum.default] }) + @Type(() => Date) + @ApiPropertyOptional() + contentUpdatedAt?: Date; + @Expose() @IsDate({ groups: [ValidationsGroupsEnum.default] }) @Type(() => Date) diff --git a/api/src/services/listing.service.ts b/api/src/services/listing.service.ts index fd9b9adf22..24361a1657 100644 --- a/api/src/services/listing.service.ts +++ b/api/src/services/listing.service.ts @@ -893,6 +893,7 @@ export class ListingService implements OnModuleInit { } : undefined, requestedChangesUser: undefined, + contentUpdatedAt: new Date(), }, }); @@ -1363,6 +1364,7 @@ export class ListingService implements OnModuleInit { })), } : undefined, + contentUpdatedAt: new Date(), publishedAt: storedListing.status !== ListingsStatusEnum.active && dto.status === ListingsStatusEnum.active diff --git a/api/src/services/translation.service.ts b/api/src/services/translation.service.ts index 5ac2c83fda..d3f410cd06 100644 --- a/api/src/services/translation.service.ts +++ b/api/src/services/translation.service.ts @@ -181,7 +181,9 @@ export class TranslationService { if (translatedValue) { [...Object.keys(cleanedPaths).values()].forEach((path, index) => { - lodash.set(listing, path, translatedValue[0][index]); + if (translatedValue[0][index]) { + lodash.set(listing, path, translatedValue[0][index]); + } }); } @@ -192,9 +194,37 @@ export class TranslationService { listing: Listing, language: LanguagesEnum, ) { - return this.prisma.generatedListingTranslations.findFirst({ - where: { listingId: listing.id, language: language }, + const existingTranslations = + await this.prisma.generatedListingTranslations.findFirst({ + where: { + listingId: listing.id, + language: language, + }, + }); + + //determine when listing or associated preferences most recently changed + let mostRecentUpdate = listing.contentUpdatedAt; + listing.listingMultiselectQuestions?.forEach((multiselectObj) => { + const multiselectUpdatedAt = + multiselectObj.multiselectQuestions?.updatedAt; + if (mostRecentUpdate < multiselectUpdatedAt) { + mostRecentUpdate = multiselectUpdatedAt; + } }); + //refresh translations if application content changed since translation creation + if ( + existingTranslations && + existingTranslations.createdAt < mostRecentUpdate + ) { + await this.prisma.generatedListingTranslations.delete({ + where: { + id: existingTranslations.id, + }, + }); + return undefined; + } + + return existingTranslations; } private async persistNewTranslatedValues( diff --git a/api/test/unit/services/listing.service.spec.ts b/api/test/unit/services/listing.service.spec.ts index 9e05da05d7..9b8f56f1d9 100644 --- a/api/test/unit/services/listing.service.spec.ts +++ b/api/test/unit/services/listing.service.spec.ts @@ -1771,6 +1771,7 @@ describe('Testing listing service', () => { }, data: { name: 'example listing name', + contentUpdatedAt: expect.anything(), depositMin: '5', assets: { create: [ @@ -1870,6 +1871,7 @@ describe('Testing listing service', () => { }, data: { ...val, + contentUpdatedAt: expect.anything(), assets: { create: [exampleAsset], }, @@ -2306,6 +2308,7 @@ describe('Testing listing service', () => { }, data: { name: 'example listing name', + contentUpdatedAt: expect.anything(), depositMin: '5', assets: [ { @@ -2430,6 +2433,7 @@ describe('Testing listing service', () => { ...val, id: undefined, publishedAt: expect.anything(), + contentUpdatedAt: expect.anything(), assets: [exampleAsset], applicationMethods: { create: [ diff --git a/api/test/unit/services/translation.service.spec.ts b/api/test/unit/services/translation.service.spec.ts index b0b615c28d..f04cfa4ef6 100644 --- a/api/test/unit/services/translation.service.spec.ts +++ b/api/test/unit/services/translation.service.spec.ts @@ -9,12 +9,14 @@ import { TranslationService } from '../../../src/services/translation.service'; import { PrismaService } from '../../../src/services/prisma.service'; import { Test, TestingModule } from '@nestjs/testing'; import { GoogleTranslateService } from '../../../src/services/google-translate.service'; +import dayjs from 'dayjs'; const mockListing = (): Listing => { const basicListing = { id: 'id 1', createdAt: new Date(), updatedAt: new Date(), + contentUpdatedAt: new Date(), name: 'listing 1', status: ListingsStatusEnum.active, displayWaitlistSize: true, @@ -210,6 +212,31 @@ describe('Testing translations service', () => { validateTranslatedFields(result); }); + it('Should fetch translations and translate listing if db translations outdated', async () => { + googleTranslateServiceMock.fetch.mockResolvedValueOnce([translatedStrings]); + prisma.generatedListingTranslations.findFirst = jest + .fn() + .mockResolvedValue({ + createdAt: dayjs(new Date()).subtract(1, 'days').toDate(), + id: randomUUID(), + translations: [translatedStrings], + }); + prisma.generatedListingTranslations.delete = jest + .fn() + .mockResolvedValue(null); + + const result = await service.translateListing( + mockListing() as Listing, + LanguagesEnum.es, + ); + expect(googleTranslateServiceMock.fetch).toHaveBeenCalledTimes(1); + expect(prisma.generatedListingTranslations.findFirst).toHaveBeenCalledTimes( + 1, + ); + expect(prisma.generatedListingTranslations.delete).toHaveBeenCalledTimes(1); + validateTranslatedFields(result); + }); + it('Should fetch translations from db and translate listing', async () => { prisma.generatedListingTranslations.findFirst = jest .fn() @@ -238,6 +265,7 @@ describe('Testing translations service', () => { expect(prisma.translations.findFirst).toBeCalledTimes(1); expect(result).toEqual(nullJurisdiction); }); + it('Should get merged translations for jurisdiction in english', async () => { const nullJurisdiction = { value: 'null jurisdiction', diff --git a/shared-helpers/src/types/backend-swagger.ts b/shared-helpers/src/types/backend-swagger.ts index db69975761..84d9b77919 100644 --- a/shared-helpers/src/types/backend-swagger.ts +++ b/shared-helpers/src/types/backend-swagger.ts @@ -2945,6 +2945,9 @@ export interface Listing { /** */ customMapPin?: boolean + /** */ + contentUpdatedAt?: Date + /** */ publishedAt?: Date @@ -3449,6 +3452,9 @@ export interface ListingCreate { /** */ customMapPin?: boolean + /** */ + contentUpdatedAt?: Date + /** */ lastApplicationUpdateAt?: Date @@ -3697,6 +3703,9 @@ export interface ListingUpdate { /** */ customMapPin?: boolean + /** */ + contentUpdatedAt?: Date + /** */ lastApplicationUpdateAt?: Date