-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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
refactor(api): Use UpdatePreference
use-case for all Subscriber Preference updates
#6889
Changes from all commits
123046e
ecfac24
27493b2
b61109d
e638dc0
af7821e
fd87c40
f5d76bb
ce4b0ba
4e7c973
b2f9974
dcc98e1
b96fc0f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,13 +9,13 @@ import { | |
UpsertSubscriberWorkflowPreferencesCommand, | ||
UpsertSubscriberGlobalPreferencesCommand, | ||
InstrumentUsecase, | ||
Instrument, | ||
} from '@novu/application-generic'; | ||
import { | ||
NotificationTemplateEntity, | ||
NotificationTemplateRepository, | ||
PreferenceLevelEnum, | ||
SubscriberEntity, | ||
SubscriberPreferenceEntity, | ||
SubscriberPreferenceRepository, | ||
SubscriberRepository, | ||
} from '@novu/dal'; | ||
|
@@ -55,46 +55,16 @@ export class UpdatePreferences { | |
} | ||
} | ||
|
||
const userPreference: SubscriberPreferenceEntity | null = await this.subscriberPreferenceRepository.findOne( | ||
this.commonQuery(command, subscriber) | ||
); | ||
if (!userPreference) { | ||
await this.createUserPreference(command, subscriber); | ||
} else { | ||
await this.updateUserPreference(command, subscriber); | ||
} | ||
await this.updateSubscriberPreference(command, subscriber); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As said above, we treat this as an upsert now. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Much cleaner! |
||
|
||
return await this.findPreference(command, subscriber); | ||
} | ||
|
||
private async createUserPreference(command: UpdatePreferencesCommand, subscriber: SubscriberEntity): Promise<void> { | ||
const channelPreferences: IPreferenceChannels = this.buildPreferenceChannels(command); | ||
|
||
await this.storePreferencesV2({ | ||
channels: channelPreferences, | ||
organizationId: command.organizationId, | ||
environmentId: command.environmentId, | ||
_subscriberId: subscriber._id, | ||
templateId: command.workflowId, | ||
}); | ||
|
||
this.analyticsService.mixpanelTrack(AnalyticsEventsEnum.CREATE_PREFERENCES, '', { | ||
_organization: command.organizationId, | ||
_subscriber: subscriber._id, | ||
_workflowId: command.workflowId, | ||
level: command.level, | ||
channels: channelPreferences, | ||
}); | ||
|
||
const query = this.commonQuery(command, subscriber); | ||
await this.subscriberPreferenceRepository.create({ | ||
...query, | ||
enabled: true, | ||
channels: channelPreferences, | ||
}); | ||
} | ||
|
||
private async updateUserPreference(command: UpdatePreferencesCommand, subscriber: SubscriberEntity): Promise<void> { | ||
@Instrument() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding some more instrumentation to the update preference flow. |
||
private async updateSubscriberPreference( | ||
command: UpdatePreferencesCommand, | ||
subscriber: SubscriberEntity | ||
): Promise<void> { | ||
const channelPreferences: IPreferenceChannels = this.buildPreferenceChannels(command); | ||
|
||
await this.storePreferencesV2({ | ||
|
@@ -136,6 +106,7 @@ export class UpdatePreferences { | |
}; | ||
} | ||
|
||
@Instrument() | ||
private async findPreference( | ||
command: UpdatePreferencesCommand, | ||
subscriber: SubscriberEntity | ||
|
@@ -198,6 +169,7 @@ export class UpdatePreferences { | |
/** | ||
* Strangler pattern to migrate to V2 preferences. | ||
*/ | ||
@Instrument() | ||
private async storePreferencesV2(item: { | ||
channels: IPreferenceChannels; | ||
organizationId: string; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -77,7 +77,14 @@ describe('Update Subscribers global preferences - /subscribers/:subscriberId/pre | |
expect(response.data.data.preference.channels).to.eql({}); | ||
}); | ||
|
||
it('should update user global preference and disable the flag for the future channels update', async function () { | ||
it('should unset all preferences when the preferences object is empty', async function () { | ||
const response = await updateGlobalPreferences({}, session); | ||
|
||
expect(response.data.data.preference.channels).to.eql({}); | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test case asserts that empty objects can be supplied when updating global preferences in relation to this |
||
|
||
// `enabled` flag is not used anymore. The presence of a preference object means that the subscriber has enabled notifications. | ||
it.skip('should update user global preference and disable the flag for the future channels update', async function () { | ||
const disablePreferenceData = { | ||
enabled: false, | ||
}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ import { | |
Get, | ||
HttpCode, | ||
HttpStatus, | ||
NotFoundException, | ||
Param, | ||
Patch, | ||
Post, | ||
|
@@ -29,6 +30,8 @@ import { | |
ApiRateLimitCostEnum, | ||
ButtonTypeEnum, | ||
ChatProviderIdEnum, | ||
IPreferenceChannels, | ||
TriggerTypeEnum, | ||
UserSessionData, | ||
} from '@novu/shared'; | ||
import { MessageEntity, PreferenceLevelEnum } from '@novu/dal'; | ||
|
@@ -50,8 +53,6 @@ import { GetSubscribers, GetSubscribersCommand } from './usecases/get-subscriber | |
import { GetSubscriber, GetSubscriberCommand } from './usecases/get-subscriber'; | ||
import { GetPreferencesByLevelCommand } from './usecases/get-preferences-by-level/get-preferences-by-level.command'; | ||
import { GetPreferencesByLevel } from './usecases/get-preferences-by-level/get-preferences-by-level.usecase'; | ||
import { UpdatePreference } from './usecases/update-preference/update-preference.usecase'; | ||
import { UpdateSubscriberPreferenceCommand } from './usecases/update-subscriber-preference'; | ||
import { UpdateSubscriberPreferenceResponseDto } from '../widgets/dtos/update-subscriber-preference-response.dto'; | ||
import { UpdateSubscriberPreferenceRequestDto } from '../widgets/dtos/update-subscriber-preference-request.dto'; | ||
import { MessageResponseDto } from '../widgets/dtos/message-response.dto'; | ||
|
@@ -90,10 +91,6 @@ import { MarkAllMessagesAs } from '../widgets/usecases/mark-all-messages-as/mark | |
import { MarkAllMessageAsRequestDto } from './dtos/mark-all-messages-as-request.dto'; | ||
import { BulkCreateSubscribers } from './usecases/bulk-create-subscribers/bulk-create-subscribers.usecase'; | ||
import { BulkCreateSubscribersCommand } from './usecases/bulk-create-subscribers'; | ||
import { | ||
UpdateSubscriberGlobalPreferences, | ||
UpdateSubscriberGlobalPreferencesCommand, | ||
} from './usecases/update-subscriber-global-preferences'; | ||
import { GetSubscriberPreferencesByLevelParams } from './params'; | ||
import { ThrottlerCategory, ThrottlerCost } from '../rate-limiting/guards'; | ||
import { MessageMarkAsRequestDto } from '../widgets/dtos/mark-as-request.dto'; | ||
|
@@ -102,6 +99,8 @@ import { MarkMessageAsByMark } from '../widgets/usecases/mark-message-as-by-mark | |
import { FeedResponseDto } from '../widgets/dtos/feeds-response.dto'; | ||
import { UserAuthentication } from '../shared/framework/swagger/api.key.security'; | ||
import { SdkGroupName, SdkMethodName, SdkUsePagination } from '../shared/framework/swagger/sdk.decorators'; | ||
import { UpdatePreferences } from '../inbox/usecases/update-preferences/update-preferences.usecase'; | ||
import { UpdatePreferencesCommand } from '../inbox/usecases/update-preferences/update-preferences.command'; | ||
|
||
@ThrottlerCategory(ApiRateLimitCategoryEnum.CONFIGURATION) | ||
@ApiCommonResponses() | ||
|
@@ -117,8 +116,7 @@ export class SubscribersController { | |
private getSubscriberUseCase: GetSubscriber, | ||
private getSubscribersUsecase: GetSubscribers, | ||
private getPreferenceUsecase: GetPreferencesByLevel, | ||
private updatePreferenceUsecase: UpdatePreference, | ||
private updateGlobalPreferenceUsecase: UpdateSubscriberGlobalPreferences, | ||
private updatePreferencesUsecase: UpdatePreferences, | ||
private getNotificationsFeedUsecase: GetNotificationsFeed, | ||
private getFeedCountUsecase: GetFeedCount, | ||
private markMessageAsUsecase: MarkMessageAs, | ||
|
@@ -465,16 +463,37 @@ export class SubscribersController { | |
@Param('parameter') templateId: string, | ||
@Body() body: UpdateSubscriberPreferenceRequestDto | ||
): Promise<UpdateSubscriberPreferenceResponseDto> { | ||
const command = UpdateSubscriberPreferenceCommand.create({ | ||
organizationId: user.organizationId, | ||
subscriberId, | ||
environmentId: user.environmentId, | ||
templateId, | ||
...(typeof body.enabled === 'boolean' && { enabled: body.enabled }), | ||
...(body.channel && { channel: body.channel }), | ||
}); | ||
const result = await this.updatePreferencesUsecase.execute( | ||
UpdatePreferencesCommand.create({ | ||
environmentId: user.environmentId, | ||
organizationId: user.organizationId, | ||
subscriberId, | ||
workflowId: templateId, | ||
level: PreferenceLevelEnum.TEMPLATE, | ||
...(body.channel && { [body.channel.type]: body.channel.enabled }), | ||
}) | ||
); | ||
|
||
return await this.updatePreferenceUsecase.execute(command); | ||
if (!result.workflow) throw new NotFoundException('Workflow not found'); | ||
|
||
return { | ||
preference: { | ||
channels: result.channels, | ||
enabled: result.enabled, | ||
}, | ||
template: { | ||
_id: result.workflow.id, | ||
name: result.workflow.name, | ||
critical: result.workflow.critical, | ||
triggers: [ | ||
{ | ||
identifier: result.workflow.identifier, | ||
type: TriggerTypeEnum.EVENT, | ||
variables: [], | ||
}, | ||
], | ||
}, | ||
}; | ||
} | ||
|
||
@Patch('/:subscriberId/preferences') | ||
|
@@ -490,16 +509,29 @@ export class SubscribersController { | |
@UserSession() user: UserSessionData, | ||
@Param('subscriberId') subscriberId: string, | ||
@Body() body: UpdateSubscriberGlobalPreferencesRequestDto | ||
) { | ||
const command = UpdateSubscriberGlobalPreferencesCommand.create({ | ||
organizationId: user.organizationId, | ||
subscriberId, | ||
environmentId: user.environmentId, | ||
enabled: body.enabled, | ||
preferences: body.preferences, | ||
}); | ||
): Promise<Omit<UpdateSubscriberPreferenceResponseDto, 'template'>> { | ||
const channels = body.preferences?.reduce((acc, curr) => { | ||
acc[curr.type] = curr.enabled; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this enabled flag the same as the one we talked about in skipped tests? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, this is the channel-level flag. There are 2
|
||
|
||
return acc; | ||
}, {} as IPreferenceChannels); | ||
|
||
const result = await this.updatePreferencesUsecase.execute( | ||
UpdatePreferencesCommand.create({ | ||
environmentId: user.environmentId, | ||
organizationId: user.organizationId, | ||
subscriberId, | ||
level: PreferenceLevelEnum.GLOBAL, | ||
...channels, | ||
}) | ||
); | ||
|
||
return await this.updateGlobalPreferenceUsecase.execute(command); | ||
return { | ||
preference: { | ||
channels: result.channels, | ||
enabled: result.enabled, | ||
}, | ||
}; | ||
} | ||
|
||
@ExternalApiAccessible() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In a follow-up PR (to keep this diff clean), I will rename this usecase from
UpdatePreferences
toUpsertSubscriberPreferences
as it functions as such now.