Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ hideToc: true
<$CodeTabs>

```text name=.env
PUBLIC_VITE_SUPABASE_URL=<SUBSTITUTE_SUPABASE_URL>
PUBLIC_VITE_SUPABASE_PUBLISHABLE_KEY=<SUBSTITUTE_SUPABASE_PUBLISHABLE_KEY>
PUBLIC_SUPABASE_URL=<SUBSTITUTE_SUPABASE_URL>
PUBLIC_SUPABASE_PUBLISHABLE_KEY=<SUBSTITUTE_SUPABASE_PUBLISHABLE_KEY>
```

</$CodeTabs>
Expand All @@ -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>
Expand Down
Original file line number Diff line number Diff line change
@@ -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 = () => {
Expand All @@ -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 (
<Panel
className="mb-4 md:mb-8"
Expand Down Expand Up @@ -63,9 +95,37 @@ export const AccountConnections = () => {
</p>
</div>
</div>
<div className="flex items-center gap-x-1">
<div className="flex items-center gap-x-2 ml-2">
{isConnected ? (
<Badge variant="success">Connected</Badge>
<>
<Badge variant="success">Connected</Badge>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button iconRight={<ChevronDown size={14} />} type="default">
<span>Manage</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent side="bottom" align="end">
<DropdownMenuItem
className="space-x-2"
onSelect={(event) => {
event.preventDefault()
handleReauthenticate()
}}
>
<RefreshCw size={14} />
<p>Re-authenticate</p>
</DropdownMenuItem>
<DropdownMenuItem
className="space-x-2"
onSelect={() => setIsRemoveModalOpen(true)}
>
<Unlink size={14} />
<p>Remove connection</p>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</>
) : (
<Button type="primary" onClick={handleConnect}>
Connect
Expand All @@ -74,6 +134,21 @@ export const AccountConnections = () => {
</div>
</Panel.Content>
)}
<ConfirmationModal
variant="destructive"
size="small"
visible={isRemoveModalOpen}
title="Confirm to remove GitHub authorization"
confirmLabel="Remove connection"
onCancel={() => setIsRemoveModalOpen(false)}
onConfirm={handleRemove}
loading={isRemoving}
>
<p className="text-sm text-foreground-light">
Removing this authorization will disconnect your GitHub account from Supabase. You can
reconnect at any time.
</p>
</ConfirmationModal>
</Panel>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,7 @@ export function LogDrainDestinationSheetForm({
<span>See full pricing breakdown</span>{' '}
<Link
href={`${DOCS_URL}/guides/platform/manage-your-usage/log-drains`}
target="_blank"
className="text-foreground underline underline-offset-2 decoration-foreground-muted hover:decoration-foreground transition-all"
>
here
Expand Down
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -33,6 +33,7 @@ import {
CommandInput_Shadcn_,
CommandItem_Shadcn_,
CommandList_Shadcn_,
CommandSeparator_Shadcn_,
Form_Shadcn_,
FormControl_Shadcn_,
FormField_Shadcn_,
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -474,30 +477,32 @@ const GitHubIntegrationConnectionForm = ({
<CommandInput_Shadcn_ placeholder="Search repositories..." />
<CommandList_Shadcn_ className="!max-h-[200px]">
<CommandEmpty_Shadcn_>No repositories found.</CommandEmpty_Shadcn_>
<CommandGroup_Shadcn_>
{githubRepos.map((repo, i) => (
<CommandItem_Shadcn_
key={repo.id}
value={`${repo.name.replaceAll('"', '')}-${i}`}
className="flex gap-2 items-center"
onSelect={() => {
field.onChange(repo.id)
setRepoComboboxOpen(false)
githubSettingsForm.setValue(
'branchName',
repo.default_branch || 'main'
)
}}
>
<div className="bg-black shadow rounded p-1 w-5 h-5 flex justify-center items-center">
{GITHUB_ICON}
</div>
<span className="truncate" title={repo.name}>
{repo.name}
</span>
</CommandItem_Shadcn_>
))}
</CommandGroup_Shadcn_>
{githubRepos.length > 0 ? (
<CommandGroup_Shadcn_>
{githubRepos.map((repo, i) => (
<CommandItem_Shadcn_
key={repo.id}
value={`${repo.name.replaceAll('"', '')}-${i}`}
className="flex gap-2 items-center"
onSelect={() => {
field.onChange(repo.id)
setRepoComboboxOpen(false)
githubSettingsForm.setValue(
'branchName',
repo.default_branch || 'main'
)
}}
>
<div className="bg-black shadow rounded p-1 w-5 h-5 flex justify-center items-center">
{GITHUB_ICON}
</div>
<span className="truncate" title={repo.name}>
{repo.name}
</span>
</CommandItem_Shadcn_>
))}
</CommandGroup_Shadcn_>
) : null}
<CommandGroup_Shadcn_>
<CommandItem_Shadcn_
className="flex gap-2 items-center cursor-pointer"
Expand All @@ -512,6 +517,27 @@ const GitHubIntegrationConnectionForm = ({
Add GitHub Repositories
</CommandItem_Shadcn_>
</CommandGroup_Shadcn_>
{hasPartialResponseDueToSSO && (
<>
<CommandSeparator_Shadcn_ />
<CommandGroup_Shadcn_>
<CommandItem_Shadcn_
className="flex gap-2 items-start cursor-pointer"
onSelect={() => {
openInstallGitHubIntegrationWindow(
'authorize',
refetchGitHubAuthorizationAndRepositories
)
}}
>
<RefreshCw size={16} className="mt-0.5 shrink-0" />
<div className="text-xs text-foreground-light">
Re-authorize GitHub with SSO to show all repositories
</div>
</CommandItem_Shadcn_>
</CommandGroup_Shadcn_>
</>
)}
</CommandList_Shadcn_>
</Command_Shadcn_>
</PopoverContent_Shadcn_>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand Down
13 changes: 13 additions & 0 deletions apps/studio/components/interfaces/Settings/Logs/Logs.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 13 additions & 4 deletions apps/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -206,15 +207,20 @@ 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 || '')
setSelectedLogId(null)
} 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 {
Expand All @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,6 @@ const LogsQueryPanel = ({
hideFooter
triggerElement={
<Button
asChild // ?: we don't want a button inside a button
type="text"
onClick={() => setShowReference(true)}
icon={<BookOpen />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,12 @@ const PreviewFilterPanel = ({
return (
<div
className={cn(
'flex w-full items-center justify-between',
'flex w-full items-center justify-between overflow-x-scroll no-scrollbar',
condensedLayout ? ' p-3' : '',
className
)}
>
<div className="flex flex-row items-center gap-x-2">
<div className="flex flex-row items-center gap-x-2 mr-2">
<form
id="log-panel-search"
onSubmit={(e) => {
Expand Down
Loading
Loading