Skip to content
Open
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
19 changes: 19 additions & 0 deletions packages/frontend/src/app-extensions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { FrontEndAppExtension } from './types'

// Nothing for now
const APP_EXTENSIONS: Record<string, FrontEndAppExtension> = {}

export function getExtension(
appKey?: string,
stepKey?: string,
): FrontEndAppExtension | null {
if (!appKey || !stepKey) {
return null
}
return APP_EXTENSIONS[`${appKey}-${stepKey}`] ?? null
}

export type {
CheckStepButtonExtensionProps,
FrontEndAppExtension,
} from './types'
22 changes: 22 additions & 0 deletions packages/frontend/src/app-extensions/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { IStep } from '@plumber/types'

import type { ComponentType, ReactNode } from 'react'

export interface CheckStepButtonExtensionProps {
step: IStep
/** The Check Step / Check Step Again button itself. */
children: ReactNode
}

export interface FrontEndAppExtension {
/**
* Wraps the Check Step / Check Step Again button.
*
* Mounted ONLY WHEN the button is enabled.
* TBD: Allow extending when button is disabled (for showing custom failure
* tooltip etc)
**/
CheckStepButton?: ComponentType<CheckStepButtonExtensionProps>

// Room to grow: ResultPanelWrapper, SubstepWrapper, etc.
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IExecutionStepMetadata, IStep } from '@plumber/types'

import { useCallback, useMemo } from 'react'
import { forwardRef, useCallback, useMemo } from 'react'
import { IconType } from 'react-icons'
import { BiChevronDown } from 'react-icons/bi'
import { PiRobot, PiUser } from 'react-icons/pi'
Expand Down Expand Up @@ -30,21 +30,25 @@ interface CheckAgainButtonProps {
executionStepMetadata?: IExecutionStepMetadata
}

