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
5 changes: 5 additions & 0 deletions .changeset/silent-maps-kick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@llamaindex/ui": patch
---

Add filter/styles to grid
12 changes: 12 additions & 0 deletions packages/ui/src/item-grid/extracted-data-item-grid.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type {
TypedAgentData,
ExtractedData,
FilterOperation,
} from "llama-cloud-services/beta/agent";
import { ItemGrid } from "./item-grid";
import {
Expand All @@ -19,13 +20,21 @@ export interface ExtractedDataItemGridProps<T> {
onRowClick?: (item: TypedAgentData<ExtractedData<T>>) => void;
// Other configurations
defaultPageSize?: number;
// Optional base filter to be passed to search API
filter?: Record<string, FilterOperation>;
// Styling (forwarded to ItemGrid)
className?: string;
style?: React.CSSProperties;
}

export function ExtractedDataItemGrid<T>({
customColumns = [],
builtInColumns = {},
onRowClick,
defaultPageSize = 20,
filter,
className,
style,
}: ExtractedDataItemGridProps<T>) {
const confidenceThreshold = useUIConfigStore(
(state) => state.confidenceThreshold
Expand Down Expand Up @@ -58,6 +67,9 @@ export function ExtractedDataItemGrid<T>({
customColumns={columns}
onRowClick={onRowClick}
defaultPageSize={defaultPageSize}
filter={filter}
className={className}
style={style}
/>
);
}
30 changes: 20 additions & 10 deletions packages/ui/src/item-grid/item-grid.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useMemo } from "react";
import { useState, useMemo, type CSSProperties } from "react";
import { cn } from "@/lib/utils";
import {
Table,
Expand Down Expand Up @@ -35,13 +35,21 @@ export interface ItemGridProp<T = unknown> {
onRowClick?: (item: TypedAgentData<T>) => void;
// Other configurations
defaultPageSize?: number;
// Optional root filter passed through directly to the search API
filter?: Record<string, FilterOperation>;
// Styling (outermost container only)
className?: string;
style?: CSSProperties;
}

// Main Business Component
export function ItemGrid<T = unknown>({
customColumns = [],
onRowClick,
defaultPageSize = 20,
filter,
className,
style,
}: ItemGridProp<T>) {
const [paginationState, setPaginationState] = useState<PaginationState>({
page: 0,
Expand All @@ -54,7 +62,7 @@ export function ItemGrid<T = unknown>({
direction: "desc",
});

const [filters, setFilters] = useState<Record<string, string[]>>({});
const [uiFilters, setUiFilters] = useState<Record<string, string[]>>({});

// Generate final columns array
const columns = useMemo(() => {
Expand All @@ -67,17 +75,19 @@ export function ItemGrid<T = unknown>({
}, [customColumns]);

// Convert frontend filter state to API format
const apiFilters = useMemo(() => {
const result: Record<string, FilterOperation> = {};
const searchFilter = useMemo(() => {
const result: Record<string, FilterOperation> = {
...(filter || {}),
};

Object.entries(filters).forEach(([columnKey, filterValues]) => {
Object.entries(uiFilters).forEach(([columnKey, filterValues]) => {
if (filterValues.length > 0) {
result[columnKey] = { includes: filterValues };
}
});

return result;
}, [filters]);
}, [uiFilters, filter]);

// Convert frontend sort state to API format
const apiSort = useMemo(() => {
Expand All @@ -91,7 +101,7 @@ export function ItemGrid<T = unknown>({
}, [sortState]);

const { data, loading, error, totalSize, deleteItem, fetchData } =
useItemGridData<T>(paginationState, apiFilters, apiSort);
useItemGridData<T>(paginationState, searchFilter, apiSort);

// Create hooks object for passing to renderCell
const hooks = useMemo(
Expand Down Expand Up @@ -122,7 +132,7 @@ export function ItemGrid<T = unknown>({

// Handle filtering
const handleFilterChange = (columnKey: string, values: string[]) => {
setFilters((prev) => ({
setUiFilters((prev) => ({
...prev,
[columnKey]: values,
}));
Expand Down Expand Up @@ -168,7 +178,7 @@ export function ItemGrid<T = unknown>({
}

return (
<div className="w-full space-y-4">
<div className={cn("w-full space-y-4", className)} style={style}>
<div className="rounded-md border">
<Table className="table-fixed">
<TableHeader>
Expand All @@ -186,7 +196,7 @@ export function ItemGrid<T = unknown>({
sortState={sortState}
onSort={handleSort}
filterOptions={getFilterOptions(column.key)}
selectedFilters={filters[column.key]}
selectedFilters={uiFilters[column.key]}
onFilterChange={handleFilterChange}
/>
</TableHead>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { describe, it, expect, vi } from "vitest";
import { render } from "@testing-library/react";
import { ApiProvider, createMockClients } from "../../src/lib";
import { ExtractedDataItemGrid } from "../../src/item-grid/extracted-data-item-grid";
import type { FilterOperation } from "llama-cloud-services/beta/agent";
import type { ReactNode } from "react";

function renderWithProvider(ui: ReactNode, clients = createMockClients()) {
return render(<ApiProvider clients={clients}>{ui}</ApiProvider>);
}

describe("ExtractedDataItemGrid baseFilter", () => {
it("passes filter through to search API", async () => {
const clients = createMockClients();
if (!clients.agentDataClient) {
throw new Error("AgentDataClient not found");
}
const spy = vi.spyOn(clients.agentDataClient, "search");

const filter: Record<string, FilterOperation> = {
status: { includes: ["approved"] },
};

renderWithProvider(
<ExtractedDataItemGrid
customColumns={[]}
builtInColumns={{}}
defaultPageSize={5}
filter={filter}
/>,
clients
);

// Wait a microtask tick for effects to run
await Promise.resolve();

expect(spy).toHaveBeenCalled();
const call = spy.mock.calls.at(-1) as any[];
expect(call?.[0]?.filter).toMatchObject(filter);
});
});