[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:
+80
-18
@@ -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,
|
||||
},
|
||||
}),
|
||||
|
||||
+14
-13
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user