10876138d2
## Summary `joinColumnName` on relation field settings is always derivable from the field name (and the target object name for morph relations). This PR stops reading it from settings anywhere in production code; the stored value is no longer used. The settings field is **not** removed from data yet — a follow-up can drop it once we are confident nothing depends on the stored value. ## Helpers The helpers are split by layer because frontend and backend hold morph relations differently: the frontend has a base name plus a `morphRelations[]` array, the backend has one row per target with the name already morph-resolved. | Helper | Layer | When to use | |---|---|---| | `computeRelationGqlFieldJoinColumnName` | Shared / frontend (`gqlField`) | Non-morph relation on the frontend. | | `computeMorphRelationGqlFieldName` | Shared / frontend (`gqlField`) | Need the per-target morph gqlField name (e.g. `targetCompany`). | | `computeMorphRelationGqlFieldJoinColumnName` | Shared / frontend (`gqlField`) | Per-target morph join column on the frontend. Prefer over the non-morph helper for any morph field — it forces the per-target inputs. | | `computeMorphOrRelationFieldJoinColumnName` | Backend (`FlatFieldMetadata.name`) | Any backend read or write — the flat name is already morph-resolved, so one helper covers both cases. | | `computeMorphRelationFlatFieldName` | Backend (`FlatFieldMetadata.name`) | **Mutation paths only** (create / update / object rename). Reads consume the stored `field.name` and never call this. | ## Test plan - [x] Typecheck and lint (front, server, shared) - [x] Existing unit tests pass - [ ] CI green
102 lines
3.5 KiB
TypeScript
102 lines
3.5 KiB
TypeScript
import {
|
|
checkIfFieldIsImageIdentifier,
|
|
checkIfFieldIsLabelIdentifier,
|
|
} from 'twenty-shared/metadata';
|
|
import {
|
|
FieldMetadataType,
|
|
RelationType,
|
|
type RestrictedFieldsPermissions,
|
|
compositeTypeDefinitions,
|
|
} from 'twenty-shared/types';
|
|
|
|
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
|
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 { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
|
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
|
import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
|
|
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
|
|
|
type SelectableFieldsStructured = Record<
|
|
string,
|
|
boolean | Record<string, boolean>
|
|
>;
|
|
|
|
export const getAllSelectableFields = ({
|
|
restrictedFields,
|
|
flatObjectMetadata,
|
|
flatFieldMetadataMaps,
|
|
onlyUseLabelIdentifierFieldsInRelations = false,
|
|
}: {
|
|
restrictedFields: RestrictedFieldsPermissions;
|
|
flatObjectMetadata: FlatObjectMetadata;
|
|
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
|
onlyUseLabelIdentifierFieldsInRelations?: boolean;
|
|
}): SelectableFieldsStructured => {
|
|
const result: SelectableFieldsStructured = {};
|
|
|
|
for (const fieldId of flatObjectMetadata.fieldIds) {
|
|
const flatField = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
|
flatEntityMaps: flatFieldMetadataMaps,
|
|
flatEntityId: fieldId,
|
|
});
|
|
|
|
if (restrictedFields[flatField.id]?.canRead === false) continue;
|
|
|
|
if (onlyUseLabelIdentifierFieldsInRelations) {
|
|
const fieldIsLabelIdentifier = checkIfFieldIsLabelIdentifier(
|
|
flatField,
|
|
flatObjectMetadata,
|
|
);
|
|
const fieldIsImageIdentifier = checkIfFieldIsImageIdentifier(
|
|
flatField,
|
|
flatObjectMetadata,
|
|
);
|
|
const fieldIsIdField = flatField.name === 'id';
|
|
|
|
if (
|
|
!fieldIsLabelIdentifier &&
|
|
!fieldIsImageIdentifier &&
|
|
!fieldIsIdField
|
|
) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (isCompositeFieldMetadataType(flatField.type)) {
|
|
const compositeType = compositeTypeDefinitions.get(flatField.type);
|
|
|
|
if (!compositeType) {
|
|
throw new Error(
|
|
`Composite type definition not found for type: ${flatField.type}`,
|
|
);
|
|
}
|
|
|
|
const compositeFields: Record<string, boolean> = {};
|
|
|
|
for (const property of compositeType.properties) {
|
|
compositeFields[property.name] = true;
|
|
}
|
|
|
|
result[flatField.name] = compositeFields;
|
|
} else if (
|
|
(isFlatFieldMetadataOfType(flatField, FieldMetadataType.RELATION) ||
|
|
isFlatFieldMetadataOfType(
|
|
flatField,
|
|
FieldMetadataType.MORPH_RELATION,
|
|
)) &&
|
|
flatField.settings.relationType === RelationType.MANY_TO_ONE
|
|
) {
|
|
const joinColumnName = computeMorphOrRelationFieldJoinColumnName({
|
|
name: flatField.name,
|
|
});
|
|
|
|
result[joinColumnName] = true;
|
|
} else {
|
|
result[flatField.name] = true;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
};
|