Skip to content
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
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,5 +105,5 @@
"tsconfig-paths": "^4.2.0",
"type-fest": "4.10.3"
},
"version": "1.29.7"
"version": "1.29.8"
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const action: IRawAction = {
required: true,
description: `The HTTP method we'll use to perform the request.`,
value: 'GET',
showOptionValue: false,
options: [
{ label: 'DELETE', value: 'DELETE' },
{ label: 'GET', value: 'GET' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const action: IRawAction = {
value: null,
description: 'Delay for unit, e.g. minutes, hours, days, weeks.',
variables: false,
showOptionValue: false,
options: [
{
label: 'Minutes',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const dataOutSchema = z.object(
'BLACKLISTED',
'RATE-LIMITED',
'INVALID-ATTACHMENT',
'ATTACHMENT-SIZE-EXCEEDED',
'INTERMITTENT-ERROR',
'ERROR',
]),
Expand Down
8 changes: 5 additions & 3 deletions packages/backend/src/apps/postman/common/email-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,14 +159,16 @@ export async function sendTransactionalEmails(
* Since we can only return one error per postman step, we have to select in terms of priority
* 1. RATE-LIMITED (so we can auto-retry)
* 2. INVALID-ATTACHMENT (probably all recipients should fail)
* 3. INTERMITTENT-ERROR (some recipients failed, auto-retry)
* 4. ERROR (probably all recipients should fail)
* 5. BLACKLISTED (blacklisted errors are returned even if there are other errors like invalid attachment)
* 3. ATTACHMENT-SIZE-EXCEEDED (probably all recipients should fail)
* 4. INTERMITTENT-ERROR (some recipients failed, auto-retry)
* 5. ERROR (probably all recipients should fail)
* 6. BLACKLISTED (blacklisted errors are returned even if there are other errors like invalid attachment)
*/
const sortedErrors = sortBy(errors, (error) =>
[
'RATE-LIMITED',
'INVALID-ATTACHMENT',
'ATTACHMENT-SIZE-EXCEEDED',
'INTERMITTENT-ERROR',
'ERROR',
'BLACKLISTED',
Expand Down
10 changes: 10 additions & 0 deletions packages/backend/src/apps/postman/common/throw-errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export function getPostmanErrorStatus(
}
case 'rate_limit':
return 'RATE-LIMITED'
case 'attachment_limit':
return 'ATTACHMENT-SIZE-EXCEEDED'
default:
if (POSTMAN_RETRIABLE_HTTP_CODES.includes(error.response?.status)) {
return 'INTERMITTENT-ERROR'
Expand Down Expand Up @@ -105,6 +107,14 @@ export function throwPostmanStepError({
appName,
error,
)
case 'ATTACHMENT-SIZE-EXCEEDED':
throw new StepError(
'Total attachment size exceeded',
'Click on set up action and check that the attachments do not exceed 10MB in total.',
position,
appName,
error,
)
case 'INTERMITTENT-ERROR':
throw new RetriableError({
error: error.details,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const trigger: IRawTrigger = {
required: true,
value: null,
variables: false,
showOptionValue: false,
options: [
{
label: '00:00',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const trigger: IRawTrigger = {
required: true,
value: null,
variables: false,
showOptionValue: false,
options: [
{
label: '1',
Expand Down Expand Up @@ -156,6 +157,7 @@ const trigger: IRawTrigger = {
required: true,
value: null,
variables: false,
showOptionValue: false,
options: [
{
label: '00:00',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const trigger: IRawTrigger = {
required: true,
value: null,
variables: false,
showOptionValue: false,
options: [
{
label: 'Monday',
Expand Down Expand Up @@ -60,6 +61,7 @@ const trigger: IRawTrigger = {
required: true,
value: null,
variables: false,
showOptionValue: false,
options: [
{
label: '00:00',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const action: IRawAction = {
required: true,
value: 'score',
variables: false,
showOptionValue: false,
options: [
{
label: 'Match strength',
Expand All @@ -45,6 +46,7 @@ const action: IRawAction = {
required: true,
value: 'desc',
variables: false,
showOptionValue: false,
options: [
{
label: 'Descending (newest or best match first)',
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "frontend",
"version": "1.29.7",
"version": "1.29.8",
"scripts": {
"dev": "wait-on tcp:3000 && vite --host --force",
"build": "tsc && vite build --mode=${VITE_MODE:-prod}",
Expand Down
68 changes: 45 additions & 23 deletions packages/frontend/src/components/AppConnectionRow/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { IConnection } from '@plumber/types'
import * as React from 'react'
import { useCallback, useRef, useState } from 'react'
import { useLazyQuery, useMutation } from '@apollo/client'
import { Card } from '@chakra-ui/react'
import { Card, useDisclosure } from '@chakra-ui/react'
import CheckCircleIcon from '@mui/icons-material/CheckCircle'
import ErrorIcon from '@mui/icons-material/Error'
import MoreHorizIcon from '@mui/icons-material/MoreHoriz'
Expand All @@ -15,6 +15,7 @@ import { useToast } from '@opengovsg/design-system-react'
import { DateTime } from 'luxon'

import ConnectionContextMenu from '@/components/AppConnectionContextMenu'
import MenuAlertDialog from '@/components/MenuAlertDialog'
import { DELETE_CONNECTION } from '@/graphql/mutations/delete-connection'
import { TEST_CONNECTION } from '@/graphql/queries/test-connection'
import useFormatMessage from '@/hooks/useFormatMessage'
Expand Down Expand Up @@ -46,46 +47,58 @@ function AppConnectionRow(props: AppConnectionRowProps): React.ReactElement {
setTimeout(() => setVerificationVisible(false), 3000)
},
})
const [deleteConnection] = useMutation(DELETE_CONNECTION)
const [deleteConnection, { loading: isDeletingConnection }] =
useMutation(DELETE_CONNECTION)

const formatMessage = useFormatMessage()
const { id, key, formattedData, createdAt, flowCount } = props.connection

const contextButtonRef = useRef<SVGSVGElement | null>(null)
const [anchorEl, setAnchorEl] = useState<SVGSVGElement | null>(null)
const cancelRef = useRef<HTMLButtonElement>(null)
const {
isOpen: isDialogOpen,
onOpen: onDialogOpen,
onClose: onDialogClose,
} = useDisclosure()

const handleClose = () => {
setAnchorEl(null)
}

const onContextMenuClick = () => setAnchorEl(contextButtonRef.current)
const onContextMenuAction = useCallback(
async (
_event: React.MouseEvent<Element, MouseEvent>,
action: { [key: string]: string },
) => {
if (action.type === 'delete') {
await deleteConnection({
variables: { input: { id } },
update: (cache) => {
const connectionCacheId = cache.identify({
__typename: 'Connection',
id,
})

cache.evict({
id: connectionCacheId,
})
},
const onConnectionDelete = useCallback(async () => {
await deleteConnection({
variables: { input: { id } },
update: (cache) => {
const connectionCacheId = cache.identify({
__typename: 'Connection',
id,
})

cache.evict({
id: connectionCacheId,
})
},
onCompleted: () => {
onDialogClose()
toast({
title: 'The connection has been deleted.',
status: 'success',
duration: 3000,
isClosable: true,
position: 'bottom-right',
})
},
})
}, [deleteConnection, id, toast, onDialogClose])

const onContextMenuClick = () => setAnchorEl(contextButtonRef.current)
const onContextMenuAction = useCallback(
async (
_event: React.MouseEvent<Element, MouseEvent>,
action: { [key: string]: string },
) => {
if (action.type === 'delete') {
onDialogOpen()
} else if (action.type === 'test') {
setVerificationVisible(true)
const testResults = await testConnection({
Expand All @@ -101,7 +114,7 @@ function AppConnectionRow(props: AppConnectionRowProps): React.ReactElement {
}
}
},
[deleteConnection, id, testConnection, toast],
[id, onDialogOpen, testConnection],
)

const relativeCreatedAt = DateTime.fromMillis(
Expand Down Expand Up @@ -195,6 +208,15 @@ function AppConnectionRow(props: AppConnectionRowProps): React.ReactElement {
anchorEl={anchorEl}
/>
)}
<MenuAlertDialog
isDialogOpen={isDialogOpen}
cancelRef={cancelRef}
onDialogClose={onDialogClose}
dialogHeader="Connection"
dialogType="delete"
onClick={onConnectionDelete}
isLoading={isDeletingConnection}
/>
</>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,11 @@ import {
useToast,
} from '@opengovsg/design-system-react'

import MenuAlertDialog, { AlertDialogType } from '@/components/MenuAlertDialog'
import * as URLS from '@/config/urls'
import { DELETE_FLOW } from '@/graphql/mutations/delete-flow'
import { DUPLICATE_FLOW } from '@/graphql/mutations/duplicate-flow'

import MenuAlertDialog, { AlertDialogType } from './MenuAlertDialog'

interface FlowContextMenuProps {
flow: IFlow
}
Expand Down Expand Up @@ -184,7 +183,8 @@ export default function FlowContextMenu(props: FlowContextMenuProps) {
isDialogOpen={isDialogOpen}
cancelRef={cancelRef}
onDialogClose={onDialogClose}
type={dialogType}
dialogHeader="Pipe"
dialogType={dialogType}
onClick={dialogType === 'delete' ? onFlowDelete : onFlowDuplicate}
isLoading={dialogType === 'delete' ? isDeletingFlow : isDuplicatingFlow}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import {
import { Button } from '@opengovsg/design-system-react'

export type AlertDialogType = 'delete' | 'duplicate'
export type AlertHeaderType = 'Connection' | 'Pipe' | 'Tile'

interface MenuAlertDialogProps {
isDialogOpen: boolean
cancelRef: React.RefObject<HTMLButtonElement>
onDialogClose: () => void
type: AlertDialogType
dialogType: AlertDialogType
dialogHeader: AlertHeaderType
onClick: () => void
isLoading: boolean
}
Expand All @@ -25,12 +27,15 @@ interface AlertDialogContent {
buttonText: string
}

function getAlertDialogContent(type: AlertDialogType): AlertDialogContent {
switch (type) {
function getAlertDialogContent(
dialogHeader: AlertHeaderType,
dialogType: AlertDialogType,
): AlertDialogContent {
switch (dialogType) {
case 'delete':
return {
header: 'Delete Pipe',
body: "Are you sure you want to delete this pipe? You can't undo this action afterwards.",
header: `Delete ${dialogHeader}`,
body: `Are you sure you want to delete this ${dialogHeader?.toLowerCase()}? You can't undo this action afterwards.`,
buttonText: 'Delete',
}
case 'duplicate':
Expand All @@ -43,9 +48,19 @@ function getAlertDialogContent(type: AlertDialogType): AlertDialogContent {
}

export default function MenuAlertDialog(props: MenuAlertDialogProps) {
const { isDialogOpen, cancelRef, onDialogClose, type, onClick, isLoading } =
props
const { header, body, buttonText } = getAlertDialogContent(type)
const {
isDialogOpen,
cancelRef,
onDialogClose,
dialogHeader,
dialogType,
onClick,
isLoading,
} = props
const { header, body, buttonText } = getAlertDialogContent(
dialogHeader,
dialogType,
)

return (
<AlertDialog
Expand Down
Loading
Loading