mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 01:45:10 +03:00
Refresh report and status update (#39473)
* refresh report * project status * prettier * Minor refactors * add back refreshing --------- Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
This commit is contained in:
1 parent
b36792aa49
commit
622486f750
3 files changed
+114
-28
No files matched your search
@@ -9,7 +9,7 @@ import {
|
||||
import { SortableContext, arrayMove, rectSortingStrategy, useSortable } from '@dnd-kit/sortable'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import dayjs from 'dayjs'
|
||||
import { Plus } from 'lucide-react'
|
||||
import { Plus, RefreshCw } from 'lucide-react'
|
||||
import type { CSSProperties, ReactNode } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
@@ -18,8 +18,10 @@ import { useParams } from 'common'
|
||||
import { SnippetDropdown } from 'components/interfaces/HomeNew/SnippetDropdown'
|
||||
import { ReportBlock } from 'components/interfaces/Reports/ReportBlock/ReportBlock'
|
||||
import type { ChartConfig } from 'components/interfaces/SQLEditor/UtilityPanel/ChartConfig'
|
||||
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
|
||||
import { DEFAULT_CHART_CONFIG } from 'components/ui/QueryBlock/QueryBlock'
|
||||
import { AnalyticsInterval } from 'data/analytics/constants'
|
||||
import { useInvalidateAnalyticsQuery } from 'data/analytics/utils'
|
||||
import { useContentInfiniteQuery } from 'data/content/content-infinite-query'
|
||||
import { Content } from 'data/content/content-query'
|
||||
import { useContentUpsertMutation } from 'data/content/content-upsert-mutation'
|
||||
@@ -28,6 +30,7 @@ import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions'
|
||||
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
|
||||
import { uuidv4 } from 'lib/helpers'
|
||||
import { useProfile } from 'lib/profile'
|
||||
import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
|
||||
import type { Dashboards } from 'types'
|
||||
import { Button } from 'ui'
|
||||
import { Row } from 'ui-patterns'
|
||||
@@ -35,10 +38,15 @@ import { Row } from 'ui-patterns'
|
||||
export function CustomReportSection() {
|
||||
const startDate = dayjs().subtract(7, 'day').toISOString()
|
||||
const endDate = dayjs().toISOString()
|
||||
|
||||
const { ref } = useParams()
|
||||
const { profile } = useProfile()
|
||||
const { mutate: sendEvent } = useSendEventMutation()
|
||||
const state = useDatabaseSelectorStateSnapshot()
|
||||
const { data: organization } = useSelectedOrganizationQuery()
|
||||
const { mutate: sendEvent } = useSendEventMutation()
|
||||
const { invalidateInfraMonitoringQuery } = useInvalidateAnalyticsQuery()
|
||||
|
||||
const [isRefreshing, setIsRefreshing] = useState<boolean>(false)
|
||||
|
||||
const { data: reportsData } = useContentInfiniteQuery(
|
||||
{ projectRef: ref, type: 'report', name: 'Home', limit: 1 },
|
||||
@@ -269,6 +277,25 @@ export function CustomReportSection() {
|
||||
persistReport(updated)
|
||||
}
|
||||
|
||||
const onRefreshReport = () => {
|
||||
if (!ref) return
|
||||
|
||||
setIsRefreshing(true)
|
||||
const monitoringCharts = editableReport?.layout.filter(
|
||||
(x) => x.provider === 'infra-monitoring' || x.provider === 'daily-stats'
|
||||
)
|
||||
monitoringCharts?.forEach((x) => {
|
||||
invalidateInfraMonitoringQuery(ref, {
|
||||
attribute: x.attribute,
|
||||
startDate,
|
||||
endDate,
|
||||
interval: editableReport?.interval || '1d',
|
||||
databaseIdentifier: state.selectedDatabaseId,
|
||||
})
|
||||
})
|
||||
setTimeout(() => setIsRefreshing(false), 1000)
|
||||
}
|
||||
|
||||
const layout = useMemo(() => editableReport?.layout ?? [], [editableReport])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -279,20 +306,32 @@ export function CustomReportSection() {
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="heading-section">Reports</h3>
|
||||
{canUpdateReport || canCreateReport ? (
|
||||
<SnippetDropdown
|
||||
projectRef={ref}
|
||||
onSelect={addSnippetToReport}
|
||||
trigger={
|
||||
<Button type="default" icon={<Plus />}>
|
||||
Add block
|
||||
</Button>
|
||||
}
|
||||
side="bottom"
|
||||
align="end"
|
||||
autoFocus
|
||||
/>
|
||||
) : null}
|
||||
<div className="flex items-center gap-x-2">
|
||||
{layout.length > 0 && (
|
||||
<ButtonTooltip
|
||||
type="default"
|
||||
icon={<RefreshCw className={isRefreshing ? 'animate-spin' : ''} />}
|
||||
className="w-7"
|
||||
disabled={isRefreshing}
|
||||
tooltip={{ content: { side: 'bottom', text: 'Refresh report' } }}
|
||||
onClick={onRefreshReport}
|
||||
/>
|
||||
)}
|
||||
{canUpdateReport || canCreateReport ? (
|
||||
<SnippetDropdown
|
||||
projectRef={ref}
|
||||
onSelect={addSnippetToReport}
|
||||
trigger={
|
||||
<Button type="default" icon={<Plus />}>
|
||||
Add block
|
||||
</Button>
|
||||
}
|
||||
side="bottom"
|
||||
align="end"
|
||||
autoFocus
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative">
|
||||
{layout.length === 0 ? (
|
||||
@@ -343,7 +382,7 @@ export function CustomReportSection() {
|
||||
('1d' as AnalyticsInterval)
|
||||
}
|
||||
disableUpdate={false}
|
||||
isRefreshing={false}
|
||||
isRefreshing={isRefreshing}
|
||||
onRemoveChart={handleRemoveChart}
|
||||
onUpdateChart={(config) => handleUpdateChart(item.id, config)}
|
||||
/>
|
||||
|
||||
@@ -37,16 +37,17 @@ const StatusMessage = ({
|
||||
status,
|
||||
isLoading,
|
||||
isHealthy,
|
||||
isProjectNew,
|
||||
}: {
|
||||
isLoading: boolean
|
||||
isHealthy: boolean
|
||||
isProjectNew: boolean
|
||||
status?: ProjectServiceStatus
|
||||
}) => {
|
||||
if (isHealthy) return 'Healthy'
|
||||
if (isHealthy || status === 'ACTIVE_HEALTHY') return 'Healthy'
|
||||
if (isLoading) return 'Checking status'
|
||||
if (status === 'UNHEALTHY') return 'Unhealthy'
|
||||
if (status === 'COMING_UP') return 'Coming up...'
|
||||
if (status === 'ACTIVE_HEALTHY') return 'Healthy'
|
||||
if (isProjectNew || status === 'COMING_UP') return 'Coming up...'
|
||||
if (status) return status
|
||||
return 'Unable to connect'
|
||||
}
|
||||
@@ -62,17 +63,16 @@ const CheckIcon = () => <CheckCircle2 {...iconProps} className="text-brand" />
|
||||
const StatusIcon = ({
|
||||
isLoading,
|
||||
isHealthy,
|
||||
isProjectNew,
|
||||
projectStatus,
|
||||
}: {
|
||||
isLoading: boolean
|
||||
isHealthy: boolean
|
||||
isProjectNew: boolean
|
||||
projectStatus?: ProjectServiceStatus
|
||||
}) => {
|
||||
if (isHealthy) return <CheckIcon />
|
||||
if (isLoading) return <LoaderIcon />
|
||||
if (projectStatus === 'UNHEALTHY') return <AlertIcon />
|
||||
if (projectStatus === 'COMING_UP') return <LoaderIcon />
|
||||
if (projectStatus === 'ACTIVE_HEALTHY') return <CheckIcon />
|
||||
if (isHealthy || projectStatus === 'ACTIVE_HEALTHY') return <CheckIcon />
|
||||
if (isLoading || isProjectNew || projectStatus === 'COMING_UP') return <LoaderIcon />
|
||||
return <AlertIcon />
|
||||
}
|
||||
|
||||
@@ -242,15 +242,23 @@ export const ServiceStatus = () => {
|
||||
const isLoadingChecks = services.some((service) => service.isLoading)
|
||||
const allServicesOperational = services.every((service) => service.isHealthy)
|
||||
|
||||
// Check if project or branch is in a startup state
|
||||
const isProjectNew =
|
||||
project?.status === 'COMING_UP' ||
|
||||
(isBranch &&
|
||||
(currentBranch?.status === 'CREATING_PROJECT' ||
|
||||
currentBranch?.status === 'RUNNING_MIGRATIONS' ||
|
||||
isMigrationLoading))
|
||||
|
||||
const anyUnhealthy = services.some(
|
||||
(service) => !service.isHealthy && service.status !== 'COMING_UP'
|
||||
)
|
||||
const anyComingUp = services.some((service) => service.status === 'COMING_UP')
|
||||
const overallStatusLabel = isLoadingChecks
|
||||
? 'Checking...'
|
||||
: anyUnhealthy
|
||||
: anyUnhealthy && !isProjectNew
|
||||
? 'Unhealthy'
|
||||
: anyComingUp || isMigrationLoading
|
||||
: anyComingUp || isMigrationLoading || (isProjectNew && !allServicesOperational)
|
||||
? 'Coming up...'
|
||||
: 'Healthy'
|
||||
|
||||
@@ -265,7 +273,9 @@ export const ServiceStatus = () => {
|
||||
key={`${service.name}-${index}`}
|
||||
className={cn(
|
||||
'w-1.5 h-1.5 rounded-full',
|
||||
service.isLoading || service.status === 'COMING_UP'
|
||||
service.isLoading ||
|
||||
service.status === 'COMING_UP' ||
|
||||
(isProjectNew && !service.isHealthy)
|
||||
? 'bg-foreground-lighter animate-pulse'
|
||||
: service.isHealthy
|
||||
? 'bg-brand'
|
||||
@@ -290,6 +300,7 @@ export const ServiceStatus = () => {
|
||||
<StatusIcon
|
||||
isLoading={service.isLoading}
|
||||
isHealthy={!!service.isHealthy}
|
||||
isProjectNew={isProjectNew}
|
||||
projectStatus={service.status}
|
||||
/>
|
||||
<div className="flex-1">
|
||||
@@ -298,6 +309,7 @@ export const ServiceStatus = () => {
|
||||
<StatusMessage
|
||||
isLoading={service.isLoading}
|
||||
isHealthy={!!service.isHealthy}
|
||||
isProjectNew={isProjectNew}
|
||||
status={service.status}
|
||||
/>
|
||||
</p>
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { analyticsKeys } from './keys'
|
||||
|
||||
export const useInvalidateAnalyticsQuery = () => {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const invalidateInfraMonitoringQuery = (
|
||||
ref: string,
|
||||
{
|
||||
attribute,
|
||||
startDate,
|
||||
endDate,
|
||||
interval,
|
||||
databaseIdentifier,
|
||||
}: {
|
||||
attribute?: string
|
||||
startDate?: string
|
||||
endDate?: string
|
||||
interval?: string
|
||||
databaseIdentifier?: string
|
||||
}
|
||||
) => {
|
||||
queryClient.invalidateQueries(
|
||||
analyticsKeys.infraMonitoring(ref, {
|
||||
attribute,
|
||||
startDate,
|
||||
endDate,
|
||||
interval,
|
||||
databaseIdentifier,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
return { invalidateInfraMonitoringQuery }
|
||||
}
|
||||
Reference in new issue
Block a user