fix: Hide favourites and share snippets on self-hosted variant (#41227)

* Hide favorite and share actions for self-hosted version.

* Rename the query on save only on platform.

* Simplify useCheckOpenAiKeyQuery.

* Rename with AI now depends if the OPENAI_API_KEY is set.

* Minor fixes.

* Fix the tests to use .skip for skipping tests. Remove extra port params.

* Make the test for favourites work only on platform variant.
This commit is contained in:
Ivan Vasilov authored and GitHub committed 2025-12-10 10:12:15 -07:00
1 parent 6d10a4ddad
commit 581ae07120
12 files changed
+297 -266

No files matched your search

@@ -2,6 +2,8 @@ import { useEffect, useState } from 'react'
import { toast } from 'sonner'
import { useParams } from 'common'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { useCheckOpenAIKeyQuery } from 'data/ai/check-api-key-query'
import { useSqlTitleGenerateMutation } from 'data/ai/sql-title-mutation'
import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query'
import { getContentById } from 'data/content/content-id-query'
@@ -13,6 +15,7 @@ import { Snippet } from 'data/content/sql-folders-query'
import type { SqlSnippet } from 'data/content/sql-snippets-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { useRouter } from 'next/router'
import { useSqlEditorV2StateSnapshot } from 'state/sql-editor-v2'
import { createTabId, useTabsStateSnapshot } from 'state/tabs'
import { AiIconAnimation, Button, Form, Input, Modal } from 'ui'
@@ -32,6 +35,7 @@ const RenameQueryModal = ({
onComplete,
}: RenameQueryModalProps) => {
const { ref } = useParams()
const router = useRouter()
const { data: organization } = useSelectedOrganizationQuery()
const snapV2 = useSqlEditorV2StateSnapshot()
@@ -61,6 +65,8 @@ const RenameQueryModal = ({
toast.error(`Failed to rename query: ${error.message}`)
},
})
const { data: check } = useCheckOpenAIKeyQuery()
const isApiKeySet = !!check?.hasKey
const generateTitle = async () => {
if ('content' in snippet && isSQLSnippet) {
@@ -149,11 +155,19 @@ const RenameQueryModal = ({
/>
<div className="flex w-full justify-end mt-2">
{!hasHipaaAddon && (
<Button
<ButtonTooltip
type="default"
onClick={() => generateTitle()}
size="tiny"
disabled={isTitleGenerationLoading}
disabled={isTitleGenerationLoading || !isApiKeySet}
tooltip={{
content: {
side: 'bottom',
text: isApiKeySet
? undefined
: 'Add your "OPENAI_API_KEY" to your environment variables to use this feature.',
},
}}
>
<div className="flex items-center gap-1">
<div className="scale-75">
@@ -161,7 +175,7 @@ const RenameQueryModal = ({
</div>
<span>Rename with Supabase AI</span>
</div>
</Button>
</ButtonTooltip>
)}
</div>
<Input.TextArea
@@ -6,7 +6,7 @@ import { useRouter } from 'next/router'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { toast } from 'sonner'
import { LOCAL_STORAGE_KEYS, useParams } from 'common'
import { IS_PLATFORM, LOCAL_STORAGE_KEYS, useParams } from 'common'
import { SIDEBAR_KEYS } from 'components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
import ResizableAIWidget from 'components/ui/AIEditor/ResizableAIWidget'
import { GridFooter } from 'components/ui/GridFooter'
@@ -38,6 +38,7 @@ import { getSqlEditorV2StateSnapshot, useSqlEditorV2StateSnapshot } from 'state/
import { createTabId, useTabsStateSnapshot } from 'state/tabs'
import {
Button,
cn,
DropdownMenu,
DropdownMenuContent,
DropdownMenuRadioGroup,
@@ -49,7 +50,6 @@ import {
Tooltip,
TooltipContent,
TooltipTrigger,
cn,
} from 'ui'
import { useSqlEditorDiff, useSqlEditorPrompt } from './hooks'
import { RunQueryWarningModal } from './RunQueryWarningModal'
@@ -285,7 +285,11 @@ export const SQLEditor = () => {
return
}
if (!isHipaaProjectDisallowed && snippet?.snippet.name === untitledSnippetTitle) {
if (
!isHipaaProjectDisallowed &&
snippet?.snippet.name.startsWith(untitledSnippetTitle) &&
IS_PLATFORM
) {
// Intentionally don't await title gen (lazy)
setAiTitle(id, sql)
}
@@ -92,23 +92,27 @@ const UtilityActions = ({
</span>
{intellisenseEnabled && <Check className="text-brand" size={16} />}
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
className="gap-x-2"
onClick={() => {
if (isFavorite) removeFavorite()
else addFavorite()
}}
>
<Heart
size={14}
strokeWidth={2}
className={
isFavorite ? 'fill-brand stroke-none' : 'fill-none stroke-foreground-light'
}
/>
{isFavorite ? 'Remove from' : 'Add to'} favorites
</DropdownMenuItem>
{IS_PLATFORM && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem
className="gap-x-2"
onClick={() => {
if (isFavorite) removeFavorite()
else addFavorite()
}}
>
<Heart
size={14}
strokeWidth={2}
className={
isFavorite ? 'fill-brand stroke-none' : 'fill-none stroke-foreground-light'
}
/>
{isFavorite ? 'Remove from' : 'Add to'} favorites
</DropdownMenuItem>
</>
)}
<DropdownMenuItem className="gap-x-2" onClick={prettifyQuery}>
<AlignLeft size={14} strokeWidth={2} className="text-foreground-light" />
Prettify SQL
@@ -1,10 +1,10 @@
import { keepPreviousData } from '@tanstack/react-query'
import { Heart } from 'lucide-react'
import { useRouter } from 'next/router'
import { useEffect, useMemo, useState } from 'react'
import { toast } from 'sonner'
import { keepPreviousData } from '@tanstack/react-query'
import { LOCAL_STORAGE_KEYS, useParams } from 'common'
import { IS_PLATFORM, LOCAL_STORAGE_KEYS, useParams } from 'common'
import DownloadSnippetModal from 'components/interfaces/SQLEditor/DownloadSnippetModal'
import { MoveQueryModal } from 'components/interfaces/SQLEditor/MoveQueryModal'
import RenameQueryModal from 'components/interfaces/SQLEditor/RenameQueryModal'
@@ -440,160 +440,168 @@ export const SQLEditorNav = ({ sort = 'inserted_at' }: SQLEditorNavProps) => {
return (
<>
<InnerSideMenuSeparator />
<InnerSideMenuCollapsible
className="px-0"
open={showSharedSnippets}
onOpenChange={(value) => {
setSectionVisibility({ ...(sectionVisibility ?? DEFAULT_SECTION_STATE), shared: value })
}}
>
<InnerSideMenuCollapsibleTrigger
title={`Shared ${numProjectSnippets > 0 ? ` (${numProjectSnippets})` : ''}`}
/>
<InnerSideMenuCollapsibleContent className="group-data-[state=open]:pt-2">
{isLoadingSharedSqlSnippets ? (
<SQLEditorLoadingSnippets />
) : sharedSnippets.length === 0 ? (
<InnerSideBarEmptyPanel
className="mx-2"
title="No shared queries"
description="Share queries with your team by right-clicking on the query."
{IS_PLATFORM && (
<>
<InnerSideMenuCollapsible
className="px-0"
open={showSharedSnippets}
onOpenChange={(value) => {
setSectionVisibility({
...(sectionVisibility ?? DEFAULT_SECTION_STATE),
shared: value,
})
}}
>
<InnerSideMenuCollapsibleTrigger
title={`Shared ${numProjectSnippets > 0 ? ` (${numProjectSnippets})` : ''}`}
/>
) : (
<TreeView
data={projectSnippetsTreeState}
aria-label="project-level-snippets"
nodeRenderer={({ element, ...props }) => {
const isOpened = Object.values(tabs.tabsMap).some(
(tab) => tab.metadata?.sqlId === element.metadata?.id
)
const tabId = createTabId('sql', {
id: element?.metadata?.id as unknown as Snippet['id'],
})
const isPreview = tabs.previewTabId === tabId
const isActive = !isPreview && element.metadata?.id === id
const isSelected = selectedSnippets.some((x) => x.id === element.metadata?.id)
<InnerSideMenuCollapsibleContent className="group-data-[state=open]:pt-2">
{isLoadingSharedSqlSnippets ? (
<SQLEditorLoadingSnippets />
) : sharedSnippets.length === 0 ? (
<InnerSideBarEmptyPanel
className="mx-2"
title="No shared queries"
description="Share queries with your team by right-clicking on the query."
/>
) : (
<TreeView
data={projectSnippetsTreeState}
aria-label="project-level-snippets"
nodeRenderer={({ element, ...props }) => {
const isOpened = Object.values(tabs.tabsMap).some(
(tab) => tab.metadata?.sqlId === element.metadata?.id
)
const tabId = createTabId('sql', {
id: element?.metadata?.id as unknown as Snippet['id'],
})
const isPreview = tabs.previewTabId === tabId
const isActive = !isPreview && element.metadata?.id === id
const isSelected = selectedSnippets.some((x) => x.id === element.metadata?.id)
return (
<SQLEditorTreeViewItem
{...props}
isOpened={isOpened && !isPreview}
isSelected={isActive || isSelected}
isPreview={isPreview}
onDoubleClick={(e) => {
e.preventDefault()
tabs.makeTabPermanent(tabId)
}}
element={element}
onSelectDelete={() => {
setShowDeleteModal(true)
setSelectedSnippets([element.metadata as unknown as Snippet])
}}
onSelectRename={() => {
setShowRenameModal(true)
setSelectedSnippetToRename(element.metadata as Snippet)
}}
onSelectDownload={() => {
setSelectedSnippetToDownload(element.metadata as Snippet)
}}
onSelectUnshare={() => {
setSelectedSnippetToUnshare(element.metadata as Snippet)
}}
isLastItem={projectSnippetsLastItemIds.has(element.id as string)}
hasNextPage={hasMoreSharedSqlSnippets}
fetchNextPage={fetchNextSharedSqlSnippets}
isFetchingNextPage={isFetchingMoreSharedSqlSnippets}
/>
)
}}
return (
<SQLEditorTreeViewItem
{...props}
isOpened={isOpened && !isPreview}
isSelected={isActive || isSelected}
isPreview={isPreview}
onDoubleClick={(e) => {
e.preventDefault()
tabs.makeTabPermanent(tabId)
}}
element={element}
onSelectDelete={() => {
setShowDeleteModal(true)
setSelectedSnippets([element.metadata as unknown as Snippet])
}}
onSelectRename={() => {
setShowRenameModal(true)
setSelectedSnippetToRename(element.metadata as Snippet)
}}
onSelectDownload={() => {
setSelectedSnippetToDownload(element.metadata as Snippet)
}}
onSelectUnshare={() => {
setSelectedSnippetToUnshare(element.metadata as Snippet)
}}
isLastItem={projectSnippetsLastItemIds.has(element.id as string)}
hasNextPage={hasMoreSharedSqlSnippets}
fetchNextPage={fetchNextSharedSqlSnippets}
isFetchingNextPage={isFetchingMoreSharedSqlSnippets}
/>
)
}}
/>
)}
</InnerSideMenuCollapsibleContent>
</InnerSideMenuCollapsible>
<InnerSideMenuSeparator />
<InnerSideMenuCollapsible
className="px-0"
open={showFavoriteSnippets}
onOpenChange={(value) => {
setSectionVisibility({
...(sectionVisibility ?? DEFAULT_SECTION_STATE),
favorite: value,
})
}}
>
<InnerSideMenuCollapsibleTrigger
title={`Favorites ${numFavoriteSnippets > 0 ? ` (${numFavoriteSnippets})` : ''}`}
/>
)}
</InnerSideMenuCollapsibleContent>
</InnerSideMenuCollapsible>
<InnerSideMenuCollapsibleContent className="group-data-[state=open]:pt-2">
{isLoadingFavoriteSqlSnippets ? (
<SQLEditorLoadingSnippets />
) : favoriteSnippets.length === 0 ? (
<InnerSideBarEmptyPanel
title="No favorite queries"
className="mx-2 px-3"
description={
<>
Save a query to favorites for easy accessibility by clicking the{' '}
<Heart size={12} className="inline-block relative align-center -top-[1px]" />{' '}
icon.
</>
}
/>
) : (
<TreeView
data={favoritesTreeState}
aria-label="favorite-snippets"
nodeRenderer={({ element, ...props }) => {
const isOpened = Object.values(tabs.tabsMap).some(
(tab) => tab.metadata?.sqlId === element.metadata?.id
)
const tabId = createTabId('sql', {
id: element?.metadata?.id as unknown as Snippet['id'],
})
const isPreview = tabs.previewTabId === tabId
const isActive = !isPreview && element.metadata?.id === id
const isSelected = selectedSnippets.some((x) => x.id === element.metadata?.id)
<InnerSideMenuSeparator />
<InnerSideMenuCollapsible
className="px-0"
open={showFavoriteSnippets}
onOpenChange={(value) => {
setSectionVisibility({ ...(sectionVisibility ?? DEFAULT_SECTION_STATE), favorite: value })
}}
>
<InnerSideMenuCollapsibleTrigger
title={`Favorites ${numFavoriteSnippets > 0 ? ` (${numFavoriteSnippets})` : ''}`}
/>
<InnerSideMenuCollapsibleContent className="group-data-[state=open]:pt-2">
{isLoadingFavoriteSqlSnippets ? (
<SQLEditorLoadingSnippets />
) : favoriteSnippets.length === 0 ? (
<InnerSideBarEmptyPanel
title="No favorite queries"
className="mx-2 px-3"
description={
<>
Save a query to favorites for easy accessibility by clicking the{' '}
<Heart size={12} className="inline-block relative align-center -top-[1px]" />{' '}
icon.
</>
}
/>
) : (
<TreeView
data={favoritesTreeState}
aria-label="favorite-snippets"
nodeRenderer={({ element, ...props }) => {
const isOpened = Object.values(tabs.tabsMap).some(
(tab) => tab.metadata?.sqlId === element.metadata?.id
)
const tabId = createTabId('sql', {
id: element?.metadata?.id as unknown as Snippet['id'],
})
const isPreview = tabs.previewTabId === tabId
const isActive = !isPreview && element.metadata?.id === id
const isSelected = selectedSnippets.some((x) => x.id === element.metadata?.id)
return (
<SQLEditorTreeViewItem
{...props}
isSelected={isActive || isSelected}
isOpened={isOpened && !isPreview}
isPreview={isPreview}
onDoubleClick={(e) => {
e.preventDefault()
tabs.makeTabPermanent(tabId)
}}
element={element}
onSelectDelete={() => {
setShowDeleteModal(true)
setSelectedSnippets([element.metadata as unknown as Snippet])
}}
onSelectRename={() => {
setShowRenameModal(true)
setSelectedSnippetToRename(element.metadata as Snippet)
}}
onSelectDownload={() => {
setSelectedSnippetToDownload(element.metadata as Snippet)
}}
onSelectShare={() => setSelectedSnippetToShare(element.metadata as Snippet)}
onSelectUnshare={() => {
setSelectedSnippetToUnshare(element.metadata as Snippet)
}}
isLastItem={favoriteSnippetsLastItemIds.has(element.id as string)}
hasNextPage={hasMoreFavoriteSqlSnippets}
fetchNextPage={fetchNextFavoriteSqlSnippets}
isFetchingNextPage={isFetchingMoreFavoriteSqlSnippets}
/>
)
}}
/>
)}
</InnerSideMenuCollapsibleContent>
</InnerSideMenuCollapsible>
<InnerSideMenuSeparator />
return (
<SQLEditorTreeViewItem
{...props}
isSelected={isActive || isSelected}
isOpened={isOpened && !isPreview}
isPreview={isPreview}
onDoubleClick={(e) => {
e.preventDefault()
tabs.makeTabPermanent(tabId)
}}
element={element}
onSelectDelete={() => {
setShowDeleteModal(true)
setSelectedSnippets([element.metadata as unknown as Snippet])
}}
onSelectRename={() => {
setShowRenameModal(true)
setSelectedSnippetToRename(element.metadata as Snippet)
}}
onSelectDownload={() => {
setSelectedSnippetToDownload(element.metadata as Snippet)
}}
onSelectShare={() => setSelectedSnippetToShare(element.metadata as Snippet)}
onSelectUnshare={() => {
setSelectedSnippetToUnshare(element.metadata as Snippet)
}}
isLastItem={favoriteSnippetsLastItemIds.has(element.id as string)}
hasNextPage={hasMoreFavoriteSqlSnippets}
fetchNextPage={fetchNextFavoriteSqlSnippets}
isFetchingNextPage={isFetchingMoreFavoriteSqlSnippets}
/>
)
}}
/>
)}
</InnerSideMenuCollapsibleContent>
</InnerSideMenuCollapsible>
<InnerSideMenuSeparator />
</>
)}
<InnerSideMenuCollapsible
open={showPrivateSnippets}
onOpenChange={(value) => {
@@ -354,16 +354,19 @@ export const SQLEditorTreeViewItem = ({
Move query
</ContextMenuItem_Shadcn_>
)}
{onSelectShare !== undefined && !isSharedSnippet && canCreateSQLSnippet && (
<ContextMenuItem_Shadcn_
className="gap-x-2"
onSelect={() => onSelectShare()}
onFocusCapture={(e) => e.stopPropagation()}
>
<Share size={14} />
Share query with team
</ContextMenuItem_Shadcn_>
)}
{onSelectShare !== undefined &&
!isSharedSnippet &&
canCreateSQLSnippet &&
IS_PLATFORM && (
<ContextMenuItem_Shadcn_
className="gap-x-2"
onSelect={() => onSelectShare()}
onFocusCapture={(e) => e.stopPropagation()}
>
<Share size={14} />
Share query with team
</ContextMenuItem_Shadcn_>
)}
{onSelectUnshare !== undefined && isSharedSnippet && isOwner && (
<ContextMenuItem_Shadcn_
className="gap-x-2"
@@ -384,19 +387,21 @@ export const SQLEditorTreeViewItem = ({
Duplicate query
</ContextMenuItem_Shadcn_>
)}
<ContextMenuItem_Shadcn_
className="gap-x-2"
onSelect={() => onToggleFavorite()}
onFocusCapture={(e) => e.stopPropagation()}
>
<Heart
size={14}
className={cn(
isFavorite ? 'fill-brand stroke-none' : 'fill-none stroke-foreground-light'
)}
/>
{isFavorite ? 'Remove from' : 'Add to'} favorites
</ContextMenuItem_Shadcn_>
{IS_PLATFORM && (
<ContextMenuItem_Shadcn_
className="gap-x-2"
onSelect={() => onToggleFavorite()}
onFocusCapture={(e) => e.stopPropagation()}
>
<Heart
size={14}
className={cn(
isFavorite ? 'fill-brand stroke-none' : 'fill-none stroke-foreground-light'
)}
/>
{isFavorite ? 'Remove from' : 'Add to'} favorites
</ContextMenuItem_Shadcn_>
)}
{onSelectDownload !== undefined && IS_PLATFORM && (
<ContextMenuItem_Shadcn_
className="gap-x-2"
@@ -103,7 +103,7 @@ export const AIAssistant = ({ className }: AIAssistantProps) => {
const [messageRatings, setMessageRatings] = useState<Record<string, 'positive' | 'negative'>>({})
const { data: check, isSuccess } = useCheckOpenAIKeyQuery()
const isApiKeySet = IS_PLATFORM || !!check?.hasKey
const isApiKeySet = !!check?.hasKey
const { mutateAsync: rateMessage } = useRateMessageMutation()
+5 -1
View File
@@ -9,6 +9,10 @@ import { aiKeys } from './keys'
// so we can disable the chat editor and add a warning about manually adding the key
export async function checkOpenAIKey(signal?: AbortSignal) {
if (IS_PLATFORM) {
return { hasKey: true }
}
const headers = await constructHeaders()
const response = await fetchHandler(`${BASE_PATH}/api/ai/sql/check-api-key`, {
headers,
@@ -37,6 +41,6 @@ export const useCheckOpenAIKeyQuery = <TData = ResourceData>({
useQuery<ResourceData, ResourceError, TData>({
queryKey: aiKeys.apiKey(),
queryFn: ({ signal }) => checkOpenAIKey(signal),
enabled: !IS_PLATFORM && enabled,
enabled: enabled,
...options,
})
@@ -6,6 +6,7 @@ import { handleError, put } from 'data/fetchers'
import type { ResponseError, UseCustomMutationOptions } from 'types'
import type { Content } from './content-query'
import { contentKeys } from './keys'
import type { Snippet } from './sql-folders-query'
export type UpsertContentPayload = Omit<components['schemas']['UpsertContentBody'], 'content'> & {
content: Partial<Content['content']>
@@ -29,7 +30,7 @@ export async function upsertContent(
})
if (error) handleError(error)
return data
return data as Snippet | null
}
export type UpsertContentData = Awaited<ReturnType<typeof upsertContent>>
@@ -22,12 +22,13 @@ const SQLEditorIndexPage: NextPageWithLayout = () => {
// Handle redirect to last opened snippet tab, or last snippet tab
const lastOpenedTab = history.sql
const lastTabId = store.openTabs.find((id) => store.tabsMap[id]?.type === 'sql')
if (lastOpenedTab !== undefined) {
router.push(`/project/${projectRef}/sql/${history.sql}`)
} else if (lastTabId) {
const lastTab = store.tabsMap[lastTabId]
if (lastTab) router.push(`/project/${projectRef}/sql/${lastTab.id.replace('sql-', '')}`)
if (lastTab) {
router.push(`/project/${projectRef}/sql/${lastTab.id.replace('sql-', '')}`)
}
} else {
router.push(`/project/${projectRef}/sql/new`)
}
+3
View File
@@ -169,6 +169,9 @@ function createTabsState(projectRef: string) {
tabsMap,
previewTabId,
hasTab: (id: string) => {
return !!store.tabsMap[id]
},
addTab: (tab: Tab) => {
// If tab exists and is active, don't do anything
if (store.tabsMap[tab.id] && store.activeTab === tab.id) {
+49 -62
View File
@@ -1,12 +1,12 @@
import { expect, Page } from '@playwright/test'
import fs from 'fs'
import { env } from '../env.config.js'
import { isCLI } from '../utils/is-cli.js'
import { resetLocalStorage } from '../utils/reset-local-storage.js'
import { test } from '../utils/test.js'
import { toUrl } from '../utils/to-url.js'
import { waitForApiResponseWithTimeout } from '../utils/wait-for-response-with-timeout.js'
import { waitForApiResponse } from '../utils/wait-for-response.js'
import { env } from '../env.config.js'
const sqlSnippetName = 'pw_sql_snippet'
const sqlSnippetNameDuplicate = 'pw_sql_snippet (Duplicate)'
@@ -215,23 +215,23 @@ hello world`)
})
test('snippet favourite works as expected', async ({ ref }) => {
if (!isCLI()) {
// clean up private snippets and snippets shared with the team
await waitForApiResponseWithTimeout(
page,
(response) => response.url().includes('query?key=table-columns'),
3000
)
const privateSnippetSection = page.getByLabel('private-snippets')
if ((await privateSnippetSection.getByText(newSqlSnippetName, { exact: true }).count()) > 0) {
await deleteSqlSnippet(page, ref, newSqlSnippetName)
}
test.skip(isCLI(), 'This test does not work in self-hosted environments.')
if (
(await privateSnippetSection.getByText(sqlSnippetNameFavorite, { exact: true }).count()) > 0
) {
await deleteSqlSnippet(page, ref, sqlSnippetNameFavorite)
}
// clean up private snippets and snippets shared with the team
await waitForApiResponseWithTimeout(
page,
(response) => response.url().includes('query?key=table-columns'),
3000
)
const privateSnippetSection = page.getByLabel('private-snippets')
if ((await privateSnippetSection.getByText(newSqlSnippetName, { exact: true }).count()) > 0) {
await deleteSqlSnippet(page, ref, newSqlSnippetName)
}
if (
(await privateSnippetSection.getByText(sqlSnippetNameFavorite, { exact: true }).count()) > 0
) {
await deleteSqlSnippet(page, ref, sqlSnippetNameFavorite)
}
// create sql snippet
@@ -242,7 +242,6 @@ hello world`)
await page.getByTestId('sql-run-button').click()
// rename snippet
const privateSnippetSection = page.getByLabel('private-snippets')
await privateSnippetSection.getByText(newSqlSnippetName).click({ button: 'right' })
await page.getByRole('menuitem', { name: 'Rename query', exact: true }).click()
await expect(page.getByRole('heading', { name: 'Rename' })).toBeVisible()
@@ -283,10 +282,7 @@ hello world`)
})
test('share with team works as expected', async ({ ref }) => {
if (!isCLI()) {
console.log('Sharing and unsharing SQL snippet has issues in staging')
return
}
test.skip(isCLI(), 'Sharing and unsharing SQL snippet has issues in staging')
// clean up private snippets and snippets shared with the team
await waitForApiResponseWithTimeout(
@@ -379,25 +375,21 @@ hello world`)
})
test('folders works as expected', async ({ ref }) => {
if (!isCLI()) {
// clean up folders and snippets
await waitForApiResponseWithTimeout(
page,
(response) => response.url().includes('query?key=table-columns'),
3000
)
const privateSnippetSection = page.getByLabel('private-snippets')
if ((await privateSnippetSection.getByText(sqlFolderName, { exact: true }).count()) > 0) {
await deleteFolder(page, ref, sqlFolderName)
}
if (
(await privateSnippetSection.getByText(sqlFolderNameUpdated, { exact: true }).count()) > 0
) {
await deleteFolder(page, ref, sqlFolderNameUpdated)
}
} else {
console.log('This test does not work in self-hosted environments.')
return
test.skip(isCLI(), 'This test does not work in self-hosted environments.')
// clean up folders and snippets
await waitForApiResponseWithTimeout(
page,
(response) => response.url().includes('query?key=table-columns'),
3000
)
const privateSnippetSection = page.getByLabel('private-snippets')
if ((await privateSnippetSection.getByText(sqlFolderName, { exact: true }).count()) > 0) {
await deleteFolder(page, ref, sqlFolderName)
}
if (
(await privateSnippetSection.getByText(sqlFolderNameUpdated, { exact: true }).count()) > 0
) {
await deleteFolder(page, ref, sqlFolderNameUpdated)
}
// create sql snippet
@@ -408,7 +400,6 @@ hello world`)
await page.getByTestId('sql-run-button').click()
// rename snippet
const privateSnippetSection = page.getByLabel('private-snippets')
await privateSnippetSection.getByText(newSqlSnippetName).click({ button: 'right' })
await page.getByRole('menuitem', { name: 'Rename query', exact: true }).click()
await expect(page.getByRole('heading', { name: 'Rename' })).toBeVisible()
@@ -473,26 +464,22 @@ hello world`)
})
test('other SQL snippets actions work as expected', async ({ ref }) => {
if (!isCLI()) {
// clean up 'Untitled query', 'pw_sql_snippet' and 'pw_sql_snippet (Duplicate)' snippets if exists
await waitForApiResponseWithTimeout(
page,
(response) => response.url().includes('query?key=table-columns'),
3000
)
const privateSnippet = page.getByLabel('private-snippets')
if ((await privateSnippet.getByText(newSqlSnippetName).count()) > 0) {
deleteSqlSnippet(page, ref, newSqlSnippetName)
}
if ((await privateSnippet.getByText(sqlSnippetNameDuplicate, { exact: true }).count()) > 0) {
await deleteSqlSnippet(page, ref, sqlSnippetNameDuplicate)
}
if ((await privateSnippet.getByText(sqlSnippetName, { exact: true }).count()) > 0) {
await deleteSqlSnippet(page, ref, sqlSnippetName)
}
} else {
console.log('This test does not work in self-hosted environments.')
return
test.skip(isCLI(), 'This test does not work in self-hosted environments.')
// clean up 'Untitled query', 'pw_sql_snippet' and 'pw_sql_snippet (Duplicate)' snippets if exists
await waitForApiResponseWithTimeout(
page,
(response) => response.url().includes('query?key=table-columns'),
3000
)
const privateSnippet = page.getByLabel('private-snippets')
if ((await privateSnippet.getByText(newSqlSnippetName).count()) > 0) {
deleteSqlSnippet(page, ref, newSqlSnippetName)
}
if ((await privateSnippet.getByText(sqlSnippetNameDuplicate, { exact: true }).count()) > 0) {
await deleteSqlSnippet(page, ref, sqlSnippetNameDuplicate)
}
if ((await privateSnippet.getByText(sqlSnippetName, { exact: true }).count()) > 0) {
await deleteSqlSnippet(page, ref, sqlSnippetName)
}
// create sql snippet
+2 -2
View File
@@ -30,8 +30,8 @@
"test:studio": "turbo run test --filter=studio",
"test:studio:watch": "turbo run test --filter=studio -- watch",
"e2e:setup:cli": "supabase stop --all --no-backup ; supabase start --exclude studio && supabase db reset && supabase status --output json > keys.json && node scripts/generateLocalEnv.js",
"e2e:setup:selfhosted": "SKIP_ASSET_UPLOAD=1 pnpm e2e:setup:cli && NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=4096\" pnpm run build:studio && NODE_ENV=test pnpm --prefix ./apps/studio start --port 8082",
"e2e:setup:platform": "SKIP_ASSET_UPLOAD=1 NODE_OPTIONS=\"--max-old-space-size=4096\" pnpm run build:studio && pnpm --prefix ./apps/studio start --port 8082",
"e2e:setup:selfhosted": "SKIP_ASSET_UPLOAD=1 pnpm e2e:setup:cli && NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=4096\" pnpm run build:studio && NODE_ENV=test pnpm --prefix ./apps/studio start",
"e2e:setup:platform": "SKIP_ASSET_UPLOAD=1 NODE_OPTIONS=\"--max-old-space-size=4096\" pnpm run build:studio && pnpm --prefix ./apps/studio start",
"e2e": "pnpm --prefix e2e/studio run e2e",
"e2e:ui": "pnpm --prefix e2e/studio run e2e:ui",
"perf:kong": "ab -t 5 -c 20 -T application/json http://localhost:8000/",