Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions apps/docs/content/guides/local-development.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ subtitle: Learn how to develop locally and use the Supabase CLI

Develop locally while running the Supabase stack on your machine.

<Admonition type="note">

As a prerequisite, you must install a container runtime compatible with Docker APIs.

- [Docker Desktop](https://docs.docker.com/desktop/) (macOS, Windows, Linux)
- [Rancher Desktop](https://rancherdesktop.io/) (macOS, Windows, Linux)
- [Podman](https://podman.io/) (macOS, Windows, Linux)
- [OrbStack](https://orbstack.dev/) (macOS)

</Admonition>

## Quickstart

1. Install the Supabase CLI:
Expand Down Expand Up @@ -101,17 +112,6 @@ Develop locally while running the Supabase stack on your machine.

</Tabs>

<Admonition type="note">

As a prerequisite, you must install a container runtime compatible with Docker APIs.

- [Docker Desktop](https://docs.docker.com/desktop/) (macOS, Windows, Linux)
- [Rancher Desktop](https://rancherdesktop.io/) (macOS, Windows, Linux)
- [Podman](https://podman.io/) (macOS, Windows, Linux)
- [OrbStack](https://orbstack.dev/) (macOS)

</Admonition>

4. View your local Supabase instance at [http://localhost:54323](http://localhost:54323).

## Local development
Expand Down
31 changes: 19 additions & 12 deletions apps/studio/components/interfaces/Reports/ReportChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,19 @@
* This component acts as a bridge between the data-fetching logic and the
* presentational chart component.
*/
import { useFillTimeseriesSorted } from 'hooks/analytics/useFillTimeseriesSorted'

import Link from 'next/link'
import { useRef, useState } from 'react'

import type { MultiAttribute } from 'components/ui/Charts/ComposedChart.utils'
import LogChartHandler from 'components/ui/Charts/LogChartHandler'
import Panel from 'components/ui/Panel'
import { useFillTimeseriesSorted } from 'hooks/analytics/useFillTimeseriesSorted'
import { useCurrentOrgPlan } from 'hooks/misc/useCurrentOrgPlan'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { useChartData } from 'hooks/useChartData'
import type { UpdateDateRange } from 'pages/project/[ref]/reports/database'
import type { MultiAttribute } from 'components/ui/Charts/ComposedChart.utils'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import Link from 'next/link'
import { Button, cn } from 'ui'
import Panel from 'components/ui/Panel'
import { useRef, useState } from 'react'

const ReportChart = ({
chart,
Expand All @@ -25,9 +28,7 @@ const ReportChart = ({
interval,
updateDateRange,
functionIds,
orgPlanId,
isLoading,
availableIn,
className,
}: {
chart: any
Expand All @@ -36,15 +37,17 @@ const ReportChart = ({
interval: string
updateDateRange: UpdateDateRange
functionIds?: string[]
orgPlanId?: string
isLoading?: boolean
availableIn?: string[]
className?: string
}) => {
const org = useSelectedOrganization()
const { plan: orgPlan } = useCurrentOrgPlan()
const orgPlanId = orgPlan?.id

const [isHoveringUpgrade, setIsHoveringUpgrade] = useState(false)
const isAvailable =
chart.availableIn === undefined || (orgPlanId && chart.availableIn.includes(orgPlanId))

const canFetch = orgPlanId !== undefined
const {
data,
Expand Down Expand Up @@ -114,7 +117,9 @@ const ReportChart = ({
<h2 className="">{chart.label}</h2>
<p className="text-sm text-foreground-light">
This chart is available from{' '}
<span className="capitalize">{!!availableIn?.length ? availableIn[0] : 'Pro'}</span>{' '}
<span className="capitalize">
{!!chart.availableIn?.length ? chart.availableIn[0] : 'Pro'}
</span>{' '}
plan and above
</p>
<Button
Expand All @@ -125,7 +130,9 @@ const ReportChart = ({
>
<Link href={`/org/${org?.slug}/billing?panel=subscriptionPlan&source=reports`}>
Upgrade to{' '}
<span className="capitalize">{!!availableIn?.length ? availableIn[0] : 'Pro'}</span>
<span className="capitalize">
{!!chart.availableIn?.length ? chart.availableIn[0] : 'Pro'}
</span>
</Link>
</Button>
</div>
Expand Down
5 changes: 4 additions & 1 deletion apps/studio/components/layouts/DefaultLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { useRouter } from 'next/router'
import { PropsWithChildren } from 'react'

import { useParams } from 'common'
import { AppBannerWrapper } from 'components/interfaces/App'
import { AppBannerContextProvider } from 'components/interfaces/App/AppBannerWrapperContext'
import { Sidebar } from 'components/interfaces/Sidebar'
import { useRouter } from 'next/router'
import { useCheckLatestDeploy } from 'hooks/use-check-latest-deploy'
import { SidebarProvider } from 'ui'
import { LayoutHeader } from './ProjectLayout/LayoutHeader'
import MobileNavigationBar from './ProjectLayout/NavigationBar/MobileNavigationBar'
Expand All @@ -29,6 +30,8 @@ const DefaultLayout = ({ children, headerTitle }: PropsWithChildren<DefaultLayou
const router = useRouter()
const showProductMenu = !!ref && router.pathname !== '/project/[ref]'

useCheckLatestDeploy()

return (
<SidebarProvider defaultOpen={false}>
<ProjectContextProvider projectRef={ref}>
Expand Down
2 changes: 1 addition & 1 deletion apps/studio/data/reports/auth-charts.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const getAuthReportAttributes = (isFreePlan: boolean) => [
export const getAuthReportAttributes = () => [
{
id: 'active-users',
label: 'Active Users',
Expand Down
2 changes: 1 addition & 1 deletion apps/studio/data/reports/database-charts.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { numberFormatter } from 'components/ui/Charts/Charts.utils'
import { ReportAttributes } from 'components/ui/Charts/ComposedChart.utils'
import { formatBytes } from 'lib/helpers'
import { Organization } from 'types'
import { Project } from '../projects/project-detail-query'
import { ReportAttributes } from 'components/ui/Charts/ComposedChart.utils'

export const getReportAttributes = (org: Organization, project: Project): ReportAttributes[] => {
const computeSize = project?.infra_compute_size || 'medium'
Expand Down
6 changes: 3 additions & 3 deletions apps/studio/data/reports/edgefn-charts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const getEdgeFunctionReportAttributes = (): ReportAttributes[] => [
hideChartType: false,
defaultChartStyle: 'bar',
titleTooltip: 'The total number of edge function executions by status code.',
availableIn: ['free', 'pro', 'team'],
availableIn: ['free', 'pro', 'team', 'enterprise'],
attributes: [
{
attribute: 'ExecutionStatusCodes',
Expand All @@ -32,7 +32,7 @@ export const getEdgeFunctionReportAttributes = (): ReportAttributes[] => [
hideChartType: false,
defaultChartStyle: 'line',
titleTooltip: 'Average execution time for edge functions.',
availableIn: ['free', 'pro', 'team'],
availableIn: ['free', 'pro', 'team', 'enterprise'],
format: 'ms',
YAxisProps: {
width: 50,
Expand All @@ -58,7 +58,7 @@ export const getEdgeFunctionReportAttributes = (): ReportAttributes[] => [
hideChartType: false,
defaultChartStyle: 'bar',
titleTooltip: 'The total number of edge function invocations by region.',
availableIn: ['pro', 'team'],
availableIn: ['pro', 'team', 'enterprise'],
attributes: [
{
attribute: 'InvocationsByRegion',
Expand Down
23 changes: 23 additions & 0 deletions apps/studio/data/utils/deployment-commit-query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { fetchHandler } from 'data/fetchers'
import { BASE_PATH } from 'lib/constants'
import { ResponseError } from 'types'

export async function getDeploymentCommit(signal?: AbortSignal) {
const response = await fetchHandler(`${BASE_PATH}/api/get-deployment-commit`)
return (await response.json()) as { commitSha: string; commitTime: string }
}

export type DeploymentCommitData = Awaited<ReturnType<typeof getDeploymentCommit>>

export const useDeploymentCommitQuery = <TData = DeploymentCommitData>({
enabled = true,
...options
}: UseQueryOptions<DeploymentCommitData, ResponseError, TData> = {}) =>
useQuery<DeploymentCommitData, ResponseError, TData>(
['deployment-commit'],
({ signal }) => getDeploymentCommit(signal),
{
...options,
}
)
83 changes: 83 additions & 0 deletions apps/studio/hooks/use-check-latest-deploy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import dayjs from 'dayjs'
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
import { toast } from 'sonner'

import { IS_PLATFORM } from 'common'
import { useDeploymentCommitQuery } from 'data/utils/deployment-commit-query'
import { Button, StatusIcon } from 'ui'
import { useFlag } from './ui/useFlag'

const DeployCheckToast = ({ id }: { id: string | number }) => {
const router = useRouter()

return (
<div className="flex gap-3 flex-col w-full">
<div className="flex gap-3 flex-row">
<StatusIcon variant="default" className="mt-0.5" />
<div className="flex w-full justify-between flex-col text-sm">
<p className="text-foreground">A new version of this page is available</p>
<p className="text-foreground-light">Refresh to see the latest changes.</p>
</div>
</div>

<div className="flex gap-5 justify-end">
<Button type="outline" onClick={() => toast.dismiss(id)}>
Not now
</Button>
<Button onClick={() => router.reload()}>Refresh</Button>
</div>
</div>
)
}

// This hook checks if the user is using old Studio pages and shows a toast to refresh the page. It's only triggered if
// there's a new version of Studio is available, and the user has been on the old dashboard (based on commit) for more than 24 hours.
// [Joshen] K-Dog has a suggestion here to bring down the time period here by checking commits
export function useCheckLatestDeploy() {
const showRefreshToast = useFlag('showRefreshToast')

const [currentCommitTime, setCurrentCommitTime] = useState('')
const [isToastShown, setIsToastShown] = useState(false)

const { data: commit } = useDeploymentCommitQuery({
enabled: IS_PLATFORM && showRefreshToast,
staleTime: 10000, // 10 seconds
})

useEffect(() => {
// if the fetched commit is undefined is undefined
if (!commit || commit.commitTime === 'unknown') {
return
}

// set the current commit on first load
if (!currentCommitTime) {
setCurrentCommitTime(commit.commitTime)
return
}

// if the current commit is the same as the fetched commit, do nothing
if (currentCommitTime === commit.commitTime) {
return
}

// prevent showing the toast again if user has already seen and dismissed it
if (isToastShown) {
return
}

// check if the time difference between commits is more than 24 hours
const hourDiff = dayjs(commit.commitTime).diff(dayjs(currentCommitTime), 'hour')
if (hourDiff < 24) {
return
}

// show the toast
toast.custom((id) => <DeployCheckToast id={id} />, {
duration: Infinity,
position: 'bottom-right',
})
setIsToastShown(true)
}, [commit, isToastShown, currentCommitTime])
}
1 change: 1 addition & 0 deletions apps/studio/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const HOSTED_SUPPORTED_API_URLS = [
'/ai/feedback/classify',
'/get-ip-address',
'/get-utc-time',
'/get-deployment-commit',
'/check-cname',
'/edge-functions/test',
'/edge-functions/body',
Expand Down
40 changes: 40 additions & 0 deletions apps/studio/pages/api/get-deployment-commit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { NextApiRequest, NextApiResponse } from 'next'

async function getCommitTime(commitSha: string) {
try {
const response = await fetch(`https://github.com/supabase/supabase/commit/${commitSha}.json`, {
headers: {
Accept: 'application/json',
},
})

if (!response.ok) {
throw new Error('Failed to fetch commit details')
}

const data = await response.json()
return new Date(data.payload.commit.committedDate).toISOString()
} catch (error) {
console.error('Error fetching commit time:', error)
return 'unknown'
}
}

export default async function handler(
req: NextApiRequest,
res: NextApiResponse<{ commitSha: string; commitTime: string }>
) {
// Set cache control headers for 10 minutes so that we don't get banned by Github API
res.setHeader('Cache-Control', 's-maxage=600')

// Get the build commit SHA from Vercel environment variable
const commitSha = process.env.VERCEL_GIT_COMMIT_SHA || 'development'

// Only fetch commit time if we have a valid SHA
const commitTime = commitSha !== 'development' ? await getCommitTime(commitSha) : 'unknown'

res.status(200).json({
commitSha,
commitTime,
})
}
26 changes: 11 additions & 15 deletions apps/studio/pages/project/[ref]/reports/auth.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
import { useState } from 'react'
import { useQueryClient } from '@tanstack/react-query'
import { useParams } from 'common'
import dayjs from 'dayjs'
import { ArrowRight, RefreshCw } from 'lucide-react'
import { useParams } from 'common'
import { useState } from 'react'

import ReportChart from 'components/interfaces/Reports/ReportChart'
import ReportHeader from 'components/interfaces/Reports/ReportHeader'
import ReportPadding from 'components/interfaces/Reports/ReportPadding'
import ReportStickyNav from 'components/interfaces/Reports/ReportStickyNav'
import { LogsDatePicker } from 'components/interfaces/Settings/Logs/Logs.DatePickers'
import DefaultLayout from 'components/layouts/DefaultLayout'
import ReportsLayout from 'components/layouts/ReportsLayout/ReportsLayout'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { LogsDatePicker } from 'components/interfaces/Settings/Logs/Logs.DatePickers'
import ReportChart from 'components/interfaces/Reports/ReportChart'
import ReportStickyNav from 'components/interfaces/Reports/ReportStickyNav'

import { getAuthReportAttributes } from 'data/reports/auth-charts'
import { useReportDateRange } from 'hooks/misc/useReportDateRange'
import ReportFilterBar from 'components/interfaces/Reports/ReportFilterBar'
import { REPORT_DATERANGE_HELPER_LABELS } from 'components/interfaces/Reports/Reports.constants'
import UpgradePrompt from 'components/interfaces/Settings/Logs/UpgradePrompt'
import type { NextPageWithLayout } from 'types'
import { SharedAPIReport } from 'components/interfaces/Reports/SharedAPIReport/SharedAPIReport'
import { useSharedAPIReport } from 'components/interfaces/Reports/SharedAPIReport/SharedAPIReport.constants'
import ReportFilterBar from 'components/interfaces/Reports/ReportFilterBar'
import UpgradePrompt from 'components/interfaces/Settings/Logs/UpgradePrompt'
import { getAuthReportAttributes } from 'data/reports/auth-charts'
import { useReportDateRange } from 'hooks/misc/useReportDateRange'
import type { NextPageWithLayout } from 'types'

const AuthReport: NextPageWithLayout = () => {
return (
Expand All @@ -47,8 +47,6 @@ const AuthUsage = () => {
updateDateRange,
datePickerValue,
datePickerHelpers,
isOrgPlanLoading,
orgPlan,
showUpgradePrompt,
setShowUpgradePrompt,
handleDatePickerChange,
Expand All @@ -73,8 +71,7 @@ const AuthUsage = () => {
const queryClient = useQueryClient()
const [isRefreshing, setIsRefreshing] = useState(false)

const isFreePlan = !isOrgPlanLoading && orgPlan?.id === 'free'
const AUTH_REPORT_ATTRIBUTES = getAuthReportAttributes(isFreePlan)
const AUTH_REPORT_ATTRIBUTES = getAuthReportAttributes()

const onRefreshReport = async () => {
if (!selectedDateRange) return
Expand Down Expand Up @@ -142,7 +139,6 @@ const AuthUsage = () => {
startDate={selectedDateRange?.period_start?.date}
endDate={selectedDateRange?.period_end?.date}
updateDateRange={updateDateRange}
orgPlanId={orgPlan?.id}
isLoading={isRefreshing}
/>
))}
Expand Down
Loading
Loading