Skip to content
Open
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: 3 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ on:

concurrency: ${{ github.workflow }}-${{ github.ref }}

permissions:
id-token: write # Required for OIDC

jobs:
release:
name: Release
Expand Down
4 changes: 2 additions & 2 deletions packages/typegen/src/typegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,11 +177,11 @@ const generateTypedClientsSingle = async (
apiKey: undefined,
username:
envNames?.auth && "username" in envNames.auth
? envNames.auth.username ?? defaultEnvNames.username
? (envNames.auth.username ?? defaultEnvNames.username)
: defaultEnvNames.username,
password:
envNames?.auth && "password" in envNames.auth
? envNames.auth.password ?? defaultEnvNames.password
? (envNames.auth.password ?? defaultEnvNames.password)
: defaultEnvNames.password,
},
db: envNames?.db ?? defaultEnvNames.db,
Expand Down
1 change: 1 addition & 0 deletions packages/typegen/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@tailwindcss/vite": "^4.1.18",
"@tanstack/react-query": "^5.90.12",
"@tanstack/react-table": "^8.21.3",
"@tanstack/react-virtual": "^3.13.13",
"@uidotdev/usehooks": "^2.4.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
Expand Down
172 changes: 164 additions & 8 deletions packages/typegen/web/src/components/MetadataFieldsDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import {
getSortedRowModel,
getFilteredRowModel,
type ColumnDef,
flexRender,
} from "@tanstack/react-table";
import { useVirtualizer } from "@tanstack/react-virtual";
import { DataGrid, DataGridContainer } from "./ui/data-grid";
import { DataGridTable } from "./ui/data-grid-table";
import { DataGridColumnHeader } from "./ui/data-grid-column-header";
import { ScrollArea, ScrollBar } from "./ui/scroll-area";
import { Input, InputWrapper } from "./ui/input";
import { Switch } from "./ui/switch";
import { Skeleton } from "./ui/skeleton";
Expand Down Expand Up @@ -131,6 +131,13 @@ export function MetadataFieldsDialog({

const [globalFilter, setGlobalFilter] = useState("");

// Reset search filter when dialog opens or table changes
useEffect(() => {
if (open) {
setGlobalFilter("");
}
}, [open, tableName]);

// Get the config type to validate we're working with fmodata
const configType = useWatch({
control,
Expand Down Expand Up @@ -923,7 +930,7 @@ export function MetadataFieldsDialog({
)}
<span
className={`font-medium ${
row.isExcluded ? "text-muted-foreground line-through" : ""
row.isExcluded ? "italic text-muted-foreground/50" : ""
}`}
>
{info.getValue() as string}
Expand Down Expand Up @@ -1055,6 +1062,40 @@ export function MetadataFieldsDialog({
return fieldsData.filter((row) => !row.isExcluded).length;
}, [fieldsData]);

// Ref for the scrollable container
const tableContainerRef = useRef<HTMLDivElement>(null);

// Get filtered rows for virtualization
const { rows } = fieldsTable.getRowModel();

// Setup virtualizer
const rowVirtualizer = useVirtualizer({
count: rows.length,
getScrollElement: () => tableContainerRef.current,
estimateSize: () => 57, // Estimated row height in pixels
overscan: 10, // Number of items to render outside of the visible area
});

// Recalculate virtualizer when dialog opens or rows change
useEffect(() => {
if (open && tableContainerRef.current && rows.length > 0) {
// Small delay to ensure container is fully rendered
const timeoutId = setTimeout(() => {
rowVirtualizer.measure();
}, 0);
return () => clearTimeout(timeoutId);
}
}, [open, rows.length, rowVirtualizer]);

const virtualRows = rowVirtualizer.getVirtualItems();
const totalSize = rowVirtualizer.getTotalSize();

const paddingTop = virtualRows.length > 0 ? virtualRows?.[0]?.start || 0 : 0;
const paddingBottom =
virtualRows.length > 0
? totalSize - (virtualRows?.[virtualRows.length - 1]?.end || 0)
: 0;

return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent
Expand Down Expand Up @@ -1100,11 +1141,126 @@ export function MetadataFieldsDialog({
emptyMessage="No fields found."
tableLayout={{ width: "auto", headerSticky: true }}
>
<DataGridContainer>
<ScrollArea className="max-h-[650px]">
<DataGridTable />
<ScrollBar orientation="horizontal" />
</ScrollArea>
<DataGridContainer border={true}>
<div
ref={tableContainerRef}
className="overflow-auto"
style={{
contain: "strict",
height: "650px",
maxHeight: "650px",
}}
>
<table className="w-full border-separate border-spacing-0">
<thead className="sticky top-0 z-10 bg-background">
{fieldsTable.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th
key={header.id}
className="h-10 px-4 text-left align-middle font-normal text-secondary-foreground/80 border-b"
style={{
width: header.getSize(),
}}
>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</th>
))}
</tr>
))}
</thead>
<tbody>
{isLoading ? (
<tr>
<td
colSpan={fieldsTable.getAllColumns().length}
className="text-center py-8"
>
<div className="text-muted-foreground">
Loading fields...
</div>
</td>
</tr>
) : rows.length === 0 ? (
<tr>
<td
colSpan={fieldsTable.getAllColumns().length}
className="text-center py-8"
>
<div className="text-muted-foreground">
No fields found.
</div>
</td>
</tr>
) : virtualRows.length === 0 && rows.length > 0 ? (
// Fallback: if virtualizer hasn't initialized yet, show all rows
rows.map((row) => (
<tr
key={row.id}
className="hover:bg-muted/40 border-b"
>
{row.getVisibleCells().map((cell) => (
<td
key={cell.id}
className="px-4 py-3 align-middle"
>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</td>
))}
</tr>
))
) : (
<>
{paddingTop > 0 && (
<tr>
<td
colSpan={fieldsTable.getAllColumns().length}
style={{ height: `${paddingTop}px` }}
/>
</tr>
)}
{virtualRows.map((virtualRow) => {
const row = rows[virtualRow.index];
return (
<tr
key={row.id}
className="hover:bg-muted/40 border-b"
>
{row.getVisibleCells().map((cell) => (
<td
key={cell.id}
className="px-4 py-3 align-middle"
>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</td>
))}
</tr>
);
})}
{paddingBottom > 0 && (
<tr>
<td
colSpan={fieldsTable.getAllColumns().length}
style={{ height: `${paddingBottom}px` }}
/>
</tr>
)}
</>
)}
</tbody>
</table>
</div>
</DataGridContainer>
</DataGrid>
)}
Expand Down
108 changes: 105 additions & 3 deletions packages/typegen/web/src/components/MetadataTablesEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { ScrollArea, ScrollBar } from "./ui/scroll-area";
import { Skeleton } from "./ui/skeleton";
import { Badge } from "./ui/badge";
import { DropdownMenuItem } from "./ui/dropdown-menu";
import { Tooltip, TooltipTrigger, TooltipContent } from "./ui/tooltip";

