diff --git a/package-lock.json b/package-lock.json index b98c20f91f..c668959c04 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23451,7 +23451,7 @@ } }, "packages/backend": { - "version": "1.29.7", + "version": "1.29.8", "dependencies": { "@apollo/server": "4.9.4", "@aws-sdk/client-dynamodb": "3.460.0", @@ -23551,7 +23551,7 @@ } }, "packages/frontend": { - "version": "1.29.7", + "version": "1.29.8", "hasInstallScript": true, "dependencies": { "@datadog/browser-rum": "^5.2.0", @@ -23762,7 +23762,7 @@ }, "packages/types": { "name": "@plumber/types", - "version": "1.29.7" + "version": "1.29.8" } } } diff --git a/packages/backend/package.json b/packages/backend/package.json index 52d70331c3..35f84b5d49 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -105,5 +105,5 @@ "tsconfig-paths": "^4.2.0", "type-fest": "4.10.3" }, - "version": "1.29.7" + "version": "1.29.8" } diff --git a/packages/backend/src/apps/custom-api/actions/http-request/index.ts b/packages/backend/src/apps/custom-api/actions/http-request/index.ts index 4c23bb117c..c3b957e80c 100644 --- a/packages/backend/src/apps/custom-api/actions/http-request/index.ts +++ b/packages/backend/src/apps/custom-api/actions/http-request/index.ts @@ -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' }, diff --git a/packages/backend/src/apps/delay/actions/delay-for/index.ts b/packages/backend/src/apps/delay/actions/delay-for/index.ts index 8ad6140d94..9d026fd9b1 100644 --- a/packages/backend/src/apps/delay/actions/delay-for/index.ts +++ b/packages/backend/src/apps/delay/actions/delay-for/index.ts @@ -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', diff --git a/packages/backend/src/apps/postman/common/data-out-validator.ts b/packages/backend/src/apps/postman/common/data-out-validator.ts index 2439f74cc1..d6a0de6d67 100644 --- a/packages/backend/src/apps/postman/common/data-out-validator.ts +++ b/packages/backend/src/apps/postman/common/data-out-validator.ts @@ -8,6 +8,7 @@ export const dataOutSchema = z.object( 'BLACKLISTED', 'RATE-LIMITED', 'INVALID-ATTACHMENT', + 'ATTACHMENT-SIZE-EXCEEDED', 'INTERMITTENT-ERROR', 'ERROR', ]), diff --git a/packages/backend/src/apps/postman/common/email-helper.ts b/packages/backend/src/apps/postman/common/email-helper.ts index 905bfd93b7..b13462f6aa 100644 --- a/packages/backend/src/apps/postman/common/email-helper.ts +++ b/packages/backend/src/apps/postman/common/email-helper.ts @@ -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', diff --git a/packages/backend/src/apps/postman/common/throw-errors.ts b/packages/backend/src/apps/postman/common/throw-errors.ts index 02bec0ec87..8edd3798e7 100644 --- a/packages/backend/src/apps/postman/common/throw-errors.ts +++ b/packages/backend/src/apps/postman/common/throw-errors.ts @@ -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' @@ -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, diff --git a/packages/backend/src/apps/scheduler/triggers/every-day/index.ts b/packages/backend/src/apps/scheduler/triggers/every-day/index.ts index 86a5e79a17..354ef7a022 100644 --- a/packages/backend/src/apps/scheduler/triggers/every-day/index.ts +++ b/packages/backend/src/apps/scheduler/triggers/every-day/index.ts @@ -38,6 +38,7 @@ const trigger: IRawTrigger = { required: true, value: null, variables: false, + showOptionValue: false, options: [ { label: '00:00', diff --git a/packages/backend/src/apps/scheduler/triggers/every-month/index.ts b/packages/backend/src/apps/scheduler/triggers/every-month/index.ts index deebcbdf56..7da53c3942 100644 --- a/packages/backend/src/apps/scheduler/triggers/every-month/index.ts +++ b/packages/backend/src/apps/scheduler/triggers/every-month/index.ts @@ -21,6 +21,7 @@ const trigger: IRawTrigger = { required: true, value: null, variables: false, + showOptionValue: false, options: [ { label: '1', @@ -156,6 +157,7 @@ const trigger: IRawTrigger = { required: true, value: null, variables: false, + showOptionValue: false, options: [ { label: '00:00', diff --git a/packages/backend/src/apps/scheduler/triggers/every-week/index.ts b/packages/backend/src/apps/scheduler/triggers/every-week/index.ts index d82b1ad3e8..81e1c7466c 100644 --- a/packages/backend/src/apps/scheduler/triggers/every-week/index.ts +++ b/packages/backend/src/apps/scheduler/triggers/every-week/index.ts @@ -21,6 +21,7 @@ const trigger: IRawTrigger = { required: true, value: null, variables: false, + showOptionValue: false, options: [ { label: 'Monday', @@ -60,6 +61,7 @@ const trigger: IRawTrigger = { required: true, value: null, variables: false, + showOptionValue: false, options: [ { label: '00:00', diff --git a/packages/backend/src/apps/slack/actions/find-message/index.ts b/packages/backend/src/apps/slack/actions/find-message/index.ts index 051410f3d3..ffc82a902b 100644 --- a/packages/backend/src/apps/slack/actions/find-message/index.ts +++ b/packages/backend/src/apps/slack/actions/find-message/index.ts @@ -25,6 +25,7 @@ const action: IRawAction = { required: true, value: 'score', variables: false, + showOptionValue: false, options: [ { label: 'Match strength', @@ -45,6 +46,7 @@ const action: IRawAction = { required: true, value: 'desc', variables: false, + showOptionValue: false, options: [ { label: 'Descending (newest or best match first)', diff --git a/packages/frontend/package.json b/packages/frontend/package.json index f674cd7d8a..8b08e869df 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -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}", diff --git a/packages/frontend/src/components/AppConnectionRow/index.tsx b/packages/frontend/src/components/AppConnectionRow/index.tsx index e4d8ac7774..b0fbd05202 100644 --- a/packages/frontend/src/components/AppConnectionRow/index.tsx +++ b/packages/frontend/src/components/AppConnectionRow/index.tsx @@ -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' @@ -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' @@ -46,39 +47,39 @@ 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(null) const [anchorEl, setAnchorEl] = useState(null) + const cancelRef = useRef(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, - 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', @@ -86,6 +87,18 @@ function AppConnectionRow(props: AppConnectionRowProps): React.ReactElement { isClosable: true, position: 'bottom-right', }) + }, + }) + }, [deleteConnection, id, toast, onDialogClose]) + + const onContextMenuClick = () => setAnchorEl(contextButtonRef.current) + const onContextMenuAction = useCallback( + async ( + _event: React.MouseEvent, + action: { [key: string]: string }, + ) => { + if (action.type === 'delete') { + onDialogOpen() } else if (action.type === 'test') { setVerificationVisible(true) const testResults = await testConnection({ @@ -101,7 +114,7 @@ function AppConnectionRow(props: AppConnectionRowProps): React.ReactElement { } } }, - [deleteConnection, id, testConnection, toast], + [id, onDialogOpen, testConnection], ) const relativeCreatedAt = DateTime.fromMillis( @@ -195,6 +208,15 @@ function AppConnectionRow(props: AppConnectionRowProps): React.ReactElement { anchorEl={anchorEl} /> )} + ) } diff --git a/packages/frontend/src/components/FlowRow/FlowContextMenu.tsx b/packages/frontend/src/components/FlowRow/FlowContextMenu.tsx index a4118ee07e..2640b16919 100644 --- a/packages/frontend/src/components/FlowRow/FlowContextMenu.tsx +++ b/packages/frontend/src/components/FlowRow/FlowContextMenu.tsx @@ -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 } @@ -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} /> diff --git a/packages/frontend/src/components/FlowRow/MenuAlertDialog.tsx b/packages/frontend/src/components/MenuAlertDialog/index.tsx similarity index 72% rename from packages/frontend/src/components/FlowRow/MenuAlertDialog.tsx rename to packages/frontend/src/components/MenuAlertDialog/index.tsx index fa3d623d13..facedb9f94 100644 --- a/packages/frontend/src/components/FlowRow/MenuAlertDialog.tsx +++ b/packages/frontend/src/components/MenuAlertDialog/index.tsx @@ -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 onDialogClose: () => void - type: AlertDialogType + dialogType: AlertDialogType + dialogHeader: AlertHeaderType onClick: () => void isLoading: boolean } @@ -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': @@ -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 ( { const navigate = useNavigate() + const toast = useToast() const [deleteTable, { loading: isDeletingTable }] = useMutation( DELETE_TABLE, { @@ -73,7 +69,14 @@ const TileListItem = ({ table }: { table: TableMetadata }): JSX.Element => { const deleteTile = useCallback(async () => { await deleteTable() onDialogClose() - }, [deleteTable, onDialogClose]) + toast({ + title: 'The tile has been deleted.', + status: 'success', + duration: 3000, + isClosable: true, + position: 'top', + }) + }, [deleteTable, onDialogClose, toast]) return ( @@ -145,40 +148,15 @@ const TileListItem = ({ table }: { table: TableMetadata }): JSX.Element => { - - - - Delete Tile - - - {"Are you sure? You can't undo this action afterwards."} - - - - - - - - - + ) } diff --git a/packages/types/package.json b/packages/types/package.json index f20054abdb..acfc8e2785 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -2,5 +2,5 @@ "name": "@plumber/types", "description": "Shared types for plumber", "types": "./index.d.ts", - "version": "1.29.7" + "version": "1.29.8" }