Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…nfig panel UI, i18n, and tests Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
|
@copilot 仔细评估是否已经完成了任务中要求的所有开发 |
…or userFilters Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
经过仔细评估,补充完成了遗漏的部分 (3a22154): 原有已完成:
本次补充完成:
所有 477 个测试通过(6 个文件),CodeQL 0 告警。 |
There was a problem hiding this comment.
Pull request overview
This PR completes userFilters end-to-end wiring so the Airtable Interfaces–style filter controls can be configured, persisted on NamedListView, and correctly passed through both Console and plugin-view ObjectView rendering paths.
Changes:
- Add
userFilterstoNamedListViewto matchListViewSchema.userFilters. - Pass
userFiltersthrough Console andplugin-viewObjectView schema construction. - Add a User Filters editor to the Console view config panel (with i18n keys) and extend tests to cover the new field + passthrough behavior.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/types/src/objectql.ts | Adds NamedListView.userFilters type to align with ListViewSchema. |
| apps/console/src/components/ObjectView.tsx | Passes userFilters into the ListView schema used for preview/render. |
| packages/plugin-view/src/ObjectView.tsx | Passes userFilters into rendered view schema based on activeView. |
| apps/console/src/utils/view-config-schema.tsx | Implements the config-panel editor UI for userFilters. |
| packages/i18n/src/locales/en.ts | Adds English strings for the new config UI controls. |
| packages/i18n/src/locales/zh.ts | Adds Chinese strings for the new config UI controls. |
| apps/console/src/tests/view-config-schema.test.tsx | Asserts _userFilters exists and is ordered correctly. |
| apps/console/src/tests/ViewConfigPanel.test.tsx | Adds interaction tests for the userFilters editor (mode switch, add/remove). |
| apps/console/src/tests/ObjectView.test.tsx | Verifies Console ObjectView passes userFilters into ListView schema. |
| packages/plugin-view/src/tests/ObjectView.test.tsx | Verifies activeView.userFilters propagates into renderListView schema. |
| ROADMAP.md | Marks the config-panel work as completed. |
| userFilters?: { | ||
| element: 'dropdown' | 'tabs' | 'toggle'; | ||
| fields?: Array<{ | ||
| field: string; | ||
| label?: string; | ||
| type?: 'select' | 'multi-select' | 'boolean' | 'date-range' | 'text'; | ||
| options?: Array<{ label: string; value: string | number | boolean; color?: string }>; | ||
| showCount?: boolean; | ||
| defaultValues?: (string | number | boolean)[]; | ||
| }>; | ||
| tabs?: Array<{ | ||
| id: string; | ||
| label: string; | ||
| filters: Array<any[] | string>; | ||
| icon?: string; | ||
| default?: boolean; | ||
| }>; | ||
| allowAddTab?: boolean; | ||
| showAllRecords?: boolean; | ||
| }; |
There was a problem hiding this comment.
NamedListView.userFilters duplicates the full ListViewSchema['userFilters'] shape inline. Since this is explicitly meant to stay in parity, it’s easy for the two definitions to drift over time. Consider reusing the existing type (e.g., userFilters?: ListViewSchema['userFilters']) or extracting a shared UserFiltersConfig type alias used by both ListViewSchema and NamedListView.
| userFilters?: { | |
| element: 'dropdown' | 'tabs' | 'toggle'; | |
| fields?: Array<{ | |
| field: string; | |
| label?: string; | |
| type?: 'select' | 'multi-select' | 'boolean' | 'date-range' | 'text'; | |
| options?: Array<{ label: string; value: string | number | boolean; color?: string }>; | |
| showCount?: boolean; | |
| defaultValues?: (string | number | boolean)[]; | |
| }>; | |
| tabs?: Array<{ | |
| id: string; | |
| label: string; | |
| filters: Array<any[] | string>; | |
| icon?: string; | |
| default?: boolean; | |
| }>; | |
| allowAddTab?: boolean; | |
| showAllRecords?: boolean; | |
| }; | |
| userFilters?: ListViewSchema['userFilters']; |
| - [x] Merged UserFilters row and tool buttons row into single toolbar line — left: field filter badges, right: tool buttons with separator | ||
| - [x] Search changed from inline input to icon button + Popover — saves toolbar space, matches Airtable pattern | ||
| - [x] UserFilters `maxVisible` prop added — overflow badges collapse into "More" dropdown with Popover | ||
| - [x] UserFilters config panel: `buildUserFiltersSection` in ViewConfigPanel — element type selector (dropdown/tabs/toggle), field picker, live preview sync, i18n (en/zh), `NamedListView.userFilters` type parity with `ListViewSchema.userFilters` |
There was a problem hiding this comment.
ROADMAP entry mentions a buildUserFiltersSection implementation in ViewConfigPanel, but the current change adds _userFilters directly inside buildDataSection (no buildUserFiltersSection helper appears to exist). Consider updating this line to match the actual implementation (or introduce the named helper if that’s the intended structure).
| - [x] UserFilters config panel: `buildUserFiltersSection` in ViewConfigPanel — element type selector (dropdown/tabs/toggle), field picker, live preview sync, i18n (en/zh), `NamedListView.userFilters` type parity with `ListViewSchema.userFilters` | |
| - [x] UserFilters config panel: `_userFilters` integrated into `buildDataSection` in ViewConfigPanel — element type selector (dropdown/tabs/toggle), field picker, live preview sync, i18n (en/zh), `NamedListView.userFilters` type parity with `ListViewSchema.userFilters` |
| <div className="flex items-center gap-2 pt-1"> | ||
| <Checkbox | ||
| data-testid="uf-show-all-records" | ||
| checked={uf.showAllRecords === true} | ||
| onCheckedChange={(checked: boolean) => { | ||
| updateField('userFilters', { ...uf, showAllRecords: checked }); | ||
| }} | ||
| className="h-3.5 w-3.5" | ||
| /> | ||
| <span className="text-xs">{t('console.objectView.ufShowAllRecords')}</span> | ||
| </div> |
There was a problem hiding this comment.
showAllRecords defaults to enabled at runtime (see plugin-list showAllRecords={config.showAllRecords !== false}), but this checkbox is rendered as unchecked when uf.showAllRecords is undefined (checked={uf.showAllRecords === true}). This makes the config panel disagree with actual behavior and can lead to saving a config that appears disabled but still shows the "All records" tab. Consider using checked={uf.showAllRecords !== false} (and when toggling, set showAllRecords to false when unchecked, otherwise undefined or true depending on whether you want to persist the default).
| {availableFields.length > 0 ? ( | ||
| <select | ||
| data-testid="uf-add-field" | ||
| className="text-xs h-6 w-full border rounded px-1" | ||
| value="" | ||
| onChange={(e: React.ChangeEvent<HTMLSelectElement>) => { | ||
| if (!e.target.value) return; | ||
| const newField = { field: e.target.value }; | ||
| updateField('userFilters', { ...uf, fields: [...currentFields, newField] }); | ||
| e.target.value = ''; | ||
| }} | ||
| > | ||
| <option value="">{t('console.objectView.ufAddField')}</option> | ||
| {availableFields | ||
| .filter(fv => !currentFields.some((cf: any) => cf.field === fv)) | ||
| .map(fv => { | ||
| const fo = fieldOptions.find(o => o.value === fv); | ||
| return <option key={fv} value={fv}>{fo?.label || fv}</option>; | ||
| })} | ||
| </select> | ||
| ) : ( | ||
| <span className="text-xs text-muted-foreground">{t('console.objectView.ufNoFields')}</span> | ||
| )} |
There was a problem hiding this comment.
The field picker uses availableFields = fieldOptions.map(f => f.value) which includes all object fields, but the runtime UserFilters dropdown/toggle UX only works well for fields with discrete options (plugin-list auto-derives from select/multi-select/boolean or fields with options). Since this UI doesn’t let the user configure type/options, selecting a free-text/date field will typically render a filter badge with an empty popover (non-functional). Consider deriving availableFields from objectDef.fields and only offering fields with options or a supported type, or expand the editor to configure type/options for non-option fields.
| {availableFields.length > 0 ? ( | |
| <select | |
| data-testid="uf-add-field" | |
| className="text-xs h-6 w-full border rounded px-1" | |
| value="" | |
| onChange={(e: React.ChangeEvent<HTMLSelectElement>) => { | |
| if (!e.target.value) return; | |
| const newField = { field: e.target.value }; | |
| updateField('userFilters', { ...uf, fields: [...currentFields, newField] }); | |
| e.target.value = ''; | |
| }} | |
| > | |
| <option value="">{t('console.objectView.ufAddField')}</option> | |
| {availableFields | |
| .filter(fv => !currentFields.some((cf: any) => cf.field === fv)) | |
| .map(fv => { | |
| const fo = fieldOptions.find(o => o.value === fv); | |
| return <option key={fv} value={fv}>{fo?.label || fv}</option>; | |
| })} | |
| </select> | |
| ) : ( | |
| <span className="text-xs text-muted-foreground">{t('console.objectView.ufNoFields')}</span> | |
| )} | |
| {(() => { | |
| // Only expose fields that are backed by discrete options or supported types. | |
| const discreteFieldValues = fieldOptions | |
| .filter((fo: any) => { | |
| const hasOptions = Array.isArray(fo.options) && fo.options.length > 0; | |
| const type = fo.type as string | undefined; | |
| const isSupportedType = type === 'select' || type === 'multi-select' || type === 'boolean'; | |
| return hasOptions || isSupportedType; | |
| }) | |
| .map((fo: FieldOption | any) => fo.value); | |
| const selectableFields = availableFields.filter(fv => discreteFieldValues.includes(fv)); | |
| if (selectableFields.length === 0) { | |
| return ( | |
| <span className="text-xs text-muted-foreground"> | |
| {t('console.objectView.ufNoFields')} | |
| </span> | |
| ); | |
| } | |
| return ( | |
| <select | |
| data-testid="uf-add-field" | |
| className="text-xs h-6 w-full border rounded px-1" | |
| value="" | |
| onChange={(e: React.ChangeEvent<HTMLSelectElement>) => { | |
| if (!e.target.value) return; | |
| const newField = { field: e.target.value }; | |
| updateField('userFilters', { ...uf, fields: [...currentFields, newField] }); | |
| e.target.value = ''; | |
| }} | |
| > | |
| <option value="">{t('console.objectView.ufAddField')}</option> | |
| {selectableFields | |
| .filter(fv => !currentFields.some((cf: any) => cf.field === fv)) | |
| .map(fv => { | |
| const fo = fieldOptions.find(o => o.value === fv); | |
| return ( | |
| <option key={fv} value={fv}> | |
| {fo?.label || fv} | |
| </option> | |
| ); | |
| })} | |
| </select> | |
| ); | |
| })()} |
|
@copilot fix all |
…owAllRecords default, discrete field filtering Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…rds default Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
All 4 review comments addressed in
All 477 tests pass, CodeQL 0 alerts. |
userFilters(Airtable Interfaces-style dropdown/tabs/toggle filter controls) existed onListViewSchemaand rendered inplugin-list, but was missing fromNamedListView, the config panel, and both ObjectView passthrough paths — so it couldn't be configured or persisted.Type parity
userFilterstoNamedListViewinpackages/types/src/objectql.tsviaListViewSchema['userFilters']to avoid inline duplication and prevent type drift.Schema passthrough
ObjectView.tsx:userFilters: viewDef.userFilters ?? listSchema.userFiltersinrenderListViewfullSchema construction.ObjectView.tsx:userFilters: activeView?.userFilters ?? (schema as any).userFiltersinrenderContent.Config panel UI
_userFilterscustom field inbuildDataSection(after_quickFilters):dropdown/tabs/toggle<select>filtered to discrete-option fields only (select/boolean types or fields with explicit options), per-field remove button<Input>, default tab checkbox,showAllRecordstoggle (defaults to checked, matching runtime behavior whereshowAllRecords !== false),allowAddTabtoggleExpandableWidgetwith comma-joined field/tab label summaryupdateField('userFilters', ...)→ draft → live previewi18n
en.ts/zh.ts:ufElements,ufDropdown,ufTabs,ufToggle,ufAddField,ufNoFields,ufAddTab,ufTabLabel,ufShowAllRecords,ufAllowAddTab.Tests
view-config-schema.test.tsx: field key order updated,_userFiltersexistence + ordering assertions.ObjectView.test.tsx: mock rendersschema-userFilters, passthrough test fromlistViews[].userFilters.ObjectView.test.tsx: propagation test fromactiveView.userFilters.ViewConfigPanel.test.tsx: 8 new tests — editor rendering, element type switching, field add/remove, tabs section rendering, tab add/remove/edit label.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.