From 622486f750b3c6969bcb5f7dbcaeebda60e548bf Mon Sep 17 00:00:00 2001 From: Saxon Fletcher Date: Tue, 14 Oct 2025 08:09:48 +1000 Subject: [PATCH] Refresh report and status update (#39473) * refresh report * project status * prettier * Minor refactors * add back refreshing --------- Co-authored-by: Joshen Lim --- .../HomeNew/CustomReportSection.tsx | 73 ++++++++++++++----- .../interfaces/HomeNew/ServiceStatus.tsx | 34 ++++++--- apps/studio/data/analytics/utils.ts | 35 +++++++++ 3 files changed, 114 insertions(+), 28 deletions(-) create mode 100644 apps/studio/data/analytics/utils.ts diff --git a/apps/studio/components/interfaces/HomeNew/CustomReportSection.tsx b/apps/studio/components/interfaces/HomeNew/CustomReportSection.tsx index ad0e12284ad..91a635f39aa 100644 --- a/apps/studio/components/interfaces/HomeNew/CustomReportSection.tsx +++ b/apps/studio/components/interfaces/HomeNew/CustomReportSection.tsx @@ -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(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() {

Reports

- {canUpdateReport || canCreateReport ? ( - }> - Add block - - } - side="bottom" - align="end" - autoFocus - /> - ) : null} +
+ {layout.length > 0 && ( + } + className="w-7" + disabled={isRefreshing} + tooltip={{ content: { side: 'bottom', text: 'Refresh report' } }} + onClick={onRefreshReport} + /> + )} + {canUpdateReport || canCreateReport ? ( + }> + Add block + + } + side="bottom" + align="end" + autoFocus + /> + ) : null} +
{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)} /> diff --git a/apps/studio/components/interfaces/HomeNew/ServiceStatus.tsx b/apps/studio/components/interfaces/HomeNew/ServiceStatus.tsx index 294aecd09b8..d5ed3f78d2b 100644 --- a/apps/studio/components/interfaces/HomeNew/ServiceStatus.tsx +++ b/apps/studio/components/interfaces/HomeNew/ServiceStatus.tsx @@ -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 = () => const StatusIcon = ({ isLoading, isHealthy, + isProjectNew, projectStatus, }: { isLoading: boolean isHealthy: boolean + isProjectNew: boolean projectStatus?: ProjectServiceStatus }) => { - if (isHealthy) return - if (isLoading) return - if (projectStatus === 'UNHEALTHY') return - if (projectStatus === 'COMING_UP') return - if (projectStatus === 'ACTIVE_HEALTHY') return + if (isHealthy || projectStatus === 'ACTIVE_HEALTHY') return + if (isLoading || isProjectNew || projectStatus === 'COMING_UP') return return } @@ -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 = () => {
@@ -298,6 +309,7 @@ export const ServiceStatus = () => {

diff --git a/apps/studio/data/analytics/utils.ts b/apps/studio/data/analytics/utils.ts new file mode 100644 index 00000000000..543bd472de6 --- /dev/null +++ b/apps/studio/data/analytics/utils.ts @@ -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 } +}