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

fix: provide notification group on creation by blueprint #5296

Merged
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
32 changes: 28 additions & 4 deletions apps/api/src/app/blueprint/blueprint.controller.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,44 @@
import { ClassSerializerInterceptor, Controller, Get, Param, UseInterceptors } from '@nestjs/common';

import { EnvironmentRepository, NotificationTemplateRepository } from '@novu/dal';

import { GroupedBlueprintResponse } from './dto/grouped-blueprint.response.dto';
import { GetBlueprint, GetBlueprintCommand } from './usecases/get-blueprint';
import { GetGroupedBlueprints } from './usecases/get-grouped-blueprints';
import { GetGroupedBlueprints, GetGroupedBlueprintsCommand } from './usecases/get-grouped-blueprints';
import { GetBlueprintResponse } from './dto/get-blueprint.response.dto';
import { ApiCommonResponses } from '../shared/framework/response.decorator';

@ApiCommonResponses()
@Controller('/blueprints')
@UseInterceptors(ClassSerializerInterceptor)
export class BlueprintController {
constructor(private getBlueprintUsecase: GetBlueprint, private getGroupedBlueprintsUsecase: GetGroupedBlueprints) {}
constructor(
private environmentRepository: EnvironmentRepository,
private getBlueprintUsecase: GetBlueprint,
private getGroupedBlueprintsUsecase: GetGroupedBlueprints
) {}

@Get('/group-by-category')
getGroupedBlueprints(): Promise<GroupedBlueprintResponse> {
return this.getGroupedBlueprintsUsecase.execute();
async getGroupedBlueprints(): Promise<GroupedBlueprintResponse> {
const prodEnvironmentId = await this.getProdEnvironmentId();

return this.getGroupedBlueprintsUsecase.execute(
GetGroupedBlueprintsCommand.create({ environmentId: prodEnvironmentId })
);
}

private async getProdEnvironmentId() {
const productionEnvironmentId = (
await this.environmentRepository.findOrganizationEnvironments(
NotificationTemplateRepository.getBlueprintOrganizationId() || ''
)
)?.find((env) => env.name === 'Production')?._id;

if (!productionEnvironmentId) {
throw new Error('Production environment id was not found');
}

return productionEnvironmentId;
}

@Get('/:templateIdOrIdentifier')
Expand Down
12 changes: 7 additions & 5 deletions apps/api/src/app/blueprint/e2e/get-grouped-blueprints.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect } from 'chai';
import * as sinon from 'sinon';

import { UserSession } from '@novu/testing';
import { NotificationTemplateRepository, EnvironmentRepository } from '@novu/dal';
import { NotificationTemplateRepository, EnvironmentRepository, EnvironmentEntity } from '@novu/dal';
import {
EmailBlockTypeEnum,
FieldLogicalOperatorEnum,
Expand Down Expand Up @@ -52,6 +52,7 @@ describe('Get grouped notification template blueprints - /blueprints/group-by-ca

it('should get the grouped blueprints', async function () {
const prodEnv = await getProductionEnvironment();
if (!prodEnv) throw new Error('production environment was not found');

await createTemplateFromBlueprint({ session, notificationTemplateRepository, prodEnv });

Expand Down Expand Up @@ -82,6 +83,7 @@ describe('Get grouped notification template blueprints - /blueprints/group-by-ca

it('should get the updated grouped blueprints (after invalidation)', async function () {
const prodEnv = await getProductionEnvironment();
if (!prodEnv) throw new Error('production environment was not found');

await createTemplateFromBlueprint({
session,
Expand Down Expand Up @@ -112,6 +114,8 @@ describe('Get grouped notification template blueprints - /blueprints/group-by-ca

it('should update the static POPULAR_TEMPLATES_GROUPED with fresh data', async () => {
const prodEnv = await getProductionEnvironment();
if (!prodEnv) throw new Error('production environment was not found');

await createTemplateFromBlueprint({ session, notificationTemplateRepository, prodEnv });

const data = await session.testAgent.get(`/v1/blueprints/group-by-category`).send();
Expand All @@ -128,7 +132,7 @@ describe('Get grouped notification template blueprints - /blueprints/group-by-ca
indexModuleStub.value(mockedValue);

await invalidateCache.invalidateByKey({
key: buildGroupedBlueprintsKey(),
key: buildGroupedBlueprintsKey(prodEnv._id),
});

const updatedBlueprintFromDb = (await session.testAgent.get(`/v1/blueprints/group-by-category`).send()).body.data
Expand Down Expand Up @@ -165,7 +169,7 @@ export async function createTemplateFromBlueprint({
}: {
session: UserSession;
notificationTemplateRepository: NotificationTemplateRepository;
prodEnv;
prodEnv: EnvironmentEntity;
}) {
const testTemplateRequestDto: Partial<CreateWorkflowRequestDto> = {
name: 'test email template',
Expand Down Expand Up @@ -213,8 +217,6 @@ export async function createTemplateFromBlueprint({
enabled: false,
});

if (!prodEnv) throw new Error('production environment was not found');

const blueprintId = (
await notificationTemplateRepository.findOne({
_environmentId: prodEnv._id,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Injectable, NotFoundException } from '@nestjs/common';

import { NotificationTemplateRepository } from '@novu/dal';
import { NotificationTemplateRepository, NotificationTemplateEntity } from '@novu/dal';

import { GetBlueprintCommand } from './get-blueprint.command';
import { GetBlueprintResponse } from '../../dto/get-blueprint.response.dto';
import { NotificationTemplateEntity } from '@novu/dal/src';

@Injectable()
export class GetBlueprint {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { BaseCommand } from '@novu/application-generic';
import { EnvironmentLevelCommand } from '@novu/application-generic';

export class GetGroupedBlueprintsCommand extends BaseCommand {}
export class GetGroupedBlueprintsCommand extends EnvironmentLevelCommand {}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { NotificationTemplateRepository, NotificationTemplateEntity } from '@novu/dal';
import { buildGroupedBlueprintsKey, CachedEntity } from '@novu/application-generic';
import { INotificationTemplate, IGroupedBlueprint } from '@novu/shared';
import { IGroupedBlueprint } from '@novu/shared';

import { GroupedBlueprintResponse } from '../../dto/grouped-blueprint.response.dto';
import { POPULAR_GROUPED_NAME, POPULAR_TEMPLATES_ID_LIST } from './index';
import { GetGroupedBlueprintsCommand, POPULAR_GROUPED_NAME, POPULAR_TEMPLATES_ID_LIST } from './index';

const WEEK_IN_SECONDS = 60 * 60 * 24 * 7;

Expand All @@ -13,17 +13,17 @@ export class GetGroupedBlueprints {
constructor(private notificationTemplateRepository: NotificationTemplateRepository) {}

@CachedEntity({
builder: () => buildGroupedBlueprintsKey(),
builder: (command: GetGroupedBlueprintsCommand) => buildGroupedBlueprintsKey(command.environmentId),
options: { ttl: WEEK_IN_SECONDS },
})
async execute(): Promise<GroupedBlueprintResponse> {
const groups = await this.fetchGroupedBlueprints();
async execute(command: GetGroupedBlueprintsCommand): Promise<GroupedBlueprintResponse> {
const generalGroups = await this.fetchGroupedBlueprints();

const updatePopularBlueprints = this.updatePopularBlueprints(groups);
const updatePopularBlueprints = this.getPopularGroupBlueprints(generalGroups);

const popular = { name: POPULAR_GROUPED_NAME, blueprints: updatePopularBlueprints };
const popularGroup = { name: POPULAR_GROUPED_NAME, blueprints: updatePopularBlueprints };

return { general: groups as IGroupedBlueprint[], popular };
return { general: generalGroups as IGroupedBlueprint[], popular: popularGroup as IGroupedBlueprint };
}

private async fetchGroupedBlueprints() {
Expand All @@ -41,27 +41,28 @@ export class GetGroupedBlueprints {
return groups.map((group) => group.blueprints).flat();
}

private updatePopularBlueprints(
private getPopularGroupBlueprints(
groups: { name: string; blueprints: NotificationTemplateEntity[] }[]
): INotificationTemplate[] {
): NotificationTemplateEntity[] {
const storedBlueprints = this.groupedToBlueprintsArray(groups);

const localPopularIds = [...POPULAR_TEMPLATES_ID_LIST];

const result: INotificationTemplate[] = [];
const result: NotificationTemplateEntity[] = [];

for (const localPopularId of localPopularIds) {
const storedBlueprint = storedBlueprints.find((blueprint) => blueprint._id === localPopularId);

if (!storedBlueprint) {
Logger.warn(
`Could not find stored popular blueprint id: ${localPopularId}, BLUEPRINT_CREATOR: ${NotificationTemplateRepository.getBlueprintOrganizationId()}`
`Could not find stored popular blueprint id: ${localPopularId}, BLUEPRINT_CREATOR:
${NotificationTemplateRepository.getBlueprintOrganizationId()}`
);

continue;
}

result.push(storedBlueprint as INotificationTemplate);
result.push(storedBlueprint);
}

return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
NotificationStepEntity,
NotificationGroupRepository,
StepVariantEntity,
EnvironmentRepository,
} from '@novu/dal';
import { ChangeEntityTypeEnum } from '@novu/shared';
import {
Expand All @@ -24,6 +25,7 @@ export class PromoteNotificationTemplateChange {
constructor(
private invalidateCache: InvalidateCacheService,
private notificationTemplateRepository: NotificationTemplateRepository,
private environmentRepository: EnvironmentRepository,
private messageTemplateRepository: MessageTemplateRepository,
private notificationGroupRepository: NotificationGroupRepository,
@Inject(forwardRef(() => ApplyChange)) private applyChange: ApplyChange,
Expand Down Expand Up @@ -217,9 +219,15 @@ export class PromoteNotificationTemplateChange {

private async invalidateBlueprints(command: PromoteTypeChangeCommand) {
if (command.organizationId === this.blueprintOrganizationId) {
await this.invalidateCache.invalidateByKey({
key: buildGroupedBlueprintsKey(),
});
const productionEnvironmentId = (
await this.environmentRepository.findOrganizationEnvironments(this.blueprintOrganizationId)
)?.find((env) => env.name === 'Production')?._id;

if (productionEnvironmentId) {
await this.invalidateCache.invalidateByKey({
key: buildGroupedBlueprintsKey(productionEnvironmentId),
});
}
}
}

Expand Down
11 changes: 10 additions & 1 deletion apps/api/src/app/workflows/dto/create-workflow.request.dto.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { IsArray, IsBoolean, IsDefined, IsOptional, IsString, MaxLength, ValidateNested } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { ICreateWorkflowDto, IPreferenceChannels, NotificationTemplateCustomData } from '@novu/shared';
import {
ICreateWorkflowDto,
INotificationGroup,
IPreferenceChannels,
NotificationTemplateCustomData,
} from '@novu/shared';

import { PreferenceChannels } from '../../shared/dtos/preference-channels';
import { NotificationStep } from '../../shared/dtos/notification-step';
Expand All @@ -18,6 +23,10 @@ export class CreateWorkflowRequestDto implements ICreateWorkflowDto {
})
notificationGroupId: string;

@ApiProperty()
@IsOptional()
notificationGroup?: INotificationGroup;

@ApiPropertyOptional()
@IsOptional()
@IsArray()
Expand Down
Loading
Loading