interface MetadataTablesEditorProps {
configIndex: number;
Expand Down Expand Up @@ -138,6 +139,72 @@ function FieldCountCell({
return <span className="text-sm">{fieldCount}</span>;
}

// Helper component to fetch and display relationship count for a table
function RelationshipCountCell({
tableName,
isIncluded,
configIndex,
}: {
tableName: string;
isIncluded: boolean;
configIndex: number;
}) {
const { data: parsedMetadata, isLoading } = useTableMetadata(
configIndex,
tableName,
isIncluded, // Only fetch when table is included
);

const relationships = useMemo(() => {
if (!parsedMetadata?.entitySets || !parsedMetadata?.entityTypes) {
return [];
}

const entitySet = Object.values(parsedMetadata.entitySets).find(
(es) => es.Name === tableName,
);
if (!entitySet) return [];

const entityType = parsedMetadata.entityTypes[entitySet.EntityType];
if (!entityType?.NavigationProperties) return [];

const navProps = entityType.NavigationProperties;
// Handle both Array and other formats
if (Array.isArray(navProps)) {
return navProps.map((np) => np.Name);
}
return [];
}, [parsedMetadata, tableName]);

if (isLoading) {
return <Skeleton className="w-12 h-5" />;
}

if (!isIncluded) {
return null;
}

const count = relationships.length;

if (count === 0) {
return <span className="text-muted-foreground">-</span>;
}

const relationshipNames = relationships.join(", ");

return (
<Tooltip>
<TooltipTrigger asChild>
<span className="text-sm cursor-help">{count}</span>
</TooltipTrigger>
<TooltipContent className="max-w-xs">
<div className="font-medium mb-1">Relationships:</div>
<div className="text-xs">{relationshipNames}</div>
</TooltipContent>
</Tooltip>
);
}

export function MetadataTablesEditor({
configIndex,
}: MetadataTablesEditorProps) {
Expand Down Expand Up @@ -358,12 +425,15 @@ export function MetadataTablesEditor({
<DataGridColumnHeader column={column} title="Table Occurrence Name" />
),
enableSorting: true,
// Use a large size relative to other columns so it takes most space
// In fixed layout, space is distributed proportionally

cell: (info) => {
const row = info.row.original;
return (
<span
className={`font-medium ${
!row.isIncluded ? "text-muted-foreground" : ""
!row.isIncluded ? "italic text-muted-foreground" : ""
}`}
>
{info.getValue() as string}
Expand All @@ -380,7 +450,9 @@ export function MetadataTablesEditor({
<DataGridColumnHeader column={column} title="Fields" />
),
enableSorting: false,
size: 100,
size: 50,
minSize: 50,
maxSize: 100,
cell: (info) => {
const row = info.row.original;
if (!row.isIncluded) {
Expand All @@ -398,6 +470,32 @@ export function MetadataTablesEditor({
skeleton: <Skeleton className="w-12 h-5" />,
},
},
{
id: "relationships",
header: ({ column }) => (
<DataGridColumnHeader column={column} title="Relationships" />
),
enableSorting: false,
size: 50,
minSize: 10,
maxSize: 100,
cell: (info) => {
const row = info.row.original;
if (!row.isIncluded) {
return null;
}
return (
<RelationshipCountCell
tableName={row.tableName}
isIncluded={row.isIncluded}
configIndex={configIndex}
/>
);
},
meta: {
skeleton: <Skeleton className="w-12 h-5" />,
},
},
{
id: "actions",
header: () => null,
Expand Down Expand Up @@ -600,7 +698,11 @@ export function MetadataTablesEditor({
recordCount={tablesTable.getFilteredRowModel().rows.length}
isLoading={isLoadingTables}
emptyMessage="No tables found."
tableLayout={{ width: "fixed", headerSticky: true }}
tableLayout={{
width: "auto",
headerSticky: true,
dense: true,
}}
>
<DataGridContainer>
<ScrollArea className="max-h-[650px]">
Expand Down
Loading
Loading