import * as Tooltip from '@radix-ui/react-tooltip' import saveAs from 'file-saver' import { Copy, Download, Edit, Eye, Lock, MoreHorizontal, Table2, Trash, Unlock, } from 'lucide-react' import Link from 'next/link' import Papa from 'papaparse' import toast from 'react-hot-toast' import { IS_PLATFORM } from 'common' import { parseSupaTable } from 'components/grid/SupabaseGrid.utils' import { getEntityLintDetails } from 'components/interfaces/TableGridEditor/TableEntity.utils' import type { ItemRenderer } from 'components/ui/InfiniteList' import { ENTITY_TYPE } from 'data/entity-types/entity-type-constants' import type { Entity } from 'data/entity-types/entity-type-query' import { useProjectLintsQuery } from 'data/lint/lint-query' import { fetchAllTableRows } from 'data/table-rows/table-rows-query' import { getTable } from 'data/tables/table-query' import { useTableEditorStateSnapshot } from 'state/table-editor' import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, cn, } from 'ui' import { useProjectContext } from '../ProjectLayout/ProjectContext' export interface EntityListItemProps { id: number projectRef: string isLocked: boolean } const EntityListItem: ItemRenderer = ({ id, projectRef, item: entity, isLocked, }) => { const { project } = useProjectContext() const snap = useTableEditorStateSnapshot() const isActive = Number(id) === entity.id const { data: lints = [] } = useProjectLintsQuery({ projectRef: project?.ref, }) const tableHasLints: boolean = getEntityLintDetails( entity.name, 'rls_disabled_in_public', ['ERROR'], lints, snap.selectedSchemaName ).hasLint const viewHasLints: boolean = getEntityLintDetails( entity.name, 'security_definer_view', ['ERROR', 'WARN'], lints, snap.selectedSchemaName ).hasLint const materializedViewHasLints: boolean = getEntityLintDetails( entity.name, 'materialized_view_in_api', ['ERROR', 'WARN'], lints, snap.selectedSchemaName ).hasLint const formatTooltipText = (entityType: string) => { return Object.entries(ENTITY_TYPE) .find(([, value]) => value === entityType)?.[0] ?.toLowerCase() ?.split('_') ?.join(' ') } const exportTableAsCSV = async () => { if (IS_PLATFORM && !project?.connectionString) { return console.error('Connection string is required') } const toastId = toast.loading(`Exporting ${entity.name} as CSV...`) try { const table = await getTable({ id: entity.id, projectRef, connectionString: project?.connectionString, }) const supaTable = table && parseSupaTable( { table: table, columns: table.columns ?? [], primaryKeys: table.primary_keys, relationships: table.relationships, }, [] ) const rows = await fetchAllTableRows({ projectRef, connectionString: project?.connectionString, table: supaTable, }) const formattedRows = rows.map((row) => { const formattedRow = row Object.keys(row).map((column) => { if (typeof row[column] === 'object' && row[column] !== null) formattedRow[column] = JSON.stringify(formattedRow[column]) }) return formattedRow }) if (formattedRows.length > 0) { const csv = Papa.unparse(formattedRows, { columns: supaTable.columns.map((column) => column.name), }) const csvData = new Blob([csv], { type: 'text/csv;charset=utf-8;' }) saveAs(csvData, `${entity!.name}_rows.csv`) } toast.success(`Successfully exported ${entity.name} as CSV`, { id: toastId }) } catch (error: any) { toast.error(`Failed to export table: ${error.message}`, { id: toastId }) } } const EntityTooltipTrigger = ({ entity }: { entity: Entity }) => { let tooltipContent = null switch (entity.type) { case ENTITY_TYPE.TABLE: if (tableHasLints) { tooltipContent = 'RLS Disabled' } break case ENTITY_TYPE.VIEW: if (viewHasLints) { tooltipContent = 'Security Definer view' } break case ENTITY_TYPE.MATERIALIZED_VIEW: if (materializedViewHasLints) { tooltipContent = 'Security Definer view' } break case ENTITY_TYPE.FOREIGN_TABLE: tooltipContent = 'RLS is not enforced on foreign tables' break default: break } if (tooltipContent) { return ( {tooltipContent} ) } return null } return ( {entity.type === ENTITY_TYPE.TABLE ? ( ) : entity.type === ENTITY_TYPE.VIEW ? ( ) : (
{Object.entries(ENTITY_TYPE) .find(([, value]) => value === entity.type)?.[0]?.[0] ?.toUpperCase()}
)}
{formatTooltipText(entity.type)}
{entity.name}
{entity.type === ENTITY_TYPE.TABLE && isActive && !isLocked && ( { e.stopPropagation() snap.onEditTable() }} > Edit Table { e.stopPropagation() snap.onDuplicateTable() }} > Duplicate Table View Policies { e.stopPropagation() exportTableAsCSV() }} > Export as CSV { e.stopPropagation() snap.onDeleteTable() }} > Delete Table )} ) } export default EntityListItem