Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…xis truncation - Add resolveGroupByLabels() to convert groupBy field values to display labels using field metadata (select options, lookup record names, humanizeLabel fallback) - Integrate label resolution into ObjectChart fetchData flow - Remove value.slice(0, 3) truncation from AdvancedChartImpl XAxis tickFormatters - Replace with adaptive formatting: mobile truncates at 8 chars, desktop shows full labels with angle=-35 rotation for long text - Add comprehensive tests for label resolution (17 tests) Agent-Logs-Url: https://github.com/objectstack-ai/objectui/sessions/eae827ef-e746-4d58-8b55-9f9e8fcd1dd0 Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…Impl Address code review feedback: memoize the data.some() long label check using useMemo to avoid O(n) recalculation on every render. Agent-Logs-Url: https://github.com/objectstack-ai/objectui/sessions/eae827ef-e746-4d58-8b55-9f9e8fcd1dd0 Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR improves chart X-axis labeling in @object-ui/plugin-charts by resolving groupBy values into human-readable labels using object field metadata, and by replacing an overly aggressive tick truncation strategy in the advanced chart implementation.
Changes:
- Added
resolveGroupByLabels()(plus a localhumanizeLabel()fallback) and integrated it into theObjectChartdata-fetch flow. - Updated
AdvancedChartImplX-axis tick formatting to be adaptive (mobile truncation, desktop full labels with rotation when needed). - Added targeted tests for groupBy label resolution and documented the fix in
CHANGELOG.md.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| packages/plugin-charts/src/ObjectChart.tsx | Adds value→label resolution for groupBy (select + lookup + fallback) and wires it into fetch flow. |
| packages/plugin-charts/src/AdvancedChartImpl.tsx | Replaces hardcoded slice(0, 3) with adaptive tick formatting and conditional label rotation. |
| packages/plugin-charts/src/tests/ObjectChart.labelResolution.test.ts | Adds unit tests covering select/lookup/fallback label resolution behavior. |
| CHANGELOG.md | Documents the charts groupBy label resolution fix and the tick formatting change. |
|
|
||
| /** | ||
| * Humanize a snake_case or kebab-case string into Title Case. | ||
| * Local implementation to avoid a dependency on @object-ui/fields. | ||
| */ | ||
| export function humanizeLabel(value: string): string { | ||
| return value.replace(/[_-]/g, ' ').replace(/\b\w/g, c => c.toUpperCase()); |
There was a problem hiding this comment.
humanizeLabel() duplicates the existing implementation in @object-ui/fields (packages/fields/src/index.tsx). If keeping charts independent from fields is important, consider moving this shared helper to a common package (e.g. @object-ui/core utils) so the behavior doesn’t diverge over time across packages.
| /** | |
| * Humanize a snake_case or kebab-case string into Title Case. | |
| * Local implementation to avoid a dependency on @object-ui/fields. | |
| */ | |
| export function humanizeLabel(value: string): string { | |
| return value.replace(/[_-]/g, ' ').replace(/\b\w/g, c => c.toUpperCase()); | |
| import startCase from 'lodash/startCase'; | |
| /** | |
| * Humanize a snake_case or kebab-case string into Title Case. | |
| * Uses lodash's startCase to avoid duplicating logic from other packages. | |
| */ | |
| export function humanizeLabel(value: string): string { | |
| const normalized = value.replace(/[_-]+/g, ' ').trim(); | |
| return startCase(normalized); |
| const ids = [...new Set(data.map(row => row[groupByField]).filter(v => v != null))]; | ||
| if (ids.length === 0) return data; | ||
|
|
||
| try { | ||
| const results = await dataSource.find(referenceTo, { | ||
| $filter: { id: { $in: ids } }, | ||
| $top: ids.length, | ||
| }); |
There was a problem hiding this comment.
Lookup label resolution hardcodes the ID field in the query as id ($filter: { id: { $in: ids } }). Elsewhere (e.g. LookupField) metadata can specify id_field, and some datasets use _id. Consider deriving the ID field from lookup metadata (fallback to id) and normalizing ids to strings to improve compatibility across DataSource implementations.
| // Build id→name map using common display fields | ||
| const idToName: Record<string, string> = {}; | ||
| for (const rec of records) { | ||
| const id = String(rec.id ?? rec._id ?? ''); | ||
| const name = rec.name || rec.label || rec.title || id; |
There was a problem hiding this comment.
When building the lookup id→label map, the display label is inferred via rec.name || rec.label || rec.title, but lookup metadata already provides reference_field (and some schemas use a non-name display field). Using fieldDef.reference_field/display_field (with a sensible fallback) would make label resolution correct and consistent with the LookupField widget behavior.
| // Build id→name map using common display fields | |
| const idToName: Record<string, string> = {}; | |
| for (const rec of records) { | |
| const id = String(rec.id ?? rec._id ?? ''); | |
| const name = rec.name || rec.label || rec.title || id; | |
| // Build id→label map using reference/display field metadata with sensible fallbacks | |
| const idToName: Record<string, string> = {}; | |
| const displayField = | |
| (fieldDef as any).reference_field || | |
| (fieldDef as any).display_field || | |
| 'name'; | |
| for (const rec of records) { | |
| const id = String(rec.id ?? rec._id ?? ''); | |
| const configuredLabel = displayField ? rec[displayField] : undefined; | |
| const name = | |
| configuredLabel || | |
| rec.name || | |
| rec.label || | |
| rec.title || | |
| id; |
| console.log('📈 Rendering Chart:', { chartType, dataLength: data.length, config, series, xAxisKey }); | ||
|
|
||
| // Memoize whether any X-axis label is long enough to warrant angle rotation | ||
| const hasLongLabels = React.useMemo( | ||
| () => data.some((d: any) => String(d[xAxisKey] || '').length > 5), | ||
| [data, xAxisKey], |
There was a problem hiding this comment.
There’s a console.log in the render path which will run on every render and can create noisy output (and slight perf impact) in consuming apps. Consider removing it or gating it behind an explicit debug flag/env check before shipping.
|
@copilot fix all |
- Use id_field/display_field from lookup metadata instead of hardcoding 'id'/'name' - Remove console.log from AdvancedChartImpl render path - Add tests for display_field and id_field metadata support Agent-Logs-Url: https://github.com/objectstack-ai/objectui/sessions/2a93e570-33d5-4c95-9f22-d13493ed8da4 Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Fixed all review comments in a897212:
Added 2 new tests for |
Chart X-axis labels showed raw field values (
closed_won, lookup IDs like2) and were hard-truncated to 3 characters viavalue.slice(0, 3), making them unreadable.Data layer (
ObjectChart.tsx)resolveGroupByLabels()— after data fetch, resolves groupBy field values to display labels using object schema metadata:field.options(handles both{value, label}objects and plain strings)dataSource.find()onreference_toobject, maps id→name using metadata-drivenid_fieldandreference_field/display_field(with sensible fallbacks)humanizeLabel()(snake_case → Title Case)dataSource.getObjectSchema()infetchData, gracefully no-ops if unavailablefieldDef.id_field(fallback toid) and the display label fromfieldDef.reference_field/fieldDef.display_field(fallback chain: configured field →name→label→title→ id), consistent with LookupField widget behaviorRendering layer (
AdvancedChartImpl.tsx)value.slice(0, 3)from both XAxis tickFormatters (combo + default charts)…; Desktop: full label withangle: -35rotation when labels > 5 charshasLongLabelsviauseMemoto avoid O(n) check per renderconsole.logfrom render path to avoid noisy output in consuming appsTests
display_fieldandid_fieldmetadata), error fallback, edge cases