mirror of
https://github.com/supabase/supabase.git
synced 2026-10-19 08:09:11 +03:00
Update warning on card buttons
This commit is contained in:
1 parent
cccf9a7da5
commit
17d4445ce7
8 files changed
+110
-118
No files matched your search
@@ -12,7 +12,7 @@ import { useProjectReadOnlyStatus } from 'hooks/misc/useProjectReadOnlyStatus'
|
||||
import { BASE_PATH, PROJECT_STATUS } from 'lib/constants'
|
||||
import { Project } from 'types'
|
||||
import { IntegrationProjectConnection } from 'data/integrations/integrations.types'
|
||||
import { ResourceWarningResponse } from 'data/usage/resource-warnings-query'
|
||||
import { ResourceWarning } from 'data/usage/resource-warnings-query'
|
||||
import ProjectCardWarnings from './ProjectCardWarnings'
|
||||
|
||||
export interface ProjectCardProps {
|
||||
@@ -20,7 +20,7 @@ export interface ProjectCardProps {
|
||||
rewriteHref?: string
|
||||
githubIntegration?: IntegrationProjectConnection
|
||||
vercelIntegration?: IntegrationProjectConnection
|
||||
resourceWarnings?: ResourceWarningResponse
|
||||
resourceWarnings?: ResourceWarning
|
||||
}
|
||||
|
||||
const ProjectCard = ({
|
||||
@@ -34,10 +34,15 @@ const ProjectCard = ({
|
||||
const desc = `${project.cloud_provider} | ${project.region}`
|
||||
|
||||
const isReadonly = useProjectReadOnlyStatus(projectRef)
|
||||
const isBranchingEnabled = project.preview_branch_refs.length > 0
|
||||
const isGithubIntegrated = githubIntegration !== undefined
|
||||
const isVercelIntegrated = vercelIntegration !== undefined
|
||||
const githubRepository = githubIntegration?.metadata.name ?? undefined
|
||||
// const isBranchingEnabled = project.preview_branch_refs.length > 0
|
||||
// const isGithubIntegrated = githubIntegration !== undefined
|
||||
// const isVercelIntegrated = vercelIntegration !== undefined
|
||||
// const githubRepository = githubIntegration?.metadata.name ?? undefined
|
||||
|
||||
const isBranchingEnabled = true
|
||||
const isGithubIntegrated = true
|
||||
const isVercelIntegrated = true
|
||||
const githubRepository = 'supabase/supabase-js'
|
||||
|
||||
// Project status should supersede its read only status
|
||||
const isHealthy = project.status === PROJECT_STATUS.ACTIVE_HEALTHY
|
||||
@@ -46,22 +51,21 @@ const ProjectCard = ({
|
||||
const isPaused = project.status === PROJECT_STATUS.INACTIVE
|
||||
const isRestoring = project.status === PROJECT_STATUS.RESTORING
|
||||
|
||||
const checkProjectResourceWarnings = (resourceWarnings: ResourceWarningResponse) => {
|
||||
const checkProjectResourceWarnings = (resourceWarnings: ResourceWarning) => {
|
||||
return Object.values(resourceWarnings).some(
|
||||
(value) => typeof value === 'boolean' && value === true
|
||||
)
|
||||
}
|
||||
|
||||
// const projectHasResourceWarnings =
|
||||
// resourceWarnings !== undefined ? checkProjectResourceWarnings(resourceWarnings) : false
|
||||
const projectHasResourceWarnings = true
|
||||
const projectHasResourceWarnings =
|
||||
resourceWarnings !== undefined ? checkProjectResourceWarnings(resourceWarnings) : false
|
||||
|
||||
return (
|
||||
<li className="col-span-1 list-none">
|
||||
<CardButton
|
||||
linkHref={rewriteHref ? rewriteHref : `/project/${projectRef}`}
|
||||
title={
|
||||
<div className="w-full justify-between space-y-1">
|
||||
<div className="w-full justify-between space-y-1.5 px-6">
|
||||
<p className="flex-shrink truncate">{name}</p>
|
||||
<div className="flex items-center space-x-1.5">
|
||||
{isVercelIntegrated && (
|
||||
@@ -87,61 +91,66 @@ const ProjectCard = ({
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{resourceWarnings && projectHasResourceWarnings && (
|
||||
<ProjectCardWarnings resourceWarnings={resourceWarnings} />
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
footer={
|
||||
<div className="flex items-end justify-between">
|
||||
<span className="text-sm lowercase text-scale-1000">{desc}</span>
|
||||
<div className="flex items-end justify-between px-6">
|
||||
<span className="text-xs lowercase text-scale-1000">{desc}</span>
|
||||
|
||||
{isHealthy && isReadonly && (
|
||||
<div className="grow text-right">
|
||||
<Badge color="yellow">
|
||||
<div className="flex items-center gap-2">
|
||||
<IconAlertTriangle size={14} strokeWidth={2} />
|
||||
<span className="truncate">Read-only mode</span>
|
||||
</div>
|
||||
</Badge>
|
||||
</div>
|
||||
{isHealthy && (
|
||||
<>
|
||||
{isReadonly ? (
|
||||
<Badge color="yellow">
|
||||
<div className="flex items-center gap-2">
|
||||
<IconAlertTriangle size={14} strokeWidth={2} />
|
||||
<span className="truncate">Read-only mode</span>
|
||||
</div>
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge color="green">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="truncate">Active</span>
|
||||
</div>
|
||||
</Badge>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{isRestoring && (
|
||||
<div className="grow text-right">
|
||||
<Badge color="brand">
|
||||
<div className="flex items-center gap-2">
|
||||
<IconLoader className="animate-spin" size={14} strokeWidth={2} />
|
||||
<span className="truncate">Restoring</span>
|
||||
</div>
|
||||
</Badge>
|
||||
</div>
|
||||
<Badge color="brand">
|
||||
<div className="flex items-center gap-2">
|
||||
<IconLoader className="animate-spin" size={14} strokeWidth={2} />
|
||||
<span className="truncate">Restoring</span>
|
||||
</div>
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
{isPausing && (
|
||||
<div className="grow text-right">
|
||||
<Badge color="scale">
|
||||
<div className="flex items-center gap-2">
|
||||
<IconLoader className="animate-spin" size={14} strokeWidth={2} />
|
||||
<span className="truncate">Pausing</span>
|
||||
</div>
|
||||
</Badge>
|
||||
</div>
|
||||
<Badge color="scale">
|
||||
<div className="flex items-center gap-2">
|
||||
<IconLoader className="animate-spin" size={14} strokeWidth={2} />
|
||||
<span className="truncate">Pausing</span>
|
||||
</div>
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
{isPaused && (
|
||||
<div className="grow text-right">
|
||||
<Badge color="scale">
|
||||
<div className="flex items-center gap-2">
|
||||
<IconPauseCircle size={14} strokeWidth={2} />
|
||||
<span className="truncate">Paused</span>
|
||||
</div>
|
||||
</Badge>
|
||||
</div>
|
||||
<Badge color="scale">
|
||||
<div className="flex items-center gap-2">
|
||||
<IconPauseCircle size={14} strokeWidth={2} />
|
||||
<span className="truncate">Paused</span>
|
||||
</div>
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
>
|
||||
{resourceWarnings && projectHasResourceWarnings ? (
|
||||
<ProjectCardWarnings resourceWarnings={resourceWarnings} />
|
||||
) : (
|
||||
<div className="py-2" />
|
||||
)}
|
||||
</CardButton>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,41 +1,36 @@
|
||||
import { ResourceWarningResponse } from 'data/usage/resource-warnings-query'
|
||||
import { ResourceWarning } from 'data/usage/resource-warnings-query'
|
||||
import { Alert_Shadcn_, AlertDescription_Shadcn_, AlertTitle_Shadcn_, IconAlertTriangle } from 'ui'
|
||||
|
||||
export type ResourceWarningResponses = Omit<
|
||||
Omit<ResourceWarningResponse, 'project'>,
|
||||
'is_readonly_mode_enabled'
|
||||
>
|
||||
|
||||
export interface ProjectCardWarningsProps {
|
||||
resourceWarnings: ResourceWarningResponses
|
||||
resourceWarnings: ResourceWarning
|
||||
}
|
||||
|
||||
// reference for message wording
|
||||
// www.notion.so/supabase/Notify-users-on-resource-exhaustion-on-the-dashboard-4bb7b9a990104720b179b7a37d07f41c?pvs=4#a79798b5ac72402e8bfea4c4715e7439
|
||||
const RESOURCE_WARNING_MESSAGES = {
|
||||
is_readonly_mode_enabled: {
|
||||
title: 'Project in readonly mode',
|
||||
description: 'This database will no longer accept write requests.',
|
||||
title: 'Project is in readonly mode',
|
||||
description: 'Database is no longer accept write requests.',
|
||||
},
|
||||
is_disk_io_budget_below_threshold: {
|
||||
title: 'Your project is depleting its Disk IO Budget',
|
||||
description: 'Your instance may become unresponsive if fully exhausted.',
|
||||
title: 'Project is depleting its Disk IO Budget',
|
||||
description: 'Instance may become unresponsive if fully exhausted.',
|
||||
},
|
||||
is_disk_space_usage_beyond_threshold: {
|
||||
title: 'Project has exhausted disk space budget',
|
||||
description: 'Your instance may become unresponsive if fully exhausted.',
|
||||
title: 'Project is exhausting disk space budget',
|
||||
description: 'Instance may become unresponsive if fully exhausted.',
|
||||
},
|
||||
is_cpu_load_beyond_threshold: {
|
||||
title: 'Your project has high CPU usage',
|
||||
description: `Your instance's performance is affected`,
|
||||
title: 'Project has high CPU usage',
|
||||
description: `Instance's performance is affected`,
|
||||
},
|
||||
is_memory_and_swap_usage_beyond_threshold: {
|
||||
title: 'Your project has high memory usage',
|
||||
description: `Your instance's performance is affected.`,
|
||||
title: 'Project has high memory usage',
|
||||
description: `Instance's performance is affected.`,
|
||||
},
|
||||
multiple_resource_warnings: {
|
||||
title: 'Your project is burning up',
|
||||
description: `Your instance's performance is affected.`,
|
||||
title: 'Project is exhausting multiple resources',
|
||||
description: `Instance's performance is affected.`,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -43,20 +38,26 @@ export default function ProjectCardWarnings({ resourceWarnings }: ProjectCardWar
|
||||
const activeWarnings = Object.keys(resourceWarnings).filter(
|
||||
(property) => resourceWarnings[property as keyof typeof resourceWarnings] === true
|
||||
)
|
||||
const hasHighPriorityWarning = activeWarnings.includes('is_readonly_mode_enabled')
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Alert_Shadcn_ variant="warning">
|
||||
<Alert_Shadcn_
|
||||
variant={hasHighPriorityWarning ? 'destructive' : 'warning'}
|
||||
className="border-r-0 border-l-0 rounded-none my-2 mb-2.5"
|
||||
>
|
||||
<IconAlertTriangle strokeWidth={2} />
|
||||
<AlertTitle_Shadcn_>
|
||||
|
||||
<AlertTitle_Shadcn_ className="text-xs mb-0.5">
|
||||
{activeWarnings.length > 1
|
||||
? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.title
|
||||
: RESOURCE_WARNING_MESSAGES[activeWarnings[0] as keyof typeof resourceWarnings]?.title}
|
||||
: RESOURCE_WARNING_MESSAGES[activeWarnings[0] as keyof typeof RESOURCE_WARNING_MESSAGES]
|
||||
?.title}
|
||||
</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
<AlertDescription_Shadcn_ className="text-xs">
|
||||
{activeWarnings.length > 1
|
||||
? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.description
|
||||
: RESOURCE_WARNING_MESSAGES[activeWarnings[0] as keyof typeof resourceWarnings]
|
||||
: RESOURCE_WARNING_MESSAGES[activeWarnings[0] as keyof typeof RESOURCE_WARNING_MESSAGES]
|
||||
?.description}
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
|
||||
import { usePermissionsQuery } from 'data/permissions/permissions-query'
|
||||
import { useProjectsQuery } from 'data/projects/projects-query'
|
||||
import { useResourceWarningQuery } from 'data/usage/resource-warnings-query'
|
||||
import { ResourceWarning, useResourceWarningsQuery } from 'data/usage/resource-warnings-query'
|
||||
import { useCheckPermissions } from 'hooks'
|
||||
import { IS_PLATFORM } from 'lib/constants'
|
||||
import { makeRandomString } from 'lib/helpers'
|
||||
@@ -37,6 +37,7 @@ const ProjectList = ({ rewriteHref }: ProjectListProps) => {
|
||||
isError: isErrorPermissions,
|
||||
error: permissionsError,
|
||||
} = usePermissionsQuery()
|
||||
const { data: resourceWarnings } = useResourceWarningsQuery()
|
||||
const { data: allOverdueInvoices } = useOverdueInvoicesQuery({ enabled: IS_PLATFORM })
|
||||
const projectsByOrg = groupBy(allProjects, 'organization_id')
|
||||
const isLoadingPermissions = IS_PLATFORM ? _isLoadingPermissions : false
|
||||
@@ -52,6 +53,7 @@ const ProjectList = ({ rewriteHref }: ProjectListProps) => {
|
||||
overdueInvoices={(allOverdueInvoices ?? []).filter(
|
||||
(it) => it.organization_id === organization.id
|
||||
)}
|
||||
resourceWarnings={resourceWarnings ?? []}
|
||||
rewriteHref={rewriteHref}
|
||||
isLoadingPermissions={isLoadingPermissions}
|
||||
isErrorPermissions={isErrorPermissions}
|
||||
@@ -72,6 +74,7 @@ type OrganizationProjectsProps = {
|
||||
organization: Organization
|
||||
projects: Project[]
|
||||
overdueInvoices: OverdueInvoicesResponse[]
|
||||
resourceWarnings: ResourceWarning[]
|
||||
isLoadingPermissions: boolean
|
||||
isErrorPermissions: boolean
|
||||
permissionsError: ResponseError | null
|
||||
@@ -82,9 +85,10 @@ type OrganizationProjectsProps = {
|
||||
}
|
||||
|
||||
const OrganizationProjects = ({
|
||||
organization: { id, name, slug, subscription_id },
|
||||
organization: { id, name, slug },
|
||||
projects,
|
||||
overdueInvoices,
|
||||
resourceWarnings,
|
||||
isLoadingPermissions,
|
||||
isErrorPermissions,
|
||||
permissionsError,
|
||||
@@ -95,10 +99,6 @@ const OrganizationProjects = ({
|
||||
}: OrganizationProjectsProps) => {
|
||||
const isEmpty = !projects || projects.length === 0
|
||||
const canReadProjects = useCheckPermissions(PermissionAction.READ, 'projects', undefined, id)
|
||||
const { data: resourceWarnings } = useResourceWarningQuery()
|
||||
|
||||
console.log(resourceWarnings)
|
||||
console.log('one render')
|
||||
|
||||
return (
|
||||
<div className="space-y-3" key={makeRandomString(5)}>
|
||||
@@ -166,7 +166,7 @@ const OrganizationProjects = ({
|
||||
key={makeRandomString(5)}
|
||||
project={project}
|
||||
rewriteHref={rewriteHref ? rewriteHref(project.ref) : undefined}
|
||||
resourceWarnings={resourceWarnings?.find(
|
||||
resourceWarnings={resourceWarnings.find(
|
||||
(resourceWarning) => resourceWarning.project === project.ref
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -18,7 +18,7 @@ import RestoringState from './RestoringState'
|
||||
import UpgradingState from './UpgradingState'
|
||||
import AppLayout from '../AppLayout/AppLayout'
|
||||
import UsageWarningBanner from 'components/ui/UsageWarnings/UsageWarningBanner'
|
||||
import { useResourceWarningQuery } from 'data/usage/resource-warnings-query'
|
||||
import { useResourceWarningsQuery } from 'data/usage/resource-warnings-query'
|
||||
|
||||
// [Joshen] This is temporary while we unblock users from managing their project
|
||||
// if their project is not responding well for any reason. Eventually needs a bit of an overhaul
|
||||
@@ -179,7 +179,7 @@ const ContentWrapper = ({ isLoading, children }: ContentWrapperProps) => {
|
||||
selectedProject?.status === PROJECT_STATUS.PAUSING
|
||||
const isProjectOffline = selectedProject?.postgrestStatus === 'OFFLINE'
|
||||
|
||||
const { data: resourceWarnings } = useResourceWarningQuery()
|
||||
const { data: resourceWarnings } = useResourceWarningsQuery()
|
||||
console.log(resourceWarnings)
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -27,7 +27,7 @@ const CardButton = ({
|
||||
imgAlt,
|
||||
onClick,
|
||||
icon,
|
||||
containerHeightClassName = 'h-48',
|
||||
containerHeightClassName = '',
|
||||
loading = false,
|
||||
}: PropsWithChildren<CardButtonProps>) => {
|
||||
const LinkContainer = ({ children }: { children: React.ReactNode }) => (
|
||||
@@ -47,7 +47,7 @@ const CardButton = ({
|
||||
'group relative text-left',
|
||||
'bg-panel-header-light dark:bg-panel-header-dark',
|
||||
'border border-panel-border-light dark:border-panel-border-dark',
|
||||
'rounded-md py-4 px-6 flex flex-row',
|
||||
'rounded-md py-4 flex flex-row',
|
||||
'transition ease-in-out duration-150',
|
||||
containerHeightClassName,
|
||||
]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const usageKeys = {
|
||||
usage: (projectRef: string | undefined) => ['projects', projectRef, 'usage'] as const,
|
||||
orgUsage: (orgSlug: string | undefined) => ['organizations', orgSlug, 'usage'] as const,
|
||||
resourceWarnings: () => ['project', 'resource-warnings'] as const,
|
||||
resourceWarnings: () => ['projects', 'resource-warnings'] as const,
|
||||
}
|
||||
@@ -1,40 +1,26 @@
|
||||
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
|
||||
import { get, isResponseOk } from 'lib/common/fetch'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { usageKeys } from './keys'
|
||||
|
||||
export type ResourceWarningResponse = {
|
||||
project: string
|
||||
is_readonly_mode_enabled: boolean
|
||||
is_disk_io_budget_below_threshold: boolean
|
||||
is_disk_space_usage_beyond_threshold: boolean
|
||||
is_cpu_load_beyond_threshold: boolean
|
||||
is_memory_and_swap_usage_beyond_threshold: boolean
|
||||
}
|
||||
import { get } from 'data/fetchers'
|
||||
import { usageKeys } from './keys'
|
||||
import { ResponseError } from 'types'
|
||||
import { components } from 'data/api'
|
||||
|
||||
export async function getResourceWarnings(signal?: AbortSignal) {
|
||||
const response = await get<ResourceWarningResponse[]>(`${API_URL}/projects-resource-warnings`, {
|
||||
signal,
|
||||
})
|
||||
if (!isResponseOk(response)) {
|
||||
throw response.error
|
||||
}
|
||||
|
||||
return response
|
||||
const { data, error } = await get(`/platform/projects-resource-warnings`, { signal })
|
||||
if (error) throw error
|
||||
return data
|
||||
}
|
||||
|
||||
export type ResourceWarningData = Awaited<ReturnType<typeof getResourceWarnings>>
|
||||
export type ResourceError = unknown
|
||||
export type ResourceWarning = components['schemas']['ProjectResourceWarningsResponse']
|
||||
export type ResourceWarningsData = Awaited<ReturnType<typeof getResourceWarnings>>
|
||||
export type ResourceWarningsError = ResponseError
|
||||
|
||||
export const useResourceWarningQuery = <TData = ResourceWarningData>({
|
||||
export const useResourceWarningsQuery = <TData = ResourceWarningsData>({
|
||||
enabled = true,
|
||||
...options
|
||||
}: UseQueryOptions<ResourceWarningData, ResourceError, TData> = {}) =>
|
||||
useQuery<ResourceWarningData, ResourceError, TData>(
|
||||
}: UseQueryOptions<ResourceWarningsData, ResourceWarningsError, TData> = {}) =>
|
||||
useQuery<ResourceWarningsData, ResourceWarningsError, TData>(
|
||||
usageKeys.resourceWarnings(),
|
||||
({ signal }) => getResourceWarnings(signal),
|
||||
{
|
||||
enabled: enabled,
|
||||
...options,
|
||||
}
|
||||
{ enabled, ...options }
|
||||
)
|
||||
@@ -4,7 +4,6 @@ import ProjectUsageSection from 'components/interfaces/Home/ProjectUsageSection'
|
||||
import { ProjectLayoutWithAuth } from 'components/layouts'
|
||||
import ProjectPausedState from 'components/layouts/ProjectLayout/ProjectPausedState'
|
||||
import ProjectUpgradeFailedBanner from 'components/ui/ProjectUpgradeFailedBanner'
|
||||
import { useResourceWarningQuery } from 'data/usage/resource-warnings-query'
|
||||
import { useSelectedProject } from 'hooks'
|
||||
import { IS_PLATFORM, PROJECT_STATUS } from 'lib/constants'
|
||||
import { NextPageWithLayout } from 'types'
|
||||
@@ -17,9 +16,6 @@ const Home: NextPageWithLayout = () => {
|
||||
? project?.name
|
||||
: 'Welcome to your project'
|
||||
|
||||
const { data: resourceWarnings } = useResourceWarningQuery()
|
||||
|
||||
console.log(resourceWarnings)
|
||||
return (
|
||||
<div className="mx-auto my-16 w-full max-w-7xl space-y-16">
|
||||
<div className="mx-6 flex items-center space-x-6">
|
||||
|
||||
Reference in new issue
Block a user