Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion apps/studio/components/interfaces/App/AppBannerWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import { PropsWithChildren } from 'react'

import { useIncidentStatusQuery } from '@/data/platform/incident-status-query'
import { useFlag } from 'common'
import { ClockSkewBanner } from 'components/layouts/AppLayout/ClockSkewBanner'
import { IncidentBanner } from 'components/layouts/AppLayout/IncidentBanner'
import { NoticeBanner } from 'components/layouts/AppLayout/NoticeBanner'
import { OrganizationResourceBanner } from '../Organization/HeaderBanner'

export const AppBannerWrapper = ({ children }: PropsWithChildren<{}>) => {
const { data: incidents } = useIncidentStatusQuery()

const ongoingIncident =
useFlag('ongoingIncident') || process.env.NEXT_PUBLIC_ONGOING_INCIDENT === 'true'
useFlag('ongoingIncident') ||
process.env.NEXT_PUBLIC_ONGOING_INCIDENT === 'true' ||
(incidents?.length ?? 0) > 0
const showNoticeBanner = useFlag('showNoticeBanner')
const clockSkewBanner = useFlag('clockSkewBanner')

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { isEmpty, isUndefined, noop } from 'lodash'
import { useState } from 'react'
import { toast } from 'sonner'

import { useTableApiAccessPrivilegesMutation } from '@/data/privileges/table-api-access-mutation'
import { useDataApiGrantTogglesEnabled } from '@/hooks/misc/useDataApiGrantTogglesEnabled'
import { type ApiPrivilegesByRole } from '@/lib/data-api-types'
import type { DeepReadonly, Prettify } from '@/lib/type-helpers'
import { useParams } from 'common'
import { type GeneratedPolicy } from 'components/interfaces/Auth/Policies/Policies.utils'
import { databasePoliciesKeys } from 'data/database-policies/keys'
Expand Down Expand Up @@ -33,7 +37,7 @@ import { usePHFlag } from 'hooks/ui/useFlag'
import { useUrlState } from 'hooks/ui/useUrlState'
import { useTrack } from 'lib/telemetry/track'
import { useGetImpersonatedRoleState } from 'state/role-impersonation-state'
import { useTableEditorStateSnapshot } from 'state/table-editor'
import { useTableEditorStateSnapshot, type TableEditorState } from 'state/table-editor'
import { createTabId, useTabsStateSnapshot } from 'state/tabs'
import type { Dictionary } from 'types'
import { SonnerProgress } from 'ui'
Expand All @@ -57,6 +61,10 @@ import {
updateTable,
} from './SidePanelEditor.utils'
import { SpreadsheetImport } from './SpreadsheetImport/SpreadsheetImport'
import {
useTableApiAccessHandlerWithHistory,
type TableApiAccessParams,
} from './TableEditor/ApiAccessToggle'
import { TableEditor } from './TableEditor/TableEditor'
import type { ImportContent } from './TableEditor/TableEditor.types'

Expand Down Expand Up @@ -89,6 +97,12 @@ type SaveTableParamsExisting = SaveTableParamsBase & {
}

type SaveTablePayloadBase = {
/**
* Comment to set on the table
*
* `null` removes existing comment
* `undefined` leaves comment unchanged
*/
comment?: string | null
}

Expand All @@ -106,14 +120,55 @@ type SaveTablePayloadExisting = SaveTablePayloadBase & {
rls_enabled?: boolean
}

type SaveTableConfiguration = {
type SaveTableConfiguration = Prettify<{
tableId?: number
importContent?: ImportContent
isRLSEnabled: boolean
isRealtimeEnabled: boolean
isDuplicateRows: boolean
existingForeignKeyRelations: ForeignKeyConstraint[]
primaryKey?: Constraint
}>

const DUMMY_TABLE_API_ACCESS_PARAMS: TableApiAccessParams = {
type: 'new',
}

const createTableApiAccessHandlerParams = ({
enabled,
snap,
selectedTable,
}: {
enabled: boolean
snap: DeepReadonly<TableEditorState>
selectedTable?: PostgresTable
}): TableApiAccessParams | undefined => {
if (!enabled) return undefined

const tableSidePanel = snap.sidePanel?.type === 'table' ? snap.sidePanel : undefined
if (!tableSidePanel) return undefined

if (tableSidePanel.mode === 'new') {
return {
type: 'new',
}
}

if (!selectedTable) return undefined

if (tableSidePanel.mode === 'duplicate') {
return {
type: 'duplicate',
templateSchemaName: selectedTable.schema,
templateTableName: selectedTable.name,
}
}

return {
type: 'edit',
schemaName: selectedTable.schema,
tableName: selectedTable.name,
}
}

export interface SidePanelEditorProps {
Expand Down Expand Up @@ -142,6 +197,8 @@ export const SidePanelEditor = ({
const { data: project } = useSelectedProjectQuery()
const { data: org } = useSelectedOrganizationQuery()
const getImpersonatedRoleState = useGetImpersonatedRoleState()

const isApiGrantTogglesEnabled = useDataApiGrantTogglesEnabled()
const generatePoliciesFlag = usePHFlag<string>('tableCreateGeneratePolicies')

const [isEdited, setIsEdited] = useState<boolean>(false)
Expand All @@ -151,6 +208,19 @@ export const SidePanelEditor = ({
connectionString: project?.connectionString,
})

const tableApiAccessParams = createTableApiAccessHandlerParams({
enabled: isApiGrantTogglesEnabled,
snap,
selectedTable,
})
const apiAccessToggleHandler = useTableApiAccessHandlerWithHistory(
// Dummy params used to appease TypeScript, actually gated by enabled flag
tableApiAccessParams ?? DUMMY_TABLE_API_ACCESS_PARAMS,
{
enabled: tableApiAccessParams !== undefined,
}
)

const { confirmOnClose, modalProps: closeConfirmationModalProps } = useConfirmOnClose({
checkIsDirty: () => isEdited,
onClose: () => {
Expand Down Expand Up @@ -179,6 +249,9 @@ export const SidePanelEditor = ({
const { mutateAsync: updatePublication } = useDatabasePublicationUpdateMutation({
onError: () => {},
})
const { mutateAsync: updateApiPrivileges } = useTableApiAccessPrivilegesMutation({
onError: () => {}, // Errors handled inline
})

const isDuplicating = snap.sidePanel?.type === 'table' && snap.sidePanel.mode === 'duplicate'

Expand Down Expand Up @@ -480,6 +553,26 @@ export const SidePanelEditor = ({
}
}

const updateTableApiAccess = async (
table: RetrieveTableResult,
privileges: DeepReadonly<ApiPrivilegesByRole>
) => {
if (!project) return console.error('Project is required')

try {
await updateApiPrivileges({
projectRef: project.ref,
connectionString: project.connectionString ?? undefined,
relationId: table.id,
privileges,
})
} catch (error) {
const message = error instanceof Error ? error.message : undefined
const toastDetail = message ? `: ${message}` : ''
toast.error(`Failed to update API access privileges for ${table.name}${toastDetail}`)
}
}

const saveTable = async ({
action,
payload,
Expand All @@ -492,6 +585,19 @@ export const SidePanelEditor = ({
let toastId
let saveTableError = false

if (isApiGrantTogglesEnabled && !apiAccessToggleHandler.isSuccess) {
if (apiAccessToggleHandler.isPending) {
toast.info(
'Cannot save table yet because Data API settings are still loading. Please try again in a moment.'
)
} else {
toast.error(
'Cannot save table because there was an error loading Data API settings. Please refresh the page and try again.'
)
}
return
}

const {
importContent,
isRLSEnabled,
Expand Down Expand Up @@ -521,6 +627,15 @@ export const SidePanelEditor = ({

if (isRealtimeEnabled) await updateTableRealtime(table, true)

if (isApiGrantTogglesEnabled) {
const privilegesToSet = apiAccessToggleHandler.data?.schemaExposed
? apiAccessToggleHandler.data.privileges
: undefined
if (privilegesToSet) {
await updateTableApiAccess(table, privilegesToSet)
}
}

// Invalidate queries for table creation
await Promise.all([
queryClient.invalidateQueries({
Expand Down Expand Up @@ -577,6 +692,15 @@ export const SidePanelEditor = ({
})
if (isRealtimeEnabled) await updateTableRealtime(table, isRealtimeEnabled)

if (isApiGrantTogglesEnabled) {
const privilegesToSet = apiAccessToggleHandler.data?.schemaExposed
? apiAccessToggleHandler.data.privileges
: undefined
if (privilegesToSet) {
await updateTableApiAccess(table, privilegesToSet)
}
}

await Promise.all([
queryClient.invalidateQueries({
queryKey: tableKeys.list(project?.ref, table.schema, includeColumns),
Expand Down Expand Up @@ -614,6 +738,14 @@ export const SidePanelEditor = ({
}
if (isTableLike(table)) {
await updateTableRealtime(table, isRealtimeEnabled)
if (isApiGrantTogglesEnabled) {
const privilegesToSet = apiAccessToggleHandler.data?.schemaExposed
? apiAccessToggleHandler.data.privileges
: undefined
if (privilegesToSet) {
await updateTableApiAccess(table, privilegesToSet)
}
}
}

if (hasError) {
Expand Down Expand Up @@ -761,6 +893,7 @@ export const SidePanelEditor = ({
closePanel={onClosePanel}
saveChanges={saveTable}
updateEditorDirty={() => setIsEdited(true)}
apiAccessToggleHandler={apiAccessToggleHandler}
/>
<SchemaEditor
visible={snap.sidePanel?.type === 'schema'}
Expand Down
Loading
Loading