This is an automated email from the ASF dual-hosted git repository.

jbonofre pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/polaris-tools.git


The following commit(s) were added to refs/heads/main by this push:
     new 126959c  Fix Some Generic Tables API Issues (#112)
126959c is described below

commit 126959c6064973afddc30ddc43cac88ca3051902
Author: Adam Christian 
<[email protected]>
AuthorDate: Sat Dec 20 01:33:14 2025 -0500

    Fix Some Generic Tables API Issues (#112)
---
 console/docker/nginx.conf              |  19 ++++
 console/src/api/catalog/tables.ts      |  39 +++++++
 console/src/pages/NamespaceDetails.tsx |   8 --
 console/src/pages/TableDetails.tsx     | 202 +++++++++++++++++++++++++++------
 console/vite.config.ts                 |  21 ++++
 5 files changed, 246 insertions(+), 43 deletions(-)

diff --git a/console/docker/nginx.conf b/console/docker/nginx.conf
index aeaca69..ff9da4a 100644
--- a/console/docker/nginx.conf
+++ b/console/docker/nginx.conf
@@ -50,6 +50,25 @@ server {
         proxy_read_timeout 60s;
     }
 
+    # Proxy Polaris-specific API requests to Polaris backend
+    # Rewrite /polaris/v1/* to /api/catalog/polaris/v1/*
+    location /polaris/ {
+        rewrite ^/polaris/(.*)$ /api/catalog/polaris/$1 break;
+        proxy_pass ${VITE_POLARIS_API_URL};
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto $scheme;
+
+        # Disable buffering for streaming responses
+        proxy_buffering off;
+
+        # Timeouts
+        proxy_connect_timeout 60s;
+        proxy_send_timeout 60s;
+        proxy_read_timeout 60s;
+    }
+
     # Serve static files
     location / {
         try_files $uri $uri/ /index.html;
diff --git a/console/src/api/catalog/tables.ts 
b/console/src/api/catalog/tables.ts
index 06a2c72..5a22e2f 100644
--- a/console/src/api/catalog/tables.ts
+++ b/console/src/api/catalog/tables.ts
@@ -194,6 +194,26 @@ export const tablesApi = {
     }))
   },
 
+  /**
+   * Get generic table details.
+   * @param prefix - The catalog name
+   * @param namespace - Namespace array (e.g., ["accounting", "tax"])
+   * @param tableName - Table name
+   */
+  getGeneric: async (
+    prefix: string,
+    namespace: string[],
+    tableName: string
+  ): Promise<unknown> => {
+    const namespaceStr = encodeNamespace(namespace)
+    const response = await apiClient
+      .getPolarisClient()
+      .get<unknown>(
+        
`/${encodeURIComponent(prefix)}/namespaces/${encodeURIComponent(namespaceStr)}/generic-tables/${encodeURIComponent(tableName)}`
+      )
+    return response.data
+  },
+
   /**
    * Create a generic table.
    * @param prefix - The catalog name
@@ -214,5 +234,24 @@ export const tablesApi = {
       )
     return response.data
   },
+
+  /**
+   * Delete a generic table.
+   * @param prefix - The catalog name
+   * @param namespace - Namespace array
+   * @param tableName - Table name
+   */
+  deleteGeneric: async (
+    prefix: string,
+    namespace: string[],
+    tableName: string
+  ): Promise<void> => {
+    const namespaceStr = encodeNamespace(namespace)
+    await apiClient
+      .getPolarisClient()
+      .delete(
+        
`/${encodeURIComponent(prefix)}/namespaces/${encodeURIComponent(namespaceStr)}/generic-tables/${encodeURIComponent(tableName)}`
+      )
+  },
 }
 
