From b3a8f922fbcbda8f2d006e25b58b6c3032e9e3b2 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Fri, 18 Aug 2023 17:00:50 -0230 Subject: [PATCH 001/105] 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 35a13a8c7aa61aaf7c0d7ac24bb5f2a268913845 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Wed, 23 Aug 2023 03:16:10 +0800 Subject: [PATCH 002/105] fix: update vector to set postgres logs to LOG instead of INFO --- docker/volumes/logs/vector.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker/volumes/logs/vector.yml b/docker/volumes/logs/vector.yml index 52394f8fc0f..3b664deb6d2 100644 --- a/docker/volumes/logs/vector.yml +++ b/docker/volumes/logs/vector.yml @@ -135,6 +135,9 @@ transforms: .metadata.host = "db-default" .metadata.parsed.timestamp = .timestamp .metadata.parsed.error_severity = replace!(.severity, r'^err$', "warning") + if .metadata.parsed.error_severity == "info" { + .metadata.parsed.error_severity = "log" + } sinks: logflare_auth: From bf7cc8f66be4a02fe5ab3ecf1a1822cd75840c56 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Wed, 23 Aug 2023 03:25:21 +0800 Subject: [PATCH 003/105] fix: add upcase to log level --- docker/volumes/logs/vector.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/volumes/logs/vector.yml b/docker/volumes/logs/vector.yml index 3b664deb6d2..2b925a56c05 100644 --- a/docker/volumes/logs/vector.yml +++ b/docker/volumes/logs/vector.yml @@ -138,6 +138,7 @@ transforms: if .metadata.parsed.error_severity == "info" { .metadata.parsed.error_severity = "log" } + .metadata.parsed.error_severity = upcase(.metadata.parsed.error_severity) sinks: logflare_auth: From 17d4445ce70cd2a4870d12fa1b88ca90de707fc5 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 23 Aug 2023 15:54:14 +0800 Subject: [PATCH 004/105] 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 005/105] 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 006/105] 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 007/105] 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 008/105] 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 009/105] 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 010/105] 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 011/105] 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 012/105] 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 013/105] 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 014/105] 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 015/105] 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 9705aea5a662a8cb7f2e718d8bcfc1ac695bb7c4 Mon Sep 17 00:00:00 2001 From: Ivan Vasilov Date: Fri, 1 Sep 2023 13:14:22 +0200 Subject: [PATCH 016/105] Add a warning and disable save when the column types don't match during foreign column selection. --- .../ForeignKeySelector/ForeignKeySelector.tsx | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx index 9a97b7eba14..d019b7e4dc0 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx @@ -2,7 +2,17 @@ import type { PostgresColumn, PostgresSchema, PostgresTable } from '@supabase/po import { Dictionary } from 'components/grid' import { find, get, isEmpty, sortBy } from 'lodash' import { useEffect, useState } from 'react' -import { IconDatabase, IconHelpCircle, Input, Listbox, SidePanel } from 'ui' +import { + AlertDescription_Shadcn_, + AlertTitle_Shadcn_, + Alert_Shadcn_, + IconAlertTriangle, + IconDatabase, + IconHelpCircle, + Input, + Listbox, + SidePanel, +} from 'ui' import InformationBox from 'components/ui/InformationBox' import { FOREIGN_KEY_DELETION_ACTION } from 'data/database/database-query-constants' @@ -153,6 +163,8 @@ const ForeignKeySelector = ({ resolve() } + const matchingColumnTypes = selectedColumn?.format === column?.format + return ( )} + {!matchingColumnTypes && ( + + + The column types don't match + + The referenced column {column.name} is of + type {column.format} while the selected + foreign column + + {selectedTable?.name}.{selectedColumn?.name} + + has{selectedColumn?.data_type}type. These two + columns can't be referenced until one of them change its type. + + + )} } From d4885df7f317b4716945a1b6b22b2923f20d7bd9 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Fri, 1 Sep 2023 11:16:35 -0230 Subject: [PATCH 017/105] 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 d5b1f219a935a0dbd02e41b731fa3f3178f00981 Mon Sep 17 00:00:00 2001 From: Ivan Vasilov Date: Mon, 4 Sep 2023 17:03:35 +0200 Subject: [PATCH 018/105] Handle empty column names and types. --- .../ForeignKeySelector/ForeignKeySelector.tsx | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx index d019b7e4dc0..35769943d49 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx @@ -304,14 +304,23 @@ const ForeignKeySelector = ({ The column types don't match - The referenced column {column.name} is of - type {column.format} while the selected - foreign column + The referenced column + {column?.name && {column.name}} + {column?.format ? ( + <> + is of type + {column.format} + + ) : ( + has no type + )} + while the selected foreign column {selectedTable?.name}.{selectedColumn?.name} - has{selectedColumn?.data_type}type. These two - columns can't be referenced until one of them change its type. + has + {selectedColumn?.data_type}type. These two + columns can't be referenced until they of the same type. )} From 1068773733ee107c2d1b034a3d75afceb6554b68 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Wed, 6 Sep 2023 17:08:27 -0230 Subject: [PATCH 019/105] 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 020/105] 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 021/105] 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 24820c5f3fc0128cc5495f4b65cfa7ea226b34d7 Mon Sep 17 00:00:00 2001 From: Ivan Vasilov Date: Tue, 29 Aug 2023 15:59:58 +0200 Subject: [PATCH 022/105] Add profile audit logs page. --- .../Account/AuditLogs/AuditLogs.tsx | 343 ++++++++++++++++++ .../Account/AuditLogs/FilterPopover.tsx | 100 +++++ .../Account/AuditLogs/LogDetailsPanel.tsx | 97 +++++ studio/components/interfaces/Account/index.ts | 3 +- .../NavigationBar/NavigationBar.tsx | 9 + studio/data/profile/keys.ts | 7 + .../data/profile/profile-audit-logs-query.ts | 48 +++ studio/pages/account/audit.tsx | 27 ++ 8 files changed, 633 insertions(+), 1 deletion(-) create mode 100644 studio/components/interfaces/Account/AuditLogs/AuditLogs.tsx create mode 100644 studio/components/interfaces/Account/AuditLogs/FilterPopover.tsx create mode 100644 studio/components/interfaces/Account/AuditLogs/LogDetailsPanel.tsx create mode 100644 studio/data/profile/profile-audit-logs-query.ts create mode 100644 studio/pages/account/audit.tsx diff --git a/studio/components/interfaces/Account/AuditLogs/AuditLogs.tsx b/studio/components/interfaces/Account/AuditLogs/AuditLogs.tsx new file mode 100644 index 00000000000..58875a395ce --- /dev/null +++ b/studio/components/interfaces/Account/AuditLogs/AuditLogs.tsx @@ -0,0 +1,343 @@ +import * as Tooltip from '@radix-ui/react-tooltip' +import dayjs from 'dayjs' +import Image from 'next/image' +import { useState } from 'react' + +import { useParams } from 'common' +import { ScaffoldContainerLegacy } from 'components/layouts/Scaffold' +import Table from 'components/to-be-cleaned/Table' +import AlertError from 'components/ui/AlertError' +import { DatePicker } from 'components/ui/DatePicker' +import ShimmeringLoader from 'components/ui/ShimmeringLoader' +import { OrganizationAuditLog } from 'data/organizations/organization-audit-logs-query' +import { useOrganizationDetailQuery } from 'data/organizations/organization-detail-query' +import { useOrganizationRolesQuery } from 'data/organizations/organization-roles-query' +import { useOrganizationsQuery } from 'data/organizations/organizations-query' +import { useProfileAuditLogsQuery } from 'data/profile/profile-audit-logs-query' +import { useProjectsQuery } from 'data/projects/projects-query' +import { Alert, Button, IconArrowDown, IconArrowUp, IconRefreshCw, IconUser } from 'ui' +import FilterPopover from './FilterPopover' +import LogDetailsPanel from './LogDetailsPanel' + +// [Joshen considerations] +// - Maybe fix the height of the table to the remaining height of the viewport, so that the search input is always visible +// - We'll need pagination as well if the audit logs get too large, but that needs to be implemented on the API side first if possible +// - I've hidden time input in the date picker for now cause the time support in the component is a bit iffy, need to investigate +// - Maybe a rule to follow from here is just everytime we call dayjs, use UTC(), one TZ to rule them all + +const AuditLogs = () => { + const { slug } = useParams() + const currentTime = dayjs().utc().set('millisecond', 0) + const [dateSortDesc, setDateSortDesc] = useState(true) + const [dateRange, setDateRange] = useState({ + from: currentTime.subtract(1, 'day').toISOString(), + to: currentTime.toISOString(), + }) + const [selectedLog, setSelectedLog] = useState() + const [filters, setFilters] = useState<{ users: string[]; projects: string[] }>({ + users: [], // gotrue_id[] + projects: [], // project_ref[] + }) + + const { data: projects } = useProjectsQuery() + const { data: organizations } = useOrganizationsQuery() + const { data: detailData } = useOrganizationDetailQuery({ slug }) + const { data: rolesData } = useOrganizationRolesQuery({ slug }) + const { data, error, isLoading, isSuccess, isError, isRefetching, refetch } = + useProfileAuditLogsQuery({ + iso_timestamp_start: dateRange.from, + iso_timestamp_end: dateRange.to, + }) + + const members = detailData?.members ?? [] + const roles = rolesData?.roles ?? [] + + const retentionPeriod = data?.retention_period ?? 0 + const logs = data?.result ?? [] + const sortedLogs = logs + ?.sort((a, b) => + dateSortDesc + ? Number(new Date(b.occurred_at)) - Number(new Date(a.occurred_at)) + : Number(new Date(a.occurred_at)) - Number(new Date(b.occurred_at)) + ) + ?.filter((log) => { + if (filters.users.length > 0) { + return filters.users.includes(log.actor.id) + } else { + return log + } + }) + ?.filter((log) => { + if (filters.projects.length > 0) { + return filters.projects.includes(log.target.metadata.project_ref || '') + } else { + return log + } + }) + + return ( + <> + +
    +
    +
    +

    Filter by

    + setFilters({ ...filters, users: values })} + /> + setFilters({ ...filters, projects: values })} + /> + { + if (value.from !== null && value.to !== null) { + const current = dayjs().utc() + const from = dayjs(value.from) + .utc() + .hour(current.hour()) + .minute(current.minute()) + .second(current.second()) + .toISOString() + const to = dayjs(value.to) + .utc() + .hour(current.hour()) + .minute(current.minute()) + .second(current.second()) + .toISOString() + setDateRange({ from, to }) + } + }} + renderFooter={() => { + return ( + + Your organization has a log retention period of{' '} + + {retentionPeriod} day + {retentionPeriod > 1 ? 's' : ''} + + . You may only view logs from{' '} + {dayjs().subtract(retentionPeriod, 'days').format('DD MMM YYYY')} as the + earliest date. + + ) + }} + /> + {isSuccess && ( + <> +
    +

    Viewing {sortedLogs.length} logs in total

    + + )} +
    + +
    + + {isLoading && ( +
    + + + +
    + )} + + {isError && } + + {isSuccess && ( + <> + {logs.length === 0 ? ( +
    +

    + Your organization does not have any audit logs available yet +

    +
    + ) : logs.length > 0 && sortedLogs.length === 0 ? ( +
    +

    No audit logs found based on the filters applied

    +
    + ) : ( + + User + , + + Action + , + + Target + , + +
    +

    Date

    + + + +
    +
    , + , + ]} + body={ + sortedLogs?.map((log) => { + const user = members.find((member) => member.gotrue_id === log.actor.id) + const role = roles.find((role) => user?.role_ids?.[0] === role.id) + const project = projects?.find( + (project) => project.ref === log.target.metadata.project_ref + ) + const organization = organizations?.find( + (org) => org.slug === log.target.metadata.org_slug + ) + + const hasStatusCode = log.action.metadata[0]?.status !== undefined + const userIcon = + user === undefined ? ( +
    +

    ?

    +
    + ) : user?.invited_id || user?.username === user?.primary_email ? ( +
    + +
    + ) : ( + + ) + + return ( + setSelectedLog(log)} + className="cursor-pointer hover:!bg-scale-100 transition duration-100" + > + +
    +
    {userIcon}
    +
    +

    {user?.username ?? 'Unknown'}

    + {role && ( +

    {role?.name}

    + )} +
    +
    +
    + +
    + {hasStatusCode && ( +

    + {log.action.metadata[0].status} +

    + )} +

    + {log.action.name} +

    +
    +
    + +

    + {project?.name + ? 'Project: ' + : organization?.name + ? 'Organization: ' + : null} + {project?.name ?? organization?.name ?? 'Unknown'} +

    +

    + {log.target.metadata.project_ref + ? 'Ref: ' + : log.target.metadata.org_slug + ? 'Slug: ' + : null} + {log.target.metadata.project_ref ?? log.target.metadata.org_slug} +

    +
    + + {dayjs(log.occurred_at).format('DD MMM YYYY, HH:mm:ss')} + + + + +
    + ) + }) ?? [] + } + /> + )} + + )} + + + + setSelectedLog(undefined)} /> + + ) +} + +export default AuditLogs diff --git a/studio/components/interfaces/Account/AuditLogs/FilterPopover.tsx b/studio/components/interfaces/Account/AuditLogs/FilterPopover.tsx new file mode 100644 index 00000000000..c38c96736e3 --- /dev/null +++ b/studio/components/interfaces/Account/AuditLogs/FilterPopover.tsx @@ -0,0 +1,100 @@ +import { useState } from 'react' +import { Button, Checkbox, Popover } from 'ui' + +interface FilterPopoverProps { + options: any[] + activeOptions: any[] + valueKey: string + labelKey: string + name: string + onSaveFilters: (options: string[]) => void +} + +// [Joshen] Form + Checkbox.Group doesn't seem to work properly RE form state +// hence why the manual implementation here as a workaround + +const FilterPopover = ({ + options = [], + activeOptions = [], + valueKey, + labelKey, + name, + onSaveFilters, +}: FilterPopoverProps) => { + const [open, setOpen] = useState(false) + const [selectedOptions, setSelectedOptions] = useState([]) + + const formattedOptions = activeOptions.map((option) => { + const base = options.find((x) => x[valueKey] === option) + return base[labelKey] + }) + + return ( + setOpen(!open)} + header={
    Select {name.toLowerCase()}
    } + overlay={ + <> +
    + { + const value = event.target.value + if (selectedOptions.includes(value)) { + setSelectedOptions(selectedOptions.filter((x) => x !== value)) + } else { + setSelectedOptions(selectedOptions.concat(value)) + } + }} + options={options.map((option) => { + return { + value: option[valueKey], + label: option[labelKey], + defaultChecked: activeOptions.includes(option[valueKey]), + } + })} + > +
    +
    + + +
    + + } + > + +
    + ) +} + +export default FilterPopover diff --git a/studio/components/interfaces/Account/AuditLogs/LogDetailsPanel.tsx b/studio/components/interfaces/Account/AuditLogs/LogDetailsPanel.tsx new file mode 100644 index 00000000000..0f374319b15 --- /dev/null +++ b/studio/components/interfaces/Account/AuditLogs/LogDetailsPanel.tsx @@ -0,0 +1,97 @@ +import { FormSection, FormSectionContent, FormSectionLabel } from 'components/ui/Forms' +import { OrganizationAuditLog } from 'data/organizations/organization-audit-logs-query' +import { useOrganizationsQuery } from 'data/organizations/organizations-query' +import { useProjectsQuery } from 'data/projects/projects-query' +import dayjs from 'dayjs' +import { Input, SidePanel } from 'ui' + +export interface LogDetailsPanelProps { + selectedLog?: OrganizationAuditLog + onClose: () => void +} + +const LogDetailsPanel = ({ selectedLog, onClose }: LogDetailsPanelProps) => { + const { data: projects } = useProjectsQuery() + const { data: organizations } = useOrganizationsQuery() + + const project = projects?.find( + (project) => project.ref === selectedLog?.target.metadata.project_ref + ) + const organization = organizations?.find( + (org) => org.slug === selectedLog?.target.metadata.org_slug + ) + + return ( + + General}> + + + + + + + + Actor}> + + + + + + + + + Action}> + + + + + + + + + Target}> + + + + + + + ) +} + +export default LogDetailsPanel diff --git a/studio/components/interfaces/Account/index.ts b/studio/components/interfaces/Account/index.ts index 4630ecbb9d8..3c653490437 100644 --- a/studio/components/interfaces/Account/index.ts +++ b/studio/components/interfaces/Account/index.ts @@ -1,6 +1,7 @@ import AccessTokenList from './AccessTokenList' +import AuditLogs from './AuditLogs/AuditLogs' import NewAccessTokenButton from './NewAccessTokenButton' import NewTokenBanner from './NewTokenBanner' import TOTPFactors from './TOTPFactors' -export { AccessTokenList, NewTokenBanner, NewAccessTokenButton, TOTPFactors } +export { AccessTokenList, NewTokenBanner, NewAccessTokenButton, TOTPFactors, AuditLogs } diff --git a/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.tsx b/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.tsx index 614154776f3..52c1554eab0 100644 --- a/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.tsx +++ b/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.tsx @@ -10,6 +10,7 @@ import { detectOS } from 'lib/helpers' import { Button, Dropdown, + IconBookOpen, IconCommand, IconHome, IconSearch, @@ -146,6 +147,14 @@ const NavigationBar = () => { Account Preferences + + } + > + Audit Logs + + )} diff --git a/studio/data/profile/keys.ts b/studio/data/profile/keys.ts index 2ed0d876185..bd25d359282 100644 --- a/studio/data/profile/keys.ts +++ b/studio/data/profile/keys.ts @@ -2,4 +2,11 @@ export const profileKeys = { profile: () => ['profile'] as const, mfaFactors: () => ['mfa', 'factors'] as const, aaLevel: () => ['mfa', 'aaLevel'] as const, + auditLogs: ({ + date_start, + date_end, + }: { + date_start: string | undefined + date_end: string | undefined + }) => ['profile', 'audit-logs', { date_start, date_end }] as const, } diff --git a/studio/data/profile/profile-audit-logs-query.ts b/studio/data/profile/profile-audit-logs-query.ts new file mode 100644 index 00000000000..9e7b3ae6101 --- /dev/null +++ b/studio/data/profile/profile-audit-logs-query.ts @@ -0,0 +1,48 @@ +import { useQuery, UseQueryOptions } from '@tanstack/react-query' +import { get } from 'data/fetchers' +import dayjs from 'dayjs' +import { ResponseError } from 'types' +import { profileKeys } from './keys' + +export type ProfileAuditLogsVariables = { + iso_timestamp_start?: string + iso_timestamp_end?: string +} + +export async function getProfileAuditLogs( + { iso_timestamp_start, iso_timestamp_end }: ProfileAuditLogsVariables, + signal?: AbortSignal +) { + const { data, error } = await get('/platform/profile/audit', { + params: { + query: { + iso_timestamp_start, + iso_timestamp_end, + }, + }, + signal, + }) + + if (error) throw error + return data +} + +export type ProfileAuditLogsData = Awaited> +export type ProfileAuditLogsError = ResponseError + +export const useProfileAuditLogsQuery = ( + vars: ProfileAuditLogsVariables, + options: UseQueryOptions = {} +) => { + const { iso_timestamp_start, iso_timestamp_end } = vars + const date_start = dayjs(iso_timestamp_start).utc().format('YYYY-MM-DD') + const date_end = dayjs(iso_timestamp_end).utc().format('YYYY-MM-DD') + + return useQuery( + profileKeys.auditLogs({ date_start, date_end }), + ({ signal }) => getProfileAuditLogs(vars, signal), + { + ...options, + } + ) +} diff --git a/studio/pages/account/audit.tsx b/studio/pages/account/audit.tsx new file mode 100644 index 00000000000..7dddb8705a3 --- /dev/null +++ b/studio/pages/account/audit.tsx @@ -0,0 +1,27 @@ +import { AuditLogs } from 'components/interfaces/Account' +import { AccountLayout } from 'components/layouts' +import { NextPageWithLayout } from 'types' + +const User: NextPageWithLayout = () => { + return ( +
    + +
    + ) +} + +User.getLayout = (page) => ( + + {page} + +) + +export default User From 655fffe5f7aa1e91fe486810b18db10febdaac91 Mon Sep 17 00:00:00 2001 From: Ivan Vasilov Date: Wed, 6 Sep 2023 14:19:34 +0200 Subject: [PATCH 023/105] Rename the account audit logs file. --- .../AuditLogs/{AuditLogs.tsx => index.tsx} | 24 +------------------ studio/components/interfaces/Account/index.ts | 2 +- 2 files changed, 2 insertions(+), 24 deletions(-) rename studio/components/interfaces/Account/AuditLogs/{AuditLogs.tsx => index.tsx} (92%) diff --git a/studio/components/interfaces/Account/AuditLogs/AuditLogs.tsx b/studio/components/interfaces/Account/AuditLogs/index.tsx similarity index 92% rename from studio/components/interfaces/Account/AuditLogs/AuditLogs.tsx rename to studio/components/interfaces/Account/AuditLogs/index.tsx index 58875a395ce..bf64028bee7 100644 --- a/studio/components/interfaces/Account/AuditLogs/AuditLogs.tsx +++ b/studio/components/interfaces/Account/AuditLogs/index.tsx @@ -19,12 +19,6 @@ import { Alert, Button, IconArrowDown, IconArrowUp, IconRefreshCw, IconUser } fr import FilterPopover from './FilterPopover' import LogDetailsPanel from './LogDetailsPanel' -// [Joshen considerations] -// - Maybe fix the height of the table to the remaining height of the viewport, so that the search input is always visible -// - We'll need pagination as well if the audit logs get too large, but that needs to be implemented on the API side first if possible -// - I've hidden time input in the date picker for now cause the time support in the component is a bit iffy, need to investigate -// - Maybe a rule to follow from here is just everytime we call dayjs, use UTC(), one TZ to rule them all - const AuditLogs = () => { const { slug } = useParams() const currentTime = dayjs().utc().set('millisecond', 0) @@ -34,8 +28,7 @@ const AuditLogs = () => { to: currentTime.toISOString(), }) const [selectedLog, setSelectedLog] = useState() - const [filters, setFilters] = useState<{ users: string[]; projects: string[] }>({ - users: [], // gotrue_id[] + const [filters, setFilters] = useState<{ projects: string[] }>({ projects: [], // project_ref[] }) @@ -60,13 +53,6 @@ const AuditLogs = () => { ? Number(new Date(b.occurred_at)) - Number(new Date(a.occurred_at)) : Number(new Date(a.occurred_at)) - Number(new Date(b.occurred_at)) ) - ?.filter((log) => { - if (filters.users.length > 0) { - return filters.users.includes(log.actor.id) - } else { - return log - } - }) ?.filter((log) => { if (filters.projects.length > 0) { return filters.projects.includes(log.target.metadata.project_ref || '') @@ -82,14 +68,6 @@ const AuditLogs = () => {

    Filter by

    - setFilters({ ...filters, users: values })} - /> Date: Wed, 6 Sep 2023 14:21:42 +0200 Subject: [PATCH 024/105] Reuse the AuditLog type. --- .../organizations/organization-audit-logs-query.ts | 9 +++++---- studio/data/profile/profile-audit-logs-query.ts | 11 ++++++++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/studio/data/organizations/organization-audit-logs-query.ts b/studio/data/organizations/organization-audit-logs-query.ts index 8b5663dd6c2..abd43dd4b04 100644 --- a/studio/data/organizations/organization-audit-logs-query.ts +++ b/studio/data/organizations/organization-audit-logs-query.ts @@ -1,12 +1,13 @@ import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query' +import dayjs from 'dayjs' +import { useCallback } from 'react' + import { get } from 'lib/common/fetch' import { API_URL } from 'lib/constants' -import { useCallback } from 'react' -import { organizationKeys } from './keys' import { ResponseError } from 'types' -import dayjs from 'dayjs' +import { organizationKeys } from './keys' -export type OrganizationAuditLog = { +export type AuditLog = { action: { metadata: { method?: string diff --git a/studio/data/profile/profile-audit-logs-query.ts b/studio/data/profile/profile-audit-logs-query.ts index 9e7b3ae6101..44d5f5a1143 100644 --- a/studio/data/profile/profile-audit-logs-query.ts +++ b/studio/data/profile/profile-audit-logs-query.ts @@ -1,6 +1,8 @@ import { useQuery, UseQueryOptions } from '@tanstack/react-query' -import { get } from 'data/fetchers' import dayjs from 'dayjs' + +import { get } from 'data/fetchers' +import { AuditLog } from 'data/organizations/organization-audit-logs-query' import { ResponseError } from 'types' import { profileKeys } from './keys' @@ -24,11 +26,14 @@ export async function getProfileAuditLogs( }) if (error) throw error - return data + return data as unknown as ProfileAuditLogsData } -export type ProfileAuditLogsData = Awaited> export type ProfileAuditLogsError = ResponseError +export type ProfileAuditLogsData = { + result: AuditLog[] + retention_period: number +} export const useProfileAuditLogsQuery = ( vars: ProfileAuditLogsVariables, From b9173073bba8390c2697e4dbb20f43e0e8c86a1a Mon Sep 17 00:00:00 2001 From: Ivan Vasilov Date: Wed, 6 Sep 2023 14:22:06 +0200 Subject: [PATCH 025/105] Add audit logs to the navigation. --- studio/components/layouts/AccountLayout/AccountLayout.tsx | 7 +++++++ .../layouts/SettingsLayout/AccountSettingsMenu.tsx | 1 + 2 files changed, 8 insertions(+) diff --git a/studio/components/layouts/AccountLayout/AccountLayout.tsx b/studio/components/layouts/AccountLayout/AccountLayout.tsx index 5ebbab909b1..f9e308a04b3 100644 --- a/studio/components/layouts/AccountLayout/AccountLayout.tsx +++ b/studio/components/layouts/AccountLayout/AccountLayout.tsx @@ -81,6 +81,13 @@ const AccountLayout = ({ children, title, breadcrumbs }: PropsWithChildren { { label: 'Preferences', pathname: `/account/me` }, { label: 'Access Tokens', pathname: `/account/tokens` }, { label: 'Security', pathname: `/account/security` }, + { label: 'Audit logs', pathname: `/account/audit` }, ] return ( From 4b6a0f1ac34d4a6b4f906cc260f78983e6075b9c Mon Sep 17 00:00:00 2001 From: Ivan Vasilov Date: Wed, 6 Sep 2023 14:45:00 +0200 Subject: [PATCH 026/105] Add links to audit logs to all nav panels and dropdowns. --- .../layouts/AccountLayout/AccountLayout.tsx | 14 +++++++------- .../layouts/AppLayout/UserSettingsDropdown.tsx | 15 +++++++++------ .../ProjectLayout/NavigationBar/NavigationBar.tsx | 9 --------- 3 files changed, 16 insertions(+), 22 deletions(-) diff --git a/studio/components/layouts/AccountLayout/AccountLayout.tsx b/studio/components/layouts/AccountLayout/AccountLayout.tsx index f9e308a04b3..f4f77fda73a 100644 --- a/studio/components/layouts/AccountLayout/AccountLayout.tsx +++ b/studio/components/layouts/AccountLayout/AccountLayout.tsx @@ -81,13 +81,6 @@ const AccountLayout = ({ children, title, breadcrumbs }: PropsWithChildren { { - router.push('/account/me') - }} onClick={() => setOpen(false)} asChild > @@ -68,15 +65,21 @@ const UserSettingsDropdown = () => { { - router.push('/account/tokens') - }} onClick={() => setOpen(false)} asChild > Access tokens + + setOpen(false)} + asChild + > + Audit logs + + { Account Preferences - - } - > - Audit Logs - - )} From 2d73c28b139964b6a2eac5ca11a5f600f66fd177 Mon Sep 17 00:00:00 2001 From: Ivan Vasilov Date: Wed, 6 Sep 2023 17:20:42 +0200 Subject: [PATCH 027/105] Remove leftovers from organization audit logs. --- .../Account/AuditLogs/LogDetailsPanel.tsx | 16 +------ .../interfaces/Account/AuditLogs/index.tsx | 42 +------------------ 2 files changed, 4 insertions(+), 54 deletions(-) diff --git a/studio/components/interfaces/Account/AuditLogs/LogDetailsPanel.tsx b/studio/components/interfaces/Account/AuditLogs/LogDetailsPanel.tsx index 0f374319b15..c39bf9cb446 100644 --- a/studio/components/interfaces/Account/AuditLogs/LogDetailsPanel.tsx +++ b/studio/components/interfaces/Account/AuditLogs/LogDetailsPanel.tsx @@ -1,26 +1,14 @@ import { FormSection, FormSectionContent, FormSectionLabel } from 'components/ui/Forms' -import { OrganizationAuditLog } from 'data/organizations/organization-audit-logs-query' -import { useOrganizationsQuery } from 'data/organizations/organizations-query' -import { useProjectsQuery } from 'data/projects/projects-query' +import { AuditLog } from 'data/organizations/organization-audit-logs-query' import dayjs from 'dayjs' import { Input, SidePanel } from 'ui' export interface LogDetailsPanelProps { - selectedLog?: OrganizationAuditLog + selectedLog?: AuditLog onClose: () => void } const LogDetailsPanel = ({ selectedLog, onClose }: LogDetailsPanelProps) => { - const { data: projects } = useProjectsQuery() - const { data: organizations } = useOrganizationsQuery() - - const project = projects?.find( - (project) => project.ref === selectedLog?.target.metadata.project_ref - ) - const organization = organizations?.find( - (org) => org.slug === selectedLog?.target.metadata.org_slug - ) - return ( { const { data: projects } = useProjectsQuery() const { data: organizations } = useOrganizationsQuery() - const { data: detailData } = useOrganizationDetailQuery({ slug }) - const { data: rolesData } = useOrganizationRolesQuery({ slug }) const { data, error, isLoading, isSuccess, isError, isRefetching, refetch } = useProfileAuditLogsQuery({ iso_timestamp_start: dateRange.from, @@ -106,7 +104,7 @@ const AuditLogs = () => { renderFooter={() => { return ( - Your organization has a log retention period of{' '} + You have a log retention period of{' '} {retentionPeriod} day {retentionPeriod > 1 ? 's' : ''} @@ -149,9 +147,7 @@ const AuditLogs = () => { <> {logs.length === 0 ? (
    -

    - Your organization does not have any audit logs available yet -

    +

    You do not have any audit logs available yet

    ) : logs.length > 0 && sortedLogs.length === 0 ? (
    @@ -160,9 +156,6 @@ const AuditLogs = () => { ) : (
    - User - , Action , @@ -212,8 +205,6 @@ const AuditLogs = () => { ]} body={ sortedLogs?.map((log) => { - const user = members.find((member) => member.gotrue_id === log.actor.id) - const role = roles.find((role) => user?.role_ids?.[0] === role.id) const project = projects?.find( (project) => project.ref === log.target.metadata.project_ref ) @@ -222,24 +213,6 @@ const AuditLogs = () => { ) const hasStatusCode = log.action.metadata[0]?.status !== undefined - const userIcon = - user === undefined ? ( -
    -

    ?

    -
    - ) : user?.invited_id || user?.username === user?.primary_email ? ( -
    - -
    - ) : ( - - ) return ( { onClick={() => setSelectedLog(log)} className="cursor-pointer hover:!bg-scale-100 transition duration-100" > - -
    -
    {userIcon}
    -
    -

    {user?.username ?? 'Unknown'}

    - {role && ( -

    {role?.name}

    - )} -
    -
    -
    {hasStatusCode && ( From ba40f7d88bd64a07993ffbeab916582a68526cc2 Mon Sep 17 00:00:00 2001 From: Ivan Vasilov Date: Wed, 6 Sep 2023 17:21:11 +0200 Subject: [PATCH 028/105] Add fix for the sliding log dates. --- .../interfaces/Account/AuditLogs/index.tsx | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/studio/components/interfaces/Account/AuditLogs/index.tsx b/studio/components/interfaces/Account/AuditLogs/index.tsx index 2f536fac8d3..69461de5c21 100644 --- a/studio/components/interfaces/Account/AuditLogs/index.tsx +++ b/studio/components/interfaces/Account/AuditLogs/index.tsx @@ -1,33 +1,29 @@ import * as Tooltip from '@radix-ui/react-tooltip' import dayjs from 'dayjs' -import Image from 'next/image' -import { useState } from 'react' +import { useEffect, useState } from 'react' -import { useParams } from 'common' import { ScaffoldContainerLegacy } from 'components/layouts/Scaffold' import Table from 'components/to-be-cleaned/Table' import AlertError from 'components/ui/AlertError' import { DatePicker } from 'components/ui/DatePicker' import ShimmeringLoader from 'components/ui/ShimmeringLoader' -import { OrganizationAuditLog } from 'data/organizations/organization-audit-logs-query' -import { useOrganizationDetailQuery } from 'data/organizations/organization-detail-query' -import { useOrganizationRolesQuery } from 'data/organizations/organization-roles-query' +import { AuditLog } from 'data/organizations/organization-audit-logs-query' import { useOrganizationsQuery } from 'data/organizations/organizations-query' import { useProfileAuditLogsQuery } from 'data/profile/profile-audit-logs-query' import { useProjectsQuery } from 'data/projects/projects-query' -import { Alert, Button, IconArrowDown, IconArrowUp, IconRefreshCw, IconUser } from 'ui' +import { Alert, Button, IconArrowDown, IconArrowUp, IconRefreshCw } from 'ui' import FilterPopover from './FilterPopover' import LogDetailsPanel from './LogDetailsPanel' const AuditLogs = () => { - const { slug } = useParams() const currentTime = dayjs().utc().set('millisecond', 0) const [dateSortDesc, setDateSortDesc] = useState(true) const [dateRange, setDateRange] = useState({ from: currentTime.subtract(1, 'day').toISOString(), to: currentTime.toISOString(), }) - const [selectedLog, setSelectedLog] = useState() + + const [selectedLog, setSelectedLog] = useState() const [filters, setFilters] = useState<{ projects: string[] }>({ projects: [], // project_ref[] }) @@ -40,8 +36,22 @@ const AuditLogs = () => { iso_timestamp_end: dateRange.to, }) - const members = detailData?.members ?? [] - const roles = rolesData?.roles ?? [] + // This feature depends on the subscription tier of the user. Free user can view logs up to 1 day + // in the past. The API limits the logs to maximum of 1 day and 5 minutes so when the page is + // viewed for more than 5 minutes, the call parameters needs to be updated. This also works with + // higher tiers (7 days of logs).The user will see a loading shimmer. + useEffect(() => { + const duration = dayjs(dateRange.from).diff(dayjs(dateRange.to)) + const interval = setInterval(() => { + const currentTime = dayjs().utc().set('millisecond', 0) + setDateRange({ + from: currentTime.add(duration).toISOString(), + to: currentTime.toISOString(), + }) + }, 5 * 60000) + + return () => clearInterval(interval) + }, [dateRange.from, dateRange.to]) const retentionPeriod = data?.retention_period ?? 0 const logs = data?.result ?? [] From 3f8439566820687a0df0065e7078f1abc969f73d Mon Sep 17 00:00:00 2001 From: Ivan Vasilov Date: Wed, 6 Sep 2023 17:21:22 +0200 Subject: [PATCH 029/105] Minor type fixes. --- .../interfaces/Organization/AuditLogs/AuditLogs.tsx | 6 +++--- studio/data/profile/profile-audit-logs-query.ts | 11 ++++------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx b/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx index 7a864aef626..fc10ea0873d 100644 --- a/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx +++ b/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx @@ -4,12 +4,13 @@ import Image from 'next/image' import { useState } from 'react' import { useParams } from 'common' +import { ScaffoldContainerLegacy } from 'components/layouts/Scaffold' import Table from 'components/to-be-cleaned/Table' import AlertError from 'components/ui/AlertError' import { DatePicker } from 'components/ui/DatePicker' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { - OrganizationAuditLog, + AuditLog, useOrganizationAuditLogsQuery, } from 'data/organizations/organization-audit-logs-query' import { useOrganizationDetailQuery } from 'data/organizations/organization-detail-query' @@ -19,7 +20,6 @@ import { useProjectsQuery } from 'data/projects/projects-query' import { Alert, Button, IconArrowDown, IconArrowUp, IconRefreshCw, IconUser } from 'ui' import FilterPopover from './FilterPopover' import LogDetailsPanel from './LogDetailsPanel' -import { ScaffoldContainerLegacy } from 'components/layouts/Scaffold' // [Joshen considerations] // - Maybe fix the height of the table to the remaining height of the viewport, so that the search input is always visible @@ -35,7 +35,7 @@ const AuditLogs = () => { from: currentTime.subtract(1, 'day').toISOString(), to: currentTime.toISOString(), }) - const [selectedLog, setSelectedLog] = useState() + const [selectedLog, setSelectedLog] = useState() const [filters, setFilters] = useState<{ users: string[]; projects: string[] }>({ users: [], // gotrue_id[] projects: [], // project_ref[] diff --git a/studio/data/profile/profile-audit-logs-query.ts b/studio/data/profile/profile-audit-logs-query.ts index 44d5f5a1143..f91d5723f1c 100644 --- a/studio/data/profile/profile-audit-logs-query.ts +++ b/studio/data/profile/profile-audit-logs-query.ts @@ -1,5 +1,4 @@ import { useQuery, UseQueryOptions } from '@tanstack/react-query' -import dayjs from 'dayjs' import { get } from 'data/fetchers' import { AuditLog } from 'data/organizations/organization-audit-logs-query' @@ -7,14 +6,15 @@ import { ResponseError } from 'types' import { profileKeys } from './keys' export type ProfileAuditLogsVariables = { - iso_timestamp_start?: string - iso_timestamp_end?: string + iso_timestamp_start: string + iso_timestamp_end: string } export async function getProfileAuditLogs( { iso_timestamp_start, iso_timestamp_end }: ProfileAuditLogsVariables, signal?: AbortSignal ) { + console.log(iso_timestamp_start, iso_timestamp_end) const { data, error } = await get('/platform/profile/audit', { params: { query: { @@ -40,11 +40,8 @@ export const useProfileAuditLogsQuery = ( options: UseQueryOptions = {} ) => { const { iso_timestamp_start, iso_timestamp_end } = vars - const date_start = dayjs(iso_timestamp_start).utc().format('YYYY-MM-DD') - const date_end = dayjs(iso_timestamp_end).utc().format('YYYY-MM-DD') - return useQuery( - profileKeys.auditLogs({ date_start, date_end }), + profileKeys.auditLogs({ date_start: iso_timestamp_start, date_end: iso_timestamp_end }), ({ signal }) => getProfileAuditLogs(vars, signal), { ...options, From b501faed834d2857b90d884a288322d639b0e6e6 Mon Sep 17 00:00:00 2001 From: Ivan Vasilov Date: Wed, 6 Sep 2023 21:07:09 +0200 Subject: [PATCH 030/105] Clean duplicate files. --- .../{AuditLogs/index.tsx => AuditLogs.tsx} | 3 +- .../Account/AuditLogs/FilterPopover.tsx | 100 ------------------ .../Account/AuditLogs/LogDetailsPanel.tsx | 85 --------------- .../AuditLogs/LogDetailsPanel.tsx | 4 +- 4 files changed, 3 insertions(+), 189 deletions(-) rename studio/components/interfaces/Account/{AuditLogs/index.tsx => AuditLogs.tsx} (99%) delete mode 100644 studio/components/interfaces/Account/AuditLogs/FilterPopover.tsx delete mode 100644 studio/components/interfaces/Account/AuditLogs/LogDetailsPanel.tsx diff --git a/studio/components/interfaces/Account/AuditLogs/index.tsx b/studio/components/interfaces/Account/AuditLogs.tsx similarity index 99% rename from studio/components/interfaces/Account/AuditLogs/index.tsx rename to studio/components/interfaces/Account/AuditLogs.tsx index 69461de5c21..578ce2f6e3e 100644 --- a/studio/components/interfaces/Account/AuditLogs/index.tsx +++ b/studio/components/interfaces/Account/AuditLogs.tsx @@ -12,8 +12,7 @@ import { useOrganizationsQuery } from 'data/organizations/organizations-query' import { useProfileAuditLogsQuery } from 'data/profile/profile-audit-logs-query' import { useProjectsQuery } from 'data/projects/projects-query' import { Alert, Button, IconArrowDown, IconArrowUp, IconRefreshCw } from 'ui' -import FilterPopover from './FilterPopover' -import LogDetailsPanel from './LogDetailsPanel' +import FilterPopover from '../Organization/AuditLogs/FilterPopover' const AuditLogs = () => { const currentTime = dayjs().utc().set('millisecond', 0) diff --git a/studio/components/interfaces/Account/AuditLogs/FilterPopover.tsx b/studio/components/interfaces/Account/AuditLogs/FilterPopover.tsx deleted file mode 100644 index c38c96736e3..00000000000 --- a/studio/components/interfaces/Account/AuditLogs/FilterPopover.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { useState } from 'react' -import { Button, Checkbox, Popover } from 'ui' - -interface FilterPopoverProps { - options: any[] - activeOptions: any[] - valueKey: string - labelKey: string - name: string - onSaveFilters: (options: string[]) => void -} - -// [Joshen] Form + Checkbox.Group doesn't seem to work properly RE form state -// hence why the manual implementation here as a workaround - -const FilterPopover = ({ - options = [], - activeOptions = [], - valueKey, - labelKey, - name, - onSaveFilters, -}: FilterPopoverProps) => { - const [open, setOpen] = useState(false) - const [selectedOptions, setSelectedOptions] = useState([]) - - const formattedOptions = activeOptions.map((option) => { - const base = options.find((x) => x[valueKey] === option) - return base[labelKey] - }) - - return ( - setOpen(!open)} - header={
    Select {name.toLowerCase()}
    } - overlay={ - <> -
    - { - const value = event.target.value - if (selectedOptions.includes(value)) { - setSelectedOptions(selectedOptions.filter((x) => x !== value)) - } else { - setSelectedOptions(selectedOptions.concat(value)) - } - }} - options={options.map((option) => { - return { - value: option[valueKey], - label: option[labelKey], - defaultChecked: activeOptions.includes(option[valueKey]), - } - })} - > -
    -
    - - -
    - - } - > - -
    - ) -} - -export default FilterPopover diff --git a/studio/components/interfaces/Account/AuditLogs/LogDetailsPanel.tsx b/studio/components/interfaces/Account/AuditLogs/LogDetailsPanel.tsx deleted file mode 100644 index c39bf9cb446..00000000000 --- a/studio/components/interfaces/Account/AuditLogs/LogDetailsPanel.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { FormSection, FormSectionContent, FormSectionLabel } from 'components/ui/Forms' -import { AuditLog } from 'data/organizations/organization-audit-logs-query' -import dayjs from 'dayjs' -import { Input, SidePanel } from 'ui' - -export interface LogDetailsPanelProps { - selectedLog?: AuditLog - onClose: () => void -} - -const LogDetailsPanel = ({ selectedLog, onClose }: LogDetailsPanelProps) => { - return ( - - General}> - - - - - - - - Actor}> - - - - - - - - - Action}> - - - - - - - - - Target}> - - - - - - - ) -} - -export default LogDetailsPanel diff --git a/studio/components/interfaces/Organization/AuditLogs/LogDetailsPanel.tsx b/studio/components/interfaces/Organization/AuditLogs/LogDetailsPanel.tsx index 0f374319b15..99c53e2f906 100644 --- a/studio/components/interfaces/Organization/AuditLogs/LogDetailsPanel.tsx +++ b/studio/components/interfaces/Organization/AuditLogs/LogDetailsPanel.tsx @@ -1,12 +1,12 @@ import { FormSection, FormSectionContent, FormSectionLabel } from 'components/ui/Forms' -import { OrganizationAuditLog } from 'data/organizations/organization-audit-logs-query' +import { AuditLog } from 'data/organizations/organization-audit-logs-query' import { useOrganizationsQuery } from 'data/organizations/organizations-query' import { useProjectsQuery } from 'data/projects/projects-query' import dayjs from 'dayjs' import { Input, SidePanel } from 'ui' export interface LogDetailsPanelProps { - selectedLog?: OrganizationAuditLog + selectedLog?: AuditLog onClose: () => void } From aa8dc9a762cddf96bd799c67725536c2b394867d Mon Sep 17 00:00:00 2001 From: Ivan Vasilov Date: Thu, 7 Sep 2023 11:16:38 +0200 Subject: [PATCH 031/105] Add missing import. --- studio/components/interfaces/Account/AuditLogs.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/studio/components/interfaces/Account/AuditLogs.tsx b/studio/components/interfaces/Account/AuditLogs.tsx index 578ce2f6e3e..1e054d24c86 100644 --- a/studio/components/interfaces/Account/AuditLogs.tsx +++ b/studio/components/interfaces/Account/AuditLogs.tsx @@ -13,6 +13,7 @@ import { useProfileAuditLogsQuery } from 'data/profile/profile-audit-logs-query' import { useProjectsQuery } from 'data/projects/projects-query' import { Alert, Button, IconArrowDown, IconArrowUp, IconRefreshCw } from 'ui' import FilterPopover from '../Organization/AuditLogs/FilterPopover' +import LogDetailsPanel from '../Organization/AuditLogs/LogDetailsPanel' const AuditLogs = () => { const currentTime = dayjs().utc().set('millisecond', 0) From 3a6ed0599085016c72690d72c1b6247f474406c8 Mon Sep 17 00:00:00 2001 From: Ivan Vasilov Date: Thu, 7 Sep 2023 11:24:46 +0200 Subject: [PATCH 032/105] Fix a typo. --- studio/data/organizations/organization-audit-logs-query.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/data/organizations/organization-audit-logs-query.ts b/studio/data/organizations/organization-audit-logs-query.ts index abd43dd4b04..8da3d9c1c44 100644 --- a/studio/data/organizations/organization-audit-logs-query.ts +++ b/studio/data/organizations/organization-audit-logs-query.ts @@ -33,7 +33,7 @@ export type AuditLog = { } export type OrganizationAuditLogsResponse = { - result: OrganizationAuditLog[] + result: AuditLog[] retention_period: number } From eff4373b6b82731f4df02cf4dc76237e5154db42 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 8 Sep 2023 14:04:06 +0800 Subject: [PATCH 033/105] Small refactor --- .../interfaces/Account/AuditLogs.tsx | 424 +++++++++--------- .../AuditLogs/FilterPopover.tsx | 0 .../AuditLogs/LogDetailsPanel.tsx | 0 .../components/interfaces/AuditLogs/index.ts | 4 + .../Organization/AuditLogs/AuditLogs.tsx | 9 +- .../components/ui/DatePicker/DatePicker.tsx | 4 +- studio/pages/account/audit.tsx | 12 +- 7 files changed, 229 insertions(+), 224 deletions(-) rename studio/components/interfaces/{Organization => }/AuditLogs/FilterPopover.tsx (100%) rename studio/components/interfaces/{Organization => }/AuditLogs/LogDetailsPanel.tsx (100%) create mode 100644 studio/components/interfaces/AuditLogs/index.ts diff --git a/studio/components/interfaces/Account/AuditLogs.tsx b/studio/components/interfaces/Account/AuditLogs.tsx index 1e054d24c86..2fba78c31de 100644 --- a/studio/components/interfaces/Account/AuditLogs.tsx +++ b/studio/components/interfaces/Account/AuditLogs.tsx @@ -1,8 +1,9 @@ import * as Tooltip from '@radix-ui/react-tooltip' import dayjs from 'dayjs' import { useEffect, useState } from 'react' +import { Alert, Button, IconArrowDown, IconArrowUp, IconRefreshCw } from 'ui' -import { ScaffoldContainerLegacy } from 'components/layouts/Scaffold' +import { FilterPopover, LogDetailsPanel } from 'components/interfaces/AuditLogs' import Table from 'components/to-be-cleaned/Table' import AlertError from 'components/ui/AlertError' import { DatePicker } from 'components/ui/DatePicker' @@ -11,9 +12,6 @@ import { AuditLog } from 'data/organizations/organization-audit-logs-query' import { useOrganizationsQuery } from 'data/organizations/organizations-query' import { useProfileAuditLogsQuery } from 'data/profile/profile-audit-logs-query' import { useProjectsQuery } from 'data/projects/projects-query' -import { Alert, Button, IconArrowDown, IconArrowUp, IconRefreshCw } from 'ui' -import FilterPopover from '../Organization/AuditLogs/FilterPopover' -import LogDetailsPanel from '../Organization/AuditLogs/LogDetailsPanel' const AuditLogs = () => { const currentTime = dayjs().utc().set('millisecond', 0) @@ -71,219 +69,215 @@ const AuditLogs = () => { return ( <> - -
    -
    -
    -

    Filter by

    - setFilters({ ...filters, projects: values })} - /> - { - if (value.from !== null && value.to !== null) { - const current = dayjs().utc() - const from = dayjs(value.from) - .utc() - .hour(current.hour()) - .minute(current.minute()) - .second(current.second()) - .toISOString() - const to = dayjs(value.to) - .utc() - .hour(current.hour()) - .minute(current.minute()) - .second(current.second()) - .toISOString() - setDateRange({ from, to }) - } - }} - renderFooter={() => { - return ( - - You have a log retention period of{' '} - - {retentionPeriod} day - {retentionPeriod > 1 ? 's' : ''} - - . You may only view logs from{' '} - {dayjs().subtract(retentionPeriod, 'days').format('DD MMM YYYY')} as the - earliest date. - - ) - }} - /> - {isSuccess && ( - <> -
    -

    Viewing {sortedLogs.length} logs in total

    - - )} -
    - +
    +
    +
    +

    Filter by

    + setFilters({ ...filters, projects: values })} + /> + { + if (value.from !== null && value.to !== null) { + const current = dayjs().utc() + const from = dayjs(value.from) + .utc() + .hour(current.hour()) + .minute(current.minute()) + .second(current.second()) + .toISOString() + const to = dayjs(value.to) + .utc() + .hour(current.hour()) + .minute(current.minute()) + .second(current.second()) + .toISOString() + setDateRange({ from, to }) + } + }} + renderFooter={() => { + return ( + + You have a log retention period of{' '} + + {retentionPeriod} day + {retentionPeriod > 1 ? 's' : ''} + + . You may only view logs from{' '} + {dayjs().subtract(retentionPeriod, 'days').format('DD MMM YYYY')} as the + earliest date. + + ) + }} + /> + {isSuccess && ( + <> +
    +

    Viewing {sortedLogs.length} logs in total

    + + )}
    - - {isLoading && ( -
    - - - -
    - )} - - {isError && } - - {isSuccess && ( - <> - {logs.length === 0 ? ( -
    -

    You do not have any audit logs available yet

    -
    - ) : logs.length > 0 && sortedLogs.length === 0 ? ( -
    -

    No audit logs found based on the filters applied

    -
    - ) : ( -
    - Action - , - - Target - , - -
    -

    Date

    - - - -
    -
    , - , - ]} - body={ - sortedLogs?.map((log) => { - const project = projects?.find( - (project) => project.ref === log.target.metadata.project_ref - ) - const organization = organizations?.find( - (org) => org.slug === log.target.metadata.org_slug - ) - - const hasStatusCode = log.action.metadata[0]?.status !== undefined - - return ( - setSelectedLog(log)} - className="cursor-pointer hover:!bg-scale-100 transition duration-100" - > - -
    - {hasStatusCode && ( -

    - {log.action.metadata[0].status} -

    - )} -

    - {log.action.name} -

    -
    -
    - -

    - {project?.name - ? 'Project: ' - : organization?.name - ? 'Organization: ' - : null} - {project?.name ?? organization?.name ?? 'Unknown'} -

    -

    - {log.target.metadata.project_ref - ? 'Ref: ' - : log.target.metadata.org_slug - ? 'Slug: ' - : null} - {log.target.metadata.project_ref ?? log.target.metadata.org_slug} -

    -
    - - {dayjs(log.occurred_at).format('DD MMM YYYY, HH:mm:ss')} - - - - -
    - ) - }) ?? [] - } - /> - )} - - )} + - + + {isLoading && ( +
    + + + +
    + )} + + {isError && } + + {isSuccess && ( + <> + {logs.length === 0 ? ( +
    +

    You do not have any audit logs available yet

    +
    + ) : logs.length > 0 && sortedLogs.length === 0 ? ( +
    +

    No audit logs found based on the filters applied

    +
    + ) : ( +
    + Action + , + + Target + , + +
    +

    Date

    + + + +
    +
    , + , + ]} + body={ + sortedLogs?.map((log) => { + const project = projects?.find( + (project) => project.ref === log.target.metadata.project_ref + ) + const organization = organizations?.find( + (org) => org.slug === log.target.metadata.org_slug + ) + + const hasStatusCode = log.action.metadata[0]?.status !== undefined + + return ( + setSelectedLog(log)} + className="cursor-pointer hover:!bg-scale-100 transition duration-100" + > + +
    + {hasStatusCode && ( +

    + {log.action.metadata[0].status} +

    + )} +

    + {log.action.name} +

    +
    +
    + +

    + {project?.name + ? 'Project: ' + : organization?.name + ? 'Organization: ' + : null} + {project?.name ?? organization?.name ?? 'Unknown'} +

    +

    + {log.target.metadata.project_ref + ? 'Ref: ' + : log.target.metadata.org_slug + ? 'Slug: ' + : null} + {log.target.metadata.project_ref ?? log.target.metadata.org_slug} +

    +
    + + {dayjs(log.occurred_at).format('DD MMM YYYY, HH:mm:ss')} + + + + +
    + ) + }) ?? [] + } + /> + )} + + )} + setSelectedLog(undefined)} /> diff --git a/studio/components/interfaces/Organization/AuditLogs/FilterPopover.tsx b/studio/components/interfaces/AuditLogs/FilterPopover.tsx similarity index 100% rename from studio/components/interfaces/Organization/AuditLogs/FilterPopover.tsx rename to studio/components/interfaces/AuditLogs/FilterPopover.tsx diff --git a/studio/components/interfaces/Organization/AuditLogs/LogDetailsPanel.tsx b/studio/components/interfaces/AuditLogs/LogDetailsPanel.tsx similarity index 100% rename from studio/components/interfaces/Organization/AuditLogs/LogDetailsPanel.tsx rename to studio/components/interfaces/AuditLogs/LogDetailsPanel.tsx diff --git a/studio/components/interfaces/AuditLogs/index.ts b/studio/components/interfaces/AuditLogs/index.ts new file mode 100644 index 00000000000..d54f1b5d120 --- /dev/null +++ b/studio/components/interfaces/AuditLogs/index.ts @@ -0,0 +1,4 @@ +import FilterPopover from './FilterPopover' +import LogDetailsPanel from './LogDetailsPanel' + +export { FilterPopover, LogDetailsPanel } diff --git a/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx b/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx index fc10ea0873d..5d93d958d68 100644 --- a/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx +++ b/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx @@ -2,8 +2,10 @@ import * as Tooltip from '@radix-ui/react-tooltip' import dayjs from 'dayjs' import Image from 'next/image' import { useState } from 'react' +import { Alert, Button, IconArrowDown, IconArrowUp, IconRefreshCw, IconUser } from 'ui' import { useParams } from 'common' +import { FilterPopover, LogDetailsPanel } from 'components/interfaces/AuditLogs' import { ScaffoldContainerLegacy } from 'components/layouts/Scaffold' import Table from 'components/to-be-cleaned/Table' import AlertError from 'components/ui/AlertError' @@ -17,9 +19,6 @@ import { useOrganizationDetailQuery } from 'data/organizations/organization-deta import { useOrganizationRolesQuery } from 'data/organizations/organization-roles-query' import { useOrganizationsQuery } from 'data/organizations/organizations-query' import { useProjectsQuery } from 'data/projects/projects-query' -import { Alert, Button, IconArrowDown, IconArrowUp, IconRefreshCw, IconUser } from 'ui' -import FilterPopover from './FilterPopover' -import LogDetailsPanel from './LogDetailsPanel' // [Joshen considerations] // - Maybe fix the height of the table to the remaining height of the viewport, so that the search input is always visible @@ -283,14 +282,14 @@ const AuditLogs = () => { - +
    {hasStatusCode && (

    {log.action.metadata[0].status}

    )} -

    +

    {log.action.name}

    diff --git a/studio/components/ui/DatePicker/DatePicker.tsx b/studio/components/ui/DatePicker/DatePicker.tsx index ec327d36b8f..9d1deb498f1 100644 --- a/studio/components/ui/DatePicker/DatePicker.tsx +++ b/studio/components/ui/DatePicker/DatePicker.tsx @@ -274,7 +274,7 @@ function _DatePicker({ {children !== undefined ? ( children ) : ( - + <> {/* Custom */} {selectsRange && appliedStartDate && @@ -289,7 +289,7 @@ function _DatePicker({ ) : ( 'Custom' )} - + )} diff --git a/studio/pages/account/audit.tsx b/studio/pages/account/audit.tsx index 7dddb8705a3..1e6398b63d4 100644 --- a/studio/pages/account/audit.tsx +++ b/studio/pages/account/audit.tsx @@ -1,12 +1,20 @@ import { AuditLogs } from 'components/interfaces/Account' import { AccountLayout } from 'components/layouts' +import { ScaffoldContainerLegacy } from 'components/layouts/Scaffold' +import { FormHeader } from 'components/ui/Forms' import { NextPageWithLayout } from 'types' const User: NextPageWithLayout = () => { return ( -
    + +
    + +
    -
    + ) } From 43e55fbbe8594f86d33e213b8684466bfab51841 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 8 Sep 2023 14:04:22 +0800 Subject: [PATCH 034/105] Remove console log --- studio/data/profile/profile-audit-logs-query.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/studio/data/profile/profile-audit-logs-query.ts b/studio/data/profile/profile-audit-logs-query.ts index f91d5723f1c..6c536df3795 100644 --- a/studio/data/profile/profile-audit-logs-query.ts +++ b/studio/data/profile/profile-audit-logs-query.ts @@ -14,7 +14,6 @@ export async function getProfileAuditLogs( { iso_timestamp_start, iso_timestamp_end }: ProfileAuditLogsVariables, signal?: AbortSignal ) { - console.log(iso_timestamp_start, iso_timestamp_end) const { data, error } = await get('/platform/profile/audit', { params: { query: { From afa6ebbc24caed9674807a57a809fca3e58df7f4 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 8 Sep 2023 14:13:39 +0800 Subject: [PATCH 035/105] Small change --- studio/components/interfaces/Account/AuditLogs.tsx | 4 ++-- .../interfaces/Organization/AuditLogs/AuditLogs.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/studio/components/interfaces/Account/AuditLogs.tsx b/studio/components/interfaces/Account/AuditLogs.tsx index 2fba78c31de..fcf3d12a52d 100644 --- a/studio/components/interfaces/Account/AuditLogs.tsx +++ b/studio/components/interfaces/Account/AuditLogs.tsx @@ -242,14 +242,14 @@ const AuditLogs = () => {

    {project?.name ? 'Project: ' : organization?.name ? 'Organization: ' : null} - {project?.name ?? organization?.name ?? 'Unknown'} + {project?.name ?? organization?.name ?? '-'}

    {

    {userIcon}
    -

    {user?.username ?? 'Unknown'}

    +

    {user?.username ?? '-'}

    {role && (

    {role?.name}

    )} @@ -297,7 +297,7 @@ const AuditLogs = () => {

    {project?.name ? 'Project: ' From 86e51ddb2f8190653b9be521f78847a5d2dde748 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 8 Sep 2023 14:41:06 +0800 Subject: [PATCH 036/105] UI fixes --- .../interfaces/Integrations/ProjectLinker.tsx | 8 +- .../Organization/AuditLogs/AuditLogs.tsx | 19 +- studio/components/ui/AlertError.tsx | 9 +- .../components/ui/DatePicker/DatePicker.tsx | 169 +++++++++--------- studio/package.json | 2 +- 5 files changed, 113 insertions(+), 94 deletions(-) diff --git a/studio/components/interfaces/Integrations/ProjectLinker.tsx b/studio/components/interfaces/Integrations/ProjectLinker.tsx index a619b126559..b537e6a6602 100644 --- a/studio/components/interfaces/Integrations/ProjectLinker.tsx +++ b/studio/components/interfaces/Integrations/ProjectLinker.tsx @@ -1,7 +1,9 @@ import { ReactNode, useRef, useState } from 'react' -import { IntegrationProjectConnection } from 'data/integrations/integrations.types' -import { IntegrationConnectionsCreateVariables } from 'data/integrations/integrations.types' +import { + IntegrationConnectionsCreateVariables, + IntegrationProjectConnection, +} from 'data/integrations/integrations.types' import { useSelectedOrganization } from 'hooks' import { BASE_PATH } from 'lib/constants' import { toast } from 'react-hot-toast' @@ -19,8 +21,6 @@ import { Popover_Shadcn_, cn, } from 'ui' -import { useProjectsQuery } from 'data/projects/projects-query' -import { useVercelProjectsQuery } from 'data/integrations/integrations-vercel-projects-query' export interface Project { id: string diff --git a/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx b/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx index aa36a0286b7..56dda8f2de7 100644 --- a/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx +++ b/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx @@ -1,7 +1,7 @@ import * as Tooltip from '@radix-ui/react-tooltip' import dayjs from 'dayjs' import Image from 'next/image' -import { useState } from 'react' +import { useEffect, useState } from 'react' import { Alert, Button, IconArrowDown, IconArrowUp, IconRefreshCw, IconUser } from 'ui' import { useParams } from 'common' @@ -51,6 +51,23 @@ const AuditLogs = () => { iso_timestamp_end: dateRange.to, }) + // This feature depends on the subscription tier of the user. Free user can view logs up to 1 day + // in the past. The API limits the logs to maximum of 1 day and 5 minutes so when the page is + // viewed for more than 5 minutes, the call parameters needs to be updated. This also works with + // higher tiers (7 days of logs).The user will see a loading shimmer. + useEffect(() => { + const duration = dayjs(dateRange.from).diff(dayjs(dateRange.to)) + const interval = setInterval(() => { + const currentTime = dayjs().utc().set('millisecond', 0) + setDateRange({ + from: currentTime.add(duration).toISOString(), + to: currentTime.toISOString(), + }) + }, 5 * 60000) + + return () => clearInterval(interval) + }, [dateRange.from, dateRange.to]) + const members = detailData?.members ?? [] const roles = rolesData?.roles ?? [] diff --git a/studio/components/ui/AlertError.tsx b/studio/components/ui/AlertError.tsx index dfd6b976a9a..aed01b2fc59 100644 --- a/studio/components/ui/AlertError.tsx +++ b/studio/components/ui/AlertError.tsx @@ -31,8 +31,13 @@ const AlertError = ({ ref, subject, error, className }: AlertErrorProps) => { {subject} - {error?.message &&

    Error: {error?.message}

    } - Try refreshing your browser, but if the issue persists, please reach out to us via support. +
    + {error?.message &&

    Error: {error?.message}

    } +

    + Try refreshing your browser, but if the issue persists, please reach out to us via + support. +

    +
    + + + <> + {hideTime ? null : ( + <> +
    + {!selectsRange ? null : ( + <> +
    + +
    +
    - -
    - - )} -
    - + > + +
    + + )} +
    + +
    -
    - - ) - } - overlay={ - <> + + )}
    handleSubmit()}>Apply
    - } - > - - + + ) } diff --git a/studio/package.json b/studio/package.json index 9bad8f60b1d..a8c3639f106 100644 --- a/studio/package.json +++ b/studio/package.json @@ -82,7 +82,7 @@ "react-beautiful-dnd": "^13.1.0", "react-contexify": "^5.0.0", "react-csv": "^2.0.3", - "react-datepicker": "^4.8.0", + "react-datepicker": "^4.16.0", "react-dnd": "^14.0.3", "react-dnd-html5-backend": "^14.0.1", "react-dom": "^17.0.2", From 32fc602adad64a0cc04a2dd44ef63dc3547340c5 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 8 Sep 2023 14:43:19 +0800 Subject: [PATCH 037/105] Update package lock --- package-lock.json | 64 ++++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/package-lock.json b/package-lock.json index 19ad31b2ede..288adec0c22 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33658,36 +33658,6 @@ "version": "2.2.2", "license": "MIT" }, - "node_modules/react-datepicker": { - "version": "4.12.0", - "license": "MIT", - "dependencies": { - "@popperjs/core": "^2.9.2", - "classnames": "^2.2.6", - "date-fns": "^2.24.0", - "prop-types": "^15.7.2", - "react-onclickoutside": "^6.12.2", - "react-popper": "^2.3.0" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17 || ^18", - "react-dom": "^16.9.0 || ^17 || ^18" - } - }, - "node_modules/react-datepicker/node_modules/date-fns": { - "version": "2.30.0", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.21.0" - }, - "engines": { - "node": ">=0.11" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" - } - }, "node_modules/react-dnd": { "version": "14.0.5", "license": "MIT", @@ -42543,7 +42513,7 @@ "react-beautiful-dnd": "^13.1.0", "react-contexify": "^5.0.0", "react-csv": "^2.0.3", - "react-datepicker": "^4.8.0", + "react-datepicker": "^4.16.0", "react-dnd": "^14.0.3", "react-dnd-html5-backend": "^14.0.1", "react-dom": "^17.0.2", @@ -42926,6 +42896,21 @@ "tslib": "^2.4.1" } }, + "studio/node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "studio/node_modules/doctrine": { "version": "2.1.0", "license": "Apache-2.0", @@ -43107,6 +43092,23 @@ "url": "https://opencollective.com/ramda" } }, + "studio/node_modules/react-datepicker": { + "version": "4.16.0", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.16.0.tgz", + "integrity": "sha512-hNQ0PAg/LQoVbDUO/RWAdm/RYmPhN3cz7LuQ3hqbs24OSp69QCiKOJRrQ4jk1gv1jNR5oYu8SjjgfDh8q6Q1yw==", + "dependencies": { + "@popperjs/core": "^2.11.8", + "classnames": "^2.2.6", + "date-fns": "^2.30.0", + "prop-types": "^15.7.2", + "react-onclickoutside": "^6.12.2", + "react-popper": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17 || ^18", + "react-dom": "^16.9.0 || ^17 || ^18" + } + }, "studio/node_modules/resolve": { "version": "2.0.0-next.4", "license": "MIT", From 948ba823ebc9cedd1e993034ab6d0840139119ea Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Fri, 8 Sep 2023 11:56:16 +0200 Subject: [PATCH 038/105] add support to developers nav --- apps/www/components/Nav/Developers.tsx | 8 ++++---- apps/www/data/Developers.tsx | 6 ++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/www/components/Nav/Developers.tsx b/apps/www/components/Nav/Developers.tsx index 71061cd2fc8..9fdc46c12bf 100644 --- a/apps/www/components/Nav/Developers.tsx +++ b/apps/www/components/Nav/Developers.tsx @@ -60,11 +60,11 @@ const Developers = () => { 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 043/105] 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 044/105] 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 045/105] 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 046/105] 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 047/105] 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 f55bad23907bea9a93a8793dd85f343aea915c1a Mon Sep 17 00:00:00 2001 From: Pamela Chia Date: Mon, 11 Sep 2023 16:43:45 +0800 Subject: [PATCH 048/105] feat/add security page to footer --- apps/www/data/Footer.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/www/data/Footer.json b/apps/www/data/Footer.json index cf4f5de392d..f05fe79ce6c 100644 --- a/apps/www/data/Footer.json +++ b/apps/www/data/Footer.json @@ -63,6 +63,10 @@ "text": "Brand Assets / Logos", "url": "/brand-assets" }, + { + "text": "Security and Compliance", + "url": "/security" + }, { "text": "DPA", "url": "/legal/dpa" From ce34b229eb6f4ea80beca8ba7308ca052a676168 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Mon, 11 Sep 2023 16:55:30 +0800 Subject: [PATCH 049/105] Use custom domain if active in API docs --- .../interfaces/Docs/ResourceContent.tsx | 42 ++++++---- .../TableGridEditor/APIDocumentationPanel.tsx | 7 +- .../Docs/Pages/UserManagement.js | 12 ++- .../Docs/{Snippets.js => Snippets.ts} | 84 ++++++++++++------- .../StorageExplorer/StorageExplorer.tsx | 5 +- .../custom-domains/custom-domains-query.ts | 9 +- 6 files changed, 95 insertions(+), 64 deletions(-) rename studio/components/to-be-cleaned/Docs/{Snippets.js => Snippets.ts} (86%) diff --git a/studio/components/interfaces/Docs/ResourceContent.tsx b/studio/components/interfaces/Docs/ResourceContent.tsx index 80388c6e5e2..83210e50b2e 100644 --- a/studio/components/interfaces/Docs/ResourceContent.tsx +++ b/studio/components/interfaces/Docs/ResourceContent.tsx @@ -1,9 +1,12 @@ -import Snippets from 'components/to-be-cleaned/Docs/Snippets' -import CodeSnippet from 'components/to-be-cleaned/Docs/CodeSnippet' -import Param from 'components/to-be-cleaned/Docs/Param' -import Description from 'components/to-be-cleaned/Docs/Description' +import { useParams } from 'common' import { IconTable } from 'ui' +import CodeSnippet from 'components/to-be-cleaned/Docs/CodeSnippet' +import Description from 'components/to-be-cleaned/Docs/Description' +import Param from 'components/to-be-cleaned/Docs/Param' +import Snippets from 'components/to-be-cleaned/Docs/Snippets' +import { useCustomDomainsQuery } from 'data/custom-domains/custom-domains-query' + const ResourceContent = ({ autoApiService, resourceId, @@ -14,7 +17,12 @@ const ResourceContent = ({ showApiKey, refreshDocs, }: any) => { - if (!paths || !definitions) return null + const { ref } = useParams() + const { data: customDomainData } = useCustomDomainsQuery({ projectRef: ref }) + const endpoint = + customDomainData?.customDomain?.status === 'active' + ? `https://${customDomainData.customDomain.hostname}` + : autoApiService.endpoint const keyToShow = !!showApiKey ? showApiKey : 'SUPABASE_KEY' const resourcePaths = paths[`/${resourceId}`] @@ -28,6 +36,8 @@ const ResourceContent = ({ required: resourceDefinition?.required?.includes(id), })) + if (!paths || !definitions) return null + return ( <>

    @@ -75,7 +85,7 @@ const ResourceContent = ({ snippet={Snippets.readColumns({ title: `Select ${x.id}`, resourceId, - endpoint: autoApiService.endpoint, + endpoint: endpoint, apiKey: keyToShow, columnName: x.id, })} @@ -106,23 +116,23 @@ const ResourceContent = ({
    @@ -143,7 +153,7 @@ const ResourceContent = ({
    @@ -174,15 +184,15 @@ const ResourceContent = ({
    @@ -214,7 +224,7 @@ const ResourceContent = ({
    @@ -242,7 +252,7 @@ const ResourceContent = ({
    diff --git a/studio/components/interfaces/TableGridEditor/APIDocumentationPanel.tsx b/studio/components/interfaces/TableGridEditor/APIDocumentationPanel.tsx index d03907ffbef..abedb7240cb 100644 --- a/studio/components/interfaces/TableGridEditor/APIDocumentationPanel.tsx +++ b/studio/components/interfaces/TableGridEditor/APIDocumentationPanel.tsx @@ -22,6 +22,8 @@ const APIDocumentationPanel = ({ visible, onClose }: APIDocumentationPanelProps) const { data: jsonSchema, error: jsonSchemaError, + isError: isErrorJSONSchema, + isSuccess: isSuccessJSONSchema, refetch, } = useProjectJsonSchemaQuery({ projectRef: ref }) const { data: settings } = useProjectApiQuery({ projectRef: ref }) @@ -84,14 +86,15 @@ const APIDocumentationPanel = ({ visible, onClose }: APIDocumentationPanelProps) >
    - {jsonSchemaError ? ( + {isErrorJSONSchema && (

    Error connecting to API

    {`${jsonSchemaError}`}

    - ) : ( + )} + {isSuccessJSONSchema && ( <> {jsonSchema ? ( <> diff --git a/studio/components/to-be-cleaned/Docs/Pages/UserManagement.js b/studio/components/to-be-cleaned/Docs/Pages/UserManagement.js index 8d01d5d56aa..83a82df7c94 100644 --- a/studio/components/to-be-cleaned/Docs/Pages/UserManagement.js +++ b/studio/components/to-be-cleaned/Docs/Pages/UserManagement.js @@ -1,8 +1,9 @@ import Link from 'next/link' -import Snippets from '../Snippets' -import CodeSnippet from '../CodeSnippet' import { useRouter } from 'next/router' + import { makeRandomString } from 'lib/helpers' +import CodeSnippet from '../CodeSnippet' +import Snippets from '../Snippets' const randomPassword = makeRandomString(20) @@ -212,10 +213,7 @@ export default function UserManagement({ autoApiService, selectedLang, showApiKe

    - +
    @@ -275,7 +273,7 @@ export default function UserManagement({ autoApiService, selectedLang, showApiKe
    diff --git a/studio/components/to-be-cleaned/Docs/Snippets.js b/studio/components/to-be-cleaned/Docs/Snippets.ts similarity index 86% rename from studio/components/to-be-cleaned/Docs/Snippets.js rename to studio/components/to-be-cleaned/Docs/Snippets.ts index 98d9b8eb026..44aa501ec92 100644 --- a/studio/components/to-be-cleaned/Docs/Snippets.js +++ b/studio/components/to-be-cleaned/Docs/Snippets.ts @@ -1,5 +1,5 @@ const snippets = { - endpoint: (endpoint) => ({ + endpoint: (endpoint: string) => ({ title: 'API URL', bash: { language: 'bash', @@ -18,7 +18,7 @@ const snippets = { code: `npm install --save @supabase/supabase-js`, }, }), - init: (endpoint) => ({ + init: (endpoint: string) => ({ title: 'Initializing', bash: { language: 'bash', @@ -56,7 +56,7 @@ Future main() async { }`, }, }), - authKey: (title, varName, apikey) => ({ + authKey: (title: string, varName: string, apikey: string) => ({ title: `${title}`, bash: { language: 'bash', @@ -67,7 +67,11 @@ Future main() async { code: `const ${varName} = '${apikey}'`, }, }), - authKeyExample: (defaultApiKey, endpoint, { keyName, showBearer = true }) => ({ + authKeyExample: ( + defaultApiKey: string, + endpoint: string, + { keyName, showBearer = true }: { keyName: string; showBearer: boolean } + ) => ({ title: 'Example usage', bash: { language: 'bash', @@ -90,7 +94,19 @@ const supabase = createClient(SUPABASE_URL, process.env.${keyName || 'SUPABASE_K `, }, }), - rpcSingle: ({ rpcName, rpcParams, endpoint, apiKey, showBearer = true }) => { + rpcSingle: ({ + rpcName, + rpcParams, + endpoint, + apiKey, + showBearer = true, + }: { + rpcName: string + rpcParams: any[] + endpoint: string + apiKey: string + showBearer: boolean + }) => { let rpcList = rpcParams.map((x) => `"${x.name}": "value"`).join(', ') let noParams = !rpcParams.length let bashParams = noParams ? '' : `\n-d '{ ${rpcList} }' \\` @@ -131,7 +147,7 @@ else console.log(data) }, } }, - subscribeAll: (listenerName, resourceId) => ({ + subscribeAll: (listenerName: string, resourceId: string) => ({ title: 'Subscribe to all events', bash: { language: 'bash', @@ -151,7 +167,7 @@ const ${listenerName} = supabase.channel('custom-all-channel') .subscribe()`, }, }), - subscribeInserts: (listenerName, resourceId) => ({ + subscribeInserts: (listenerName: string, resourceId: string) => ({ title: 'Subscribe to inserts', bash: { language: 'bash', @@ -171,7 +187,7 @@ const ${listenerName} = supabase.channel('custom-insert-channel') .subscribe()`, }, }), - subscribeUpdates: (listenerName, resourceId) => ({ + subscribeUpdates: (listenerName: string, resourceId: string) => ({ title: 'Subscribe to updates', bash: { language: 'bash', @@ -191,7 +207,7 @@ const ${listenerName} = supabase.channel('custom-update-channel') .subscribe()`, }, }), - subscribeDeletes: (listenerName, resourceId) => ({ + subscribeDeletes: (listenerName: string, resourceId: string) => ({ title: 'Subscribe to deletes', bash: { language: 'bash', @@ -211,7 +227,7 @@ const ${listenerName} = supabase.channel('custom-delete-channel') .subscribe()`, }, }), - subscribeEq: (listenerName, resourceId, columnName, value) => ({ + subscribeEq: (listenerName: string, resourceId: string, columnName: string, value: string) => ({ title: 'Subscribe to specific rows', bash: { language: 'bash', @@ -231,7 +247,7 @@ const ${listenerName} = supabase.channel('custom-filter-channel') .subscribe()`, }, }), - readAll: (resourceId, endpoint, apiKey) => ({ + readAll: (resourceId: string, endpoint: string, apiKey: string) => ({ title: 'Read all rows', bash: { language: 'bash', @@ -256,6 +272,12 @@ let { data: ${resourceId}, error } = await supabase endpoint, apiKey, columnName = 'some_column,other_column', + }: { + title?: string + resourceId: string + endpoint: string + apiKey: string + columnName?: string }) => ({ title, bash: { @@ -275,7 +297,7 @@ let { data: ${resourceId}, error } = await supabase `, }, }), - readForeignTables: (resourceId, endpoint, apiKey) => ({ + readForeignTables: (resourceId: string, endpoint: string, apiKey: string) => ({ title: 'Read foreign tables', bash: { language: 'bash', @@ -299,7 +321,7 @@ let { data: ${resourceId}, error } = await supabase `, }, }), - readRange: (resourceId, endpoint, apiKey) => ({ + readRange: (resourceId: string, endpoint: string, apiKey: string) => ({ title: 'With pagination', bash: { language: 'bash', @@ -320,7 +342,7 @@ let { data: ${resourceId}, error } = await supabase `, }, }), - readFilters: (resourceId, endpoint, apiKey) => ({ + readFilters: (resourceId: string, endpoint: string, apiKey: string) => ({ title: 'With filtering', bash: { language: 'bash', @@ -357,7 +379,7 @@ let { data: ${resourceId}, error } = await supabase `, }, }), - insertSingle: (resourceId, endpoint, apiKey) => ({ + insertSingle: (resourceId: string, endpoint: string, apiKey: string) => ({ title: 'Insert a row', bash: { language: 'bash', @@ -382,7 +404,7 @@ const { data, error } = await supabase `, }, }), - insertMany: (resourceId, endpoint, apiKey) => ({ + insertMany: (resourceId: string, endpoint: string, apiKey: string) => ({ title: 'Insert many rows', bash: { language: 'bash', @@ -407,7 +429,7 @@ const { data, error } = await supabase `, }, }), - upsert: (resourceId, endpoint, apiKey) => ({ + upsert: (resourceId: string, endpoint: string, apiKey: string) => ({ title: 'Upsert matching rows', bash: { language: 'bash', @@ -430,7 +452,7 @@ const { data, error } = await supabase `, }, }), - update: (resourceId, endpoint, apiKey) => ({ + update: (resourceId: string, endpoint: string, apiKey: string) => ({ title: 'Update matching rows', bash: { language: 'bash', @@ -454,7 +476,7 @@ const { data, error } = await supabase `, }, }), - delete: (resourceId, endpoint, apiKey) => ({ + delete: (resourceId: string, endpoint: string, apiKey: string) => ({ title: 'Delete matching rows', bash: { language: 'bash', @@ -474,7 +496,7 @@ const { error } = await supabase `, }, }), - authSignup: (endpoint, apiKey, randomPassword) => ({ + authSignup: (endpoint: string, apiKey: string, randomPassword: string) => ({ title: 'User signup', bash: { language: 'bash', @@ -498,7 +520,7 @@ let { data, error } = await supabase.auth.signUp({ `, }, }), - authLogin: (endpoint, apiKey, randomPassword) => ({ + authLogin: (endpoint: string, apiKey: string, randomPassword: string) => ({ title: 'User login', bash: { language: 'bash', @@ -522,7 +544,7 @@ let { data, error } = await supabase.auth.signInWithPassword({ `, }, }), - authMagicLink: (endpoint, apiKey) => ({ + authMagicLink: (endpoint: string, apiKey: string) => ({ title: 'User login', bash: { language: 'bash', @@ -544,7 +566,7 @@ let { data, error } = await supabase.auth.signInWithOtp({ `, }, }), - authPhoneSignUp: (endpoint, apiKey) => ({ + authPhoneSignUp: (endpoint: string, apiKey: string) => ({ title: 'Phone Signup', bash: { language: 'bash', @@ -568,7 +590,7 @@ let { data, error } = await supabase.auth.signUp({ `, }, }), - authMobileOTPLogin: (endpoint, apiKey) => ({ + authMobileOTPLogin: (endpoint: string, apiKey: string) => ({ title: 'Phone Login', bash: { language: 'bash', @@ -590,7 +612,7 @@ let { data, error } = await supabase.auth.signInWithOtp({ `, }, }), - authMobileOTPVerify: (endpoint, apiKey) => ({ + authMobileOTPVerify: (endpoint: string, apiKey: string) => ({ title: 'Verify Pin', bash: { language: 'bash', @@ -616,7 +638,7 @@ let { data, error } = await supabase.auth.verifyOtp({ `, }, }), - authInvite: (endpoint, apiKey) => ({ + authInvite: (endpoint: string, apiKey: string) => ({ title: 'Invite User', bash: { language: 'bash', @@ -637,7 +659,7 @@ let { data, error } = await supabase.auth.api.inviteUserByEmail('someone@email.c `, }, }), - authThirdPartyLogin: (endpoint, apiKey) => ({ + authThirdPartyLogin: () => ({ title: 'Third Party Login', bash: { language: 'bash', @@ -652,7 +674,7 @@ let { data, error } = await supabase.auth.signInWithOAuth({ `, }, }), - authUser: (endpoint, apiKey) => ({ + authUser: (endpoint: string, apiKey: string) => ({ title: 'Get User', bash: { language: 'bash', @@ -669,7 +691,7 @@ const { data: { user } } = await supabase.auth.getUser() `, }, }), - authRecover: (endpoint, apiKey) => ({ + authRecover: (endpoint: string, apiKey: string) => ({ title: 'Password Recovery', bash: { language: 'bash', @@ -689,7 +711,7 @@ let { data, error } = await supabase.auth.resetPasswordForEmail(email) `, }, }), - authUpdate: (endpoint, apiKey) => ({ + authUpdate: (endpoint: string, apiKey: string) => ({ title: 'Update User', bash: { language: 'bash', @@ -718,7 +740,7 @@ const { data, error } = await supabase.auth.updateUser({ `, }, }), - authLogout: (endpoint, apiKey, randomPassword) => ({ + authLogout: (endpoint: string, apiKey: string) => ({ title: 'User logout', bash: { language: 'bash', diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/StorageExplorer.tsx b/studio/components/to-be-cleaned/Storage/StorageExplorer/StorageExplorer.tsx index 77de7810008..5dee9047a6e 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/StorageExplorer.tsx +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/StorageExplorer.tsx @@ -57,10 +57,7 @@ const StorageExplorer = ({ bucket }: StorageExplorerProps) => { const { ui } = useStore() const { ref } = useParams() - const { data: customDomainData } = useCustomDomainsQuery( - { projectRef: ref }, - { enabled: IS_PLATFORM } - ) + const { data: customDomainData } = useCustomDomainsQuery({ projectRef: ref }) const { data: projectSettings } = useProjectSettingsQuery({ projectRef: ref }) const apiService = (projectSettings?.services ?? []).find( (x) => x.app.id == DEFAULT_PROJECT_API_SERVICE_ID diff --git a/studio/data/custom-domains/custom-domains-query.ts b/studio/data/custom-domains/custom-domains-query.ts index 5bd0bcd6fda..1bcaf877586 100644 --- a/studio/data/custom-domains/custom-domains-query.ts +++ b/studio/data/custom-domains/custom-domains-query.ts @@ -1,7 +1,8 @@ -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 { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query' + +import { get } from 'lib/common/fetch' +import { API_ADMIN_URL, IS_PLATFORM } from 'lib/constants' import { customDomainKeys } from './keys' export type CustomDomainsVariables = { @@ -132,7 +133,7 @@ export const useCustomDomainsQuery = ( useQuery( customDomainKeys.list(projectRef), ({ signal }) => getCustomDomains({ projectRef }, signal), - { enabled: enabled && typeof projectRef !== 'undefined', ...options } + { enabled: enabled && IS_PLATFORM && typeof projectRef !== 'undefined', ...options } ) export const useCustomDomainsPrefetch = ({ projectRef }: CustomDomainsVariables) => { From 4b7ea6eb8c6ac01ba7f5e46363575ae3c0214d8b Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Mon, 11 Sep 2023 17:13:11 +0800 Subject: [PATCH 050/105] 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 509a79d19b026f6ff0e373e881b3ccf8f0ad30b3 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Mon, 11 Sep 2023 17:21:09 +0800 Subject: [PATCH 051/105] Improve empty state by adding import CSV cta for empty table in table editor --- studio/components/grid/SupabaseGrid.tsx | 1 + .../components/grid/components/grid/Grid.tsx | 53 +++++++++++++------ .../grid/components/header/Header.tsx | 10 ++-- studio/pages/project/[ref]/editor/[id].tsx | 27 +++++----- 4 files changed, 59 insertions(+), 32 deletions(-) diff --git a/studio/components/grid/SupabaseGrid.tsx b/studio/components/grid/SupabaseGrid.tsx index 373ee642756..888988ce60d 100644 --- a/studio/components/grid/SupabaseGrid.tsx +++ b/studio/components/grid/SupabaseGrid.tsx @@ -187,6 +187,7 @@ const SupabaseGridLayout = forwardRef((props {...gridProps} rows={data?.rows ?? []} updateRow={updateTableRow} + onImportData={onImportData} onEditForeignKeyColumnValue={onEditForeignKeyColumnValue} />
    diff --git a/studio/components/grid/components/grid/Grid.tsx b/studio/components/grid/components/grid/Grid.tsx index c88d3143e0d..a64a6d0ee1b 100644 --- a/studio/components/grid/components/grid/Grid.tsx +++ b/studio/components/grid/components/grid/Grid.tsx @@ -1,22 +1,34 @@ +/* eslint-disable react/display-name */ + +import DataGrid, { DataGridHandle, RowsChangeData } from '@supabase/react-data-grid' +import AwesomeDebouncePromise from 'awesome-debounce-promise' import { forwardRef } from 'react' import { memo } from 'react-tracked' -import DataGrid, { DataGridHandle, RowsChangeData } from '@supabase/react-data-grid' -import { IconLoader } from 'ui' -import { GridProps, SupaRow } from '../../types' -import { useDispatch, useTrackedState } from '../../store' -import RowRenderer from './RowRenderer' -import AwesomeDebouncePromise from 'awesome-debounce-promise' + import { ForeignRowSelectorProps } from 'components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import { useForeignKeyConstraintsQuery } from 'data/database/foreign-key-constraints-query' +import { useDispatch, useTrackedState } from '../../store' +import { GridProps, SupaRow } from '../../types' +import RowRenderer from './RowRenderer' +import { Button } from 'ui' -function rowKeyGetter(row: SupaRow) { +const rowKeyGetter = (row: SupaRow) => { return row?.idx ?? -1 } +const updateColumnResize = (index: number, width: number, dispatch: (value: unknown) => void) => { + dispatch({ + type: 'UPDATE_COLUMN_SIZE', + payload: { index, width: Math.round(width) }, + }) +} +const updateColumnResizeDebounced = AwesomeDebouncePromise(updateColumnResize, 500) + interface IGrid extends GridProps { rows: any[] updateRow: (previousRow: any, updatedData: any) => void + onImportData?: () => void onEditForeignKeyColumnValue: (args: { foreignKey: NonNullable row: any @@ -36,6 +48,7 @@ export const Grid = memo( rowClass, rows, updateRow, + onImportData, onEditForeignKeyColumnValue, }, ref: React.Ref | undefined @@ -126,17 +139,27 @@ export const Grid = memo( className={gridClass} rowClass={rowClass} style={{ height: '100%' }} + noRowsFallback={ +
    +

    This table is empty

    +

    Add or generate rows to get started.

    +
    + {/* [Joshen] Leaving this as a placeholder */} + {/* */} + {onImportData !== undefined && ( + + )} +
    +
    + } />
    ) } ) ) - -const updateColumnResize = (index: number, width: number, dispatch: (value: unknown) => void) => { - dispatch({ - type: 'UPDATE_COLUMN_SIZE', - payload: { index, width: Math.round(width) }, - }) -} -const updateColumnResizeDebounced = AwesomeDebouncePromise(updateColumnResize, 500) diff --git a/studio/components/grid/components/header/Header.tsx b/studio/components/grid/components/header/Header.tsx index 029dd615bda..758cb072500 100644 --- a/studio/components/grid/components/header/Header.tsx +++ b/studio/components/grid/components/header/Header.tsx @@ -150,7 +150,9 @@ const DefaultHeader = ({ >

    Insert row

    -

    Insert a new row into {table.name}

    +

    + Insert a new row into {table.name} +

    , ] @@ -176,7 +178,9 @@ const DefaultHeader = ({ >

    Insert column

    -

    Insert a new column into {table.name}

    +

    + Insert a new column into {table.name} +

    , ] @@ -203,7 +207,7 @@ const DefaultHeader = ({ >

    Import data from CSV

    -

    Insert new rows from a CSV

    +

    Insert new rows from a CSV

    , ] diff --git a/studio/pages/project/[ref]/editor/[id].tsx b/studio/pages/project/[ref]/editor/[id].tsx index 863f1dda3c8..9cf12e1ac2b 100644 --- a/studio/pages/project/[ref]/editor/[id].tsx +++ b/studio/pages/project/[ref]/editor/[id].tsx @@ -1,29 +1,28 @@ +import type { PostgresColumn, PostgresTable } from '@supabase/postgres-meta' import { useQueryClient } from '@tanstack/react-query' -import { useEffect, useState } from 'react' +import { useParams, useTheme } from 'common' +import { isNaN, isUndefined } from 'lodash' +import { observer } from 'mobx-react-lite' import Link from 'next/link' import { useRouter } from 'next/router' -import { observer } from 'mobx-react-lite' -import { isUndefined, isNaN } from 'lodash' +import { useEffect, useState } from 'react' import { Alert, Button, Checkbox, IconExternalLink, Modal } from 'ui' -import type { PostgresTable, PostgresColumn } from '@supabase/postgres-meta' -import { useStore, withAuth, useUrlState } from 'hooks' -import { useParams } from 'common/hooks' -import { entityTypeKeys } from 'data/entity-types/keys' -import { Entity } from 'data/entity-types/entity-type-query' -import { sqlKeys } from 'data/sql/keys' import { Dictionary } from 'components/grid' -import { TableEditorLayout } from 'components/layouts' import { TableGridEditor } from 'components/interfaces' -import ConfirmationModal from 'components/ui/ConfirmationModal' -import { NextPageWithLayout, SchemaView } from 'types' +import { ForeignRowSelectorProps } from 'components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector' import { JsonEditValue } from 'components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.types' +import { TableEditorLayout } from 'components/layouts' import { ProjectContextFromParamsProvider, useProjectContext, } from 'components/layouts/ProjectLayout/ProjectContext' -import { ForeignRowSelectorProps } from 'components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector' -import { useTheme } from 'common' +import ConfirmationModal from 'components/ui/ConfirmationModal' +import { Entity } from 'data/entity-types/entity-type-query' +import { entityTypeKeys } from 'data/entity-types/keys' +import { sqlKeys } from 'data/sql/keys' +import { useStore, useUrlState, withAuth } from 'hooks' +import { NextPageWithLayout, SchemaView } from 'types' const TableEditorPage: NextPageWithLayout = () => { const router = useRouter() From 5817e8bd8b7491bf258a554c4c473414d42bfd46 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Mon, 11 Sep 2023 17:52:28 +0800 Subject: [PATCH 052/105] Only show CTA if table is not protected --- studio/components/grid/SupabaseGrid.tsx | 1 + .../components/grid/components/grid/Grid.tsx | 26 ++++++++++++------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/studio/components/grid/SupabaseGrid.tsx b/studio/components/grid/SupabaseGrid.tsx index 888988ce60d..0cd01351b10 100644 --- a/studio/components/grid/SupabaseGrid.tsx +++ b/studio/components/grid/SupabaseGrid.tsx @@ -187,6 +187,7 @@ const SupabaseGridLayout = forwardRef((props {...gridProps} rows={data?.rows ?? []} updateRow={updateTableRow} + onAddRow={onAddRow} onImportData={onImportData} onEditForeignKeyColumnValue={onEditForeignKeyColumnValue} /> diff --git a/studio/components/grid/components/grid/Grid.tsx b/studio/components/grid/components/grid/Grid.tsx index a64a6d0ee1b..3d28e84c4ad 100644 --- a/studio/components/grid/components/grid/Grid.tsx +++ b/studio/components/grid/components/grid/Grid.tsx @@ -28,6 +28,7 @@ const updateColumnResizeDebounced = AwesomeDebouncePromise(updateColumnResize, 5 interface IGrid extends GridProps { rows: any[] updateRow: (previousRow: any, updatedData: any) => void + onAddRow?: () => void onImportData?: () => void onEditForeignKeyColumnValue: (args: { foreignKey: NonNullable @@ -48,6 +49,7 @@ export const Grid = memo( rowClass, rows, updateRow, + onAddRow, onImportData, onEditForeignKeyColumnValue, }, @@ -145,16 +147,20 @@ export const Grid = memo( className="flex flex-col items-center justify-center" >

    This table is empty

    -

    Add or generate rows to get started.

    -
    - {/* [Joshen] Leaving this as a placeholder */} - {/* */} - {onImportData !== undefined && ( - - )} -
    + {onAddRow !== undefined && onImportData !== undefined && ( + <> +

    Add or generate rows to get started.

    +
    + {/* [Joshen] Leaving this as a placeholder */} + {/* */} + {onAddRow !== undefined && onImportData !== undefined && ( + + )} +
    + + )} } /> From e53d93c4dcb1622781af864602427355fc93c0ea Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Mon, 11 Sep 2023 18:32:06 +0800 Subject: [PATCH 053/105] 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 054/105] 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 83d4b2bc305f68854112a16e8c1606894df3f0d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Forna=C3=A7on?= Date: Mon, 11 Sep 2023 13:26:29 +0200 Subject: [PATCH 055/105] Update supabase_dart_v1.yml to fix typo in upsert with constraints --- spec/supabase_dart_v1.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/supabase_dart_v1.yml b/spec/supabase_dart_v1.yml index b9c9eebb313..8b5cdf5c014 100644 --- a/spec/supabase_dart_v1.yml +++ b/spec/supabase_dart_v1.yml @@ -992,7 +992,7 @@ functions: ```dart await supabase .from('users') - .upsert({ 'username': 'supabot' }, { 'onConflict': 'username' }); + .upsert({ 'username': 'supabot' }, onConflict: 'username'); ``` - id: return-the-exact-number-of-rows name: Return the exact number of rows From 34d9e3fe1fd23a5bec74aaada01dc5040dfd649f Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Mon, 11 Sep 2023 09:48:43 -0230 Subject: [PATCH 056/105] Update heading --- studio/components/interfaces/Functions/TerminalInstructions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/components/interfaces/Functions/TerminalInstructions.tsx b/studio/components/interfaces/Functions/TerminalInstructions.tsx index 87eecf571df..a5b4f58b3f1 100644 --- a/studio/components/interfaces/Functions/TerminalInstructions.tsx +++ b/studio/components/interfaces/Functions/TerminalInstructions.tsx @@ -93,7 +93,7 @@ const TerminalInstructions = ({
    -

    Terminal instructions

    +

    Create your first Edge Function via the CLI

    {closable && (
    setShowInstructions(!showInstructions)}> From c37a4a7f41cad2031c5722ba82f142ce5ac42ae8 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Mon, 11 Sep 2023 10:00:38 -0230 Subject: [PATCH 057/105] 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 058/105] 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 059/105] 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 060/105] 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 061/105] 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 062/105] 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 117220cafabbfc1b4e65b17cd2d8944c8a8193a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Mon, 11 Sep 2023 16:22:38 +0200 Subject: [PATCH 063/105] chore: add faq about project addons --- apps/docs/pages/guides/platform/org-based-billing.mdx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/docs/pages/guides/platform/org-based-billing.mdx b/apps/docs/pages/guides/platform/org-based-billing.mdx index deca224bf12..7d9a737a9ab 100644 --- a/apps/docs/pages/guides/platform/org-based-billing.mdx +++ b/apps/docs/pages/guides/platform/org-based-billing.mdx @@ -315,6 +315,15 @@ If you head over to your [organizations' billing settings](https://supabase.com/ infrastructure. +
    + + Where do I change my project addons such as PITR, Compute and Custom Domain? + + +Head over to your [projects add ons page](https://supabase.com/dashboard/project/_/settings/addons) to change your compute size, Point-In-Time-Recovery or custom domain. + +
    +
    I have additional questions/concerns, how do I get help? From 9164f5e0356a80da6d665287210263e06ac1c2bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Mon, 11 Sep 2023 16:53:07 +0200 Subject: [PATCH 064/105] Update apps/docs/pages/guides/platform/org-based-billing.mdx Co-authored-by: Francesco Sansalvadore --- apps/docs/pages/guides/platform/org-based-billing.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/pages/guides/platform/org-based-billing.mdx b/apps/docs/pages/guides/platform/org-based-billing.mdx index 7d9a737a9ab..8471a114642 100644 --- a/apps/docs/pages/guides/platform/org-based-billing.mdx +++ b/apps/docs/pages/guides/platform/org-based-billing.mdx @@ -317,7 +317,7 @@ If you head over to your [organizations' billing settings](https://supabase.com/
    - Where do I change my project addons such as PITR, Compute and Custom Domain? + Where do I change my project add-ons such as PITR, Compute and Custom Domain? Head over to your [projects add ons page](https://supabase.com/dashboard/project/_/settings/addons) to change your compute size, Point-In-Time-Recovery or custom domain. From 207bfc61507f8e9ea1196623b7fa4a452c088157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Mon, 11 Sep 2023 16:53:12 +0200 Subject: [PATCH 065/105] Update apps/docs/pages/guides/platform/org-based-billing.mdx Co-authored-by: Francesco Sansalvadore --- apps/docs/pages/guides/platform/org-based-billing.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/pages/guides/platform/org-based-billing.mdx b/apps/docs/pages/guides/platform/org-based-billing.mdx index 8471a114642..1f9b521f539 100644 --- a/apps/docs/pages/guides/platform/org-based-billing.mdx +++ b/apps/docs/pages/guides/platform/org-based-billing.mdx @@ -320,7 +320,7 @@ If you head over to your [organizations' billing settings](https://supabase.com/ Where do I change my project add-ons such as PITR, Compute and Custom Domain?
    -Head over to your [projects add ons page](https://supabase.com/dashboard/project/_/settings/addons) to change your compute size, Point-In-Time-Recovery or custom domain. +Head over to your project [Add-ons page](https://supabase.com/dashboard/project/_/settings/addons) to change your compute size, Point-In-Time-Recovery or custom domain.
    From c87e0714f4484bfe07b58b91c9510074c5302825 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Mon, 11 Sep 2023 12:38:45 -0230 Subject: [PATCH 066/105] Pass project ref --- .../interfaces/Settings/Database/ConnectionPooling.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/studio/components/interfaces/Settings/Database/ConnectionPooling.tsx b/studio/components/interfaces/Settings/Database/ConnectionPooling.tsx index f3ae3ed6099..5661cd4a781 100644 --- a/studio/components/interfaces/Settings/Database/ConnectionPooling.tsx +++ b/studio/components/interfaces/Settings/Database/ConnectionPooling.tsx @@ -140,7 +140,12 @@ export const PgbouncerConfig = ({ projectRef, bouncerInfo, connectionInfo }: Con const canUpdateConnectionPoolingConfiguration = useCheckPermissions( PermissionAction.UPDATE, - 'projects' + 'projects', + { + resource: { + project_id: projectRef, + }, + } ) const updateConfig = async (updatedConfig: any) => { From 2c156f11b32be7468a5c6b1ef074acf65e201a46 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Mon, 11 Sep 2023 14:13:27 -0230 Subject: [PATCH 067/105] Swap ref for project id --- .../interfaces/Settings/Database/ConnectionPooling.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/studio/components/interfaces/Settings/Database/ConnectionPooling.tsx b/studio/components/interfaces/Settings/Database/ConnectionPooling.tsx index 5661cd4a781..f12a0da1993 100644 --- a/studio/components/interfaces/Settings/Database/ConnectionPooling.tsx +++ b/studio/components/interfaces/Settings/Database/ConnectionPooling.tsx @@ -122,6 +122,7 @@ interface ConfigProps { export const PgbouncerConfig = ({ projectRef, bouncerInfo, connectionInfo }: ConfigProps) => { const { ui } = useStore() + const { project } = useProjectContext() const [updates, setUpdates] = useState({ pool_mode: bouncerInfo.pool_mode || 'transaction', default_pool_size: bouncerInfo.default_pool_size || undefined, @@ -143,7 +144,7 @@ export const PgbouncerConfig = ({ projectRef, bouncerInfo, connectionInfo }: Con 'projects', { resource: { - project_id: projectRef, + project_id: project?.id, }, } ) From 1d83665a9cc152aab3aa1f6eefeef663434309fc Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Mon, 11 Sep 2023 10:43:19 -0700 Subject: [PATCH 068/105] fix vector example --- examples/ai/vector_hello_world.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/ai/vector_hello_world.ipynb b/examples/ai/vector_hello_world.ipynb index 7749e2a755a..002ceb8efe7 100644 --- a/examples/ai/vector_hello_world.ipynb +++ b/examples/ai/vector_hello_world.ipynb @@ -141,7 +141,7 @@ "source": [ "# add records to the collection\n", "docs.upsert(\n", - " vectors=[\n", + " records=[\n", " (\n", " \"vec0\", # the vector's identifier\n", " [0.1, 0.2, 0.3], # the vector. list or np.array\n", @@ -273,7 +273,7 @@ ], "source": [ "docs.query(\n", - " query_vector=[0.4,0.5,0.6], # required\n", + " data=[0.4,0.5,0.6], # required\n", " limit=5, # number of records to return\n", " filters={}, # metadata filters\n", " measure=\"cosine_distance\", # distance measure to use\n", From 5e49e0ed03f4e97bbc87d2bbe25189d3bfc033b5 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Mon, 11 Sep 2023 16:15:19 -0230 Subject: [PATCH 069/105] Fix mime type refs --- studio/components/interfaces/Storage/CreateBucketModal.tsx | 2 +- studio/components/interfaces/Storage/EditBucketModal.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/studio/components/interfaces/Storage/CreateBucketModal.tsx b/studio/components/interfaces/Storage/CreateBucketModal.tsx index 6a2129661ea..8c002cdd778 100644 --- a/studio/components/interfaces/Storage/CreateBucketModal.tsx +++ b/studio/components/interfaces/Storage/CreateBucketModal.tsx @@ -228,7 +228,7 @@ const CreateBucketModal = ({ visible, onClose }: CreateBucketModalProps) => { name="allowed_mime_types" layout="vertical" label="Allowed MIME types" - placeholder="e.g image/jpg, image/png, audio/mpeg, video/mp4, etc" + placeholder="e.g image/jpeg, image/png, audio/mpeg, video/mp4, etc" descriptionText="Comma separated values" />
    diff --git a/studio/components/interfaces/Storage/EditBucketModal.tsx b/studio/components/interfaces/Storage/EditBucketModal.tsx index 59b691e7435..4e7e590220c 100644 --- a/studio/components/interfaces/Storage/EditBucketModal.tsx +++ b/studio/components/interfaces/Storage/EditBucketModal.tsx @@ -237,7 +237,7 @@ const EditBucketModal = ({ visible, bucket, onClose }: EditBucketModalProps) => name="allowed_mime_types" layout="vertical" label="Allowed MIME types" - placeholder="e.g image/jpg, image/png, audio/mpeg, video/mp4, etc" + placeholder="e.g image/jpeg, image/png, audio/mpeg, video/mp4, etc" labelOptional="Comma separated values" descriptionText="Leave the field blank to allow any MIME type to be uploaded" /> From dda76b794cc47b41c6db2a5d994ba0c6ae6a4848 Mon Sep 17 00:00:00 2001 From: egor-romanov Date: Mon, 11 Sep 2023 22:09:42 +0300 Subject: [PATCH 070/105] docs: add hnsw compute add-ons table --- .../guides/ai/choosing-compute-addon.mdx | 200 +++++++++++------- apps/docs/pages/guides/ai/going-to-prod.mdx | 28 +-- 2 files changed, 135 insertions(+), 93 deletions(-) diff --git a/apps/docs/pages/guides/ai/choosing-compute-addon.mdx b/apps/docs/pages/guides/ai/choosing-compute-addon.mdx index 4ab4e13e808..9d2bf0bd929 100644 --- a/apps/docs/pages/guides/ai/choosing-compute-addon.mdx +++ b/apps/docs/pages/guides/ai/choosing-compute-addon.mdx @@ -17,7 +17,122 @@ You have two options for scaling your vector workload: ## Dimensionality -The number of dimensions in your embeddings is the most important factor in choosing the right Compute Add-on. In general, the lower the dimensionality the better the performance. We've provided guidance for some of the more common embedding dimensions below. For each benchmark, we used [Vecs](https://github.com/supabase/vecs) to create a collection, upload the embeddings to a single table, and create an `inner-product` index for the embedding column. We then ran a series of queries to measure the performance of different compute add-ons: +The number of dimensions in your embeddings is the most important factor in choosing the right Compute Add-on. In general, the lower the dimensionality the better the performance. We've provided guidance for some of the more common embedding dimensions below. For each benchmark, we used [Vecs](https://github.com/supabase/vecs) to create a collection, upload the embeddings to a single table, and create both the `IVFFlat` and `HNSW` indexes for `inner-product` distance measure for the embedding column. We then ran a series of queries to measure the performance of different compute add-ons: + +## HNSW + +### 1536 Dimensions + +This benchmark uses the [dbpedia-entities-openai-1M](https://huggingface.co/datasets/KShivendu/dbpedia-entities-openai-1M) dataset, which contains 1,000,000 embeddings of text. And 224,482 embeddings from [Wikipedia articles](https://huggingface.co/datasets/Supabase/wikipedia-en-embeddings) for compute add-ons `large` and below. Each embedding is 1536 dimensions created with the [OpenAI Embeddings API](https://platform.openai.com/docs/guides/embeddings). + + + + +| Plan | Vectors | m | ef_construction | ef_search | QPS | Latency Mean | Latency p95 | RAM Usage | RAM | +| ------ | --------- | --- | --------------- | --------- | ---- | ------------ | ----------- | ------------------ | ------ | +| Free | 15,000 | 16 | 40 | 40 | 480 | 0.011 sec | 0.016 sec | 1 GB + 200 Mb Swap | 1 GB | +| Small | 50,000 | 32 | 64 | 100 | 175 | 0.031 sec | 0.051 sec | 2 GB + 200 Mb Swap | 2 GB | +| Medium | 100,000 | 32 | 64 | 100 | 240 | 0.083 sec | 0.126 sec | 4 GB | 4 GB | +| Large | 224,482 | 32 | 64 | 100 | 280 | 0.017 sec | 0.028 sec | 8 GB | 8 GB | +| XL | 500,000 | 24 | 56 | 100 | 360 | 0.055 sec | 0.135 sec | 13 GB | 16 GB | +| 2XL | 1,000,000 | 24 | 56 | 250 | 560 | 0.036 sec | 0.058 sec | 32 GB | 32 GB | +| 4XL | 1,000,000 | 24 | 56 | 250 | 950 | 0.021 sec | 0.033 sec | 39 GB | 64 GB | +| 8XL | 1,000,000 | 24 | 56 | 250 | 1650 | 0.016 sec | 0.023 sec | 40 GB | 128 GB | +| 12XL | 1,000,000 | 24 | 56 | 250 | 1900 | 0.015 sec | 0.021 sec | 38 GB | 192 GB | +| 16XL | 1,000,000 | 24 | 56 | 250 | 2200 | 0.015 sec | 0.020 sec | 40 GB | 256 GB | + +Accuracy was 0.99 for benchmarks. + +QPS can also be improved by increasing `m` and `ef_construction`. This will allow you to use a smaller value for `ef_search` and increase QPS. For example, increasing `m` to 32 and `ef_construction` to 80 for 4XL will increase QPS to 1280. + + + + + + +It is possible to upload more vectors to a single table if Memory allows it (for example, 4XL plan and higher for OpenAI embeddings). But it will affect the performance of the queries: QPS will be lower, and latency will be higher. Scaling should be almost linear, but it is recommended to benchmark your workload to find the optimal number of vectors per table and per database instance. + + + +## IVFFlat + +### 512 Dimensions + +This benchmark uses the [GloVe Reddit comments](https://nlp.stanford.edu/projects/glove/) dataset, which contains 1,623,397 embeddings of text. Each embedding is 512 dimensions. Random vectors were generated for queries. + + + + +| Plan | Vectors | Lists | QPS | Latency Mean | Latency p95 | RAM Usage | RAM | +| ------ | --------- | ----- | ---- | ------------ | ----------- | ------------------ | ------ | +| Free | 100,000 | 100 | 250 | 0.395 sec | 0.432 sec | 1 GB + 300 Mb Swap | 1 GB | +| Small | 250,000 | 250 | 440 | 0.223 sec | 0.250 sec | 2 GB + 200 Mb Swap | 2 GB | +| Medium | 500,000 | 500 | 425 | 0.116 sec | 0.143 sec | 3.7 GB | 4 GB | +| Large | 1,000,000 | 1000 | 515 | 0.096 sec | 0.116 sec | 7.5 GB | 8 GB | +| XL | 1,623,397 | 1275 | 465 | 0.212 sec | 0.272 sec | 14 GB | 16 GB | +| 2XL | 1,623,397 | 1275 | 1400 | 0.061 sec | 0.075 sec | 22 GB | 32 GB | +| 4XL | 1,623,397 | 1275 | 1800 | 0.027 sec | 0.043 sec | 20 GB | 64 GB | +| 8XL | 1,623,397 | 1275 | 2850 | 0.032 sec | 0.049 sec | 21 GB | 128 GB | +| 12XL | 1,623,397 | 1275 | 3700 | 0.020 sec | 0.036 sec | 26 GB | 192 GB | +| 16XL | 1,623,397 | 1275 | 3700 | 0.025 sec | 0.042 sec | 29 GB | 256 GB | + + + + +| Plan | Vectors | Lists | QPS | Latency Mean | Latency p95 | RAM Usage | RAM | +| ------ | --------- | ----- | --- | ------------ | ----------- | --------- | ------ | +| Free | 100,000 | 100 | - | - | - | - | 1 GB | +| Small | 250,000 | 250 | - | - | - | - | 2 GB | +| Medium | 500,000 | 500 | 75 | 0.656 sec | 0.750 sec | 3.7 GB | 4 GB | +| Large | 1,000,000 | 1000 | 102 | 0.488 sec | 0.580 sec | 7.5 GB | 8 GB | +| XL | 1,000,000 | 1000 | 188 | 0.525 sec | 0.596 sec | 14 GB | 16 GB | +| XL | 1,623,397 | 1275 | 75 | 0.679 sec | 0.798 sec | 14 GB | 16 GB | +| 2XL | 1,623,397 | 1275 | 160 | 0.314 sec | 0.384 sec | 22 GB | 32 GB | +| 4XL | 1,623,397 | 1275 | 300 | 0.083 sec | 0.113 sec | 20 GB | 64 GB | +| 8XL | 1,623,397 | 1275 | 565 | 0.105 sec | 0.141 sec | 21 GB | 128 GB | +| 12XL | 1,623,397 | 1275 | 840 | 0.093 sec | 0.124 sec | 26 GB | 192 GB | +| 16XL | 1,623,397 | 1275 | 940 | 0.084 sec | 0.108 sec | 29 GB | 256 GB | + + + + +### 960 Dimensions + +This benchmark uses the [gist-960-angular](http://corpus-texmex.irisa.fr/) dataset, which contains 1,000,000 embeddings of images. Each embedding is 960 dimensions. + + + + +| Plan | Vectors | Lists | QPS | Latency Mean | Latency p95 | RAM Usage | RAM | +| ------ | --------- | ----- | ---- | ------------ | ----------- | ------------------ | ------ | +| Free | 30,000 | 30 | 75 | 0.065 sec | 0.088 sec | 1 GB + 100 Mb Swap | 1 GB | +| Small | 100,000 | 100 | 78 | 0.064 sec | 0.092 sec | 1.8 GB | 2 GB | +| Medium | 250,000 | 250 | 58 | 0.085 sec | 0.129 sec | 3.2 GB | 4 GB | +| Large | 500,000 | 500 | 55 | 0.088 sec | 0.140 sec | 5 GB | 8 GB | +| XL | 1,000,000 | 1000 | 110 | 0.046 sec | 0.070 sec | 14 GB | 16 GB | +| 2XL | 1,000,000 | 1000 | 235 | 0.083 sec | 0.136 sec | 10 GB | 32 GB | +| 4XL | 1,000,000 | 1000 | 420 | 0.071 sec | 0.106 sec | 11 GB | 64 GB | +| 8XL | 1,000,000 | 1000 | 815 | 0.072 sec | 0.106 sec | 13 GB | 128 GB | +| 12XL | 1,000,000 | 1000 | 1150 | 0.052 sec | 0.078 sec | 15.5 GB | 192 GB | +| 16XL | 1,000,000 | 1000 | 1345 | 0.072 sec | 0.106 sec | 17.5 GB | 256 GB | + + + ### 1536 Dimensions @@ -31,7 +146,7 @@ This benchmark uses the [dbpedia-entities-openai-1M](https://huggingface.co/data > -| Plan | Vectors | Lists | RPS | Latency Mean | Latency p95 | RAM Usage | RAM | +| Plan | Vectors | Lists | QPS | Latency Mean | Latency p95 | RAM Usage | RAM | | ------ | --------- | ----- | ---- | ------------ | ----------- | ------------------ | ------ | | Free | 20,000 | 40 | 135 | 0.372 sec | 0.412 sec | 1 GB + 200 Mb Swap | 1 GB | | Small | 50,000 | 100 | 140 | 0.357 sec | 0.398 sec | 1.8 GB | 2 GB | @@ -44,12 +159,12 @@ This benchmark uses the [dbpedia-entities-openai-1M](https://huggingface.co/data | 12XL | 1,000,000 | 2000 | 1600 | 0.030 sec | 0.052 sec | 41 GB | 192 GB | | 16XL | 1,000,000 | 2000 | 1790 | 0.029 sec | 0.051 sec | 45 GB | 256 GB | -For 1,000,000 vectors 10 probes results to precision of 0.91. And for 500,000 vectors and below 10 probes results to precision in the range of 0.95 - 0.99. To increase precision, you need to increase the number of probes. +For 1,000,000 vectors 10 probes results to accuracy of 0.91. And for 500,000 vectors and below 10 probes results to accuracy in the range of 0.95 - 0.99. To increase accuracy, you need to increase the number of probes. -| Plan | Vectors | Lists | RPS | Latency Mean | Latency p95 | RAM Usage | RAM | +| Plan | Vectors | Lists | QPS | Latency Mean | Latency p95 | RAM Usage | RAM | | ------ | --------- | ----- | --- | ------------ | ----------- | --------- | ------ | | Free | 20,000 | 40 | - | - | - | - | 1 GB | | Small | 50,000 | 100 | - | - | - | - | 2 GB | @@ -62,7 +177,7 @@ For 1,000,000 vectors 10 probes results to precision of 0.91. And for 500,000 ve | 12XL | 1,000,000 | 2000 | 600 | 0.085 sec | 0.132 sec | 41 GB | 192 GB | | 16XL | 1,000,000 | 2000 | 670 | 0.081 sec | 0.129 sec | 45 GB | 256 GB | -For 1,000,000 vectors 40 probes results to precision of 0.98. Note that exact values may vary depending on the dataset and queries, we recommend to run benchmarks with your own data to get precise results. Use this table as a reference. +For 1,000,000 vectors 40 probes results to accuracy of 0.98. Note that exact values may vary depending on the dataset and queries, we recommend to run benchmarks with your own data to get precise results. Use this table as a reference. @@ -80,82 +195,9 @@ For 1,000,000 vectors 40 probes results to precision of 0.98. Note that exact va /> -### 960 Dimensions - -This benchmark uses the [gist-960-angular](http://corpus-texmex.irisa.fr/) dataset, which contains 1,000,000 embeddings of images. Each embedding is 960 dimensions. - - - - -| Plan | Vectors | Lists | RPS | Latency Mean | Latency p95 | RAM Usage | RAM | -| ------ | --------- | ----- | ---- | ------------ | ----------- | ------------------ | ------ | -| Free | 30,000 | 30 | 75 | 0.065 sec | 0.088 sec | 1 GB + 100 Mb Swap | 1 GB | -| Small | 100,000 | 100 | 78 | 0.064 sec | 0.092 sec | 1.8 GB | 2 GB | -| Medium | 250,000 | 250 | 58 | 0.085 sec | 0.129 sec | 3.2 GB | 4 GB | -| Large | 500,000 | 500 | 55 | 0.088 sec | 0.140 sec | 5 GB | 8 GB | -| XL | 1,000,000 | 1000 | 110 | 0.046 sec | 0.070 sec | 14 GB | 16 GB | -| 2XL | 1,000,000 | 1000 | 235 | 0.083 sec | 0.136 sec | 10 GB | 32 GB | -| 4XL | 1,000,000 | 1000 | 420 | 0.071 sec | 0.106 sec | 11 GB | 64 GB | -| 8XL | 1,000,000 | 1000 | 815 | 0.072 sec | 0.106 sec | 13 GB | 128 GB | -| 12XL | 1,000,000 | 1000 | 1150 | 0.052 sec | 0.078 sec | 15.5 GB | 192 GB | -| 16XL | 1,000,000 | 1000 | 1345 | 0.072 sec | 0.106 sec | 17.5 GB | 256 GB | - - - - -### 512 Dimensions - -This benchmark uses the [GloVe Reddit comments](https://nlp.stanford.edu/projects/glove/) dataset, which contains 1,623,397 embeddings of text. Each embedding is 512 dimensions. Random vectors were generated for queries. - - - - -| Plan | Vectors | Lists | RPS | Latency Mean | Latency p95 | RAM Usage | RAM | -| ------ | --------- | ----- | ---- | ------------ | ----------- | ------------------ | ------ | -| Free | 100,000 | 100 | 250 | 0.395 sec | 0.432 sec | 1 GB + 300 Mb Swap | 1 GB | -| Small | 250,000 | 250 | 440 | 0.223 sec | 0.250 sec | 2 GB + 200 Mb Swap | 2 GB | -| Medium | 500,000 | 500 | 425 | 0.116 sec | 0.143 sec | 3.7 GB | 4 GB | -| Large | 1,000,000 | 1000 | 515 | 0.096 sec | 0.116 sec | 7.5 GB | 8 GB | -| XL | 1,623,397 | 1275 | 465 | 0.212 sec | 0.272 sec | 14 GB | 16 GB | -| 2XL | 1,623,397 | 1275 | 1400 | 0.061 sec | 0.075 sec | 22 GB | 32 GB | -| 4XL | 1,623,397 | 1275 | 1800 | 0.027 sec | 0.043 sec | 20 GB | 64 GB | -| 8XL | 1,623,397 | 1275 | 2850 | 0.032 sec | 0.049 sec | 21 GB | 128 GB | -| 12XL | 1,623,397 | 1275 | 3700 | 0.020 sec | 0.036 sec | 26 GB | 192 GB | -| 16XL | 1,623,397 | 1275 | 3700 | 0.025 sec | 0.042 sec | 29 GB | 256 GB | - - - - -| Plan | Vectors | Lists | RPS | Latency Mean | Latency p95 | RAM Usage | RAM | -| ------ | --------- | ----- | --- | ------------ | ----------- | --------- | ------ | -| Free | 100,000 | 100 | - | - | - | - | 1 GB | -| Small | 250,000 | 250 | - | - | - | - | 2 GB | -| Medium | 500,000 | 500 | 75 | 0.656 sec | 0.750 sec | 3.7 GB | 4 GB | -| Large | 1,000,000 | 1000 | 102 | 0.488 sec | 0.580 sec | 7.5 GB | 8 GB | -| XL | 1,000,000 | 1000 | 188 | 0.525 sec | 0.596 sec | 14 GB | 16 GB | -| XL | 1,623,397 | 1275 | 75 | 0.679 sec | 0.798 sec | 14 GB | 16 GB | -| 2XL | 1,623,397 | 1275 | 160 | 0.314 sec | 0.384 sec | 22 GB | 32 GB | -| 4XL | 1,623,397 | 1275 | 300 | 0.083 sec | 0.113 sec | 20 GB | 64 GB | -| 8XL | 1,623,397 | 1275 | 565 | 0.105 sec | 0.141 sec | 21 GB | 128 GB | -| 12XL | 1,623,397 | 1275 | 840 | 0.093 sec | 0.124 sec | 26 GB | 192 GB | -| 16XL | 1,623,397 | 1275 | 940 | 0.084 sec | 0.108 sec | 29 GB | 256 GB | - - - - -It is possible to upload more vectors to a single table if Memory allows it (for example, 4XL plan and higher for OpenAI embeddings). But it will affect the performance of the queries: RPS will be lower, and latency will be higher. Scaling should be almost linear, but it is recommended to benchmark your workload to find the optimal number of vectors per table and per database instance. +It is possible to upload more vectors to a single table if Memory allows it (for example, 4XL plan and higher for OpenAI embeddings). But it will affect the performance of the queries: QPS will be lower, and latency will be higher. Scaling should be almost linear, but it is recommended to benchmark your workload to find the optimal number of vectors per table and per database instance. diff --git a/apps/docs/pages/guides/ai/going-to-prod.mdx b/apps/docs/pages/guides/ai/going-to-prod.mdx index 443e559d19a..608cfe5226c 100644 --- a/apps/docs/pages/guides/ai/going-to-prod.mdx +++ b/apps/docs/pages/guides/ai/going-to-prod.mdx @@ -8,35 +8,35 @@ export const meta = { sidebar_label: 'Going to Production', } -This guide will help you to prepare your application for production. We'll provide actionable steps to help you scale your application, ensure that it is reliable, can handle the load, and provide optimal precision for your use case. +This guide will help you to prepare your application for production. We'll provide actionable steps to help you scale your application, ensure that it is reliable, can handle the load, and provide optimal accuracy for your use case. See our [Engineering for Scale](/docs/guides/ai/engineering-for-scale) guide for more information about engineering at scale. ## Do you need indexes? -Sequential scans will result in significantly higher latencies and lower throughput, guaranteeing 100% precision and not being RAM bound. +Sequential scans will result in significantly higher latencies and lower throughput, guaranteeing 100% accuracy and not being RAM bound. There are a couple of cases where you might not need indexes: - You have a small dataset and don't need to scale it. - You are not expecting high amounts of vector search queries per second. -- You need to guarantee 100% precision. +- You need to guarantee 100% accuracy. You don't have to create indexes in these cases and can use sequential scans instead. This type of workload will not be RAM bound and will not require any additional resources but will result in higher latencies and lower throughput. Extra CPU cores may help to improve queries per second, but it will not help to improve latency. -On the other hand, if you need to scale your application, you will need to create indexes. This will result in lower latencies and higher throughput, but will require additional RAM to make use of Postgres Caching. Also, using indexes will result in lower precision, since you are replacing exact (KNN) search with approximate (ANN) search. +On the other hand, if you need to scale your application, you will need to create indexes. This will result in lower latencies and higher throughput, but will require additional RAM to make use of Postgres Caching. Also, using indexes will result in lower accuracy, since you are replacing exact (KNN) search with approximate (ANN) search. ## Understanding `probes` and `lists` Indexes used for approximate vector similarity search in pgvector divides a dataset into partitions. The number of these partitions is defined by the `lists` constant. The `probes` controls how many lists are going to be searched during a query. -The values of lists and probes directly affect precision and requests per second (RPS). +The values of lists and probes directly affect accuracy and queries per second (QPS). -- Higher `lists` means an index will be built slower, but you can achieve better RPS and precision. -- Higher `probes` means that select queries will be slower, but you can achieve better precision. -- `lists` and `probes` are not independent. Higher `lists` means that you will have to use higher `probes` to achieve the same precision. +- Higher `lists` means an index will be built slower, but you can achieve better QPS and accuracy. +- Higher `probes` means that select queries will be slower, but you can achieve better accuracy. +- `lists` and `probes` are not independent. Higher `lists` means that you will have to use higher `probes` to achieve the same accuracy. -You can find more examples of how `lists` and `probes` constants affect precision and RPS in [pgvector 0.4.0 performance](https://supabase.com/blog/pgvector-performance) blogpost. +You can find more examples of how `lists` and `probes` constants affect accuracy and QPS in [pgvector 0.4.0 performance](https://supabase.com/blog/pgvector-performance) blogpost.
    Date: Tue, 12 Sep 2023 00:07:46 +0000 Subject: [PATCH 071/105] Fix verifyOtp method name in the example snippets --- apps/docs/pages/guides/auth/phone-login/messagebird.mdx | 6 +++--- apps/docs/pages/guides/auth/phone-login/twilio.mdx | 2 +- apps/docs/pages/guides/auth/phone-login/vonage.mdx | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/docs/pages/guides/auth/phone-login/messagebird.mdx b/apps/docs/pages/guides/auth/phone-login/messagebird.mdx index 273b4870944..ad70cc72d52 100644 --- a/apps/docs/pages/guides/auth/phone-login/messagebird.mdx +++ b/apps/docs/pages/guides/auth/phone-login/messagebird.mdx @@ -111,7 +111,7 @@ curl -X POST 'https://cvwawazfelidkloqmbma.supabase.co/auth/v1/signup' \ The user will now receive an SMS with a 6-digit pin that you will need to receive from them within 60-seconds before they can login to their account. -You should present a form to the user so they can input the 6 digit pin, then send it along with the phone number to `verifyOTP`: +You should present a form to the user so they can input the 6 digit pin, then send it along with the phone number to `verifyOtp`: ```js -let { session, error } = await supabase.auth.verifyOTP({ +let { session, error } = await supabase.auth.verifyOtp({ phone: '+13334445555', token: '123456', }) @@ -237,7 +237,7 @@ The second step is the same as the previous section, you need to collect the 6-d ```js -let { session, error } = await supabase.auth.verifyOTP({ +let { session, error } = await supabase.auth.verifyOtp({ phone: '+13334445555', token: '123456', }) diff --git a/apps/docs/pages/guides/auth/phone-login/twilio.mdx b/apps/docs/pages/guides/auth/phone-login/twilio.mdx index 70dd119c164..7489c644267 100644 --- a/apps/docs/pages/guides/auth/phone-login/twilio.mdx +++ b/apps/docs/pages/guides/auth/phone-login/twilio.mdx @@ -157,7 +157,7 @@ curl -X POST 'https://cvwawazfelidkloqmbma.supabase.co/auth/v1/signup' \ The user will now receive an SMS with a 6-digit pin that you will need to receive from them within 60-seconds before they can login to their account. -You should present a form to the user so they can input the 6 digit pin, then send it along with the phone number to `verifyOTP`: +You should present a form to the user so they can input the 6 digit pin, then send it along with the phone number to `verifyOtp`: ```js -let { session, error } = await supabase.auth.verifyOTP({ +let { session, error } = await supabase.auth.verifyOtp({ phone: '491512223334444', token: '123456', }) @@ -230,7 +230,7 @@ The second step is the same as the previous section, you need to collect the 6-d ```js -let { session, error } = await supabase.auth.verifyOTP({ +let { session, error } = await supabase.auth.verifyOtp({ phone: '491512223334444', token: '123456', }) From 38f42ad8fc7abeed3501a447f991587727e717ce Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 12 Sep 2023 13:26:01 +0800 Subject: [PATCH 072/105] Edge functions use custom domain if configured --- .../EdgeFunctionDetails/EdgeFunctionDetails.tsx | 7 ++++++- .../Functions/EdgeFunctionsListItem.tsx | 17 +++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx b/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx index 3d984779e4e..24372391c6d 100644 --- a/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx +++ b/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx @@ -35,6 +35,7 @@ import { useEdgeFunctionUpdateMutation } from 'data/edge-functions/edge-function import { useCheckPermissions, useStore } from 'hooks' import CommandRender from '../CommandRender' import { generateCLICommands } from './EdgeFunctionDetails.utils' +import { useCustomDomainsQuery } from 'data/custom-domains/custom-domains-query' const EdgeFunctionDetails = () => { const router = useRouter() @@ -44,6 +45,7 @@ const EdgeFunctionDetails = () => { const [showInstructions, setShowInstructions] = useState(false) const { data: settings } = useProjectApiQuery({ projectRef }) + const { data: customDomainData } = useCustomDomainsQuery({ projectRef }) const { data: selectedFunction } = useEdgeFunctionQuery({ projectRef, slug: functionSlug }) const { mutateAsync: updateEdgeFunction, isLoading: isUpdating } = useEdgeFunctionUpdateMutation() const { mutate: deleteEdgeFunction, isLoading: isDeleting } = useEdgeFunctionDeleteMutation({ @@ -66,7 +68,10 @@ const EdgeFunctionDetails = () => { : '[YOUR ANON KEY]' const endpoint = apiService?.app_config.endpoint ?? '' - const functionUrl = `${apiService?.protocol}://${endpoint}/functions/v1/${selectedFunction?.slug}` + const functionUrl = + customDomainData?.customDomain?.status === 'active' + ? `${apiService?.protocol}://${customDomainData.customDomain.hostname}/functions/v1/${selectedFunction?.slug}` + : `${apiService?.protocol}://${endpoint}/functions/v1/${selectedFunction?.slug}` const { managementCommands, secretCommands, invokeCommands } = generateCLICommands( selectedFunction, diff --git a/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx b/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx index 946befc0a33..cc40c235b31 100644 --- a/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx +++ b/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx @@ -8,6 +8,7 @@ import { useParams } from 'common/hooks' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import Table from 'components/to-be-cleaned/Table' import { EdgeFunctionsResponse } from 'data/edge-functions/edge-functions-query' +import { useCustomDomainsQuery } from 'data/custom-domains/custom-domains-query' interface EdgeFunctionsListItemProps { function: EdgeFunctionsResponse @@ -16,14 +17,22 @@ interface EdgeFunctionsListItemProps { const EdgeFunctionsListItem = ({ function: item }: EdgeFunctionsListItemProps) => { const router = useRouter() const { ref } = useParams() + const { project } = useProjectContext() const [isCopied, setIsCopied] = useState(false) - const { project } = useProjectContext() + const { data: customDomainData } = useCustomDomainsQuery({ projectRef: ref }) + // get the .co or .net TLD from the restUrl const restUrl = project?.restUrl - const restUrlTld = restUrl !== undefined ? new URL(restUrl as string).hostname.split('.').pop() : 'co' + const restUrlTld = + restUrl !== undefined ? new URL(restUrl as string).hostname.split('.').pop() : 'co' const functionUrl = `https://${ref}.supabase.${restUrlTld}/functions/v1/${item.slug}` + const endpoint = + customDomainData?.customDomain?.status === 'active' + ? `https://${customDomainData.customDomain.hostname}/functions/v1/${item.slug}` + : functionUrl + return (
    -

    {functionUrl}

    +

    {endpoint}

    - Name - URL - Created - Last updated - Deployments - - } - body={ - <> - {functions.length > 0 && - functions.map((item) => )} - - } - /> + return ( +
    + {isLoading && } + {isError && } + {isSuccess && ( + <> + {hasFunctions ? ( +
    +
    + {`${functions.length} function${ + functions.length > 1 ? 's' : '' + } deployed`} +
    +
    + Name + URL + Created + Last updated + Deployments + + } + body={ + <> + {functions.length > 0 && + functions.map((item) => ( + + ))} + + } + /> + + ) : ( + + )} + + )} - ) : ( - ) } From 6d7e84756d20d4321fc22c9d36244b12e2a3571b Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 12 Sep 2023 14:14:30 +0800 Subject: [PATCH 074/105] Small fixes to loading states of backups UI --- .../Database/Backups/BackupItem.tsx | 50 +------- .../Database/Backups/BackupsList.tsx | 109 ++++++++++++++---- .../Database/Backups/PITR/PITRSelection.tsx | 12 +- studio/data/database/backups-query.ts | 3 + 4 files changed, 103 insertions(+), 71 deletions(-) diff --git a/studio/components/interfaces/Database/Backups/BackupItem.tsx b/studio/components/interfaces/Database/Backups/BackupItem.tsx index 8d13167d797..e8c3fbf82b1 100644 --- a/studio/components/interfaces/Database/Backups/BackupItem.tsx +++ b/studio/components/interfaces/Database/Backups/BackupItem.tsx @@ -1,65 +1,27 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' -import { useQueryClient } from '@tanstack/react-query' import dayjs from 'dayjs' -import { useRouter } from 'next/router' import { Badge, Button } from 'ui' -import { confirmAlert } from 'components/to-be-cleaned/ModalsDeprecated/ConfirmModal' -import { useBackupRestoreMutation } from 'data/database/backup-restore-mutation' -import { setProjectStatus } from 'data/projects/projects-query' -import { useCheckPermissions, useStore } from 'hooks' -import { PROJECT_STATUS } from 'lib/constants' +import { DatabaseBackup } from 'data/database/backups-query' +import { useCheckPermissions } from 'hooks' interface BackupItemProps { - projectRef: string - backup: any index: number + backup: DatabaseBackup + onSelectBackup: () => void } -const BackupItem = ({ projectRef, backup, index }: BackupItemProps) => { - const router = useRouter() - const { ui } = useStore() - const queryClient = useQueryClient() - +const BackupItem = ({ index, backup, onSelectBackup }: BackupItemProps) => { const canTriggerScheduledBackups = useCheckPermissions( PermissionAction.INFRA_EXECUTE, 'queue_job.restore.prepare' ) - const { mutate: restoreFromBackup, isLoading: isRestoring } = useBackupRestoreMutation({ - onSuccess: () => { - setTimeout(() => { - setProjectStatus(queryClient, projectRef, PROJECT_STATUS.RESTORING) - ui.setNotification({ - category: 'success', - message: `Restoring database back to ${dayjs(backup.inserted_at).format( - 'DD MMM YYYY HH:mm:ss' - )}`, - }) - router.push(`/project/${projectRef}`) - }, 3000) - }, - }) - - const onRestoreClick = () => { - confirmAlert({ - title: 'Confirm to restore', - message: `Are you sure you want to restore from ${dayjs(backup.inserted_at).format( - 'DD MMM YYYY' - )}? This will destroy any new data written since this backup was made.`, - onAsyncConfirm: async () => restoreFromBackup({ ref: projectRef, backup }), - }) - } - const generateSideButtons = (backup: any) => { if (backup.status === 'COMPLETED') return (
    -
    diff --git a/studio/components/interfaces/Database/Backups/BackupsList.tsx b/studio/components/interfaces/Database/Backups/BackupsList.tsx index 6719577921c..94a92ca09d7 100644 --- a/studio/components/interfaces/Database/Backups/BackupsList.tsx +++ b/studio/components/interfaces/Database/Backups/BackupsList.tsx @@ -1,27 +1,58 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' +import { useQueryClient } from '@tanstack/react-query' +import dayjs from 'dayjs' import { observer } from 'mobx-react-lite' -import { IconAlertCircle, IconClock } from 'ui' +import { useRouter } from 'next/router' +import { useState } from 'react' +import { IconAlertCircle, IconClock, Modal } from 'ui' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' +import ConfirmationModal from 'components/ui/ConfirmationModal' import InformationBox from 'components/ui/InformationBox' import Panel from 'components/ui/Panel' import UpgradeToPro from 'components/ui/UpgradeToPro' -import { useBackupsQuery } from 'data/database/backups-query' -import { useCheckPermissions } from 'hooks' +import { useBackupRestoreMutation } from 'data/database/backup-restore-mutation' +import { DatabaseBackup, useBackupsQuery } from 'data/database/backups-query' +import { setProjectStatus } from 'data/projects/projects-query' +import { useCheckPermissions, useStore } from 'hooks' +import { PROJECT_STATUS } from 'lib/constants' import BackupItem from './BackupItem' import BackupsEmpty from './BackupsEmpty' const BackupsList = () => { + const { ui } = useStore() + const router = useRouter() + const queryClient = useQueryClient() const { project: selectedProject } = useProjectContext() const projectRef = selectedProject?.ref || 'default' - const { data: backups } = useBackupsQuery({ projectRef }) + const [selectedBackup, setSelectedBackup] = useState() const canTriggerScheduledBackups = useCheckPermissions( PermissionAction.INFRA_EXECUTE, 'queue_job.restore.prepare' ) + const { data: backups } = useBackupsQuery({ projectRef }) + const { + mutate: restoreFromBackup, + isLoading: isRestoring, + isSuccess: isSuccessBackup, + } = useBackupRestoreMutation({ + onSuccess: () => { + setTimeout(() => { + setProjectStatus(queryClient, projectRef, PROJECT_STATUS.RESTORING) + ui.setNotification({ + category: 'success', + message: `Restoring database back to ${dayjs(selectedBackup?.inserted_at).format( + 'DD MMM YYYY HH:mm:ss' + )}`, + }) + router.push(`/project/${projectRef}`) + }, 3000) + }, + }) + const planKey = backups?.tierKey ?? '' const sortedBackups = (backups?.backups ?? []).sort( (a, b) => new Date(b.inserted_at).valueOf() - new Date(a.inserted_at).valueOf() @@ -43,25 +74,57 @@ const BackupsList = () => { if (isPitrEnabled) return null return ( -
    - {sortedBackups.length === 0 && planKey !== 'FREE' ? ( - - ) : ( - <> - {!canTriggerScheduledBackups && ( - } - title="You need additional permissions to trigger a scheduled backup" - /> - )} - - {sortedBackups?.map((x: any, i: number) => { - return - })} - - - )} -
    + <> +
    + {sortedBackups.length === 0 && planKey !== 'FREE' ? ( + + ) : ( + <> + {!canTriggerScheduledBackups && ( + } + title="You need additional permissions to trigger a scheduled backup" + /> + )} + + {sortedBackups?.map((x, i: number) => { + return ( + setSelectedBackup(x)} + /> + ) + })} + + + )} +
    + setSelectedBackup(undefined)} + onSelectConfirm={() => { + if (selectedBackup === undefined) return console.error('Backup required') + restoreFromBackup({ ref: projectRef, backup: selectedBackup }) + }} + > + +
    +

    + Are you sure you want to restore from $ + {dayjs(selectedBackup?.inserted_at).format('DD MMM YYYY')}? This will destroy any new + data written since this backup was made. +

    +
    +
    +
    + ) } diff --git a/studio/components/interfaces/Database/Backups/PITR/PITRSelection.tsx b/studio/components/interfaces/Database/Backups/PITR/PITRSelection.tsx index 23ca285c73d..65b0ff813cc 100644 --- a/studio/components/interfaces/Database/Backups/PITR/PITRSelection.tsx +++ b/studio/components/interfaces/Database/Backups/PITR/PITRSelection.tsx @@ -36,7 +36,11 @@ const PITRSelection = () => { const [showConfirmation, setShowConfirmation] = useState(false) const [selectedTimezone, setSelectedTimezone] = useState(getClientTimezone()) - const { mutate: restoreFromPitr, isLoading: isRestoring } = usePitrRestoreMutation({ + const { + mutate: restoreFromPitr, + isLoading: isRestoring, + isSuccess: isSuccessPITR, + } = usePitrRestoreMutation({ onSuccess: (res, variables) => { setTimeout(() => { setShowConfirmation(false) @@ -321,15 +325,15 @@ const PITRSelection = () => {
    + + + + +
    + + } + /> + + +
    +
    +
    - )} - {!isOrgBilling && ( -
    -
    - -
    +
    + +
    +
    +
    - )} -
    +
    + +
    - {!isOrgBilling && ( -
    -
    - -
    + +
    +
    +
    - )} +
    ) } diff --git a/studio/components/interfaces/BillingV2/Subscription/Tier/SubscriptionTier.tsx b/studio/components/interfaces/BillingV2/Subscription/Tier/SubscriptionTier.tsx index 73084a1bb4d..1e1e9a70140 100644 --- a/studio/components/interfaces/BillingV2/Subscription/Tier/SubscriptionTier.tsx +++ b/studio/components/interfaces/BillingV2/Subscription/Tier/SubscriptionTier.tsx @@ -42,13 +42,11 @@ const SubscriptionTier = ({}: SubscriptionTierProps) => {
    To manage your billing address, emails or Tax ID, head to your{' '} - - - organization settings - - . + + organization settings + .

    More information

    diff --git a/studio/components/interfaces/BillingV2/Subscription/Tier/TierUpdateSidePanel.tsx b/studio/components/interfaces/BillingV2/Subscription/Tier/TierUpdateSidePanel.tsx index 5a7219bd1e9..72d3b8103cf 100644 --- a/studio/components/interfaces/BillingV2/Subscription/Tier/TierUpdateSidePanel.tsx +++ b/studio/components/interfaces/BillingV2/Subscription/Tier/TierUpdateSidePanel.tsx @@ -137,19 +137,17 @@ const TierUpdateSidePanel = () => { icon={} defaultVisibility={true} hideCollapse - title="Legacy Billing" + title="We're upgrading our billing system" className="mt-4" description={

    This organization uses the legacy project-based billing. We’ve recently made some - big improvements to our billing system. To opt-in to the new organization-based + big improvements to our billing system. To migrate to the new organization-based billing, head over to your{' '} - - - organization billing settings - + + organization billing settings . diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.tsx b/studio/components/interfaces/BillingV2/Usage/Usage.tsx index cd61a2d5889..d768880161b 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Usage.tsx @@ -9,13 +9,15 @@ 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 { Button, IconAlertCircle, IconExternalLink, IconInfo, IconLoader, cn } from 'ui' import Activity from './Activity' import Bandwidth from './Bandwidth' import Infrastructure from './Infrastructure' import SizeAndCounts from './SizeAndCounts' import { USAGE_CATEGORIES, USAGE_STATUS } from './Usage.constants' import { getUsageStatus } from './Usage.utils' +import Link from 'next/link' +import InformationBox from 'components/ui/InformationBox' type UsageSectionIds = 'infra' | 'bandwidth' | 'sizeCount' | 'activity' @@ -214,6 +216,47 @@ const Usage = () => {

    +
    + } + defaultVisibility={true} + hideCollapse + title="We're upgrading our billing system" + description={ +
    +

    + This organization uses the legacy project-based billing. We’ve recently made some + big improvements to our billing system. To migrate to the new organization-based + billing, head over to your{' '} + + + organization billing settings + + + . +

    + +
    + + + + + + +
    +
    + } + /> +
    + inView && setActiveTab('infra')}>
    string @@ -96,6 +95,8 @@ const OrganizationProjects = ({ }: OrganizationProjectsProps) => { const isEmpty = !projects || projects.length === 0 + const [orgBillingMigrationModalVisible, setOrgBillingMigrationModalVisible] = useState(false) + return (
    @@ -110,6 +111,14 @@ const OrganizationProjects = ({
    )} + + {!subscription_id && ( +
    + +
    + )}
    {isLoadingPermissions || isLoadingProjects ? ( @@ -146,6 +155,48 @@ const OrganizationProjects = ({ )} )} + + setOrgBillingMigrationModalVisible(false)} + header="We're upgrading our billing system" + > + +
    +

    + The organization "{name}" still uses the legacy project-based billing. We've recently + made some big improvements to our billing system and require your action. To migrate + to the new organization-based billing, head over to your{' '} + + + organization billing settings + + + . +

    + +
    + + + + + + +
    +
    +
    +
    ) } diff --git a/studio/components/interfaces/Organization/BillingSettingsV2/Subscription/PlanUpdateSidePanel.tsx b/studio/components/interfaces/Organization/BillingSettingsV2/Subscription/PlanUpdateSidePanel.tsx index 649e6984542..3b3a11436f8 100644 --- a/studio/components/interfaces/Organization/BillingSettingsV2/Subscription/PlanUpdateSidePanel.tsx +++ b/studio/components/interfaces/Organization/BillingSettingsV2/Subscription/PlanUpdateSidePanel.tsx @@ -25,6 +25,7 @@ import { useOrganizationBillingSubscriptionPreview } from 'data/organizations/or import InformationBox from 'components/ui/InformationBox' import AlertError from 'components/ui/AlertError' import { SubscriptionTier } from 'data/subscriptions/types' +import Table from 'components/to-be-cleaned/Table' // [Joshen TODO] Need to remove all contexts of "projects" @@ -322,70 +323,71 @@ const PlanUpdateSidePanel = () => { Estimating monthly costs... )} {subscriptionPreviewInitialized && ( - - Estimated monthly price is $ - {Math.round( - subscriptionPreview.breakdown.reduce((prev, cur) => prev + cur.total_price, 0) - )}{' '} - + usage - - } - hideCollapse={false} - description={ -
    -
    - - - - - - - - - - {subscriptionPreview.breakdown.map((item) => ( - - - - - - - ))} - +
    + + Estimated monthly price is $ + {Math.round( + subscriptionPreview.breakdown.reduce((prev, cur) => prev + cur.total_price, 0) + )}{' '} + + usage + + } + hideCollapse={false} + description={ +
    - Item - - Count - - Unit Price - - Price -
    {item.description ?? 'Unknown'}{item.quantity} - {item.unit_price === 0 ? 'FREE' : `$${item.unit_price}`} - ${item.total_price}
    Item, + Count, + Unit Price, + + Price + , + ]} + body={ + <> + {subscriptionPreview.breakdown.map((item) => ( + + {item.description ?? 'Unknown'} + {item.quantity} + + {item.unit_price === 0 ? 'FREE' : `$${item.unit_price}`} + + ${item.total_price} + + ))} - - - - - - -
    Total - - - $ - {Math.round( - subscriptionPreview.breakdown.reduce( - (prev, cur) => prev + cur.total_price, - 0 - ) - ) ?? 0} -
    -
    - } - /> + + Total + + + + $ + {Math.round( + subscriptionPreview.breakdown.reduce( + (prev, cur) => prev + cur.total_price, + 0 + ) + ) ?? 0} + + + + } + > + } + /> + + {subscriptionPreview.number_of_projects && + subscriptionPreview.number_of_projects > 1 && ( +

    + All {subscriptionPreview.number_of_projects} projects from your organization " + {selectedOrganization?.name}" will use the new {planMeta?.name} plan. +

    + )} +
    )} diff --git a/studio/data/organizations/organization-billing-subscription-preview.ts b/studio/data/organizations/organization-billing-subscription-preview.ts index 910eef3bed4..a18677868d6 100644 --- a/studio/data/organizations/organization-billing-subscription-preview.ts +++ b/studio/data/organizations/organization-billing-subscription-preview.ts @@ -16,6 +16,7 @@ export type OrganizationBillingSubscriptionPreviewResponse = { quantity: number total_price: number }[] + number_of_projects?: number } export async function previewOrganizationBillingSubscription({ diff --git a/studio/pages/new/[slug].tsx b/studio/pages/new/[slug].tsx index 16300ebd9ad..6bce0bddebb 100644 --- a/studio/pages/new/[slug].tsx +++ b/studio/pages/new/[slug].tsx @@ -497,19 +497,30 @@ const Wizard: NextPageWithLayout = () => { )}
  • @@ -586,37 +597,46 @@ const Wizard: NextPageWithLayout = () => { icon={} defaultVisibility={true} hideCollapse - title="Legacy Billing" + title="We're upgrading our billing system" description={

    This organization uses the legacy project-based billing. We’ve recently made - some big improvements to our billing system. To opt-in to the new + some big improvements to our billing system. To migrate to the new organization-based billing, head over to your{' '} - - - organization billing settings - + + organization billing settings .

    diff --git a/studio/pages/project/[ref]/settings/billing/invoices.tsx b/studio/pages/project/[ref]/settings/billing/invoices.tsx index 4d5cbbbb70a..ca536f73645 100644 --- a/studio/pages/project/[ref]/settings/billing/invoices.tsx +++ b/studio/pages/project/[ref]/settings/billing/invoices.tsx @@ -2,11 +2,12 @@ import Link from 'next/link' import { Invoices } from 'components/interfaces/BillingV2' import { SettingsLayout } from 'components/layouts' -import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import { useSelectedOrganization } from 'hooks' import { NextPageWithLayout } from 'types' import { useRouter } from 'next/router' import { useEffect } from 'react' +import InformationBox from 'components/ui/InformationBox' +import { Button, IconExternalLink, IconInfo } from 'ui' const ProjectBilling: NextPageWithLayout = () => { const organization = useSelectedOrganization() @@ -43,18 +44,57 @@ const Settings = () => { return (
    +
    + } + defaultVisibility={true} + hideCollapse + title="We're upgrading our billing system" + description={ +
    +

    + This organization uses the legacy project-based billing. We’ve recently made some + big improvements to our billing system. To migrate to the new organization-based + billing, head over to your{' '} + + + organization billing settings + + + . +

    + +
    + + + + + + +
    +
    + } + /> +
    +

    Invoices

    To manage your billing address, emails or Tax ID, head to your{' '} - - - - organization settings - - . + + + organization settings + .
    From 0887e5290c77d034add50155a57f86c0ce413d51 Mon Sep 17 00:00:00 2001 From: egor-romanov Date: Tue, 12 Sep 2023 13:54:43 +0300 Subject: [PATCH 081/105] fix: misspelling --- apps/docs/pages/guides/ai/going-to-prod.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/pages/guides/ai/going-to-prod.mdx b/apps/docs/pages/guides/ai/going-to-prod.mdx index 78facbc6c80..bfd1924b43f 100644 --- a/apps/docs/pages/guides/ai/going-to-prod.mdx +++ b/apps/docs/pages/guides/ai/going-to-prod.mdx @@ -102,7 +102,7 @@ First, a few generic tips which you can pick and choose from: 3. **Pre-warm your database.** Implement the warm-up technique before transitioning to production or running benchmarks. - Use [pg_prewarm](https://www.postgresql.org/docs/current/pgprewarm.html) to load the index into RAM `select pg_prewarm('vecs.docs_vec_idx');`. This will help to avoid cold cache issues. - Execute 10,000 to 50,000 "warm-up" queries before each benchmark/prod. This will help to utilize cache and buffers more efficiently. -4. **Establish your workload.** Finetune `m` and `ef_construction` or `lists` constants for the pgvector index to accelerate your queries (at the expense of a slower build times). For instance, for benchmarks with 1,000,000 OpenAI embeddings, we set `m` and `ef_constuction` to 32 and 80, and it resulted in 35% higher QPS than 24 and 56 values respectively. +4. **Establish your workload.** Finetune `m` and `ef_construction` or `lists` constants for the pgvector index to accelerate your queries (at the expense of a slower build times). For instance, for benchmarks with 1,000,000 OpenAI embeddings, we set `m` and `ef_construction` to 32 and 80, and it resulted in 35% higher QPS than 24 and 56 values respectively. 5. **Benchmark your own specific workloads.** Doing this during cache warm-up helps gauge the best value for the index build parameters, balancing accuracy with queries per second (QPS). ## Going into production From 5fed701a10c9995ba958986c6633e0b6246ed949 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 12 Sep 2023 18:55:19 +0800 Subject: [PATCH 082/105] 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 ff9501fc30d39acfe6feeeb6df84e232329241b7 Mon Sep 17 00:00:00 2001 From: Jacob Millward Date: Sat, 9 Sep 2023 12:38:28 +0100 Subject: [PATCH 083/105] Remove broken links --- apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx b/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx index 10fdf26cd6e..38fec39a043 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx @@ -1868,8 +1868,6 @@ export const GET: RequestHandler = withAuth(async ({ session, getSupabaseClient - [Auth Helpers Source code](https://github.com/supabase/auth-helpers) - [SvelteKit example](https://github.com/supabase/auth-helpers/tree/main/examples/sveltekit) -- [SvelteKit Email/Password example](https://github.com/supabase/auth-helpers/tree/main/examples/sveltekit-email-password) -- [SvelteKit Magiclink example](https://github.com/supabase/auth-helpers/tree/main/examples/sveltekit-magic-link) export const Page = ({ children }) => From d4f9ec2f40912fded4afd14b4fbc10981888a88f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramiro=20Nu=C3=B1ez=20Dosio?= Date: Tue, 5 Sep 2023 15:44:26 +0100 Subject: [PATCH 084/105] Edited copy. --- .../www/_blog/2023-09-06-increase-performance-pgvector-hnsw.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/www/_blog/2023-09-06-increase-performance-pgvector-hnsw.mdx b/apps/www/_blog/2023-09-06-increase-performance-pgvector-hnsw.mdx index 0ce3b312e93..85c4fa8f35f 100644 --- a/apps/www/_blog/2023-09-06-increase-performance-pgvector-hnsw.mdx +++ b/apps/www/_blog/2023-09-06-increase-performance-pgvector-hnsw.mdx @@ -17,7 +17,7 @@ thumb: 2023-09-06-increase-performance-pgvector-hnsw/pgvector-v0-5-0-thumb.png _Contributed by: [Egor Romanov](https://github.com/egor-romanov)_ -[Supabase Vector](https://supabase.com/vector) is about to get a lot faster. Starting today, new Supabase databases will ship with pgvector v0.5.0 which adds a new type of index: Hierarchical Navigable Small World (HNSW). +[Supabase Vector](https://supabase.com/vector) is about to get a lot faster. New Supabase databases will ship with pgvector v0.5.0 which adds a new type of index: Hierarchical Navigable Small World (HNSW). HNSW is an algorithm for approximate nearest neighbor search, often used in high-dimensional spaces like those found in embeddings. From 3d86630f245e0ad4e242bff1ae8203479ffbfffa Mon Sep 17 00:00:00 2001 From: Oliver Rice Date: Tue, 12 Sep 2023 09:19:43 -0500 Subject: [PATCH 085/105] separate python/js ai docs --- .../NavigationMenu.constants.ts | 69 ++++---- .../pages/guides/ai/managing-collections.mdx | 156 ------------------ apps/docs/pages/guides/ai/python-clients.mdx | 18 ++ .../pages/guides/ai/vecs-python-client.mdx | 2 +- apps/docs/pages/guides/ai/vector-columns.mdx | 2 +- apps/docs/pages/guides/ai/vector-indexes.mdx | 2 - 6 files changed, 56 insertions(+), 193 deletions(-) delete mode 100644 apps/docs/pages/guides/ai/managing-collections.mdx create mode 100644 apps/docs/pages/guides/ai/python-clients.mdx diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 9498cbe2ac4..2fe63d684ad 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -964,34 +964,9 @@ export const ai = { url: '/guides/ai/structured-unstructured', }, { - name: 'Quickstarts', + name: 'Learn', url: undefined, items: [ - { name: 'Developing locally with Vecs', url: '/guides/ai/vecs-python-client' }, - { name: 'Creating and managing collections', url: '/guides/ai/quickstarts/hello-world' }, - { - name: 'Generate Embeddings', - url: '/guides/ai/quickstarts/generate-text-embeddings', - }, - { name: 'Text Deduplication', url: '/guides/ai/quickstarts/text-deduplication' }, - { name: 'Face similarity search', url: '/guides/ai/quickstarts/face-similarity' }, - ], - }, - { - name: 'Python Client', - url: undefined, - items: [ - { name: 'API', url: '/guides/ai/python/api' }, - { name: 'Collections', url: '/guides/ai/python/collections' }, - { name: 'Indexes', url: '/guides/ai/python/indexes' }, - { name: 'Metadata', url: '/guides/ai/python/metadata' }, - ], - }, - { - name: 'Guides', - url: undefined, - items: [ - { name: 'Managing collections', url: '/guides/ai/managing-collections' }, { name: 'Vector columns', url: '/guides/ai/vector-columns' }, { name: 'Vector indexes', url: '/guides/ai/vector-indexes', items: vectorIndexItems }, { name: 'Engineering for scale', url: '/guides/ai/engineering-for-scale' }, @@ -1000,25 +975,23 @@ export const ai = { ], }, { - name: 'Examples', + name: 'JavaScript Examples', url: undefined, items: [ { name: 'OpenAI completions using Edge Functions', url: '/guides/ai/examples/openai', }, - { - name: 'Image search with OpenAI CLIP', - url: '/guides/ai/examples/image-search-openai-clip', - }, + { name: 'Generate image captions using Hugging Face', url: '/guides/ai/examples/huggingface-image-captioning', }, { - name: 'Building ChatGPT Plugins', - url: '/guides/ai/examples/building-chatgpt-plugins', + name: 'Generate Embeddings', + url: '/guides/ai/quickstarts/generate-text-embeddings', }, + { name: 'Adding generative Q&A to your documentation', url: '/guides/ai/examples/headless-vector-search', @@ -1029,6 +1002,36 @@ export const ai = { }, ], }, + { + name: 'Python Client', + url: undefined, + items: [ + { name: 'Choosing a Client', url: '/guides/ai/python-clients' }, + { name: 'API', url: '/guides/ai/python/api' }, + { name: 'Collections', url: '/guides/ai/python/collections' }, + { name: 'Indexes', url: '/guides/ai/python/indexes' }, + { name: 'Metadata', url: '/guides/ai/python/metadata' }, + ], + }, + { + name: 'Python Examples', + url: undefined, + items: [ + { name: 'Developing locally with Vecs', url: '/guides/ai/vecs-python-client' }, + { name: 'Creating and managing collections', url: '/guides/ai/quickstarts/hello-world' }, + + { name: 'Text Deduplication', url: '/guides/ai/quickstarts/text-deduplication' }, + { name: 'Face similarity search', url: '/guides/ai/quickstarts/face-similarity' }, + { + name: 'Image search with OpenAI CLIP', + url: '/guides/ai/examples/image-search-openai-clip', + }, + { + name: 'Building ChatGPT Plugins', + url: '/guides/ai/examples/building-chatgpt-plugins', + }, + ], + }, { name: 'Third-Party Tools', url: undefined, diff --git a/apps/docs/pages/guides/ai/managing-collections.mdx b/apps/docs/pages/guides/ai/managing-collections.mdx deleted file mode 100644 index fc56a8ae249..00000000000 --- a/apps/docs/pages/guides/ai/managing-collections.mdx +++ /dev/null @@ -1,156 +0,0 @@ -import Layout from '~/layouts/DefaultGuideLayout' - -export const meta = { - id: 'ai-collections', - title: 'Managing collections', - description: 'Learn how to manage groups of vector records using the vecs Python library', - sidebar_label: 'Managing collections', -} - -A collection is a group of vector records managed by the `vecs` Python library. Records can be added to or updated in a collection. Collections can be queried at any time, but should be indexed for scalable query performance. - -Supabase provides a [Python client](/docs/guides/ai/vecs-python-client) called `vecs` for managing unstructured vector stores in Postgres. If you come from a data science background, this unstructured data approach will feel familiar. If you are more interested in a structured data approach, see [Vector columns](/docs/guides/ai/vector-columns) or read our guide on [Structured & Unstructured Embeddings](/docs/guides/ai/structured-unstructured). - -Under the hood `vecs` will manage the necessary Postgres tables and columns to store and query your collections. - -## API - -Find the full API in the [official API docs](https://supabase.github.io/vecs/api). - -### Connecting - -Before you can interact with vecs, create the client to communicate with Postgres. - -```python -import vecs - -DB_CONNECTION = "postgresql://:@:/" - -# create vector store client -vx = vecs.create_client(DB_CONNECTION) -``` - -### Create collection - -You can create a collection to store vectors specifying the collection's name and the number of dimensions in the vectors you intend to store. - -```python -docs = vx.create_collection(name="docs", dimension=3) -``` - -If another collection exists with the same name, - -### Get an existing collection - -To access a previously created collection, use `get_collection` to retrieve it by name - -```python -docs = vx.get_collection(name="docs") -``` - -### Upserting vectors - -`vecs` combines the concepts of "insert" and "update" into "upsert". Upserting records adds them to the collection if the `id` is not present, or updates the existing record if the `id` does exist. - -```python -# add records to the collection -docs.upsert( - vectors=[ - ( - "vec0", # the vector's identifier - [0.1, 0.2, 0.3], # the vector. list or np.array - {"year": 1973} # associated metadata - ), - ( - "vec1", - [0.7, 0.8, 0.9], - {"year": 2012} - ) - ] -) -``` - -### Create an index - -Collections can be queried immediately after being created. -However, for good performance, the collection should be indexed after records have been upserted. - -Indexes should be created **after** the collection has been populated with records. Building an index on an empty collection will significantly reduce recall. Once the index has been created you can still upsert new documents into the collection but you should rebuild the index if the size of the collection more than doubles. - -Only one index may exist per-collection. By default, creating an index will replace any existing index. - -To create an index: - -```python -## -# INSERT RECORDS HERE -## - -# index the collection to be queried by cosine distance -docs.create_index(measure=vecs.IndexMeasure.cosine_distance) -``` - -Available options for query `measure` are: - -- `vecs.IndexMeasure.cosine_distance` -- `vecs.IndexMeasure.l2_distance` -- `vecs.IndexMeasure.max_inner_product` - -which correspond to different methods for comparing query vectors to the vectors in the database. - -If you aren't sure which to use, stick with the default (cosine_distance) by omitting the parameter i.e.: `docs.create_index()`. - - - -The time required to create an index grows with the number of records and size of vectors. For a few thousand records expect sub-minute a response in under a minute. It may take a few minutes for larger collections. - - - -For an in-depth guide on vector indexes, see [Vector indexes](/docs/guides/ai/vector-indexes). - -### Query - -Be aware that indexes are essential for good performance. If you do not create an index, every query will return a warning that includes the `IndexMeasure` you should index. - -#### Basic - -The simplest form of search is to provide a query vector. - -```python -docs.query( - query_vector=[0.4,0.5,0.6], # required - limit=5, # number of records to return - filters={}, # metadata filters - measure="cosine_distance", # distance measure to use - include_value=False, # should distance measure values be returned? - include_metadata=False, # should record metadata be returned? -) -``` - -Which returns a list of vector record `ids`. - -#### Metadata Filtering - -The metadata that is associated with each record can also be filtered during a query. - -As an example, `{"year": {"$eq": 2005}}` filters a `year` metadata key to be equal to 2005 - -In context: - -```python -docs.query( - query_vector=[0.4,0.5,0.6], - filters={"year": {"$eq": 2012}}, # metadata filters -) -``` - -For a complete reference, see the [metadata guide](https://supabase.github.io/vecs/concepts_metadata/). - -## Resources - -- Official Vecs Documentation: https://supabase.github.io/vecs/api -- Source Code: https://github.com/supabase/vecs - -export const Page = ({ children }) => - -export default Page diff --git a/apps/docs/pages/guides/ai/python-clients.mdx b/apps/docs/pages/guides/ai/python-clients.mdx new file mode 100644 index 00000000000..5daeb28b610 --- /dev/null +++ b/apps/docs/pages/guides/ai/python-clients.mdx @@ -0,0 +1,18 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'ai-python-clients', + title: 'Choosing a Client', + description: 'Learn how to manage vectors using Python', + sidebar_label: 'Choosing a Client', +} + +As described in [Structured & Unstructured Embeddings](/docs/guides/ai/structured-unstructured), AI workloads come in many forms. + +For data science or ephemeral workloads, the [Supabase Vecs](https://supabase.github.io/vecs/) client gets you started quickly. All you need is a connection string and vecs handles setting up your database to store and query vectors with associated metadata. + +For production python applications with version controled migrations, we recommend adding first class vector support to your toolchain by [registering the vector type with your ORM](https://github.com/pgvector/pgvector-python). pgvector provides bindings for the most commonly used SQL drivers/libraries including Django, SQLAlchemy, SQLModel, psycopg, asyncpg and Peewee. + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/ai/vecs-python-client.mdx b/apps/docs/pages/guides/ai/vecs-python-client.mdx index bdcecb77c43..ce7e5a7517c 100644 --- a/apps/docs/pages/guides/ai/vecs-python-client.mdx +++ b/apps/docs/pages/guides/ai/vecs-python-client.mdx @@ -79,7 +79,7 @@ docs.query( ## Deep Dive -For a more in-depth guide on `vecs` collections, see [Managing collections](/docs/guides/ai/managing-collections). +For a more in-depth guide on `vecs` collections, see [API](/docs/guides/ai/python/api). ## Resources diff --git a/apps/docs/pages/guides/ai/vector-columns.mdx b/apps/docs/pages/guides/ai/vector-columns.mdx index 4cfa8259708..51e995ccdcf 100644 --- a/apps/docs/pages/guides/ai/vector-columns.mdx +++ b/apps/docs/pages/guides/ai/vector-columns.mdx @@ -7,7 +7,7 @@ export const meta = { sidebar_label: 'Vector columns', } -Supabase offers a number of different ways to store and query vectors within Postgres. If you prefer to use Python to store and query your vectors using collections, see [Managing collections](/docs/guides/ai/managing-collections). If you want more control over vectors within your own Postgres tables or would like to interact with them using a different language like JavaScript, keep reading. +Supabase offers a number of different ways to store and query vectors within Postgres. The SQL included in this guide is applicable for clients in all programming languages. If you are a python user see your [python client options](/docs/guides/ai/python-clients) after reading the `Learn` section. Vectors in Supabase are enabled via [pgvector](https://github.com/pgvector/pgvector/), a PostgreSQL extension for storing and querying vectors in Postgres. It can be used to store [embeddings](/docs/guides/ai/concepts#what-are-embeddings). diff --git a/apps/docs/pages/guides/ai/vector-indexes.mdx b/apps/docs/pages/guides/ai/vector-indexes.mdx index 041e33a02cb..2bb3a4e310e 100644 --- a/apps/docs/pages/guides/ai/vector-indexes.mdx +++ b/apps/docs/pages/guides/ai/vector-indexes.mdx @@ -30,8 +30,6 @@ Indexes can be used to improve performance of nearest neighbor search using vari Currently vectors with up to 2,000 dimensions can be indexed. -If you are using the `vecs` Python library, follow the instructions in [Managing collections](/docs/guides/ai/managing-collections#create-an-index) to create indexes. - ## Resources Read more about indexing on `pgvector`'s [GitHub page](https://github.com/pgvector/pgvector#indexing). From cd5efb8dcf8695abeeaa84678d1d66aec5a95ee3 Mon Sep 17 00:00:00 2001 From: Oliver Rice Date: Tue, 12 Sep 2023 09:24:50 -0500 Subject: [PATCH 086/105] remove vecs reference from index info pages --- apps/docs/pages/guides/ai/vector-indexes/hnsw-indexes.mdx | 2 -- apps/docs/pages/guides/ai/vector-indexes/ivf-indexes.mdx | 2 -- 2 files changed, 4 deletions(-) diff --git a/apps/docs/pages/guides/ai/vector-indexes/hnsw-indexes.mdx b/apps/docs/pages/guides/ai/vector-indexes/hnsw-indexes.mdx index 23bcadcc661..75fb6d336e8 100644 --- a/apps/docs/pages/guides/ai/vector-indexes/hnsw-indexes.mdx +++ b/apps/docs/pages/guides/ai/vector-indexes/hnsw-indexes.mdx @@ -41,8 +41,6 @@ create index on items using hnsw (column_name vector_cosine_ops); Currently vectors with up to 2,000 dimensions can be indexed. -If you are using the `vecs` Python library, follow the instructions in [Managing collections](/docs/guides/ai/managing-collections#create-an-index) to create indexes. - ## How does HNSW work? HNSW uses proximity graphs (graphs connecting nodes based on distance between them) to approximate nearest-neighbor search. To understand HNSW, we can break it down into 2 parts: diff --git a/apps/docs/pages/guides/ai/vector-indexes/ivf-indexes.mdx b/apps/docs/pages/guides/ai/vector-indexes/ivf-indexes.mdx index cd03f55bd13..46795777c68 100644 --- a/apps/docs/pages/guides/ai/vector-indexes/ivf-indexes.mdx +++ b/apps/docs/pages/guides/ai/vector-indexes/ivf-indexes.mdx @@ -50,8 +50,6 @@ create index on items using ivfflat (column_name vector_cosine_ops) with (lists Currently vectors with up to 2,000 dimensions can be indexed. -If you are using the `vecs` Python library, follow the instructions in [Managing collections](/docs/guides/ai/managing-collections#create-an-index) to create indexes. - ## How does IVFFlat work? IVF stands for 'inverted file indexes'. It works by clustering your vectors in order to reduce the similarity search scope. Rather than comparing a vector to every other vector, the vector is only compared against vectors within the same cell cluster (or nearby clusters, depending on your configuration). From 43e6c73178658ad7aef40bb57f213e880e7f60ba Mon Sep 17 00:00:00 2001 From: Oliver Rice Date: Tue, 12 Sep 2023 09:30:30 -0500 Subject: [PATCH 087/105] typo --- apps/docs/pages/guides/ai/python-clients.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/pages/guides/ai/python-clients.mdx b/apps/docs/pages/guides/ai/python-clients.mdx index 5daeb28b610..6e6695d3592 100644 --- a/apps/docs/pages/guides/ai/python-clients.mdx +++ b/apps/docs/pages/guides/ai/python-clients.mdx @@ -11,7 +11,7 @@ As described in [Structured & Unstructured Embeddings](/docs/guides/ai/structure For data science or ephemeral workloads, the [Supabase Vecs](https://supabase.github.io/vecs/) client gets you started quickly. All you need is a connection string and vecs handles setting up your database to store and query vectors with associated metadata. -For production python applications with version controled migrations, we recommend adding first class vector support to your toolchain by [registering the vector type with your ORM](https://github.com/pgvector/pgvector-python). pgvector provides bindings for the most commonly used SQL drivers/libraries including Django, SQLAlchemy, SQLModel, psycopg, asyncpg and Peewee. +For production python applications with version controlled migrations, we recommend adding first class vector support to your toolchain by [registering the vector type with your ORM](https://github.com/pgvector/pgvector-python). pgvector provides bindings for the most commonly used SQL drivers/libraries including Django, SQLAlchemy, SQLModel, psycopg, asyncpg and Peewee. export const Page = ({ children }) => From 71e4eca02958113f2a0ffba7056672b1f7a3aef6 Mon Sep 17 00:00:00 2001 From: Oliver Rice Date: Tue, 12 Sep 2023 09:38:00 -0500 Subject: [PATCH 088/105] prettier --- .../Navigation/NavigationMenu/NavigationMenu.constants.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 2fe63d684ad..3e26e339ec4 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -982,7 +982,7 @@ export const ai = { name: 'OpenAI completions using Edge Functions', url: '/guides/ai/examples/openai', }, - + { name: 'Generate image captions using Hugging Face', url: '/guides/ai/examples/huggingface-image-captioning', @@ -991,7 +991,7 @@ export const ai = { name: 'Generate Embeddings', url: '/guides/ai/quickstarts/generate-text-embeddings', }, - + { name: 'Adding generative Q&A to your documentation', url: '/guides/ai/examples/headless-vector-search', @@ -1019,10 +1019,10 @@ export const ai = { items: [ { name: 'Developing locally with Vecs', url: '/guides/ai/vecs-python-client' }, { name: 'Creating and managing collections', url: '/guides/ai/quickstarts/hello-world' }, - + { name: 'Text Deduplication', url: '/guides/ai/quickstarts/text-deduplication' }, { name: 'Face similarity search', url: '/guides/ai/quickstarts/face-similarity' }, - { + { name: 'Image search with OpenAI CLIP', url: '/guides/ai/examples/image-search-openai-clip', }, From 3802d34111b9801a65ba4caa24475dc596acd89a Mon Sep 17 00:00:00 2001 From: Oliver Rice Date: Tue, 12 Sep 2023 09:57:05 -0500 Subject: [PATCH 089/105] Update apps/docs/pages/guides/ai/vector-columns.mdx Co-authored-by: Greg Richardson --- apps/docs/pages/guides/ai/vector-columns.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/pages/guides/ai/vector-columns.mdx b/apps/docs/pages/guides/ai/vector-columns.mdx index 51e995ccdcf..8b4d29643a5 100644 --- a/apps/docs/pages/guides/ai/vector-columns.mdx +++ b/apps/docs/pages/guides/ai/vector-columns.mdx @@ -7,7 +7,7 @@ export const meta = { sidebar_label: 'Vector columns', } -Supabase offers a number of different ways to store and query vectors within Postgres. The SQL included in this guide is applicable for clients in all programming languages. If you are a python user see your [python client options](/docs/guides/ai/python-clients) after reading the `Learn` section. +Supabase offers a number of different ways to store and query vectors within Postgres. The SQL included in this guide is applicable for clients in all programming languages. If you are a Python user see your [Python client options](/docs/guides/ai/python-clients) after reading the `Learn` section. Vectors in Supabase are enabled via [pgvector](https://github.com/pgvector/pgvector/), a PostgreSQL extension for storing and querying vectors in Postgres. It can be used to store [embeddings](/docs/guides/ai/concepts#what-are-embeddings). From 6ee067feafbdc8d593473c15b914570eb02c0e5d Mon Sep 17 00:00:00 2001 From: egor <58992960+egor-romanov@users.noreply.github.com> Date: Tue, 12 Sep 2023 18:17:53 +0300 Subject: [PATCH 090/105] Apply suggestions from code review Co-authored-by: Greg Richardson --- apps/docs/pages/guides/ai/going-to-prod.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/docs/pages/guides/ai/going-to-prod.mdx b/apps/docs/pages/guides/ai/going-to-prod.mdx index bfd1924b43f..a664933b0e3 100644 --- a/apps/docs/pages/guides/ai/going-to-prod.mdx +++ b/apps/docs/pages/guides/ai/going-to-prod.mdx @@ -24,11 +24,11 @@ There are a couple of cases where you might not need indexes: You don't have to create indexes in these cases and can use sequential scans instead. This type of workload will not be RAM bound and will not require any additional resources but will result in higher latencies and lower throughput. Extra CPU cores may help to improve queries per second, but it will not help to improve latency. -On the other hand, if you need to scale your application, you will need to [create indexes](https://supabase.com/docs/guides/ai/vector-indexes). This will result in lower latencies and higher throughput, but will require additional RAM to make use of Postgres Caching. Also, using indexes will result in lower accuracy, since you are replacing exact (KNN) search with approximate (ANN) search. +On the other hand, if you need to scale your application, you will need to [create indexes](/docs/guides/ai/vector-indexes). This will result in lower latencies and higher throughput, but will require additional RAM to make use of Postgres Caching. Also, using indexes will result in lower accuracy, since you are replacing exact (KNN) search with approximate (ANN) search. ## HNSW vs IVFFlat indexes -`pgvector` supports two types of indexes: HNSW and IVFFlat. We recommend using [HNSW](https://supabase.com/docs/guides/ai/vector-indexes/hnsw-indexes) because of its [performance](https://supabase.com/blog/increase-performance-pgvector-hnsw#hnsw-performance-1536-dimensions) and [robustness against changing data](https://supabase.com/docs/guides/ai/vector-indexes). +`pgvector` supports two types of indexes: HNSW and IVFFlat. We recommend using [HNSW](/docs/guides/ai/vector-indexes/hnsw-indexes) because of its [performance](https://supabase.com/blog/increase-performance-pgvector-hnsw#hnsw-performance-1536-dimensions) and [robustness against changing data](/docs/guides/ai/vector-indexes/hnsw-indexes#when-should-you-create-hnsw-indexes).
    Date: Tue, 12 Sep 2023 18:20:01 +0300 Subject: [PATCH 091/105] Apply suggestions from code review Co-authored-by: Greg Richardson --- apps/docs/pages/guides/ai/choosing-compute-addon.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/pages/guides/ai/choosing-compute-addon.mdx b/apps/docs/pages/guides/ai/choosing-compute-addon.mdx index 9d2bf0bd929..fb7bf5c9fa6 100644 --- a/apps/docs/pages/guides/ai/choosing-compute-addon.mdx +++ b/apps/docs/pages/guides/ai/choosing-compute-addon.mdx @@ -48,7 +48,7 @@ This benchmark uses the [dbpedia-entities-openai-1M](https://huggingface.co/data Accuracy was 0.99 for benchmarks. -QPS can also be improved by increasing `m` and `ef_construction`. This will allow you to use a smaller value for `ef_search` and increase QPS. For example, increasing `m` to 32 and `ef_construction` to 80 for 4XL will increase QPS to 1280. +QPS can also be improved by increasing [`m` and `ef_construction`](/docs/guides/ai/going-to-prod#hnsw-understanding-efconstruction--efsearch--and-m). This will allow you to use a smaller value for `ef_search` and increase QPS. For example, increasing `m` to 32 and `ef_construction` to 80 for 4XL will increase QPS to 1280. From 97c990cd30e73d4b3502b1e0fddb602db0fa7774 Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Tue, 12 Sep 2023 17:50:56 +0200 Subject: [PATCH 092/105] 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 093/105] 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 = () => {

    -
    - - stars background - -
    -
    - Launch Week 8 purple background gradient -
    -
    ) } From 956f9443f8fd8747a9482dcc84887bf66622b450 Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Tue, 12 Sep 2023 18:42:00 +0200 Subject: [PATCH 094/105] 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 095/105] 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 096/105] 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 f72ef25e18ee25c89c81bce85c7df51cb6001eb5 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Tue, 12 Sep 2023 16:27:55 -0230 Subject: [PATCH 097/105] Update --- .../SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx index 35769943d49..477252ef4eb 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx @@ -303,7 +303,7 @@ const ForeignKeySelector = ({ The column types don't match - + The referenced column {column?.name && {column.name}} {column?.format ? ( @@ -320,7 +320,7 @@ const ForeignKeySelector = ({ has {selectedColumn?.data_type}type. These two - columns can't be referenced until they of the same type. + columns can't be referenced until they are of the same type. )} From f4133d63d8da64e84dc4b5e221ff9ef15578328a Mon Sep 17 00:00:00 2001 From: Greg Richardson Date: Tue, 12 Sep 2023 13:15:17 -0600 Subject: [PATCH 098/105] chore(sql-editor): add more log information for ai requests --- studio/pages/api/ai/sql/debug.ts | 4 +++- studio/pages/api/ai/sql/edit.ts | 4 +++- studio/pages/api/ai/sql/generate.ts | 4 +++- studio/pages/api/ai/sql/title.ts | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/studio/pages/api/ai/sql/debug.ts b/studio/pages/api/ai/sql/debug.ts index 353f11b5a87..2a67dc50062 100644 --- a/studio/pages/api/ai/sql/debug.ts +++ b/studio/pages/api/ai/sql/debug.ts @@ -178,7 +178,9 @@ export async function handlePost(req: NextApiRequest, res: NextApiResponse) { return res.json(debugSqlResult) } catch (error) { console.error( - `AI SQL editing failed: ${isError(error) ? error.message : 'An unknown error occurred'}` + `AI SQL editing failed: ${ + isError(error) ? error.message : 'An unknown error occurred' + }, sqlResponseString: ${sqlResponseString}` ) return res.status(500).json({ diff --git a/studio/pages/api/ai/sql/edit.ts b/studio/pages/api/ai/sql/edit.ts index 70d4ad4e19a..979b13a5a2f 100644 --- a/studio/pages/api/ai/sql/edit.ts +++ b/studio/pages/api/ai/sql/edit.ts @@ -170,7 +170,9 @@ export async function handlePost(req: NextApiRequest, res: NextApiResponse) { return res.json(editSqlResult) } catch (error) { console.error( - `AI SQL editing failed: ${isError(error) ? error.message : 'An unknown error occurred'}` + `AI SQL editing failed: ${ + isError(error) ? error.message : 'An unknown error occurred' + }, sqlResponseString: ${sqlResponseString}` ) return res.status(500).json({ diff --git a/studio/pages/api/ai/sql/generate.ts b/studio/pages/api/ai/sql/generate.ts index 7fb2dbbfc77..7231c9c953c 100644 --- a/studio/pages/api/ai/sql/generate.ts +++ b/studio/pages/api/ai/sql/generate.ts @@ -158,7 +158,9 @@ export async function handlePost(req: NextApiRequest, res: NextApiResponse) { return res.json(generateSqlResult) } catch (error) { console.error( - `AI SQL editing failed: ${isError(error) ? error.message : 'An unknown error occurred'}` + `AI SQL editing failed: ${ + isError(error) ? error.message : 'An unknown error occurred' + }, sqlResponseString: ${sqlResponseString}` ) return res.status(500).json({ diff --git a/studio/pages/api/ai/sql/title.ts b/studio/pages/api/ai/sql/title.ts index bde2ee4db7c..7f34a3227bd 100644 --- a/studio/pages/api/ai/sql/title.ts +++ b/studio/pages/api/ai/sql/title.ts @@ -136,7 +136,9 @@ export async function handlePost(req: NextApiRequest, res: NextApiResponse) { return res.json(generateTitleResult) } catch (error) { console.error( - `AI SQL editing failed: ${isError(error) ? error.message : 'An unknown error occurred'}` + `AI SQL editing failed: ${ + isError(error) ? error.message : 'An unknown error occurred' + }, titleResponseString: ${titleResponseString}` ) return res.status(500).json({ From 4bd241af271d1e8a6f89ef35c665424011d60a3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Wed, 13 Sep 2023 00:21:53 +0200 Subject: [PATCH 099/105] 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 9070a469df761142da019ea024cf09c93e4d25c0 Mon Sep 17 00:00:00 2001 From: Pamela Chia Date: Wed, 13 Sep 2023 08:07:58 +0800 Subject: [PATCH 100/105] fix/ correct hipaa description in pricing page --- packages/shared-data/plans.ts | 2 +- packages/shared-data/pricing.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/shared-data/plans.ts b/packages/shared-data/plans.ts index 5e40e4b5c51..d13c90cb463 100644 --- a/packages/shared-data/plans.ts +++ b/packages/shared-data/plans.ts @@ -94,7 +94,7 @@ export const plans: PricingInformation[] = [ 'Daily backups stored for 14 days', 'Standardised Security Questionnaire', 'SOC2', - 'HIPAA', + 'HIPAA available as paid add-on', 'SSO for Supabase Dashboard', 'Priority email support & SLAs', '28-day log retention', diff --git a/packages/shared-data/pricing.ts b/packages/shared-data/pricing.ts index b955a8cfe2f..3bd10372288 100644 --- a/packages/shared-data/pricing.ts +++ b/packages/shared-data/pricing.ts @@ -469,8 +469,8 @@ export const pricing: Pricing = { plans: { free: false, pro: false, - team: true, - enterprise: true, + team: 'Available as paid add-on', + enterprise: 'Available as paid add-on', }, tooltips: { main: 'Available as a paid add-on on Team plan and above.', From 31eb73684b0c697c9076dde9e188200ee13d5972 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Tue, 12 Sep 2023 22:10:08 -0230 Subject: [PATCH 101/105] 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 102/105] 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 103/105] 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 104/105] 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 105/105] 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,