mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 01:15:03 +03:00
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:
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) => {
|
||||
|
||||
+28
-23
@@ -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()
|
||||
|
||||
|
||||
@@ -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`)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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/",
|
||||
|
||||
Reference in new issue
Block a user