Skip to content

Commit

Permalink
chore(root): polish after review
Browse files Browse the repository at this point in the history
  • Loading branch information
djabarovgeorge committed Dec 27, 2024
1 parent 5fa6d4a commit 6667626
Show file tree
Hide file tree
Showing 25 changed files with 906 additions and 796 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,6 @@ export class DigestOutputRendererUsecase {
execute(renderCommand: RenderCommand): DigestRenderOutput {
const { skip, ...outputControls } = renderCommand.controlValues ?? {};

if (outputControls.length === 0) {
throw new InternalServerErrorException({
message: `Invalid digest control value data sent for rendering`,
controlValues: renderCommand.controlValues,
});
}

return outputControls as any;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
PreferencesResponseDto,
RuntimeIssueDto,
ShortIsPrefixEnum,
StepDataDto,
StepResponseDto,
StepTypeEnum,
WorkflowCreateAndUpdateKeys,
WorkflowListResponseDto,
Expand All @@ -17,7 +17,7 @@ import { buildSlug } from '../../shared/helpers/build-slug';

export function toResponseWorkflowDto(
workflow: WorkflowInternalResponseDto,
steps: StepDataDto[]
steps: StepResponseDto[]
): WorkflowResponseDto {
const preferencesDto: PreferencesResponseDto = {
user: workflow.userPreferences,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import { ControlValuesLevelEnum, ShortIsPrefixEnum, StepDataDto, WorkflowOriginEnum } from '@novu/shared';
import { ControlValuesLevelEnum, ShortIsPrefixEnum, StepResponseDto, WorkflowOriginEnum } from '@novu/shared';
import { ControlValuesRepository, NotificationStepEntity, NotificationTemplateEntity } from '@novu/dal';
import {
GetWorkflowByIdsUseCase,
Expand All @@ -21,7 +21,7 @@ export class BuildStepDataUsecase {
) {}

@InstrumentUsecase()
async execute(command: BuildStepDataCommand): Promise<StepDataDto> {
async execute(command: BuildStepDataCommand): Promise<StepResponseDto> {
const workflow: WorkflowInternalResponseDto = await this.fetchWorkflow(command);

const { currentStep } = await this.loadStepsFromDb(command, workflow);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
GeneratePreviewResponseDto,
JobStatusEnum,
PreviewPayload,
StepDataDto,
StepResponseDto,
WorkflowOriginEnum,
TipTapNode,
StepTypeEnum,
Expand Down Expand Up @@ -136,7 +136,7 @@ export class GeneratePreviewUsecase {
}
}

private sanitizeControlsForPreview(initialControlValues: Record<string, unknown>, stepData: StepDataDto) {
private sanitizeControlsForPreview(initialControlValues: Record<string, unknown>, stepData: StepResponseDto) {
const sanitizedValues = dashboardSanitizeControlValues(this.logger, initialControlValues, stepData.type);

return sanitizeControlValuesByOutputSchema(sanitizedValues || {}, stepData.type);
Expand Down Expand Up @@ -224,7 +224,7 @@ export class GeneratePreviewUsecase {
@Instrument()
private async executePreviewUsecase(
command: GeneratePreviewCommand,
stepData: StepDataDto,
stepData: StepResponseDto,
hydratedPayload: PreviewPayload,
controlValues: Record<string, unknown>
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Injectable, InternalServerErrorException } from '@nestjs/common';

import { StepDataDto, UserSessionData, WorkflowResponseDto } from '@novu/shared';
import { StepResponseDto, UserSessionData, WorkflowResponseDto } from '@novu/shared';
import {
GetWorkflowByIdsCommand,
GetWorkflowByIdsUseCase,
Expand Down Expand Up @@ -40,7 +40,7 @@ export class GetWorkflowUseCase {
private async getFullWorkflowSteps(
workflow: WorkflowInternalResponseDto,
user: UserSessionData
): Promise<StepDataDto[]> {
): Promise<StepResponseDto[]> {
const stepPromises = workflow.steps.map((step: NotificationStepEntity & { _id: string }) =>
this.buildStepForWorkflow(workflow, step, user)
);
Expand All @@ -52,7 +52,7 @@ export class GetWorkflowUseCase {
workflow: WorkflowInternalResponseDto,
step: NotificationStepEntity & { _id: string },
user: UserSessionData
): Promise<StepDataDto> {
): Promise<StepResponseDto> {
try {
return await this.buildStepDataUsecase.execute(
BuildStepDataCommand.create({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable no-param-reassign */
import { BadRequestException, forwardRef, Inject, Injectable } from '@nestjs/common';
import { StepDataDto, UserSessionData } from '@novu/shared';
import { StepResponseDto, UserSessionData } from '@novu/shared';
import { NotificationStepEntity, NotificationTemplateEntity, NotificationTemplateRepository } from '@novu/dal';
import {
DeleteControlValuesCommand,
Expand All @@ -27,7 +27,7 @@ export class PatchStepUsecase {
private deleteControlValuesUseCase: DeleteControlValuesUseCase
) {}

async execute(command: PatchStepCommand): Promise<StepDataDto> {
async execute(command: PatchStepCommand): Promise<StepResponseDto> {
const persistedItems = await this.loadPersistedItems(command);
await this.patchFieldsOnPersistedItems(command, persistedItems);
await this.persistWorkflow(persistedItems.workflow, command.user);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { BadRequestException, Injectable } from '@nestjs/common';
import {
PreferencesTypeEnum,
StepCreateDto,
StepDataDto,
StepResponseDto,
StepUpdateDto,
WorkflowCreationSourceEnum,
WorkflowOriginEnum,
Expand Down Expand Up @@ -131,8 +131,8 @@ export class SyncToEnvironmentUseCase {

@Instrument()
private async mapStepsToCreateOrUpdateDto(
originSteps: StepDataDto[],
targetEnvSteps?: StepDataDto[]
originSteps: StepResponseDto[],
targetEnvSteps?: StepResponseDto[]
): Promise<(StepUpdateDto | StepCreateDto)[]> {
return originSteps.map((sourceStep) => {
// if we find matching step in target environment, we are updating
Expand All @@ -144,7 +144,10 @@ export class SyncToEnvironmentUseCase {
});
}

private buildStepCreateOrUpdateDto(step: StepDataDto, existingInternalId?: string): StepUpdateDto | StepCreateDto {
private buildStepCreateOrUpdateDto(
step: StepResponseDto,
existingInternalId?: string
): StepUpdateDto | StepCreateDto {
return {
...(existingInternalId && { _id: existingInternalId }),
name: step.name ?? '',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,7 @@ function mapAjvErrorToMessage(error: ErrorObject<string, Record<string, unknown>
error.message?.includes('mailto') &&
error.message?.includes('https')
) {
return 'Invalid URL format. Must be a valid absolute URL, path, or valid variable';
return `Invalid URL format. Must be a valid absolute URL, path starting with /, or {{variable}}`;
}

return error.message || 'Invalid value';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ const LIQUID_CONFIG = {
greedy: false,
catchAllErrors: true,
} as const;
const DOT_ANNOTATION_MARKDOWN_LINK =
'[dot notation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors#dot_notation)';

export type Variable = {
/**
Expand Down Expand Up @@ -162,7 +164,11 @@ function extractProps(template: any): { valid: boolean; props: string[]; error?:
* Invalid: {{user.first name}} - postfix length would be 2 due to space
*/
if (initial.postfix.length > 1) {
return { valid: false, props: [], error: 'Variables with spaces are not supported' };
return {
valid: false,
props: [],
error: `Invalid variable name containing whitespaces. Variables must follow the ${DOT_ANNOTATION_MARKDOWN_LINK}`,
};
}

const validProps: string[] = [];
Expand All @@ -184,7 +190,8 @@ function extractProps(template: any): { valid: boolean; props: string[]; error?:
return {
valid: false,
props: [],
error: `Variables must include a namespace (e.g. payload.${validProps[0]})`,
// eslint-disable-next-line max-len
error: `Invalid variable name missing namespace. Variables must follow the ${DOT_ANNOTATION_MARKDOWN_LINK} (e.g. payload.${validProps[0]})`,
};
}

Expand Down
8 changes: 4 additions & 4 deletions apps/api/src/app/workflows-v2/workflow.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
ListWorkflowResponse,
PatchStepDataDto,
PatchWorkflowDto,
StepDataDto,
StepResponseDto,
SyncWorkflowDto,
UpdateWorkflowDto,
UserSessionData,
Expand Down Expand Up @@ -113,7 +113,7 @@ export class WorkflowController {
): Promise<WorkflowResponseDto> {
return await this.upsertWorkflowUseCase.execute(
UpsertWorkflowCommand.create({
workflowDto: { ...updateWorkflowDto },
workflowDto: updateWorkflowDto,
user,
workflowIdOrInternalId,
})
Expand Down Expand Up @@ -196,7 +196,7 @@ export class WorkflowController {
@UserSession(ParseSlugEnvironmentIdPipe) user: UserSessionData,
@Param('workflowId', ParseSlugIdPipe) workflowIdOrInternalId: string,
@Param('stepId', ParseSlugIdPipe) stepIdOrInternalId: string
): Promise<StepDataDto> {
): Promise<StepResponseDto> {
return await this.buildStepDataUsecase.execute(
BuildStepDataCommand.create({ user, workflowIdOrInternalId, stepIdOrInternalId })
);
Expand All @@ -209,7 +209,7 @@ export class WorkflowController {
@Param('workflowId', ParseSlugIdPipe) workflowIdOrInternalId: string,
@Param('stepId', ParseSlugIdPipe) stepIdOrInternalId: string,
@Body() patchStepDataDto: PatchStepDataDto
): Promise<StepDataDto> {
): Promise<StepResponseDto> {
return await this.patchStepDataUsecase.execute(
PatchStepCommand.create({
user,
Expand Down
2 changes: 2 additions & 0 deletions apps/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"cmdk": "1.0.0",
"cron-parser": "^4.9.0",
"date-fns": "^4.1.0",
"dompurify": "^3.2.3",
"flat": "^6.0.1",
"js-cookie": "^3.0.5",
"launchdarkly-react-client-sdk": "^3.3.2",
Expand Down Expand Up @@ -112,6 +113,7 @@
"@types/node": "^22.7.0",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/dompurify": "^3.2.0",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.20",
"eslint": "^9.9.0",
Expand Down
13 changes: 10 additions & 3 deletions apps/dashboard/src/api/steps.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import type { StepDataDto, GeneratePreviewRequestDto, GeneratePreviewResponseDto, IEnvironment } from '@novu/shared';
import type {
StepResponseDto,
GeneratePreviewRequestDto,
GeneratePreviewResponseDto,
IEnvironment,
} from '@novu/shared';
import { getV2, postV2 } from './api.client';

export const getStep = async ({
Expand All @@ -9,8 +14,10 @@ export const getStep = async ({
environment: IEnvironment;
stepSlug: string;
workflowSlug: string;
}): Promise<StepDataDto> => {
const { data } = await getV2<{ data: StepDataDto }>(`/workflows/${workflowSlug}/steps/${stepSlug}`, { environment });
}): Promise<StepResponseDto> => {
const { data } = await getV2<{ data: StepResponseDto }>(`/workflows/${workflowSlug}/steps/${stepSlug}`, {
environment,
});

return data;
};
Expand Down
26 changes: 25 additions & 1 deletion apps/dashboard/src/components/primitives/form/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as React from 'react';
import * as LabelPrimitive from '@radix-ui/react-label';
import { Slot } from '@radix-ui/react-slot';
import { Controller, ControllerProps, FieldPath, FieldValues, FormProvider } from 'react-hook-form';
import DOMPurify from 'dompurify';

import { cn } from '@/utils/ui';
import { Label } from '@/components/primitives/label';
Expand Down Expand Up @@ -120,6 +121,27 @@ const FormMessagePure = React.forwardRef<
return null;
}

const parseMarkdownLinks = (text: string) => {
if (!text.includes('[') || !text.includes(']')) return text;

const markdownLinkRegex = /\[([^\]]+)\]\(([^)"']+)(?:\s+"[^"]*")?\)/g;

const htmlContent = text.replace(markdownLinkRegex, (_, text, url) => {
const isValidUrl = /^(https?:\/\/|\/)[^\s<>{}\\^~[\]`]+$/i.test(url);
if (!isValidUrl) return text;

return `<a href="${url}" class="underline text-primary hover:text-primary/80" target="_blank" rel="noopener noreferrer nofollow">${text}</a>`;
});

return DOMPurify.sanitize(htmlContent, {
ALLOWED_TAGS: ['a'],
ALLOWED_ATTR: ['href', 'target', 'rel', 'class'],
ALLOW_UNKNOWN_PROTOCOLS: false,
ALLOWED_URI_REGEXP: /^(https?:|\/)/i,
ADD_ATTR: ['target', 'rel'],
});
};

return (
<p
ref={ref}
Expand All @@ -128,7 +150,9 @@ const FormMessagePure = React.forwardRef<
{...props}
>
<span>{error ? <RiErrorWarningFill className="size-4" /> : <RiInformationFill className="size-4" />}</span>
<span className="mt-[1px] text-xs leading-4">{body}</span>
<span className="mt-[1px] text-xs leading-4">
{typeof body === 'string' ? <span dangerouslySetInnerHTML={{ __html: parseMarkdownLinks(body) }} /> : body}
</span>
</p>
);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { StepDataDto } from '@novu/shared';
import { StepResponseDto } from '@novu/shared';
import { buildDefaultValues, buildDefaultValuesOfDataSchema } from '@/utils/schema';

// Use the UI Schema to build the default values if it exists else use the data schema (code-first approach) values
export const getStepDefaultValues = (step: StepDataDto): Record<string, unknown> => {
export const getStepDefaultValues = (step: StepResponseDto): Record<string, unknown> => {
const controlValues = step.controls.values;
const hasControlValues = Object.keys(controlValues).length > 0;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
IEnvironment,
StepDataDto,
StepResponseDto,
StepTypeEnum,
StepUpdateDto,
UpdateWorkflowDto,
Expand Down Expand Up @@ -75,7 +75,7 @@ const STEP_TYPE_TO_PREVIEW: Record<StepTypeEnum, ((props: HTMLAttributes<HTMLDiv
type ConfigureStepFormProps = {
workflow: WorkflowResponseDto;
environment: IEnvironment;
step: StepDataDto;
step: StepResponseDto;
update: (data: UpdateWorkflowDto) => void;
};

Expand Down Expand Up @@ -108,7 +108,7 @@ export const ConfigureStepForm = (props: ConfigureStepFormProps) => {
};

const registerInlineControlValues = useMemo(() => {
return (step: StepDataDto) => {
return (step: StepResponseDto) => {
if (isInlineConfigurableStep) {
return {
controlValues: getStepDefaultValues(step),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useMemo } from 'react';
import { useForm } from 'react-hook-form';
import {
type StepDataDto,
type StepResponseDto,
StepTypeEnum,
StepUpdateDto,
UpdateWorkflowDto,
Expand Down Expand Up @@ -34,7 +34,7 @@ const STEP_TYPE_TO_TEMPLATE_FORM: Record<StepTypeEnum, (args: StepEditorProps) =

export type StepEditorProps = {
workflow: WorkflowResponseDto;
step: StepDataDto;
step: StepResponseDto;
};

type ConfigureStepTemplateFormProps = StepEditorProps & {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Button } from '@/components/primitives/button';
import { SidebarContent } from '@/components/side-navigation/sidebar';
import TruncatedText from '@/components/truncated-text';
import { StepDataDto } from '@novu/shared';
import { StepResponseDto } from '@novu/shared';
import { RiArrowRightUpLine } from 'react-icons/ri';
import { Link } from 'react-router-dom';

type ConfigureStepTemplateIssueCtaProps = {
step: StepDataDto;
step: StepResponseDto;
issue: string;
};
export const ConfigureStepTemplateIssueCta = (props: ConfigureStepTemplateIssueCtaProps) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PatchWorkflowDto, StepDataDto, UpdateWorkflowDto, WorkflowResponseDto } from '@novu/shared';
import { PatchWorkflowDto, StepResponseDto, UpdateWorkflowDto, WorkflowResponseDto } from '@novu/shared';
import { createContext, ReactNode, useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react';
import { useBlocker, useNavigate, useParams } from 'react-router-dom';

Expand Down Expand Up @@ -27,7 +27,7 @@ import { getWorkflowIdFromSlug } from '@/utils/step';
export type WorkflowContextType = {
isPending: boolean;
workflow?: WorkflowResponseDto;
step?: StepDataDto;
step?: StepResponseDto;
update: (data: UpdateWorkflowDto) => void;
patch: (data: PatchWorkflowDto) => void;
};
Expand Down
Loading

0 comments on commit 6667626

Please sign in to comment.