Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
- Fix objectViewSchema to read showSearch/showFilters from activeView instead of hardcoded true - Add handleViewUpdate callback for real-time field-by-field draft propagation - Wire onViewUpdate prop to ViewConfigPanel for live preview - Pass draft-aware views to PluginObjectView - Fix ViewConfigPanel useEffect to depend on activeView.id (prevents draft reset loop) - Add tests for draft persistence and real-time propagation Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
|
@copilot 右侧属性面板还是有很多内容不能修改。筛选条件应该调用统一的过滤组件 数据 列 筛选条件 排序条件 |
…and column checkboxes in ViewConfigPanel - Integrate FilterBuilder for inline filter editing (with data bridge to flat filter array) - Integrate SortBuilder for inline sort editing (with data bridge to SortItem[]) - Add column selector with Checkbox for each objectDef field - Use crypto.randomUUID() for stable fallback IDs - Update tests: mock FilterBuilder/SortBuilder/Checkbox, add tests for inline editing Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Replaced the read-only clickable rows with inline editable components in
All changes propagate via draft → |
|
@copilot 12:44:29.311 @object-ui/plugin-view:build: �[96msrc/ViewTabBar.tsx�[0m:�[93m188�[0m:�[93m66�[0m - �[91merror�[0m�[90m TS2352: �[0mConversion of type 'DraggableAttributes' to type 'Record<string, unknown>' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. |
- Fix DraggableAttributes cast by using double assertion (as unknown as Record<string, unknown>) - Remove redundant visibility !== 'private' check (already handled by prior if-branch) - Add explicit event handler types for Input onChange/onKeyDown/onClick Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Fixed in
All 44 ViewTabBar tests + 48 console tests pass. |
There was a problem hiding this comment.
Pull request overview
This PR makes the Console’s ObjectView consume ViewConfigPanel draft changes immediately so the main view updates in real time, and upgrades the config panel’s Data section to edit columns/filters/sort inline using shared builder components.
Changes:
- Wire
ViewConfigPaneldraft updates intoObjectView(onViewUpdate+ draft merge) soshowSearch/showFilters(and other fields) react without clicking Save. - Replace read-only “Columns / Filter / Sort” summary rows with inline editors (Checkbox list,
FilterBuilder,SortBuilder). - Extend
ViewConfigPaneltests to cover live draft propagation and inline builder interactions.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| apps/console/src/components/ViewConfigPanel.tsx | Adds inline columns/filter/sort editors and changes draft reset behavior to depend on activeView.id. |
| apps/console/src/components/ObjectView.tsx | Merges draft into activeView/schema and passes onViewUpdate for real-time preview. |
| apps/console/src/tests/ViewConfigPanel.test.tsx | Adds mocks and assertions for inline builders and real-time draft propagation. |
| ROADMAP_CONSOLE.md | Notes real-time preview support in the Console roadmap. |
| ROADMAP.md | Notes real-time preview support in the overall roadmap. |
Comments suppressed due to low confidence (1)
apps/console/src/tests/ViewConfigPanel.test.tsx:91
- The test fixtures use object-shaped filters (
{ field, operator, value }), but reallistViews.filterin this repo is typically spec-style arrays (e.g.['type', '=', 'Customer']or[['status','!=','Done']]). As a result, these tests won’t catch the current bridge incompatibility or operator mapping issues. Update the mockedactiveView.filterto use the canonical array format and add assertions that the bridge renders the correct number of conditions and preserves operators/values when edited.
const mockActiveView = {
id: 'all',
label: 'All Records',
type: 'grid',
columns: ['name', 'stage', 'amount'],
filter: [{ field: 'stage', operator: '=', value: 'active' }],
sort: [{ field: 'name', order: 'asc' }],
};
| // Bridge: view filter array → FilterGroup | ||
| const filterGroupValue = useMemo<FilterGroup>(() => { | ||
| const conditions = (Array.isArray(draft.filter) ? draft.filter : []).map((f: any) => ({ | ||
| id: f.id || crypto.randomUUID(), | ||
| field: f.field || '', | ||
| operator: f.operator || 'equals', | ||
| value: f.value ?? '', | ||
| })); | ||
| return { id: 'root', logic: 'and' as const, conditions }; | ||
| }, [draft.filter]); |
There was a problem hiding this comment.
filterGroupValue assumes draft.filter is an array of objects ({field, operator, value}), but in this repo view configs commonly use @objectstack/spec-style array filters (e.g. ['field', '=', value], [['field','=',value]], or ['and', ...]). With current code, a triplet like ['type','=', 'Customer'] will be treated as 3 conditions and rendered/edited incorrectly, and saving will rewrite the filter into a different shape. Please implement a robust bridge that detects and converts the supported array formats into FilterGroup (including logic 'and'/'or' where possible), and converts back to the canonical filter format expected by @object-ui/plugin-view / dataSource.find.
| const conditions = (Array.isArray(draft.filter) ? draft.filter : []).map((f: any) => ({ | ||
| id: f.id || crypto.randomUUID(), | ||
| field: f.field || '', | ||
| operator: f.operator || 'equals', | ||
| value: f.value ?? '', | ||
| })); |
There was a problem hiding this comment.
FilterBuilder expects operator values like equals, notEquals, etc. Passing through f.operator (which is often '='/'!=' in spec-style filters) will result in an operator that doesn't match any option in the builder UI. The filter bridge should map between spec operators (=, !=, in, etc.) and FilterBuilder operator IDs in both directions to avoid rendering blank operators and to prevent saving incompatible values.
| return Object.entries(objectDef.fields).map(([key, field]: [string, any]) => ({ | ||
| value: key, | ||
| label: field.label || key, | ||
| type: field.type || 'text', | ||
| options: field.options, | ||
| })); |
There was a problem hiding this comment.
fieldOptions passes through Object field types (e.g. currency, percent, etc.), but FilterBuilder only switches on text | number | boolean | date | select. This will cause numeric/date fields to get the wrong operator set and input type. Consider normalizing ObjectUI field types to the FilterBuilder types when building fieldOptions (e.g. currency→number, datetime→date).
| return Object.entries(objectDef.fields).map(([key, field]: [string, any]) => ({ | |
| value: key, | |
| label: field.label || key, | |
| type: field.type || 'text', | |
| options: field.options, | |
| })); | |
| return Object.entries(objectDef.fields).map(([key, field]: [string, any]) => { | |
| const rawType = (field?.type as string | undefined)?.toLowerCase(); | |
| let normalizedType: 'text' | 'number' | 'boolean' | 'date' | 'select'; | |
| // Map common Object field types to FilterBuilder's limited type set | |
| if ( | |
| rawType === 'integer' || | |
| rawType === 'int' || | |
| rawType === 'float' || | |
| rawType === 'double' || | |
| rawType === 'number' || | |
| rawType === 'currency' || | |
| rawType === 'money' || | |
| rawType === 'percent' || | |
| rawType === 'rating' | |
| ) { | |
| normalizedType = 'number'; | |
| } else if ( | |
| rawType === 'date' || | |
| rawType === 'datetime' || | |
| rawType === 'datetime_tz' || | |
| rawType === 'timestamp' | |
| ) { | |
| normalizedType = 'date'; | |
| } else if ( | |
| rawType === 'boolean' || | |
| rawType === 'bool' || | |
| rawType === 'checkbox' || | |
| rawType === 'switch' | |
| ) { | |
| normalizedType = 'boolean'; | |
| } else if ( | |
| rawType === 'select' || | |
| rawType === 'picklist' || | |
| rawType === 'single_select' || | |
| rawType === 'multi_select' || | |
| rawType === 'enum' || | |
| // Heuristic: fields with discrete choices behave like selects | |
| Array.isArray(field?.options?.choices) || | |
| Array.isArray(field?.options?.options) | |
| ) { | |
| normalizedType = 'select'; | |
| } else { | |
| normalizedType = 'text'; | |
| } | |
| return { | |
| value: key, | |
| label: field.label || key, | |
| type: normalizedType, | |
| options: field.options, | |
| }; | |
| }); |
| <Checkbox | ||
| data-testid={`col-checkbox-${f.value}`} | ||
| checked={checked} | ||
| onCheckedChange={(c: boolean) => handleColumnToggle(f.value, c)} |
There was a problem hiding this comment.
Checkbox (Radix) onCheckedChange provides CheckedState (boolean | 'indeterminate'). Typing the callback parameter as boolean can break type-checking and may mishandle the indeterminate state at runtime. Convert with c === true (or similar) before calling handleColumnToggle so the handler always receives a boolean.
| onCheckedChange={(c: boolean) => handleColumnToggle(f.value, c)} | |
| onCheckedChange={(c) => handleColumnToggle(f.value, c === true)} |
| views={views.map((v: any) => | ||
| v.id === activeViewId && viewDraft && viewDraft.id === v.id | ||
| ? { ...v, ...viewDraft } | ||
| : v |
There was a problem hiding this comment.
views={views.map(...)} recreates the active view object on every parent render (via { ...v, ...viewDraft }), even when viewDraft hasn't changed. @object-ui/plugin-view's ObjectView effect depends on activeView, so this can trigger unnecessary data refetches / state resets. Consider memoizing the merged views array (and reusing the merged active view object) with useMemo keyed on [views, activeViewId, viewDraft] to keep references stable when nothing changed.
| views={views.map((v: any) => | |
| v.id === activeViewId && viewDraft && viewDraft.id === v.id | |
| ? { ...v, ...viewDraft } | |
| : v | |
| views={useMemo( | |
| () => | |
| views.map((v: any) => | |
| v.id === activeViewId && viewDraft && viewDraft.id === v.id | |
| ? { ...v, ...viewDraft } | |
| : v | |
| ), | |
| [views, activeViewId, viewDraft] |
|
@copilot fix all and ci errors |
…tion, memoized views
- Implement robust spec-style filter bridge: parse ['field','=',val], [['f','=',v],...],
and ['and'|'or',...] formats to FilterGroup, and convert back to spec format on save
- Map operators between spec ('=','!=','>',etc.) and FilterBuilder IDs (equals, notEquals, etc.)
- Normalize ObjectUI field types to FilterBuilder types (currency→number, datetime→date, etc.)
- Fix Checkbox onCheckedChange to handle CheckedState properly (c === true)
- Memoize merged views array in ObjectView.tsx to prevent unnecessary re-renders
- Add tests for nested filter arrays, and/or logic, and field type normalization
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
All review comments and CI errors fixed in
All 50 tests pass + 0 CodeQL alerts. |
❌ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
objectViewSchemahardcodedshowSearch: trueandshowFilters: true, ignoring the ViewConfigPanel draft state. Toggling switches in the config panel had no visible effect until Save was clicked. Additionally, the filter, sort, and column rows were read-only clickable summaries that required navigating to sub-editors — they now use the unified inline components for direct editing.Changes
objectViewSchema: ReadshowSearch/showFiltersfromactiveView(which merges draft) instead of hardcodedtruehandleViewUpdate: New callback propagates each field change toviewDraftstate immediately, wired toViewConfigPanelviaonViewUpdatePluginObjectViewviews prop: Memoized merged views array (useMemokeyed on[views, activeViewId, viewDraft]) injects draft into active view entry, preventing unnecessary re-rendersuseEffectdep: Changed from[activeView]→[activeView.id]to prevent the draft/dirty reset loop when parent re-renders with merged draftFilterBuildercomponent, with a robust spec-style filter bridge that parses all@objectstack/specformats: single triplets['field','=',value], nested arrays[['f','=',v],...], logic prefixes['and'|'or',...], and object-style{field, operator, value}=,!=,>,<,in, etc.) and FilterBuilder operator IDs (equals,notEquals,greaterThan,lessThan,in, etc.) so operators render and save correctlynormalizeFieldType()maps ObjectUI field types (currency→number,datetime→date,picklist→select,boolean→boolean, etc.) so FilterBuilder shows correct operator sets and input typesSortBuildercomponent, with a data bridge converting between the view's flat sort array andSortItem[]formatCheckboxlist of all fields fromobjectDef, with properCheckedStatehandling (c === true)Tests
Original prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.