[DASHBOARDS] chat agent improvements + new validation layer (#17722)

https://github.com/user-attachments/assets/09550210-76c5-4a40-83b6-9ab785ca10c3



https://github.com/user-attachments/assets/352427fc-0a2a-4f1b-86e9-db99daea0018

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
nitin
2026-02-10 19:13:36 +05:30
committed by GitHub
parent 148584c730
commit e995e84621
36 changed files with 1334 additions and 168 deletions
@@ -0,0 +1,21 @@
import { isDefined } from 'twenty-shared/utils';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
export const findActiveFlatFieldMetadataById = (
fieldId: string | null | undefined,
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
): FlatFieldMetadata | null => {
if (!isDefined(fieldId)) return null;
const field = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: fieldId,
flatEntityMaps: flatFieldMetadataMaps,
});
if (!isDefined(field) || !field.isActive) return null;
return field;
};
@@ -0,0 +1,10 @@
import { COMPOSITE_FIELD_TYPE_SUB_FIELDS_NAMES } from 'twenty-shared/constants';
import { type FieldMetadataType } from 'twenty-shared/types';
const compositeSubFieldMaps = COMPOSITE_FIELD_TYPE_SUB_FIELDS_NAMES as Record<
string,
Record<string, string>
>;
export const getCompositeSubfieldNames = (fieldType: FieldMetadataType) =>
Object.values(compositeSubFieldMaps[fieldType] ?? {});
@@ -0,0 +1,8 @@
import { GRAPH_CONFIGURATION_TYPES } from 'src/engine/metadata-modules/page-layout-widget/constants/graph-configuration-types.constant';
import { type AllPageLayoutWidgetConfiguration } from 'src/engine/metadata-modules/page-layout-widget/types/all-page-layout-widget-configuration.type';
import { type ChartFieldsForValidation } from 'src/engine/metadata-modules/page-layout-widget/types/chart-fields-for-validation.type';
export const isChartFieldsForValidation = (
configuration: AllPageLayoutWidgetConfiguration,
): configuration is ChartFieldsForValidation =>
GRAPH_CONFIGURATION_TYPES.has(configuration.configurationType);
@@ -0,0 +1,32 @@
import { isDefined } from 'twenty-shared/utils';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
export const resolveMorphTargetObjectId = ({
field,
allFields,
}: {
field: FlatFieldMetadata;
allFields: FlatFieldMetadata[];
}): string | null => {
if (!isDefined(field.morphId)) {
return null;
}
const targetIds = new Set<string>();
allFields.forEach((flatField) => {
if (
flatField.morphId === field.morphId &&
isDefined(flatField.relationTargetObjectMetadataId)
) {
targetIds.add(flatField.relationTargetObjectMetadataId);
}
});
if (targetIds.size !== 1) {
return null;
}
return [...targetIds][0] ?? null;
};
@@ -0,0 +1,138 @@
import { isDefined } from 'twenty-shared/utils';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
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';
import { type AllPageLayoutWidgetConfiguration } from 'src/engine/metadata-modules/page-layout-widget/types/all-page-layout-widget-configuration.type';
import { findActiveFlatFieldMetadataById } from 'src/engine/metadata-modules/page-layout-widget/utils/find-active-flat-field-metadata-by-id.util';
import { isChartFieldsForValidation } from 'src/engine/metadata-modules/page-layout-widget/utils/is-chart-fields-for-validation.util';
import { validateGroupByField } from 'src/engine/metadata-modules/page-layout-widget/utils/validate-group-by-field.util';
export const validateChartConfigurationFieldReferences = ({
configuration,
objectMetadataId,
widgetType,
flatObjectMetadataMaps,
flatFieldMetadataMaps,
}: {
configuration?: AllPageLayoutWidgetConfiguration | null;
objectMetadataId?: string | null;
widgetType?: WidgetType | null;
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
}): void => {
if (!isDefined(configuration)) return;
if (!isChartFieldsForValidation(configuration)) {
if (widgetType === WidgetType.GRAPH) {
throw new Error(
'GRAPH widgets require configurationType AGGREGATE_CHART, BAR_CHART, GAUGE_CHART, LINE_CHART, or PIE_CHART.',
);
}
return;
}
if (widgetType && widgetType !== WidgetType.GRAPH) {
throw new Error(
`Graph configuration is only valid for widgets of type GRAPH.`,
);
}
if (!isDefined(objectMetadataId)) {
throw new Error('objectMetadataId is required for graph widgets.');
}
const objectMetadata = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: objectMetadataId,
flatEntityMaps: flatObjectMetadataMaps,
});
if (!isDefined(objectMetadata) || !objectMetadata.isActive) {
throw new Error(`objectMetadataId "${objectMetadataId}" not found.`);
}
const allFields = Object.values(flatFieldMetadataMaps.byUniversalIdentifier)
.filter(isDefined)
.filter((field) => field.isActive);
const fieldsByObjectId = new Map<string, FlatFieldMetadata[]>();
allFields.forEach((field) => {
const existing = fieldsByObjectId.get(field.objectMetadataId) ?? [];
existing.push(field);
fieldsByObjectId.set(field.objectMetadataId, existing);
});
const aggregateField = findActiveFlatFieldMetadataById(
configuration.aggregateFieldMetadataId,
flatFieldMetadataMaps,
);
if (!isDefined(aggregateField)) {
throw new Error(
`aggregateFieldMetadataId "${configuration.aggregateFieldMetadataId}" not found.`,
);
}
if (aggregateField.objectMetadataId !== objectMetadataId) {
throw new Error(
`aggregateFieldMetadataId must belong to objectMetadataId "${objectMetadataId}".`,
);
}
switch (configuration.configurationType) {
case WidgetConfigurationType.BAR_CHART:
case WidgetConfigurationType.LINE_CHART: {
validateGroupByField({
fieldId: configuration.primaryAxisGroupByFieldMetadataId,
subFieldName: configuration.primaryAxisGroupBySubFieldName,
paramName: 'primaryAxisGroupByFieldMetadataId',
objectMetadataId,
flatFieldMetadataMaps,
allFields,
fieldsByObjectId,
});
if (isDefined(configuration.secondaryAxisGroupBySubFieldName)) {
if (!isDefined(configuration.secondaryAxisGroupByFieldMetadataId)) {
throw new Error(
'secondaryAxisGroupByFieldMetadataId is required when secondaryAxisGroupBySubFieldName is provided.',
);
}
}
if (isDefined(configuration.secondaryAxisGroupByFieldMetadataId)) {
validateGroupByField({
fieldId: configuration.secondaryAxisGroupByFieldMetadataId,
subFieldName: configuration.secondaryAxisGroupBySubFieldName,
paramName: 'secondaryAxisGroupByFieldMetadataId',
objectMetadataId,
flatFieldMetadataMaps,
allFields,
fieldsByObjectId,
});
}
break;
}
case WidgetConfigurationType.PIE_CHART: {
validateGroupByField({
fieldId: configuration.groupByFieldMetadataId,
subFieldName: configuration.groupBySubFieldName,
paramName: 'groupByFieldMetadataId',
objectMetadataId,
flatFieldMetadataMaps,
allFields,
fieldsByObjectId,
});
break;
}
case WidgetConfigurationType.AGGREGATE_CHART:
default:
break;
}
};
@@ -0,0 +1,34 @@
import { isDefined } from 'twenty-shared/utils';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { getCompositeSubfieldNames } from 'src/engine/metadata-modules/page-layout-widget/utils/get-composite-subfield-names.util';
export const validateCompositeSubfield = ({
field,
subFieldName,
paramName,
}: {
field: FlatFieldMetadata;
subFieldName: string | null | undefined;
paramName: string;
}): void => {
const allowedSubFields = getCompositeSubfieldNames(field.type);
if (!isDefined(subFieldName)) {
throw new Error(
`Composite field "${paramName}" requires a subfield. Allowed: ${allowedSubFields.join(', ')}`,
);
}
if (subFieldName.includes('.')) {
throw new Error(`Composite subfield "${subFieldName}" is invalid.`);
}
if (!allowedSubFields.includes(subFieldName)) {
throw new Error(
`Invalid subfield "${subFieldName}" for "${paramName}". Allowed: ${allowedSubFields.join(
', ',
)}`,
);
}
};
@@ -0,0 +1,69 @@
import { isDefined } from 'twenty-shared/utils';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { isMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util';
import { findActiveFlatFieldMetadataById } from 'src/engine/metadata-modules/page-layout-widget/utils/find-active-flat-field-metadata-by-id.util';
import { validateCompositeSubfield } from 'src/engine/metadata-modules/page-layout-widget/utils/validate-composite-subfield.util';
import { validateRelationSubfield } from 'src/engine/metadata-modules/page-layout-widget/utils/validate-relation-subfield.util';
export const validateGroupByField = ({
fieldId,
subFieldName,
paramName,
objectMetadataId,
flatFieldMetadataMaps,
allFields,
fieldsByObjectId,
}: {
fieldId?: string | null;
subFieldName?: string | null;
paramName: string;
objectMetadataId: string;
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
allFields: FlatFieldMetadata[];
fieldsByObjectId: Map<string, FlatFieldMetadata[]>;
}): void => {
if (!isDefined(fieldId)) {
throw new Error(`${paramName} is required.`);
}
const field = findActiveFlatFieldMetadataById(fieldId, flatFieldMetadataMaps);
if (!isDefined(field)) {
throw new Error(`${paramName} "${fieldId}" not found.`);
}
if (field.objectMetadataId !== objectMetadataId) {
throw new Error(
`${paramName} must belong to objectMetadataId "${objectMetadataId}".`,
);
}
if (isCompositeFieldMetadataType(field.type)) {
validateCompositeSubfield({
field,
subFieldName,
paramName: field.name,
});
return;
}
if (isMorphOrRelationFlatFieldMetadata(field)) {
validateRelationSubfield({
field,
subFieldName,
paramName: field.name,
allFields,
fieldsByObjectId,
});
return;
}
if (isDefined(subFieldName)) {
throw new Error(`Field "${field.name}" does not support subfields.`);
}
};
@@ -0,0 +1,85 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { getCompositeSubfieldNames } from 'src/engine/metadata-modules/page-layout-widget/utils/get-composite-subfield-names.util';
import { resolveMorphTargetObjectId } from 'src/engine/metadata-modules/page-layout-widget/utils/resolve-morph-target-object-id.util';
import { validateCompositeSubfield } from 'src/engine/metadata-modules/page-layout-widget/utils/validate-composite-subfield.util';
export const validateRelationSubfield = ({
field,
subFieldName,
paramName,
allFields,
fieldsByObjectId,
}: {
field: FlatFieldMetadata;
subFieldName: string | null | undefined;
paramName: string;
allFields: FlatFieldMetadata[];
fieldsByObjectId: Map<string, FlatFieldMetadata[]>;
}): void => {
if (!isDefined(subFieldName)) {
return;
}
const dotIndex = subFieldName.indexOf('.');
const nestedFieldName =
dotIndex === -1 ? subFieldName : subFieldName.slice(0, dotIndex);
const nestedSubFieldName =
dotIndex === -1 ? undefined : subFieldName.slice(dotIndex + 1);
if (!nestedFieldName) {
throw new Error(`Relation subfield "${subFieldName}" is invalid.`);
}
if (isDefined(nestedSubFieldName) && nestedSubFieldName.includes('.')) {
throw new Error(`Relation subfield "${subFieldName}" is invalid.`);
}
let targetObjectId = field.relationTargetObjectMetadataId ?? null;
if (field.type === FieldMetadataType.MORPH_RELATION) {
targetObjectId = resolveMorphTargetObjectId({ field, allFields });
}
if (!isDefined(targetObjectId)) {
throw new Error(
`Relation field "${paramName}" does not have a resolvable target object.`,
);
}
const targetFields = fieldsByObjectId.get(targetObjectId) ?? [];
const nestedField = targetFields.find(
(targetField) => targetField.name === nestedFieldName,
);
if (!isDefined(nestedField)) {
throw new Error(
`Relation subfield "${nestedFieldName}" not found for "${paramName}".`,
);
}
if (!isDefined(nestedSubFieldName)) {
if (isCompositeFieldMetadataType(nestedField.type)) {
const allowed = getCompositeSubfieldNames(nestedField.type);
throw new Error(
`Composite field "${nestedFieldName}" requires a subfield. Use "${nestedFieldName}.<subfield>" where subfield is one of: ${allowed.join(', ')}`,
);
}
return;
}
if (!isCompositeFieldMetadataType(nestedField.type)) {
throw new Error(`Field "${nestedFieldName}" is not composite.`);
}
validateCompositeSubfield({
field: nestedField,
subFieldName: nestedSubFieldName,
paramName: nestedFieldName,
});
};