mirror of
https://github.com/supabase/supabase.git
synced 2026-09-06 09:59:03 +08:00
## Summary * Adds a `delete_notebook` AI assistant tool (`needsApproval: true`) that lets the assistant delete a notebook with explicit user approval, mirroring the existing `create_notebook`/`update_notebook` tools. * Wires up a destructive-styled approval card in the AI Assistant Panel (fetches the notebook to show its name, warns the deletion is permanent) using the same `Confirm`/tool-approval plumbing as the other notebook tools. * Updates `tool-filter.ts` opt-in gating, the assistant system prompt, the eval-harness mock tools, and the eval dataset with `delete_notebook` coverage. * Adds test coverage in `notebook-tools.test.ts`, `mock-tools.test.ts`, and `NotebookProposalRenderer.test.tsx`. Closes [FE-4242](https://linear.app/supabase/issue/FE-4242/assistant-delete-notebook-tool). ## Test plan - [X] `pnpm typecheck --filter=studio` passes - [X] `pnpm --filter studio exec vitest run` for the touched files (notebook-tools, mock-tools, NotebookProposalRenderer, [Message.Parts](<http://Message.Parts>), and existing consumers of `content-delete-mutation`) — all passing - [X] `eslint` and `prettier --check` clean on all touched files - [X] Manual verification of the approval UI in a running Studio instance (not done in this session) ## Summary by CodeRabbit * **New Features** * Added AI-assisted notebook deletion with explicit confirmation and irreversible-action warnings. * Added safeguards to distinguish deleting an entire notebook from removing individual panels. * Completed deletions now display the deleted notebook’s name without an option to reopen it. * **Bug Fixes** * Improved handling of missing notebooks and invalid deletion requests. * **Tests** * Added coverage for deletion approval, denial, errors, and successful completion. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added AI-assisted notebook deletion with explicit approval and irreversible-action warnings. * Added confirmation, loading, error, and completion states for notebook deletion. * Prevented accidental full-notebook deletion when only a panel or section should be removed. * Improved notebook update results by showing applied changes when available. * **Bug Fixes** * Notebook deletion now uses the required API version. * Improved handling and validation of missing notebooks during deletion. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
96 lines
3.2 KiB
TypeScript
96 lines
3.2 KiB
TypeScript
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
|
import type { QueryKey } from '@tanstack/react-query'
|
|
import { toast } from 'sonner'
|
|
|
|
import type { ContentData } from './content-query'
|
|
import { contentKeys } from './keys'
|
|
import { del, handleError } from '@/data/fetchers'
|
|
import type { ResponseError, UseCustomMutationOptions } from '@/types'
|
|
|
|
type DeleteContentVariables = { projectRef: string; ids: string[] }
|
|
type DeleteContext = { snapshots: [QueryKey, ContentData | undefined][] }
|
|
|
|
export async function deleteContents(
|
|
{ projectRef, ids }: DeleteContentVariables,
|
|
signal?: AbortSignal,
|
|
headersInit?: HeadersInit
|
|
) {
|
|
const headers = new Headers(headersInit)
|
|
headers.set('Version', '2')
|
|
|
|
const { data, error } = await del('/platform/projects/{ref}/content', {
|
|
headers,
|
|
params: {
|
|
path: { ref: projectRef },
|
|
query: { ids: ids.join(',') },
|
|
},
|
|
signal,
|
|
})
|
|
|
|
if (error) handleError(error)
|
|
return data.map((x) => x.id)
|
|
}
|
|
|
|
type DeleteContentData = Awaited<ReturnType<typeof deleteContents>>
|
|
|
|
/** Returns a copy of a cached content list with the given ids removed. Exported for testing. */
|
|
export function removeContentFromList(data: ContentData, ids: string[]): ContentData {
|
|
return { ...data, content: data.content.filter((item) => !ids.includes(item.id)) }
|
|
}
|
|
|
|
export const useContentDeleteMutation = ({
|
|
onSuccess,
|
|
onError,
|
|
...options
|
|
}: Omit<
|
|
UseCustomMutationOptions<DeleteContentData, ResponseError, DeleteContentVariables, DeleteContext>,
|
|
'mutationFn'
|
|
> = {}) => {
|
|
const queryClient = useQueryClient()
|
|
|
|
return useMutation<DeleteContentData, ResponseError, DeleteContentVariables, DeleteContext>({
|
|
mutationFn: (args) => deleteContents(args),
|
|
async onMutate({ projectRef, ids }) {
|
|
await queryClient.cancelQueries({ queryKey: contentKeys.allContentLists(projectRef) })
|
|
|
|
const snapshots = queryClient.getQueriesData<ContentData>({
|
|
queryKey: contentKeys.allContentLists(projectRef),
|
|
})
|
|
|
|
// allContentLists is a prefix of sibling caches (sql snippets, folders, counts) that
|
|
// don't share the { content: [...] } shape, so only optimistically update entries that do.
|
|
for (const [queryKey, data] of snapshots) {
|
|
if (data && Array.isArray(data.content)) {
|
|
queryClient.setQueryData<ContentData>(queryKey, removeContentFromList(data, ids))
|
|
}
|
|
}
|
|
|
|
return { snapshots }
|
|
},
|
|
async onSuccess(data, variables, context) {
|
|
const { projectRef } = variables
|
|
await Promise.all([
|
|
queryClient.invalidateQueries({ queryKey: contentKeys.allContentLists(projectRef) }),
|
|
queryClient.invalidateQueries({ queryKey: contentKeys.infiniteList(projectRef) }),
|
|
])
|
|
|
|
await onSuccess?.(data, variables, context)
|
|
},
|
|
async onError(error, variables, context) {
|
|
// Restore the snapshots captured in onMutate before surfacing the error.
|
|
if (context?.snapshots) {
|
|
for (const [queryKey, data] of context.snapshots) {
|
|
queryClient.setQueryData(queryKey, data)
|
|
}
|
|
}
|
|
|
|
if (onError === undefined) {
|
|
toast.error(`Failed to delete contents: ${error.message}`)
|
|
} else {
|
|
onError(error, variables, context)
|
|
}
|
|
},
|
|
...options,
|
|
})
|
|
}
|