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
3 changes: 0 additions & 3 deletions apps/docs/content/guides/platform/credits.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@ As an example, if you start a Pro Plan subscription on January 1 and downgrade t
## Credit top-ups

You can top up credits at any time, with a maximum of <Price price="2000" /> per top-up. These credits do not expire and are non-refundable.

If you have any outstanding invoices, we’ll automatically use your credits to pay them off. Any remaining credits will be applied to future invoices.

You may want to consider this option to avoid issues with recurring payments, gain more control over how often your credit card is charged, and potentially make things easier for your accounting department.

<Admonition type="note">
Expand Down
11 changes: 2 additions & 9 deletions apps/studio/components/grid/SupabaseGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,10 @@ export const SupabaseGrid = ({
},
{
keepPreviousData: true,
retryDelay: (retryAttempt, error: any) => {
retry: (_, error: any) => {
const doesNotExistError = error && error.message?.includes('does not exist')
const tooManyRequestsError = error.message?.includes('Too Many Requests')
const vaultError = error.message?.includes('query vault failed')

if (doesNotExistError) onApplySorts([])

if (retryAttempt > 3 || doesNotExistError || tooManyRequestsError || vaultError) {
return Infinity
}
return 5000
return false
},
}
)
Expand Down
14 changes: 11 additions & 3 deletions apps/studio/components/grid/components/common/DropdownControl.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { DropdownMenuItemTooltip } from 'components/ui/DropdownMenuItemTooltip'
import { PropsWithChildren } from 'react'
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from 'ui'
import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from 'ui'

interface DropdownControlProps {
options: {
value: string | number
label: string
postLabel?: string
preLabel?: string
disabled?: boolean
tooltip?: string
}[]
onSelect: (value: string | number) => void
side?: 'bottom' | 'left' | 'top' | 'right' | undefined
Expand All @@ -30,13 +33,18 @@ export const DropdownControl = ({
{options.length === 0 && <p className="dropdown-control__empty-text">No more items</p>}
{options.map((x) => {
return (
<DropdownMenuItem key={x.value} onClick={() => onSelect(x.value)}>
<DropdownMenuItemTooltip
key={x.value}
disabled={x.disabled}
tooltip={{ content: { side: 'right', text: x.tooltip } }}
onClick={() => onSelect(x.value)}
>
<div className="flex items-center gap-2">
{x.preLabel && <span className="grow text-foreground-lighter">{x.preLabel}</span>}
<span>{x.label}</span>
{x.postLabel && <span className="text-foreground-lighter">{x.postLabel}</span>}
</div>
</DropdownMenuItem>
</DropdownMenuItemTooltip>
)
})}
</div>
Expand Down
56 changes: 4 additions & 52 deletions apps/studio/components/grid/components/grid/Grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import { ref as valtioRef } from 'valtio'

import { handleCopyCell } from 'components/grid/SupabaseGrid.utils'
import { formatForeignKeys } from 'components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.utils'
import AlertError from 'components/ui/AlertError'
import { InlineLink } from 'components/ui/InlineLink'
import { useForeignKeyConstraintsQuery } from 'data/database/foreign-key-constraints-query'
import { ENTITY_TYPE } from 'data/entity-types/entity-type-constants'
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
Expand All @@ -15,10 +13,10 @@ import { useCsvFileDrop } from 'hooks/ui/useCsvFileDrop'
import { useTableEditorStateSnapshot } from 'state/table-editor'
import { useTableEditorTableStateSnapshot } from 'state/table-editor-table'
import { Button, cn } from 'ui'
import { Admonition } from 'ui-patterns'
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
import type { Filter, GridProps, SupaRow } from '../../types'
import { useOnRowsChange } from './Grid.utils'
import { GridError } from './GridError'
import RowRenderer from './RowRenderer'

const rowKeyGetter = (row: SupaRow) => {
Expand Down Expand Up @@ -60,7 +58,6 @@ export const Grid = memo(

const { data: org } = useSelectedOrganizationQuery()
const { data: project } = useSelectedProjectQuery()
const isBranch = project?.parent_project_ref !== undefined

const onRowsChange = useOnRowsChange(rows)

Expand All @@ -80,9 +77,6 @@ export const Grid = memo(
const isForeignTable = tableEntityType === ENTITY_TYPE.FOREIGN_TABLE
const isTableEmpty = (rows ?? []).length === 0

const isForeignTableMissingVaultKeyError =
isForeignTable && isError && error.message.includes('query vault failed')

const { mutate: sendEvent } = useSendEventMutation()

const { isDraggedOver, onDragOver, onFileDrop } = useCsvFileDrop({
Expand Down Expand Up @@ -155,51 +149,9 @@ export const Grid = memo(
className="absolute top-9 p-2 w-full z-[1] pointer-events-none"
>
{isLoading && <GenericSkeletonLoader />}
{isError ? (
isForeignTableMissingVaultKeyError ? (
<Admonition
type="warning"
className="pointer-events-auto"
title="Failed to retrieve rows from foreign table"
>
<p>
The key that's used to retrieve data from your foreign table is either
incorrect or missing. Verify the key in your{' '}
<InlineLink href={`/project/${project?.ref}/integrations?category=wrapper`}>
wrapper's settings
</InlineLink>{' '}
or in{' '}
<InlineLink href={`/project/${project?.ref}/integrations/vault/overview`}>
Vault
</InlineLink>
.
</p>
{isBranch && (
<p>
Note: Vault keys from the main project do not sync to branches. You may add
them manually into{' '}
<InlineLink href={`/project/${project?.ref}/integrations/vault/overview`}>
Vault
</InlineLink>{' '}
if you want to query foreign tables while on a branch.
</p>
)}
</Admonition>
) : (
<AlertError
className="pointer-events-auto"
error={error}
subject="Failed to retrieve rows from table"
>
{filters.length > 0 && (
<p>
Verify that the filter values are correct, as the error may stem from an
incorrectly applied filter
</p>
)}
</AlertError>
)
) : null}

{isError && <GridError error={error} />}

{isSuccess && (
<>
{(filters ?? []).length === 0 ? (
Expand Down
147 changes: 147 additions & 0 deletions apps/studio/components/grid/components/grid/GridError.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { useParams } from 'common'
import { useTableFilter } from 'components/grid/hooks/useTableFilter'
import { useTableSort } from 'components/grid/hooks/useTableSort'
import AlertError from 'components/ui/AlertError'
import { InlineLink } from 'components/ui/InlineLink'
import { ENTITY_TYPE } from 'data/entity-types/entity-type-constants'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { useTableEditorTableStateSnapshot } from 'state/table-editor-table'
import { Button } from 'ui'
import { Admonition } from 'ui-patterns'

export const GridError = ({ error }: { error?: any }) => {
const { filters } = useTableFilter()
const { sorts } = useTableSort()
const snap = useTableEditorTableStateSnapshot()

const tableEntityType = snap.originalTable?.entity_type
const isForeignTable = tableEntityType === ENTITY_TYPE.FOREIGN_TABLE

const isForeignTableMissingVaultKeyError =
isForeignTable && error?.message?.includes('query vault failed')

const isInvalidSyntaxError =
filters.length > 0 && error?.message?.includes('invalid input syntax')

const isInvalidOrderingOperatorError =
sorts.length > 0 && error?.message?.includes('identify an ordering operator')

if (isForeignTableMissingVaultKeyError) {
return <ForeignTableMissingVaultKeyError />
} else if (isInvalidSyntaxError) {
return <InvalidSyntaxError error={error} />
} else if (isInvalidOrderingOperatorError) {
return <InvalidOrderingOperatorError error={error} />
}

return <GeneralError error={error} />
}

const ForeignTableMissingVaultKeyError = () => {
const { ref } = useParams()
const { data: project } = useSelectedProjectQuery()
const isBranch = project?.parent_project_ref !== undefined

return (
<Admonition
type="warning"
className="pointer-events-auto"
title="Failed to retrieve rows from foreign table"
>
<p>
The key that's used to retrieve data from your foreign table is either incorrect or missing.
Verify the key in your{' '}
<InlineLink href={`/project/${ref}/integrations?category=wrapper`}>
wrapper's settings
</InlineLink>{' '}
or in <InlineLink href={`/project/${ref}/integrations/vault/overview`}>Vault</InlineLink>.
</p>
{isBranch && (
<p>
Note: Vault keys from the main project do not sync to branches. You may add them manually
into <InlineLink href={`/project/${ref}/integrations/vault/overview`}>Vault</InlineLink>{' '}
if you want to query foreign tables while on a branch.
</p>
)}
</Admonition>
)
}

const InvalidSyntaxError = ({ error }: { error?: any }) => {
const { onApplyFilters } = useTableFilter()

return (
<Admonition
type="warning"
className="pointer-events-auto"
title="Invalid input syntax provided in filter value(s)"
>
<p className="!mb-0">
Unable to retrieve results as the provided value in your filter(s) doesn't match it's column
data type.
</p>
<p className="!mb-2">
Verify that your filter values are correct before applying the filters again.
</p>
<p className="text-sm text-foreground-lighter prose max-w-full !mb-4">
Error: <code className="text-xs">{error.message}</code>
</p>

<Button type="default" onClick={() => onApplyFilters([])}>
Remove filters
</Button>
</Admonition>
)
}

const InvalidOrderingOperatorError = ({ error }: { error: any }) => {
const { sorts, onApplySorts } = useTableSort()
const invalidDataType = (error?.message ?? '').split('type ').pop()
const formattedInvalidDataType = invalidDataType.includes('json')
? invalidDataType.toUpperCase()
: invalidDataType

return (
<Admonition
type="warning"
className="pointer-events-auto"
title={`Sorting is not supporting on ${sorts.length > 1 ? 'one of the selected columns' : 'the selected column'}`}
>
<p className="!mb-0">
Unable to retrieve results as sorting is not supported on{' '}
{sorts.length > 1 ? 'one of the selected columns' : 'the selected column'} due to its data
type. ({formattedInvalidDataType})
</p>
<p className="!mb-2">
Remove any sorts on columns with the data type {formattedInvalidDataType} applying the sorts
again.
</p>
<p className="text-sm text-foreground-lighter prose max-w-full !mb-4">
Error: <code className="text-xs">{error.message}</code>
</p>

<Button type="default" onClick={() => onApplySorts([])}>
Remove sorts
</Button>
</Admonition>
)
}

const GeneralError = ({ error }: { error: any }) => {
const { filters } = useTableFilter()

return (
<AlertError
className="pointer-events-auto"
error={error}
subject="Failed to retrieve rows from table"
>
{filters.length > 0 && (
<p>
Verify that the filter values are correct, as the error may stem from an incorrectly
applied filter
</p>
)}
</AlertError>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export const FilterPopoverPrimitive = ({
))}
{localFilters.length == 0 && (
<div className="space-y-1 px-3">
<h5 className="text-foreground-light">No filters applied to this view</h5>
<h5 className="text-xs text-foreground-light">No filters applied to this view</h5>
<p className="text-xs text-foreground-lighter">
Add a column below to filter the view
</p>
Expand All @@ -118,7 +118,7 @@ export const FilterPopoverPrimitive = ({
</div>
<PopoverSeparator_Shadcn_ />
<div className="px-3 flex flex-row justify-between">
<Button icon={<Plus />} type="text" onClick={onAddFilter}>
<Button icon={<Plus />} type="dashed" onClick={onAddFilter}>
Add filter
</Button>
<Button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,25 @@ export const SortPopoverPrimitive = ({
const columns = useMemo(() => {
if (!snap?.table?.columns) return []
return snap.table.columns.filter((x) => {
if (x.dataType === 'json' || x.dataType === 'jsonb') return false
const found = localSorts.find((y) => y.column == x.name)
return !found
})
}, [snap?.table?.columns, localSorts])

// Format the columns for the dropdown
const dropdownOptions = useMemo(() => {
return columns?.map((x) => ({ value: x.name, label: x.name })) || []
return (
columns?.map((x) => ({
value: x.name,
label: x.name,
postLabel: x.dataType,
disabled: x.dataType === 'json' || x.dataType === 'jsonb',
tooltip:
x.dataType === 'json' || x.dataType === 'jsonb'
? 'Sorting on JSON-based columns is currently not supported'
: '',
})) || []
)
}, [columns])

// Add a new sort
Expand Down Expand Up @@ -185,7 +195,7 @@ export const SortPopoverPrimitive = ({
))}
{localSorts.length === 0 && (
<div className="space-y-1 px-3">
<h5 className="text-foreground-light">No sorts applied to this view</h5>
<h5 className="text-xs text-foreground-light">No sorts applied to this view</h5>
<p className="text-xs text-foreground-lighter">Add a column below to sort the view</p>
</div>
)}
Expand All @@ -201,7 +211,7 @@ export const SortPopoverPrimitive = ({
>
<Button
asChild
type="text"
type="dashed"
iconRight={<ChevronDown size="14" className="text-foreground-light" />}
className="sb-grid-dropdown__item-trigger"
data-testid="table-editor-pick-column-to-sort-button"
Expand Down
Loading
Loading