export function CheckAgainButton(props: CheckAgainButtonProps) {
export const CheckAgainButton = forwardRef<
HTMLButtonElement,
CheckAgainButtonProps
>((props, ref) => {
const { isUnstyledInfobox, onClick, isLoading, isDisabled, step } = props
const isFormSgTrigger =
step.appKey === FORMSG_APP_KEY && step.key === FORMSG_TRIGGER_KEY
const isFormSgAction =
step.appKey === FORMSG_APP_KEY && step.key === MRF_ACTION_KEY

if (isFormSgTrigger) {
return <FormSGCheckAgainButton {...props} />
return <FormSGCheckAgainButton ref={ref} {...props} />

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will migrate this to extension approach in a later PR

}
if (isFormSgAction) {
return <FormSGCheckAgainButton {...props} />
return <FormSGCheckAgainButton ref={ref} {...props} />
}
return (
<Button
ref={ref}
variant={isUnstyledInfobox ? 'solid' : 'outline'}
onClick={() => onClick()}
isLoading={isLoading}
Expand All @@ -56,7 +60,7 @@ export function CheckAgainButton(props: CheckAgainButtonProps) {
Check step again
</Button>
)
}
})

/**
* For UX reasons, we need to have a different button for FormSG.
Expand Down Expand Up @@ -104,7 +108,10 @@ function FormSGMenuItem({
)
}

function FormSGCheckAgainButton(props: CheckAgainButtonProps) {
const FormSGCheckAgainButton = forwardRef<
HTMLButtonElement,
CheckAgainButtonProps
>((props, ref) => {
const {
isUnstyledInfobox: isTransparentInfobox,
onClick,
Expand Down Expand Up @@ -155,6 +162,7 @@ function FormSGCheckAgainButton(props: CheckAgainButtonProps) {
colorScheme={isTransparentInfobox ? 'primary' : 'black'}
>
<Button
ref={ref}
onClick={onTestClick}
isLoading={isLoading}
gap={2}
Expand Down Expand Up @@ -190,4 +198,4 @@ function FormSGCheckAgainButton(props: CheckAgainButtonProps) {
)}
</ButtonGroup>
)
}
})
107 changes: 66 additions & 41 deletions packages/frontend/src/components/FlowStepTestController/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { IBaseTrigger, IStep, ITriggerInstructions } from '@plumber/types'

import { useContext, useEffect, useMemo, useRef, useState } from 'react'
import {
forwardRef,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from 'react'
import { useFormContext } from 'react-hook-form'
import { BiChevronDown, BiChevronUp } from 'react-icons/bi'
import {
Expand All @@ -20,6 +27,7 @@ import {
Infobox,
} from '@opengovsg/design-system-react'

import { CheckStepButtonExtensionProps, getExtension } from '@/app-extensions'
import { EditorContext } from '@/contexts/Editor'
import { validateStepParams } from '@/helpers/validateStepParams'
import { useStepMetadata } from '@/hooks/useStepMetadata'
Expand Down Expand Up @@ -63,25 +71,32 @@ type CheckStepTooltipProps = {
children: React.ReactNode
}

const CheckStepTooltip = (props: CheckStepTooltipProps) => {
const { children, hasDeletedVars, isDisabled, isReadOnly } = props
return (
<Tooltip
label={
isReadOnly
? 'Unpublish your pipe to check step'
: hasDeletedVars
? 'Remove variables from deleted steps to check step'
: 'Complete required fields to check step'
}
aria-label="check step tooltip"
isDisabled={isDisabled}
hasArrow
>
{children}
</Tooltip>
)
}
const CheckStepTooltip = forwardRef<HTMLDivElement, CheckStepTooltipProps>(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the forwardRef is needed here because eventually I'll wrap this in another tooltip, which requires its child to support refs

(props, ref) => {
const { children, hasDeletedVars, isDisabled, isReadOnly } = props
return (
<Tooltip
ref={ref}
label={
isReadOnly
? 'Unpublish your pipe to check step'
: hasDeletedVars
? 'Remove variables from deleted steps to check step'
: 'Complete required fields to check step'
}
aria-label="check step tooltip"
isDisabled={isDisabled}
hasArrow
>
{children}
</Tooltip>
)
},
)

const NoOpCheckStepButtonExtension = ({
children,
}: CheckStepButtonExtensionProps) => children

export default function FlowStepTestController(
props: FlowStepTestControllerProps,
Expand Down Expand Up @@ -248,6 +263,12 @@ export default function FlowStepTestController(
return collapseDirection === 'up' ? <BiChevronUp /> : <BiChevronDown />
}

const appExtension = getExtension(step.appKey, step.key)
const CheckStepButtonExtension =
shouldAllowCheckStep && appExtension?.CheckStepButton != null
? appExtension.CheckStepButton
: NoOpCheckStepButtonExtension

return (
<>
{isWebhookSubstep && (
Expand Down Expand Up @@ -337,14 +358,16 @@ export default function FlowStepTestController(
{!isDirty ? 'Saved' : 'Save without checking'}
</Button>
)}
<CheckAgainButton
isUnstyledInfobox={isStepUnchecked}
onClick={handleSaveAndTest}
isLoading={isTestExecuting}
isDisabled={!isValid || readOnly}
step={step}
executionStepMetadata={currentTestExecutionStep?.metadata}
/>
<CheckStepButtonExtension step={step}>
<CheckAgainButton
isUnstyledInfobox={isStepUnchecked}
onClick={handleSaveAndTest}
isLoading={isTestExecuting}
isDisabled={!isValid || readOnly}
step={step}
executionStepMetadata={currentTestExecutionStep?.metadata}
/>
</CheckStepButtonExtension>
</Flex>
</Flex>
</Infobox>
Expand Down Expand Up @@ -379,20 +402,22 @@ export default function FlowStepTestController(
{isDirty ? 'Save' : 'Saved'}
</Button>
)}
<CheckStepTooltip
hasDeletedVars={hasDeletedVars}
isDisabled={shouldAllowCheckStep}
isReadOnly={readOnly}
>
<Button
onClick={() => handleSaveAndTest()}
data-test="flow-substep-continue-button"
isDisabled={!shouldAllowCheckStep}
isLoading={isTestExecuting}
<CheckStepButtonExtension step={step}>
<CheckStepTooltip
hasDeletedVars={hasDeletedVars}
isDisabled={shouldAllowCheckStep}
isReadOnly={readOnly}
>
Check step
</Button>
</CheckStepTooltip>
<Button
onClick={() => handleSaveAndTest()}
data-test="flow-substep-continue-button"
isDisabled={!shouldAllowCheckStep}
isLoading={isTestExecuting}
>
Check step
</Button>
</CheckStepTooltip>
</CheckStepButtonExtension>
</HStack>
</VStack>
)}
Expand Down
Loading