diff --git a/.source b/.source index ef867923b25..3be61785b93 160000 --- a/.source +++ b/.source @@ -1 +1 @@ -Subproject commit ef867923b25c505183a779045f4a25bada8c6697 +Subproject commit 3be61785b93134067621bc6163c2c3d4930df41d diff --git a/apps/api/src/app/bridge/usecases/get-bridge-status/get-bridge-status.usecase.ts b/apps/api/src/app/bridge/usecases/get-bridge-status/get-bridge-status.usecase.ts index 12dc4df7439..17c5ca8bfbf 100644 --- a/apps/api/src/app/bridge/usecases/get-bridge-status/get-bridge-status.usecase.ts +++ b/apps/api/src/app/bridge/usecases/get-bridge-status/get-bridge-status.usecase.ts @@ -1,10 +1,12 @@ -import { BadRequestException, Injectable } from '@nestjs/common'; +import { BadRequestException, Logger, Injectable } from '@nestjs/common'; import axios from 'axios'; import { HealthCheck } from '@novu/framework'; import { GetBridgeStatusCommand } from './get-bridge-status.command'; const axiosInstance = axios.create(); +export const LOG_CONTEXT = 'GetBridgeStatusUsecase'; + @Injectable() export class GetBridgeStatus { async execute(command: GetBridgeStatusCommand): Promise { @@ -18,6 +20,11 @@ export class GetBridgeStatus { return response.data; } catch (err: any) { + Logger.error( + `Failed to verify Bridge endpoint ${command.bridgeUrl} with error: ${(err as Error).message || err}`, + (err as Error).stack, + LOG_CONTEXT + ); throw new BadRequestException(`Bridge is not accessible. ${err.message}`); } } diff --git a/apps/api/src/app/organization/usecases/create-organization/create-organization.command.ts b/apps/api/src/app/organization/usecases/create-organization/create-organization.command.ts index ac5288cf53c..173cee4e2e9 100644 --- a/apps/api/src/app/organization/usecases/create-organization/create-organization.command.ts +++ b/apps/api/src/app/organization/usecases/create-organization/create-organization.command.ts @@ -1,6 +1,6 @@ import { IsDefined, IsEnum, IsOptional, IsString } from 'class-validator'; -import { JobTitleEnum, ProductUseCases } from '@novu/shared'; +import { JobTitleEnum } from '@novu/shared'; import { AuthenticatedCommand } from '../../../shared/commands/authenticated.command'; diff --git a/apps/api/src/app/workflows-v2/mappers/notification-template-mapper.ts b/apps/api/src/app/workflows-v2/mappers/notification-template-mapper.ts index b03faa81795..4064ee4678e 100644 --- a/apps/api/src/app/workflows-v2/mappers/notification-template-mapper.ts +++ b/apps/api/src/app/workflows-v2/mappers/notification-template-mapper.ts @@ -8,6 +8,8 @@ import { WorkflowListResponseDto, WorkflowOriginEnum, WorkflowResponseDto, + WorkflowStatusEnum, + WorkflowTypeEnum, } from '@novu/shared'; import { ControlValuesEntity, NotificationStepEntity, NotificationTemplateEntity } from '@novu/dal'; import { GetPreferencesResponseDto } from '@novu/application-generic'; @@ -31,8 +33,10 @@ export function toResponseWorkflowDto( name: template.name, description: template.description, origin: template.origin || WorkflowOriginEnum.NOVU_CLOUD, + type: template.type || WorkflowTypeEnum.BRIDGE, updatedAt: template.updatedAt || 'Missing Updated At', createdAt: template.createdAt || 'Missing Create At', + status: WorkflowStatusEnum.ACTIVE, }; } @@ -52,12 +56,15 @@ function getSteps(template: NotificationTemplateEntity, controlValuesMap: { [p: function toMinifiedWorkflowDto(template: NotificationTemplateEntity): WorkflowListResponseDto { return { + origin: template.origin || WorkflowOriginEnum.NOVU_CLOUD, + type: template.type || WorkflowTypeEnum.BRIDGE, _id: template._id, name: template.name, tags: template.tags, updatedAt: template.updatedAt || 'Missing Updated At', stepTypeOverviews: template.steps.map(buildStepTypeOverview).filter((stepTypeEnum) => !!stepTypeEnum), createdAt: template.createdAt || 'Missing Create At', + status: WorkflowStatusEnum.ACTIVE, }; } diff --git a/apps/api/src/app/workflows-v2/usecases/upsert-workflow/upsert-workflow.usecase.ts b/apps/api/src/app/workflows-v2/usecases/upsert-workflow/upsert-workflow.usecase.ts index 0678780617d..60ce422b20b 100644 --- a/apps/api/src/app/workflows-v2/usecases/upsert-workflow/upsert-workflow.usecase.ts +++ b/apps/api/src/app/workflows-v2/usecases/upsert-workflow/upsert-workflow.usecase.ts @@ -2,7 +2,6 @@ import { BadRequestException, Injectable } from '@nestjs/common'; import { ControlValuesEntity, - EnvironmentRepository, NotificationGroupRepository, NotificationStepEntity, NotificationTemplateEntity, @@ -58,7 +57,6 @@ export class UpsertWorkflowUseCase { private notificationGroupRepository: NotificationGroupRepository, private upsertPreferencesUsecase: UpsertPreferences, private upsertControlValuesUseCase: UpsertControlValuesUseCase, - private environmentRepository: EnvironmentRepository, private getPreferencesUseCase: GetPreferences ) {} async execute(command: UpsertWorkflowCommand): Promise { diff --git a/apps/api/src/app/workflows-v2/workflow.controller.e2e.ts b/apps/api/src/app/workflows-v2/workflow.controller.e2e.ts index c2d8c946ca0..1afe59a9eea 100644 --- a/apps/api/src/app/workflows-v2/workflow.controller.e2e.ts +++ b/apps/api/src/app/workflows-v2/workflow.controller.e2e.ts @@ -194,7 +194,6 @@ function buildErrorMsg(createWorkflowDto: Omit, cr async function createWorkflowAndValidate(nameSuffix: string = ''): Promise { const createWorkflowDto: CreateWorkflowDto = buildCreateWorkflowDto(nameSuffix); - console.log('createWorkflowDto', JSON.stringify(createWorkflowDto, null, 2)); const res = await session.testAgent.post(`${v2Prefix}/workflows`).send(createWorkflowDto); const workflowResponseDto: WorkflowResponseDto = res.body.data; expect(workflowResponseDto, JSON.stringify(res, null, 2)).to.be.ok; @@ -202,13 +201,16 @@ async function createWorkflowAndValidate(nameSuffix: string = ''): Promise removeFields(step, 'stepUuid') @@ -249,9 +251,6 @@ function buildCreateWorkflowDto(nameSuffix: string): CreateWorkflowDto { } async function updateWorkflowRest(id: string, workflow: UpdateWorkflowDto): Promise { - console.log(`updateWorkflow- ${id}: - ${JSON.stringify(workflow, null, 2)}`); - return await safePut(`${v2Prefix}/workflows/${id}`, workflow); } @@ -296,7 +295,14 @@ function validateUpdatedWorkflowAndRemoveResponseFields( workflowResponse: WorkflowResponseDto, workflowUpdateRequest: UpdateWorkflowDto ): UpdateWorkflowDto { - const updatedWorkflowWoUpdated: UpdateWorkflowDto = removeFields(workflowResponse, 'updatedAt', 'origin', '_id'); + const updatedWorkflowWoUpdated: UpdateWorkflowDto = removeFields( + workflowResponse, + 'updatedAt', + 'origin', + '_id', + 'status', + 'type' + ); const augmentedStep: (StepUpdateDto | StepCreateDto)[] = []; for (const stepInResponse of workflowResponse.steps) { expect(stepInResponse.stepUuid).to.be.ok; @@ -318,7 +324,6 @@ async function updateWorkflowAndValidate( updatedAt: string, updateRequest: UpdateWorkflowDto ): Promise { - console.log('updateRequest:::'.toUpperCase(), JSON.stringify(updateRequest.steps, null, 2)); const updatedWorkflow: WorkflowResponseDto = await updateWorkflowRest(id, updateRequest); const updatedWorkflowWithResponseFieldsRemoved = validateUpdatedWorkflowAndRemoveResponseFields( updatedWorkflow, @@ -542,24 +547,19 @@ function buildInAppStepWithValues() { } function convertResponseToUpdateDto(workflowCreated: WorkflowResponseDto): UpdateWorkflowDto { - return removeFields(workflowCreated, 'updatedAt', '_id', 'origin') as UpdateWorkflowDto; + return removeFields(workflowCreated, 'updatedAt', '_id', 'origin', 'type', 'status') as UpdateWorkflowDto; } function buildUpdateDtoWithValues(workflowCreated: WorkflowResponseDto): UpdateWorkflowDto { const updateDto = convertResponseToUpdateDto(workflowCreated); const updatedStep = addValueToExistingStep(updateDto.steps); const newStep = buildInAppStepWithValues(); - console.log('newStep:::', JSON.stringify(newStep, null, 2)); - const stoWithValues: UpdateWorkflowDto = { + return { ...updateDto, name: `${TEST_WORKFLOW_UPDATED_NAME}-${generateUUID()}`, steps: [updatedStep, newStep], }; - - console.log('updateDto:::', JSON.stringify(stoWithValues, null, 2)); - - return stoWithValues; } function createStep(): StepCreateDto { return { @@ -576,7 +576,14 @@ function createStep(): StepCreateDto { function buildUpdateRequest(workflowCreated: WorkflowResponseDto): UpdateWorkflowDto { const steps = [createStep()]; - const updateRequest = removeFields(workflowCreated, 'updatedAt', '_id', 'origin') as UpdateWorkflowDto; + const updateRequest = removeFields( + workflowCreated, + 'updatedAt', + '_id', + 'origin', + 'status', + 'type' + ) as UpdateWorkflowDto; return { ...updateRequest, name: TEST_WORKFLOW_UPDATED_NAME, steps }; } diff --git a/apps/api/src/instrument.ts b/apps/api/src/instrument.ts index 557e11fc94e..b4aad3add2e 100644 --- a/apps/api/src/instrument.ts +++ b/apps/api/src/instrument.ts @@ -1,24 +1,11 @@ -import { nodeProfilingIntegration } from '@sentry/profiling-node'; import { init } from '@sentry/nestjs'; +import { version } from '../package.json'; if (process.env.SENTRY_DSN) { init({ dsn: process.env.SENTRY_DSN, - integrations: [ - // Add our Profiling integration - nodeProfilingIntegration(), - ], - - /* - * Add Tracing by setting tracesSampleRate - * We recommend adjusting this value in production - */ - tracesSampleRate: 1.0, - - /* - * Set sampling rate for profiling - * This is relative to tracesSampleRate - */ - profilesSampleRate: 1.0, + environment: process.env.NODE_ENV, + release: `v${version}`, + ignoreErrors: ['Non-Error exception captured'], }); } diff --git a/apps/dashboard/src/components/side-navigation/constants.ts b/apps/dashboard/src/components/side-navigation/constants.ts index 5d457d59531..d5137618681 100644 --- a/apps/dashboard/src/components/side-navigation/constants.ts +++ b/apps/dashboard/src/components/side-navigation/constants.ts @@ -8,79 +8,86 @@ import { RiStore3Line, RiUserAddLine, } from 'react-icons/ri'; +import type { IEnvironment } from '@novu/shared'; import { NavItemsGroup } from './types'; -import { LEGACY_ROUTES, ROUTES } from '@/utils/routes'; +import { buildRoute, LEGACY_ROUTES, ROUTES } from '@/utils/routes'; -export const navigationItems: NavItemsGroup[] = [ - { - items: [ - { - label: 'Workflows', - icon: RiRouteFill, - to: ROUTES.WORKFLOWS, - }, - { - label: 'Subscribers', - icon: RiGroup2Line, - isExternal: true, - to: 'https://docs.novu.co/api-reference/subscribers/get-subscribers', - disabled: true, - }, - ], - }, - { - label: 'Monitor', - items: [ - { - label: 'Activity Feed', - icon: RiBarChartBoxLine, - to: LEGACY_ROUTES.ACTIVITY_FEED, - isExternal: true, - }, - ], - }, - { - label: 'Developer', - items: [ - { - label: 'Integration Store', - icon: RiStore3Line, - to: LEGACY_ROUTES.INTEGRATIONS, - isExternal: true, - }, - { - label: 'API Keys', - icon: RiKey2Line, - to: LEGACY_ROUTES.API_KEYS, - isExternal: true, - }, - ], - }, - { - label: 'Application', - items: [ - { - label: 'Branding', - icon: RiPaintBrushLine, - to: LEGACY_ROUTES.BRANDING, - isExternal: true, - }, - { - label: 'Settings', - icon: RiSettings4Line, - to: LEGACY_ROUTES.SETTINGS, - isExternal: true, - }, - ], - }, - { - items: [ - { - label: 'Invite teammates', - icon: RiUserAddLine, - to: LEGACY_ROUTES.INVITE_TEAM_MEMBERS, - isExternal: true, - }, - ], - }, -]; +export const buildNavigationItems = ({ + currentEnvironment, +}: { + currentEnvironment?: IEnvironment; +}): NavItemsGroup[] => { + return [ + { + items: [ + { + label: 'Workflows', + icon: RiRouteFill, + to: buildRoute(ROUTES.WORKFLOWS, { environmentId: currentEnvironment?._id ?? '' }), + }, + { + label: 'Subscribers', + icon: RiGroup2Line, + isExternal: true, + to: 'https://docs.novu.co/api-reference/subscribers/get-subscribers', + disabled: true, + }, + ], + }, + { + label: 'Monitor', + items: [ + { + label: 'Activity Feed', + icon: RiBarChartBoxLine, + to: LEGACY_ROUTES.ACTIVITY_FEED, + isExternal: true, + }, + ], + }, + { + label: 'Developer', + items: [ + { + label: 'Integration Store', + icon: RiStore3Line, + to: LEGACY_ROUTES.INTEGRATIONS, + isExternal: true, + }, + { + label: 'API Keys', + icon: RiKey2Line, + to: LEGACY_ROUTES.API_KEYS, + isExternal: true, + }, + ], + }, + { + label: 'Application', + items: [ + { + label: 'Branding', + icon: RiPaintBrushLine, + to: LEGACY_ROUTES.BRANDING, + isExternal: true, + }, + { + label: 'Settings', + icon: RiSettings4Line, + to: LEGACY_ROUTES.SETTINGS, + isExternal: true, + }, + ], + }, + { + items: [ + { + label: 'Invite teammates', + icon: RiUserAddLine, + to: LEGACY_ROUTES.INVITE_TEAM_MEMBERS, + isExternal: true, + }, + ], + }, + ]; +}; diff --git a/apps/dashboard/src/components/side-navigation/side-navigation.tsx b/apps/dashboard/src/components/side-navigation/side-navigation.tsx index 636133473ec..97b71787af4 100644 --- a/apps/dashboard/src/components/side-navigation/side-navigation.tsx +++ b/apps/dashboard/src/components/side-navigation/side-navigation.tsx @@ -6,7 +6,7 @@ import { Badge } from '../primitives/badge'; import { EnvironmentDropdown } from './environment-dropdown'; import { useEnvironment } from '@/context/environment/hooks'; import { OrganizationDropdown } from './organization-dropdown'; -import { navigationItems } from './constants'; +import { buildNavigationItems } from './constants'; import { NavItemsGroup, NavItem } from './types'; import { FreeTrialCard } from './free-trial-card'; @@ -91,7 +91,12 @@ const NavigationItemsGroup = ({ group }: { group: NavItemsGroup }) => { export const SideNavigation = () => { const { currentEnvironment, environments, switchEnvironment } = useEnvironment(); const environmentNames = useMemo(() => environments?.map((env) => env.name), [environments]); - const onEnvironmentChange = (value: string) => switchEnvironment(value); + const onEnvironmentChange = (value: string) => { + const environment = environments?.find((env) => env.name === value); + switchEnvironment(environment?._id); + }; + + const navigationItems = useMemo(() => buildNavigationItems({ currentEnvironment }), [currentEnvironment]); return (