From b3a8f922fbcbda8f2d006e25b58b6c3032e9e3b2 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Fri, 18 Aug 2023 17:00:50 -0230 Subject: [PATCH 01/49] First draft --- .../Home/ProjectList/ProjectCard.tsx | 20 +++++- .../Home/ProjectList/ProjectCardWarnings.tsx | 66 +++++++++++++++++++ .../Home/ProjectList/ProjectList.tsx | 8 +++ .../layouts/ProjectLayout/ProjectLayout.tsx | 11 +++- studio/components/ui/CardButton.tsx | 2 +- .../ui/UsageWarnings/UsageWarningBanner.tsx | 16 +++++ studio/data/usage/keys.ts | 1 + studio/data/usage/resource-warnings-query.ts | 40 +++++++++++ studio/pages/project/[ref]/index.tsx | 4 ++ 9 files changed, 162 insertions(+), 6 deletions(-) create mode 100644 studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx create mode 100644 studio/components/ui/UsageWarnings/UsageWarningBanner.tsx create mode 100644 studio/data/usage/resource-warnings-query.ts diff --git a/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx b/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx index 0b1801f51a2..1c81df2fad7 100644 --- a/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx +++ b/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx @@ -5,7 +5,6 @@ import { IconGitHub, IconLoader, IconPauseCircle, - IconTriangle, } from 'ui' import CardButton from 'components/ui/CardButton' @@ -13,12 +12,15 @@ 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 ProjectCardWarnings from './ProjectCardWarnings' export interface ProjectCardProps { project: Project rewriteHref?: string githubIntegration?: IntegrationProjectConnection vercelIntegration?: IntegrationProjectConnection + resourceWarnings?: ResourceWarningResponse } const ProjectCard = ({ @@ -26,6 +28,7 @@ const ProjectCard = ({ rewriteHref, githubIntegration, vercelIntegration, + resourceWarnings, }: ProjectCardProps) => { const { name, ref: projectRef } = project const desc = `${project.cloud_provider} | ${project.region}` @@ -36,13 +39,23 @@ const ProjectCard = ({ const isVercelIntegrated = vercelIntegration !== undefined const githubRepository = githubIntegration?.metadata.name ?? undefined - // Project status should supersede is read only status + // Project status should supersede its read only status const isHealthy = project.status === PROJECT_STATUS.ACTIVE_HEALTHY const isPausing = project.status === PROJECT_STATUS.GOING_DOWN || project.status === PROJECT_STATUS.PAUSING const isPaused = project.status === PROJECT_STATUS.INACTIVE const isRestoring = project.status === PROJECT_STATUS.RESTORING + const checkProjectResourceWarnings = (resourceWarnings: ResourceWarningResponse) => { + return Object.values(resourceWarnings).some( + (value) => typeof value === 'boolean' && value === true + ) + } + + // const projectHasResourceWarnings = + // resourceWarnings !== undefined ? checkProjectResourceWarnings(resourceWarnings) : false + const projectHasResourceWarnings = true + return (
  • )} + {resourceWarnings && projectHasResourceWarnings && ( + + )} } footer={ diff --git a/studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx b/studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx new file mode 100644 index 00000000000..37fa3c17bd4 --- /dev/null +++ b/studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx @@ -0,0 +1,66 @@ +import { ResourceWarningResponse } from 'data/usage/resource-warnings-query' +import { Alert_Shadcn_, AlertDescription_Shadcn_, AlertTitle_Shadcn_, IconAlertTriangle } from 'ui' + +export type ResourceWarningResponses = Omit< + Omit, + 'is_readonly_mode_enabled' +> + +export interface ProjectCardWarningsProps { + resourceWarnings: ResourceWarningResponses +} + +// 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.', + }, + is_disk_io_budget_below_threshold: { + title: 'Your project is depleting its Disk IO Budget', + description: 'Your 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.', + }, + is_cpu_load_beyond_threshold: { + title: 'Your project has high CPU usage', + description: `Your 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.`, + }, + multiple_resource_warnings: { + title: 'Your project is burning up', + description: `Your instance's performance is affected.`, + }, +} + +export default function ProjectCardWarnings({ resourceWarnings }: ProjectCardWarningsProps) { + const activeWarnings = Object.keys(resourceWarnings).filter( + (property) => resourceWarnings[property as keyof typeof resourceWarnings] === true + ) + + return ( +
    + + + + {activeWarnings.length > 1 + ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.title + : RESOURCE_WARNING_MESSAGES[activeWarnings[0] as keyof typeof resourceWarnings]?.title} + + + {activeWarnings.length > 1 + ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.description + : RESOURCE_WARNING_MESSAGES[activeWarnings[0] as keyof typeof resourceWarnings] + ?.description} + + + {/*
    {JSON.stringify(resourceWarnings, null, 2)}
    */} +
    + ) +} diff --git a/studio/components/interfaces/Home/ProjectList/ProjectList.tsx b/studio/components/interfaces/Home/ProjectList/ProjectList.tsx index d24aa81b1d0..5066c17a004 100644 --- a/studio/components/interfaces/Home/ProjectList/ProjectList.tsx +++ b/studio/components/interfaces/Home/ProjectList/ProjectList.tsx @@ -19,6 +19,7 @@ import { Organization, Project, ResponseError } from 'types' import ProjectCard from './ProjectCard' import ShimmeringCard from './ShimmeringCard' import { useOrgIntegrationsQuery } from 'data/integrations/integrations-query-org-only' +import { useResourceWarningQuery } from 'data/usage/resource-warnings-query' export interface ProjectListProps { rewriteHref?: (projectRef: string) => string @@ -95,6 +96,10 @@ 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 (
    @@ -165,6 +170,9 @@ const OrganizationProjects = ({ key={makeRandomString(5)} project={project} rewriteHref={rewriteHref ? rewriteHref(project.ref) : undefined} + resourceWarnings={resourceWarnings?.find( + (resourceWarning) => resourceWarning.project === project.ref + )} /> )) )} diff --git a/studio/components/layouts/ProjectLayout/ProjectLayout.tsx b/studio/components/layouts/ProjectLayout/ProjectLayout.tsx index 90d627cc098..9f1e2429b0e 100644 --- a/studio/components/layouts/ProjectLayout/ProjectLayout.tsx +++ b/studio/components/layouts/ProjectLayout/ProjectLayout.tsx @@ -17,6 +17,8 @@ import ProjectPausedState from './ProjectPausedState' 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' // [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 @@ -94,14 +96,12 @@ const ProjectLayout = ({
    {/* Left-most navigation side bar to access products */} {!hideIconBar && } - {/* Product menu bar */} {!showPausedState && ( {productMenu} )} -
    {!navLayoutV2 && !hideHeader && IS_PLATFORM && } {showPausedState ? ( @@ -179,6 +179,8 @@ const ContentWrapper = ({ isLoading, children }: ContentWrapperProps) => { selectedProject?.status === PROJECT_STATUS.PAUSING const isProjectOffline = selectedProject?.postgrestStatus === 'OFFLINE' + const { data: resourceWarnings } = useResourceWarningQuery() + console.log(resourceWarnings) return ( <> {isLoading || (requiresProjectDetails && selectedProject === undefined) ? ( @@ -194,7 +196,10 @@ const ContentWrapper = ({ isLoading, children }: ContentWrapperProps) => { ) : requiresDbConnection && isProjectBuilding ? ( ) : ( - {children} + <> + + {children} + )} ) diff --git a/studio/components/ui/CardButton.tsx b/studio/components/ui/CardButton.tsx index f0696bafe7b..f374f6cef82 100644 --- a/studio/components/ui/CardButton.tsx +++ b/studio/components/ui/CardButton.tsx @@ -27,7 +27,7 @@ const CardButton = ({ imgAlt, onClick, icon, - containerHeightClassName = 'h-32', + containerHeightClassName = 'h-48', loading = false, }: PropsWithChildren) => { const LinkContainer = ({ children }: { children: React.ReactNode }) => ( diff --git a/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx b/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx new file mode 100644 index 00000000000..ea70a140856 --- /dev/null +++ b/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx @@ -0,0 +1,16 @@ +import Link from 'next/link' +import React from 'react' + +import { useParams } from 'common' +import { useSelectedOrganization } from 'hooks' + +export default function UsageWarningBanner() { + const { ref } = useParams() + const organization = useSelectedOrganization() + + return ( +
    + Project is experiencing high [product] usage. Learn more +
    + ) +} diff --git a/studio/data/usage/keys.ts b/studio/data/usage/keys.ts index aac6590a78e..b5aca6f8298 100644 --- a/studio/data/usage/keys.ts +++ b/studio/data/usage/keys.ts @@ -1,4 +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, } diff --git a/studio/data/usage/resource-warnings-query.ts b/studio/data/usage/resource-warnings-query.ts new file mode 100644 index 00000000000..82be8e7f1eb --- /dev/null +++ b/studio/data/usage/resource-warnings-query.ts @@ -0,0 +1,40 @@ +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 +} + +export async function getResourceWarnings(signal?: AbortSignal) { + const response = await get(`${API_URL}/projects-resource-warnings`, { + signal, + }) + if (!isResponseOk(response)) { + throw response.error + } + + return response +} + +export type ResourceWarningData = Awaited> +export type ResourceError = unknown + +export const useResourceWarningQuery = ({ + enabled = true, + ...options +}: UseQueryOptions = {}) => + useQuery( + usageKeys.resourceWarnings(), + ({ signal }) => getResourceWarnings(signal), + { + enabled: enabled, + ...options, + } + ) diff --git a/studio/pages/project/[ref]/index.tsx b/studio/pages/project/[ref]/index.tsx index 06b56fd2a10..727d5789606 100644 --- a/studio/pages/project/[ref]/index.tsx +++ b/studio/pages/project/[ref]/index.tsx @@ -4,6 +4,7 @@ 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' @@ -16,6 +17,9 @@ const Home: NextPageWithLayout = () => { ? project?.name : 'Welcome to your project' + const { data: resourceWarnings } = useResourceWarningQuery() + + console.log(resourceWarnings) return (
    From 17d4445ce70cd2a4870d12fa1b88ca90de707fc5 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 23 Aug 2023 15:54:14 +0800 Subject: [PATCH 02/49] Update warning on card buttons --- .../Home/ProjectList/ProjectCard.tsx | 109 ++++++++++-------- .../Home/ProjectList/ProjectCardWarnings.tsx | 49 ++++---- .../Home/ProjectList/ProjectList.tsx | 14 +-- .../layouts/ProjectLayout/ProjectLayout.tsx | 4 +- studio/components/ui/CardButton.tsx | 4 +- studio/data/usage/keys.ts | 2 +- studio/data/usage/resource-warnings-query.ts | 42 +++---- studio/pages/project/[ref]/index.tsx | 4 - 8 files changed, 110 insertions(+), 118 deletions(-) diff --git a/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx b/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx index 1c81df2fad7..68532417f5d 100644 --- a/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx +++ b/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx @@ -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 (
  • +

    {name}

    {isVercelIntegrated && ( @@ -87,61 +91,66 @@ const ProjectCard = ({ )}
    - {resourceWarnings && projectHasResourceWarnings && ( - - )}
    } footer={ -
    - {desc} +
    + {desc} - {isHealthy && isReadonly && ( -
    - -
    - - Read-only mode -
    -
    -
    + {isHealthy && ( + <> + {isReadonly ? ( + +
    + + Read-only mode +
    +
    + ) : ( + +
    + Active +
    +
    + )} + )} {isRestoring && ( -
    - -
    - - Restoring -
    -
    -
    + +
    + + Restoring +
    +
    )} {isPausing && ( -
    - -
    - - Pausing -
    -
    -
    + +
    + + Pausing +
    +
    )} {isPaused && ( -
    - -
    - - Paused -
    -
    -
    + +
    + + Paused +
    +
    )}
    } - /> + > + {resourceWarnings && projectHasResourceWarnings ? ( + + ) : ( +
    + )} +
  • ) } diff --git a/studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx b/studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx index 37fa3c17bd4..ec8f4af8e43 100644 --- a/studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx +++ b/studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx @@ -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, - '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 (
    - + - + + {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} - + {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} diff --git a/studio/components/interfaces/Home/ProjectList/ProjectList.tsx b/studio/components/interfaces/Home/ProjectList/ProjectList.tsx index ef5eff5de8e..a297da6c18d 100644 --- a/studio/components/interfaces/Home/ProjectList/ProjectList.tsx +++ b/studio/components/interfaces/Home/ProjectList/ProjectList.tsx @@ -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 (
    @@ -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 )} /> diff --git a/studio/components/layouts/ProjectLayout/ProjectLayout.tsx b/studio/components/layouts/ProjectLayout/ProjectLayout.tsx index 7a1cd41cb61..586022bfbec 100644 --- a/studio/components/layouts/ProjectLayout/ProjectLayout.tsx +++ b/studio/components/layouts/ProjectLayout/ProjectLayout.tsx @@ -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 ( <> diff --git a/studio/components/ui/CardButton.tsx b/studio/components/ui/CardButton.tsx index f374f6cef82..9c0690af6ac 100644 --- a/studio/components/ui/CardButton.tsx +++ b/studio/components/ui/CardButton.tsx @@ -27,7 +27,7 @@ const CardButton = ({ imgAlt, onClick, icon, - containerHeightClassName = 'h-48', + containerHeightClassName = '', loading = false, }: PropsWithChildren) => { 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, ] diff --git a/studio/data/usage/keys.ts b/studio/data/usage/keys.ts index b5aca6f8298..54be56a1858 100644 --- a/studio/data/usage/keys.ts +++ b/studio/data/usage/keys.ts @@ -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, } diff --git a/studio/data/usage/resource-warnings-query.ts b/studio/data/usage/resource-warnings-query.ts index 82be8e7f1eb..19a92acbe8d 100644 --- a/studio/data/usage/resource-warnings-query.ts +++ b/studio/data/usage/resource-warnings-query.ts @@ -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(`${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> -export type ResourceError = unknown +export type ResourceWarning = components['schemas']['ProjectResourceWarningsResponse'] +export type ResourceWarningsData = Awaited> +export type ResourceWarningsError = ResponseError -export const useResourceWarningQuery = ({ +export const useResourceWarningsQuery = ({ enabled = true, ...options -}: UseQueryOptions = {}) => - useQuery( +}: UseQueryOptions = {}) => + useQuery( usageKeys.resourceWarnings(), ({ signal }) => getResourceWarnings(signal), - { - enabled: enabled, - ...options, - } + { enabled, ...options } ) diff --git a/studio/pages/project/[ref]/index.tsx b/studio/pages/project/[ref]/index.tsx index 727d5789606..06b56fd2a10 100644 --- a/studio/pages/project/[ref]/index.tsx +++ b/studio/pages/project/[ref]/index.tsx @@ -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 (
    From 7115c81681711bea2618bb68d95896178157b08d Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 23 Aug 2023 17:16:00 +0800 Subject: [PATCH 03/49] Update usage warning banner --- .../BillingV2/Usage/Infrastructure.tsx | 1 + .../Home/ProjectList/ProjectCardWarnings.tsx | 1 + .../layouts/ProjectLayout/ProjectLayout.tsx | 9 +- .../ui/UsageWarnings/UsageWarningBanner.tsx | 145 +++++++++++++++++- studio/data/usage/resource-warnings-query.ts | 6 +- 5 files changed, 147 insertions(+), 15 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx index fdb0948c26c..f4314f551be 100644 --- a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx @@ -246,6 +246,7 @@ const Infrastructure = ({
    )} + {/* [Joshen TODO] Add the necessary alert warnings for CPU and RAM here as well */}
    {attribute.key === 'disk_io_consumption' ? ( diff --git a/studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx b/studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx index ec8f4af8e43..887230179ed 100644 --- a/studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx +++ b/studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx @@ -39,6 +39,7 @@ export default function ProjectCardWarnings({ resourceWarnings }: ProjectCardWar (property) => resourceWarnings[property as keyof typeof resourceWarnings] === true ) const hasHighPriorityWarning = activeWarnings.includes('is_readonly_mode_enabled') + // [Joshen] We should probably read readonly mode separately for multiple resource warnings, read only mode needs to take highest precedence return (
    diff --git a/studio/components/layouts/ProjectLayout/ProjectLayout.tsx b/studio/components/layouts/ProjectLayout/ProjectLayout.tsx index 586022bfbec..37ad6281554 100644 --- a/studio/components/layouts/ProjectLayout/ProjectLayout.tsx +++ b/studio/components/layouts/ProjectLayout/ProjectLayout.tsx @@ -4,8 +4,10 @@ import { Fragment, PropsWithChildren, ReactNode } from 'react' import { useParams } from 'common/hooks' import Connecting from 'components/ui/Loading' +import UsageWarningBanner from 'components/ui/UsageWarnings/UsageWarningBanner' import { useFlag, useSelectedOrganization, useSelectedProject, withAuth } from 'hooks' -import { PROJECT_STATUS, IS_PLATFORM } from 'lib/constants' +import { IS_PLATFORM, PROJECT_STATUS } from 'lib/constants' +import AppLayout from '../AppLayout/AppLayout' import BuildingState from './BuildingState' import ConnectingState from './ConnectingState' import LayoutHeader from './LayoutHeader' @@ -16,9 +18,6 @@ import { ProjectContextProvider } from './ProjectContext' import ProjectPausedState from './ProjectPausedState' import RestoringState from './RestoringState' import UpgradingState from './UpgradingState' -import AppLayout from '../AppLayout/AppLayout' -import UsageWarningBanner from 'components/ui/UsageWarnings/UsageWarningBanner' -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,8 +178,6 @@ const ContentWrapper = ({ isLoading, children }: ContentWrapperProps) => { selectedProject?.status === PROJECT_STATUS.PAUSING const isProjectOffline = selectedProject?.postgrestStatus === 'OFFLINE' - const { data: resourceWarnings } = useResourceWarningsQuery() - console.log(resourceWarnings) return ( <> {isLoading || (requiresProjectDetails && selectedProject === undefined) ? ( diff --git a/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx b/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx index ea70a140856..48656d4a197 100644 --- a/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx +++ b/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx @@ -1,16 +1,145 @@ import Link from 'next/link' -import React from 'react' - import { useParams } from 'common' -import { useSelectedOrganization } from 'hooks' +import { + AlertDescription_Shadcn_, + AlertTitle_Shadcn_, + Alert_Shadcn_, + Button, + IconAlertTriangle, + IconExternalLink, +} from 'ui' -export default function UsageWarningBanner() { +import { useResourceWarningsQuery } from 'data/usage/resource-warnings-query' + +// [TODO] Just double check if the docs url and correction urls are all correct +const RESOURCE_WARNING_MESSAGES = { + is_readonly_mode_enabled: { + title: 'Your project is currently in readonly mode and is no longer accepting write requests', + description: + 'You will need to manually override read-only mode and reduce the disk size to below 95%.', + docsUrl: 'https://supabase.com/docs/guides/platform/database-size#disabling-read-only-mode', + correctionUrl: undefined, + buttonText: undefined, + }, + is_disk_io_budget_below_threshold: { + title: + 'Your project is about to deplete its Disk IO Budget, and your instance may become unresponsive once fully exhausted', + description: + 'You will need to either optimize your performance, or upgrade your compute to a larger instance.', + docsUrl: 'https://supabase.com/docs/guides/platform/exhaust-disk-io', + correctionUrl: '/project/[ref]/settings/billing/usage#disk_io', + buttonText: 'Check usage', + }, + is_disk_space_usage_beyond_threshold: { + title: + 'Your project is about to exhaust its disk space budget, and your instance may become unresponsive once fully exhausted', + description: 'Some CTA description here', + docsUrl: undefined, + correctionUrl: undefined, + buttonText: undefined, + }, + is_cpu_load_beyond_threshold: { + title: + "Your project is currently facing high CPU usage, and your instance's performance is affected", + description: + 'You will need to either optimize your performance or upgrade your compute to a larger instance', + docsUrl: 'https://supabase.com/docs/guides/platform/exhaust-cpu', + correctionUrl: '/project/[ref]/settings/billing/usage#cpu', + buttonText: 'Check usage', + }, + is_memory_and_swap_usage_beyond_threshold: { + title: + "Your project is currently facing high memory usage, and your instance's performance is affected", + description: + 'You will need to either optimize your performance or upgrade your compute to a larger instance', + docsUrl: 'https://supabase.com/docs/guides/platform/exhaust-ram', + correctionUrl: '/project/[ref]/settings/billing/usage#ram', + buttonText: 'Check usage', + }, + multiple_resource_warnings: { + title: + "Your project is currently exhausting multiple resources, and your instance's performance is affected", + description: "Check which resources are reaching their threshold on your project's usage page.", + docsUrl: undefined, + correctionUrl: '/project/[ref]/settings/billing/usage', + buttonText: 'Check usage', + }, +} + +const UsageWarningBanner = () => { const { ref } = useParams() - const organization = useSelectedOrganization() + const { data: resourceWarnings } = useResourceWarningsQuery() + const projectResourceWarnings = (resourceWarnings ?? [])?.find( + (warning) => warning.project === ref + ) + + const activeWarnings = + projectResourceWarnings !== undefined + ? Object.keys(projectResourceWarnings).filter( + (property) => + projectResourceWarnings[property as keyof typeof projectResourceWarnings] === true + ) + : [] + + const hasHighPriorityWarning = activeWarnings.includes('is_readonly_mode_enabled') + + const title = + activeWarnings.length > 1 + ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.title + : RESOURCE_WARNING_MESSAGES[activeWarnings[0] as keyof typeof RESOURCE_WARNING_MESSAGES] + ?.title + const description = + activeWarnings.length > 1 + ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.description + : RESOURCE_WARNING_MESSAGES[activeWarnings[0] as keyof typeof RESOURCE_WARNING_MESSAGES] + ?.description + const learnMoreUrl = + activeWarnings.length > 1 + ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.docsUrl + : RESOURCE_WARNING_MESSAGES[activeWarnings[0] as keyof typeof RESOURCE_WARNING_MESSAGES] + ?.docsUrl + const correctionUrl = ( + activeWarnings.length > 1 + ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.correctionUrl + : RESOURCE_WARNING_MESSAGES[activeWarnings[0] as keyof typeof RESOURCE_WARNING_MESSAGES] + ?.correctionUrl + )?.replace('[ref]', ref ?? 'default') + const buttonText = + activeWarnings.length > 1 + ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.buttonText + : RESOURCE_WARNING_MESSAGES[activeWarnings[0] as keyof typeof RESOURCE_WARNING_MESSAGES] + ?.buttonText + + if (activeWarnings.length === 0) return null return ( -
    - Project is experiencing high [product] usage. Learn more -
    + + + {title} + {description} +
    + {learnMoreUrl !== undefined && ( + + + + + + )} + {correctionUrl !== undefined && ( + + + + + + )} +
    +
    ) } + +export default UsageWarningBanner diff --git a/studio/data/usage/resource-warnings-query.ts b/studio/data/usage/resource-warnings-query.ts index 19a92acbe8d..d7ecd7c350e 100644 --- a/studio/data/usage/resource-warnings-query.ts +++ b/studio/data/usage/resource-warnings-query.ts @@ -22,5 +22,9 @@ export const useResourceWarningsQuery = ({ useQuery( usageKeys.resourceWarnings(), ({ signal }) => getResourceWarnings(signal), - { enabled, ...options } + { + enabled, + staleTime: 1000 * 60 * 30, // default 30 minutes + ...options, + } ) From 760500f7cd5f50a4c558cc238d5a56f6e879545b Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 23 Aug 2023 17:19:44 +0800 Subject: [PATCH 04/49] Remove all other queries that check project read only status --- .../Home/ProjectList/ProjectCard.tsx | 36 ++---- .../LayoutHeader/LayoutHeader.tsx | 22 +--- studio/data/config/project-read-only-query.ts | 60 ---------- .../projects/project-readonly-status-query.ts | 55 --------- .../projects-readonly-status-query.ts | 110 ------------------ studio/hooks/misc/useProjectReadOnlyStatus.ts | 9 -- 6 files changed, 11 insertions(+), 281 deletions(-) delete mode 100644 studio/data/config/project-read-only-query.ts delete mode 100644 studio/data/projects/project-readonly-status-query.ts delete mode 100644 studio/data/projects/projects-readonly-status-query.ts delete mode 100644 studio/hooks/misc/useProjectReadOnlyStatus.ts diff --git a/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx b/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx index 68532417f5d..430de0ce0d3 100644 --- a/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx +++ b/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx @@ -8,7 +8,6 @@ import { } from 'ui' import CardButton from 'components/ui/CardButton' -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' @@ -33,16 +32,10 @@ const ProjectCard = ({ const { name, ref: projectRef } = project 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 = true - const isGithubIntegrated = true - const isVercelIntegrated = true - const githubRepository = 'supabase/supabase-js' + const isBranchingEnabled = project.preview_branch_refs.length > 0 + const isGithubIntegrated = githubIntegration !== undefined + const isVercelIntegrated = vercelIntegration !== undefined + const githubRepository = githubIntegration?.metadata.name ?? undefined // Project status should supersede its read only status const isHealthy = project.status === PROJECT_STATUS.ACTIVE_HEALTHY @@ -98,22 +91,11 @@ const ProjectCard = ({ {desc} {isHealthy && ( - <> - {isReadonly ? ( - -
    - - Read-only mode -
    -
    - ) : ( - -
    - Active -
    -
    - )} - + +
    + Active +
    +
    )} {isRestoring && ( diff --git a/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx b/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx index cfd42ae8d16..96924e3d888 100644 --- a/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx +++ b/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx @@ -3,7 +3,6 @@ import Link from 'next/link' import { Badge } from 'ui' import { getResourcesExceededLimits } from 'components/ui/OveragesBanner/OveragesBanner.utils' -import { useProjectReadOnlyQuery } from 'data/config/project-read-only-query' import { useProjectUsageQuery } from 'data/usage/project-usage-query' import { useFlag, useSelectedOrganization, useSelectedProject } from 'hooks' import { IS_PLATFORM } from 'lib/constants' @@ -16,14 +15,9 @@ import ProjectDropdown from './ProjectDropdown' import { useProjectSubscriptionV2Query } from 'data/subscriptions/project-subscription-v2-query' const LayoutHeader = ({ customHeaderComponents, breadcrumbs = [], headerBorder = true }: any) => { - const selectedOrganization = useSelectedOrganization() - const selectedProject = useSelectedProject() - const { ref: projectRef } = useParams() - const { data: isReadOnlyMode } = useProjectReadOnlyQuery({ - projectRef: selectedProject?.ref, - connectionString: selectedProject?.connectionString, - }) + const selectedProject = useSelectedProject() + const selectedOrganization = useSelectedOrganization() // Skip with org-level-billing, as quota is for the entire org const { data: usage } = useProjectUsageQuery( @@ -81,18 +75,6 @@ const LayoutHeader = ({ customHeaderComponents, breadcrumbs = [], headerBorder = {/* Project Dropdown */} - {/* [Terry] Temporary until we figure out how we want to display this permanently */} - {/* context: https://www.notion.so/supabase/DB-Disk-Size-Free-tier-Read-only-Critical-f2b8937c13a149e3ac769fe5888f6db0*/} - {isReadOnlyMode && ( - - )} - {showOverUsageBadge && (
    diff --git a/studio/data/config/project-read-only-query.ts b/studio/data/config/project-read-only-query.ts deleted file mode 100644 index ebbdbe55768..00000000000 --- a/studio/data/config/project-read-only-query.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { UseQueryOptions } from '@tanstack/react-query' -import { useCallback } from 'react' -import { ExecuteSqlData, useExecuteSqlPrefetch, useExecuteSqlQuery } from '../sql/execute-sql-query' - -// TODO: temporary solution to check if project is in read only mode -// until we get an api endpoint for this - -export const getProjectReadOnlySql = () => { - const sql = /* SQL */ ` - show default_transaction_read_only; - ` - - return sql -} - -export type ProjectReadOnlyVariables = { - projectRef?: string - connectionString?: string -} - -export type ProjectReadOnlyData = boolean -export type ProjectReadOnlyError = unknown - -export const useProjectReadOnlyQuery = ( - { projectRef, connectionString }: ProjectReadOnlyVariables, - options: Omit< - UseQueryOptions, - 'select' - > = {} -) => - useExecuteSqlQuery( - { - projectRef, - connectionString, - sql: getProjectReadOnlySql(), - queryKey: ['project-read-only'], - }, - { - select(data) { - return data.result[0]?.default_transaction_read_only === 'on' - }, - enabled: typeof projectRef !== 'undefined' && typeof connectionString !== 'undefined', - ...options, - } - ) - -export const useProjectReadOnlyPrefetch = () => { - const prefetch = useExecuteSqlPrefetch() - - return useCallback( - ({ projectRef, connectionString }: ProjectReadOnlyVariables) => - prefetch({ - projectRef, - connectionString, - sql: getProjectReadOnlySql(), - queryKey: ['project-read-only'], - }), - [prefetch] - ) -} diff --git a/studio/data/projects/project-readonly-status-query.ts b/studio/data/projects/project-readonly-status-query.ts deleted file mode 100644 index bcd6274f02c..00000000000 --- a/studio/data/projects/project-readonly-status-query.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query' -import { get } from 'lib/common/fetch' -import { API_ADMIN_URL } from 'lib/constants' -import { useCallback } from 'react' -import { projectKeys } from './keys' - -export type ProjectReadOnlyStatusVariables = { projectRef?: string } -export type ProjectReadOnlyStatus = { - enabled: boolean - override_enabled: boolean - override_active_until: string -} - -export async function getProjectReadOnlyStatus( - { projectRef }: ProjectReadOnlyStatusVariables, - signal?: AbortSignal -) { - if (!projectRef) throw new Error('Project ref is required') - const data = await get(`${API_ADMIN_URL}/projects/${projectRef}/readonly`, { signal }) - if (data.error) throw data.error - return data as ProjectReadOnlyStatus -} - -export type ProjectReadOnlyStatusData = Awaited> -export type ProjectReadOnlyStatusError = unknown - -export const useProjectReadOnlyStatusQuery = ( - { projectRef }: ProjectReadOnlyStatusVariables, - { - enabled = true, - ...options - }: UseQueryOptions = {} -) => - useQuery( - projectKeys.readonlyStatus(projectRef), - ({ signal }) => getProjectReadOnlyStatus({ projectRef }, signal), - { - enabled: enabled && typeof projectRef !== 'undefined', - ...options, - } - ) - -export const useProjectReadOnlyStatusPrefetch = ({ - projectRef, -}: ProjectReadOnlyStatusVariables) => { - const client = useQueryClient() - - return useCallback(() => { - if (projectRef) { - client.prefetchQuery(projectKeys.readonlyStatus(projectRef), ({ signal }) => - getProjectReadOnlyStatus({ projectRef }, signal) - ) - } - }, [projectRef]) -} diff --git a/studio/data/projects/projects-readonly-status-query.ts b/studio/data/projects/projects-readonly-status-query.ts deleted file mode 100644 index 0cae487cb70..00000000000 --- a/studio/data/projects/projects-readonly-status-query.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { QueryClient, useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query' -import { useCallback, useRef } from 'react' - -import { get } from 'lib/common/fetch' -import { API_URL } from 'lib/constants' -import { Project, ResponseError } from 'types' -import { projectKeys } from './keys' - -export type ProjectsReadonlyStatusesVariables = { - ref?: string -} - -export async function getProjectsReadonlyStatuses(signal?: AbortSignal) { - const data = await get(`${API_URL}/projects?include_readonly_mode_status=true`, { signal }) - if (data.error) throw data.error - - return Object.fromEntries( - data.map((project: any) => [project.ref, project.is_readonly_mode_enabled]) - ) as { - [k: string]: boolean - } -} - -export type ProjectsReadonlyStatusesData = Awaited> -export type ProjectsReadonlyStatusesError = ResponseError - -export const useProjectsReadonlyStatusesQuery = ({ - enabled = true, - ...options -}: UseQueryOptions = {}) => - useQuery( - projectKeys.readonlyStatusList(), - ({ signal }) => getProjectsReadonlyStatuses(signal), - { enabled: enabled, ...options } - ) - -export function prefetchProjectsReadonlyStatuses(client: QueryClient) { - return client.prefetchQuery(projectKeys.readonlyStatusList(), ({ signal }) => - getProjectsReadonlyStatuses(signal) - ) -} - -export function useProjectsReadonlyStatusesPrefetch() { - const client = useQueryClient() - - return useCallback(() => { - prefetchProjectsReadonlyStatuses(client) - }, [client]) -} - -export function useAutoProjectsReadonlyStatusesPrefetch() { - const prefetch = useProjectsReadonlyStatusesPrefetch() - - const called = useRef(false) - if (called.current === false) { - called.current = true - prefetch() - } -} - -export function invalidateProjectsReadonlyStatusesQuery(client: QueryClient) { - return client.invalidateQueries(projectKeys.readonlyStatusList()) -} - -export function setProjectStatus( - client: QueryClient, - projectRef: Project['ref'], - status: Project['status'] -) { - client.setQueriesData( - projectKeys.readonlyStatusList(), - (old) => { - if (!old) return old - - return old.map((project) => { - if (project.ref === projectRef) { - return { ...project, status } - } - return project - }) - }, - { updatedAt: Date.now() } - ) - - client.setQueriesData( - projectKeys.detail(projectRef), - (old) => { - if (!old) return old - - return { ...old, status } - }, - { updatedAt: Date.now() } - ) -} - -export function setProjectPostgrestStatus( - client: QueryClient, - projectRef: Project['ref'], - status: Project['postgrestStatus'] -) { - client.setQueriesData( - projectKeys.detail(projectRef), - (old) => { - if (!old) return old - - return { ...old, postgrestStatus: status } - }, - { updatedAt: Date.now() } - ) -} diff --git a/studio/hooks/misc/useProjectReadOnlyStatus.ts b/studio/hooks/misc/useProjectReadOnlyStatus.ts deleted file mode 100644 index 4c889b54528..00000000000 --- a/studio/hooks/misc/useProjectReadOnlyStatus.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { useProjectsReadonlyStatusesQuery } from 'data/projects/projects-readonly-status-query' - -export function useProjectReadOnlyStatus(projectRef?: string) { - const { data } = useProjectsReadonlyStatusesQuery() - - if (!projectRef) return false - - return data?.[projectRef] ?? false -} From 68a305201d7ce3d69980fc2995b4d9ef4bca7cef Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 23 Aug 2023 17:45:18 +0800 Subject: [PATCH 05/49] Read only error takes higher precendence over multiple resource warnings --- .../Home/ProjectList/ProjectCardWarnings.tsx | 14 +++++++++----- .../ui/UsageWarnings/UsageWarningBanner.tsx | 11 +++++++---- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx b/studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx index 887230179ed..5453f2df74b 100644 --- a/studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx +++ b/studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx @@ -34,12 +34,14 @@ const RESOURCE_WARNING_MESSAGES = { }, } -export default function ProjectCardWarnings({ resourceWarnings }: ProjectCardWarningsProps) { - const activeWarnings = Object.keys(resourceWarnings).filter( - (property) => resourceWarnings[property as keyof typeof resourceWarnings] === true - ) +const ProjectCardWarnings = ({ resourceWarnings }: ProjectCardWarningsProps) => { + // [Joshen] Read only takes higher precendence over multiple resource warnings + const activeWarnings = resourceWarnings.is_readonly_mode_enabled + ? ['is_readonly_mode_enabled'] + : Object.keys(resourceWarnings).filter( + (property) => resourceWarnings[property as keyof typeof resourceWarnings] === true + ) const hasHighPriorityWarning = activeWarnings.includes('is_readonly_mode_enabled') - // [Joshen] We should probably read readonly mode separately for multiple resource warnings, read only mode needs to take highest precedence return (
    @@ -66,3 +68,5 @@ export default function ProjectCardWarnings({ resourceWarnings }: ProjectCardWar
    ) } + +export default ProjectCardWarnings diff --git a/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx b/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx index 48656d4a197..aab7bd51390 100644 --- a/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx +++ b/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx @@ -73,12 +73,15 @@ const UsageWarningBanner = () => { (warning) => warning.project === ref ) + // [Joshen] Read only takes higher precendence over multiple resource warnings const activeWarnings = projectResourceWarnings !== undefined - ? Object.keys(projectResourceWarnings).filter( - (property) => - projectResourceWarnings[property as keyof typeof projectResourceWarnings] === true - ) + ? projectResourceWarnings.is_readonly_mode_enabled + ? ['is_readonly_mode_enabled'] + : Object.keys(projectResourceWarnings).filter( + (property) => + projectResourceWarnings[property as keyof typeof projectResourceWarnings] === true + ) : [] const hasHighPriorityWarning = activeWarnings.includes('is_readonly_mode_enabled') From 1daa80e15c7bc32cf1ddddfa9c1f2d6c8589d292 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Mon, 28 Aug 2023 16:21:44 +0800 Subject: [PATCH 06/49] Scaffold warnings in usage project usage page for CPU and RAM --- .../BillingV2/Usage/Infrastructure.tsx | 107 ++++-------------- .../interfaces/BillingV2/Usage/Usage.tsx | 9 +- .../Usage/UsageWarningAlerts/CPUWarnings.tsx | 74 ++++++++++++ .../DiskIOBandwidthWarnings.tsx | 106 +++++++++++++++++ .../Usage/UsageWarningAlerts/RAMWarnings.tsx | 74 ++++++++++++ .../Usage/UsageWarningAlerts/index.ts | 5 + .../Home/ProjectList/ProjectCardWarnings.tsx | 2 +- .../layouts/ProjectLayout/BuildingState.tsx | 15 ++- .../layouts/ProjectLayout/ProjectLayout.tsx | 2 +- .../UsageWarningBanner.constants.ts | 54 +++++++++ .../ui/UsageWarnings/UsageWarningBanner.tsx | 62 +--------- 11 files changed, 355 insertions(+), 155 deletions(-) create mode 100644 studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/CPUWarnings.tsx create mode 100644 studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/DiskIOBandwidthWarnings.tsx create mode 100644 studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/RAMWarnings.tsx create mode 100644 studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/index.ts create mode 100644 studio/components/ui/UsageWarnings/UsageWarningBanner.constants.ts diff --git a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx index f4314f551be..60d929e28fb 100644 --- a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx @@ -1,18 +1,19 @@ +import dayjs from 'dayjs' +import { IconBarChart2 } from 'ui' + +import Panel from 'components/ui/Panel' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { DataPoint } from 'data/analytics/constants' import { useInfraMonitoringQuery } from 'data/analytics/infra-monitoring-query' -import dayjs from 'dayjs' -import Link from 'next/link' -import { Alert, Button, IconBarChart2 } from 'ui' +import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' +import { useProjectSubscriptionV2Query } from 'data/subscriptions/project-subscription-v2-query' +import { getAddons } from '../Subscription/Subscription.utils' import SectionContent from './SectionContent' import SectionHeader from './SectionHeader' import { USAGE_CATEGORIES } from './Usage.constants' import { getUpgradeUrl } from './Usage.utils' import UsageBarChart from './UsageBarChart' -import Panel from 'components/ui/Panel' -import { useProjectSubscriptionV2Query } from 'data/subscriptions/project-subscription-v2-query' -import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' -import { getAddons } from '../Subscription/Subscription.utils' +import { CPUWarnings, DiskIOBandwidthWarnings, RAMWarnings } from './UsageWarningAlerts' export interface InfrastructureProps { projectRef: string @@ -28,7 +29,6 @@ const Infrastructure = ({ currentBillingCycleSelected, }: InfrastructureProps) => { const { data: subscription } = useProjectSubscriptionV2Query({ projectRef }) - const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'infra') const upgradeUrl = getUpgradeUrl(projectRef, subscription) @@ -125,81 +125,14 @@ const Infrastructure = ({ {attribute.key === 'disk_io_consumption' && ( <> - {hasLatest && latestIoBudgetConsumption >= 100 ? ( - -

    - Your workload has used up all your Disk IO Budget and is now running at the - baseline performance. If you need consistent disk performance, consider - upgrading to a larger compute add-on. -

    - - - - - -
    - ) : hasLatest && latestIoBudgetConsumption >= 80 ? ( - -

    - Your workload has consumed {latestIoBudgetConsumption}% of your Disk IO - Budget. If you use up all your Disk IO Budget, your instance will reverted - to baseline performance. If you need consistent disk performance, consider - upgrading to a larger compute add-on. -

    - - - - - -
    - ) : currentBillingCycleSelected && highestIoBudgetConsumption >= 100 ? ( - -

    - Your workload has used up all your Disk IO Budget and reverted to baseline - performance at least once during this billing cycle. If you need consistent - disk performance, consider upgrading to a larger compute add-on. -

    - - - - - -
    - ) : currentBillingCycleSelected && highestIoBudgetConsumption >= 80 ? ( - -

    - Your workload has consumed {highestIoBudgetConsumption}% of your Disk IO - budget during this billing cycle. If you use up all your Disk IO Budget, - your instance will reverted to baseline performance. If you need consistent - disk performance, consider upgrading to a larger compute add-on. -

    - - - - - -
    - ) : null} +

    Disk IO Bandwidth

    @@ -246,7 +179,13 @@ const Infrastructure = ({
    )} - {/* [Joshen TODO] Add the necessary alert warnings for CPU and RAM here as well */} + {attribute.key === 'max_cpu_usage' && ( + + )} + {attribute.key === 'ram_usage' && ( + + )} +
    {attribute.key === 'disk_io_consumption' ? ( diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.tsx b/studio/components/interfaces/BillingV2/Usage/Usage.tsx index cd61a2d5889..456e3de8db6 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Usage.tsx @@ -1,15 +1,16 @@ import clsx from 'clsx' import { useParams } from 'common' +import dayjs from 'dayjs' +import { useMemo, useRef, useState } from 'react' +import { InView } from 'react-intersection-observer' +import { IconAlertCircle, IconLoader, cn } from 'ui' + import DateRangePicker from 'components/to-be-cleaned/DateRangePicker' import { useInfraMonitoringQuery } from 'data/analytics/infra-monitoring-query' import { useProjectSubscriptionV2Query } from 'data/subscriptions/project-subscription-v2-query' import { useProjectUsageQuery } from 'data/usage/project-usage-query' -import dayjs from 'dayjs' import { useSelectedOrganization } from 'hooks' import { TIME_PERIODS_BILLING, TIME_PERIODS_REPORTS } from 'lib/constants' -import { useMemo, useRef, useState } from 'react' -import { InView } from 'react-intersection-observer' -import { IconAlertCircle, IconLoader, cn } from 'ui' import Activity from './Activity' import Bandwidth from './Bandwidth' import Infrastructure from './Infrastructure' diff --git a/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/CPUWarnings.tsx b/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/CPUWarnings.tsx new file mode 100644 index 00000000000..fe6a94f102d --- /dev/null +++ b/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/CPUWarnings.tsx @@ -0,0 +1,74 @@ +import { AlertTitle } from '@ui/components/shadcn/ui/alert' +import Link from 'next/link' +import { AlertDescription_Shadcn_, Alert_Shadcn_, Button, IconAlertCircle } from 'ui' + +interface CPUWarningsProps { + isFreeTier: boolean + upgradeUrl: string +} + +const CPUWarnings = ({ isFreeTier, upgradeUrl }: CPUWarningsProps) => { + // [Joshen TODO] Need to implement the necessary conditionals for rendering the warnings + const isApproaching = false + const isExceeded = false + + if (isApproaching) { + return ( + + + Your max CPU usage has exceeded 80% + + High CPU usage could result in slower queries, disruption of daily back routines, and in + rare cases, your instance may become unresponsive. If you need more resources, consider + upgrading to a larger compute add-on. + + + + ) + } + + if (isExceeded) { + return ( + + + Your max CPU usage has reached 100% + + High CPU usage could result in slower queries, disruption of daily back routines, and in + rare cases, your instance may become unresponsive. If you need more resources, consider + upgrading to a larger compute add-on. + + + + ) + } + + return null +} + +export default CPUWarnings diff --git a/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/DiskIOBandwidthWarnings.tsx b/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/DiskIOBandwidthWarnings.tsx new file mode 100644 index 00000000000..0d092e75878 --- /dev/null +++ b/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/DiskIOBandwidthWarnings.tsx @@ -0,0 +1,106 @@ +import Link from 'next/link' +import { Alert, Button } from 'ui' + +// [Joshen] In the future, conditionals should be from resource exhaustion endpoint as single source of truth +interface DiskIOBandwidthWarningsProps { + isFreeTier: boolean + hasLatest: boolean + upgradeUrl: string + currentBillingCycleSelected: boolean + latestIoBudgetConsumption: number + highestIoBudgetConsumption: number +} + +const DiskIOBandwidthWarnings = ({ + isFreeTier, + hasLatest, + currentBillingCycleSelected, + upgradeUrl, + latestIoBudgetConsumption, + highestIoBudgetConsumption, +}: DiskIOBandwidthWarningsProps) => { + if (hasLatest && latestIoBudgetConsumption >= 100) { + return ( + +

    + Your workload has used up all your Disk IO Budget and is now running at the baseline + performance. If you need consistent disk performance, consider upgrading to a larger + compute add-on. +

    + + + + + +
    + ) + } + + if (hasLatest && latestIoBudgetConsumption >= 80) { + return ( + +

    + Your workload has consumed {latestIoBudgetConsumption}% of your Disk IO Budget. If you use + up all your Disk IO Budget, your instance will reverted to baseline performance. If you + need consistent disk performance, consider upgrading to a larger compute add-on. +

    + + + + + +
    + ) + } + + if (currentBillingCycleSelected && highestIoBudgetConsumption >= 100) { + return ( + +

    + Your workload has used up all your Disk IO Budget and reverted to baseline performance at + least once during this billing cycle. If you need consistent disk performance, consider + upgrading to a larger compute add-on. +

    + + + + + +
    + ) + } + + if (currentBillingCycleSelected && highestIoBudgetConsumption >= 80) { + return ( + +

    + Your workload has consumed {highestIoBudgetConsumption}% of your Disk IO budget during + this billing cycle. If you use up all your Disk IO Budget, your instance will reverted to + baseline performance. If you need consistent disk performance, consider upgrading to a + larger compute add-on. +

    + + + + + +
    + ) + } + + return null +} + +export default DiskIOBandwidthWarnings diff --git a/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/RAMWarnings.tsx b/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/RAMWarnings.tsx new file mode 100644 index 00000000000..0a106669311 --- /dev/null +++ b/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/RAMWarnings.tsx @@ -0,0 +1,74 @@ +import { AlertTitle } from '@ui/components/shadcn/ui/alert' +import Link from 'next/link' +import { AlertDescription_Shadcn_, Alert_Shadcn_, Button, IconAlertCircle } from 'ui' + +interface RAMWarningsProps { + isFreeTier: boolean + upgradeUrl: string +} + +// [Joshen TODO] Need to implement the necessary conditionals for rendering the warnings +const RAMWarnings = ({ isFreeTier, upgradeUrl }: RAMWarningsProps) => { + const isApproaching = true + const isExceeded = false + + if (isApproaching) { + return ( + + + Your memory usage has exceeded 80% + + High memory usage could result in overall degraded performance, and in rare cases, your + instance may become unresponsive. If you need more resources, consider upgrading to a + larger compute add-on. + + + + ) + } + + if (isExceeded) { + return ( + + + Your memory usage has reached 100% + + High memory usage could result in overall degraded performance, and in rare cases, your + instance may become unresponsive. If you need more resources, consider upgrading to a + larger compute add-on. + + + + ) + } + + return null +} + +export default RAMWarnings diff --git a/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/index.ts b/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/index.ts new file mode 100644 index 00000000000..3b3afe3f30e --- /dev/null +++ b/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/index.ts @@ -0,0 +1,5 @@ +import CPUWarnings from './CPUWarnings' +import DiskIOBandwidthWarnings from './DiskIOBandwidthWarnings' +import RAMWarnings from './RAMWarnings' + +export { CPUWarnings, DiskIOBandwidthWarnings, RAMWarnings } diff --git a/studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx b/studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx index 5453f2df74b..a0d7b2bbd85 100644 --- a/studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx +++ b/studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx @@ -35,7 +35,7 @@ const RESOURCE_WARNING_MESSAGES = { } const ProjectCardWarnings = ({ resourceWarnings }: ProjectCardWarningsProps) => { - // [Joshen] Read only takes higher precendence over multiple resource warnings + // [Joshen] Read only takes higher precedence over multiple resource warnings const activeWarnings = resourceWarnings.is_readonly_mode_enabled ? ['is_readonly_mode_enabled'] : Object.keys(resourceWarnings).filter( diff --git a/studio/components/layouts/ProjectLayout/BuildingState.tsx b/studio/components/layouts/ProjectLayout/BuildingState.tsx index 42418ba43ab..733d04fbe93 100644 --- a/studio/components/layouts/ProjectLayout/BuildingState.tsx +++ b/studio/components/layouts/ProjectLayout/BuildingState.tsx @@ -6,20 +6,17 @@ import Link from 'next/link' import { useEffect, useRef } from 'react' import { Badge, Button, IconArrowRight, IconLoader } from 'ui' +import { useParams } from 'common' import { DisplayApiSettings, DisplayConfigSettings } from 'components/ui/ProjectSettings' +import { invalidateProjectDetailsQuery } from 'data/projects/project-detail-query' import { invalidateProjectsQuery } from 'data/projects/projects-query' +import { useSelectedProject } from 'hooks' import { getWithTimeout } from 'lib/common/fetch' import { API_URL, PROJECT_STATUS } from 'lib/constants' -import { Project } from 'types' -import { invalidateProjectDetailsQuery } from 'data/projects/project-detail-query' -import { useParams } from 'common' -export interface BuildingStateProps { - project: Project -} - -const BuildingState = ({ project }: BuildingStateProps) => { +const BuildingState = () => { const { ref } = useParams() + const project = useSelectedProject() const queryClient = useQueryClient() const checkServerInterval = useRef() @@ -48,6 +45,8 @@ const BuildingState = ({ project }: BuildingStateProps) => { } }, []) + if (project === undefined) return null + return (
    diff --git a/studio/components/layouts/ProjectLayout/ProjectLayout.tsx b/studio/components/layouts/ProjectLayout/ProjectLayout.tsx index 37ad6281554..adeba22da72 100644 --- a/studio/components/layouts/ProjectLayout/ProjectLayout.tsx +++ b/studio/components/layouts/ProjectLayout/ProjectLayout.tsx @@ -191,7 +191,7 @@ const ContentWrapper = ({ isLoading, children }: ContentWrapperProps) => { ) : requiresDbConnection && isProjectRestoring ? ( ) : requiresDbConnection && isProjectBuilding ? ( - + ) : ( <> diff --git a/studio/components/ui/UsageWarnings/UsageWarningBanner.constants.ts b/studio/components/ui/UsageWarnings/UsageWarningBanner.constants.ts new file mode 100644 index 00000000000..2a6ca960637 --- /dev/null +++ b/studio/components/ui/UsageWarnings/UsageWarningBanner.constants.ts @@ -0,0 +1,54 @@ +// [TODO] Just double check if the docs url and correction urls are all correct +export const RESOURCE_WARNING_MESSAGES = { + is_readonly_mode_enabled: { + title: 'Your project is currently in readonly mode and is no longer accepting write requests', + description: + 'You will need to manually override read-only mode and reduce the disk size to below 95%.', + docsUrl: 'https://supabase.com/docs/guides/platform/database-size#disabling-read-only-mode', + correctionUrl: undefined, + buttonText: undefined, + }, + is_disk_io_budget_below_threshold: { + title: + 'Your project is about to deplete its Disk IO Budget, and your instance may become unresponsive once fully exhausted', + description: + 'You will need to either optimize your performance, or upgrade your compute to a larger instance.', + docsUrl: 'https://supabase.com/docs/guides/platform/exhaust-disk-io', + correctionUrl: '/project/[ref]/settings/billing/usage#disk_io', + buttonText: 'Check usage', + }, + is_disk_space_usage_beyond_threshold: { + title: + 'Your project is about to exhaust its disk space budget, and your instance may become unresponsive once fully exhausted', + description: 'Some CTA description here', + docsUrl: undefined, + correctionUrl: undefined, + buttonText: undefined, + }, + is_cpu_load_beyond_threshold: { + title: + "Your project is currently facing high CPU usage, and your instance's performance is affected", + description: + 'You will need to either optimize your performance or upgrade your compute to a larger instance', + docsUrl: 'https://supabase.com/docs/guides/platform/exhaust-cpu', + correctionUrl: '/project/[ref]/settings/billing/usage#cpu', + buttonText: 'Check usage', + }, + is_memory_and_swap_usage_beyond_threshold: { + title: + "Your project is currently facing high memory usage, and your instance's performance is affected", + description: + 'You will need to either optimize your performance or upgrade your compute to a larger instance', + docsUrl: 'https://supabase.com/docs/guides/platform/exhaust-ram', + correctionUrl: '/project/[ref]/settings/billing/usage#ram', + buttonText: 'Check usage', + }, + multiple_resource_warnings: { + title: + "Your project is currently exhausting multiple resources, and your instance's performance is affected", + description: "Check which resources are reaching their threshold on your project's usage page.", + docsUrl: undefined, + correctionUrl: '/project/[ref]/settings/billing/usage', + buttonText: 'Check usage', + }, +} diff --git a/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx b/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx index aab7bd51390..c3672308ae0 100644 --- a/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx +++ b/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx @@ -10,70 +10,18 @@ import { } from 'ui' import { useResourceWarningsQuery } from 'data/usage/resource-warnings-query' - -// [TODO] Just double check if the docs url and correction urls are all correct -const RESOURCE_WARNING_MESSAGES = { - is_readonly_mode_enabled: { - title: 'Your project is currently in readonly mode and is no longer accepting write requests', - description: - 'You will need to manually override read-only mode and reduce the disk size to below 95%.', - docsUrl: 'https://supabase.com/docs/guides/platform/database-size#disabling-read-only-mode', - correctionUrl: undefined, - buttonText: undefined, - }, - is_disk_io_budget_below_threshold: { - title: - 'Your project is about to deplete its Disk IO Budget, and your instance may become unresponsive once fully exhausted', - description: - 'You will need to either optimize your performance, or upgrade your compute to a larger instance.', - docsUrl: 'https://supabase.com/docs/guides/platform/exhaust-disk-io', - correctionUrl: '/project/[ref]/settings/billing/usage#disk_io', - buttonText: 'Check usage', - }, - is_disk_space_usage_beyond_threshold: { - title: - 'Your project is about to exhaust its disk space budget, and your instance may become unresponsive once fully exhausted', - description: 'Some CTA description here', - docsUrl: undefined, - correctionUrl: undefined, - buttonText: undefined, - }, - is_cpu_load_beyond_threshold: { - title: - "Your project is currently facing high CPU usage, and your instance's performance is affected", - description: - 'You will need to either optimize your performance or upgrade your compute to a larger instance', - docsUrl: 'https://supabase.com/docs/guides/platform/exhaust-cpu', - correctionUrl: '/project/[ref]/settings/billing/usage#cpu', - buttonText: 'Check usage', - }, - is_memory_and_swap_usage_beyond_threshold: { - title: - "Your project is currently facing high memory usage, and your instance's performance is affected", - description: - 'You will need to either optimize your performance or upgrade your compute to a larger instance', - docsUrl: 'https://supabase.com/docs/guides/platform/exhaust-ram', - correctionUrl: '/project/[ref]/settings/billing/usage#ram', - buttonText: 'Check usage', - }, - multiple_resource_warnings: { - title: - "Your project is currently exhausting multiple resources, and your instance's performance is affected", - description: "Check which resources are reaching their threshold on your project's usage page.", - docsUrl: undefined, - correctionUrl: '/project/[ref]/settings/billing/usage', - buttonText: 'Check usage', - }, -} +import { RESOURCE_WARNING_MESSAGES } from './UsageWarningBanner.constants' +import { useRouter } from 'next/router' const UsageWarningBanner = () => { const { ref } = useParams() + const router = useRouter() const { data: resourceWarnings } = useResourceWarningsQuery() const projectResourceWarnings = (resourceWarnings ?? [])?.find( (warning) => warning.project === ref ) - // [Joshen] Read only takes higher precendence over multiple resource warnings + // [Joshen] Read only takes higher precedence over multiple resource warnings const activeWarnings = projectResourceWarnings !== undefined ? projectResourceWarnings.is_readonly_mode_enabled @@ -113,7 +61,7 @@ const UsageWarningBanner = () => { : RESOURCE_WARNING_MESSAGES[activeWarnings[0] as keyof typeof RESOURCE_WARNING_MESSAGES] ?.buttonText - if (activeWarnings.length === 0) return null + if (activeWarnings.length === 0 || router.pathname.includes('/usage')) return null return ( Date: Mon, 28 Aug 2023 17:09:42 +0800 Subject: [PATCH 07/49] Add warnings for CPU and RAM in Infrastructure and INfrastructureActivity, and fix link from UsageWarningBanner chekc usage CTA --- .../BillingV2/Subscription/AddOns/AddOns.tsx | 4 +- .../BillingV2/Usage/Infrastructure.tsx | 21 ++- .../Usage/UsageWarningAlerts/CPUWarnings.tsx | 22 ++-- .../DiskIOBandwidthWarnings.tsx | 12 +- .../Usage/UsageWarningAlerts/RAMWarnings.tsx | 14 +- .../interfaces/Settings/Addons/Addons.tsx | 20 +-- .../Infrastructure/InfrastructureActivity.tsx | 123 ++++++++++-------- .../UsageWarningBanner.constants.ts | 13 +- .../ui/UsageWarnings/UsageWarningBanner.tsx | 30 ++++- 9 files changed, 158 insertions(+), 101 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Subscription/AddOns/AddOns.tsx b/studio/components/interfaces/BillingV2/Subscription/AddOns/AddOns.tsx index 3d7c6d34e0d..444317ed056 100644 --- a/studio/components/interfaces/BillingV2/Subscription/AddOns/AddOns.tsx +++ b/studio/components/interfaces/BillingV2/Subscription/AddOns/AddOns.tsx @@ -22,9 +22,7 @@ import { } from 'components/layouts/ProjectLayout/ProjectContext' import { getCloudProviderArchitecture } from 'lib/cloudprovider-utils' -export interface AddOnsProps {} - -const AddOns = ({}: AddOnsProps) => { +const AddOns = () => { const { ref: projectRef } = useParams() const snap = useSubscriptionPageStateSnapshot() const projectUpdateDisabled = useFlag('disableProjectCreationAndUpdate') diff --git a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx index 60d929e28fb..2789f1e139b 100644 --- a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx @@ -14,6 +14,7 @@ import { USAGE_CATEGORIES } from './Usage.constants' import { getUpgradeUrl } from './Usage.utils' import UsageBarChart from './UsageBarChart' import { CPUWarnings, DiskIOBandwidthWarnings, RAMWarnings } from './UsageWarningAlerts' +import { useResourceWarningsQuery } from 'data/usage/resource-warnings-query' export interface InfrastructureProps { projectRef: string @@ -29,10 +30,12 @@ const Infrastructure = ({ currentBillingCycleSelected, }: InfrastructureProps) => { const { data: subscription } = useProjectSubscriptionV2Query({ projectRef }) + const { data: resourceWarnings } = useResourceWarningsQuery() + const projectResourceWarnings = resourceWarnings?.find((x) => x.project === projectRef) const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'infra') const upgradeUrl = getUpgradeUrl(projectRef, subscription) - const isFreeTier = subscription?.plan?.id === 'free' + const isFreePlan = subscription?.plan?.id === 'free' const { data: addons, isLoading } = useProjectAddonsQuery({ projectRef }) const selectedAddons = addons?.selected_addons ?? [] @@ -127,7 +130,7 @@ const Infrastructure = ({ <> )} {attribute.key === 'max_cpu_usage' && ( - + )} {attribute.key === 'ram_usage' && ( - + )}
    diff --git a/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/CPUWarnings.tsx b/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/CPUWarnings.tsx index fe6a94f102d..818e649f4ac 100644 --- a/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/CPUWarnings.tsx +++ b/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/CPUWarnings.tsx @@ -3,13 +3,15 @@ import Link from 'next/link' import { AlertDescription_Shadcn_, Alert_Shadcn_, Button, IconAlertCircle } from 'ui' interface CPUWarningsProps { - isFreeTier: boolean + isFreePlan: boolean upgradeUrl: string + isBeyondThreshold: boolean } -const CPUWarnings = ({ isFreeTier, upgradeUrl }: CPUWarningsProps) => { - // [Joshen TODO] Need to implement the necessary conditionals for rendering the warnings - const isApproaching = false +const CPUWarnings = ({ isFreePlan, upgradeUrl, isBeyondThreshold }: CPUWarningsProps) => { + // [Joshen TODO] Need to implement the necessary conditionals for rendering the warnings once + // endpoint is updated to return text instead of boolean + const isApproaching = isBeyondThreshold const isExceeded = false if (isApproaching) { @@ -18,8 +20,8 @@ const CPUWarnings = ({ isFreeTier, upgradeUrl }: CPUWarningsProps) => { Your max CPU usage has exceeded 80% - High CPU usage could result in slower queries, disruption of daily back routines, and in - rare cases, your instance may become unresponsive. If you need more resources, consider + High CPU usage could result in slower queries, disruption of daily back up routines, and + in rare cases, your instance may become unresponsive. If you need more resources, consider upgrading to a larger compute add-on.
    @@ -31,7 +33,7 @@ const CPUWarnings = ({ isFreeTier, upgradeUrl }: CPUWarningsProps) => { @@ -46,8 +48,8 @@ const CPUWarnings = ({ isFreeTier, upgradeUrl }: CPUWarningsProps) => { Your max CPU usage has reached 100% - High CPU usage could result in slower queries, disruption of daily back routines, and in - rare cases, your instance may become unresponsive. If you need more resources, consider + High CPU usage could result in slower queries, disruption of daily back up routines, and + in rare cases, your instance may become unresponsive. If you need more resources, consider upgrading to a larger compute add-on.
    @@ -59,7 +61,7 @@ const CPUWarnings = ({ isFreeTier, upgradeUrl }: CPUWarningsProps) => { diff --git a/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/DiskIOBandwidthWarnings.tsx b/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/DiskIOBandwidthWarnings.tsx index 0d092e75878..6d257ac3f10 100644 --- a/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/DiskIOBandwidthWarnings.tsx +++ b/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/DiskIOBandwidthWarnings.tsx @@ -3,7 +3,7 @@ import { Alert, Button } from 'ui' // [Joshen] In the future, conditionals should be from resource exhaustion endpoint as single source of truth interface DiskIOBandwidthWarningsProps { - isFreeTier: boolean + isFreePlan: boolean hasLatest: boolean upgradeUrl: string currentBillingCycleSelected: boolean @@ -12,7 +12,7 @@ interface DiskIOBandwidthWarningsProps { } const DiskIOBandwidthWarnings = ({ - isFreeTier, + isFreePlan, hasLatest, currentBillingCycleSelected, upgradeUrl, @@ -30,7 +30,7 @@ const DiskIOBandwidthWarnings = ({ @@ -49,7 +49,7 @@ const DiskIOBandwidthWarnings = ({ @@ -68,7 +68,7 @@ const DiskIOBandwidthWarnings = ({ @@ -92,7 +92,7 @@ const DiskIOBandwidthWarnings = ({ diff --git a/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/RAMWarnings.tsx b/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/RAMWarnings.tsx index 0a106669311..8006c3d079d 100644 --- a/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/RAMWarnings.tsx +++ b/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/RAMWarnings.tsx @@ -3,13 +3,15 @@ import Link from 'next/link' import { AlertDescription_Shadcn_, Alert_Shadcn_, Button, IconAlertCircle } from 'ui' interface RAMWarningsProps { - isFreeTier: boolean + isFreePlan: boolean upgradeUrl: string + isBeyondThreshold: boolean } -// [Joshen TODO] Need to implement the necessary conditionals for rendering the warnings -const RAMWarnings = ({ isFreeTier, upgradeUrl }: RAMWarningsProps) => { - const isApproaching = true +const RAMWarnings = ({ isFreePlan, upgradeUrl, isBeyondThreshold }: RAMWarningsProps) => { + // [Joshen TODO] Need to implement the necessary conditionals for rendering the warnings once + // endpoint is updated to return text instead of boolean + const isApproaching = isBeyondThreshold const isExceeded = false if (isApproaching) { @@ -31,7 +33,7 @@ const RAMWarnings = ({ isFreeTier, upgradeUrl }: RAMWarningsProps) => { @@ -59,7 +61,7 @@ const RAMWarnings = ({ isFreeTier, upgradeUrl }: RAMWarningsProps) => { diff --git a/studio/components/interfaces/Settings/Addons/Addons.tsx b/studio/components/interfaces/Settings/Addons/Addons.tsx index 88035a87b70..edff68aac31 100644 --- a/studio/components/interfaces/Settings/Addons/Addons.tsx +++ b/studio/components/interfaces/Settings/Addons/Addons.tsx @@ -134,14 +134,14 @@ const Addons = () => {
    -

    Optimized compute

    +

    Optimized compute

    -

    More information

    +

    More information

    -

    About compute add-ons

    +

    About compute add-ons

    @@ -151,7 +151,7 @@ const Addons = () => {
    -

    Connection Pooler

    +

    Connection Pooler

    @@ -339,14 +339,14 @@ const Addons = () => {
    -

    Point in time recovery

    +

    Point in time recovery

    -

    More information

    +

    More information

    -

    About PITR backups

    +

    About PITR backups

    @@ -403,14 +403,14 @@ const Addons = () => {
    -

    Custom domain

    +

    Custom domain

    -

    More information

    +

    More information

    -

    About custom domains

    +

    About custom domains

    diff --git a/studio/components/interfaces/Settings/Infrastructure/InfrastructureActivity.tsx b/studio/components/interfaces/Settings/Infrastructure/InfrastructureActivity.tsx index caacdeee269..28e6dce7415 100644 --- a/studio/components/interfaces/Settings/Infrastructure/InfrastructureActivity.tsx +++ b/studio/components/interfaces/Settings/Infrastructure/InfrastructureActivity.tsx @@ -1,8 +1,16 @@ +import { useParams } from 'common' import dayjs from 'dayjs' import Link from 'next/link' import { Fragment, useMemo, useState } from 'react' +import { IconBarChart2, IconExternalLink } from 'ui' -import { useParams } from 'common' +import { getAddons } from 'components/interfaces/BillingV2/Subscription/Subscription.utils' +import { + CPUWarnings, + DiskIOBandwidthWarnings, + RAMWarnings, +} from 'components/interfaces/BillingV2/Usage/UsageWarningAlerts' +import UsageBarChart from 'components/interfaces/Organization/Usage/UsageBarChart' import { ScaffoldContainer, ScaffoldDivider, @@ -19,10 +27,8 @@ import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-que import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' import { useSelectedOrganization } from 'hooks' import { TIME_PERIODS_BILLING, TIME_PERIODS_REPORTS } from 'lib/constants' -import { Alert, Button, IconBarChart2, IconExternalLink } from 'ui' -import { getAddons } from '../../BillingV2/Subscription/Subscription.utils' -import UsageBarChart from '../../Organization/Usage/UsageBarChart' import { INFRA_ACTIVITY_METRICS } from './Infrastructure.constants' +import { useResourceWarningsQuery } from 'data/usage/resource-warnings-query' const InfrastructureActivity = () => { const { ref: projectRef } = useParams() @@ -30,17 +36,17 @@ const InfrastructureActivity = () => { const isOrgBilling = !!organization?.subscription_id const [dateRange, setDateRange] = useState() - const currentBillingCycleSelected = true - const upgradeUrl = '/' - const categoryMeta = INFRA_ACTIVITY_METRICS.find((category) => category.key === 'infra') - const { data: subscription, isLoading: isLoadingSubscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug, }) const isFreePlan = subscription?.plan?.id === 'free' + const { data: resourceWarnings } = useResourceWarningsQuery() + const projectResourceWarnings = resourceWarnings?.find((x) => x.project === projectRef) + const { data: addons, isLoading } = useProjectAddonsQuery({ projectRef }) const selectedAddons = addons?.selected_addons ?? [] + const { computeInstance } = getAddons(selectedAddons) const currentComputeInstanceSpecs = computeInstance?.variant?.meta ?? { baseline_disk_io_mbs: 87, @@ -50,6 +56,27 @@ const InfrastructureActivity = () => { memory_gb: 1, } + const currentBillingCycleSelected = useMemo(() => { + // Selected by default + if (!dateRange?.period_start || !dateRange?.period_end || !subscription) return true + + const { current_period_start, current_period_end } = subscription + + return ( + dayjs(dateRange.period_start.date).isSame(new Date(current_period_start * 1000)) && + dayjs(dateRange.period_end.date).isSame(new Date(current_period_end * 1000)) + ) + }, [dateRange, subscription]) + + const upgradeUrl = + subscription === undefined + ? `/` + : subscription.plan.id === 'free' + ? `/org/${organization?.slug ?? '[slug]'}/billing#subscription` + : `/project/${projectRef}/settings/addons` + + const categoryMeta = INFRA_ACTIVITY_METRICS.find((category) => category.key === 'infra') + const startDate = useMemo(() => { if (dateRange?.period_start?.date === 'Invalid Date') return undefined @@ -116,6 +143,13 @@ const InfrastructureActivity = () => { dateFormat, }) + const hasLatest = dayjs(endDate!).isAfter(dayjs().startOf('day')) + + const latestIoBudgetConsumption = + hasLatest && ioBudgetData?.data?.slice(-1)?.[0] + ? Number(ioBudgetData.data.slice(-1)[0].disk_io_consumption) + : 0 + const highestIoBudgetConsumption = Math.max( ...(ioBudgetData?.data || []).map((x) => Number(x.disk_io_consumption) ?? 0), 0 @@ -180,11 +214,11 @@ const InfrastructureActivity = () => {
    -

    {attribute.name}

    +

    {attribute.name}

    {attribute.description.split('\n').map((value, idx) => ( -

    +

    {value}

    ))} @@ -199,7 +233,7 @@ const InfrastructureActivity = () => {
    -

    {link.name}

    +

    {link.name}

    @@ -212,46 +246,14 @@ const InfrastructureActivity = () => { {attribute.key === 'disk_io_consumption' && ( <> - {currentBillingCycleSelected && highestIoBudgetConsumption >= 100 ? ( - -

    - Your workload has used up all the burst IO throughput minutes and ran at - the baseline performance. If you need consistent disk performance, - consider upgrading to a larger compute add-on. -

    - - - - - -
    - ) : currentBillingCycleSelected && highestIoBudgetConsumption >= 80 ? ( - -

    - Your workload is about to use up all the burst IO throughput minutes - during the day. Once this is completely used up, your workload will run - at the baseline performance. If you need consistent disk performance, - consider upgrading to a larger compute add-on. -

    - - - - - -
    - ) : null} +

    Disk IO Bandwidth

    @@ -302,6 +304,25 @@ const InfrastructureActivity = () => {
    )} + {attribute.key === 'max_cpu_usage' && ( + + )} + {attribute.key === 'ram_usage' && ( + + )} +
    {attribute.key === 'disk_io_consumption' ? ( diff --git a/studio/components/ui/UsageWarnings/UsageWarningBanner.constants.ts b/studio/components/ui/UsageWarnings/UsageWarningBanner.constants.ts index 2a6ca960637..2ef96fafd7a 100644 --- a/studio/components/ui/UsageWarnings/UsageWarningBanner.constants.ts +++ b/studio/components/ui/UsageWarnings/UsageWarningBanner.constants.ts @@ -1,12 +1,13 @@ // [TODO] Just double check if the docs url and correction urls are all correct +// [TODO] Correction URL should depend if V2 or V1 billing export const RESOURCE_WARNING_MESSAGES = { is_readonly_mode_enabled: { title: 'Your project is currently in readonly mode and is no longer accepting write requests', description: 'You will need to manually override read-only mode and reduce the disk size to below 95%.', docsUrl: 'https://supabase.com/docs/guides/platform/database-size#disabling-read-only-mode', - correctionUrl: undefined, buttonText: undefined, + metric: undefined, }, is_disk_io_budget_below_threshold: { title: @@ -14,16 +15,16 @@ export const RESOURCE_WARNING_MESSAGES = { description: 'You will need to either optimize your performance, or upgrade your compute to a larger instance.', docsUrl: 'https://supabase.com/docs/guides/platform/exhaust-disk-io', - correctionUrl: '/project/[ref]/settings/billing/usage#disk_io', buttonText: 'Check usage', + metric: 'disk_io', }, is_disk_space_usage_beyond_threshold: { title: 'Your project is about to exhaust its disk space budget, and your instance may become unresponsive once fully exhausted', description: 'Some CTA description here', docsUrl: undefined, - correctionUrl: undefined, buttonText: undefined, + metric: undefined, }, is_cpu_load_beyond_threshold: { title: @@ -31,8 +32,8 @@ export const RESOURCE_WARNING_MESSAGES = { description: 'You will need to either optimize your performance or upgrade your compute to a larger instance', docsUrl: 'https://supabase.com/docs/guides/platform/exhaust-cpu', - correctionUrl: '/project/[ref]/settings/billing/usage#cpu', buttonText: 'Check usage', + metric: 'cpu', }, is_memory_and_swap_usage_beyond_threshold: { title: @@ -40,15 +41,15 @@ export const RESOURCE_WARNING_MESSAGES = { description: 'You will need to either optimize your performance or upgrade your compute to a larger instance', docsUrl: 'https://supabase.com/docs/guides/platform/exhaust-ram', - correctionUrl: '/project/[ref]/settings/billing/usage#ram', buttonText: 'Check usage', + metric: 'ram', }, multiple_resource_warnings: { title: "Your project is currently exhausting multiple resources, and your instance's performance is affected", description: "Check which resources are reaching their threshold on your project's usage page.", docsUrl: undefined, - correctionUrl: '/project/[ref]/settings/billing/usage', buttonText: 'Check usage', + metric: null, }, } diff --git a/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx b/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx index c3672308ae0..b000addf113 100644 --- a/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx +++ b/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx @@ -12,15 +12,19 @@ import { import { useResourceWarningsQuery } from 'data/usage/resource-warnings-query' import { RESOURCE_WARNING_MESSAGES } from './UsageWarningBanner.constants' import { useRouter } from 'next/router' +import { useSelectedOrganization } from 'hooks' const UsageWarningBanner = () => { const { ref } = useParams() const router = useRouter() + const organization = useSelectedOrganization() const { data: resourceWarnings } = useResourceWarningsQuery() const projectResourceWarnings = (resourceWarnings ?? [])?.find( (warning) => warning.project === ref ) + const isOrgBilling = !!organization?.subscription_id + // [Joshen] Read only takes higher precedence over multiple resource warnings const activeWarnings = projectResourceWarnings !== undefined @@ -49,19 +53,35 @@ const UsageWarningBanner = () => { ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.docsUrl : RESOURCE_WARNING_MESSAGES[activeWarnings[0] as keyof typeof RESOURCE_WARNING_MESSAGES] ?.docsUrl - const correctionUrl = ( + const metric = activeWarnings.length > 1 - ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.correctionUrl + ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.metric : RESOURCE_WARNING_MESSAGES[activeWarnings[0] as keyof typeof RESOURCE_WARNING_MESSAGES] - ?.correctionUrl - )?.replace('[ref]', ref ?? 'default') + ?.metric + + const correctionUrl = ( + metric === undefined + ? undefined + : metric === null + ? '/project/[ref]/settings/[infra-path]' + : `/project/[ref]/settings/[infra-path]#${metric}` + ) + ?.replace('[ref]', ref ?? 'default') + ?.replace('[infra-path]', isOrgBilling ? 'infrastructure' : 'billing/usage') + const buttonText = activeWarnings.length > 1 ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.buttonText : RESOURCE_WARNING_MESSAGES[activeWarnings[0] as keyof typeof RESOURCE_WARNING_MESSAGES] ?.buttonText - if (activeWarnings.length === 0 || router.pathname.includes('/usage')) return null + // Don't show banner if no warnings, or on usage/infra page + if ( + activeWarnings.length === 0 || + router.pathname.endsWith('/usage') || + router.pathname.endsWith('/infrastructure') + ) + return null return ( Date: Mon, 28 Aug 2023 17:39:43 +0800 Subject: [PATCH 08/49] Add mock stubs for new data fetchers --- studio/__mocks__/data/fetchers.js | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 studio/__mocks__/data/fetchers.js diff --git a/studio/__mocks__/data/fetchers.js b/studio/__mocks__/data/fetchers.js new file mode 100644 index 00000000000..2331502c3d9 --- /dev/null +++ b/studio/__mocks__/data/fetchers.js @@ -0,0 +1,8 @@ +export const get = jest.fn() +export const post = jest.fn() +export const patch = jest.fn() +export const del = jest.fn() +export const put = jest.fn() +export const head = jest.fn() +export const trace = jest.fn() +export const options = jest.fn() From cc43b2e0e4988d14df8a6958f0bbaa1901be4f53 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 30 Aug 2023 16:25:02 +0800 Subject: [PATCH 09/49] Midway updating based on endpoint changes --- .../Home/ProjectList/ProjectCard.tsx | 11 +- .../Home/ProjectList/ProjectCardWarnings.tsx | 112 +++++++++++++----- .../UsageWarningBanner.constants.ts | 112 +++++++++++++----- .../ui/UsageWarnings/UsageWarningBanner.tsx | 46 +++++-- studio/data/api.d.ts | 40 ++++++- studio/data/usage/resource-warnings-query.ts | 19 +++ studio/pages/projects.tsx | 2 +- 7 files changed, 259 insertions(+), 83 deletions(-) diff --git a/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx b/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx index 430de0ce0d3..6c70c5ced6b 100644 --- a/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx +++ b/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx @@ -44,15 +44,6 @@ const ProjectCard = ({ const isPaused = project.status === PROJECT_STATUS.INACTIVE const isRestoring = project.status === PROJECT_STATUS.RESTORING - const checkProjectResourceWarnings = (resourceWarnings: ResourceWarning) => { - return Object.values(resourceWarnings).some( - (value) => typeof value === 'boolean' && value === true - ) - } - - const projectHasResourceWarnings = - resourceWarnings !== undefined ? checkProjectResourceWarnings(resourceWarnings) : false - return (
  • } > - {resourceWarnings && projectHasResourceWarnings ? ( + {resourceWarnings ? ( ) : (
    diff --git a/studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx b/studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx index a0d7b2bbd85..7ae429f8976 100644 --- a/studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx +++ b/studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx @@ -5,32 +5,66 @@ export interface ProjectCardWarningsProps { 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 is in readonly mode', - description: 'Database is no longer accept write requests.', + warning: { + title: 'Project is in readonly mode', + description: 'Database is no longer accept write requests.', + }, + critical: { + title: 'Project is in readonly mode', + description: 'Database is no longer accept write requests.', + }, }, - is_disk_io_budget_below_threshold: { - title: 'Project is depleting its Disk IO Budget', - description: 'Instance may become unresponsive if fully exhausted.', + disk_io_exhaustion: { + warning: { + title: 'Project is depleting its Disk IO Budget', + description: 'Instance may become unresponsive if fully exhausted.', + }, + critical: { + title: 'Project has depleted its Disk IO Budget', + description: 'Instance may become unresponsive.', + }, }, - is_disk_space_usage_beyond_threshold: { - title: 'Project is exhausting disk space budget', - description: 'Instance may become unresponsive if fully exhausted.', + disk_space_exhaustion: { + warning: { + title: 'Project is exhausting disk space budget', + description: 'Instance may become unresponsive if fully exhausted.', + }, + critical: { + title: 'Project has exhausted disk space budget', + description: 'Instance may become unresponsive.', + }, }, - is_cpu_load_beyond_threshold: { - title: 'Project has high CPU usage', - description: `Instance's performance is affected`, + cpu_exhaustion: { + warning: { + title: 'Project has high CPU usage', + description: `Instance's performance is affected`, + }, + critical: { + title: 'Project CPU usage is at 100%', + description: `Instance's performance is affected`, + }, }, - is_memory_and_swap_usage_beyond_threshold: { - title: 'Project has high memory usage', - description: `Instance's performance is affected.`, + memory_and_swap_exhaustion: { + warning: { + title: 'Project has high memory usage', + description: `Instance's performance is affected.`, + }, + critical: { + title: 'Project memory usage is at 100%', + description: `Instance's performance is affected.`, + }, }, multiple_resource_warnings: { - title: 'Project is exhausting multiple resources', - description: `Instance's performance is affected.`, + warning: { + title: 'Project is exhausting multiple resources', + description: `Instance's performance is affected.`, + }, + critical: { + title: 'Project has exhausted at least one resource', + description: `Instance's performance is affected.`, + }, }, } @@ -39,32 +73,54 @@ const ProjectCardWarnings = ({ resourceWarnings }: ProjectCardWarningsProps) => const activeWarnings = resourceWarnings.is_readonly_mode_enabled ? ['is_readonly_mode_enabled'] : Object.keys(resourceWarnings).filter( - (property) => resourceWarnings[property as keyof typeof resourceWarnings] === true + (property) => + property !== 'project' && + property !== 'is_readonly_mode_enabled' && + resourceWarnings[property as keyof typeof resourceWarnings] !== null ) - const hasHighPriorityWarning = activeWarnings.includes('is_readonly_mode_enabled') + + const getContent = (metric: string) => { + if (metric === 'is_readonly_mode_enabled') { + return RESOURCE_WARNING_MESSAGES.is_readonly_mode_enabled.warning + } + const severity = resourceWarnings[metric as keyof typeof resourceWarnings] + if (typeof severity !== 'string') return undefined + return RESOURCE_WARNING_MESSAGES[metric as keyof typeof RESOURCE_WARNING_MESSAGES][ + severity as 'warning' | 'critical' + ] + } + + const hasCriticalWarning = activeWarnings.some( + (x) => resourceWarnings[x as keyof typeof resourceWarnings] === 'critical' + ) + + const isCritical = activeWarnings.includes('is_readonly_mode_enabled') || hasCriticalWarning + + if (activeWarnings.length === 0) return
    return (
    {activeWarnings.length > 1 - ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.title - : RESOURCE_WARNING_MESSAGES[activeWarnings[0] as keyof typeof RESOURCE_WARNING_MESSAGES] - ?.title} + ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings[ + hasCriticalWarning ? 'critical' : 'warning' + ].title + : getContent(activeWarnings[0])?.title} {activeWarnings.length > 1 - ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.description - : RESOURCE_WARNING_MESSAGES[activeWarnings[0] as keyof typeof RESOURCE_WARNING_MESSAGES] - ?.description} + ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings[ + hasCriticalWarning ? 'critical' : 'warning' + ].description + : getContent(activeWarnings[0])?.description} - {/*
    {JSON.stringify(resourceWarnings, null, 2)}
    */}
    ) } diff --git a/studio/components/ui/UsageWarnings/UsageWarningBanner.constants.ts b/studio/components/ui/UsageWarnings/UsageWarningBanner.constants.ts index 2ef96fafd7a..2cb01037184 100644 --- a/studio/components/ui/UsageWarnings/UsageWarningBanner.constants.ts +++ b/studio/components/ui/UsageWarnings/UsageWarningBanner.constants.ts @@ -1,53 +1,111 @@ -// [TODO] Just double check if the docs url and correction urls are all correct -// [TODO] Correction URL should depend if V2 or V1 billing export const RESOURCE_WARNING_MESSAGES = { is_readonly_mode_enabled: { - title: 'Your project is currently in readonly mode and is no longer accepting write requests', - description: - 'You will need to manually override read-only mode and reduce the disk size to below 95%.', + content: { + warning: { + title: + 'Your project is currently in readonly mode and is no longer accepting write requests', + description: + 'You will need to manually override read-only mode and reduce the disk size to below 95%.', + }, + critical: { + title: + 'Your project is currently in readonly mode and is no longer accepting write requests', + description: + 'You will need to manually override read-only mode and reduce the disk size to below 95%.', + }, + }, docsUrl: 'https://supabase.com/docs/guides/platform/database-size#disabling-read-only-mode', buttonText: undefined, metric: undefined, }, - is_disk_io_budget_below_threshold: { - title: - 'Your project is about to deplete its Disk IO Budget, and your instance may become unresponsive once fully exhausted', - description: - 'You will need to either optimize your performance, or upgrade your compute to a larger instance.', + disk_io_exhaustion: { + content: { + warning: { + title: + 'Your project is about to deplete its Disk IO Budget, and your instance may become unresponsive once fully exhausted', + description: + 'You will need to either optimize your performance, or upgrade your compute to a larger instance.', + }, + critical: { + title: + 'Your project has depleted its Disk IO Budget, and your instance may become unresponsive', + description: + 'You will need to either optimize your performance, or upgrade your compute to a larger instance.', + }, + }, docsUrl: 'https://supabase.com/docs/guides/platform/exhaust-disk-io', buttonText: 'Check usage', metric: 'disk_io', }, - is_disk_space_usage_beyond_threshold: { - title: - 'Your project is about to exhaust its disk space budget, and your instance may become unresponsive once fully exhausted', - description: 'Some CTA description here', + disk_space_exhaustion: { + content: { + warning: { + title: + 'Your project is about to exhaust its disk space budget, and your instance may become unresponsive once fully exhausted', + description: 'Some CTA description here', + }, + critical: { + title: + 'Your project has exhausted its disk space budget, and your instance may become unresponsive', + description: 'Some CTA description here', + }, + }, docsUrl: undefined, buttonText: undefined, metric: undefined, }, - is_cpu_load_beyond_threshold: { - title: - "Your project is currently facing high CPU usage, and your instance's performance is affected", - description: - 'You will need to either optimize your performance or upgrade your compute to a larger instance', + cpu_exhaustion: { + content: { + warning: { + title: + "Your project is currently facing high CPU usage, and your instance's performance is affected", + description: + 'You will need to either optimize your performance or upgrade your compute to a larger instance', + }, + critical: { + title: "Your project's CPU usage is at 100% and your instance's performance is affected", + description: + 'You will need to either optimize your performance or upgrade your compute to a larger instance', + }, + }, docsUrl: 'https://supabase.com/docs/guides/platform/exhaust-cpu', buttonText: 'Check usage', metric: 'cpu', }, - is_memory_and_swap_usage_beyond_threshold: { - title: - "Your project is currently facing high memory usage, and your instance's performance is affected", - description: - 'You will need to either optimize your performance or upgrade your compute to a larger instance', + memory_and_swap_exhaustion: { + content: { + warning: { + title: + "Your project is currently facing high memory usage, and your instance's performance is affected", + description: + 'You will need to either optimize your performance or upgrade your compute to a larger instance', + }, + critical: { + title: + "Your project's memory usage is at 100%, and your instance's performance is affected", + description: + 'You will need to either optimize your performance or upgrade your compute to a larger instance', + }, + }, docsUrl: 'https://supabase.com/docs/guides/platform/exhaust-ram', buttonText: 'Check usage', metric: 'ram', }, multiple_resource_warnings: { - title: - "Your project is currently exhausting multiple resources, and your instance's performance is affected", - description: "Check which resources are reaching their threshold on your project's usage page.", + content: { + warning: { + title: + "Your project is currently exhausting multiple resources, and your instance's performance is affected", + description: + "Check which resources are reaching their threshold on your project's usage page.", + }, + critical: { + title: + "Your project has exhausted at least one resource, and your instance's performance is affected", + description: + "Check which resources have reached their threshold on your project's usage page.", + }, + }, docsUrl: undefined, buttonText: 'Check usage', metric: null, diff --git a/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx b/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx index b000addf113..647882dd84c 100644 --- a/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx +++ b/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx @@ -1,5 +1,6 @@ -import Link from 'next/link' import { useParams } from 'common' +import Link from 'next/link' +import { useRouter } from 'next/router' import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, @@ -10,9 +11,8 @@ import { } from 'ui' import { useResourceWarningsQuery } from 'data/usage/resource-warnings-query' -import { RESOURCE_WARNING_MESSAGES } from './UsageWarningBanner.constants' -import { useRouter } from 'next/router' import { useSelectedOrganization } from 'hooks' +import { RESOURCE_WARNING_MESSAGES } from './UsageWarningBanner.constants' const UsageWarningBanner = () => { const { ref } = useParams() @@ -32,22 +32,44 @@ const UsageWarningBanner = () => { ? ['is_readonly_mode_enabled'] : Object.keys(projectResourceWarnings).filter( (property) => - projectResourceWarnings[property as keyof typeof projectResourceWarnings] === true + property !== 'project' && + property !== 'is_readonly_mode_enabled' && + projectResourceWarnings[property as keyof typeof projectResourceWarnings] !== null ) : [] - const hasHighPriorityWarning = activeWarnings.includes('is_readonly_mode_enabled') + const hasCriticalWarning = + projectResourceWarnings !== undefined + ? activeWarnings.some( + (x) => projectResourceWarnings[x as keyof typeof projectResourceWarnings] === 'critical' + ) + : false + const isCritical = activeWarnings.includes('is_readonly_mode_enabled') || hasCriticalWarning + + const getContent = (metric: string) => { + if (projectResourceWarnings === undefined) return undefined + if (metric === 'is_readonly_mode_enabled') { + return RESOURCE_WARNING_MESSAGES.is_readonly_mode_enabled.content.warning + } + const severity = projectResourceWarnings[metric as keyof typeof projectResourceWarnings] + if (typeof severity !== 'string') return undefined + return RESOURCE_WARNING_MESSAGES[metric as keyof typeof RESOURCE_WARNING_MESSAGES].content[ + severity as 'warning' | 'critical' + ] + } const title = activeWarnings.length > 1 - ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.title - : RESOURCE_WARNING_MESSAGES[activeWarnings[0] as keyof typeof RESOURCE_WARNING_MESSAGES] - ?.title + ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.content[ + hasCriticalWarning ? 'critical' : 'warning' + ].title + : getContent(activeWarnings[0])?.title const description = activeWarnings.length > 1 - ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.description - : RESOURCE_WARNING_MESSAGES[activeWarnings[0] as keyof typeof RESOURCE_WARNING_MESSAGES] - ?.description + ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.content[ + hasCriticalWarning ? 'critical' : 'warning' + ].description + : getContent(activeWarnings[0])?.description const learnMoreUrl = activeWarnings.length > 1 ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.docsUrl @@ -85,7 +107,7 @@ const UsageWarningBanner = () => { return ( diff --git a/studio/data/api.d.ts b/studio/data/api.d.ts index 30c7c2551c9..adf68e2f1f7 100644 --- a/studio/data/api.d.ts +++ b/studio/data/api.d.ts @@ -214,9 +214,13 @@ export interface paths { "/platform/organizations/{slug}/billing/subscription": { /** Gets the current subscription */ get: operations["SubscriptionController_getSubscription"]; - /** Updates subscription */ + /** Previews subscription change */ put: operations["SubscriptionController_updateSubscription"]; }; + "/platform/organizations/{slug}/billing/subscription/preview": { + /** Updates subscription */ + post: operations["SubscriptionController_previewSubscriptionChange"]; + }; "/platform/organizations/{slug}/billing/plans": { /** Gets subscription plans */ get: operations["OrgPlansController_getAvailablePlans"]; @@ -1643,12 +1647,16 @@ export interface components { redirectTo?: string; }; ProjectResourceWarningsResponse: { + /** @enum {string|null} */ + disk_io_exhaustion: "critical" | "warning" | null; + /** @enum {string|null} */ + disk_space_exhaustion: "critical" | "warning" | null; + /** @enum {string|null} */ + cpu_exhaustion: "critical" | "warning" | null; + /** @enum {string|null} */ + memory_and_swap_exhaustion: "critical" | "warning" | null; 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; }; GetGoTrueConfigResponse: { SITE_URL: string; @@ -1818,6 +1826,8 @@ export interface components { SMS_PROVIDER?: string; SMS_MESSAGEBIRD_ACCESS_KEY?: string; SMS_MESSAGEBIRD_ORIGINATOR?: string; + SMS_TEST_OTP?: string; + SMS_TEST_OTP_VALID_UNTIL?: string; SMS_TEXTLOCAL_API_KEY?: string; SMS_TEXTLOCAL_SENDER?: string; SMS_TWILIO_ACCOUNT_SID?: string; @@ -5463,6 +5473,26 @@ export interface operations { 500: never; }; }; + /** Updates subscription */ + SubscriptionController_previewSubscriptionChange: { + parameters: { + path: { + /** @description Organization slug */ + slug: string; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UpdateSubscriptionBody"]; + }; + }; + responses: { + 201: never; + 403: never; + /** @description Failed to update subscription */ + 500: never; + }; + }; /** Gets subscription plans */ OrgPlansController_getAvailablePlans: { parameters: { diff --git a/studio/data/usage/resource-warnings-query.ts b/studio/data/usage/resource-warnings-query.ts index d7ecd7c350e..7249b41e989 100644 --- a/studio/data/usage/resource-warnings-query.ts +++ b/studio/data/usage/resource-warnings-query.ts @@ -8,6 +8,25 @@ import { components } from 'data/api' export async function getResourceWarnings(signal?: AbortSignal) { const { data, error } = await get(`/platform/projects-resource-warnings`, { signal }) if (error) throw error + + return [ + { + project: 'iqixcyokaezexqncgirb', + is_readonly_mode_enabled: false, + disk_io_exhaustion: null, + disk_space_exhaustion: null, + cpu_exhaustion: null, + memory_and_swap_exhaustion: null, + }, + { + project: 'yhkgeeesimzjzbigwxwv', + is_readonly_mode_enabled: false, + disk_io_exhaustion: null, + disk_space_exhaustion: null, + cpu_exhaustion: 'critical', + memory_and_swap_exhaustion: null, + }, + ] as ResourceWarning[] return data } diff --git a/studio/pages/projects.tsx b/studio/pages/projects.tsx index c7e183eced8..909a567dad3 100644 --- a/studio/pages/projects.tsx +++ b/studio/pages/projects.tsx @@ -48,7 +48,7 @@ const ProjectsPage: NextPageWithLayout = () => {
    )} - {isError && ( + {true && (
    From e0b554a2472d91a7b5f39ea8e22a9fca4029ae8a Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 30 Aug 2023 16:56:41 +0800 Subject: [PATCH 10/49] Wrap up updates according to endpoint changes --- .../BillingV2/Usage/Infrastructure.tsx | 6 ++---- .../Usage/UsageWarningAlerts/CPUWarnings.tsx | 13 ++++--------- .../Usage/UsageWarningAlerts/RAMWarnings.tsx | 13 ++++--------- .../Infrastructure/InfrastructureActivity.tsx | 8 ++------ .../ui/UsageWarnings/UsageWarningBanner.tsx | 4 ++-- studio/data/usage/resource-warnings-query.ts | 19 ------------------- studio/pages/projects.tsx | 2 +- 7 files changed, 15 insertions(+), 50 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx index 2789f1e139b..39c3c24459a 100644 --- a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx @@ -186,16 +186,14 @@ const Infrastructure = ({ )} {attribute.key === 'ram_usage' && ( )} diff --git a/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/CPUWarnings.tsx b/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/CPUWarnings.tsx index 818e649f4ac..8a207e12518 100644 --- a/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/CPUWarnings.tsx +++ b/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/CPUWarnings.tsx @@ -5,16 +5,11 @@ import { AlertDescription_Shadcn_, Alert_Shadcn_, Button, IconAlertCircle } from interface CPUWarningsProps { isFreePlan: boolean upgradeUrl: string - isBeyondThreshold: boolean + severity?: 'warning' | 'critical' | null } -const CPUWarnings = ({ isFreePlan, upgradeUrl, isBeyondThreshold }: CPUWarningsProps) => { - // [Joshen TODO] Need to implement the necessary conditionals for rendering the warnings once - // endpoint is updated to return text instead of boolean - const isApproaching = isBeyondThreshold - const isExceeded = false - - if (isApproaching) { +const CPUWarnings = ({ isFreePlan, upgradeUrl, severity }: CPUWarningsProps) => { + if (severity === 'warning') { return ( @@ -42,7 +37,7 @@ const CPUWarnings = ({ isFreePlan, upgradeUrl, isBeyondThreshold }: CPUWarningsP ) } - if (isExceeded) { + if (severity === 'critical') { return ( diff --git a/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/RAMWarnings.tsx b/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/RAMWarnings.tsx index 8006c3d079d..fe233f08695 100644 --- a/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/RAMWarnings.tsx +++ b/studio/components/interfaces/BillingV2/Usage/UsageWarningAlerts/RAMWarnings.tsx @@ -5,16 +5,11 @@ import { AlertDescription_Shadcn_, Alert_Shadcn_, Button, IconAlertCircle } from interface RAMWarningsProps { isFreePlan: boolean upgradeUrl: string - isBeyondThreshold: boolean + severity?: 'warning' | 'critical' | null } -const RAMWarnings = ({ isFreePlan, upgradeUrl, isBeyondThreshold }: RAMWarningsProps) => { - // [Joshen TODO] Need to implement the necessary conditionals for rendering the warnings once - // endpoint is updated to return text instead of boolean - const isApproaching = isBeyondThreshold - const isExceeded = false - - if (isApproaching) { +const RAMWarnings = ({ isFreePlan, upgradeUrl, severity }: RAMWarningsProps) => { + if (severity === 'warning') { return ( @@ -42,7 +37,7 @@ const RAMWarnings = ({ isFreePlan, upgradeUrl, isBeyondThreshold }: RAMWarningsP ) } - if (isExceeded) { + if (severity === 'critical') { return ( diff --git a/studio/components/interfaces/Settings/Infrastructure/InfrastructureActivity.tsx b/studio/components/interfaces/Settings/Infrastructure/InfrastructureActivity.tsx index 28e6dce7415..1594655f2da 100644 --- a/studio/components/interfaces/Settings/Infrastructure/InfrastructureActivity.tsx +++ b/studio/components/interfaces/Settings/Infrastructure/InfrastructureActivity.tsx @@ -308,18 +308,14 @@ const InfrastructureActivity = () => { )} {attribute.key === 'ram_usage' && ( )} diff --git a/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx b/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx index 647882dd84c..19b7fd6e1d8 100644 --- a/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx +++ b/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx @@ -100,8 +100,8 @@ const UsageWarningBanner = () => { // Don't show banner if no warnings, or on usage/infra page if ( activeWarnings.length === 0 || - router.pathname.endsWith('/usage') || - router.pathname.endsWith('/infrastructure') + ((router.pathname.endsWith('/usage') || router.pathname.endsWith('/infrastructure')) && + !activeWarnings.includes('is_readonly_mode_enabled')) ) return null diff --git a/studio/data/usage/resource-warnings-query.ts b/studio/data/usage/resource-warnings-query.ts index 7249b41e989..d7ecd7c350e 100644 --- a/studio/data/usage/resource-warnings-query.ts +++ b/studio/data/usage/resource-warnings-query.ts @@ -8,25 +8,6 @@ import { components } from 'data/api' export async function getResourceWarnings(signal?: AbortSignal) { const { data, error } = await get(`/platform/projects-resource-warnings`, { signal }) if (error) throw error - - return [ - { - project: 'iqixcyokaezexqncgirb', - is_readonly_mode_enabled: false, - disk_io_exhaustion: null, - disk_space_exhaustion: null, - cpu_exhaustion: null, - memory_and_swap_exhaustion: null, - }, - { - project: 'yhkgeeesimzjzbigwxwv', - is_readonly_mode_enabled: false, - disk_io_exhaustion: null, - disk_space_exhaustion: null, - cpu_exhaustion: 'critical', - memory_and_swap_exhaustion: null, - }, - ] as ResourceWarning[] return data } diff --git a/studio/pages/projects.tsx b/studio/pages/projects.tsx index 909a567dad3..c7e183eced8 100644 --- a/studio/pages/projects.tsx +++ b/studio/pages/projects.tsx @@ -48,7 +48,7 @@ const ProjectsPage: NextPageWithLayout = () => {
    )} - {true && ( + {isError && (
    From 464b51d791a41f03a143b19a09ccfc4a8e61beee Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 30 Aug 2023 17:11:56 +0800 Subject: [PATCH 11/49] Update CTAs for disk space exhaustion --- .../ui/UsageWarnings/UsageWarningBanner.constants.ts | 10 ++++++---- .../components/ui/UsageWarnings/UsageWarningBanner.tsx | 2 ++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/studio/components/ui/UsageWarnings/UsageWarningBanner.constants.ts b/studio/components/ui/UsageWarnings/UsageWarningBanner.constants.ts index 2cb01037184..51855ae57ec 100644 --- a/studio/components/ui/UsageWarnings/UsageWarningBanner.constants.ts +++ b/studio/components/ui/UsageWarnings/UsageWarningBanner.constants.ts @@ -42,17 +42,19 @@ export const RESOURCE_WARNING_MESSAGES = { warning: { title: 'Your project is about to exhaust its disk space budget, and your instance may become unresponsive once fully exhausted', - description: 'Some CTA description here', + description: + 'You can opt to increase your disk size up to 200GB if required on the database settings page.', }, critical: { title: 'Your project has exhausted its disk space budget, and your instance may become unresponsive', - description: 'Some CTA description here', + description: + 'You can opt to increase your disk size up to 200GB if required on the database settings page.', }, }, - docsUrl: undefined, + docsUrl: 'https://supabase.com/docs/guides/platform/database-size#disk-management', buttonText: undefined, - metric: undefined, + metric: 'disk_space', }, cpu_exhaustion: { content: { diff --git a/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx b/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx index 19b7fd6e1d8..e058b084801 100644 --- a/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx +++ b/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx @@ -86,6 +86,8 @@ const UsageWarningBanner = () => { ? undefined : metric === null ? '/project/[ref]/settings/[infra-path]' + : metric === 'disk_space' + ? '/project/[ref]/settings/database' : `/project/[ref]/settings/[infra-path]#${metric}` ) ?.replace('[ref]', ref ?? 'default') From f134966bbd40bb8cea623e32ab8a6b74a6c5e066 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 30 Aug 2023 17:18:36 +0800 Subject: [PATCH 12/49] Add feature flag, and ensure that banner shows up in all ProjectLayout --- .../Home/ProjectList/ProjectCard.tsx | 5 ++++- .../layouts/ProjectLayout/ProjectLayout.tsx | 19 ++++++++++++------- ...ourceExhaustionWarningBanner.constants.ts} | 0 .../ResourceExhaustionWarningBanner.tsx} | 6 +++--- 4 files changed, 19 insertions(+), 11 deletions(-) rename studio/components/ui/{UsageWarnings/UsageWarningBanner.constants.ts => ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts} (100%) rename studio/components/ui/{UsageWarnings/UsageWarningBanner.tsx => ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.tsx} (96%) diff --git a/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx b/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx index 6c70c5ced6b..68817113fd7 100644 --- a/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx +++ b/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx @@ -13,6 +13,7 @@ import { Project } from 'types' import { IntegrationProjectConnection } from 'data/integrations/integrations.types' import { ResourceWarning } from 'data/usage/resource-warnings-query' import ProjectCardWarnings from './ProjectCardWarnings' +import { useFlag } from 'hooks' export interface ProjectCardProps { project: Project @@ -32,6 +33,8 @@ const ProjectCard = ({ const { name, ref: projectRef } = project const desc = `${project.cloud_provider} | ${project.region}` + const showResourceExhaustionWarnings = useFlag('resourceExhaustionWarnings') + const isBranchingEnabled = project.preview_branch_refs.length > 0 const isGithubIntegrated = githubIntegration !== undefined const isVercelIntegrated = vercelIntegration !== undefined @@ -118,7 +121,7 @@ const ProjectCard = ({
    } > - {resourceWarnings ? ( + {showResourceExhaustionWarnings && resourceWarnings ? ( ) : (
    diff --git a/studio/components/layouts/ProjectLayout/ProjectLayout.tsx b/studio/components/layouts/ProjectLayout/ProjectLayout.tsx index adeba22da72..f314cb4aae9 100644 --- a/studio/components/layouts/ProjectLayout/ProjectLayout.tsx +++ b/studio/components/layouts/ProjectLayout/ProjectLayout.tsx @@ -4,7 +4,7 @@ import { Fragment, PropsWithChildren, ReactNode } from 'react' import { useParams } from 'common/hooks' import Connecting from 'components/ui/Loading' -import UsageWarningBanner from 'components/ui/UsageWarnings/UsageWarningBanner' +import ResourceExhaustionWarningBanner from 'components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner' import { useFlag, useSelectedOrganization, useSelectedProject, withAuth } from 'hooks' import { IS_PLATFORM, PROJECT_STATUS } from 'lib/constants' import AppLayout from '../AppLayout/AppLayout' @@ -69,6 +69,7 @@ const ProjectLayout = ({ const organizationName = selectedOrganization?.name const navLayoutV2 = useFlag('navigationLayoutV2') + const showResourceExhaustionWarnings = useFlag('resourceExhaustionWarnings') const isPaused = selectedProject?.status === PROJECT_STATUS.INACTIVE const ignorePausedState = @@ -110,7 +111,10 @@ const ProjectLayout = ({
    ) : ( - {children} + + {showResourceExhaustionWarnings && } + {children} + )}
  • @@ -193,10 +197,7 @@ const ContentWrapper = ({ isLoading, children }: ContentWrapperProps) => { ) : requiresDbConnection && isProjectBuilding ? ( ) : ( - <> - - {children} - + {children} )} ) @@ -224,6 +225,7 @@ export const ProjectLayoutNonBlocking = ({ const showPausedState = isPaused && !ignorePausedState const navLayoutV2 = useFlag('navigationLayoutV2') + const showResourceExhaustionWarnings = useFlag('resourceExhaustionWarnings') return ( @@ -251,7 +253,10 @@ export const ProjectLayoutNonBlocking = ({
    ) : ( - children + <> + {showResourceExhaustionWarnings && } + {children} + )} diff --git a/studio/components/ui/UsageWarnings/UsageWarningBanner.constants.ts b/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts similarity index 100% rename from studio/components/ui/UsageWarnings/UsageWarningBanner.constants.ts rename to studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts diff --git a/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx b/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.tsx similarity index 96% rename from studio/components/ui/UsageWarnings/UsageWarningBanner.tsx rename to studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.tsx index e058b084801..76a9dd279db 100644 --- a/studio/components/ui/UsageWarnings/UsageWarningBanner.tsx +++ b/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.tsx @@ -12,9 +12,9 @@ import { import { useResourceWarningsQuery } from 'data/usage/resource-warnings-query' import { useSelectedOrganization } from 'hooks' -import { RESOURCE_WARNING_MESSAGES } from './UsageWarningBanner.constants' +import { RESOURCE_WARNING_MESSAGES } from './ResourceExhaustionWarningBanner.constants' -const UsageWarningBanner = () => { +const ResourceExhaustionWarningBanner = () => { const { ref } = useParams() const router = useRouter() const organization = useSelectedOrganization() @@ -137,4 +137,4 @@ const UsageWarningBanner = () => { ) } -export default UsageWarningBanner +export default ResourceExhaustionWarningBanner From 8e50bc2c990948bf922c6854c9d8b408bbe20a5b Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 30 Aug 2023 18:01:17 +0800 Subject: [PATCH 13/49] Add badge for coming up in project card --- .../interfaces/Home/ProjectList/ProjectCard.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx b/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx index 68817113fd7..3af4a774a98 100644 --- a/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx +++ b/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx @@ -46,6 +46,8 @@ const ProjectCard = ({ project.status === PROJECT_STATUS.GOING_DOWN || project.status === PROJECT_STATUS.PAUSING const isPaused = project.status === PROJECT_STATUS.INACTIVE const isRestoring = project.status === PROJECT_STATUS.RESTORING + const isComingUp = + project.status === PROJECT_STATUS.UNKNOWN || project.status === PROJECT_STATUS.COMING_UP return (
  • @@ -118,6 +120,15 @@ const ProjectCard = ({ )} + + {isComingUp && ( + +
    + + Coming up +
    +
    + )} } > From d4885df7f317b4716945a1b6b22b2923f20d7bd9 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Fri, 1 Sep 2023 11:16:35 -0230 Subject: [PATCH 14/49] Updates --- .../Home/ProjectList/ProjectCard.tsx | 1 + .../Home/ProjectList/ProjectCardWarnings.tsx | 3 +- .../Infrastructure/InfrastructureActivity.tsx | 2 +- ...sourceExhaustionWarningBanner.constants.ts | 43 ++++++++----------- .../ResourceExhaustionWarningBanner.tsx | 3 +- studio/data/usage/resource-warnings-query.ts | 1 + 6 files changed, 26 insertions(+), 27 deletions(-) diff --git a/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx b/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx index 3af4a774a98..bf101db33cd 100644 --- a/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx +++ b/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx @@ -53,6 +53,7 @@ const ProjectCard = ({
  • {name}

    diff --git a/studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx b/studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx index 7ae429f8976..a07d41ff3b1 100644 --- a/studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx +++ b/studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx @@ -102,7 +102,8 @@ const ProjectCardWarnings = ({ resourceWarnings }: ProjectCardWarningsProps) =>
    diff --git a/studio/components/interfaces/Settings/Infrastructure/InfrastructureActivity.tsx b/studio/components/interfaces/Settings/Infrastructure/InfrastructureActivity.tsx index 1594655f2da..9a077356b21 100644 --- a/studio/components/interfaces/Settings/Infrastructure/InfrastructureActivity.tsx +++ b/studio/components/interfaces/Settings/Infrastructure/InfrastructureActivity.tsx @@ -214,7 +214,7 @@ const InfrastructureActivity = () => {
    -

    {attribute.name}

    +

    {attribute.name}

    {attribute.description.split('\n').map((value, idx) => ( diff --git a/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts b/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts index 51855ae57ec..5e54f833199 100644 --- a/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts +++ b/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts @@ -3,13 +3,13 @@ export const RESOURCE_WARNING_MESSAGES = { content: { warning: { title: - 'Your project is currently in readonly mode and is no longer accepting write requests', + 'Your project is currently in read-only mode and is no longer accepting write requests', description: 'You will need to manually override read-only mode and reduce the disk size to below 95%.', }, critical: { title: - 'Your project is currently in readonly mode and is no longer accepting write requests', + 'Your project is currently in read-only mode and is no longer accepting write requests', description: 'You will need to manually override read-only mode and reduce the disk size to below 95%.', }, @@ -22,15 +22,14 @@ export const RESOURCE_WARNING_MESSAGES = { content: { warning: { title: - 'Your project is about to deplete its Disk IO Budget, and your instance may become unresponsive once fully exhausted', + 'Your project is about to deplete its Disk IO Budget, and may become unresponsive once fully exhausted', description: - 'You will need to either optimize your performance, or upgrade your compute to a larger instance.', + 'You will need to either optimize your performance, or upgrade your compute to a larger plan.', }, critical: { - title: - 'Your project has depleted its Disk IO Budget, and your instance may become unresponsive', + title: 'Your project has depleted its Disk IO Budget, and may become unresponsive', description: - 'You will need to either optimize your performance, or upgrade your compute to a larger instance.', + 'You will need to either optimize your performance, or upgrade your compute to a larger plan.', }, }, docsUrl: 'https://supabase.com/docs/guides/platform/exhaust-disk-io', @@ -41,15 +40,14 @@ export const RESOURCE_WARNING_MESSAGES = { content: { warning: { title: - 'Your project is about to exhaust its disk space budget, and your instance may become unresponsive once fully exhausted', + 'Your project is about to exhaust its disk space budget, and may become unresponsive once fully exhausted', description: - 'You can opt to increase your disk size up to 200GB if required on the database settings page.', + 'You can opt to increase your disk size up to 200GB on the database settings page.', }, critical: { - title: - 'Your project has exhausted its disk space budget, and your instance may become unresponsive', + title: 'Your project has exhausted its disk space budget, and may become unresponsive', description: - 'You can opt to increase your disk size up to 200GB if required on the database settings page.', + 'You can opt to increase your disk size up to 200GB on the database settings page.', }, }, docsUrl: 'https://supabase.com/docs/guides/platform/database-size#disk-management', @@ -59,15 +57,14 @@ export const RESOURCE_WARNING_MESSAGES = { cpu_exhaustion: { content: { warning: { - title: - "Your project is currently facing high CPU usage, and your instance's performance is affected", + title: 'Your project is currently facing high CPU usage, and its performance is affected', description: 'You will need to either optimize your performance or upgrade your compute to a larger instance', }, critical: { - title: "Your project's CPU usage is at 100% and your instance's performance is affected", + title: "Your project's CPU usage is at 100% and its performance is affected", description: - 'You will need to either optimize your performance or upgrade your compute to a larger instance', + 'You will need to either optimize your performance or upgrade your compute to a larger plan', }, }, docsUrl: 'https://supabase.com/docs/guides/platform/exhaust-cpu', @@ -78,15 +75,14 @@ export const RESOURCE_WARNING_MESSAGES = { content: { warning: { title: - "Your project is currently facing high memory usage, and your instance's performance is affected", + 'Your project is currently facing high memory usage, and its performance is affected', description: - 'You will need to either optimize your performance or upgrade your compute to a larger instance', + 'You will need to either optimize your performance or upgrade your compute to a larger plan', }, critical: { - title: - "Your project's memory usage is at 100%, and your instance's performance is affected", + title: "Your project's memory usage is at 100%, and its performance is affected", description: - 'You will need to either optimize your performance or upgrade your compute to a larger instance', + 'You will need to either optimize your performance or upgrade your compute to a larger plan', }, }, docsUrl: 'https://supabase.com/docs/guides/platform/exhaust-ram', @@ -97,13 +93,12 @@ export const RESOURCE_WARNING_MESSAGES = { content: { warning: { title: - "Your project is currently exhausting multiple resources, and your instance's performance is affected", + 'Your project is currently exhausting multiple resources, and its performance is affected', description: "Check which resources are reaching their threshold on your project's usage page.", }, critical: { - title: - "Your project has exhausted at least one resource, and your instance's performance is affected", + title: 'Your project has exhausted at least one resource, and its performance is affected', description: "Check which resources have reached their threshold on your project's usage page.", }, diff --git a/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.tsx b/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.tsx index 76a9dd279db..e406b1baaa8 100644 --- a/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.tsx +++ b/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.tsx @@ -110,7 +110,8 @@ const ResourceExhaustionWarningBanner = () => { return ( {title} diff --git a/studio/data/usage/resource-warnings-query.ts b/studio/data/usage/resource-warnings-query.ts index d7ecd7c350e..b1d56c12cfe 100644 --- a/studio/data/usage/resource-warnings-query.ts +++ b/studio/data/usage/resource-warnings-query.ts @@ -8,6 +8,7 @@ import { components } from 'data/api' export async function getResourceWarnings(signal?: AbortSignal) { const { data, error } = await get(`/platform/projects-resource-warnings`, { signal }) if (error) throw error + return data } From 1068773733ee107c2d1b034a3d75afceb6554b68 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Wed, 6 Sep 2023 17:08:27 -0230 Subject: [PATCH 15/49] Combine project status with resource warnings --- .../Home/ProjectList/ProjectCard.tsx | 111 +++++---------- .../Home/ProjectList/ProjectCardStatus.tsx | 134 ++++++++++++++++++ .../Home/ProjectList/ProjectCardWarnings.tsx | 129 ----------------- ...sourceExhaustionWarningBanner.constants.ts | 80 +++++++++-- .../ResourceExhaustionWarningBanner.tsx | 13 +- studio/data/usage/resource-warnings-query.ts | 19 +++ 6 files changed, 267 insertions(+), 219 deletions(-) create mode 100644 studio/components/interfaces/Home/ProjectList/ProjectCardStatus.tsx delete mode 100644 studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx diff --git a/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx b/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx index bf101db33cd..9d3f44c63ec 100644 --- a/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx +++ b/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx @@ -1,19 +1,12 @@ -import { - Badge, - IconAlertTriangle, - IconGitBranch, - IconGitHub, - IconLoader, - IconPauseCircle, -} from 'ui' +import { IconGitBranch, IconGitHub } from 'ui' import CardButton from 'components/ui/CardButton' import { BASE_PATH, PROJECT_STATUS } from 'lib/constants' import { Project } from 'types' import { IntegrationProjectConnection } from 'data/integrations/integrations.types' import { ResourceWarning } from 'data/usage/resource-warnings-query' -import ProjectCardWarnings from './ProjectCardWarnings' import { useFlag } from 'hooks' +import { ProjectCardStatus } from './ProjectCardStatus' export interface ProjectCardProps { project: Project @@ -23,6 +16,32 @@ export interface ProjectCardProps { resourceWarnings?: ResourceWarning } +const getProjetStatus = (project: Project) => { + let status + switch (project.status) { + case PROJECT_STATUS.ACTIVE_HEALTHY: + status = 'isHealthy' + break + case PROJECT_STATUS.GOING_DOWN: + case PROJECT_STATUS.PAUSING: + status = 'isPausing' + break + case PROJECT_STATUS.INACTIVE: + status = 'isPaused' + break + case PROJECT_STATUS.RESTORING: + status = 'isRestoring' + break + case PROJECT_STATUS.UNKNOWN: + case PROJECT_STATUS.COMING_UP: + status = 'isComingUp' + break + default: + status = '' + } + return status +} + const ProjectCard = ({ project, rewriteHref, @@ -33,21 +52,11 @@ const ProjectCard = ({ const { name, ref: projectRef } = project const desc = `${project.cloud_provider} | ${project.region}` - const showResourceExhaustionWarnings = useFlag('resourceExhaustionWarnings') - const isBranchingEnabled = project.preview_branch_refs.length > 0 const isGithubIntegrated = githubIntegration !== undefined const isVercelIntegrated = vercelIntegration !== undefined const githubRepository = githubIntegration?.metadata.name ?? undefined - - // Project status should supersede its read only status - const isHealthy = project.status === PROJECT_STATUS.ACTIVE_HEALTHY - const isPausing = - project.status === PROJECT_STATUS.GOING_DOWN || project.status === PROJECT_STATUS.PAUSING - const isPaused = project.status === PROJECT_STATUS.INACTIVE - const isRestoring = project.status === PROJECT_STATUS.RESTORING - const isComingUp = - project.status === PROJECT_STATUS.UNKNOWN || project.status === PROJECT_STATUS.COMING_UP + const projectStatus = getProjetStatus(project) return (
  • @@ -56,7 +65,8 @@ const ProjectCard = ({ containerHeightClassName="h-48" title={
    -

    {name}

    +

    {name}

    + {desc}
    {isVercelIntegrated && (
    @@ -84,61 +94,16 @@ const ProjectCard = ({
    } footer={ -
    - {desc} - - {isHealthy && ( - -
    - Active -
    -
    - )} - - {isRestoring && ( - -
    - - Restoring -
    -
    - )} - - {isPausing && ( - -
    - - Pausing -
    -
    - )} - - {isPaused && ( - -
    - - Paused -
    -
    - )} - - {isComingUp && ( - -
    - - Coming up -
    -
    +
    + {resourceWarnings && ( + )}
    } - > - {showResourceExhaustionWarnings && resourceWarnings ? ( - - ) : ( -
    - )} - + >
  • ) } diff --git a/studio/components/interfaces/Home/ProjectList/ProjectCardStatus.tsx b/studio/components/interfaces/Home/ProjectList/ProjectCardStatus.tsx new file mode 100644 index 00000000000..e04a27d4bcb --- /dev/null +++ b/studio/components/interfaces/Home/ProjectList/ProjectCardStatus.tsx @@ -0,0 +1,134 @@ +import { RESOURCE_WARNING_MESSAGES } from 'components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants' +import { ResourceWarning } from 'data/usage/resource-warnings-query' +import { + Alert_Shadcn_, + AlertDescription_Shadcn_, + AlertTitle_Shadcn_, + IconAlertTriangle, + IconLoader, + IconPauseCircle, +} from 'ui' + +export interface ProjectCardWarningsProps { + resourceWarnings: ResourceWarning + projectStatus: string +} + +export const ProjectCardStatus = ({ + resourceWarnings, + projectStatus, +}: ProjectCardWarningsProps) => { + //const showResourceExhaustionWarnings = useFlag('resourceExhaustionWarnings') + const showResourceExhaustionWarnings = false + + // [Joshen] Read only takes higher precedence over multiple resource warnings + const activeWarnings = resourceWarnings.is_readonly_mode_enabled + ? ['is_readonly_mode_enabled'] + : Object.keys(resourceWarnings).filter( + (property) => + property !== 'project' && + property !== 'is_readonly_mode_enabled' && + resourceWarnings[property as keyof typeof resourceWarnings] !== null + ) + + const getContent = (metric: string) => { + if (metric === 'is_readonly_mode_enabled') { + return RESOURCE_WARNING_MESSAGES.is_readonly_mode_enabled.cardContent.warning + } + + const severity = resourceWarnings[metric as keyof typeof resourceWarnings] + + if (typeof severity !== 'string') return undefined + + return RESOURCE_WARNING_MESSAGES[metric as keyof typeof RESOURCE_WARNING_MESSAGES][ + 'cardContent' + ][severity as 'warning' | 'critical'] + } + + const hasCriticalWarning = activeWarnings.some( + (x) => resourceWarnings[x as keyof typeof resourceWarnings] === 'critical' + ) + + const isCritical = activeWarnings.includes('is_readonly_mode_enabled') || hasCriticalWarning + + const getWarningType = () => { + let type = 'default' + + switch (true) { + case projectStatus === 'isPaused': + type = 'default' + break + case isCritical: + type = 'destructive' + break + default: + type = 'warning' + break + } + + return type as 'warning' | 'destructive' | 'default' + } + + const AlertIcon = () => { + switch (true) { + case projectStatus === 'isPaused' || projectStatus === 'isPausing': + return + case projectStatus === 'isRestoring' || projectStatus === 'isComingUp': + return + default: + return + } + } + const getTitle = () => { + if (projectStatus === 'isPaused') return 'Project is paused' + if (projectStatus === 'isPausing') return 'Project is pausing' + if (projectStatus === 'isComingUp') return 'Project is coming up' + if (projectStatus === 'isRestoring') return 'Project is restoring' + + // If none of the paused/restoring states match, proceed with the default logic + return activeWarnings.length > 1 + ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.cardContent[ + hasCriticalWarning ? 'critical' : 'warning' + ].title + : getContent(activeWarnings[0])?.title + } + + const getDescription = () => { + if (projectStatus === 'isPaused') + return 'This project will not accept any requests until unpaused.' + if (projectStatus === 'isPausing') + return 'This project is entering a paused state. This may take a few minutes.' + if (projectStatus === 'isComingUp') + return 'This project is coming up. This may take a few minutes.' + if (projectStatus === 'isRestoring') + return 'This project is restoring. This may take a few minutes.' + + // If none of the paused/restoring states match, proceed with the default logic + return activeWarnings.length > 1 && showResourceExhaustionWarnings + ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.cardContent[ + hasCriticalWarning ? 'critical' : 'warning' + ].description + : getContent(activeWarnings[0])?.description + } + + const alertTitle = getTitle() + const alertDescription = getDescription() + const alertType = getWarningType() + + if (activeWarnings.length === 0 && projectStatus === 'isHealthy') return
    + + return ( +
    + + + + {alertTitle} + {alertDescription} + +
    + ) +} diff --git a/studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx b/studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx deleted file mode 100644 index a07d41ff3b1..00000000000 --- a/studio/components/interfaces/Home/ProjectList/ProjectCardWarnings.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import { ResourceWarning } from 'data/usage/resource-warnings-query' -import { Alert_Shadcn_, AlertDescription_Shadcn_, AlertTitle_Shadcn_, IconAlertTriangle } from 'ui' - -export interface ProjectCardWarningsProps { - resourceWarnings: ResourceWarning -} - -const RESOURCE_WARNING_MESSAGES = { - is_readonly_mode_enabled: { - warning: { - title: 'Project is in readonly mode', - description: 'Database is no longer accept write requests.', - }, - critical: { - title: 'Project is in readonly mode', - description: 'Database is no longer accept write requests.', - }, - }, - disk_io_exhaustion: { - warning: { - title: 'Project is depleting its Disk IO Budget', - description: 'Instance may become unresponsive if fully exhausted.', - }, - critical: { - title: 'Project has depleted its Disk IO Budget', - description: 'Instance may become unresponsive.', - }, - }, - disk_space_exhaustion: { - warning: { - title: 'Project is exhausting disk space budget', - description: 'Instance may become unresponsive if fully exhausted.', - }, - critical: { - title: 'Project has exhausted disk space budget', - description: 'Instance may become unresponsive.', - }, - }, - cpu_exhaustion: { - warning: { - title: 'Project has high CPU usage', - description: `Instance's performance is affected`, - }, - critical: { - title: 'Project CPU usage is at 100%', - description: `Instance's performance is affected`, - }, - }, - memory_and_swap_exhaustion: { - warning: { - title: 'Project has high memory usage', - description: `Instance's performance is affected.`, - }, - critical: { - title: 'Project memory usage is at 100%', - description: `Instance's performance is affected.`, - }, - }, - multiple_resource_warnings: { - warning: { - title: 'Project is exhausting multiple resources', - description: `Instance's performance is affected.`, - }, - critical: { - title: 'Project has exhausted at least one resource', - description: `Instance's performance is affected.`, - }, - }, -} - -const ProjectCardWarnings = ({ resourceWarnings }: ProjectCardWarningsProps) => { - // [Joshen] Read only takes higher precedence over multiple resource warnings - const activeWarnings = resourceWarnings.is_readonly_mode_enabled - ? ['is_readonly_mode_enabled'] - : Object.keys(resourceWarnings).filter( - (property) => - property !== 'project' && - property !== 'is_readonly_mode_enabled' && - resourceWarnings[property as keyof typeof resourceWarnings] !== null - ) - - const getContent = (metric: string) => { - if (metric === 'is_readonly_mode_enabled') { - return RESOURCE_WARNING_MESSAGES.is_readonly_mode_enabled.warning - } - const severity = resourceWarnings[metric as keyof typeof resourceWarnings] - if (typeof severity !== 'string') return undefined - return RESOURCE_WARNING_MESSAGES[metric as keyof typeof RESOURCE_WARNING_MESSAGES][ - severity as 'warning' | 'critical' - ] - } - - const hasCriticalWarning = activeWarnings.some( - (x) => resourceWarnings[x as keyof typeof resourceWarnings] === 'critical' - ) - - const isCritical = activeWarnings.includes('is_readonly_mode_enabled') || hasCriticalWarning - - if (activeWarnings.length === 0) return
    - - return ( -
    - - - - - {activeWarnings.length > 1 - ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings[ - hasCriticalWarning ? 'critical' : 'warning' - ].title - : getContent(activeWarnings[0])?.title} - - - {activeWarnings.length > 1 - ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings[ - hasCriticalWarning ? 'critical' : 'warning' - ].description - : getContent(activeWarnings[0])?.description} - - -
    - ) -} - -export default ProjectCardWarnings diff --git a/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts b/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts index 5e54f833199..49fd1ada259 100644 --- a/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts +++ b/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts @@ -1,17 +1,27 @@ export const RESOURCE_WARNING_MESSAGES = { is_readonly_mode_enabled: { - content: { + bannerContent: { warning: { title: 'Your project is currently in read-only mode and is no longer accepting write requests', description: - 'You will need to manually override read-only mode and reduce the disk size to below 95%.', + 'You will need to manually override read-only mode and reduce the disk size to below 95%', }, critical: { title: 'Your project is currently in read-only mode and is no longer accepting write requests', description: - 'You will need to manually override read-only mode and reduce the disk size to below 95%.', + 'You will need to manually override read-only mode and reduce the disk size to below 95%', + }, + }, + cardContent: { + warning: { + title: 'Project is in read-only mode', + description: 'Database is no longer accepting write requests.', + }, + critical: { + title: 'Project is in read-only mode', + description: 'Database is no longer accepting write requests.', }, }, docsUrl: 'https://supabase.com/docs/guides/platform/database-size#disabling-read-only-mode', @@ -19,7 +29,7 @@ export const RESOURCE_WARNING_MESSAGES = { metric: undefined, }, disk_io_exhaustion: { - content: { + bannerContent: { warning: { title: 'Your project is about to deplete its Disk IO Budget, and may become unresponsive once fully exhausted', @@ -32,12 +42,22 @@ export const RESOURCE_WARNING_MESSAGES = { 'You will need to either optimize your performance, or upgrade your compute to a larger plan.', }, }, + cardContent: { + warning: { + title: 'Project is depleting its Disk IO Budget', + description: 'Project may become unresponsive if fully exhausted.', + }, + critical: { + title: 'Project has depleted its Disk IO Budget', + description: 'Project may become unresponsive', + }, + }, docsUrl: 'https://supabase.com/docs/guides/platform/exhaust-disk-io', buttonText: 'Check usage', metric: 'disk_io', }, disk_space_exhaustion: { - content: { + bannerContent: { warning: { title: 'Your project is about to exhaust its disk space budget, and may become unresponsive once fully exhausted', @@ -50,16 +70,26 @@ export const RESOURCE_WARNING_MESSAGES = { 'You can opt to increase your disk size up to 200GB on the database settings page.', }, }, + cardContent: { + warning: { + title: 'Project is exhausting disk space budget', + description: 'Project may become unresponsive if fully exhausted', + }, + critical: { + title: 'Project has exhausted disk space budget', + description: 'Project may become unresponsive', + }, + }, docsUrl: 'https://supabase.com/docs/guides/platform/database-size#disk-management', buttonText: undefined, metric: 'disk_space', }, cpu_exhaustion: { - content: { + bannerContent: { warning: { title: 'Your project is currently facing high CPU usage, and its performance is affected', description: - 'You will need to either optimize your performance or upgrade your compute to a larger instance', + 'You will need to either optimize your performance or upgrade your compute to a larger Project', }, critical: { title: "Your project's CPU usage is at 100% and its performance is affected", @@ -67,12 +97,22 @@ export const RESOURCE_WARNING_MESSAGES = { 'You will need to either optimize your performance or upgrade your compute to a larger plan', }, }, + cardContent: { + warning: { + title: 'Project has high CPU usage', + description: `Project performance is affected`, + }, + critical: { + title: 'Project CPU usage is at 100%', + description: `Project performance is affected`, + }, + }, docsUrl: 'https://supabase.com/docs/guides/platform/exhaust-cpu', buttonText: 'Check usage', metric: 'cpu', }, memory_and_swap_exhaustion: { - content: { + bannerContent: { warning: { title: 'Your project is currently facing high memory usage, and its performance is affected', @@ -85,12 +125,22 @@ export const RESOURCE_WARNING_MESSAGES = { 'You will need to either optimize your performance or upgrade your compute to a larger plan', }, }, + cardContent: { + warning: { + title: 'Project has high memory usage', + description: `Project performance is affected.`, + }, + critical: { + title: 'Project memory usage is at 100%', + description: `Project performance is affected.`, + }, + }, docsUrl: 'https://supabase.com/docs/guides/platform/exhaust-ram', buttonText: 'Check usage', metric: 'ram', }, multiple_resource_warnings: { - content: { + bannerContent: { warning: { title: 'Your project is currently exhausting multiple resources, and its performance is affected', @@ -98,11 +148,21 @@ export const RESOURCE_WARNING_MESSAGES = { "Check which resources are reaching their threshold on your project's usage page.", }, critical: { - title: 'Your project has exhausted at least one resource, and its performance is affected', + title: 'Your project has exhausted multiple resources, and its performance is affected', description: "Check which resources have reached their threshold on your project's usage page.", }, }, + cardContent: { + warning: { + title: 'Project is exhausting multiple resources', + description: `Project performance is affected.`, + }, + critical: { + title: 'Project has exhausted multiple resources', + description: `Project performance is affected.`, + }, + }, docsUrl: undefined, buttonText: 'Check usage', metric: null, diff --git a/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.tsx b/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.tsx index e406b1baaa8..8c0ac31cf89 100644 --- a/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.tsx +++ b/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.tsx @@ -49,24 +49,23 @@ const ResourceExhaustionWarningBanner = () => { const getContent = (metric: string) => { if (projectResourceWarnings === undefined) return undefined if (metric === 'is_readonly_mode_enabled') { - return RESOURCE_WARNING_MESSAGES.is_readonly_mode_enabled.content.warning + return RESOURCE_WARNING_MESSAGES.is_readonly_mode_enabled.bannerContent.warning } const severity = projectResourceWarnings[metric as keyof typeof projectResourceWarnings] if (typeof severity !== 'string') return undefined - return RESOURCE_WARNING_MESSAGES[metric as keyof typeof RESOURCE_WARNING_MESSAGES].content[ - severity as 'warning' | 'critical' - ] + return RESOURCE_WARNING_MESSAGES[metric as keyof typeof RESOURCE_WARNING_MESSAGES] + .bannerContent[severity as 'warning' | 'critical'] } const title = activeWarnings.length > 1 - ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.content[ + ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.bannerContent[ hasCriticalWarning ? 'critical' : 'warning' ].title : getContent(activeWarnings[0])?.title const description = activeWarnings.length > 1 - ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.content[ + ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.bannerContent[ hasCriticalWarning ? 'critical' : 'warning' ].description : getContent(activeWarnings[0])?.description @@ -129,7 +128,7 @@ const ResourceExhaustionWarningBanner = () => { {correctionUrl !== undefined && ( - + )} diff --git a/studio/data/usage/resource-warnings-query.ts b/studio/data/usage/resource-warnings-query.ts index b1d56c12cfe..897628e99af 100644 --- a/studio/data/usage/resource-warnings-query.ts +++ b/studio/data/usage/resource-warnings-query.ts @@ -9,6 +9,25 @@ export async function getResourceWarnings(signal?: AbortSignal) { const { data, error } = await get(`/platform/projects-resource-warnings`, { signal }) if (error) throw error + // return [ + // { + // project: 'qahowvikfwlkdqvvbfzd', + // is_readonly_mode_enabled: false, + // disk_io_exhaustion: null, + // disk_space_exhaustion: null, + // cpu_exhaustion: null, + // memory_and_swap_exhaustion: 'critical', + // }, + // { + // project: 'pkxlobkdgearimcwwgxk', + // is_readonly_mode_enabled: false, + // disk_io_exhaustion: 'critical', + // disk_space_exhaustion: null, + // cpu_exhaustion: null, + // memory_and_swap_exhaustion: null, + // }, + // ] as ResourceWarning[] + return data } From 4b71473e31f4997b5a5f4cbbf6d22bded95730e1 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Wed, 6 Sep 2023 21:07:53 -0230 Subject: [PATCH 16/49] Nudge banners --- .../ResourceExhaustionWarningBanner.tsx | 4 ++-- studio/data/usage/resource-warnings-query.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.tsx b/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.tsx index 8c0ac31cf89..f0fcd6320ee 100644 --- a/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.tsx +++ b/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.tsx @@ -109,13 +109,13 @@ const ResourceExhaustionWarningBanner = () => { return ( svg]:left-6 px-6 [&>svg]:w-[26px] [&>svg]:h-[26px]" > {title} {description} -
    +
    {learnMoreUrl !== undefined && ( diff --git a/studio/data/usage/resource-warnings-query.ts b/studio/data/usage/resource-warnings-query.ts index 897628e99af..e85d8778fb7 100644 --- a/studio/data/usage/resource-warnings-query.ts +++ b/studio/data/usage/resource-warnings-query.ts @@ -11,7 +11,7 @@ export async function getResourceWarnings(signal?: AbortSignal) { // return [ // { - // project: 'qahowvikfwlkdqvvbfzd', + // project: 'hpzzzjznweoqiwwaxymv', // is_readonly_mode_enabled: false, // disk_io_exhaustion: null, // disk_space_exhaustion: null, From f3be5b202097da8c41d19ac72b6f47b44ca442c5 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Wed, 6 Sep 2023 21:17:09 -0230 Subject: [PATCH 17/49] Nudge card height --- studio/components/interfaces/Home/ProjectList/ProjectCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx b/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx index 9d3f44c63ec..b9dcee111b2 100644 --- a/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx +++ b/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx @@ -62,7 +62,7 @@ const ProjectCard = ({
  • {name}

    From b65ebe8bf9abf528013d07162c43c546a610721c Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Sat, 9 Sep 2023 04:09:54 +0800 Subject: [PATCH 18/49] feat: storage report cache misses --- .../interfaces/Reports/Reports.constants.ts | 44 +++++++ .../interfaces/Reports/Reports.types.ts | 1 + .../Reports/renderers/StorageRenderers.tsx | 83 +++++++++++++ .../ReportsLayout/ReportsMenu.utils.ts | 14 ++- .../components/ui/Charts/Charts.constants.ts | 49 ++++---- .../components/ui/Charts/StackedBarChart.tsx | 7 +- .../pages/project/[ref]/reports/storage.tsx | 114 ++++++++++++++++++ 7 files changed, 288 insertions(+), 24 deletions(-) create mode 100644 studio/components/interfaces/Reports/renderers/StorageRenderers.tsx create mode 100644 studio/pages/project/[ref]/reports/storage.tsx diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts index 71b5f65324f..354614b26f0 100644 --- a/studio/components/interfaces/Reports/Reports.constants.ts +++ b/studio/components/interfaces/Reports/Reports.constants.ts @@ -228,6 +228,50 @@ export const PRESET_CONFIG: Record = { title: '', queries: {}, }, + [Presets.STORAGE]: { + title: 'Storage', + queries: { + cacheHitRate: { + queryType: 'logs', + // storage report does not perform any filtering + sql: (_filters) => ` +SELECT + timestamp_trunc(timestamp, hour) as timestamp, + countif( h.cf_cache_status in ('HIT', 'STALE', 'REVALIDATED', 'UPDATING') ) as hit_count, + countif( h.cf_cache_status in ('MISS', 'NONE/UNKNOWN', 'EXPIRED', 'BYPASS', 'DYNAMIC') ) as miss_count +from edge_logs f + cross join unnest(f.metadata) as m + cross join unnest(m.request) as r + cross join unnest(m.response) as res + cross join unnest(res.headers) as h +where starts_with(r.path, '/storage/v1/object') and r.method = 'GET' +group by timestamp +order by timestamp desc +`, + }, + topCacheMisses: { + queryType: 'logs', + // storage report does not perform any filtering + sql: (_filters) => ` +SELECT + r.path as path, + r.search as search, + count(id) as count +from edge_logs f + cross join unnest(f.metadata) as m + cross join unnest(m.request) as r + cross join unnest(m.response) as res + cross join unnest(res.headers) as h +where starts_with(r.path, '/storage/v1/object') + and r.method = 'GET' + and h.cf_cache_status in ('MISS', 'NONE/UNKNOWN', 'EXPIRED', 'BYPASS', 'DYNAMIC') +group by path, search +order by count desc +limit 12 + `, + }, + }, + }, [Presets.QUERY_PERFORMANCE]: { title: 'Query performance', queries: { diff --git a/studio/components/interfaces/Reports/Reports.types.ts b/studio/components/interfaces/Reports/Reports.types.ts index a13240a5125..900a7c0c437 100644 --- a/studio/components/interfaces/Reports/Reports.types.ts +++ b/studio/components/interfaces/Reports/Reports.types.ts @@ -3,6 +3,7 @@ import { DEFAULT_QUERY_PARAMS } from './Reports.constants' export enum Presets { API = 'api', + STORAGE = 'storage', AUTH = 'auth', QUERY_PERFORMANCE = 'query_performance', } diff --git a/studio/components/interfaces/Reports/renderers/StorageRenderers.tsx b/studio/components/interfaces/Reports/renderers/StorageRenderers.tsx new file mode 100644 index 00000000000..0cc73886f19 --- /dev/null +++ b/studio/components/interfaces/Reports/renderers/StorageRenderers.tsx @@ -0,0 +1,83 @@ +import { TextFormatter } from 'components/interfaces/Settings/Logs/LogsFormatters' +import Table from 'components/to-be-cleaned/Table' +import StackedBarChart from 'components/ui/Charts/StackedBarChart' +import { Fragment } from 'react' +import { ReportWidgetProps, ReportWidgetRendererProps } from '../ReportWidget' + +export const CacheHitRateChartRenderer = ( + props: ReportWidgetProps<{ + timestamp: string + hit_count: number + miss_count: number + }> +) => { + const stackedData = props.data.flatMap((datum) => [ + { + timestamp: datum.timestamp, + count: datum.hit_count, + type: 'hit', + }, + { + timestamp: datum.timestamp, + count: datum.miss_count, + type: 'miss', + }, + ]) + + return ( + + ) +} + +export const TopCacheMissesRenderer = ( + props: ReportWidgetRendererProps<{ + path: string + search: string + count: number + }> +) => { + if (props.data.length === 0) return null + const headerClasses = '!text-xs !py-2 p-0 font-bold !bg-scale-400' + const cellClasses = '!text-xs !py-2' + + return ( + + Request + Count + + } + body={ + <> + {props.data.map((datum) => ( + + + +
    + + +
    +
    + + {datum.count} + +
    +
    + ))} + + } + /> + ) +} diff --git a/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts b/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts index 4e088fd0ae6..1595e65861b 100644 --- a/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts +++ b/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts @@ -1,9 +1,10 @@ import { Project } from 'types' import { ProductMenuGroup } from 'components/ui/ProductMenu/ProductMenu.types' +import { useFlag } from 'hooks' export const generateReportsMenu = (project?: Project): ProductMenuGroup[] => { const ref = project?.ref ?? 'default' - + const storageReportFlag = useFlag('storageReport') return [ { title: '', @@ -25,6 +26,17 @@ export const generateReportsMenu = (project?: Project): ProductMenuGroup[] => { items: [], label: 'NEW', }, + ...(storageReportFlag + ? [ + { + name: 'Storage', + key: 'storage', + url: `/project/${ref}/reports/storage`, + items: [], + label: 'NEW', + }, + ] + : []), { name: 'Database', key: 'database', diff --git a/studio/components/ui/Charts/Charts.constants.ts b/studio/components/ui/Charts/Charts.constants.ts index d760c7bf7e6..1354ac68914 100644 --- a/studio/components/ui/Charts/Charts.constants.ts +++ b/studio/components/ui/Charts/Charts.constants.ts @@ -10,28 +10,35 @@ export const CHART_COLORS = { } // refer to packages/ui/radix-colors.js for full list of colors -export const STACK_COLORS = [ - ['brand', 9], - ['mint', 9], - ['blue', 9], - ['sky', 9], - ['lime', 9], - ['yellow', 9], - ['orange', 9], -].map(([color, n]) => ({ - lighter: `var(--colors-${color}${(n as number) - 1})`, - base: `var(--colors-${color}${n})`, - darker: `var(--colors-${color}${(n as number) + 1})`, -})) +export type ValidStackColor = + | 'brand' + | 'blue' + | 'red' + | 'yellow' + | 'green' + | 'slate' + | 'indigo' + | 'tomato' + | 'orange' + | 'amber' -export const USAGE_COLORS = { - 200: 'hsl(var(--brand-default))', - 201: 'hsl(var(--brand-500))', - 400: 'var(--colors-amber9)', - 401: 'var(--colors-amber8)', - 404: 'var(--colors-amber7)', - 500: 'var(--colors-red9)', -} +export const genStackColorScales = (colors: ValidStackColor[]) => + colors.map((color) => { + // override default base scale for certain colors that do not have good contrast + const scale = + ( + { + slate: 11, + } as any + )[color] ?? 9 + return { + lighter: `var(--colors-${color}${(scale as number) - 1})`, + base: `var(--colors-${color}${scale})`, + darker: `var(--colors-${color}${(scale as number) + 1})`, + } + }) + +export const DEFAULT_STACK_COLORS = ['brand', 'slate', 'blue', 'yellow', 'indigo'] export enum DateTimeFormats { FULL = 'MMM D, YYYY, hh:mma', diff --git a/studio/components/ui/Charts/StackedBarChart.tsx b/studio/components/ui/Charts/StackedBarChart.tsx index 4ce2b8e1ee8..f3123578129 100644 --- a/studio/components/ui/Charts/StackedBarChart.tsx +++ b/studio/components/ui/Charts/StackedBarChart.tsx @@ -1,7 +1,7 @@ import { useState } from 'react' import { BarChart, Bar, XAxis, Tooltip, Legend, Cell, TooltipProps } from 'recharts' import ChartHeader from './ChartHeader' -import { CHART_COLORS, STACK_COLORS, DateTimeFormats } from './Charts.constants' +import { CHART_COLORS, STACK_COLORS, DateTimeFormats, DEFAULT_STACK_COLORS, genStackColorScales } from './Charts.constants' import { CommonChartProps } from './Charts.types' import { timestampFormatter, useChartSize, useStacked } from './Charts.utils' import { precisionFormatter } from './Charts.utils' @@ -16,6 +16,7 @@ interface Props extends CommonChartProps { displayDateInUtc?: boolean hideLegend?: boolean hideHeader?: boolean + stackColors?: ValidStackColor } const StackedBarChart: React.FC = ({ size, @@ -33,6 +34,7 @@ const StackedBarChart: React.FC = ({ displayDateInUtc, hideLegend = false, hideHeader = false, + stackColors = DEFAULT_STACK_COLORS }) => { const { Container } = useChartSize(size) const { dataKeys, stackedData, percentagesStackedData } = useStacked({ @@ -44,6 +46,7 @@ const StackedBarChart: React.FC = ({ }) const [focusDataIndex, setFocusDataIndex] = useState(null) if (!data || data.length === 0) return + const stackColorScales = genStackColorScales(stackColors) return (
    {!hideHeader && ( @@ -94,7 +97,7 @@ const StackedBarChart: React.FC = ({ dataKey={datum} type="monotone" legendType="circle" - fill={STACK_COLORS[stackIndex].base} + fill={stackColorScales[stackIndex].base} stackId={1} animationDuration={300} maxBarSize={48} diff --git a/studio/pages/project/[ref]/reports/storage.tsx b/studio/pages/project/[ref]/reports/storage.tsx new file mode 100644 index 00000000000..80047336e38 --- /dev/null +++ b/studio/pages/project/[ref]/reports/storage.tsx @@ -0,0 +1,114 @@ +import { observer } from 'mobx-react-lite' +import { NextPageWithLayout } from 'types' +import { ReportsLayout } from 'components/layouts' +import { + PRESET_CONFIG, + REPORTS_DATEPICKER_HELPERS, +} from 'components/interfaces/Reports/Reports.constants' +import ReportWidget from 'components/interfaces/Reports/ReportWidget' +import { queriesFactory } from 'components/interfaces/Reports/Reports.utils' +import { + CacheHitRateChartRenderer, + TopCacheMissesRenderer, +} from 'components/interfaces/Reports/renderers/StorageRenderers' +import { useMemo } from 'react' +import ReportHeader from 'components/interfaces/Reports/ReportHeader' +import { DatePickerToFrom, LogsEndpointParams } from 'components/interfaces/Settings/Logs' +import { useParams } from 'common' +import ShimmerLine from 'components/ui/ShimmerLine' +import ReportPadding from 'components/interfaces/Reports/ReportPadding' +import { useProjectSubscriptionV2Query } from 'data/subscriptions/project-subscription-v2-query' +import DatePickers from 'components/interfaces/Settings/Logs/Logs.DatePickers' + +export const StorageReport: NextPageWithLayout = () => { + const { ref: projectRef } = useParams() + const report = useStorageReport() + + const { data: subscription } = useProjectSubscriptionV2Query({ projectRef }) + const plan = subscription?.plan + + const handleDatepickerChange = ({ from, to }: DatePickerToFrom) => { + report.mergeParams({ + iso_timestamp_start: from || '', + iso_timestamp_end: to || '', + }) + } + + const datepickerHelpers = useMemo( + () => + REPORTS_DATEPICKER_HELPERS.map((helper, index) => ({ + ...helper, + disabled: (index > 0 && plan?.id === 'free') || (index > 1 && plan?.id !== 'pro'), + })), + [] + ) + + return ( + + +
    +
    + +
    +
    + +
    +
    + + +
    + ) +} + +// hook to fetch data +const useStorageReport = () => { + const { ref: projectRef } = useParams() + + const queryHooks = queriesFactory( + PRESET_CONFIG.storage.queries, + projectRef ?? 'default' + ) + const cacheHitRate = queryHooks.cacheHitRate() + const topCacheMisses = queryHooks.topCacheMisses() + const activeHooks = [cacheHitRate, topCacheMisses] + + const handleRefresh = async () => { + activeHooks.forEach((hook) => hook.runQuery()) + } + const handleSetParams = (params: Partial) => { + activeHooks.forEach((hook) => { + hook.setParams?.((prev: LogsEndpointParams) => ({ ...prev, ...params })) + }) + } + const isLoading = activeHooks.some((hook) => hook.isLoading) + return { + data: { + cacheHitRate: cacheHitRate.logData, + topCacheMisses: topCacheMisses.logData, + }, + params: { + cacheHitRate: cacheHitRate.params, + topCacheMisses: topCacheMisses.params, + }, + mergeParams: handleSetParams, + isLoading, + refresh: handleRefresh, + } +} + +StorageReport.getLayout = (page) => {page} + +export default observer(StorageReport) From 9938efd61a4004c5eb935dc6b2280cab5620e579 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Sat, 9 Sep 2023 04:15:45 +0800 Subject: [PATCH 19/49] feat: add title to cache misses, add tooltip, adjust widget title --- .../Reports/renderers/StorageRenderers.tsx | 77 +++++++++++-------- .../pages/project/[ref]/reports/storage.tsx | 3 +- 2 files changed, 49 insertions(+), 31 deletions(-) diff --git a/studio/components/interfaces/Reports/renderers/StorageRenderers.tsx b/studio/components/interfaces/Reports/renderers/StorageRenderers.tsx index 0cc73886f19..5c2d72afc88 100644 --- a/studio/components/interfaces/Reports/renderers/StorageRenderers.tsx +++ b/studio/components/interfaces/Reports/renderers/StorageRenderers.tsx @@ -11,6 +11,13 @@ export const CacheHitRateChartRenderer = ( miss_count: number }> ) => { + props.data = [ + { + timestamp: new Date().toISOString(), + hit_count: 123, + miss_count: 123, + }, + ] const stackedData = props.data.flatMap((datum) => [ { timestamp: datum.timestamp, @@ -44,40 +51,50 @@ export const TopCacheMissesRenderer = ( count: number }> ) => { + props.data = [ + { + path: '123', + search: '?123', + count: 123, + }, + ] if (props.data.length === 0) return null const headerClasses = '!text-xs !py-2 p-0 font-bold !bg-scale-400' const cellClasses = '!text-xs !py-2' return ( -
    - Request - Count - - } - body={ - <> - {props.data.map((datum) => ( - - - -
    - - -
    -
    - - {datum.count} - -
    -
    - ))} - - } - /> + <> +

    Top Cache Misses

    +
    + Request + Count + + } + body={ + <> + {props.data.map((datum) => ( + + + +
    + + +
    +
    + + {datum.count} + +
    +
    + ))} + + } + /> + ) } diff --git a/studio/pages/project/[ref]/reports/storage.tsx b/studio/pages/project/[ref]/reports/storage.tsx index 80047336e38..fe49c1fe7e5 100644 --- a/studio/pages/project/[ref]/reports/storage.tsx +++ b/studio/pages/project/[ref]/reports/storage.tsx @@ -63,7 +63,8 @@ export const StorageReport: NextPageWithLayout = () => { Date: Sat, 9 Sep 2023 04:17:37 +0800 Subject: [PATCH 20/49] chore: remove new label --- studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts b/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts index 1595e65861b..7fa7ca0238b 100644 --- a/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts +++ b/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts @@ -24,7 +24,6 @@ export const generateReportsMenu = (project?: Project): ProductMenuGroup[] => { key: 'api-overview', url: `/project/${ref}/reports/api-overview`, items: [], - label: 'NEW', }, ...(storageReportFlag ? [ @@ -48,7 +47,6 @@ export const generateReportsMenu = (project?: Project): ProductMenuGroup[] => { key: 'query-performance', url: `/project/${ref}/reports/query-performance`, items: [], - label: 'NEW', }, ], }, From 0ef21e837d2763d40663d948066caee499d7a662 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Sat, 9 Sep 2023 04:34:27 +0800 Subject: [PATCH 21/49] chore: fix failing tests, add test coverage to storage report --- .../interfaces/Reports/Reports.constants.ts | 2 + .../Reports/renderers/StorageRenderers.tsx | 14 ------- .../components/ui/Charts/Charts.constants.ts | 2 +- .../components/ui/Charts/StackedBarChart.tsx | 4 +- .../pages/project/[ref]/reports/storage.tsx | 13 +++++- .../projects/reports/storage-report.test.js | 42 +++++++++++++++++++ 6 files changed, 59 insertions(+), 18 deletions(-) create mode 100644 studio/tests/pages/projects/reports/storage-report.test.js diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts index 354614b26f0..f4c4f69fc39 100644 --- a/studio/components/interfaces/Reports/Reports.constants.ts +++ b/studio/components/interfaces/Reports/Reports.constants.ts @@ -235,6 +235,7 @@ export const PRESET_CONFIG: Record = { queryType: 'logs', // storage report does not perform any filtering sql: (_filters) => ` +-- cache-hit-rate SELECT timestamp_trunc(timestamp, hour) as timestamp, countif( h.cf_cache_status in ('HIT', 'STALE', 'REVALIDATED', 'UPDATING') ) as hit_count, @@ -253,6 +254,7 @@ order by timestamp desc queryType: 'logs', // storage report does not perform any filtering sql: (_filters) => ` +-- top-cache-misses SELECT r.path as path, r.search as search, diff --git a/studio/components/interfaces/Reports/renderers/StorageRenderers.tsx b/studio/components/interfaces/Reports/renderers/StorageRenderers.tsx index 5c2d72afc88..cdff467d303 100644 --- a/studio/components/interfaces/Reports/renderers/StorageRenderers.tsx +++ b/studio/components/interfaces/Reports/renderers/StorageRenderers.tsx @@ -11,13 +11,6 @@ export const CacheHitRateChartRenderer = ( miss_count: number }> ) => { - props.data = [ - { - timestamp: new Date().toISOString(), - hit_count: 123, - miss_count: 123, - }, - ] const stackedData = props.data.flatMap((datum) => [ { timestamp: datum.timestamp, @@ -51,13 +44,6 @@ export const TopCacheMissesRenderer = ( count: number }> ) => { - props.data = [ - { - path: '123', - search: '?123', - count: 123, - }, - ] if (props.data.length === 0) return null const headerClasses = '!text-xs !py-2 p-0 font-bold !bg-scale-400' const cellClasses = '!text-xs !py-2' diff --git a/studio/components/ui/Charts/Charts.constants.ts b/studio/components/ui/Charts/Charts.constants.ts index 1354ac68914..f9161102054 100644 --- a/studio/components/ui/Charts/Charts.constants.ts +++ b/studio/components/ui/Charts/Charts.constants.ts @@ -38,7 +38,7 @@ export const genStackColorScales = (colors: ValidStackColor[]) => } }) -export const DEFAULT_STACK_COLORS = ['brand', 'slate', 'blue', 'yellow', 'indigo'] +export const DEFAULT_STACK_COLORS : ValidStackColor[] = ['brand', 'slate', 'blue', 'yellow', 'indigo'] export enum DateTimeFormats { FULL = 'MMM D, YYYY, hh:mma', diff --git a/studio/components/ui/Charts/StackedBarChart.tsx b/studio/components/ui/Charts/StackedBarChart.tsx index f3123578129..3dbf7e214e0 100644 --- a/studio/components/ui/Charts/StackedBarChart.tsx +++ b/studio/components/ui/Charts/StackedBarChart.tsx @@ -1,7 +1,7 @@ import { useState } from 'react' import { BarChart, Bar, XAxis, Tooltip, Legend, Cell, TooltipProps } from 'recharts' import ChartHeader from './ChartHeader' -import { CHART_COLORS, STACK_COLORS, DateTimeFormats, DEFAULT_STACK_COLORS, genStackColorScales } from './Charts.constants' +import { CHART_COLORS, DateTimeFormats, DEFAULT_STACK_COLORS, genStackColorScales, ValidStackColor } from './Charts.constants' import { CommonChartProps } from './Charts.types' import { timestampFormatter, useChartSize, useStacked } from './Charts.utils' import { precisionFormatter } from './Charts.utils' @@ -16,7 +16,7 @@ interface Props extends CommonChartProps { displayDateInUtc?: boolean hideLegend?: boolean hideHeader?: boolean - stackColors?: ValidStackColor + stackColors?: ValidStackColor[] } const StackedBarChart: React.FC = ({ size, diff --git a/studio/pages/project/[ref]/reports/storage.tsx b/studio/pages/project/[ref]/reports/storage.tsx index fe49c1fe7e5..664265e07a0 100644 --- a/studio/pages/project/[ref]/reports/storage.tsx +++ b/studio/pages/project/[ref]/reports/storage.tsx @@ -11,7 +11,7 @@ import { CacheHitRateChartRenderer, TopCacheMissesRenderer, } from 'components/interfaces/Reports/renderers/StorageRenderers' -import { useMemo } from 'react' +import { useEffect, useMemo } from 'react' import ReportHeader from 'components/interfaces/Reports/ReportHeader' import { DatePickerToFrom, LogsEndpointParams } from 'components/interfaces/Settings/Logs' import { useParams } from 'common' @@ -94,6 +94,17 @@ const useStorageReport = () => { hook.setParams?.((prev: LogsEndpointParams) => ({ ...prev, ...params })) }) } + useEffect(()=>{ + + if (cacheHitRate.changeQuery) { + cacheHitRate.changeQuery(PRESET_CONFIG.storage.queries.cacheHitRate.sql([])) + } + + if (topCacheMisses.changeQuery) { + topCacheMisses.changeQuery(PRESET_CONFIG.storage.queries.topCacheMisses.sql([])) + } + + }, []) const isLoading = activeHooks.some((hook) => hook.isLoading) return { data: { diff --git a/studio/tests/pages/projects/reports/storage-report.test.js b/studio/tests/pages/projects/reports/storage-report.test.js new file mode 100644 index 00000000000..6d24e13ca85 --- /dev/null +++ b/studio/tests/pages/projects/reports/storage-report.test.js @@ -0,0 +1,42 @@ +import { get } from 'lib/common/fetch' +import { render } from '../../../helpers' +import { waitFor, screen } from '@testing-library/react' +import { StorageReport } from 'pages/project/[ref]/reports/storage' +import userEvent from '@testing-library/user-event' + +beforeEach(() => { + // reset mocks between tests + get.mockReset() + get.mockImplementation(async (_url) => [{ result: [] }]) +}) + +test(`static elements`, async () => { + render() + await screen.findByText('Request Caching') + await screen.findByText(/Last 24 hours/) + await screen.findByText(/Custom/) +}) + +test('refresh button', async () => { + render() + await waitFor(() => expect(get).toBeCalled()) + get.mockReset() + userEvent.click(await screen.findByText(/Refresh/)) + await waitFor(() => expect(get).toBeCalled()) +}) + +test('append - top cache misses', async () => { + get.mockImplementation(async (url) => { + if (decodeURIComponent(url).includes('misses')) { + return { + result: [{ path: 'mypath', search: 'some-query', count: 22 }], + } + } + return { result: [{ timestamp: new Date().toISOString(), miss_count: 123, hit_count: 123 }] } + }) + render() + await waitFor(() => expect(get).toBeCalled()) + await screen.findAllByText(/mypath/) + await screen.findAllByText(/some\-query/) + await screen.findAllByText(/22/) +}) From cb46a12e90670257f998eaceae4b493a9e803c05 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Sat, 9 Sep 2023 04:54:35 +0800 Subject: [PATCH 22/49] chore: fix compilation error --- .../layouts/ReportsLayout/ReportsLayout.tsx | 52 +++++++++++++++++- .../ReportsLayout/ReportsMenu.utils.ts | 54 ------------------- 2 files changed, 51 insertions(+), 55 deletions(-) delete mode 100644 studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts diff --git a/studio/components/layouts/ReportsLayout/ReportsLayout.tsx b/studio/components/layouts/ReportsLayout/ReportsLayout.tsx index f1517357776..ef82b75c120 100644 --- a/studio/components/layouts/ReportsLayout/ReportsLayout.tsx +++ b/studio/components/layouts/ReportsLayout/ReportsLayout.tsx @@ -5,6 +5,7 @@ import ProductMenu from 'components/ui/ProductMenu' import { useSelectedProject, withAuth } from 'hooks' import ProjectLayout from '../' import { generateReportsMenu } from './ReportsMenu.utils' +import { useFlag } from 'hooks' interface ReportsLayoutProps { title?: string @@ -15,12 +16,61 @@ const ReportsLayout = ({ title, children }: PropsWithChildren} + productMenu={} >
    {children} diff --git a/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts b/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts deleted file mode 100644 index 7fa7ca0238b..00000000000 --- a/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Project } from 'types' -import { ProductMenuGroup } from 'components/ui/ProductMenu/ProductMenu.types' -import { useFlag } from 'hooks' - -export const generateReportsMenu = (project?: Project): ProductMenuGroup[] => { - const ref = project?.ref ?? 'default' - const storageReportFlag = useFlag('storageReport') - return [ - { - title: '', - items: [ - { - name: 'Custom reports', - key: '', - url: `/project/${ref}/reports`, - items: [], - }, - ], - }, - { - items: [ - { - name: 'API', - key: 'api-overview', - url: `/project/${ref}/reports/api-overview`, - items: [], - }, - ...(storageReportFlag - ? [ - { - name: 'Storage', - key: 'storage', - url: `/project/${ref}/reports/storage`, - items: [], - label: 'NEW', - }, - ] - : []), - { - name: 'Database', - key: 'database', - url: `/project/${ref}/reports/database`, - items: [], - }, - { - name: 'Query Performance', - key: 'query-performance', - url: `/project/${ref}/reports/query-performance`, - items: [], - }, - ], - }, - ] -} From e35fbe564511ad4093deec4728e7323bdf51f0e5 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Sat, 9 Sep 2023 05:09:14 +0800 Subject: [PATCH 23/49] chore fix compilation --- studio/components/layouts/ReportsLayout/ReportsLayout.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/studio/components/layouts/ReportsLayout/ReportsLayout.tsx b/studio/components/layouts/ReportsLayout/ReportsLayout.tsx index ef82b75c120..0bdd0813a26 100644 --- a/studio/components/layouts/ReportsLayout/ReportsLayout.tsx +++ b/studio/components/layouts/ReportsLayout/ReportsLayout.tsx @@ -4,7 +4,6 @@ import { PropsWithChildren } from 'react' import ProductMenu from 'components/ui/ProductMenu' import { useSelectedProject, withAuth } from 'hooks' import ProjectLayout from '../' -import { generateReportsMenu } from './ReportsMenu.utils' import { useFlag } from 'hooks' interface ReportsLayoutProps { From 4acdd8b2dfeca734a92f463cfab768b1f02a956c Mon Sep 17 00:00:00 2001 From: samueldenzil Date: Sat, 9 Sep 2023 09:39:24 +0530 Subject: [PATCH 24/49] fix default value of boolean column not respecting --- .../TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx index 55a3c587b11..42b55172d31 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx @@ -202,7 +202,7 @@ const InputField = ({ ...(field.isNullable ? [{ value: 'null', label: 'NULL' }] : []), ] - const defaultValue = field.value === null ? 'null' : field.value + const defaultValue = field.defaultValue === null ? 'null' : field.defaultValue return ( Date: Mon, 11 Sep 2023 17:13:11 +0800 Subject: [PATCH 25/49] chore: fix import --- studio/components/ui/Charts/Sparkline.stories.tsx | 2 +- studio/components/ui/Charts/StackedBarChart.stories.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/studio/components/ui/Charts/Sparkline.stories.tsx b/studio/components/ui/Charts/Sparkline.stories.tsx index 5cb2e257683..18a620f4576 100644 --- a/studio/components/ui/Charts/Sparkline.stories.tsx +++ b/studio/components/ui/Charts/Sparkline.stories.tsx @@ -3,7 +3,7 @@ import { ComponentStory, ComponentMeta } from '@storybook/react' import dayjs from 'dayjs' import Chart from './Sparkline' import { StackedChartProps } from './Charts.types' -import { STACK_COLORS } from './Charts.constants' +import { DEFAULT_STACK_COLORs } from './Charts.constants' export default { title: 'Charts/Sparkline', diff --git a/studio/components/ui/Charts/StackedBarChart.stories.tsx b/studio/components/ui/Charts/StackedBarChart.stories.tsx index 9eb71d42736..822542315cf 100644 --- a/studio/components/ui/Charts/StackedBarChart.stories.tsx +++ b/studio/components/ui/Charts/StackedBarChart.stories.tsx @@ -3,7 +3,7 @@ import { ComponentStory, ComponentMeta } from '@storybook/react' import dayjs from 'dayjs' import Chart from './StackedBarChart' import { StackedChartProps } from './Charts.types' -import { STACK_COLORS } from './Charts.constants' +import { DEFAULT_STACK_COLORS } from './Charts.constants' export default { title: 'Charts/StackedBarChart', @@ -38,7 +38,7 @@ export const StackedBarChart = () => (
    Default Stacked Chart Colors
    - {STACK_COLORS.map((c) => ( + {DEFAULT_STACK_COLORS.map((c) => (
    ))}
    From e53d93c4dcb1622781af864602427355fc93c0ea Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Mon, 11 Sep 2023 18:32:06 +0800 Subject: [PATCH 26/49] chore: fix typo --- studio/components/ui/Charts/Sparkline.stories.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/studio/components/ui/Charts/Sparkline.stories.tsx b/studio/components/ui/Charts/Sparkline.stories.tsx index 18a620f4576..1808d40a2d6 100644 --- a/studio/components/ui/Charts/Sparkline.stories.tsx +++ b/studio/components/ui/Charts/Sparkline.stories.tsx @@ -1,9 +1,7 @@ import React from 'react' -import { ComponentStory, ComponentMeta } from '@storybook/react' +import { ComponentMeta } from '@storybook/react' import dayjs from 'dayjs' import Chart from './Sparkline' -import { StackedChartProps } from './Charts.types' -import { DEFAULT_STACK_COLORs } from './Charts.constants' export default { title: 'Charts/Sparkline', From 7d047ccd4da8f880581882e4320952f3260298af Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Mon, 11 Sep 2023 18:45:05 +0800 Subject: [PATCH 27/49] chore: fix typing error --- studio/components/ui/Charts/StackedBarChart.stories.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/studio/components/ui/Charts/StackedBarChart.stories.tsx b/studio/components/ui/Charts/StackedBarChart.stories.tsx index 822542315cf..8a308ea5ffd 100644 --- a/studio/components/ui/Charts/StackedBarChart.stories.tsx +++ b/studio/components/ui/Charts/StackedBarChart.stories.tsx @@ -3,7 +3,7 @@ import { ComponentStory, ComponentMeta } from '@storybook/react' import dayjs from 'dayjs' import Chart from './StackedBarChart' import { StackedChartProps } from './Charts.types' -import { DEFAULT_STACK_COLORS } from './Charts.constants' +import { DEFAULT_STACK_COLORS, genStackColorScales } from './Charts.constants' export default { title: 'Charts/StackedBarChart', @@ -38,7 +38,7 @@ export const StackedBarChart = () => (
    Default Stacked Chart Colors
    - {DEFAULT_STACK_COLORS.map((c) => ( + {genStackColorScales(DEFAULT_STACK_COLORS).map((c) => (
    ))}
    From c37a4a7f41cad2031c5722ba82f142ce5ac42ae8 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Mon, 11 Sep 2023 10:00:38 -0230 Subject: [PATCH 28/49] Update studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts Co-authored-by: Long Hoang <1732217+loong@users.noreply.github.com> --- .../ResourceExhaustionWarningBanner.constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts b/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts index 49fd1ada259..8f061122800 100644 --- a/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts +++ b/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts @@ -39,7 +39,7 @@ export const RESOURCE_WARNING_MESSAGES = { critical: { title: 'Your project has depleted its Disk IO Budget, and may become unresponsive', description: - 'You will need to either optimize your performance, or upgrade your compute to a larger plan.', + 'You will need to optimize your performance or upgrade your compute. Check the usage page for more recent and detailed statistics.', }, }, cardContent: { From e33d28e57930f464d60f715d0794ecd4c038cbdf Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Mon, 11 Sep 2023 10:01:01 -0230 Subject: [PATCH 29/49] Update studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts Co-authored-by: Long Hoang <1732217+loong@users.noreply.github.com> --- .../ResourceExhaustionWarningBanner.constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts b/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts index 8f061122800..f8a1acbc287 100644 --- a/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts +++ b/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts @@ -122,7 +122,7 @@ export const RESOURCE_WARNING_MESSAGES = { critical: { title: "Your project's memory usage is at 100%, and its performance is affected", description: - 'You will need to either optimize your performance or upgrade your compute to a larger plan', + 'You will need to optimize your performance or upgrade your compute. Check the usage page for more recent and detailed statistics.', }, }, cardContent: { From 505e7203e0b9ea410cefe8f3439eb937b138fccb Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Mon, 11 Sep 2023 10:01:12 -0230 Subject: [PATCH 30/49] Update studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts Co-authored-by: Long Hoang <1732217+loong@users.noreply.github.com> --- .../ResourceExhaustionWarningBanner.constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts b/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts index f8a1acbc287..594086c5724 100644 --- a/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts +++ b/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts @@ -117,7 +117,7 @@ export const RESOURCE_WARNING_MESSAGES = { title: 'Your project is currently facing high memory usage, and its performance is affected', description: - 'You will need to either optimize your performance or upgrade your compute to a larger plan', + 'You will need to optimize your performance or upgrade your compute. Check the usage page for more recent and detailed statistics.', }, critical: { title: "Your project's memory usage is at 100%, and its performance is affected", From ea8208daef5321c500e0598bb5157a1e28b674d0 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Mon, 11 Sep 2023 10:01:23 -0230 Subject: [PATCH 31/49] Update studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts Co-authored-by: Long Hoang <1732217+loong@users.noreply.github.com> --- .../ResourceExhaustionWarningBanner.constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts b/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts index 594086c5724..4460166f534 100644 --- a/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts +++ b/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts @@ -89,7 +89,7 @@ export const RESOURCE_WARNING_MESSAGES = { warning: { title: 'Your project is currently facing high CPU usage, and its performance is affected', description: - 'You will need to either optimize your performance or upgrade your compute to a larger Project', + 'You will need to optimize your performance or upgrade your compute. Check the usage page for more recent and detailed statistics.', }, critical: { title: "Your project's CPU usage is at 100% and its performance is affected", From 28642b1a16b5877cb0bed71de83ef44a90c40b76 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Mon, 11 Sep 2023 10:01:31 -0230 Subject: [PATCH 32/49] Update studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts Co-authored-by: Long Hoang <1732217+loong@users.noreply.github.com> --- .../ResourceExhaustionWarningBanner.constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts b/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts index 4460166f534..5b1383c274b 100644 --- a/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts +++ b/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts @@ -94,7 +94,7 @@ export const RESOURCE_WARNING_MESSAGES = { critical: { title: "Your project's CPU usage is at 100% and its performance is affected", description: - 'You will need to either optimize your performance or upgrade your compute to a larger plan', + 'You will need to optimize your performance or upgrade your compute. Check the usage page for more recent and detailed statistics.', }, }, cardContent: { From f2dc982a3a232fcf720a2cbbd0ba4cd60fd904d2 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Mon, 11 Sep 2023 10:01:39 -0230 Subject: [PATCH 33/49] Update studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts Co-authored-by: Long Hoang <1732217+loong@users.noreply.github.com> --- .../ResourceExhaustionWarningBanner.constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts b/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts index 5b1383c274b..f0e5f2730ac 100644 --- a/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts +++ b/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts @@ -34,7 +34,7 @@ export const RESOURCE_WARNING_MESSAGES = { title: 'Your project is about to deplete its Disk IO Budget, and may become unresponsive once fully exhausted', description: - 'You will need to either optimize your performance, or upgrade your compute to a larger plan.', + 'You will need to optimize your performance or upgrade your compute. Check the usage page for more recent and detailed statistics.', }, critical: { title: 'Your project has depleted its Disk IO Budget, and may become unresponsive', From 9589b4eea62504e99fc261676d58c909c89f51c1 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 12 Sep 2023 17:34:17 +0800 Subject: [PATCH 34/49] Provide convenient unit selection for custom expiry in storage --- .../StorageExplorer/CustomExpiryModal.tsx | 56 +++++++++++++++---- .../StorageExplorer/FileExplorerRow.tsx | 1 + 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx b/studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx index d20d137cdbc..55197f896fd 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx @@ -1,4 +1,4 @@ -import { Form, Modal, Input, Button } from 'ui' +import { Form, Modal, Input, Button, Listbox } from 'ui' import { observer } from 'mobx-react-lite' import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore' @@ -6,6 +6,13 @@ export interface CustomExpiryModalProps { onCopyUrl: (name: string, url: string) => void } +const unitMap: { seconds: number; days: number; months: number; years: number } = { + seconds: 1, + days: 3600 * 24, + months: 3600 * 24 * 30, + years: 3600 * 24 * 365, +} + const CustomExpiryModal = ({ onCopyUrl }: CustomExpiryModalProps) => { const storageExplorerStore = useStorageStore() const { getFileUrl, selectedFileCustomExpiry, setSelectedFileCustomExpiry } = storageExplorerStore @@ -25,34 +32,59 @@ const CustomExpiryModal = ({ onCopyUrl }: CustomExpiryModalProps) => { >
    { setSubmitting(true) onCopyUrl( selectedFileCustomExpiry.name, - await getFileUrl(selectedFileCustomExpiry, values.expiresIn) + await getFileUrl( + selectedFileCustomExpiry, + values.expiresIn * unitMap[values.units as 'seconds' | 'days' | 'months' | 'years'] + ) ) setSubmitting(false) onClose() }} validate={(values: any) => { const errors: any = {} - if (values.expiresIn !== '' && values.expiresIn <= 0) + if (values.expiresIn !== '' && values.expiresIn <= 0) { errors.expiresIn = 'Expiry duration cannot be less than 0' + } return errors }} > {({ values, isSubmitting }: { values: any; isSubmitting: boolean }) => ( <> -
    +
    - seconds

    } - /> +

    + Enter the duration for which the URL will be valid for: +

    +
    + + + + seconds + + + days + + + months + + + years + + +
    + {values.units !== 'seconds' && ( +

    + Equivalent to{' '} + {values.expiresIn * + unitMap[values.units as 'seconds' | 'days' | 'months' | 'years']}{' '} + seconds +

    + )}
    diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.tsx b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.tsx index ba4ee7bc678..36a73884997 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.tsx +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.tsx @@ -409,6 +409,7 @@ const FileExplorerRow = ({ /> ) : ( Date: Tue, 12 Sep 2023 18:55:19 +0800 Subject: [PATCH 35/49] Update custom expiry units selection --- .../StorageExplorer/CustomExpiryModal.tsx | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx b/studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx index 55197f896fd..82697ff1ce1 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx @@ -1,14 +1,17 @@ -import { Form, Modal, Input, Button, Listbox } from 'ui' +import dayjs from 'dayjs' import { observer } from 'mobx-react-lite' +import { Button, Form, Input, Listbox, Modal } from 'ui' + +import { DATETIME_FORMAT } from 'lib/constants' import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore' export interface CustomExpiryModalProps { onCopyUrl: (name: string, url: string) => void } -const unitMap: { seconds: number; days: number; months: number; years: number } = { - seconds: 1, +const unitMap = { days: 3600 * 24, + weeks: 3600 * 24 * 7, months: 3600 * 24 * 30, years: 3600 * 24 * 365, } @@ -32,14 +35,14 @@ const CustomExpiryModal = ({ onCopyUrl }: CustomExpiryModalProps) => { > { setSubmitting(true) onCopyUrl( selectedFileCustomExpiry.name, await getFileUrl( selectedFileCustomExpiry, - values.expiresIn * unitMap[values.units as 'seconds' | 'days' | 'months' | 'years'] + values.expiresIn * unitMap[values.units as 'days' | 'weeks' | 'months' | 'years'] ) ) setSubmitting(false) @@ -63,12 +66,12 @@ const CustomExpiryModal = ({ onCopyUrl }: CustomExpiryModalProps) => {
    - - seconds - days + + weeks + months @@ -77,12 +80,10 @@ const CustomExpiryModal = ({ onCopyUrl }: CustomExpiryModalProps) => {
    - {values.units !== 'seconds' && ( + {values.expiresIn !== '' && (

    - Equivalent to{' '} - {values.expiresIn * - unitMap[values.units as 'seconds' | 'days' | 'months' | 'years']}{' '} - seconds + URL will expire on{' '} + {dayjs().add(values.expiresIn, values.units).format(DATETIME_FORMAT)}

    )} From 97c990cd30e73d4b3502b1e0fddb602db0fa7774 Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Tue, 12 Sep 2023 17:50:56 +0200 Subject: [PATCH 36/49] remove lw8 announcement banner from www and docs --- apps/docs/layouts/SiteLayout.tsx | 4 ---- apps/www/components/Nav/index.tsx | 6 +----- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/apps/docs/layouts/SiteLayout.tsx b/apps/docs/layouts/SiteLayout.tsx index 1b0f0a75df1..953cb518817 100644 --- a/apps/docs/layouts/SiteLayout.tsx +++ b/apps/docs/layouts/SiteLayout.tsx @@ -8,7 +8,6 @@ import Head from 'next/head' import { PropsWithChildren, memo } from 'react' import Footer from '~/components/Navigation/Footer' import { menuState, useMenuLevelId, useMenuMobileOpen } from '~/hooks/useMenuState' -import { Announcement, LW8CountdownBanner } from 'ui' const levelsData = { home: { @@ -327,9 +326,6 @@ const SiteLayout = ({ children }: PropsWithChildren<{}>) => { Supabase Docs
    - - -
    diff --git a/apps/www/components/Nav/index.tsx b/apps/www/components/Nav/index.tsx index 2230165ec95..b36fd57c9dd 100644 --- a/apps/www/components/Nav/index.tsx +++ b/apps/www/components/Nav/index.tsx @@ -2,8 +2,7 @@ import React, { useState } from 'react' import Link from 'next/link' import { useRouter } from 'next/router' -import Announcement from '~/components/Announcement/Announcement' -import { Button, Badge, IconStar, IconChevronDown, LW8CountdownBanner } from 'ui' +import { Button, Badge, IconStar, IconChevronDown } from 'ui' import FlyOut from '~/components/UI/FlyOut' import Transition from 'lib/Transition' @@ -198,9 +197,6 @@ const Nav = () => { return ( <> - - -
    Date: Tue, 12 Sep 2023 18:09:32 +0200 Subject: [PATCH 37/49] remove lw8 gradient from hp hero --- apps/www/components/Hero/Hero.tsx | 33 ++++--------------------------- 1 file changed, 4 insertions(+), 29 deletions(-) diff --git a/apps/www/components/Hero/Hero.tsx b/apps/www/components/Hero/Hero.tsx index b9541ab3222..f67ade341c9 100644 --- a/apps/www/components/Hero/Hero.tsx +++ b/apps/www/components/Hero/Hero.tsx @@ -8,7 +8,7 @@ import SectionContainer from '~/components/Layouts/SectionContainer' import HeroFrameworks from './HeroFrameworks' import styles from './hero.module.css' import Image from 'next/image' -import AnnouncementBadge from '../Announcement/Badge' +// import AnnouncementBadge from '../Announcement/Badge' const Hero = () => { const router = useRouter() @@ -19,7 +19,7 @@ const Hero = () => { return (
    - +
    @@ -30,9 +30,9 @@ const Hero = () => { ].join(' ')} >
    -
    + {/*
    -
    +
    */}

    Build in a weekend @@ -80,32 +80,7 @@ const Hero = () => {

    -
    - - - -
    -
    - -
    -
    ) } From 956f9443f8fd8747a9482dcc84887bf66622b450 Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Tue, 12 Sep 2023 18:42:00 +0200 Subject: [PATCH 38/49] update latest announcements in nav --- apps/www/data/Announcements.json | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/apps/www/data/Announcements.json b/apps/www/data/Announcements.json index 66a4bdd4d57..d41e4517028 100644 --- a/apps/www/data/Announcements.json +++ b/apps/www/data/Announcements.json @@ -1,4 +1,19 @@ [ + { + "type": "Announcement", + "title": "Supabase Beta August 2023", + "description": "Launch Week 8 review and more things we shipped 🚀", + "imgUrl": "/images/blog/2023-09-07-beta-update-august-2023/monthly-update-august-2023.jpg", + "logoUrl": "/images/blog/2023-09-07-beta-update-august-2023/monthly-update-august-2023.jpg", + "organization": "Supabase", + "url": "/blog/beta-update-august-2023", + "postMeta": { + "name": "Ant Wilson", + "publishDate": "Sep 08, 2023", + "readLength": 5 + }, + "ctaText": "Learn more" + }, { "type": "Announcement", "title": "Launch Week 8 Hackathon Winners", @@ -13,20 +28,5 @@ "readLength": 3 }, "ctaText": "See the winners" - }, - { - "type": "Announcement", - "title": "Supabase Launch Week 8", - "description": "One new feature (or more) every day during a week.", - "imgUrl": "/images/launchweek/8/lw8-og.jpg", - "logoUrl": "/images/launchweek/8/lw8-og.jpg", - "organization": "Supabase", - "url": "/launch-week", - "postMeta": { - "name": "Paul Copplestone", - "publishDate": "Aug 07, 2023", - "readLength": 5 - }, - "ctaText": "Learn more" } ] From 6383d8ab26324874eceedb13340565decee3dddb Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Tue, 12 Sep 2023 18:42:26 +0200 Subject: [PATCH 39/49] fix react svg attributes --- apps/www/components/Nav/Developers.tsx | 2 +- apps/www/pages/partners/index.tsx | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/www/components/Nav/Developers.tsx b/apps/www/components/Nav/Developers.tsx index 9fdc46c12bf..1663d190aca 100644 --- a/apps/www/components/Nav/Developers.tsx +++ b/apps/www/components/Nav/Developers.tsx @@ -32,7 +32,7 @@ const Developers = () => { stroke="currentColor" aria-hidden="true" > - + )} {Svg && } diff --git a/apps/www/pages/partners/index.tsx b/apps/www/pages/partners/index.tsx index 6c3ae25aa8d..eae38fcd495 100644 --- a/apps/www/pages/partners/index.tsx +++ b/apps/www/pages/partners/index.tsx @@ -51,8 +51,8 @@ const Partners = () => { { y2="21.5" gradientUnits="userSpaceOnUse" > - - - + + + From 40e96436bea385f2b863743e67dd336227728f39 Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Tue, 12 Sep 2023 18:53:40 +0200 Subject: [PATCH 40/49] adjust Pricing page announcement badge --- apps/www/components/Announcement/Badge.tsx | 21 +++++++--- apps/www/pages/pricing/index.tsx | 49 ++++------------------ 2 files changed, 24 insertions(+), 46 deletions(-) diff --git a/apps/www/components/Announcement/Badge.tsx b/apps/www/components/Announcement/Badge.tsx index adbc7accd87..e2a379c8747 100644 --- a/apps/www/components/Announcement/Badge.tsx +++ b/apps/www/components/Announcement/Badge.tsx @@ -3,10 +3,17 @@ import Link from 'next/link' import { Badge } from 'ui' import { ArrowNarrowRightIcon } from '@heroicons/react/outline' -const AnnouncementBadge = () => ( +interface Props { + url: string + announcement: string + badge?: string + target?: '_self' | '_blank' +} +const AnnouncementBadge = ({ url, announcement, badge, target = '_self' }: Props) => (
    - + - - Explore - - Launch Week 8 announcements + {badge && ( + + {badge} + + )} + {announcement}
    +

    Pricing

    @@ -159,45 +160,13 @@ export default function IndexPage() {

    Start building for free, collaborate with a team, then scale to millions of users.

    -
    From ffab323ded10d3306e35c79ff497f1117e101809 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Tue, 12 Sep 2023 15:30:33 -0230 Subject: [PATCH 41/49] Update studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx --- .../TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx index 42b55172d31..ddecb240dce 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx @@ -202,7 +202,7 @@ const InputField = ({ ...(field.isNullable ? [{ value: 'null', label: 'NULL' }] : []), ] - const defaultValue = field.defaultValue === null ? 'null' : field.defaultValue +const defaultValue = field.defaultValue !== null ? field.defaultValue : 'null' return ( Date: Wed, 13 Sep 2023 00:21:53 +0200 Subject: [PATCH 42/49] feat: over-usage banners for orgs --- .../BillingBreakdown/BillingBreakdown.tsx | 13 ++++- .../interfaces/Organization/Usage/Usage.tsx | 40 ++++++++++++++- .../LayoutHeader/LayoutHeader.tsx | 49 ++++++++++++++++--- .../ui/OveragesBanner/OveragesBanner.utils.ts | 17 ++++++- 4 files changed, 108 insertions(+), 11 deletions(-) diff --git a/studio/components/interfaces/Organization/BillingSettingsV2/BillingBreakdown/BillingBreakdown.tsx b/studio/components/interfaces/Organization/BillingSettingsV2/BillingBreakdown/BillingBreakdown.tsx index 44049b6780a..c806533ad1b 100644 --- a/studio/components/interfaces/Organization/BillingSettingsV2/BillingBreakdown/BillingBreakdown.tsx +++ b/studio/components/interfaces/Organization/BillingSettingsV2/BillingBreakdown/BillingBreakdown.tsx @@ -14,8 +14,10 @@ import { Alert, Button } from 'ui' import { BILLING_BREAKDOWN_METRICS } from './BillingBreakdown.constants' import BillingMetric from './BillingMetric' import UpcomingInvoice from './UpcomingInvoice' +import { useOrgSettingsPageStateSnapshot } from 'state/organization-settings' const BillingBreakdown = () => { + const snap = useOrgSettingsPageStateSnapshot() const { slug: orgSlug } = useParams() const { data: usage, @@ -93,7 +95,16 @@ const BillingBreakdown = () => { variant="danger" title="Your organization's usage has exceeded its included quota" actions={[ - , ]} diff --git a/studio/components/interfaces/Organization/Usage/Usage.tsx b/studio/components/interfaces/Organization/Usage/Usage.tsx index 66157592ef6..34942cc2873 100644 --- a/studio/components/interfaces/Organization/Usage/Usage.tsx +++ b/studio/components/interfaces/Organization/Usage/Usage.tsx @@ -11,12 +11,13 @@ import { useProjectsQuery } from 'data/projects/projects-query' import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useSelectedOrganization } from 'hooks' import { TIME_PERIODS_BILLING, TIME_PERIODS_REPORTS } from 'lib/constants' -import { Button, IconExternalLink, IconInfo, Listbox } from 'ui' +import { Alert, Button, IconExternalLink, IconInfo, Listbox } from 'ui' import Activity from './Activity' import Bandwidth from './Bandwidth' import SizeAndCounts from './SizeAndCounts' import InformationBox from 'components/ui/InformationBox' import Link from 'next/link' +import { useOrgUsageQuery } from 'data/usage/org-usage-query' const Usage = () => { const { slug, projectRef } = useParams() @@ -32,6 +33,9 @@ const Usage = () => { isError: isErrorSubscription, isSuccess: isSuccessSubscription, } = useOrgSubscriptionQuery({ orgSlug: slug }) + + const { data: usage } = useOrgUsageQuery({ orgSlug: slug }) + const orgProjects = projects?.filter((project) => project.organization_id === organization?.id) useEffect(() => { @@ -88,6 +92,13 @@ const Usage = () => { ? orgProjects?.find((it) => it.ref === selectedProjectRef) : undefined + const hasExceededAnyLimits = Boolean( + usage?.usages.find( + (metric) => + !metric.unlimited && metric.capped && metric.usage > (metric?.pricing_free_units ?? 0) + ) + ) + return ( <> @@ -156,6 +167,33 @@ const Usage = () => {
    + {!selectedProject && subscription && hasExceededAnyLimits && ( + + + + , + ]} + > + Your projects can become unresponsive or enter read only mode.{' '} + {subscription.plan.id === 'free' + ? 'Please upgrade to the Pro plan to ensure that your projects remain available.' + : 'Please disable spend cap to ensure that your projects remain available.'} + + + )} + {selectedProjectRef && ( { const selectedOrganization = useSelectedOrganization() @@ -26,26 +31,48 @@ const LayoutHeader = ({ customHeaderComponents, breadcrumbs = [], headerBorder = }) // Skip with org-based-billing, as quota is for the entire org - const { data: usage } = useProjectUsageQuery( + const { data: projectUsage } = useProjectUsageQuery( { projectRef }, - { enabled: selectedOrganization && !selectedOrganization.subscription_id } + { enabled: Boolean(selectedOrganization && !selectedOrganization.subscription_id) } ) - const resourcesExceededLimits = getResourcesExceededLimits(usage) + const { data: orgUsage } = useOrgUsageQuery( + { orgSlug: selectedOrganization?.slug }, + { enabled: Boolean(selectedOrganization && selectedOrganization.subscription_id) } + ) + + const exceedingLimits = useMemo(() => { + if (orgUsage) { + return getResourcesExceededLimitsOrg(orgUsage?.usages || []).length > 0 + } else if (projectUsage) { + return getResourcesExceededLimits(projectUsage).length > 0 + } else { + return false + } + }, [projectUsage, orgUsage]) // Skip with org-based-billing, as quota is for the entire org - const { data: subscription } = useProjectSubscriptionV2Query( + const { data: projectSubscription } = useProjectSubscriptionV2Query( { projectRef }, { enabled: selectedOrganization && !selectedOrganization.subscription_id } ) + const { data: orgSubscription } = useProjectSubscriptionV2Query( + { projectRef }, + { enabled: Boolean(selectedOrganization && selectedOrganization.subscription_id) } + ) + + const subscription = useMemo(() => { + return projectSubscription || orgSubscription + }, [projectSubscription, orgSubscription]) + const projectHasNoLimits = subscription?.usage_billing_enabled === true const showOverUsageBadge = useFlag('overusageBadge') && subscription !== undefined && - (subscription.plan.id === 'free' || subscription?.plan.id === 'pro') && + (subscription.plan.id === 'free' || subscription.plan.id === 'pro') && !projectHasNoLimits && - resourcesExceededLimits.length > 0 + exceedingLimits return (
    - + Exceeding usage limits diff --git a/studio/components/ui/OveragesBanner/OveragesBanner.utils.ts b/studio/components/ui/OveragesBanner/OveragesBanner.utils.ts index 6c66916241f..69438ce4b20 100644 --- a/studio/components/ui/OveragesBanner/OveragesBanner.utils.ts +++ b/studio/components/ui/OveragesBanner/OveragesBanner.utils.ts @@ -1,5 +1,6 @@ import { compact } from 'lodash' import { USAGE_APPROACHING_THRESHOLD } from 'lib/constants' +import { OrgUsageResponse, UsageMetric } from 'data/usage/org-usage-query' export const getResourcesApproachingLimits = (usages: any) => { if (!usages) return [] @@ -22,7 +23,21 @@ export const getResourcesExceededLimits = (usages: any) => { .filter((resourceName) => usages[resourceName] !== null) .map((resourceName) => { const resource = usages[resourceName] - if (resource.limit > 0 && (resource.usage / resource.limit) > 1) return resourceName + if (resource.limit > 0 && resource.usage / resource.limit > 1) return resourceName }) ) } + +export const getResourcesExceededLimitsOrg = (usageMetrics: UsageMetric[]): string[] => { + if (!usageMetrics.length) return [] + + return usageMetrics + .filter((usageMetric) => { + if (!usageMetric.capped || !usageMetric.available_in_plan || usageMetric.unlimited) return false + + const freeUnits = usageMetric.pricing_free_units || 0 + + return usageMetric.usage > freeUnits + }) + .map((it) => it.metric) +} From 31eb73684b0c697c9076dde9e188200ee13d5972 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Tue, 12 Sep 2023 22:10:08 -0230 Subject: [PATCH 43/49] Cleanup --- studio/components/interfaces/Home/ProjectList/ProjectList.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/studio/components/interfaces/Home/ProjectList/ProjectList.tsx b/studio/components/interfaces/Home/ProjectList/ProjectList.tsx index 1cd3c9b70b5..c49ab30c9f8 100644 --- a/studio/components/interfaces/Home/ProjectList/ProjectList.tsx +++ b/studio/components/interfaces/Home/ProjectList/ProjectList.tsx @@ -13,7 +13,6 @@ import { useOrganizationsQuery } from 'data/organizations/organizations-query' import { usePermissionsQuery } from 'data/permissions/permissions-query' import { useProjectsQuery } from 'data/projects/projects-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' import { Organization, Project, ResponseError } from 'types' From 2a1354448c86c5d18d5c9819b1fa83cb78876e26 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Tue, 12 Sep 2023 22:50:37 -0230 Subject: [PATCH 44/49] Language updates --- ...sourceExhaustionWarningBanner.constants.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts b/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts index f0e5f2730ac..5ed9d61b5f9 100644 --- a/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts +++ b/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants.ts @@ -45,11 +45,11 @@ export const RESOURCE_WARNING_MESSAGES = { cardContent: { warning: { title: 'Project is depleting its Disk IO Budget', - description: 'Project may become unresponsive if fully exhausted.', + description: 'It may become unresponsive if fully exhausted', }, critical: { title: 'Project has depleted its Disk IO Budget', - description: 'Project may become unresponsive', + description: 'It may become unresponsive', }, }, docsUrl: 'https://supabase.com/docs/guides/platform/exhaust-disk-io', @@ -73,11 +73,11 @@ export const RESOURCE_WARNING_MESSAGES = { cardContent: { warning: { title: 'Project is exhausting disk space budget', - description: 'Project may become unresponsive if fully exhausted', + description: 'It may become unresponsive if fully exhausted', }, critical: { title: 'Project has exhausted disk space budget', - description: 'Project may become unresponsive', + description: 'It may become unresponsive', }, }, docsUrl: 'https://supabase.com/docs/guides/platform/database-size#disk-management', @@ -100,11 +100,11 @@ export const RESOURCE_WARNING_MESSAGES = { cardContent: { warning: { title: 'Project has high CPU usage', - description: `Project performance is affected`, + description: `Performance is affected`, }, critical: { title: 'Project CPU usage is at 100%', - description: `Project performance is affected`, + description: `Performance is affected`, }, }, docsUrl: 'https://supabase.com/docs/guides/platform/exhaust-cpu', @@ -128,11 +128,11 @@ export const RESOURCE_WARNING_MESSAGES = { cardContent: { warning: { title: 'Project has high memory usage', - description: `Project performance is affected.`, + description: `Performance is affected`, }, critical: { title: 'Project memory usage is at 100%', - description: `Project performance is affected.`, + description: `Performance is affected`, }, }, docsUrl: 'https://supabase.com/docs/guides/platform/exhaust-ram', @@ -156,11 +156,11 @@ export const RESOURCE_WARNING_MESSAGES = { cardContent: { warning: { title: 'Project is exhausting multiple resources', - description: `Project performance is affected.`, + description: `Performance is affected.`, }, critical: { title: 'Project has exhausted multiple resources', - description: `Project performance is affected.`, + description: `Performance is affected.`, }, }, docsUrl: undefined, From 0d056b709b70019dbcbdb94828a7f4feb8621ad7 Mon Sep 17 00:00:00 2001 From: Div Arora Date: Wed, 13 Sep 2023 13:33:16 +0800 Subject: [PATCH 45/49] chore: add Rory as codeowner on pricing data --- .github/CODEOWNERS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f7975dc05c7..c3d606e9fcb 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,6 @@ +/packages/shared-data/pricing.ts @roryw10 +/packages/shared-data/plans.ts @roryw10 + /studio/ @supabase/Dashboard /apps/www/ @supabase/Website /apps/docs/ @supabase/docs From 7a6f7c7bc39628def46d783b510c9c28bc49fee2 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 13 Sep 2023 14:39:59 +0800 Subject: [PATCH 46/49] Small refactors --- .../Home/ProjectList/ProjectCard.tsx | 31 +-------- .../Home/ProjectList/ProjectCard.utils.ts | 34 +++++++++ .../Home/ProjectList/ProjectCardStatus.tsx | 69 +++++-------------- .../ResourceExhaustionWarningBanner.tsx | 23 +++---- .../ResourceExhaustionWarningBanner.utils.ts | 19 +++++ 5 files changed, 84 insertions(+), 92 deletions(-) create mode 100644 studio/components/interfaces/Home/ProjectList/ProjectCard.utils.ts create mode 100644 studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.utils.ts diff --git a/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx b/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx index d4306e2b5ea..a3251879130 100644 --- a/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx +++ b/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx @@ -1,11 +1,12 @@ import { IconGitBranch, IconGitHub } from 'ui' import CardButton from 'components/ui/CardButton' -import { BASE_PATH, PROJECT_STATUS } from 'lib/constants' +import { BASE_PATH } from 'lib/constants' import { Project } from 'types' import { IntegrationProjectConnection } from 'data/integrations/integrations.types' import { ResourceWarning } from 'data/usage/resource-warnings-query' import { ProjectCardStatus } from './ProjectCardStatus' +import { inferProjectStatus } from './ProjectCard.utils' export interface ProjectCardProps { project: Project @@ -15,32 +16,6 @@ export interface ProjectCardProps { resourceWarnings?: ResourceWarning } -const getProjetStatus = (project: Project) => { - let status - switch (project.status) { - case PROJECT_STATUS.ACTIVE_HEALTHY: - status = 'isHealthy' - break - case PROJECT_STATUS.GOING_DOWN: - case PROJECT_STATUS.PAUSING: - status = 'isPausing' - break - case PROJECT_STATUS.INACTIVE: - status = 'isPaused' - break - case PROJECT_STATUS.RESTORING: - status = 'isRestoring' - break - case PROJECT_STATUS.UNKNOWN: - case PROJECT_STATUS.COMING_UP: - status = 'isComingUp' - break - default: - status = '' - } - return status -} - const ProjectCard = ({ project, rewriteHref, @@ -55,7 +30,7 @@ const ProjectCard = ({ const isGithubIntegrated = githubIntegration !== undefined const isVercelIntegrated = vercelIntegration !== undefined const githubRepository = githubIntegration?.metadata.name ?? undefined - const projectStatus = getProjetStatus(project) + const projectStatus = inferProjectStatus(project) return (
  • diff --git a/studio/components/interfaces/Home/ProjectList/ProjectCard.utils.ts b/studio/components/interfaces/Home/ProjectList/ProjectCard.utils.ts new file mode 100644 index 00000000000..ea1ab6b0872 --- /dev/null +++ b/studio/components/interfaces/Home/ProjectList/ProjectCard.utils.ts @@ -0,0 +1,34 @@ +import { PROJECT_STATUS } from 'lib/constants' +import { Project } from 'types' + +export const inferProjectStatus = (project: Project) => { + let status = undefined + switch (project.status) { + case PROJECT_STATUS.ACTIVE_HEALTHY: + status = 'isHealthy' + break + case PROJECT_STATUS.GOING_DOWN: + case PROJECT_STATUS.PAUSING: + status = 'isPausing' + break + case PROJECT_STATUS.INACTIVE: + status = 'isPaused' + break + case PROJECT_STATUS.RESTORING: + status = 'isRestoring' + break + case PROJECT_STATUS.UNKNOWN: + case PROJECT_STATUS.COMING_UP: + status = 'isComingUp' + break + } + return status as InferredProjectStatus +} + +export type InferredProjectStatus = + | 'isHealthy' + | 'isPausing' + | 'isPaused' + | 'isRestoring' + | 'isComingUp' + | undefined diff --git a/studio/components/interfaces/Home/ProjectList/ProjectCardStatus.tsx b/studio/components/interfaces/Home/ProjectList/ProjectCardStatus.tsx index e04a27d4bcb..5a0ec87c02e 100644 --- a/studio/components/interfaces/Home/ProjectList/ProjectCardStatus.tsx +++ b/studio/components/interfaces/Home/ProjectList/ProjectCardStatus.tsx @@ -8,10 +8,12 @@ import { IconLoader, IconPauseCircle, } from 'ui' +import { InferredProjectStatus } from './ProjectCard.utils' +import { getWarningContent } from 'components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.utils' export interface ProjectCardWarningsProps { resourceWarnings: ResourceWarning - projectStatus: string + projectStatus: InferredProjectStatus } export const ProjectCardStatus = ({ @@ -31,54 +33,11 @@ export const ProjectCardStatus = ({ resourceWarnings[property as keyof typeof resourceWarnings] !== null ) - const getContent = (metric: string) => { - if (metric === 'is_readonly_mode_enabled') { - return RESOURCE_WARNING_MESSAGES.is_readonly_mode_enabled.cardContent.warning - } - - const severity = resourceWarnings[metric as keyof typeof resourceWarnings] - - if (typeof severity !== 'string') return undefined - - return RESOURCE_WARNING_MESSAGES[metric as keyof typeof RESOURCE_WARNING_MESSAGES][ - 'cardContent' - ][severity as 'warning' | 'critical'] - } - const hasCriticalWarning = activeWarnings.some( (x) => resourceWarnings[x as keyof typeof resourceWarnings] === 'critical' ) - const isCritical = activeWarnings.includes('is_readonly_mode_enabled') || hasCriticalWarning - const getWarningType = () => { - let type = 'default' - - switch (true) { - case projectStatus === 'isPaused': - type = 'default' - break - case isCritical: - type = 'destructive' - break - default: - type = 'warning' - break - } - - return type as 'warning' | 'destructive' | 'default' - } - - const AlertIcon = () => { - switch (true) { - case projectStatus === 'isPaused' || projectStatus === 'isPausing': - return - case projectStatus === 'isRestoring' || projectStatus === 'isComingUp': - return - default: - return - } - } const getTitle = () => { if (projectStatus === 'isPaused') return 'Project is paused' if (projectStatus === 'isPausing') return 'Project is pausing' @@ -90,7 +49,7 @@ export const ProjectCardStatus = ({ ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.cardContent[ hasCriticalWarning ? 'critical' : 'warning' ].title - : getContent(activeWarnings[0])?.title + : getWarningContent(resourceWarnings, activeWarnings[0], 'cardContent')?.title } const getDescription = () => { @@ -108,12 +67,16 @@ export const ProjectCardStatus = ({ ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.cardContent[ hasCriticalWarning ? 'critical' : 'warning' ].description - : getContent(activeWarnings[0])?.description + : getWarningContent(resourceWarnings, activeWarnings[0], 'cardContent')?.description } const alertTitle = getTitle() const alertDescription = getDescription() - const alertType = getWarningType() + const alertType = isCritical + ? 'destructive' + : projectStatus === 'isPaused' + ? 'default' + : 'warning' if (activeWarnings.length === 0 && projectStatus === 'isHealthy') return
    @@ -121,11 +84,15 @@ export const ProjectCardStatus = ({
    - - + {projectStatus === 'isPaused' || projectStatus === 'isPausing' ? ( + + ) : projectStatus === 'isRestoring' || projectStatus === 'isComingUp' ? ( + + ) : ( + + )} {alertTitle} {alertDescription} diff --git a/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.tsx b/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.tsx index f0fcd6320ee..37df8e4bb7a 100644 --- a/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.tsx +++ b/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.tsx @@ -13,6 +13,7 @@ import { import { useResourceWarningsQuery } from 'data/usage/resource-warnings-query' import { useSelectedOrganization } from 'hooks' import { RESOURCE_WARNING_MESSAGES } from './ResourceExhaustionWarningBanner.constants' +import { getWarningContent } from './ResourceExhaustionWarningBanner.utils' const ResourceExhaustionWarningBanner = () => { const { ref } = useParams() @@ -46,34 +47,30 @@ const ResourceExhaustionWarningBanner = () => { : false const isCritical = activeWarnings.includes('is_readonly_mode_enabled') || hasCriticalWarning - const getContent = (metric: string) => { - if (projectResourceWarnings === undefined) return undefined - if (metric === 'is_readonly_mode_enabled') { - return RESOURCE_WARNING_MESSAGES.is_readonly_mode_enabled.bannerContent.warning - } - const severity = projectResourceWarnings[metric as keyof typeof projectResourceWarnings] - if (typeof severity !== 'string') return undefined - return RESOURCE_WARNING_MESSAGES[metric as keyof typeof RESOURCE_WARNING_MESSAGES] - .bannerContent[severity as 'warning' | 'critical'] - } - const title = activeWarnings.length > 1 ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.bannerContent[ hasCriticalWarning ? 'critical' : 'warning' ].title - : getContent(activeWarnings[0])?.title + : projectResourceWarnings !== undefined + ? getWarningContent(projectResourceWarnings, activeWarnings[0], 'bannerContent')?.title + : null + const description = activeWarnings.length > 1 ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.bannerContent[ hasCriticalWarning ? 'critical' : 'warning' ].description - : getContent(activeWarnings[0])?.description + : projectResourceWarnings !== undefined + ? getWarningContent(projectResourceWarnings, activeWarnings[0], 'bannerContent')?.description + : null + const learnMoreUrl = activeWarnings.length > 1 ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.docsUrl : RESOURCE_WARNING_MESSAGES[activeWarnings[0] as keyof typeof RESOURCE_WARNING_MESSAGES] ?.docsUrl + const metric = activeWarnings.length > 1 ? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.metric diff --git a/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.utils.ts b/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.utils.ts new file mode 100644 index 00000000000..0b3dd8b77e7 --- /dev/null +++ b/studio/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.utils.ts @@ -0,0 +1,19 @@ +import { ResourceWarning } from 'data/usage/resource-warnings-query' +import { RESOURCE_WARNING_MESSAGES } from './ResourceExhaustionWarningBanner.constants' + +export const getWarningContent = ( + resourceWarnings: ResourceWarning, + metric: string, + contentType: 'cardContent' | 'bannerContent' +) => { + if (metric === 'is_readonly_mode_enabled') { + return RESOURCE_WARNING_MESSAGES.is_readonly_mode_enabled.cardContent.warning + } + + const severity = resourceWarnings[metric as keyof typeof resourceWarnings] + if (typeof severity !== 'string') return undefined + + return RESOURCE_WARNING_MESSAGES[metric as keyof typeof RESOURCE_WARNING_MESSAGES][contentType][ + severity as 'warning' | 'critical' + ] +} From 644e4f6a9b9934f386d27ee5f0555feaa69051fa Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 13 Sep 2023 15:10:13 +0800 Subject: [PATCH 47/49] More small fixes --- .../Home/ProjectList/ProjectCard.tsx | 6 +++--- .../Home/ProjectList/ProjectCardStatus.tsx | 19 ++++++++----------- .../Home/ProjectList/ProjectList.tsx | 2 +- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx b/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx index a3251879130..a97bc84152e 100644 --- a/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx +++ b/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx @@ -36,11 +36,11 @@ const ProjectCard = ({
  • -

    {name}

    - {desc} +

    {name}

    + {desc}
    {isVercelIntegrated && (
    diff --git a/studio/components/interfaces/Home/ProjectList/ProjectCardStatus.tsx b/studio/components/interfaces/Home/ProjectList/ProjectCardStatus.tsx index 5a0ec87c02e..bc889b3c721 100644 --- a/studio/components/interfaces/Home/ProjectList/ProjectCardStatus.tsx +++ b/studio/components/interfaces/Home/ProjectList/ProjectCardStatus.tsx @@ -7,6 +7,7 @@ import { IconAlertTriangle, IconLoader, IconPauseCircle, + IconRefreshCw, } from 'ui' import { InferredProjectStatus } from './ProjectCard.utils' import { getWarningContent } from 'components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.utils' @@ -53,14 +54,10 @@ export const ProjectCardStatus = ({ } const getDescription = () => { - if (projectStatus === 'isPaused') - return 'This project will not accept any requests until unpaused.' - if (projectStatus === 'isPausing') - return 'This project is entering a paused state. This may take a few minutes.' - if (projectStatus === 'isComingUp') - return 'This project is coming up. This may take a few minutes.' - if (projectStatus === 'isRestoring') - return 'This project is restoring. This may take a few minutes.' + if (projectStatus === 'isPaused') return 'This project will not accept requests until resumed' + if (projectStatus === 'isPausing') return 'The pause process will complete in a few minutes' + if (projectStatus === 'isComingUp') return 'Your project will be ready in a few minutes' + if (projectStatus === 'isRestoring') return 'Your project will be ready in a few minutes' // If none of the paused/restoring states match, proceed with the default logic return activeWarnings.length > 1 && showResourceExhaustionWarnings @@ -84,16 +81,16 @@ export const ProjectCardStatus = ({
    {projectStatus === 'isPaused' || projectStatus === 'isPausing' ? ( ) : projectStatus === 'isRestoring' || projectStatus === 'isComingUp' ? ( - + ) : ( )} - {alertTitle} + {alertTitle} {alertDescription}
    diff --git a/studio/components/interfaces/Home/ProjectList/ProjectList.tsx b/studio/components/interfaces/Home/ProjectList/ProjectList.tsx index 10d6bb07890..ab938e7de38 100644 --- a/studio/components/interfaces/Home/ProjectList/ProjectList.tsx +++ b/studio/components/interfaces/Home/ProjectList/ProjectList.tsx @@ -86,7 +86,7 @@ type OrganizationProjectsProps = { } const OrganizationProjects = ({ - organization: { id, name, slug, subscription_id }, + organization: { name, slug, subscription_id }, projects, overdueInvoices, resourceWarnings, From 167edfdc6e5fa1de8a5595d1316617f35b374ca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Wed, 13 Sep 2023 09:56:50 +0200 Subject: [PATCH 48/49] Update CODEOWNERS --- .github/CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c3d606e9fcb..ad2cd504012 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,5 +1,5 @@ -/packages/shared-data/pricing.ts @roryw10 -/packages/shared-data/plans.ts @roryw10 +/packages/shared-data/pricing.ts @roryw10 @kevcodez +/packages/shared-data/plans.ts @roryw10 @kevcodez /studio/ @supabase/Dashboard /apps/www/ @supabase/Website From 3ee84aea034be5500131f35cdb3d3020e2980b23 Mon Sep 17 00:00:00 2001 From: Stojan Dimitrovski Date: Wed, 13 Sep 2023 13:28:33 +0200 Subject: [PATCH 49/49] feat: adds a persisted auth debug log if `supabase.dashboard.auth.debug.persisted` is `true` --- package-lock.json | 16 +++---- packages/common/gotrue.ts | 84 +++++++++++++++++++++++++++++++++++- packages/common/package.json | 2 +- 3 files changed, 91 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 288adec0c22..54e70da1f98 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14121,17 +14121,17 @@ } }, "node_modules/@supabase/gotrue-js": { - "version": "2.48.0", - "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.48.0.tgz", - "integrity": "sha512-+6g5aFL6+iaZC6mFVpRLauSkU5MrQ52QZA1TlY0/evvz9Rmhqc315tIeUfUK87ZLeX7sOL6XYVGkI6ZxgeyAUA==", + "version": "2.52.0", + "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.52.0.tgz", + "integrity": "sha512-UFCbydMYFn/LhPW08aeZ9sgFDA3kOQCjA2ieFj/sccye9m8Tv0pimMfnXh3A9TqBJ0/0utpkaGSad3XdpJ+Mbw==", "dependencies": { - "@supabase/node-fetch": "^2.6.13" + "@supabase/node-fetch": "^2.6.14" } }, "node_modules/@supabase/node-fetch": { - "version": "2.6.13", - "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.13.tgz", - "integrity": "sha512-rEHQaDVzxLZMCK3p+JW2nzEsK4AJpOQhetppaqAzrFum0Ub8wcnoM/8f1dWRZSulY5fRDP6rJaWT/8X3VleCzg==", + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.14.tgz", + "integrity": "sha512-w/Tsd22e/5fAeoxqQ4P2MX6EyF+iM6rc9kmlMVFkHuG0rAltt2TLhFbDJfemnHbtvnazWaRfy5KnFU/SYT37dQ==", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -40754,7 +40754,7 @@ "license": "MIT", "dependencies": { "@headlessui/react": "^1.7.14", - "@supabase/gotrue-js": "^2.48.0", + "@supabase/gotrue-js": "^2.52.0", "@supabase/ui": "^0.37.0-alpha.50", "@types/react": "^17.0.39", "react-use": "^17.4.0" diff --git a/packages/common/gotrue.ts b/packages/common/gotrue.ts index 22b0b68aeb8..11507565ad1 100644 --- a/packages/common/gotrue.ts +++ b/packages/common/gotrue.ts @@ -3,13 +3,20 @@ import { GoTrueClient, navigatorLock } from '@supabase/gotrue-js' export const STORAGE_KEY = process.env.NEXT_PUBLIC_STORAGE_KEY || 'supabase.dashboard.auth.token' export const AUTH_DEBUG_KEY = process.env.NEXT_PUBLIC_AUTH_DEBUG_KEY || 'supabase.dashboard.auth.debug' +export const AUTH_DEBUG_PERSISTED_KEY = + process.env.NEXT_PUBLIC_AUTH_DEBUG_PERSISTED_KEY || 'supabase.dashboard.auth.debug.persist' export const AUTH_NAVIGATOR_LOCK_DISABLED_KEY = - process.env.NEXT_PUBLIC_AUTH_NAVIGATOR_LOCK_KEY || 'supabase.dashboard.auth.navigatorLock.disabled' + process.env.NEXT_PUBLIC_AUTH_NAVIGATOR_LOCK_KEY || + 'supabase.dashboard.auth.navigatorLock.disabled' const debug = process.env.NEXT_PUBLIC_IS_PLATFORM === 'true' && globalThis?.localStorage?.getItem(AUTH_DEBUG_KEY) === 'true' +const persistedDebug = + process.env.NEXT_PUBLIC_IS_PLATFORM === 'true' && + globalThis?.localStorage?.getItem(AUTH_DEBUG_PERSISTED_KEY) === 'true' + const navigatorLockEnabled = !!( process.env.NEXT_PUBLIC_IS_PLATFORM === 'true' && !(globalThis?.localStorage?.getItem(AUTH_NAVIGATOR_LOCK_DISABLED_KEY) === 'true') && @@ -20,10 +27,83 @@ if (!globalThis?.navigator?.locks) { console.warn('This browser does not support the Navigator Locks API. Please update it.') } +const tabId = Math.random().toString(16).substring(2) + +let dbHandle = new Promise((accept, _) => { + if (!persistedDebug) { + accept(null) + return + } + + const request = indexedDB.open('auth-debug-log', 1) + + request.onupgradeneeded = (event: any) => { + const db = event?.target?.result + + if (!db) { + return + } + + db.createObjectStore('events', { autoIncrement: true }) + } + + request.onsuccess = (event: any) => { + console.log('Opened persisted auth debug log IndexedDB database', tabId) + accept(event.target.result) + } + + request.onerror = (event: any) => { + console.error('Failed to open persisted auth debug log IndexedDB database', event) + accept(null) + } +}) + +const logIndexedDB = (message: string, ...args: any[]) => { + console.log(message, ...args) + + const copyArgs = structuredClone(args) + + copyArgs.forEach((value) => { + if (typeof value === 'object' && value !== null) { + delete value.user + delete value.access_token + delete value.token_type + delete value.provider_token + } + }) + ;(async () => { + try { + const db = await dbHandle + + if (!db) { + return + } + + const tx = db.transaction(['events'], 'readwrite') + tx.onerror = (event: any) => { + console.error('Failed to write to persisted auth debug log IndexedDB database', event) + dbHandle = Promise.resolve(null) + } + + const events = tx.objectStore('events') + + events.add({ + m: message.replace(/^GoTrueClient@/i, ''), + a: copyArgs, + l: window.location.pathname, + t: tabId, + }) + } catch (e: any) { + console.error('Failed to log to persisted auth debug log IndexedDB database', e) + dbHandle = Promise.resolve(null) + } + })() +} + export const gotrueClient = new GoTrueClient({ url: process.env.NEXT_PUBLIC_GOTRUE_URL, storageKey: STORAGE_KEY, detectSessionInUrl: true, - debug, + debug: debug ? (persistedDebug ? logIndexedDB : true) : false, lock: navigatorLockEnabled ? navigatorLock : undefined, }) diff --git a/packages/common/package.json b/packages/common/package.json index b31b97019b8..e425cf31945 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -6,7 +6,7 @@ "license": "MIT", "dependencies": { "@headlessui/react": "^1.7.14", - "@supabase/gotrue-js": "^2.48.0", + "@supabase/gotrue-js": "^2.52.0", "@supabase/ui": "^0.37.0-alpha.50", "@types/react": "^17.0.39", "react-use": "^17.4.0"