Skip to content

Commit c153df2

Browse files
authored
Refactor search list in sql editor to have context menu (supabase#37945)
* Refactor search list in sql editor to have context menu * Add visibility to snippet item in search list tree view * Address feedback * Attempt to fix unit test for edit secret modal * REvert changes to edit secret modal test
1 parent b126175 commit c153df2

File tree

10 files changed

+515
-268
lines changed

10 files changed

+515
-268
lines changed

apps/studio/components/interfaces/Integrations/Vault/Secrets/__tests__/EditSecretModal.test.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1-
import { useState } from 'react'
2-
import { describe, expect, it, beforeEach, vi } from 'vitest'
3-
import { mockAnimationsApi } from 'jsdom-testing-mocks'
4-
import { screen, waitFor, fireEvent } from '@testing-library/dom'
1+
import { fireEvent, screen, waitFor } from '@testing-library/dom'
52
import userEvent from '@testing-library/user-event'
3+
import { ProjectContextProvider } from 'components/layouts/ProjectLayout/ProjectContext'
4+
import { mockAnimationsApi } from 'jsdom-testing-mocks'
5+
import { useState } from 'react'
66
import { render } from 'tests/helpers'
77
import { addAPIMock } from 'tests/lib/msw'
8-
import { ProjectContextProvider } from 'components/layouts/ProjectLayout/ProjectContext'
9-
import EditSecretModal from '../EditSecretModal'
108
import { routerMock } from 'tests/lib/route-mock'
9+
import { VaultSecret } from 'types'
10+
import { beforeEach, describe, expect, it, vi } from 'vitest'
11+
import EditSecretModal from '../EditSecretModal'
1112

12-
const secret = {
13+
const secret: VaultSecret = {
1314
id: '47ca58b4-01c5-4a71-8814-c73856b02e0e',
1415
name: 'test',
1516
description: 'new text',
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { useRouter } from 'next/router'
2+
import { toast } from 'sonner'
3+
4+
import { useParams } from 'common'
5+
import { useContentDeleteMutation } from 'data/content/content-delete-mutation'
6+
import { Snippet } from 'data/content/sql-folders-query'
7+
import { useSqlEditorV2StateSnapshot } from 'state/sql-editor-v2'
8+
import { createTabId, useTabsStateSnapshot } from 'state/tabs'
9+
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
10+
11+
export const DeleteSnippetsModal = ({
12+
snippets,
13+
visible,
14+
onClose,
15+
}: {
16+
visible: boolean
17+
snippets: Snippet[]
18+
onClose: () => void
19+
}) => {
20+
const router = useRouter()
21+
const { ref: projectRef, id } = useParams()
22+
const tabs = useTabsStateSnapshot()
23+
const snapV2 = useSqlEditorV2StateSnapshot()
24+
25+
const postDeleteCleanup = (ids: string[]) => {
26+
if (!!id && ids.includes(id)) {
27+
const openedSQLTabs = tabs.openTabs.filter((x) => x.startsWith('sql-') && !x.includes(id))
28+
if (openedSQLTabs.length > 0) {
29+
// [Joshen] For simplicity, just opening the first tab for now
30+
const firstTabId = openedSQLTabs[0].split('sql-')[1]
31+
router.push(`/project/${projectRef}/sql/${firstTabId}`)
32+
} else {
33+
router.push(`/project/${projectRef}/sql/new`)
34+
}
35+
}
36+
37+
if (ids.length > 0) ids.forEach((id) => snapV2.removeSnippet(id))
38+
}
39+
40+
const { mutate: deleteContent, isLoading: isDeleting } = useContentDeleteMutation({
41+
onSuccess: (data) => {
42+
toast.success(
43+
`Successfully deleted ${snippets.length.toLocaleString()} quer${snippets.length > 1 ? 'ies' : 'y'}`
44+
)
45+
46+
// Update Tabs state - currently unknown how to differentiate between sql and non-sql content
47+
// so we're just deleting all tabs for with matching IDs
48+
const tabIds = data.map((id) => createTabId('sql', { id }))
49+
tabs.removeTabs(tabIds)
50+
51+
postDeleteCleanup(data)
52+
onClose()
53+
},
54+
onError: (error, data) => {
55+
if (error.message.includes('Contents not found')) {
56+
postDeleteCleanup(data.ids)
57+
onClose()
58+
} else {
59+
toast.error(`Failed to delete query: ${error.message}`)
60+
}
61+
},
62+
})
63+
64+
const onConfirmDelete = () => {
65+
if (!projectRef) return console.error('Project ref is required')
66+
deleteContent({ projectRef, ids: snippets.map((x) => x.id) })
67+
}
68+
69+
return (
70+
<ConfirmationModal
71+
size="small"
72+
visible={visible}
73+
title={`Confirm to delete ${snippets.length === 1 ? 'query' : `${snippets.length.toLocaleString()} quer${snippets.length > 1 ? 'ies' : 'y'}`}`}
74+
confirmLabel={`Delete ${snippets.length.toLocaleString()} quer${snippets.length > 1 ? 'ies' : 'y'}`}
75+
confirmLabelLoading="Deleting query"
76+
loading={isDeleting}
77+
variant="destructive"
78+
onCancel={onClose}
79+
onConfirm={onConfirmDelete}
80+
alert={
81+
(snippets[0]?.visibility as unknown as string) === 'project'
82+
? {
83+
title: 'This SQL snippet will be lost forever',
84+
description:
85+
'Deleting this query will remove it for all members of the project team.',
86+
}
87+
: undefined
88+
}
89+
>
90+
<p className="text-sm">
91+
This action cannot be undone.{' '}
92+
{snippets.length === 1
93+
? `Are you sure you want to delete '${snippets[0]?.name}'?`
94+
: `Are you sure you want to delete the selected ${snippets.length} quer${snippets.length > 1 ? 'ies' : 'y'}?`}
95+
</p>
96+
</ConfirmationModal>
97+
)
98+
}

0 commit comments

Comments
 (0)