Skip to content
Closed
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
1 change: 1 addition & 0 deletions packages/backend/src/apps/postman/common/parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const transactionalEmailFields: IField[] = [
'table',
],
supportTableDisplay: true,
previewType: 'email' as const,
},
{
label: 'Recipient email(s)',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { IBaseTrigger, IStep, ITriggerInstructions } from '@plumber/types'
import {
IBaseTrigger,
IStep,
ITriggerInstructions,
TFieldPreviewType,
} from '@plumber/types'

import { useContext, useEffect, useMemo, useRef, useState } from 'react'
import { useFormContext } from 'react-hook-form'
Expand All @@ -10,6 +15,7 @@ import {
Icon,
Text,
Tooltip,
useDisclosure,
VStack,
} from '@chakra-ui/react'
import {
Expand All @@ -25,7 +31,9 @@ import { validateStepParams } from '@/helpers/validateStepParams'
import { useStepMetadata } from '@/hooks/useStepMetadata'

import { EDITOR_MARGIN_TOP_NUM } from '../Editor/constants'
import EmailPreviewModal from '../EmailPreviewModal'
import ErrorResult from '../ErrorResult'
import { substituteForPreview } from '../RichTextEditor/utils'
import WebhookUrlInfo from '../WebhookUrlInfo'

import { CheckAgainButton } from './CheckAgainButton'
Expand All @@ -38,6 +46,10 @@ import {
matchParamsToDataIn,
} from './utils'

const PREVIEW_BUTTON_COPY: Record<TFieldPreviewType, string> = {
email: 'Preview email',
}

const defaultTriggerInstructions: ITriggerInstructions = {
beforeUrlMsg: `# 1. You'll need to configure your application with this webhook URL.`,
afterUrlMsg: `# 2. Send some data to the webhook URL after configuration. Then, click check step.`,
Expand Down Expand Up @@ -122,7 +134,45 @@ export default function FlowStepTestController(
lastErrorDetails,
isWebhookSubstep,
testVariables,
} = useTestDetails(step, currentTestExecutionStep, allApps)
previewAction,
} = useTestDetails(step, currentTestExecutionStep, allApps, substeps)

const {
isOpen: isPreviewModalOpen,
onOpen: onPreviewModalOpen,
onClose: onPreviewModalClose,
} = useDisclosure()

const previewModal = useMemo(() => {
if (!previewAction) {
return null
}
switch (previewAction.kind) {
case 'email': {
const previewHtml = substituteForPreview(previewAction.html, varInfoMap)
return (
<EmailPreviewModal
isOpen={isPreviewModalOpen}
onClose={onPreviewModalClose}
html={previewHtml}
/>
)
}
}
}, [previewAction, varInfoMap, isPreviewModalOpen, onPreviewModalClose])

const previewButton = previewAction ? (
<Button
variant="outline"
colorScheme="black"
size="sm"
onClick={onPreviewModalOpen}
mr={2}
data-test={`preview-${previewAction.kind}-button`}
>
{PREVIEW_BUTTON_COPY[previewAction.kind]}
</Button>
) : null
const containerRef = useRef<HTMLDivElement>(null)
const webhookUrlInfoRef = useRef<HTMLDivElement>(null)
const [collapseDirection, setCollapseDirection] = useState<'up' | 'down'>(
Expand Down Expand Up @@ -337,6 +387,7 @@ export default function FlowStepTestController(
{!isDirty ? 'Saved' : 'Save without checking'}
</Button>
)}
{previewButton}
<CheckAgainButton
isUnstyledInfobox={isStepUnchecked}
onClick={handleSaveAndTest}
Expand Down Expand Up @@ -379,6 +430,7 @@ export default function FlowStepTestController(
{isDirty ? 'Save' : 'Saved'}
</Button>
)}
{previewButton}
<CheckStepTooltip
hasDeletedVars={hasDeletedVars}
isDisabled={shouldAllowCheckStep}
Expand All @@ -397,6 +449,7 @@ export default function FlowStepTestController(
</VStack>
)}
</VStack>
{previewModal}
</>
)
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,77 @@
import { IApp, IExecutionStep, IJSONObject, IStep } from '@plumber/types'
import {
IApp,
IExecutionStep,
IFieldRichText,
IJSONObject,
IStep,
ISubstep,
TFieldPreviewType,
} from '@plumber/types'

import { useMemo } from 'react'
import { useWatch } from 'react-hook-form'

import { extractVariables, Variable } from '@/helpers/variables'

import { isSameAppAndAppKey } from './utils'

export interface PreviewAction {
kind: TFieldPreviewType
fieldKey: string
html: string
}

interface UseTestDetailsResult {
isTestSuccessful: boolean
isWebhookSubstep: boolean
lastErrorDetails?: IJSONObject | null
testVariables: Variable[] | null
previewAction: PreviewAction | null
}

// Stable dummy field name used when no previewable arg exists in the step, so
// useWatch is still called unconditionally and the rules-of-hooks are
// respected.
const PREVIEW_WATCH_STUB = '__previewActionStub__'

export function useTestDetails(
step: IStep,
currentTestExecutionStep: IExecutionStep | null,
allApps: IApp[],
substeps: ISubstep[],
): UseTestDetailsResult {
const previewableArg = useMemo<{
key: string
previewType: TFieldPreviewType
} | null>(() => {
for (const substep of substeps ?? []) {
for (const arg of substep.arguments ?? []) {
if (arg.type === 'rich-text') {
const rt = arg as IFieldRichText
if (rt.previewType) {
return { key: rt.key, previewType: rt.previewType }
}
}
}
}
return null
}, [substeps])

const liveValue = useWatch({
name: previewableArg
? `parameters.${previewableArg.key}`
: PREVIEW_WATCH_STUB,
}) as unknown

const previewAction: PreviewAction | null =
previewableArg && typeof liveValue === 'string' && liveValue.length > 0
? {
kind: previewableArg.previewType,
fieldKey: previewableArg.key,
html: liveValue,
}
: null

const isWebhookSubstep =
(step.appKey === 'webhook' || step.appKey === 'gathersg') &&
Boolean(step?.webhookUrl)
Expand All @@ -26,6 +82,7 @@ export function useTestDetails(
isWebhookSubstep,
lastErrorDetails: null,
testVariables: null,
previewAction,
}
}

Expand All @@ -45,5 +102,6 @@ export function useTestDetails(
isWebhookSubstep,
lastErrorDetails,
testVariables,
previewAction,
}
}
Loading