[Dashboards] [ai] update dashboard tools schema and skill with correct config field names (#17180)

<img width="2560" height="1353" alt="CleanShot 2026-01-16 at 00 55 56"
src="https://github.com/user-attachments/assets/c242071c-13d3-494d-bcce-9d45f36fcd77"
/>
This commit is contained in:
nitin
2026-01-16 02:25:47 +05:30
committed by GitHub
parent 0fa8098c50
commit 8908e0785f
4 changed files with 133 additions and 51 deletions
@@ -146,41 +146,101 @@ Before creating any GRAPH widget, you MUST:
GRAPH widgets require real UUIDs from the workspace metadata, NOT made-up values.
## Widget Configuration
## Understanding User Language
### GRAPH - AGGREGATE (KPI numbers)
Shows a single aggregated value (count, sum, average).
Required:
- objectMetadataId: UUID of the object (e.g., opportunity)
- configuration.graphType: "AGGREGATE"
- configuration.aggregateFieldMetadataId: UUID of field to aggregate
- configuration.aggregateOperation: "COUNT", "SUM", "AVG", "MIN", "MAX"
Users describe charts using UI terminology. Here's how to translate:
### GRAPH - BAR/LINE Charts
Shows data grouped by a dimension.
### Bar/Line Chart Settings
**Data section:**
- "Source" / "change the object": objectMetadataId
- "Filter" / "filter the data": configuration.filter
**X axis section (primary grouping - the bars/categories):**
- "X axis" / "data on display" / "categories": primaryAxisGroupByFieldMetadataId
- "Date granularity" (on X axis): primaryAxisDateGranularity
- "Sort by" (on X axis): primaryAxisOrderBy
**Y axis section (what's being measured + optional secondary grouping):**
- "Y axis" / "data on display" / "measure" / "metric": aggregateFieldMetadataId + aggregateOperation
- "Group by" / "stacking" / "colors" / "breakdown": secondaryAxisGroupByFieldMetadataId
- "Date granularity" (on Group by): secondaryAxisDateGranularity
- "Sort by" (on Group by): secondaryAxisOrderBy
- "Cumulative" / "running total": cumulative
- "Min range" / "Max range": rangeMin, rangeMax
**Style section:**
- "Stacked" / "stacked bars": layout stays same, it's about secondaryAxisGroupByFieldMetadataId
- "Data labels" / "show values": displayDataLabel
- "Legend" / "show legend": displayLegend
### CRITICAL: "Remove groupby" / "remove stacking" / "unstacked"
When users say this for bar/line charts, they mean remove the SECONDARY grouping (the colors/stacking).
- Set secondaryAxisGroupByFieldMetadataId to null
- Keep the chart type (BAR_CHART/LINE_CHART)
- Keep primaryAxisGroupByFieldMetadataId (the X axis categories)
- DO NOT convert to AGGREGATE_CHART unless user explicitly asks for "just a number" or "KPI"
### Pie Chart Settings
- "Each slice represents" / "slices": groupByFieldMetadataId
- "Hide empty category": hideEmptyCategory
- "Show value in center": showValueInCenter
### Aggregate Chart Settings (KPI numbers)
- "Prefix" (e.g., "$"): prefix
- "Suffix" (e.g., "%"): suffix
## Widget Configuration Types
### AGGREGATE_CHART (KPI number widget)
Shows a single aggregated value.
Required:
- objectMetadataId: UUID of the object
- configuration.graphType: "VERTICAL_BAR", "HORIZONTAL_BAR", or "LINE"
- configuration.configurationType: "AGGREGATE_CHART"
- configuration.aggregateFieldMetadataId: UUID of field to aggregate
- configuration.aggregateOperation: "COUNT", "SUM", "AVG", "MIN", "MAX"
Optional: prefix, suffix, displayDataLabel, filter
### BAR_CHART
Shows data grouped by categories with optional secondary grouping.
Required:
- objectMetadataId: UUID of the object
- configuration.configurationType: "BAR_CHART"
- configuration.aggregateFieldMetadataId: field to aggregate
- configuration.aggregateOperation: aggregation type
- configuration.primaryAxisGroupByFieldMetadataId: field to group by (x-axis)
- configuration.primaryAxisGroupByFieldMetadataId: X axis categories
- configuration.layout: "VERTICAL" or "HORIZONTAL"
Optional: secondaryAxisGroupByFieldMetadataId (for stacking/colors), displayDataLabel, displayLegend, filter
### GRAPH - PIE Charts
### LINE_CHART
Shows trends over a dimension.
Required:
- objectMetadataId: UUID of the object
- configuration.configurationType: "LINE_CHART"
- configuration.aggregateFieldMetadataId: field to aggregate
- configuration.aggregateOperation: aggregation type
- configuration.primaryAxisGroupByFieldMetadataId: X axis (usually date)
Optional: secondaryAxisGroupByFieldMetadataId (for multiple lines), cumulative, displayDataLabel, filter
### PIE_CHART
Shows data distribution as slices.
Required:
- objectMetadataId: UUID of the object
- configuration.graphType: "PIE"
- configuration.configurationType: "PIE_CHART"
- configuration.aggregateFieldMetadataId: field to aggregate
- configuration.aggregateOperation: aggregation type
- configuration.groupByFieldMetadataId: field to slice by
- configuration.groupByFieldMetadataId: field to slice by (NOTE: different field name than bar/line!)
Optional: displayDataLabel, hideEmptyCategory, showValueInCenter, filter
### IFRAME
Embeds external content:
- configuration.configurationType: "IFRAME"
- configuration.url: "https://..."
### STANDALONE_RICH_TEXT
Text content widget:
- configuration.body: "Your text here"
- configuration.configurationType: "STANDALONE_RICH_TEXT"
- configuration.body: rich text content
## Grid System
@@ -197,14 +257,16 @@ Text content widget:
1. Ask user what data they want to visualize
2. Load list_object_metadata_items to discover available objects and fields
3. Create dashboard with appropriate widgets using real field IDs
4. Use get_dashboard to verify creation
4. Use get_dashboard to verify creation and see current configuration
5. When modifying, first understand current config before making changes
## Best Practices
- Place KPIs at the top (row 0)
- Group related charts together
- Use consistent heights within rows
- Start simple, add complexity as needed`,
- Start simple, add complexity as needed
- When user asks to modify a chart, clarify if they want to change settings OR change chart type`,
isCustom: false,
},
}),
@@ -60,24 +60,25 @@ GRID SYSTEM:
WIDGET TYPES:
1. GRAPH with graphType "AGGREGATE" (KPI number):
- Requires: objectMetadataId, configuration.graphType, configuration.aggregateFieldMetadataId, configuration.aggregateOperation
- Example: { type: "GRAPH", objectMetadataId: "<opportunity-object-uuid>", configuration: { graphType: "AGGREGATE", aggregateFieldMetadataId: "<amount-field-uuid>", aggregateOperation: "SUM" } }
1. GRAPH with configurationType "AGGREGATE_CHART" (KPI number):
- Requires: objectMetadataId, configuration.configurationType, configuration.aggregateFieldMetadataId, configuration.aggregateOperation
- Example: { type: "GRAPH", objectMetadataId: "<opportunity-object-uuid>", configuration: { configurationType: "AGGREGATE_CHART", aggregateFieldMetadataId: "<amount-field-uuid>", aggregateOperation: "SUM" } }
2. GRAPH with graphType "VERTICAL_BAR" or "HORIZONTAL_BAR":
2. GRAPH with configurationType "BAR_CHART":
- Additional required: configuration.primaryAxisGroupByFieldMetadataId, configuration.layout ("VERTICAL" or "HORIZONTAL")
- Example: { type: "GRAPH", objectMetadataId: "<opportunity-object-uuid>", configuration: { configurationType: "BAR_CHART", aggregateFieldMetadataId: "<amount-field-uuid>", aggregateOperation: "COUNT", primaryAxisGroupByFieldMetadataId: "<stage-field-uuid>", layout: "VERTICAL" } }
3. GRAPH with configurationType "LINE_CHART":
- Additional required: configuration.primaryAxisGroupByFieldMetadataId
- Example: { graphType: "VERTICAL_BAR", aggregateFieldMetadataId: "<count-field-uuid>", aggregateOperation: "COUNT", primaryAxisGroupByFieldMetadataId: "<stage-field-uuid>" }
- Example: { type: "GRAPH", objectMetadataId: "<opportunity-object-uuid>", configuration: { configurationType: "LINE_CHART", aggregateFieldMetadataId: "<amount-field-uuid>", aggregateOperation: "SUM", primaryAxisGroupByFieldMetadataId: "<created-date-field-uuid>" } }
3. GRAPH with graphType "LINE":
- Same as bar charts, good for time series
4. GRAPH with configurationType "PIE_CHART":
- Additional required: configuration.groupByFieldMetadataId (note: different field name!)
- Example: { type: "GRAPH", objectMetadataId: "<opportunity-object-uuid>", configuration: { configurationType: "PIE_CHART", aggregateFieldMetadataId: "<id-field-uuid>", aggregateOperation: "COUNT", groupByFieldMetadataId: "<stage-field-uuid>" } }
4. GRAPH with graphType "PIE":
- Requires: objectMetadataId, aggregateFieldMetadataId, aggregateOperation, groupByFieldMetadataId
- Example: { graphType: "PIE", aggregateFieldMetadataId: "<id-field-uuid>", aggregateOperation: "COUNT", groupByFieldMetadataId: "<stage-field-uuid>" }
5. IFRAME: { type: "IFRAME", configuration: { configurationType: "IFRAME", url: "https://..." } }
5. IFRAME: { type: "IFRAME", configuration: { url: "https://..." } }
6. STANDALONE_RICH_TEXT: { type: "STANDALONE_RICH_TEXT", configuration: { body: "..." } }
6. STANDALONE_RICH_TEXT: { type: "STANDALONE_RICH_TEXT", configuration: { configurationType: "STANDALONE_RICH_TEXT", body: { ... } } }
AGGREGATION OPERATIONS: COUNT, SUM, AVG, MIN, MAX, COUNT_EMPTY, COUNT_NOT_EMPTY`,
inputSchema: createCompleteDashboardSchema,
@@ -1,7 +1,7 @@
import { z } from 'zod';
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
import { GraphType } from 'src/engine/metadata-modules/page-layout-widget/enums/graph-type.enum';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
export const gridPositionSchema = z.object({
@@ -28,7 +28,7 @@ export const widgetTypeSchema = z.enum([
// Graph configuration schema for AGGREGATE type (KPI numbers)
const aggregateChartConfigSchema = z.object({
graphType: z.literal(GraphType.AGGREGATE_CHART),
configurationType: z.literal(WidgetConfigurationType.AGGREGATE_CHART),
aggregateFieldMetadataId: z
.string()
.uuid()
@@ -48,7 +48,7 @@ const aggregateChartConfigSchema = z.object({
// Graph configuration schema for BAR charts
const barChartConfigSchema = z
.object({
graphType: z.literal(GraphType.BAR_CHART),
configurationType: z.literal(WidgetConfigurationType.BAR_CHART),
aggregateFieldMetadataId: z
.string()
.uuid()
@@ -87,8 +87,6 @@ const barChartConfigSchema = z
displayLegend: z.boolean().optional().default(true),
layout: z
.enum(['VERTICAL', 'HORIZONTAL'])
.optional()
.default('VERTICAL')
.describe('Layout orientation for bar charts'),
filter: z.record(z.string(), z.unknown()).optional(),
})
@@ -118,7 +116,7 @@ const barChartConfigSchema = z
// Graph configuration schema for LINE charts
const lineChartConfigSchema = z
.object({
graphType: z.literal(GraphType.LINE_CHART),
configurationType: z.literal(WidgetConfigurationType.LINE_CHART),
aggregateFieldMetadataId: z.string().uuid(),
aggregateOperation: z.nativeEnum(AggregateOperations),
primaryAxisGroupByFieldMetadataId: z.string().uuid(),
@@ -176,7 +174,7 @@ const lineChartConfigSchema = z
// Graph configuration schema for PIE charts
const pieChartConfigSchema = z
.object({
graphType: z.literal(GraphType.PIE_CHART),
configurationType: z.literal(WidgetConfigurationType.PIE_CHART),
aggregateFieldMetadataId: z.string().uuid(),
aggregateOperation: z.nativeEnum(AggregateOperations),
groupByFieldMetadataId: z
@@ -211,25 +209,37 @@ const pieChartConfigSchema = z
// Iframe configuration
const iframeConfigSchema = z.object({
url: z.string().url().describe('URL to embed'),
configurationType: z.literal(WidgetConfigurationType.IFRAME),
url: z.string().url().optional().describe('URL to embed'),
});
// Rich text configuration
const richTextConfigSchema = z.object({
body: z.string().optional().describe('Rich text content'),
configurationType: z.literal(WidgetConfigurationType.STANDALONE_RICH_TEXT),
body: z.unknown().optional().describe('Rich text content (RichTextV2Body)'),
});
export const graphConfigurationSchema = z.discriminatedUnion('graphType', [
aggregateChartConfigSchema,
barChartConfigSchema,
lineChartConfigSchema,
pieChartConfigSchema,
]);
export const graphConfigurationSchema = z.discriminatedUnion(
'configurationType',
[
aggregateChartConfigSchema,
barChartConfigSchema,
lineChartConfigSchema,
pieChartConfigSchema,
],
);
export const widgetConfigurationSchema = z
.union([graphConfigurationSchema, iframeConfigSchema, richTextConfigSchema])
.discriminatedUnion('configurationType', [
aggregateChartConfigSchema,
barChartConfigSchema,
lineChartConfigSchema,
pieChartConfigSchema,
iframeConfigSchema,
richTextConfigSchema,
])
.optional()
.describe('Widget configuration - structure depends on widget type');
// Export enums for documentation
export { AggregateOperations, GraphType as GraphType };
export { AggregateOperations, WidgetConfigurationType };
@@ -1,4 +1,4 @@
import { isDefined } from 'twenty-shared/utils';
import { isDefined, isEmptyObject } from 'twenty-shared/utils';
import { z } from 'zod';
import { type WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
@@ -24,7 +24,7 @@ const updateDashboardWidgetSchema = z.object({
.uuid()
.optional()
.describe('New object metadata ID'),
configuration: widgetConfigurationSchema,
configuration: widgetConfigurationSchema.optional(),
});
export const createUpdateDashboardWidgetTool = (
@@ -54,7 +54,16 @@ Only provide fields you want to change - others remain unchanged.`,
try {
const { widgetId, ...updates } = parameters;
const updateData = Object.fromEntries(
Object.entries(updates).filter(([, value]) => isDefined(value)),
Object.entries(updates).filter(([key, value]) => {
if (!isDefined(value)) {
return false;
}
if (key === 'configuration' && isEmptyObject(value)) {
return false;
}
return true;
}),
);
const widget = await deps.pageLayoutWidgetService.update({