diff --git a/apps/docs/content/guides/getting-started/quickstarts/sveltekit.mdx b/apps/docs/content/guides/getting-started/quickstarts/sveltekit.mdx
index f22ecc0563408..5e475977a6bd5 100644
--- a/apps/docs/content/guides/getting-started/quickstarts/sveltekit.mdx
+++ b/apps/docs/content/guides/getting-started/quickstarts/sveltekit.mdx
@@ -67,8 +67,8 @@ hideToc: true
<$CodeTabs>
```text name=.env
- PUBLIC_VITE_SUPABASE_URL=
- PUBLIC_VITE_SUPABASE_PUBLISHABLE_KEY=
+ PUBLIC_SUPABASE_URL=
+ PUBLIC_SUPABASE_PUBLISHABLE_KEY=
```
$CodeTabs>
@@ -89,17 +89,17 @@ hideToc: true
<$CodeTabs>
```js name=src/lib/supabaseClient.js
- import { createClient } from '@supabase/supabase-js';
- import { PUBLIC_VITE_SUPABASE_URL, PUBLIC_VITE_SUPABASE_PUBLISHABLE_KEY } from '$env/static/public';
+ import { createClient } from '@supabase/supabase-js';
+ import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY } from '$env/static/public';
- export const supabase = createClient(PUBLIC_VITE_SUPABASE_URL, PUBLIC_VITE_SUPABASE_PUBLISHABLE_KEY)
+ export const supabase = createClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY)
```
```ts name=src/lib/supabaseClient.ts
- import { createClient } from '@supabase/supabase-js';
- import { PUBLIC_VITE_SUPABASE_URL, PUBLIC_VITE_SUPABASE_PUBLISHABLE_KEY } from '$env/static/public';
+ import { createClient } from '@supabase/supabase-js';
+ import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY } from '$env/static/public';
- export const supabase = createClient(PUBLIC_VITE_SUPABASE_URL, PUBLIC_VITE_SUPABASE_PUBLISHABLE_KEY)
+ export const supabase = createClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY)
```
$CodeTabs>
diff --git a/apps/studio/components/interfaces/Account/Preferences/AccountConnections.tsx b/apps/studio/components/interfaces/Account/Preferences/AccountConnections.tsx
index a8907fa8b35fc..5d7d40521254d 100644
--- a/apps/studio/components/interfaces/Account/Preferences/AccountConnections.tsx
+++ b/apps/studio/components/interfaces/Account/Preferences/AccountConnections.tsx
@@ -1,10 +1,24 @@
+import { ChevronDown, RefreshCw, Unlink } from 'lucide-react'
import Image from 'next/image'
+import { useState } from 'react'
+import { toast } from 'sonner'
import Panel from 'components/ui/Panel'
+import { useGitHubAuthorizationDeleteMutation } from 'data/integrations/github-authorization-delete-mutation'
import { useGitHubAuthorizationQuery } from 'data/integrations/github-authorization-query'
import { BASE_PATH } from 'lib/constants'
import { openInstallGitHubIntegrationWindow } from 'lib/github'
-import { Badge, Button, cn } from 'ui'
+import {
+ Badge,
+ Button,
+ cn,
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from 'ui'
+import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
import ShimmeringLoader from 'ui-patterns/ShimmeringLoader'
export const AccountConnections = () => {
@@ -16,12 +30,30 @@ export const AccountConnections = () => {
error,
} = useGitHubAuthorizationQuery()
+ const [isRemoveModalOpen, setIsRemoveModalOpen] = useState(false)
+
const isConnected = gitHubAuthorization !== null
+ const { mutate: removeAuthorization, isLoading: isRemoving } =
+ useGitHubAuthorizationDeleteMutation({
+ onSuccess: () => {
+ toast.success('GitHub authorization removed successfully')
+ setIsRemoveModalOpen(false)
+ },
+ })
+
const handleConnect = () => {
openInstallGitHubIntegrationWindow('authorize')
}
+ const handleReauthenticate = () => {
+ openInstallGitHubIntegrationWindow('authorize')
+ }
+
+ const handleRemove = () => {
+ removeAuthorization()
+ }
+
return (
{
-
+
{isConnected ? (
-
Connected
+ <>
+
Connected
+
+
+ } type="default">
+ Manage
+
+
+
+ {
+ event.preventDefault()
+ handleReauthenticate()
+ }}
+ >
+
+ Re-authenticate
+
+ setIsRemoveModalOpen(true)}
+ >
+
+ Remove connection
+
+
+
+ >
) : (
)}
+
setIsRemoveModalOpen(false)}
+ onConfirm={handleRemove}
+ loading={isRemoving}
+ >
+
+ Removing this authorization will disconnect your GitHub account from Supabase. You can
+ reconnect at any time.
+
+
)
}
diff --git a/apps/studio/components/interfaces/LogDrains/LogDrainDestinationSheetForm.tsx b/apps/studio/components/interfaces/LogDrains/LogDrainDestinationSheetForm.tsx
index e6bcaafa92e78..b9c930d20ed92 100644
--- a/apps/studio/components/interfaces/LogDrains/LogDrainDestinationSheetForm.tsx
+++ b/apps/studio/components/interfaces/LogDrains/LogDrainDestinationSheetForm.tsx
@@ -612,6 +612,7 @@ export function LogDrainDestinationSheetForm({
See full pricing breakdown{' '}
here
diff --git a/apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GitHubIntegrationConnectionForm.tsx b/apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GitHubIntegrationConnectionForm.tsx
index 252894e8bbec0..28b64e069304f 100644
--- a/apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GitHubIntegrationConnectionForm.tsx
+++ b/apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GitHubIntegrationConnectionForm.tsx
@@ -1,6 +1,6 @@
import { zodResolver } from '@hookform/resolvers/zod'
import { PermissionAction } from '@supabase/shared-types/out/constants'
-import { ChevronDown, Loader2, PlusIcon } from 'lucide-react'
+import { ChevronDown, Info, Loader2, PlusIcon, RefreshCw } from 'lucide-react'
import { useEffect, useMemo, useState } from 'react'
import { useForm } from 'react-hook-form'
import { toast } from 'sonner'
@@ -33,6 +33,7 @@ import {
CommandInput_Shadcn_,
CommandItem_Shadcn_,
CommandList_Shadcn_,
+ CommandSeparator_Shadcn_,
Form_Shadcn_,
FormControl_Shadcn_,
FormField_Shadcn_,
@@ -141,7 +142,7 @@ const GitHubIntegrationConnectionForm = ({
const githubRepos = useMemo(
() =>
- githubReposData?.map((repo) => ({
+ githubReposData?.repositories?.map((repo) => ({
id: repo.id.toString(),
name: repo.name,
installation_id: repo.installation_id,
@@ -150,6 +151,8 @@ const GitHubIntegrationConnectionForm = ({
[githubReposData]
)
+ const hasPartialResponseDueToSSO = githubReposData?.partial_response_due_to_sso ?? false
+
const prodBranch = existingBranches?.find((branch) => branch.is_default)
// Combined GitHub Settings Form
@@ -474,30 +477,32 @@ const GitHubIntegrationConnectionForm = ({
No repositories found.
-
- {githubRepos.map((repo, i) => (
- {
- field.onChange(repo.id)
- setRepoComboboxOpen(false)
- githubSettingsForm.setValue(
- 'branchName',
- repo.default_branch || 'main'
- )
- }}
- >
-
- {GITHUB_ICON}
-
-
- {repo.name}
-
-
- ))}
-
+ {githubRepos.length > 0 ? (
+
+ {githubRepos.map((repo, i) => (
+ {
+ field.onChange(repo.id)
+ setRepoComboboxOpen(false)
+ githubSettingsForm.setValue(
+ 'branchName',
+ repo.default_branch || 'main'
+ )
+ }}
+ >
+
+ {GITHUB_ICON}
+
+
+ {repo.name}
+
+
+ ))}
+
+ ) : null}
+ {hasPartialResponseDueToSSO && (
+ <>
+
+
+ {
+ openInstallGitHubIntegrationWindow(
+ 'authorize',
+ refetchGitHubAuthorizationAndRepositories
+ )
+ }}
+ >
+
+
+ Re-authorize GitHub with SSO to show all repositories
+
+
+
+ >
+ )}
diff --git a/apps/studio/components/interfaces/Settings/Logs/Logs.DatePickers.tsx b/apps/studio/components/interfaces/Settings/Logs/Logs.DatePickers.tsx
index 4d5083d4c8e6e..702e01e77e678 100644
--- a/apps/studio/components/interfaces/Settings/Logs/Logs.DatePickers.tsx
+++ b/apps/studio/components/interfaces/Settings/Logs/Logs.DatePickers.tsx
@@ -21,6 +21,7 @@ import {
} from 'ui'
import { LOGS_LARGE_DATE_RANGE_DAYS_THRESHOLD } from './Logs.constants'
import type { DatetimeHelper } from './Logs.types'
+import { useCheckEntitlements } from 'hooks/misc/useCheckEntitlements'
export type DatePickerValue = {
to: string
@@ -237,13 +238,16 @@ export const LogsDatePicker = ({
Math.abs(dayjs(startDate).diff(dayjs(endDate), 'days')) >
LOGS_LARGE_DATE_RANGE_DAYS_THRESHOLD - 1
- const { plan: orgPlan, isLoading: isOrgPlanLoading } = useCurrentOrgPlan()
+ const { getEntitlementNumericValue } = useCheckEntitlements('log.retention_days')
+ const entitledToAuditLogDays = getEntitlementNumericValue()
+
const showHelperBadge = (helper?: DatetimeHelper) => {
if (!helper) return false
if (!helper.availableIn?.length) return false
+ if (!entitledToAuditLogDays) return false
- if (helper.availableIn.includes('free')) return false
- if (helper.availableIn.includes(orgPlan?.id || 'free') && !isOrgPlanLoading) return false
+ const day = Math.abs(dayjs().diff(dayjs(helper.calcFrom()), 'day'))
+ if (day <= entitledToAuditLogDays) return false
return true
}
diff --git a/apps/studio/components/interfaces/Settings/Logs/Logs.utils.ts b/apps/studio/components/interfaces/Settings/Logs/Logs.utils.ts
index 279a148a57732..e21744f60f707 100644
--- a/apps/studio/components/interfaces/Settings/Logs/Logs.utils.ts
+++ b/apps/studio/components/interfaces/Settings/Logs/Logs.utils.ts
@@ -291,6 +291,19 @@ export const maybeShowUpgradePrompt = (from: string | null | undefined, planId?:
)
}
+/**
+ * Determine if we should show the user an upgrade prompt while browsing logs
+ * This method should replace maybeShowUpgradePrompt once we have migrated all usage to the Entitlements API.
+ */
+export const maybeShowUpgradePromptIfNotEntitled = (
+ from: string | null | undefined,
+ entitledToDays: number | undefined
+) => {
+ if (!entitledToDays) return false
+ const day = Math.abs(dayjs().diff(dayjs(from), 'day'))
+ return day > entitledToDays
+}
+
export const genCountQuery = (table: LogsTableName, filters: Filters): string => {
let where = genWhereStatement(table, filters)
// pg_cron logs are a subset of postgres logs
diff --git a/apps/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx b/apps/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx
index 39dcd1c83d1c7..a35bf61a4f8b8 100644
--- a/apps/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx
+++ b/apps/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx
@@ -26,9 +26,10 @@ import {
PREVIEWER_DATEPICKER_HELPERS,
} from './Logs.constants'
import type { Filters, LogSearchCallback, LogTemplate, QueryType } from './Logs.types'
-import { maybeShowUpgradePrompt } from './Logs.utils'
+import { maybeShowUpgradePromptIfNotEntitled } from './Logs.utils'
import { PreviewFilterPanelWithUniversal } from './PreviewFilterPanelWithUniversal'
import UpgradePrompt from './UpgradePrompt'
+import { useCheckEntitlements } from 'hooks/misc/useCheckEntitlements'
/**
* Calculates the appropriate time range for bar click filtering based on the current time range duration.
@@ -206,6 +207,9 @@ export const LogsPreviewer = ({
refresh()
}
+ const { getEntitlementNumericValue } = useCheckEntitlements('log.retention_days')
+ const entitledToAuditLogDays = getEntitlementNumericValue()
+
const handleSearch: LogSearchCallback = async (event, { query, to, from }) => {
if (event === 'search-input-change') {
setSearch(query || '')
@@ -213,8 +217,10 @@ export const LogsPreviewer = ({
} else if (event === 'event-chart-bar-click') {
setTimeRange(from || '', to || '')
} else if (event === 'datepicker-change') {
- const shouldShowUpgradePrompt = maybeShowUpgradePrompt(from || '', organization?.plan?.id)
-
+ const shouldShowUpgradePrompt = maybeShowUpgradePromptIfNotEntitled(
+ from || '',
+ entitledToAuditLogDays
+ )
if (shouldShowUpgradePrompt) {
setShowUpgradePrompt(!showUpgradePrompt)
} else {
@@ -226,7 +232,10 @@ export const LogsPreviewer = ({
// Show the prompt on page load based on query params
useEffect(() => {
if (timestampStart) {
- const shouldShowUpgradePrompt = maybeShowUpgradePrompt(timestampStart, organization?.plan?.id)
+ const shouldShowUpgradePrompt = maybeShowUpgradePromptIfNotEntitled(
+ timestampStart,
+ entitledToAuditLogDays
+ )
if (shouldShowUpgradePrompt) {
setShowUpgradePrompt(!showUpgradePrompt)
}
diff --git a/apps/studio/components/interfaces/Settings/Logs/LogsQueryPanel.tsx b/apps/studio/components/interfaces/Settings/Logs/LogsQueryPanel.tsx
index 6106cf020814d..bb44df24c7402 100644
--- a/apps/studio/components/interfaces/Settings/Logs/LogsQueryPanel.tsx
+++ b/apps/studio/components/interfaces/Settings/Logs/LogsQueryPanel.tsx
@@ -206,7 +206,6 @@ const LogsQueryPanel = ({
hideFooter
triggerElement={