Skip to content

Commit f65b757

Browse files
authored
Merge pull request #997 from objectstack-ai/copilot/fix-related-subtable-issue
2 parents fa2bb80 + dc11eef commit f65b757

3 files changed

Lines changed: 84 additions & 3 deletions

File tree

apps/console/src/components/RecordDetailView.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,7 @@ export function RecordDetailView({ dataSource, objects, onEdit }: RecordDetailVi
335335
const related = childRelations.map(({ childObject, childLabel }) => ({
336336
title: childLabel,
337337
type: 'table' as const,
338+
api: childObject,
338339
data: childRelatedData[childObject] || [],
339340
}));
340341

packages/plugin-detail/src/RelatedList.tsx

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,23 @@ export const RelatedList: React.FC<RelatedListProps> = ({
7676
const [sortField, setSortField] = React.useState<string | null>(null);
7777
const [sortDirection, setSortDirection] = React.useState<'asc' | 'desc'>('asc');
7878
const [filterText, setFilterText] = React.useState('');
79+
const [objectSchema, setObjectSchema] = React.useState<any>(null);
7980
const { t } = useDetailTranslation();
8081

8182
// Sync internal state when data prop changes (e.g., parent fetches async data)
8283
React.useEffect(() => {
8384
setRelatedData(data);
8485
}, [data]);
8586

87+
// Auto-fetch object schema when api/dataSource available but columns missing
88+
React.useEffect(() => {
89+
if (api && dataSource?.getObjectSchema && !columns?.length) {
90+
dataSource.getObjectSchema(api).then(setObjectSchema).catch((err: unknown) => {
91+
console.warn(`[RelatedList] Failed to fetch schema for ${api}:`, err);
92+
});
93+
}
94+
}, [api, dataSource, columns]);
95+
8696
React.useEffect(() => {
8797
if (api && !data.length) {
8898
setLoading(true);
@@ -166,6 +176,18 @@ export const RelatedList: React.FC<RelatedListProps> = ({
166176
}
167177
}, [onRowDelete, t]);
168178

179+
// Generate effective columns from explicit prop or object schema fields
180+
const effectiveColumns = React.useMemo(() => {
181+
if (columns && columns.length > 0) return columns;
182+
if (!objectSchema?.fields) return [];
183+
return Object.entries(objectSchema.fields)
184+
.filter(([key]) => !key.startsWith('_'))
185+
.map(([key, def]: [string, any]) => ({
186+
accessorKey: key,
187+
header: def.label || key,
188+
}));
189+
}, [columns, objectSchema]);
190+
169191
const viewSchema = React.useMemo(() => {
170192
if (schema) return schema;
171193

@@ -176,7 +198,7 @@ export const RelatedList: React.FC<RelatedListProps> = ({
176198
return {
177199
type: 'data-table',
178200
data: paginatedData,
179-
columns: columns || [],
201+
columns: effectiveColumns,
180202
pagination: false, // We handle pagination ourselves
181203
pageSize: effectivePageSize || 10,
182204
};
@@ -188,7 +210,7 @@ export const RelatedList: React.FC<RelatedListProps> = ({
188210
default:
189211
return { type: 'div', children: 'No view configured' };
190212
}
191-
}, [type, paginatedData, columns, schema, effectivePageSize]);
213+
}, [type, paginatedData, effectiveColumns, schema, effectivePageSize]);
192214

193215
const recordCountText = relatedData.length === 1
194216
? t('detail.relatedRecordOne', { count: relatedData.length })

packages/plugin-detail/src/__tests__/RelatedList.test.tsx

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import { describe, it, expect, vi } from 'vitest';
10-
import { render, screen, fireEvent } from '@testing-library/react';
10+
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
1111
import { RelatedList } from '../RelatedList';
1212

1313
describe('RelatedList', () => {
@@ -63,4 +63,62 @@ describe('RelatedList', () => {
6363
expect(screen.queryByText('New')).not.toBeInTheDocument();
6464
expect(screen.queryByText('View All')).not.toBeInTheDocument();
6565
});
66+
67+
it('should auto-generate columns from object schema when api and dataSource provided but no columns', async () => {
68+
const mockDataSource = {
69+
getObjectSchema: vi.fn().mockResolvedValue({
70+
name: 'order_item',
71+
fields: {
72+
product: { type: 'string', label: 'Product' },
73+
quantity: { type: 'number', label: 'Quantity' },
74+
_id: { type: 'string', label: 'ID' },
75+
},
76+
}),
77+
find: vi.fn(),
78+
} as any;
79+
80+
const data = [{ product: 'Widget', quantity: 5 }];
81+
render(
82+
<RelatedList
83+
title="Order Items"
84+
type="table"
85+
api="order_item"
86+
data={data}
87+
dataSource={mockDataSource}
88+
/>,
89+
);
90+
91+
await waitFor(() => {
92+
expect(mockDataSource.getObjectSchema).toHaveBeenCalledWith('order_item');
93+
});
94+
95+
// Verify columns are generated from schema (excluding _id)
96+
await waitFor(() => {
97+
expect(screen.getByText('Product')).toBeInTheDocument();
98+
expect(screen.getByText('Quantity')).toBeInTheDocument();
99+
});
100+
// _id should be filtered out
101+
expect(screen.queryByText('ID')).not.toBeInTheDocument();
102+
});
103+
104+
it('should not fetch object schema when explicit columns are provided', () => {
105+
const mockDataSource = {
106+
getObjectSchema: vi.fn(),
107+
find: vi.fn(),
108+
} as any;
109+
110+
const columns = [{ accessorKey: 'name', header: 'Name' }];
111+
render(
112+
<RelatedList
113+
title="Contacts"
114+
type="table"
115+
api="contact"
116+
data={[{ name: 'Alice' }]}
117+
columns={columns}
118+
dataSource={mockDataSource}
119+
/>,
120+
);
121+
122+
expect(mockDataSource.getObjectSchema).not.toHaveBeenCalled();
123+
});
66124
});

0 commit comments

Comments
 (0)