Pull master and resolve conflicts

This commit is contained in:
Joshen Lim committed 2022-10-06 10:02:15 +08:00
commit 3b48202972
8 files changed
+491 -333

No files matched your search

@@ -53,7 +53,7 @@ Visit the repo: [Snaplet](https://github.com/snaplet/postgres-wasm) | [Supaba
We're making some changes to the way the Dashboard interacts with your database. These changes simplify the database permissions so that it's easier for you to migrate in and out of Supabase, and they reduce the security surface area considerably. The change will be applied automatically in November, or you can run it today via the Dashboard.
[See the PR](https://github.com/supabase/supabase/discussions/9314).
[Review the Security Notice](https://github.com/supabase/supabase/discussions/9314).
## Quick product updates
+98 -293
View File
File diff suppressed because it is too large. Load diff
@@ -57,7 +57,7 @@ const GetStartedHero = () => {
<div
className={[
'w-[260px] lg:w-[320px] h-[160px] lg:h-[220px] bg-scale-300 border border-scale-500',
'absolute right-0 top-[50px] lg:-top-[40px] z-10 rounded-t overflow-y-hidden',
'absolute right-0 top-[50px] lg:-top-[40px] rounded-t overflow-y-hidden',
].join(' ')}
>
<div className="h-[24px] flex items-center border-b border-scale-500">
@@ -1,39 +1,102 @@
import { FC } from 'react'
import { FC, Fragment } from 'react'
import { useRouter } from 'next/router'
import { Button } from 'ui'
import { Button, IconExternalLink } from 'ui'
import { Action, ActionReason, ActionType } from '@supabase/shared-types/out/notifications'
import { Project } from 'types'
import Link from 'next/link'
// [Joshen TODO] Remove all things about "ownerReassignStatus" after 5th November
// double check with Qiao before we remove them.
interface Props {
project: Project
availableActions: any[]
changelogLink?: string
ownerReassignStatus?: any
availableActions: Action[]
onSelectRestartProject: () => void
onSelectApplyMigration: () => void
onSelectRollbackMigration: () => void
onSelectFinalizeMigration: () => void
}
const NotificationActions: FC<Props> = ({ project, availableActions, onSelectRestartProject }) => {
const NotificationActions: FC<Props> = ({
project,
changelogLink,
ownerReassignStatus,
availableActions,
onSelectRestartProject,
onSelectApplyMigration,
onSelectRollbackMigration,
onSelectFinalizeMigration,
}) => {
const router = useRouter()
const onSelectUpgradeProject = () => {
return router.push(`/project/${project.ref}/settings/billing/update/pro`)
}
return (
<div className="space-y-2">
{availableActions.map((action: any) => {
if (action.action_type === 'project.upgrade') {
const renderActionButton = (action: Action) => {
switch (action.action_type) {
case ActionType.UpgradeProjectToPro:
return (
<Button type="default" onClick={onSelectUpgradeProject}>
Upgrade project
</Button>
)
case ActionType.SchedulePostgresRestart:
return (
<Button type="default" onClick={onSelectRestartProject}>
Restart project
</Button>
)
case ActionType.MigratePostgresSchema:
if (action.reason === ActionReason.Finalize) {
return (
<Button key={action.action_type} type="default" onClick={onSelectUpgradeProject}>
Upgrade project
</Button>
ownerReassignStatus?.desired !== 'migrated' && (
<Button type="default" onClick={onSelectFinalizeMigration}>
Finalize
</Button>
)
)
} else if (action.action_type === 'postgresql.restart') {
} else if (action.reason === ActionReason.Rollback) {
return (
<Button key={action.action_type} type="default" onClick={onSelectRestartProject}>
Restart project
</Button>
ownerReassignStatus?.desired === 'temp_role' && (
<Button type="default" onClick={onSelectRollbackMigration}>
Rollback
</Button>
)
)
} else {
return (
ownerReassignStatus?.desired === 'unmigrated' && (
<Button type="default" onClick={onSelectApplyMigration}>
Apply now
</Button>
)
)
}
}
}
return (
<div className="flex items-center space-x-2">
{availableActions.map((action) => {
return (
<Fragment key={`${action.action_type}_${action.reason}`}>
{renderActionButton(action)}
</Fragment>
)
})}
{changelogLink && (
<Link href={changelogLink}>
<a>
<Button as="span" type="default" icon={<IconExternalLink size={12} strokeWidth={2} />}>
More info
</Button>
</a>
</Link>
)}
</div>
)
}
@@ -1,4 +1,5 @@
import dayjs from 'dayjs'
import useSWR from 'swr'
import { FC } from 'react'
import { Notification, NotificationStatus } from '@supabase/shared-types/out/notifications'
@@ -6,38 +7,65 @@ import { useStore } from 'hooks'
import { Project } from 'types'
import { formatNotificationCTAText, formatNotificationText } from './NotificationRows.utils'
import NotificationActions from './NotificationActions'
import { get } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
interface Props {
notification: Notification
onSelectRestartProject: (project: Project, notification: Notification) => void
onSelectApplyMigration: (project: Project, notification: Notification) => void
onSelectRollbackMigration: (project: Project, notification: Notification) => void
onSelectFinalizeMigration: (project: Project, notification: Notification) => void
}
const NotificationRow: FC<Props> = ({ notification, onSelectRestartProject }) => {
const NotificationRow: FC<Props> = ({
notification,
onSelectRestartProject,
onSelectApplyMigration,
onSelectRollbackMigration,
onSelectFinalizeMigration,
}) => {
const { app } = useStore()
const [project] = app.projects.list((project: Project) => project.id === notification.project_id)
const insertedAt = dayjs(notification.inserted_at).format('DD MMM YYYY, HH:mma')
const changelogLink = (notification.data as any).changelog_link
const availableActions = notification.meta?.actions_available ?? []
// [Joshen TODO] This should be removed after 5th November when the migration notifications
// have been removed, double check with Qiao before removing.
// Relevant PR: https://github.com/supabase/supabase/pull/9229
const { data: ownerReassignStatus } = useSWR(
(notification.data as any).upgrade_type === 'schema-migration'
? `${API_URL}/database/${project.ref}/owner-reassign`
: null,
get
)
return (
<div className="flex py-2">
<div className="flex w-[50px] justify-center">
<div className="flex min-w-[50px] justify-center">
{notification.notification_status !== NotificationStatus.Seen && (
<div className="mt-1.5 h-2 w-2 rounded-full bg-green-900" />
)}
</div>
<div className="mr-8 flex items-center space-x-4">
<div className="space-y-1">
{formatNotificationText(project, notification)}
<p className="text-sm">{formatNotificationCTAText(availableActions)}</p>
<div className="flex-grow mr-8 flex items-center space-x-4">
<div className="w-[70%] space-y-2">
{formatNotificationText(project, notification, ownerReassignStatus)}
{formatNotificationCTAText(availableActions, ownerReassignStatus)}
<p className="text-scale-1100 text-sm !mt-2">{insertedAt}</p>
</div>
<div className="col-span-3 flex items-center justify-end">
<div className="w-[30%] col-span-3 flex items-center justify-end">
{availableActions.length > 0 && (
<NotificationActions
project={project}
changelogLink={changelogLink}
ownerReassignStatus={ownerReassignStatus}
availableActions={availableActions}
onSelectRestartProject={() => onSelectRestartProject(project, notification)}
onSelectApplyMigration={() => onSelectApplyMigration(project, notification)}
onSelectRollbackMigration={() => onSelectRollbackMigration(project, notification)}
onSelectFinalizeMigration={() => onSelectFinalizeMigration(project, notification)}
/>
)}
</div>
@@ -1,6 +1,8 @@
import dayjs from 'dayjs'
import { Project } from 'types'
import {
Action,
ActionReason,
ActionType,
ExtensionsUpgrade,
Notification,
@@ -12,7 +14,11 @@ import {
import { IconArrowRight, IconExternalLink } from 'ui'
import Link from 'next/link'
export const formatNotificationText = (project: Project, notification: Notification) => {
export const formatNotificationText = (
project: Project,
notification: Notification,
ownerReassignStatus?: any
) => {
const projectName = project.name
if (notification.data.name === NotificationName.ProjectExceedingTierLimit) {
@@ -44,6 +50,22 @@ export const formatNotificationText = (project: Project, notification: Notificat
New version of "{name}" ({version_to}) is now available for project "{projectName}".
</p>
)
} else if (upgrade_type === 'schema-migration') {
const { name, version_to } = additional as ExtensionsUpgrade
return (
<div className="text-sm space-y-1">
<p>A new schema migration is available for your project "{projectName}".</p>
<ol className="list-disc pl-6">
<li>
<div className="flex items-center space-x-1">
<p>{name}</p>
<IconArrowRight size={12} strokeWidth={2} />
<p>{version_to}</p>
</div>
</li>
</ol>
</div>
)
}
return ''
} else if (notification.data.name === NotificationName.PostgresqlUpgradeCompleted) {
@@ -65,15 +87,33 @@ export const formatNotificationText = (project: Project, notification: Notificat
{projectName}".
</p>
)
} else if (upgrade_type === 'schema-migration') {
const { version_to } = additional
if (ownerReassignStatus?.desired === 'unmigrated') {
return (
<p className="text-sm">
The schema migration "{version_to}" will be applied for project "{projectName}" within a
few days. You may opt to apply the changes now, or it'll be done so automatically.
</p>
)
} else if (ownerReassignStatus?.desired === 'temp_role') {
return (
<p className="text-sm">
The schema migration "{version_to}" will be finalized for project "{projectName}" within
a few days. You may opt to finalize the changes now, or it'll be done so automatically.
</p>
)
} else {
return (
<p className="text-sm">
The schema migration "{version_to}" has been successfully applied for project "
{projectName}".
</p>
)
}
}
} else if (notification.data.name === NotificationName.ProjectUpdateCompleted) {
const { upgrades } = notification.data
const upgradesText = upgrades
.map(
(upgrade: ServiceUpgrade) =>
`${upgrade.name}: ${upgrade.version_to} ${upgrade.changelog_link}`
)
.reduce((a: string, b: string) => `${a}\n${b}`)
return (
<div>
<p className="text-sm">
@@ -117,17 +157,57 @@ export const formatNotificationText = (project: Project, notification: Notificat
}
}
export const formatNotificationCTAText = (availableActions: Action[]) => {
export const formatNotificationCTAText = (
availableActions: Action[],
ownerReassignStatus?: any
) => {
const [action] = availableActions
if (!action) return ''
if (!action) return <p className="text-sm"></p>
switch (action.action_type) {
case ActionType.SchedulePostgresRestart:
return 'Restart your project to get the latest updates.'
return <p className="text-sm">Restart your project to get the latest updates.</p>
case ActionType.UpgradeProjectToPro:
return 'Upgrade your project to ensure continued availability.'
return <p className="text-sm">Upgrade your project to ensure continued availability.</p>
case ActionType.PgBouncerRestart:
return 'Restart your connection pooler to get the latest updates.'
return <p className="text-sm">Restart your connection pooler to get the latest updates.</p>
case ActionType.MigratePostgresSchema:
if (action.deadline) {
if (ownerReassignStatus?.desired === 'migrated') {
return (
<p className="text-sm space-x-1">
This patch was applied on{' '}
{dayjs(new Date(ownerReassignStatus.migrated_at ?? action.deadline)).format('DD MMM YYYY, HH:mma')}
</p>
)
} else if (ownerReassignStatus?.desired === 'temp_role') {
if (action.reason === ActionReason.Finalize) {
return (
<p className="text-sm space-x-1">
This patch will be automatically applied after{' '}
{dayjs(new Date(action.deadline)).format('DD MMM YYYY, HH:mma')}
</p>
)
} else {
return (
<p className="text-sm space-x-1">
This patch was applied on{' '}
{dayjs(new Date(ownerReassignStatus.modified_at)).format('DD MMM YYYY, HH:mma')}
</p>
)
}
} else {
return (
<p className="text-sm space-x-1">
This patch will be automatically applied after{' '}
{dayjs(new Date(action.deadline)).format('DD MMM YYYY, HH:mma')}
</p>
)
}
} else {
return ''
}
default:
return ''
}
@@ -1,6 +1,7 @@
import dayjs from 'dayjs'
import { FC, Fragment, useState } from 'react'
import { useRouter } from 'next/router'
import { Button, IconBell, Popover } from 'ui'
import { Alert, Button, IconBell, Popover, IconArrowRight } from 'ui'
import {
Notification,
NotificationStatus,
@@ -9,7 +10,7 @@ import {
import { Project } from 'types'
import { useNotifications, useStore } from 'hooks'
import { patch, post } from 'lib/common/fetch'
import { delete_, patch, post } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import NotificationRow from './NotificationRow'
import ConfirmModal from 'components/ui/Dialogs/ConfirmDialog'
@@ -22,6 +23,10 @@ const NotificationsPopover: FC<Props> = () => {
const { notifications, refresh } = useNotifications()
const [projectToRestart, setProjectToRestart] = useState<Project>()
const [projectToApplyMigration, setProjectToApplyMigration] = useState<Project>()
const [projectToRollbackMigration, setProjectToRollbackMigration] = useState<Project>()
const [projectToFinalizeMigration, setProjectToFinalizeMigration] = useState<Project>()
const [targetNotification, setTargetNotification] = useState<Notification>()
if (!notifications) return <></>
@@ -53,6 +58,7 @@ const NotificationsPopover: FC<Props> = () => {
const serviceNamesByActionName: Record<string, string> = {
[ActionType.PgBouncerRestart]: 'pgbouncer',
[ActionType.SchedulePostgresRestart]: 'postgresql',
[ActionType.MigratePostgresSchema]: 'postgresql',
}
const services: string[] = targetNotification.meta.actions_available
.map((action) => action.action_type)
@@ -82,6 +88,76 @@ const NotificationsPopover: FC<Props> = () => {
setTargetNotification(undefined)
}
// [Joshen/Qiao] These are all very specific to the upcoming security patch
// https://github.com/supabase/supabase/discussions/9314
// We probably need to revisit this again when we're planning to push out the next wave of
// notifications. Ideally, we should allow these to be more flexible and configurable
// Perhaps the URLs could come from the notification themselves if the actions
// require an external API call, then we just need one method instead of individual ones like this
const onConfirmProjectApplyMigration = async () => {
if (!projectToApplyMigration) return
const res = await post(`${API_URL}/database/${projectToApplyMigration.ref}/owner-reassign`, {})
if (!res.error) {
app.projects.fetchDetail(projectToApplyMigration.ref)
ui.setNotification({
category: 'success',
message: `Succesfully applied migration for project "${projectToApplyMigration.name}"`,
})
} else {
ui.setNotification({
error: res.error,
category: 'error',
message: `Failed to apply migration: ${res.error.message}`,
})
}
setProjectToApplyMigration(undefined)
}
const onConfirmProjectRollbackMigration = async () => {
if (!projectToRollbackMigration) return
const res = await delete_(
`${API_URL}/database/${projectToRollbackMigration.ref}/owner-reassign`,
{}
)
if (!res.error) {
app.projects.fetchDetail(projectToRollbackMigration.ref)
ui.setNotification({
category: 'success',
message: `Succesfully rolled back migration for project "${projectToRollbackMigration.name}"`,
})
} else {
ui.setNotification({
error: res.error,
category: 'error',
message: `Failed to roll back migration: ${res.error.message}`,
})
}
setProjectToRollbackMigration(undefined)
}
const onConfirmProjectFinalizeMigration = async () => {
if (!projectToFinalizeMigration) return
const res = await patch(
`${API_URL}/database/${projectToFinalizeMigration.ref}/owner-reassign`,
{}
)
if (!res.error) {
app.projects.fetchDetail(projectToFinalizeMigration.ref)
ui.setNotification({
category: 'success',
message: `Succesfully finalized migration for project "${projectToFinalizeMigration.name}"`,
})
} else {
ui.setNotification({
error: res.error,
category: 'error',
message: `Failed to finalize migration: ${res.error.message}`,
})
}
setProjectToFinalizeMigration(undefined)
}
return (
<>
<Popover
@@ -91,7 +167,7 @@ const NotificationsPopover: FC<Props> = () => {
sideOffset={8}
onOpenChange={onOpenChange}
overlay={
<div className="w-[500px]">
<div className="w-[400px] lg:w-[700px]">
<div className="flex items-center justify-between border-b border-gray-500 bg-gray-400 px-4 py-2">
<p className="text-sm">Notifications</p>
{/* Area for improvement: Paginate notifications and show in a side panel */}
@@ -115,6 +191,18 @@ const NotificationsPopover: FC<Props> = () => {
setProjectToRestart(project)
setTargetNotification(notification)
}}
onSelectApplyMigration={(project, notification) => {
setProjectToApplyMigration(project)
setTargetNotification(notification)
}}
onSelectRollbackMigration={(project, notification) => {
setProjectToRollbackMigration(project)
setTargetNotification(notification)
}}
onSelectFinalizeMigration={(project, notification) => {
setProjectToFinalizeMigration(project)
setTargetNotification(notification)
}}
/>
{i !== notifications.length - 1 && <Popover.Seperator />}
</Fragment>
@@ -151,6 +239,100 @@ const NotificationsPopover: FC<Props> = () => {
onSelectCancel={() => setProjectToRestart(undefined)}
onSelectConfirm={onConfirmProjectRestart}
/>
<ConfirmModal
size="large"
visible={projectToApplyMigration !== undefined}
title={`Apply schema migration for "${projectToApplyMigration?.name}"`}
// @ts-ignore
description={
<div className="text-scale-1200 space-y-2">
<div className="space-y-1">
<p>The following schema migration will be applied to the project</p>
<ol className="list-disc pl-6">
<li>
<div className="flex items-center space-x-1">
<p>{(targetNotification?.data as any)?.additional?.name}</p>
<IconArrowRight size={12} strokeWidth={2} />
<p>{(targetNotification?.data as any)?.additional?.version_to}</p>
</div>
</li>
</ol>
</div>
<p>
This change can be rolled back anytime up till{' '}
{dayjs(
new Date(targetNotification?.meta.actions_available?.[0]?.deadline ?? 0)
).format('DD MMM YYYY, HH:mma ZZ')}
, after which the changes will be finalized and can no longer be undone.
</p>
</div>
}
buttonLabel="Confirm"
buttonLoadingLabel="Confirm"
onSelectCancel={() => setProjectToApplyMigration(undefined)}
onSelectConfirm={onConfirmProjectApplyMigration}
/>
<ConfirmModal
size="medium"
visible={projectToRollbackMigration !== undefined}
title={`Rollback schema migration for "${projectToRollbackMigration?.name}"`}
// @ts-ignore
description={
<div className="text-scale-1200 space-y-2">
<div className="space-y-1">
<p>The following schema migration will be rolled back for the project</p>
<ol className="list-disc pl-6">
<li>
<div className="flex items-center space-x-1">
<p>{(targetNotification?.data as any)?.additional?.name}</p>
<IconArrowRight size={12} strokeWidth={2} />
<p>{(targetNotification?.data as any)?.additional?.version_to}</p>
</div>
</li>
</ol>
</div>
<p>
This migration however will still be applied and finalized after{' '}
{dayjs(
new Date(targetNotification?.meta.actions_available?.[0]?.deadline ?? 0)
).format('DD MMM YYYY, HH:mma ZZ')}
, after which the changes can no longer be undone.
</p>
</div>
}
buttonLabel="Confirm"
buttonLoadingLabel="Confirm"
onSelectCancel={() => setProjectToRollbackMigration(undefined)}
onSelectConfirm={onConfirmProjectRollbackMigration}
/>
<ConfirmModal
danger
size="small"
visible={projectToFinalizeMigration !== undefined}
title={`Finalize schema migration for "${projectToFinalizeMigration?.name}"`}
// @ts-ignore
description={
<div className="text-scale-1200 space-y-4">
<Alert withIcon variant="warning" title="This action canot be undone" />
<div className="space-y-1">
<p>The following schema migration will be finalized for the project</p>
<ol className="list-disc pl-6">
<li>
<div className="flex items-center space-x-1">
<p>{(targetNotification?.data as any)?.additional?.name}</p>
<IconArrowRight size={12} strokeWidth={2} />
<p>{(targetNotification?.data as any)?.additional?.version_to}</p>
</div>
</li>
</ol>
</div>
</div>
}
buttonLabel="Confirm"
buttonLoadingLabel="Confirm"
onSelectCancel={() => setProjectToFinalizeMigration(undefined)}
onSelectConfirm={onConfirmProjectFinalizeMigration}
/>
</>
)
}
+1 -1
View File
@@ -27,7 +27,7 @@
"@stripe/stripe-js": "^1.25.0",
"@supabase/postgres-meta": "^0.26.1",
"@supabase/react-data-grid": "^7.1.0-beta.7",
"@supabase/shared-types": "^0.1.25",
"@supabase/shared-types": "^0.1.29",
"@supabase/supabase-js": "^2.0.0-rc.2",
"ajv": "^8.6.3",
"analytics-node": "^3.5.0",