refactor: stop reading joinColumnName from relation field settings (#20304)
## 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
This commit is contained in:
+1
-1
@@ -44,7 +44,7 @@ describe('shouldFieldBeQueried', () => {
|
||||
const res = shouldFieldBeQueried({
|
||||
gqlField: 'fieldNameId',
|
||||
fieldMetadata: {
|
||||
name: 'fieldNameId',
|
||||
name: 'fieldName',
|
||||
type: FieldMetadataType.RELATION,
|
||||
settings: {
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
|
||||
+10
-3
@@ -10,7 +10,11 @@ import {
|
||||
type ObjectPermissions,
|
||||
RelationType,
|
||||
} from 'twenty-shared/types';
|
||||
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
computeMorphRelationGqlFieldName,
|
||||
computeRelationGqlFieldJoinColumnName,
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
type MapFieldMetadataToGraphQLQueryArgs = {
|
||||
objectMetadataItems: EnrichedObjectMetadataItem[];
|
||||
@@ -52,7 +56,7 @@ export const mapFieldMetadataToGraphQLQuery = ({
|
||||
) {
|
||||
let gqlMorphField = '';
|
||||
for (const morphRelation of fieldMetadata.morphRelations ?? []) {
|
||||
const relationFieldName = computeMorphRelationFieldName({
|
||||
const relationFieldName = computeMorphRelationGqlFieldName({
|
||||
fieldName: fieldMetadata.name,
|
||||
relationType: fieldMetadata.settings?.relationType,
|
||||
targetObjectMetadataNameSingular:
|
||||
@@ -168,7 +172,10 @@ ${mapObjectMetadataToGraphQLQuery({
|
||||
}
|
||||
}
|
||||
|
||||
if (gqlField === fieldMetadata.settings?.joinColumnName) {
|
||||
if (
|
||||
gqlField ===
|
||||
computeRelationGqlFieldJoinColumnName({ name: fieldMetadata.name })
|
||||
) {
|
||||
return `${gqlField}`;
|
||||
}
|
||||
|
||||
|
||||
+26
-12
@@ -4,8 +4,17 @@ import { mapFieldMetadataToGraphQLQuery } from '@/object-metadata/utils/mapField
|
||||
import { shouldFieldBeQueried } from '@/object-metadata/utils/shouldFieldBeQueried';
|
||||
import { type RecordGqlFields } from '@/object-record/graphql/record-gql-fields/types/RecordGqlFields';
|
||||
import { isRecordGqlFieldsNode } from '@/object-record/graphql/utils/isRecordGraphlFieldsNode';
|
||||
import { FieldMetadataType, type ObjectPermissions } from 'twenty-shared/types';
|
||||
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
RelationType,
|
||||
type ObjectPermissions,
|
||||
} from 'twenty-shared/types';
|
||||
import {
|
||||
computeMorphRelationGqlFieldJoinColumnName,
|
||||
computeMorphRelationGqlFieldName,
|
||||
computeRelationGqlFieldJoinColumnName,
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
type MapObjectMetadataToGraphQLQueryArgs = {
|
||||
objectMetadataItems: EnrichedObjectMetadataItem[];
|
||||
@@ -46,12 +55,15 @@ export const mapObjectMetadataToGraphQLQuery = ({
|
||||
|
||||
const manyToOneRelationFields = objectMetadataItem?.readableFields
|
||||
.filter((field) => field.isActive)
|
||||
.filter(
|
||||
(field) =>
|
||||
field.type === FieldMetadataType.RELATION ||
|
||||
field.type === FieldMetadataType.MORPH_RELATION,
|
||||
)
|
||||
.filter((field) => isDefined(field.settings?.joinColumnName));
|
||||
.filter((field) => {
|
||||
if (field.type === FieldMetadataType.RELATION) {
|
||||
return field.relation?.type === RelationType.MANY_TO_ONE;
|
||||
}
|
||||
if (field.type === FieldMetadataType.MORPH_RELATION) {
|
||||
return field.settings?.relationType === RelationType.MANY_TO_ONE;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const manyToOneRelationGqlFieldWithFieldMetadata =
|
||||
manyToOneRelationFields.flatMap((fieldMetadata) => {
|
||||
@@ -59,7 +71,9 @@ export const mapObjectMetadataToGraphQLQuery = ({
|
||||
fieldMetadata.type === FieldMetadataType.MORPH_RELATION;
|
||||
if (!isMorphRelation) {
|
||||
return {
|
||||
gqlField: fieldMetadata.settings?.joinColumnName,
|
||||
gqlField: computeRelationGqlFieldJoinColumnName({
|
||||
name: fieldMetadata.name,
|
||||
}),
|
||||
fieldMetadata: fieldMetadata,
|
||||
};
|
||||
}
|
||||
@@ -69,14 +83,14 @@ export const mapObjectMetadataToGraphQLQuery = ({
|
||||
}
|
||||
|
||||
return fieldMetadata.morphRelations.map((morphRelation) => ({
|
||||
gqlField: `${computeMorphRelationFieldName({
|
||||
gqlField: computeMorphRelationGqlFieldJoinColumnName({
|
||||
fieldName: fieldMetadata.name,
|
||||
relationType: morphRelation.type,
|
||||
targetObjectMetadataNameSingular:
|
||||
morphRelation.targetObjectMetadata.nameSingular,
|
||||
targetObjectMetadataNamePlural:
|
||||
morphRelation.targetObjectMetadata.namePlural,
|
||||
})}Id`,
|
||||
}),
|
||||
fieldMetadata: fieldMetadata,
|
||||
}));
|
||||
});
|
||||
@@ -102,7 +116,7 @@ export const mapObjectMetadataToGraphQLQuery = ({
|
||||
}
|
||||
|
||||
return fieldMetadata.morphRelations.map((morphRelation) => ({
|
||||
gqlField: computeMorphRelationFieldName({
|
||||
gqlField: computeMorphRelationGqlFieldName({
|
||||
fieldName: fieldMetadata.name,
|
||||
relationType: morphRelation.type,
|
||||
targetObjectMetadataNameSingular:
|
||||
|
||||
@@ -4,7 +4,11 @@ import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
|
||||
import { isFieldMorphRelation } from '@/object-record/record-field/ui/types/guards/isFieldMorphRelation';
|
||||
import { isFieldRelation } from '@/object-record/record-field/ui/types/guards/isFieldRelation';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
computeMorphRelationGqlFieldJoinColumnName,
|
||||
computeRelationGqlFieldJoinColumnName,
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
|
||||
export const shouldFieldBeQueried = ({
|
||||
@@ -13,13 +17,35 @@ export const shouldFieldBeQueried = ({
|
||||
recordGqlFields,
|
||||
}: {
|
||||
gqlField: string;
|
||||
fieldMetadata: Pick<FieldMetadataItem, 'name' | 'type' | 'settings'>;
|
||||
fieldMetadata: Pick<
|
||||
FieldMetadataItem,
|
||||
'name' | 'type' | 'settings' | 'morphRelations'
|
||||
>;
|
||||
objectRecord?: ObjectRecord;
|
||||
recordGqlFields?: RecordGqlOperationGqlRecordFields;
|
||||
}): any => {
|
||||
const isRelationJoinColumn =
|
||||
isFieldRelation(fieldMetadata) &&
|
||||
computeRelationGqlFieldJoinColumnName({ name: fieldMetadata.name }) ===
|
||||
gqlField;
|
||||
|
||||
const isMorphRelationJoinColumn =
|
||||
isFieldMorphRelation(fieldMetadata) &&
|
||||
isDefined(fieldMetadata.morphRelations) &&
|
||||
fieldMetadata.morphRelations.some(
|
||||
(morphRelation) =>
|
||||
computeMorphRelationGqlFieldJoinColumnName({
|
||||
fieldName: fieldMetadata.name,
|
||||
relationType: morphRelation.type,
|
||||
targetObjectMetadataNameSingular:
|
||||
morphRelation.targetObjectMetadata.nameSingular,
|
||||
targetObjectMetadataNamePlural:
|
||||
morphRelation.targetObjectMetadata.namePlural,
|
||||
}) === gqlField,
|
||||
);
|
||||
|
||||
const isJoinColumn: boolean =
|
||||
(isFieldRelation(fieldMetadata) || isFieldMorphRelation(fieldMetadata)) &&
|
||||
fieldMetadata.settings.joinColumnName === gqlField;
|
||||
isRelationJoinColumn || isMorphRelationJoinColumn;
|
||||
|
||||
if (
|
||||
isUndefinedOrNull(recordGqlFields) &&
|
||||
|
||||
Reference in New Issue
Block a user