Skip to content

Commit 4e97f26

Browse files
authored
PLU-466: fix: prevent input edit on published pipes (#956)
## Problem Users can modify the frontend input for published pipes but it does not affect the backend ## Solution - Add readonly attribute to all inputs ## Tests Check that inputs can be edited when unpublished and cannot be edited when published - [ ] BooleanRadio type: telegram - [ ] DragDropInput type: formsg connection - [ ] Dropdown: tiles - [ ] RichTextEditor: postman email - [ ] TextField: `readOnly: true` is for slack auth only, everything else e.g. aisay prompt is false - [ ] Attachment: postman email attachment - [ ] MultiSelect: old postman email attachment, not used anywhere now (manually change to test) - [ ] MultiRow: aisay, tiles - [ ] MultiRow-MultiCol: custom-api headers
1 parent 195514b commit 4e97f26

File tree

10 files changed

+59
-32
lines changed

10 files changed

+59
-32
lines changed

packages/frontend/src/components/AttachmentSuggestions/components/Suggestions.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useMemo } from 'react'
1+
import { useContext, useMemo } from 'react'
22
import {
33
Box,
44
Collapse,
@@ -11,6 +11,7 @@ import {
1111
import FileUpload from '@/components/FileUpload'
1212
import PrimarySpinner from '@/components/PrimarySpinner'
1313
import SuggestionsWrapper from '@/components/SuggestionsWrapper'
14+
import { EditorContext } from '@/contexts/Editor'
1415
import { StepWithVariables, Variable } from '@/helpers/variables'
1516
import { POPOVER_MOTION_PROPS } from '@/theme/constants'
1617

@@ -53,6 +54,8 @@ export default function Suggestions(props: SuggestionsProps) {
5354
setCurrentTab,
5455
} = props
5556

57+
const { readOnly } = useContext(EditorContext)
58+
5659
const SuggestionsRightPanel = ({ values }: { values: any }) => {
5760
if (suggestions.length === 0) {
5861
return <Text style={noVariablesTextStyles}>No variables available</Text>
@@ -73,7 +76,7 @@ export default function Suggestions(props: SuggestionsProps) {
7376
<FileUpload
7477
accept={ACCEPTED_FILE_TYPES.join(',')}
7578
buttonType="textButton"
76-
disabled={isUploading}
79+
disabled={isUploading || readOnly}
7780
loading={isUploading}
7881
processFile={processFile}
7982
/>
@@ -88,7 +91,7 @@ export default function Suggestions(props: SuggestionsProps) {
8891
...variable,
8992
source: option.name,
9093
}}
91-
allowDelete={addNew}
94+
allowDelete={addNew && !readOnly}
9295
isChecked={
9396
values.includes(name) ||
9497
values.includes(`{{${name}}}`)
@@ -127,9 +130,12 @@ export default function Suggestions(props: SuggestionsProps) {
127130
isOpen={isSuggestionsOpen}
128131
onClose={closeSuggestions}
129132
>
130-
<div style={divWrapperStyles} onClick={openSuggestions}>
133+
<div
134+
style={divWrapperStyles}
135+
onClick={() => !readOnly && openSuggestions()}
136+
>
131137
<PopoverTrigger>
132-
<Box sx={boxStyles} onClick={openSuggestions}>
138+
<Box sx={boxStyles} onClick={() => !readOnly && openSuggestions()}>
133139
<TagList
134140
onClick={(option) => {
135141
onSuggestionClick(option, false)

packages/frontend/src/components/AttachmentSuggestions/components/TagList.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1+
import { useContext } from 'react'
12
import { Tag, TagCloseButton, TagLabel, Tooltip } from '@chakra-ui/react'
23

4+
import { EditorContext } from '@/contexts/Editor'
5+
36
interface TagsProps {
47
tags: any[]
58
onClick: (option: any) => void
69
}
710

811
function TagList(props: TagsProps) {
912
const { onClick, tags } = props
13+
const { readOnly } = useContext(EditorContext)
1014

1115
const getLabel = (option: any, tooltip?: boolean) => {
1216
const { displayedValue, label, source, uploaded } = option
@@ -36,13 +40,17 @@ function TagList(props: TagsProps) {
3640
minW="40px"
3741
maxW="200px"
3842
flex="0 1 auto"
43+
h="100%"
3944
>
4045
<Tooltip hasArrow label={getLabel(tag, true)}>
4146
<TagLabel isTruncated flex="1 1 auto" minW="0">
4247
{getLabel(tag)}
4348
</TagLabel>
4449
</Tooltip>
45-
<TagCloseButton onClick={() => onClick(tag)} />
50+
<TagCloseButton
51+
onClick={() => !readOnly && onClick(tag)}
52+
isDisabled={readOnly}
53+
/>
4654
</Tag>
4755
)
4856
})}

packages/frontend/src/components/ControlledAutocomplete/index.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { Box, Flex, FormControl, useDisclosure } from '@chakra-ui/react'
1111
import { FormErrorMessage, FormLabel } from '@opengovsg/design-system-react'
1212

1313
import { ComboboxItem, SingleSelect } from '@/components/SingleSelect'
14+
import { EditorContext } from '@/contexts/Editor'
1415
import { StepExecutionsContext } from '@/contexts/StepExecutions'
1516

1617
import extractVariablesAsItems from '../MultiSelect/helpers/extract-variables-as-items'
@@ -74,6 +75,7 @@ function ControlledAutocomplete(
7475
isSearchable,
7576
variableTypes = null,
7677
} = props
78+
const { readOnly } = useContext(EditorContext)
7779
const { priorExecutionSteps } = useContext(StepExecutionsContext)
7880

7981
/**
@@ -170,15 +172,14 @@ function ControlledAutocomplete(
170172
colorScheme="secondary"
171173
isClearable={!required}
172174
items={items}
173-
onChange={fieldOnChange}
175+
onChange={() => !readOnly && fieldOnChange}
174176
value={fieldValue ?? defaultValue}
175177
placeholder={placeholder}
176178
ref={ref}
177179
data-test={`${name}-autocomplete`}
178180
onRefresh={onRefresh}
179181
isRefreshLoading={loading}
180182
freeSolo={freeSolo}
181-
isReadOnly={isCreatingNewOption}
182183
isSearchable={isSearchable}
183184
addNew={
184185
addNewOption

packages/frontend/src/components/DragDropInput/index.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { useCallback, useState } from 'react'
1+
import { useCallback, useContext, useState } from 'react'
22
import { Controller, useFormContext } from 'react-hook-form'
33
import Markdown from 'react-markdown'
44
import { FormControl, Input, Stack } from '@chakra-ui/react'
55
import { FormErrorMessage, FormLabel } from '@opengovsg/design-system-react'
66

77
import FileUpload from '@/components/FileUpload'
8+
import { EditorContext } from '@/contexts/Editor'
89
import { usePreventDrop } from '@/hooks/useDisableDragDrop'
910

1011
const SECRET_KEY_REGEX = /^[a-zA-Z0-9/+]+={0,2}$/
@@ -29,6 +30,7 @@ function DragDropInput(props: DragDropInputProps) {
2930
required,
3031
...inputProps
3132
} = props
33+
const { readOnly } = useContext(EditorContext)
3234
const [dragging, setDragging] = useState(false)
3335

3436
// Disable drag drop anywhere else
@@ -107,7 +109,7 @@ function DragDropInput(props: DragDropInputProps) {
107109
}) => {
108110
return (
109111
<>
110-
<FormControl isInvalid={!!error}>
112+
<FormControl isInvalid={!!error} isReadOnly={readOnly}>
111113
{label && (
112114
<FormLabel
113115
isRequired={required}
@@ -138,18 +140,22 @@ function DragDropInput(props: DragDropInputProps) {
138140
: undefined)}
139141
type="password"
140142
onChange={(...args) => controllerOnChange(...args)}
141-
onDragEnter={handleDragEnter}
142-
onDragLeave={handleDragLeave}
143-
onDragOver={handleDragOver}
144-
onDrop={handleDrop}
143+
onDragEnter={readOnly ? undefined : handleDragEnter}
144+
onDragLeave={readOnly ? undefined : handleDragLeave}
145+
onDragOver={readOnly ? undefined : handleDragOver}
146+
onDrop={readOnly ? undefined : handleDrop}
145147
placeholder={
146148
dragging
147149
? 'Drop your file here'
148150
: placeholder ?? 'Enter or drop your file here'
149151
}
150152
transition="padding 0.2s ease-out"
151153
/>
152-
<FileUpload accept="text/plain" processFile={processFile} />
154+
<FileUpload
155+
accept="text/plain"
156+
processFile={processFile}
157+
disabled={readOnly}
158+
/>
153159
</Stack>
154160
{error && <FormErrorMessage>{error?.message}</FormErrorMessage>}
155161
</FormControl>

packages/frontend/src/components/FlowSubstep/index.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ function FlowSubstep(props: FlowSubstepProps): JSX.Element {
2727
const { flags } = useContext(LaunchDarklyContext)
2828
const formContext = useFormContext()
2929
const {
30-
readOnly,
3130
executeTestStep,
3231
onUpdateStep,
3332
setShouldWarnOnLeave,
@@ -150,7 +149,6 @@ function FlowSubstep(props: FlowSubstepProps): JSX.Element {
150149
schema={argument}
151150
namePrefix="parameters"
152151
stepId={step.id}
153-
disabled={readOnly || isSaving}
154152
/>
155153
))}
156154
</Stack>

packages/frontend/src/components/InputCreator/BooleanRadio.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { IFieldBooleanRadioOptions } from '@plumber/types'
22

3+
import { useContext } from 'react'
34
import { Controller, useFormContext } from 'react-hook-form'
45
import Markdown from 'react-markdown'
56
import { FormControl, RadioGroup, Stack } from '@chakra-ui/react'
@@ -9,6 +10,8 @@ import {
910
Radio,
1011
} from '@opengovsg/design-system-react'
1112

13+
import { EditorContext } from '@/contexts/Editor'
14+
1215
interface BooleanRadioProps {
1316
name: string
1417
label?: string
@@ -55,6 +58,7 @@ export default function BooleanRadio(props: BooleanRadioProps) {
5558
} = props
5659
const { control } = useFormContext()
5760
const [firstOption, secondOption] = options ?? defaultLabelOptions
61+
const { readOnly } = useContext(EditorContext)
5862

5963
return (
6064
<Controller
@@ -86,7 +90,7 @@ export default function BooleanRadio(props: BooleanRadioProps) {
8690
<RadioGroup
8791
onChange={
8892
(selectedValue) =>
89-
onChange(convertStringToBoolean(selectedValue)) // store null in db for backwards compat
93+
!readOnly && onChange(convertStringToBoolean(selectedValue)) // store null in db for backwards compat
9094
}
9195
// value will be empty if not provided on backend, null if user did not select
9296
value={value === '' ? value : convertBooleanToString(value)}

packages/frontend/src/components/InputCreator/index.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ export type InputCreatorProps = {
1616
schema: IField
1717
namePrefix?: string
1818
stepId?: string
19-
disabled?: boolean
2019
parentType?: string
2120
autoFocus?: boolean
2221
}
@@ -30,7 +29,7 @@ const optionGenerator = (options: RawOption[]): IFieldDropdownOption[] =>
3029
options?.map(({ name, value }) => ({ label: name as string, value: value }))
3130

3231
export default function InputCreator(props: InputCreatorProps): JSX.Element {
33-
const { schema, namePrefix, stepId, disabled, parentType, autoFocus } = props
32+
const { schema, namePrefix, stepId, parentType, autoFocus } = props
3433

3534
const {
3635
key: name,
@@ -116,7 +115,6 @@ export default function InputCreator(props: InputCreatorProps): JSX.Element {
116115
required={required}
117116
label={label}
118117
description={description}
119-
disabled={disabled}
120118
placeholder={placeholder}
121119
variablesEnabled={variables}
122120
isRich
@@ -132,7 +130,6 @@ export default function InputCreator(props: InputCreatorProps): JSX.Element {
132130
required={required}
133131
label={label}
134132
description={description}
135-
disabled={disabled}
136133
placeholder={placeholder}
137134
isSingleLine={parentType === 'multicol'}
138135
variablesEnabled
@@ -149,7 +146,7 @@ export default function InputCreator(props: InputCreatorProps): JSX.Element {
149146
defaultValue={value}
150147
required={required}
151148
placeholder={placeholder}
152-
readOnly={readOnly || disabled}
149+
readOnly={readOnly}
153150
name={computedName}
154151
label={label}
155152
multiline={type === 'multiline'}
@@ -195,7 +192,6 @@ export default function InputCreator(props: InputCreatorProps): JSX.Element {
195192
type={type}
196193
// These are InputCreatorProps which MultiRow will forward.
197194
stepId={stepId}
198-
disabled={disabled}
199195
/>
200196
)
201197
}

packages/frontend/src/components/MultiSelect/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
MultiSelect as DSMultiSelect,
1111
} from '@opengovsg/design-system-react'
1212

13+
import { EditorContext } from '@/contexts/Editor'
1314
import { StepExecutionsContext } from '@/contexts/StepExecutions'
1415

1516
import extractVariablesAsItems from './helpers/extract-variables-as-items'
@@ -38,6 +39,7 @@ function MultiSelect(props: MultiSelectProps): React.ReactElement {
3839
} = props
3940
const { control } = useFormContext()
4041
const { priorExecutionSteps } = useContext(StepExecutionsContext)
42+
const { readOnly } = useContext(EditorContext)
4143

4244
const items = useMemo(
4345
() => extractVariablesAsItems(priorExecutionSteps, variableTypes),
@@ -91,6 +93,7 @@ function MultiSelect(props: MultiSelectProps): React.ReactElement {
9193
// changed.
9294
onChange(newValues.sort())
9395
}
96+
isReadOnly={readOnly}
9497
/>
9598
{error && <FormErrorMessage>{error.message}</FormErrorMessage>}
9699
</FormControl>

packages/frontend/src/components/RichTextEditor/index.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { EditorContent, useEditor } from '@tiptap/react'
2929
import StarterKit from '@tiptap/starter-kit'
3030
import escapeHtml from 'escape-html'
3131

32+
import { EditorContext } from '@/contexts/Editor'
3233
import { StepExecutionsContext } from '@/contexts/StepExecutions'
3334
import {
3435
extractVariables,
@@ -240,14 +241,16 @@ const Editor = ({
240241
isLazy
241242
lazyBehavior="unmount"
242243
onClose={closeSuggestions}
243-
isOpen={isSuggestionsOpen && variablesEnabled}
244+
isOpen={isSuggestionsOpen && variablesEnabled && editable}
244245
placement={getPopoverPlacement(editor)}
245246
>
246247
<div
247248
className="editor"
248249
onClick={(e) => {
249-
e.stopPropagation()
250-
openSuggestions()
250+
if (editable) {
251+
e.stopPropagation()
252+
openSuggestions()
253+
}
251254
}}
252255
onBlur={(e) => {
253256
// Focus might shift to menu bar or other children, where we do _not_
@@ -297,7 +300,6 @@ interface RichTextEditorProps {
297300
name: string
298301
label?: string
299302
description?: string
300-
disabled?: boolean
301303
placeholder?: string
302304
variablesEnabled?: boolean
303305
isRich?: boolean
@@ -313,7 +315,6 @@ const RichTextEditor = ({
313315
name,
314316
label,
315317
description,
316-
disabled,
317318
placeholder,
318319
variablesEnabled,
319320
isRich,
@@ -323,6 +324,7 @@ const RichTextEditor = ({
323324
parentType,
324325
autoFocus,
325326
}: RichTextEditorProps) => {
327+
const { readOnly } = useContext(EditorContext)
326328
const { control, getValues } = useFormContext()
327329
const { shouldAutoFocus, isNewRow, rowData } = checkAutoFocus(
328330
name,
@@ -359,7 +361,7 @@ const RichTextEditor = ({
359361
<Editor
360362
onChange={onChange}
361363
initialValue={value}
362-
editable={!disabled}
364+
editable={!readOnly}
363365
placeholder={placeholder}
364366
variablesEnabled={variablesEnabled}
365367
isRich={isRich}

packages/frontend/src/components/TextField/index.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as React from 'react'
1+
import { useContext } from 'react'
22
import { Controller, useFormContext } from 'react-hook-form'
33
import { BiCopy } from 'react-icons/bi'
44
import Markdown from 'react-markdown'
@@ -11,6 +11,8 @@ import {
1111
} from '@opengovsg/design-system-react'
1212
import copy from 'clipboard-copy'
1313

14+
import { EditorContext } from '@/contexts/Editor'
15+
1416
type TextFieldProps = {
1517
shouldUnregister?: boolean
1618
name: string
@@ -48,6 +50,7 @@ export default function TextField(props: TextFieldProps): React.ReactElement {
4850
} = props
4951

5052
const SelectedComponent = multiline ? Textarea : Input
53+
const { readOnly: disabled } = useContext(EditorContext)
5154

5255
return (
5356
<Controller
@@ -88,7 +91,7 @@ export default function TextField(props: TextFieldProps): React.ReactElement {
8891
controllerOnBlur()
8992
onBlur?.(...args)
9093
}}
91-
isReadOnly={readOnly}
94+
isReadOnly={readOnly || disabled}
9295
/>
9396
{clickToCopy && (
9497
<InputRightElement>

0 commit comments

Comments
 (0)