Skip to content

Commit

Permalink
Allow renaming secrets (#11948)
Browse files Browse the repository at this point in the history
- Close enso-org/cloud-v2#1597
- Allow renaming secrets:
- Make name column editable
- Make name field in secret form (in sidebar) editable
- Sync values from secret form to row heading (syncing from row heading to secret form is a bit harder in the current architecture)

# Important Notes
None
  • Loading branch information
somebody1234 authored Jan 2, 2025
1 parent f7ca795 commit 6bd8cb4
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 33 deletions.
3 changes: 2 additions & 1 deletion app/common/src/services/Backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1290,7 +1290,8 @@ export interface CreateSecretRequestBody {

/** HTTP request body for the "update secret" endpoint. */
export interface UpdateSecretRequestBody {
readonly value: string
readonly title: string | null
readonly value: string | null
}

/** HTTP request body for the "create datalink" endpoint. */
Expand Down
41 changes: 31 additions & 10 deletions app/gui/src/dashboard/components/dashboard/SecretNameColumn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import * as toastAndLogHooks from '#/hooks/toastAndLogHooks'

import * as modalProvider from '#/providers/ModalProvider'

import * as ariaComponents from '#/components/AriaComponents'
import type * as column from '#/components/dashboard/column'
import SvgMask from '#/components/SvgMask'

import UpsertSecretModal from '#/modals/UpsertSecretModal'

import type * as backendModule from '#/services/Backend'
import * as backendModule from '#/services/Backend'

import EditableSpan from '#/components/EditableSpan'
import { useText } from '#/providers/TextProvider'
import * as eventModule from '#/utilities/event'
import * as indent from '#/utilities/indent'
import * as object from '#/utilities/object'
Expand All @@ -37,12 +38,18 @@ export interface SecretNameColumnProps extends column.AssetColumnProps {
*/
export default function SecretNameColumn(props: SecretNameColumnProps) {
const { item, selected, state, rowState, setRowState, isEditable, depth } = props
const { backend } = state
const { backend, nodeMap } = state
const toastAndLog = toastAndLogHooks.useToastAndLog()
const { getText } = useText()
const { setModal } = modalProvider.useSetModal()

const updateSecretMutation = useMutation(backendMutationOptions(backend, 'updateSecret'))

const doRename = async (newTitle: string) => {
await updateSecretMutation.mutateAsync([item.id, { title: newTitle, value: null }, item.title])
setIsEditing(false)
}

const setIsEditing = (isEditingName: boolean) => {
if (isEditable) {
setRowState(object.merger({ isEditingName }))
Expand All @@ -69,9 +76,9 @@ export default function SecretNameColumn(props: SecretNameColumnProps) {
<UpsertSecretModal
id={item.id}
name={item.title}
doCreate={async (_name, value) => {
doCreate={async (title, value) => {
try {
await updateSecretMutation.mutateAsync([item.id, { value }, item.title])
await updateSecretMutation.mutateAsync([item.id, { title, value }, item.title])
} catch (error) {
toastAndLog(null, error)
}
Expand All @@ -82,14 +89,28 @@ export default function SecretNameColumn(props: SecretNameColumnProps) {
}}
>
<SvgMask src={KeyIcon} className="m-name-column-icon size-4" />
{/* Secrets cannot be renamed. */}
<ariaComponents.Text
<EditableSpan
data-testid="asset-row-name"
font="naming"
className="grow bg-transparent"
editable={rowState.isEditingName}
className="grow bg-transparent font-naming"
onSubmit={doRename}
onCancel={() => {
setIsEditing(false)
}}
schema={(z) =>
z.refine(
(value) =>
backendModule.isNewTitleUnique(
item,
value,
nodeMap.current.get(item.parentId)?.children?.map((child) => child.item),
),
{ message: getText('nameShouldBeUnique') },
)
}
>
{item.title}
</ariaComponents.Text>
</EditableSpan>
</div>
)
}
4 changes: 2 additions & 2 deletions app/gui/src/dashboard/layouts/AssetProperties.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -356,8 +356,8 @@ function AssetPropertiesInternal(props: AssetPropertiesInternalProps) {
canCancel={false}
id={item.id}
name={item.title}
doCreate={async (name, value) => {
await updateSecretMutation.mutateAsync([item.id, { value }, name])
doCreate={async (title, value) => {
await updateSecretMutation.mutateAsync([item.id, { title, value }, title])
}}
/>
</div>
Expand Down
8 changes: 6 additions & 2 deletions app/gui/src/dashboard/layouts/AssetsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -948,9 +948,13 @@ function AssetsTable(props: AssetsTableProps) {
<UpsertSecretModal
id={item.item.id}
name={item.item.title}
doCreate={async (_name, value) => {
doCreate={async (title, value) => {
try {
await updateSecretMutation.mutateAsync([id, { value }, item.item.title])
await updateSecretMutation.mutateAsync([
id,
{ title, value },
item.item.title,
])
} catch (error) {
toastAndLog(null, error)
}
Expand Down
32 changes: 14 additions & 18 deletions app/gui/src/dashboard/modals/UpsertSecretModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,40 +27,36 @@ export default function UpsertSecretModal(props: UpsertSecretModalProps) {
const { getText } = useText()

const isCreatingSecret = id == null
const isNameEditable = nameRaw == null

const form = Form.useForm({
method: 'dialog',
schema: (z) =>
z.object({ name: z.string().min(1, getText('emptyStringError')), value: z.string() }),
defaultValues: { name: nameRaw ?? '', value: '' },
onSubmit: async ({ name, value }) => {
await doCreate(name, value)
z.object({ title: z.string().min(1, getText('emptyStringError')), value: z.string() }),
defaultValues: { title: nameRaw ?? '', value: '' },
onSubmit: async ({ title, value }) => {
await doCreate(title, value)
form.reset({ title, value })
},
})

const content = (
<Form form={form} testId="upsert-secret-modal" gap="none" className="w-full">
{isNameEditable && (
<Input
form={form}
name="name"
autoFocus={isNameEditable}
autoComplete="off"
isDisabled={!isNameEditable}
label={getText('name')}
placeholder={getText('secretNamePlaceholder')}
/>
)}
<Input
form={form}
name="title"
autoFocus
autoComplete="off"
label={getText('name')}
placeholder={getText('secretNamePlaceholder')}
/>
<Input
form={form}
name="value"
type="password"
autoFocus={!isNameEditable}
autoComplete="off"
label={getText('value')}
placeholder={
isNameEditable ? getText('secretValuePlaceholder') : getText('secretValueHidden')
nameRaw == null ? getText('secretValuePlaceholder') : getText('secretValueHidden')
}
/>
<ButtonGroup className="mt-2">
Expand Down

0 comments on commit 6bd8cb4

Please sign in to comment.