diff --git a/console/src/pages/NamespaceDetails.tsx 
b/console/src/pages/NamespaceDetails.tsx
index 66e87ed..1bc08db 100644
--- a/console/src/pages/NamespaceDetails.tsx
+++ b/console/src/pages/NamespaceDetails.tsx
@@ -445,7 +445,6 @@ export function NamespaceDetails() {
                   <TableRow>
                     <TableHead>Name</TableHead>
                     <TableHead>Type</TableHead>
-                    <TableHead>Created</TableHead>
                     <TableHead>Actions</TableHead>
                   </TableRow>
                 </TableHeader>
@@ -467,13 +466,6 @@ export function NamespaceDetails() {
                           {table.type || "Generic"}
                         </span>
                       </TableCell>
-                      <TableCell>
-                        <span className="text-muted-foreground text-sm">
-                          {table.createTime
-                            ? formatDistanceToNow(new Date(table.createTime), 
{ addSuffix: true })
-                            : "Unknown"}
-                        </span>
-                      </TableCell>
                       <TableCell>
                         <Button
                           variant="ghost"
diff --git a/console/src/pages/TableDetails.tsx 
b/console/src/pages/TableDetails.tsx
index 0f04002..cd02d14 100644
--- a/console/src/pages/TableDetails.tsx
+++ b/console/src/pages/TableDetails.tsx
@@ -19,7 +19,7 @@
 
 import { useState } from "react"
 import { useParams, useNavigate, Link } from "react-router-dom"
-import { useQuery } from "@tanstack/react-query"
+import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
 import { ArrowLeft, RefreshCw, Table as TableIcon, Pencil, Trash2 } from 
"lucide-react"
 import { tablesApi } from "@/api/catalog/tables"
 import { catalogsApi } from "@/api/management/catalogs"
@@ -32,6 +32,7 @@ import { MetadataViewer } from 
"@/components/table/MetadataViewer"
 import { RenameTableModal } from "@/components/forms/RenameTableModal"
 import { EditTablePropertiesModal } from 
"@/components/forms/EditTablePropertiesModal"
 import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, 
DialogTitle } from "@/components/ui/dialog"
+import { toast } from "sonner"
 
 export function TableDetails() {
   const { catalogName, namespace: namespaceParam, tableName } = useParams<{
@@ -41,6 +42,7 @@ export function TableDetails() {
   }>()
 
   const navigate = useNavigate()
+  const queryClient = useQueryClient()
 
   const namespaceArray = namespaceParam?.split(".") || []
 
@@ -58,7 +60,20 @@ export function TableDetails() {
 
   const tableQuery = useQuery({
     queryKey: ["table", catalogName, namespaceArray.join("."), tableName],
-    queryFn: () => tablesApi.get(catalogName!, namespaceArray, tableName!),
+    queryFn: async () => {
+      // Try to fetch as an Iceberg table first
+      try {
+        return await tablesApi.get(catalogName!, namespaceArray, tableName!)
+      } catch (icebergError) {
+        // If that fails, try to fetch as a generic table
+        try {
+          return await tablesApi.getGeneric(catalogName!, namespaceArray, 
tableName!)
+        } catch (genericError) {
+          // If both fail, throw the original Iceberg error
+          throw icebergError
+        }
+      }
+    },
     enabled: !!catalogName && namespaceArray.length > 0 && !!tableName,
   })
 
@@ -75,7 +90,51 @@ export function TableDetails() {
   const refreshDisabled = tableQuery.isFetching || namespaceQuery.isFetching 
|| catalogQuery.isFetching
 
   const tableData = tableQuery.data
-  const currentSchema = tableData?.metadata.schemas.find(
+
+  // Check if this is a generic table (has 'table' property) or Iceberg table 
(has 'metadata' property)
+  const isGenericTable = tableData && 'table' in tableData
+  const genericTableData = isGenericTable ? (tableData as any).table : null
+
+  // Delete mutations
+  const deleteIcebergMutation = useMutation({
+    mutationFn: () => tablesApi.delete(catalogName!, namespaceArray, 
tableName!),
+    onSuccess: () => {
+      toast.success("Table deleted successfully")
+      queryClient.invalidateQueries({ queryKey: ["tables", catalogName, 
namespaceArray] })
+      queryClient.invalidateQueries({ queryKey: ["namespace", catalogName, 
namespaceArray] })
+      
navigate(`/catalogs/${encodeURIComponent(catalogName!)}/namespaces/${encodeURIComponent(namespaceParam!)}`)
+    },
+    onError: (error: Error) => {
+      toast.error("Failed to delete table", {
+        description: error.message || "An error occurred",
+      })
+    },
+  })
+
+  const deleteGenericMutation = useMutation({
+    mutationFn: () => tablesApi.deleteGeneric(catalogName!, namespaceArray, 
tableName!),
+    onSuccess: () => {
+      toast.success("Generic table deleted successfully")
+      queryClient.invalidateQueries({ queryKey: ["generic-tables", 
catalogName, namespaceArray] })
+      queryClient.invalidateQueries({ queryKey: ["namespace", catalogName, 
namespaceArray] })
+      
navigate(`/catalogs/${encodeURIComponent(catalogName!)}/namespaces/${encodeURIComponent(namespaceParam!)}`)
+    },
+    onError: (error: Error) => {
+      toast.error("Failed to delete generic table", {
+        description: error.message || "An error occurred",
+      })
+    },
+  })
+
+  const handleDelete = () => {
+    if (isGenericTable) {
+      deleteGenericMutation.mutate()
+    } else {
+      deleteIcebergMutation.mutate()
+    }
+  }
+
+  const currentSchema = !isGenericTable && tableData?.metadata?.schemas?.find(
     (s) => s["schema-id"] === tableData.metadata["current-schema-id"]
   )
 
@@ -132,12 +191,16 @@ export function TableDetails() {
             <RefreshCw className="mr-2 h-4 w-4" />
             Refresh
           </Button>
-          <Button variant="outline" onClick={() => setRenameOpen(true)} 
disabled={!tableData}>
-            <Pencil className="mr-2 h-4 w-4" /> Rename
-          </Button>
-          <Button variant="outline" onClick={() => setEditPropsOpen(true)} 
disabled={!tableData}>
-            Edit Properties
-          </Button>
+          {!isGenericTable && (
+            <>
+              <Button variant="outline" onClick={() => setRenameOpen(true)} 
disabled={!tableData}>
+                <Pencil className="mr-2 h-4 w-4" /> Rename
+              </Button>
+              <Button variant="outline" onClick={() => setEditPropsOpen(true)} 
disabled={!tableData}>
+                Edit Properties
+              </Button>
+            </>
+          )}
           <Button variant="destructive" onClick={() => setDeleteOpen(true)} 
disabled={!tableData}>
             <Trash2 className="mr-2 h-4 w-4" /> Delete
           </Button>
@@ -150,9 +213,68 @@ export function TableDetails() {
         <div className="text-red-600">Error loading table: 
{tableQuery.error.message}</div>
       ) : !tableData ? (
         <div>Table not found</div>
+      ) : isGenericTable ? (
+        <>
+          {/* Generic Table Information */}
+          <Card>
+            <CardHeader>
+              <CardTitle>Generic Table Information</CardTitle>
+              <CardDescription>Metadata for this {genericTableData.format} 
table</CardDescription>
+            </CardHeader>
+            <CardContent>
+              <div className="space-y-4">
+                <div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
+                  <div>
+                    <label className="text-sm font-medium 
text-muted-foreground">Name</label>
+                    <p className="mt-1 text-sm 
font-mono">{genericTableData.name}</p>
+                  </div>
+                  <div>
+                    <label className="text-sm font-medium 
text-muted-foreground">Format</label>
+                    <p className="mt-1 text-sm">{genericTableData.format}</p>
+                  </div>
+                  {genericTableData["base-location"] && (
+                    <div className="md:col-span-2">
+                      <label className="text-sm font-medium 
text-muted-foreground">Base Location</label>
+                      <p className="mt-1 text-sm font-mono 
break-all">{genericTableData["base-location"]}</p>
+                    </div>
+                  )}
+                  {genericTableData.doc && (
+                    <div className="md:col-span-2">
+                      <label className="text-sm font-medium 
text-muted-foreground">Description</label>
+                      <p className="mt-1 text-sm">{genericTableData.doc}</p>
+                    </div>
+                  )}
+                </div>
+                {genericTableData.properties && 
Object.keys(genericTableData.properties).length > 0 && (
+                  <div>
+                    <label className="text-sm font-medium 
text-muted-foreground">Properties</label>
+                    <div className="mt-2 border rounded-md overflow-hidden">
+                      <table className="w-full text-sm">
+                        <thead className="bg-muted/50">
+                          <tr>
+                            <th className="px-3 py-2 text-left 
font-medium">Key</th>
+                            <th className="px-3 py-2 text-left 
font-medium">Value</th>
+                          </tr>
+                        </thead>
+                        <tbody className="divide-y">
+                          
{Object.entries(genericTableData.properties).map(([key, value]) => (
+                            <tr key={key} className="hover:bg-muted/50">
+                              <td className="px-3 py-2 font-mono 
text-xs">{key}</td>
+                              <td className="px-3 py-2 text-xs 
break-all">{value}</td>
+                            </tr>
+                          ))}
+                        </tbody>
+                      </table>
+                    </div>
+                  </div>
+                )}
+              </div>
+            </CardContent>
+          </Card>
+        </>
       ) : (
         <>
-          {/* Table Information */}
+          {/* Iceberg Table Information */}
           <Card>
             <CardHeader>
               <CardTitle>Table Information</CardTitle>
@@ -193,8 +315,12 @@ export function TableDetails() {
               />
             </CardContent>
           </Card>
+        </>
+      )}
 
-          {/* Modals */}
+      {/* Modals - shown for Iceberg tables only */}
+      {tableData && !isGenericTable && (
+        <>
           <RenameTableModal
             open={renameOpen}
             onOpenChange={setRenameOpen}
@@ -216,32 +342,38 @@ export function TableDetails() {
             tableName={tableName}
             properties={tableData.metadata.properties}
           />
-
-          {/* Delete Confirmation */}
-          <Dialog open={deleteOpen} onOpenChange={setDeleteOpen}>
-            <DialogContent>
-              <DialogHeader>
-                <DialogTitle>Delete table</DialogTitle>
-                <DialogDescription>
-                  Are you sure you want to delete "{tableName}"? This action 
cannot be undone.
-                </DialogDescription>
-              </DialogHeader>
-              <DialogFooter>
-                <Button variant="outline" onClick={() => 
setDeleteOpen(false)}>Cancel</Button>
-                <Button
-                  variant="destructive"
-                  onClick={async () => {
-                    await tablesApi.delete(catalogName, namespaceArray, 
tableName)
-                    
navigate(`/catalogs/${encodeURIComponent(catalogName)}/namespaces/${encodeURIComponent(namespaceParam)}`)
-                  }}
-                >
-                  Delete
-                </Button>
-              </DialogFooter>
-            </DialogContent>
-          </Dialog>
         </>
       )}
+
+      {/* Delete Confirmation - shown for both Iceberg and Generic tables */}
+      {tableData && (
+        <Dialog open={deleteOpen} onOpenChange={setDeleteOpen}>
+          <DialogContent>
+            <DialogHeader>
+              <DialogTitle>Delete {isGenericTable ? "generic table" : 
"table"}</DialogTitle>
+              <DialogDescription>
+                Are you sure you want to delete "{tableName}"? This action 
cannot be undone.
+              </DialogDescription>
+            </DialogHeader>
+            <DialogFooter>
+              <Button
+                variant="outline"
+                onClick={() => setDeleteOpen(false)}
+                disabled={deleteIcebergMutation.isPending || 
deleteGenericMutation.isPending}
+              >
+                Cancel
+              </Button>
+              <Button
+                variant="destructive"
+                onClick={handleDelete}
+                disabled={deleteIcebergMutation.isPending || 
deleteGenericMutation.isPending}
+              >
+                {(deleteIcebergMutation.isPending || 
deleteGenericMutation.isPending) ? "Deleting..." : "Delete"}
+              </Button>
+            </DialogFooter>
+          </DialogContent>
+        </Dialog>
+      )}
     </div>
   )
 }
diff --git a/console/vite.config.ts b/console/vite.config.ts
index 6e546eb..11a3248 100644
--- a/console/vite.config.ts
+++ b/console/vite.config.ts
@@ -51,6 +51,27 @@ export default defineConfig({
           }
         },
       },
+      '/polaris': {
+        target: process.env.VITE_POLARIS_API_URL || 'http://localhost:8181',
+        changeOrigin: true,
+        secure: false,
+        rewrite: (path) => path.replace(/^\/polaris/, '/api/catalog/polaris'),
+        configure: (proxy) => {
+          // Only log in development mode
+          if (process.env.NODE_ENV === 'development') {
+            proxy.on('error', (err) => {
+              console.error('Proxy error:', err);
+            });
+            proxy.on('proxyReq', (proxyReq, req) => {
+              const target = process.env.VITE_POLARIS_API_URL || 
'http://localhost:8181';
+              console.log('📤 Proxying:', req.method, req.url, '→', target + 
proxyReq.path);
+            });
+            proxy.on('proxyRes', (proxyRes, req) => {
+              console.log('Received Response from the Target:', 
proxyRes.statusCode, req.url);
+            });
+          }
+        },
+      },
     },
   },
 })

Reply via email to