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:
+6
-3
@@ -10,7 +10,10 @@ import {
|
||||
FieldMetadataType,
|
||||
type Nullable,
|
||||
} from 'twenty-shared/types';
|
||||
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
computeMorphRelationGqlFieldName,
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
type GetActivityTargetObjectRecordsProps = {
|
||||
activityRecord: Note | Task;
|
||||
@@ -94,7 +97,7 @@ export const getActivityTargetObjectRecords = ({
|
||||
) {
|
||||
const matchingMorphRelation = field.morphRelations.find(
|
||||
(morphRelation) => {
|
||||
const morphFieldName = computeMorphRelationFieldName({
|
||||
const morphFieldName = computeMorphRelationGqlFieldName({
|
||||
fieldName: field.name,
|
||||
relationType: morphRelation.type,
|
||||
targetObjectMetadataNameSingular:
|
||||
@@ -108,7 +111,7 @@ export const getActivityTargetObjectRecords = ({
|
||||
);
|
||||
|
||||
if (isDefined(matchingMorphRelation)) {
|
||||
matchingFieldName = computeMorphRelationFieldName({
|
||||
matchingFieldName = computeMorphRelationGqlFieldName({
|
||||
fieldName: field.name,
|
||||
relationType: matchingMorphRelation.type,
|
||||
targetObjectMetadataNameSingular:
|
||||
|
||||
+3
-3
@@ -20,7 +20,7 @@ import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { type ApolloCache } from '@apollo/client';
|
||||
import { isArray } from '@sniptt/guards';
|
||||
import {
|
||||
computeMorphRelationFieldName,
|
||||
computeMorphRelationGqlFieldName,
|
||||
CustomError,
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
@@ -186,7 +186,7 @@ const triggerUpdateRelationOptimisticEffect = ({
|
||||
const gqlFieldNameOnTargetRecord =
|
||||
targetFieldMetadataFullObject.type === FieldMetadataType.RELATION
|
||||
? targetFieldMetadataFullObject.name
|
||||
: computeMorphRelationFieldName({
|
||||
: computeMorphRelationGqlFieldName({
|
||||
fieldName: targetFieldMetadataFullObject.name,
|
||||
relationType: targetFieldMetadataFullObject.settings?.relationType,
|
||||
targetObjectMetadataNameSingular:
|
||||
@@ -276,7 +276,7 @@ const triggerUpdateMorphRelationOptimisticEffect = ({
|
||||
}
|
||||
|
||||
morphRelations.forEach((morphRelation) => {
|
||||
const gqlFieldMorphRelation = computeMorphRelationFieldName({
|
||||
const gqlFieldMorphRelation = computeMorphRelationGqlFieldName({
|
||||
fieldName: fieldMetadataItemOnSourceRecord.name,
|
||||
relationType: morphRelation.type,
|
||||
targetObjectMetadataNameSingular:
|
||||
|
||||
+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) &&
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { type FieldMorphRelationMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
|
||||
import { computeMorphRelationFieldName } from 'twenty-shared/utils';
|
||||
import { computeMorphRelationGqlFieldName } from 'twenty-shared/utils';
|
||||
export const computePossibleMorphGqlFieldForFieldName = ({
|
||||
fieldMetadata,
|
||||
}: {
|
||||
@@ -9,7 +9,7 @@ export const computePossibleMorphGqlFieldForFieldName = ({
|
||||
>;
|
||||
}) =>
|
||||
fieldMetadata.morphRelations.map((morphRelation) => {
|
||||
return computeMorphRelationFieldName({
|
||||
return computeMorphRelationGqlFieldName({
|
||||
fieldName: fieldMetadata.fieldName,
|
||||
relationType: morphRelation.type,
|
||||
targetObjectMetadataNameSingular:
|
||||
|
||||
Vendored
+7
-2
@@ -1,6 +1,7 @@
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { computePossibleMorphGqlFieldForFieldName } from '@/object-record/cache/utils/computePossibleMorphGqlFieldForFieldName';
|
||||
import { isFieldMorphRelation } from '@/object-record/record-field/ui/types/guards/isFieldMorphRelation';
|
||||
import { computeRelationGqlFieldJoinColumnName } from 'twenty-shared/utils';
|
||||
|
||||
export const getFieldMetadataFromGqlField = ({
|
||||
objectMetadataItem,
|
||||
@@ -12,7 +13,9 @@ export const getFieldMetadataFromGqlField = ({
|
||||
return (
|
||||
objectMetadataItem.fields.find((field) => field.name === gqlField) ??
|
||||
objectMetadataItem.fields.find(
|
||||
(field) => field.settings?.joinColumnName === gqlField,
|
||||
(field) =>
|
||||
computeRelationGqlFieldJoinColumnName({ name: field.name }) ===
|
||||
gqlField,
|
||||
) ??
|
||||
objectMetadataItem.fields.filter(isFieldMorphRelation).find((field) => {
|
||||
const morphRelations = field.morphRelations;
|
||||
@@ -27,7 +30,9 @@ export const getFieldMetadataFromGqlField = ({
|
||||
return possibleMorphRelationsNames
|
||||
.flatMap((possibleMorphRelationName) => [
|
||||
possibleMorphRelationName,
|
||||
`${possibleMorphRelationName}Id`,
|
||||
computeRelationGqlFieldJoinColumnName({
|
||||
name: possibleMorphRelationName,
|
||||
}),
|
||||
])
|
||||
.includes(gqlField);
|
||||
})
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
|
||||
import { type FieldMorphRelationMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
|
||||
import { computeMorphRelationFieldName } from 'twenty-shared/utils';
|
||||
import { computeMorphRelationGqlFieldName } from 'twenty-shared/utils';
|
||||
|
||||
export const getMorphRelationFromFieldMetadataAndGqlField = ({
|
||||
objectMetadataItems,
|
||||
@@ -24,7 +24,7 @@ export const getMorphRelationFromFieldMetadataAndGqlField = ({
|
||||
morphRelation: undefined,
|
||||
};
|
||||
}
|
||||
const computedName = computeMorphRelationFieldName({
|
||||
const computedName = computeMorphRelationGqlFieldName({
|
||||
fieldName: morphRelation.sourceFieldMetadata.name,
|
||||
relationType: morphRelation.type,
|
||||
targetObjectMetadataNameSingular: targetObjectMetadata.nameSingular,
|
||||
|
||||
+9
-2
@@ -9,7 +9,12 @@ import { getRecordConnectionFromRecords } from '@/object-record/cache/utils/getR
|
||||
import { getRefName } from '@/object-record/cache/utils/getRefName';
|
||||
import { type RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { getNodeTypename, isDefined, pascalCase } from 'twenty-shared/utils';
|
||||
import {
|
||||
computeRelationGqlFieldJoinColumnName,
|
||||
getNodeTypename,
|
||||
isDefined,
|
||||
pascalCase,
|
||||
} from 'twenty-shared/utils';
|
||||
import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const getRecordNodeFromRecord = <T extends ObjectRecord>({
|
||||
@@ -132,7 +137,9 @@ export const getRecordNodeFromRecord = <T extends ObjectRecord>({
|
||||
|
||||
switch (field.type) {
|
||||
case FieldMetadataType.RELATION: {
|
||||
const isJoinColumn = field.settings?.joinColumnName === gqlField;
|
||||
const isJoinColumn =
|
||||
computeRelationGqlFieldJoinColumnName({ name: field.name }) ===
|
||||
gqlField;
|
||||
if (isJoinColumn) {
|
||||
return [gqlField, value];
|
||||
}
|
||||
|
||||
+5
-2
@@ -11,7 +11,10 @@ import { buildIdentifierGqlFields } from '@/object-record/graphql/record-gql-fie
|
||||
import { generateActivityTargetGqlFields } from '@/object-record/graphql/record-gql-fields/utils/generateActivityTargetGqlFields';
|
||||
import { generateJunctionRelationGqlFields } from '@/object-record/graphql/record-gql-fields/utils/generateJunctionRelationGqlFields';
|
||||
import { isJunctionRelationField } from '@/object-record/record-field/ui/utils/junction/isJunctionRelationField';
|
||||
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
computeMorphRelationGqlFieldName,
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
export type GenerateDepthRecordGqlFieldsFromFields = {
|
||||
objectMetadataItems: Pick<
|
||||
@@ -138,7 +141,7 @@ export const generateDepthRecordGqlFieldsFromFields = ({
|
||||
}
|
||||
|
||||
return {
|
||||
gqlField: computeMorphRelationFieldName({
|
||||
gqlField: computeMorphRelationGqlFieldName({
|
||||
fieldName: fieldMetadata.name,
|
||||
relationType: morphRelation.type,
|
||||
targetObjectMetadataNameSingular:
|
||||
|
||||
+5
-2
@@ -6,7 +6,10 @@ import {
|
||||
type JunctionObjectMetadataItem,
|
||||
} from '@/object-record/record-field/ui/utils/junction/getJunctionConfig';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
computeMorphRelationGqlFieldName,
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
type JunctionFieldMetadataItem = Pick<
|
||||
FieldMetadataItem,
|
||||
@@ -56,7 +59,7 @@ const buildMorphTargetFieldGqlFields = (
|
||||
continue;
|
||||
}
|
||||
|
||||
const computedFieldName = computeMorphRelationFieldName({
|
||||
const computedFieldName = computeMorphRelationGqlFieldName({
|
||||
fieldName: morphRelation.sourceFieldMetadata.name,
|
||||
relationType: morphRelation.type,
|
||||
targetObjectMetadataNameSingular: targetObjectMetadata.nameSingular,
|
||||
|
||||
+2
-2
@@ -33,7 +33,7 @@ import { t } from '@lingui/core/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { createPortal } from 'react-dom';
|
||||
import {
|
||||
computeMorphRelationFieldName,
|
||||
computeMorphRelationGqlFieldName,
|
||||
CustomError,
|
||||
} from 'twenty-shared/utils';
|
||||
import {
|
||||
@@ -153,7 +153,7 @@ export const RecordDetailRelationRecordsListItem = ({
|
||||
relationFieldMetadataItem?.type === FieldMetadataType.MORPH_RELATION;
|
||||
|
||||
const computedName = relationFieldMetadataItem
|
||||
? computeMorphRelationFieldName({
|
||||
? computeMorphRelationGqlFieldName({
|
||||
fieldName: relationFieldMetadataItem.name,
|
||||
relationType: relationFieldMetadataItem.settings.relationType,
|
||||
targetObjectMetadataNameSingular: objectMetadataItem.nameSingular,
|
||||
|
||||
+2
-2
@@ -31,7 +31,7 @@ import {
|
||||
type RecordGqlOperationFilter,
|
||||
} from 'twenty-shared/types';
|
||||
import {
|
||||
computeMorphRelationFieldName,
|
||||
computeMorphRelationGqlFieldName,
|
||||
CustomError,
|
||||
getAppPath,
|
||||
isDefined,
|
||||
@@ -138,7 +138,7 @@ export const RecordDetailRelationSection = ({
|
||||
relationFieldMetadataItem?.type === FieldMetadataType.MORPH_RELATION;
|
||||
|
||||
const computedName = isDefined(relationFieldMetadataItem)
|
||||
? computeMorphRelationFieldName({
|
||||
? computeMorphRelationGqlFieldName({
|
||||
fieldName: relationFieldMetadataItem.name,
|
||||
relationType: relationFieldMetadataItem.settings.relationType,
|
||||
targetObjectMetadataNameSingular: objectMetadataItem.nameSingular,
|
||||
|
||||
+9
-4
@@ -14,7 +14,6 @@ import { useUpdateRelationOneToManyFieldInput } from '@/object-record/record-fie
|
||||
import { type FieldDefinition } from '@/object-record/record-field/ui/types/FieldDefinition';
|
||||
import { type FieldRelationMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
|
||||
import { extractTargetRecordsFromJunction } from '@/object-record/record-field/ui/utils/junction/extractTargetRecordsFromJunction';
|
||||
import { getJoinColumnName } from '@/object-record/record-field/ui/utils/junction/getJoinColumnName';
|
||||
import { getJunctionConfig } from '@/object-record/record-field/ui/utils/junction/getJunctionConfig';
|
||||
import { getSourceJoinColumnName } from '@/object-record/record-field/ui/utils/junction/getSourceJoinColumnName';
|
||||
import { hasJunctionConfig } from '@/object-record/record-field/ui/utils/junction/hasJunctionConfig';
|
||||
@@ -36,7 +35,11 @@ import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/h
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue';
|
||||
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
|
||||
import { CustomError, isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
computeRelationGqlFieldJoinColumnName,
|
||||
CustomError,
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
import { IconPlus } from 'twenty-ui/display';
|
||||
import { LightIconButton } from 'twenty-ui/input';
|
||||
|
||||
@@ -288,9 +291,11 @@ export const RecordDetailRelationSectionDropdownToMany = ({
|
||||
sourceObjectMetadata: objectMetadataItem,
|
||||
});
|
||||
|
||||
const targetJoinColumnName = getJoinColumnName(targetField.settings);
|
||||
const targetJoinColumnName = computeRelationGqlFieldJoinColumnName({
|
||||
name: targetField.name,
|
||||
});
|
||||
|
||||
if (!sourceJoinColumnName || !targetJoinColumnName) {
|
||||
if (!sourceJoinColumnName) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+7
-5
@@ -11,9 +11,11 @@ import { generateDefaultRecordChipData } from '@/object-metadata/utils/generateD
|
||||
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata';
|
||||
import { isFieldRelation } from '@/object-record/record-field/ui/types/guards/isFieldRelation';
|
||||
import { getJoinColumnNameOrThrow } from '@/object-record/record-field/ui/utils/junction/getJoinColumnNameOrThrow';
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
computeRelationGqlFieldJoinColumnName,
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
export const useRelationToOneFieldDisplay = () => {
|
||||
const { recordId, fieldDefinition, maxWidth } = useContext(FieldContext);
|
||||
@@ -42,9 +44,9 @@ export const useRelationToOneFieldDisplay = () => {
|
||||
fieldDefinition,
|
||||
);
|
||||
|
||||
const joinColumnName = getJoinColumnNameOrThrow(
|
||||
fieldDefinition.metadata.settings,
|
||||
);
|
||||
const joinColumnName = computeRelationGqlFieldJoinColumnName({
|
||||
name: fieldName,
|
||||
});
|
||||
|
||||
const foreignKeyFieldValue = useRecordFieldValue<string | null | undefined>(
|
||||
recordId,
|
||||
|
||||
+9
-4
@@ -22,7 +22,6 @@ import { RecordFieldComponentInstanceContext } from '@/object-record/record-fiel
|
||||
import { recordFieldInputLayoutDirectionComponentState } from '@/object-record/record-field/ui/states/recordFieldInputLayoutDirectionComponentState';
|
||||
import { type FieldDefinition } from '@/object-record/record-field/ui/types/FieldDefinition';
|
||||
import { type FieldRelationMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
|
||||
import { getJoinColumnName } from '@/object-record/record-field/ui/utils/junction/getJoinColumnName';
|
||||
import { getJunctionConfig } from '@/object-record/record-field/ui/utils/junction/getJunctionConfig';
|
||||
import { getSourceJoinColumnName } from '@/object-record/record-field/ui/utils/junction/getSourceJoinColumnName';
|
||||
import { hasJunctionConfig } from '@/object-record/record-field/ui/utils/junction/hasJunctionConfig';
|
||||
@@ -35,7 +34,11 @@ import { buildRecordLabelPayload } from '@/object-record/utils/buildRecordLabelP
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { CustomError, isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
computeRelationGqlFieldJoinColumnName,
|
||||
CustomError,
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
export const RelationOneToManyFieldInput = () => {
|
||||
const store = useStore();
|
||||
@@ -218,9 +221,11 @@ export const RelationOneToManyFieldInput = () => {
|
||||
sourceObjectMetadata: objectMetadataItem,
|
||||
});
|
||||
|
||||
const targetJoinColumnName = getJoinColumnName(targetField.settings);
|
||||
const targetJoinColumnName = computeRelationGqlFieldJoinColumnName({
|
||||
name: targetField.name,
|
||||
});
|
||||
|
||||
if (!sourceJoinColumnName || !targetJoinColumnName) {
|
||||
if (!sourceJoinColumnName) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+5
-2
@@ -13,7 +13,10 @@ import { viewableRecordNameSingularState } from '@/object-record/record-side-pan
|
||||
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
|
||||
import { buildRecordLabelPayload } from '@/object-record/utils/buildRecordLabelPayload';
|
||||
import { getOperationName } from '~/utils/getOperationName';
|
||||
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
computeMorphRelationGqlFieldName,
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql';
|
||||
|
||||
type useAddNewRecordAndOpenSidePanelProps = {
|
||||
@@ -73,7 +76,7 @@ export const useAddNewRecordAndOpenSidePanel = ({
|
||||
const gqlField =
|
||||
relationFieldMetadataItem.type === FieldMetadataType.RELATION
|
||||
? relationFieldMetadataItem.name
|
||||
: computeMorphRelationFieldName({
|
||||
: computeMorphRelationGqlFieldName({
|
||||
fieldName: relationFieldMetadataItem.name,
|
||||
relationType: relationFieldMetadataItemRelationType,
|
||||
targetObjectMetadataNameSingular:
|
||||
|
||||
+5
-2
@@ -12,7 +12,10 @@ import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePush
|
||||
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
|
||||
import { useStore } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
computeMorphRelationGqlFieldName,
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
export const useOpenMorphRelationManyToOneFieldInput = () => {
|
||||
const store = useStore();
|
||||
@@ -31,7 +34,7 @@ export const useOpenMorphRelationManyToOneFieldInput = () => {
|
||||
}) => {
|
||||
const potentielFieldNames = fieldDefinition.metadata.morphRelations.map(
|
||||
(morphRelation) => {
|
||||
return computeMorphRelationFieldName({
|
||||
return computeMorphRelationGqlFieldName({
|
||||
fieldName: fieldDefinition.metadata.fieldName,
|
||||
relationType: fieldDefinition.metadata.relationType,
|
||||
targetObjectMetadataNameSingular:
|
||||
|
||||
+5
-2
@@ -7,7 +7,10 @@ import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldCont
|
||||
import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata';
|
||||
import { isFieldRelation } from '@/object-record/record-field/ui/types/guards/isFieldRelation';
|
||||
import { type RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem';
|
||||
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
computeMorphRelationGqlFieldName,
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const useUpdateRelationOneToManyFieldInput = () => {
|
||||
@@ -72,7 +75,7 @@ export const useUpdateRelationOneToManyFieldInput = () => {
|
||||
|
||||
let targetGQLFieldName: string;
|
||||
if (targetFieldMetadata.type === FieldMetadataType.MORPH_RELATION) {
|
||||
targetGQLFieldName = computeMorphRelationFieldName({
|
||||
targetGQLFieldName = computeMorphRelationGqlFieldName({
|
||||
fieldName: fieldDefinition.metadata.targetFieldMetadataName,
|
||||
relationType: targetFieldMetadata.settings?.relationType,
|
||||
targetObjectMetadataNameSingular:
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@ import { type FieldMetadataItemRelation } from '@/object-metadata/types/FieldMet
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { buildRecordWithAllMorphObjectIdsToNull } from '@/object-record/record-field/ui/meta-types/input/utils/buildRecordWithAllMorphObjectIdsToNull';
|
||||
import { type RelationType } from 'twenty-shared/types';
|
||||
import { computeMorphRelationFieldName } from 'twenty-shared/utils';
|
||||
import { computeMorphRelationGqlFieldName } from 'twenty-shared/utils';
|
||||
|
||||
export const buildMorphRelationUpdateInput = ({
|
||||
morphRelations,
|
||||
@@ -45,7 +45,7 @@ export const buildMorphRelationUpdateInput = ({
|
||||
);
|
||||
}
|
||||
|
||||
const computedFieldName = computeMorphRelationFieldName({
|
||||
const computedFieldName = computeMorphRelationGqlFieldName({
|
||||
fieldName,
|
||||
relationType,
|
||||
targetObjectMetadataNameSingular: targetObjectMetadataItem.nameSingular,
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import { type FieldMetadataItemRelation } from '@/object-metadata/types/FieldMetadataItemRelation';
|
||||
import { type RelationType } from 'twenty-shared/types';
|
||||
import { computeMorphRelationFieldName } from 'twenty-shared/utils';
|
||||
import { computeMorphRelationGqlFieldName } from 'twenty-shared/utils';
|
||||
|
||||
export const buildRecordWithAllMorphObjectIdsToNull = ({
|
||||
morphRelations,
|
||||
@@ -13,7 +13,7 @@ export const buildRecordWithAllMorphObjectIdsToNull = ({
|
||||
}): Record<string, null> => {
|
||||
return morphRelations.reduce(
|
||||
(acc, morphRelation) => {
|
||||
const computedFieldName = computeMorphRelationFieldName({
|
||||
const computedFieldName = computeMorphRelationGqlFieldName({
|
||||
fieldName,
|
||||
relationType,
|
||||
targetObjectMetadataNameSingular:
|
||||
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
import { getJoinColumnName } from '@/object-record/record-field/ui/utils/junction/getJoinColumnName';
|
||||
|
||||
describe('getJoinColumnName', () => {
|
||||
it('should return undefined for undefined settings', () => {
|
||||
expect(getJoinColumnName(undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for null settings', () => {
|
||||
expect(getJoinColumnName(null as unknown as undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for non-object settings', () => {
|
||||
expect(getJoinColumnName('string' as unknown as undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for settings without joinColumnName', () => {
|
||||
expect(getJoinColumnName({})).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for settings with non-string joinColumnName', () => {
|
||||
expect(getJoinColumnName({ joinColumnName: 123 })).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return the joinColumnName for valid settings', () => {
|
||||
expect(getJoinColumnName({ joinColumnName: 'personId' })).toBe('personId');
|
||||
expect(getJoinColumnName({ joinColumnName: 'companyId' })).toBe(
|
||||
'companyId',
|
||||
);
|
||||
});
|
||||
});
|
||||
+5
-2
@@ -4,7 +4,10 @@ import { isObjectWithId } from '@/object-record/record-field/ui/utils/junction/i
|
||||
import { type ExtractedTargetRecord } from '@/object-record/record-field/ui/utils/junction/types/ExtractedTargetRecord';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
computeMorphRelationGqlFieldName,
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
type ExtractTargetRecordsFromJunctionArgs = {
|
||||
junctionRecords: ObjectRecord[] | undefined | null;
|
||||
@@ -85,7 +88,7 @@ const extractFromMorphRelationField = (
|
||||
}
|
||||
|
||||
for (const morphRelation of morphRelations) {
|
||||
const computedFieldName = computeMorphRelationFieldName({
|
||||
const computedFieldName = computeMorphRelationGqlFieldName({
|
||||
fieldName: morphRelation.sourceFieldMetadata.name,
|
||||
relationType: morphRelation.type,
|
||||
targetObjectMetadataNameSingular:
|
||||
|
||||
+10
-5
@@ -1,7 +1,10 @@
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { getJoinColumnName } from '@/object-record/record-field/ui/utils/junction/getJoinColumnName';
|
||||
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
computeMorphRelationGqlFieldName,
|
||||
computeRelationGqlFieldJoinColumnName,
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
export type TargetFieldInfo = {
|
||||
fieldName: string;
|
||||
@@ -34,7 +37,7 @@ const findMorphTargetFieldInfo = (
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const fieldName = computeMorphRelationFieldName({
|
||||
const fieldName = computeMorphRelationGqlFieldName({
|
||||
fieldName: matchingMorphRelation.sourceFieldMetadata.name,
|
||||
relationType: matchingMorphRelation.type,
|
||||
targetObjectMetadataNameSingular: targetObjectMetadata.nameSingular,
|
||||
@@ -45,7 +48,7 @@ const findMorphTargetFieldInfo = (
|
||||
// e.g., caretakerPerson → caretakerPersonId
|
||||
return {
|
||||
fieldName,
|
||||
joinColumnName: `${fieldName}Id`,
|
||||
joinColumnName: computeRelationGqlFieldJoinColumnName({ name: fieldName }),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -68,7 +71,9 @@ export const findTargetFieldInfo = (
|
||||
if (field.relation?.targetObjectMetadata.id === targetObjectMetadataId) {
|
||||
return {
|
||||
fieldName: field.name,
|
||||
joinColumnName: getJoinColumnName(field.settings),
|
||||
joinColumnName: computeRelationGqlFieldJoinColumnName({
|
||||
name: field.name,
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const getJoinColumnName = (
|
||||
settings: FieldMetadataItem['settings'],
|
||||
): string | undefined => {
|
||||
if (
|
||||
isDefined(settings) &&
|
||||
typeof settings === 'object' &&
|
||||
'joinColumnName' in settings &&
|
||||
typeof settings.joinColumnName === 'string'
|
||||
) {
|
||||
return settings.joinColumnName;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { getJoinColumnName } from '@/object-record/record-field/ui/utils/junction/getJoinColumnName';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const getJoinColumnNameOrThrow = (
|
||||
settings: FieldMetadataItem['settings'],
|
||||
): string => {
|
||||
const joinColumnName = getJoinColumnName(settings);
|
||||
if (!isDefined(joinColumnName)) {
|
||||
throw new Error('Join column name is not defined');
|
||||
}
|
||||
return joinColumnName;
|
||||
};
|
||||
+8
-7
@@ -1,11 +1,14 @@
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { getJoinColumnName } from '@/object-record/record-field/ui/utils/junction/getJoinColumnName';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
computeMorphRelationGqlFieldJoinColumnName,
|
||||
computeRelationGqlFieldJoinColumnName,
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
type GetSourceJoinColumnNameArgs = {
|
||||
sourceField: Pick<FieldMetadataItem, 'type' | 'morphRelations' | 'settings'>;
|
||||
sourceField: Pick<FieldMetadataItem, 'name' | 'type' | 'morphRelations'>;
|
||||
sourceObjectMetadata: Pick<
|
||||
EnrichedObjectMetadataItem,
|
||||
'id' | 'nameSingular' | 'namePlural'
|
||||
@@ -25,15 +28,13 @@ export const getSourceJoinColumnName = ({
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const computedFieldName = computeMorphRelationFieldName({
|
||||
return computeMorphRelationGqlFieldJoinColumnName({
|
||||
fieldName: morphRelation.sourceFieldMetadata.name,
|
||||
relationType: morphRelation.type,
|
||||
targetObjectMetadataNameSingular: sourceObjectMetadata.nameSingular,
|
||||
targetObjectMetadataNamePlural: sourceObjectMetadata.namePlural,
|
||||
});
|
||||
|
||||
return `${computedFieldName}Id`;
|
||||
}
|
||||
|
||||
return getJoinColumnName(sourceField.settings);
|
||||
return computeRelationGqlFieldJoinColumnName({ name: sourceField.name });
|
||||
};
|
||||
|
||||
+6
-2
@@ -29,6 +29,7 @@ import {
|
||||
type UUIDFilter,
|
||||
} from 'twenty-shared/types';
|
||||
import {
|
||||
computeRelationGqlFieldJoinColumnName,
|
||||
isDefined,
|
||||
isEmptyObject,
|
||||
isMatchingArrayFilter,
|
||||
@@ -203,7 +204,8 @@ export const isRecordMatchingFilter = ({
|
||||
(field) =>
|
||||
(field.type === FieldMetadataType.RELATION ||
|
||||
field.type === FieldMetadataType.MORPH_RELATION) &&
|
||||
field.settings?.joinColumnName === filterKey,
|
||||
computeRelationGqlFieldJoinColumnName({ name: field.name }) ===
|
||||
filterKey,
|
||||
) ??
|
||||
objectMetadataItem.fields.find(
|
||||
(field) =>
|
||||
@@ -415,7 +417,9 @@ export const isRecordMatchingFilter = ({
|
||||
case FieldMetadataType.RELATION:
|
||||
case FieldMetadataType.MORPH_RELATION: {
|
||||
const isJoinColumn =
|
||||
objectMetadataField.settings?.joinColumnName === filterKey ||
|
||||
computeRelationGqlFieldJoinColumnName({
|
||||
name: objectMetadataField.name,
|
||||
}) === filterKey ||
|
||||
(objectMetadataField.type === FieldMetadataType.MORPH_RELATION &&
|
||||
isMorphRelationJoinColumnKey({
|
||||
fieldMetadataItem: objectMetadataField,
|
||||
|
||||
+5
-2
@@ -12,7 +12,10 @@ import { isFieldMorphRelation } from '@/object-record/record-field/ui/types/guar
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector';
|
||||
import { RelationType } from 'twenty-shared/types';
|
||||
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
computeMorphRelationGqlFieldName,
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
const simpleFieldValueSelector = createAtomFamilySelector<
|
||||
unknown,
|
||||
@@ -49,7 +52,7 @@ const getMorphRelationFieldValueAtom = (
|
||||
const recordStore = get(recordStoreFamilyState.atomFamily(recordId));
|
||||
|
||||
const computeMorphFieldName = (morphRelation: FieldMetadataItemRelation) =>
|
||||
computeMorphRelationFieldName({
|
||||
computeMorphRelationGqlFieldName({
|
||||
fieldName: morphRelation.sourceFieldMetadata.name,
|
||||
relationType: morphRelation.type,
|
||||
targetObjectMetadataNameSingular:
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@ import { recordStoreFamilyState } from '@/object-record/record-store/states/reco
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector';
|
||||
import { type RelationType } from 'twenty-shared/types';
|
||||
import { computeMorphRelationFieldName } from 'twenty-shared/utils';
|
||||
import { computeMorphRelationGqlFieldName } from 'twenty-shared/utils';
|
||||
|
||||
type MorphOneToManyFamilyKey = {
|
||||
recordId: string;
|
||||
@@ -24,7 +24,7 @@ export const recordStoreMorphOneToManyValueWithObjectNameFamilySelector =
|
||||
({ get }) => {
|
||||
const morphValuesWithObjectName = morphRelations.map(
|
||||
(morphRelation) => {
|
||||
const fieldName = computeMorphRelationFieldName({
|
||||
const fieldName = computeMorphRelationGqlFieldName({
|
||||
fieldName: morphRelation.sourceFieldMetadata.name,
|
||||
relationType: morphRelation.type as RelationType,
|
||||
targetObjectMetadataNameSingular:
|
||||
|
||||
+4
-1
@@ -7,6 +7,7 @@ import { isUpdateRecordValueEmpty } from '@/object-record/record-update-multiple
|
||||
import { shouldDisplayFormMultiEditField } from '@/object-record/record-update-multiple/utils/shouldDisplayFormMultiEditField';
|
||||
import { styled } from '@linaria/react';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { computeRelationGqlFieldJoinColumnName } from 'twenty-shared/utils';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
@@ -59,7 +60,9 @@ export const UpdateMultipleRecordsForm = ({
|
||||
const isRelation = isFieldRelation(fieldDefinition);
|
||||
const fieldNameOrRelationIdName =
|
||||
isRelation && fieldMetadataItem.type === FieldMetadataType.RELATION
|
||||
? (fieldMetadataItem.settings?.joinColumnName as string)
|
||||
? computeRelationGqlFieldJoinColumnName({
|
||||
name: fieldMetadataItem.name,
|
||||
})
|
||||
: fieldName;
|
||||
|
||||
const value = values[fieldNameOrRelationIdName];
|
||||
|
||||
+15
-5
@@ -17,7 +17,11 @@ import { isFieldUuid } from '@/object-record/record-field/ui/types/guards/isFiel
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { buildOptimisticActorFieldValueFromCurrentWorkspaceMember } from '@/object-record/utils/buildOptimisticActorFieldValueFromCurrentWorkspaceMember';
|
||||
import { getForeignKeyNameFromRelationFieldName } from '@/object-record/utils/getForeignKeyNameFromRelationFieldName';
|
||||
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
computeMorphRelationGqlFieldName,
|
||||
computeRelationGqlFieldJoinColumnName,
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql';
|
||||
|
||||
type ComputeOptimisticCacheRecordInputArgs = {
|
||||
@@ -46,7 +50,8 @@ export const computeOptimisticRecordFromInput = ({
|
||||
objectMetadataItem.fields.find(
|
||||
(field) =>
|
||||
field.type === FieldMetadataType.RELATION &&
|
||||
field.settings?.joinColumnName === recordKey,
|
||||
computeRelationGqlFieldJoinColumnName({ name: field.name }) ===
|
||||
recordKey,
|
||||
);
|
||||
|
||||
const potentialMorphRelationJoinColumnNameFieldMetadataItem =
|
||||
@@ -200,7 +205,7 @@ export const computeOptimisticRecordFromInput = ({
|
||||
|
||||
const relationGqlFields = fieldMetadataItem.morphRelations?.map(
|
||||
(morphRelation) => {
|
||||
return computeMorphRelationFieldName({
|
||||
return computeMorphRelationGqlFieldName({
|
||||
fieldName: fieldMetadataItem.name,
|
||||
relationType,
|
||||
targetObjectMetadataNameSingular:
|
||||
@@ -212,10 +217,15 @@ export const computeOptimisticRecordFromInput = ({
|
||||
);
|
||||
|
||||
const relationGqlField = relationGqlFields?.find(
|
||||
(relationGqlField) => recordInput[`${relationGqlField}Id`],
|
||||
(relationGqlField) =>
|
||||
recordInput[
|
||||
computeRelationGqlFieldJoinColumnName({ name: relationGqlField })
|
||||
],
|
||||
);
|
||||
|
||||
const relationGqlFieldWithId = `${relationGqlField}Id`;
|
||||
const relationGqlFieldWithId = computeRelationGqlFieldJoinColumnName({
|
||||
name: relationGqlField ?? '',
|
||||
});
|
||||
|
||||
if (isUndefined(relationGqlField)) {
|
||||
continue;
|
||||
|
||||
@@ -4,7 +4,11 @@ import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataIte
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { generateEmptyFieldValue } from '@/object-record/utils/generateEmptyFieldValue';
|
||||
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
computeMorphRelationGqlFieldName,
|
||||
computeRelationGqlFieldJoinColumnName,
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql';
|
||||
|
||||
type PrefillRecordArgs = {
|
||||
@@ -26,13 +30,15 @@ export const prefillRecord = <T extends ObjectRecord>({
|
||||
fieldMetadataItem.type === FieldMetadataType.RELATION &&
|
||||
fieldMetadataItem.relation?.type === RelationType.MANY_TO_ONE
|
||||
) {
|
||||
const joinColumnValue =
|
||||
input[fieldMetadataItem.settings?.joinColumnName] ?? null;
|
||||
const joinColumnName = computeRelationGqlFieldJoinColumnName({
|
||||
name: fieldMetadataItem.name,
|
||||
});
|
||||
const joinColumnValue = input[joinColumnName] ?? null;
|
||||
throwIfInputRelationDataIsInconsistent(input, fieldMetadataItem);
|
||||
|
||||
return [
|
||||
[fieldMetadataItem.name, fieldValue],
|
||||
[fieldMetadataItem.settings?.joinColumnName, joinColumnValue],
|
||||
[joinColumnName, joinColumnValue],
|
||||
];
|
||||
}
|
||||
if (
|
||||
@@ -41,7 +47,7 @@ export const prefillRecord = <T extends ObjectRecord>({
|
||||
) {
|
||||
const gqlFields = fieldMetadataItem.morphRelations?.map(
|
||||
(morphRelation) => {
|
||||
return computeMorphRelationFieldName({
|
||||
return computeMorphRelationGqlFieldName({
|
||||
fieldName: fieldMetadataItem.name,
|
||||
relationType: morphRelation.type,
|
||||
targetObjectMetadataNameSingular:
|
||||
@@ -52,10 +58,15 @@ export const prefillRecord = <T extends ObjectRecord>({
|
||||
},
|
||||
);
|
||||
|
||||
return gqlFields?.flatMap((gqlField) => [
|
||||
[gqlField, fieldValue],
|
||||
[`${gqlField}Id`, input[`${gqlField}Id`] ?? null],
|
||||
]);
|
||||
return gqlFields?.flatMap((gqlField) => {
|
||||
const joinColumnName = computeRelationGqlFieldJoinColumnName({
|
||||
name: gqlField,
|
||||
});
|
||||
return [
|
||||
[gqlField, fieldValue],
|
||||
[joinColumnName, input[joinColumnName] ?? null],
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
return [[fieldMetadataItem.name, fieldValue]];
|
||||
@@ -70,7 +81,9 @@ const throwIfInputRelationDataIsInconsistent = (
|
||||
fieldMetadataItem: FieldMetadataItem,
|
||||
) => {
|
||||
const inputValue = input[fieldMetadataItem.name];
|
||||
const relationIdFieldName = `${fieldMetadataItem.name}Id`;
|
||||
const relationIdFieldName = computeRelationGqlFieldJoinColumnName({
|
||||
name: fieldMetadataItem.name,
|
||||
});
|
||||
if (isDefined(inputValue) && !isDefined(input[relationIdFieldName])) {
|
||||
throw new Error(
|
||||
`Inconsistent input: ${fieldMetadataItem.name} is specified but ${relationIdFieldName} is missing`,
|
||||
|
||||
@@ -2,7 +2,11 @@ import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/Enriche
|
||||
import { isFieldMorphRelation } from '@/object-record/record-field/ui/types/guards/isFieldMorphRelation';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { isSystemSearchVectorField } from '@/object-record/utils/isSystemSearchVectorField';
|
||||
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
computeMorphRelationGqlFieldName,
|
||||
computeRelationGqlFieldJoinColumnName,
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const sanitizeRecordInput = ({
|
||||
@@ -26,13 +30,14 @@ export const sanitizeRecordInput = ({
|
||||
objectMetadataItem.fields.find(
|
||||
(field) =>
|
||||
field.type === FieldMetadataType.RELATION &&
|
||||
field.settings?.joinColumnName === fieldName,
|
||||
computeRelationGqlFieldJoinColumnName({ name: field.name }) ===
|
||||
fieldName,
|
||||
);
|
||||
const potentialMorphRelationJoinColumnNameFieldMetadataItem =
|
||||
objectMetadataItem.fields.find((field) => {
|
||||
if (!isFieldMorphRelation(field)) return false;
|
||||
return field.morphRelations?.some((morphRelation) => {
|
||||
const computedFieldName = computeMorphRelationFieldName({
|
||||
const computedFieldName = computeMorphRelationGqlFieldName({
|
||||
fieldName: field.name,
|
||||
relationType: morphRelation.type,
|
||||
targetObjectMetadataNameSingular:
|
||||
|
||||
+4
-10
@@ -14,6 +14,7 @@ import {
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { transformActorField } from 'src/engine/api/common/common-args-processors/data-arg-processor/transformer-utils/transform-actor-field.util';
|
||||
import { transformAddressField } from 'src/engine/api/common/common-args-processors/data-arg-processor/transformer-utils/transform-address-field.util';
|
||||
import { transformArrayField } from 'src/engine/api/common/common-args-processors/data-arg-processor/transformer-utils/transform-array-field.util';
|
||||
@@ -56,12 +57,10 @@ import { transformLinksValue } from 'src/engine/core-modules/record-transformer/
|
||||
import { transformPhonesValue } from 'src/engine/core-modules/record-transformer/utils/transform-phones-value.util';
|
||||
import { transformRichTextValue } from 'src/engine/core-modules/record-transformer/utils/transform-rich-text.util';
|
||||
import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception';
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { 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 { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { buildFieldMapsFromFlatObjectMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/build-field-maps-from-flat-object-metadata.util';
|
||||
import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
|
||||
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
|
||||
@Injectable()
|
||||
@@ -245,14 +244,9 @@ export class DataArgProcessorService {
|
||||
);
|
||||
}
|
||||
|
||||
const joinColumnName = isFlatFieldMetadataOfType(
|
||||
fieldMetadata,
|
||||
FieldMetadataType.MORPH_RELATION,
|
||||
)
|
||||
? computeMorphOrRelationFieldJoinColumnName({
|
||||
name: fieldMetadata.name,
|
||||
})
|
||||
: relationSettings.joinColumnName;
|
||||
const joinColumnName = computeMorphOrRelationFieldJoinColumnName({
|
||||
name: fieldMetadata.name,
|
||||
});
|
||||
|
||||
if (key === joinColumnName) {
|
||||
return validateUUIDFieldOrThrow(value, key);
|
||||
|
||||
+1
-1
@@ -8,6 +8,7 @@ import {
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { type ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
|
||||
import { validateAndTransformOperatorAndValue } from 'src/engine/api/common/common-args-processors/filter-arg-processor/utils/validate-and-transform-operator-and-value.util';
|
||||
@@ -17,7 +18,6 @@ import {
|
||||
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
|
||||
import { type CompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/types/composite-field-metadata-type.type';
|
||||
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 { 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';
|
||||
|
||||
+25
-9
@@ -4,6 +4,7 @@ import { FieldMetadataType, type ObjectRecord } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type FindOptionsRelations, type ObjectLiteral } from 'typeorm';
|
||||
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
|
||||
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
|
||||
@@ -191,8 +192,9 @@ export class ProcessNestedRelationsV2Helper {
|
||||
select: columnsToSelect,
|
||||
});
|
||||
|
||||
const joinColumnName =
|
||||
sourceFieldMetadata.settings.joinColumnName ?? `${sourceFieldName}Id`;
|
||||
const joinColumnName = computeMorphOrRelationFieldJoinColumnName({
|
||||
name: sourceFieldName,
|
||||
});
|
||||
|
||||
const relationIds = this.getUniqueIds({
|
||||
records: parentObjectRecords,
|
||||
@@ -200,14 +202,28 @@ export class ProcessNestedRelationsV2Helper {
|
||||
relationType === RelationType.ONE_TO_MANY ? 'id' : joinColumnName,
|
||||
});
|
||||
|
||||
if (
|
||||
relationType === RelationType.ONE_TO_MANY &&
|
||||
!isDefined(targetRelationName)
|
||||
) {
|
||||
throw new GraphqlQueryRunnerException(
|
||||
`Could not resolve target relation for one-to-many field ${sourceFieldName}`,
|
||||
GraphqlQueryRunnerExceptionCode.RELATION_TARGET_OBJECT_METADATA_NOT_FOUND,
|
||||
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
|
||||
);
|
||||
}
|
||||
|
||||
const fieldMetadataTargetRelationColumnName =
|
||||
targetRelation &&
|
||||
isFieldMetadataEntityOfType(
|
||||
targetRelation,
|
||||
FieldMetadataType.MORPH_RELATION,
|
||||
)
|
||||
? `${targetRelation.settings?.joinColumnName}`
|
||||
: `${targetRelationName}Id`;
|
||||
computeMorphOrRelationFieldJoinColumnName({
|
||||
name:
|
||||
targetRelation &&
|
||||
isFieldMetadataEntityOfType(
|
||||
targetRelation,
|
||||
FieldMetadataType.MORPH_RELATION,
|
||||
)
|
||||
? targetRelation.name
|
||||
: (targetRelationName as string),
|
||||
});
|
||||
|
||||
const { relationResults, relationAggregatedFieldsResult } =
|
||||
await this.findRelations({
|
||||
|
||||
+5
-5
@@ -15,6 +15,7 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
import { FindOptionsRelations, In, ObjectLiteral } from 'typeorm';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { CommonBaseQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-base-query-runner.service';
|
||||
import {
|
||||
CommonQueryRunnerException,
|
||||
@@ -385,10 +386,7 @@ export class CommonMergeManyQueryRunnerService extends CommonBaseQueryRunnerServ
|
||||
| FieldMetadataSettingsMapping['RELATION']
|
||||
| undefined;
|
||||
|
||||
if (
|
||||
relationSettings?.relationType !== RelationType.MANY_TO_ONE ||
|
||||
!relationSettings?.joinColumnName
|
||||
) {
|
||||
if (relationSettings?.relationType !== RelationType.MANY_TO_ONE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -405,7 +403,9 @@ export class CommonMergeManyQueryRunnerService extends CommonBaseQueryRunnerServ
|
||||
objectMetadata: objMetadata,
|
||||
fieldName: field.name,
|
||||
fieldId: field.id,
|
||||
joinColumnName: relationSettings.joinColumnName,
|
||||
joinColumnName: computeMorphOrRelationFieldJoinColumnName({
|
||||
name: field.name,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -9,8 +9,8 @@ import {
|
||||
compositeTypeDefinitions,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
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';
|
||||
|
||||
+1
-2
@@ -11,8 +11,7 @@ export const getIsFlatFieldAJunctionRelationField = ({
|
||||
const isJunctionRelationField =
|
||||
isDefined(flatField.settings) &&
|
||||
'relationType' in flatField.settings &&
|
||||
flatField.settings.relationType === RelationType.MANY_TO_ONE &&
|
||||
isDefined(flatField.settings.joinColumnName);
|
||||
flatField.settings.relationType === RelationType.MANY_TO_ONE;
|
||||
|
||||
// TODO: refactor this when we remove hard-coded activity relations
|
||||
const isActivityRelationField =
|
||||
|
||||
+9
-10
@@ -4,6 +4,7 @@ import {
|
||||
} from 'twenty-shared/types';
|
||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { GraphqlQuerySelectedFieldsAggregateParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-aggregate.parser';
|
||||
import { GraphqlQuerySelectedFieldsRelationParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-relation.parser';
|
||||
import { type CompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/types/composite-field-metadata-type.type';
|
||||
@@ -104,12 +105,11 @@ export class GraphqlQuerySelectedFieldsParser {
|
||||
if (
|
||||
isFlatFieldMetadataOfType(fieldMetadata, FieldMetadataType.RELATION)
|
||||
) {
|
||||
const joinColumnName = fieldMetadata.settings?.joinColumnName;
|
||||
const joinColumnName = computeMorphOrRelationFieldJoinColumnName({
|
||||
name: fieldMetadata.name,
|
||||
});
|
||||
|
||||
if (
|
||||
isDefined(joinColumnName) &&
|
||||
isDefined(graphqlSelectedFields[joinColumnName])
|
||||
) {
|
||||
if (isDefined(graphqlSelectedFields[joinColumnName])) {
|
||||
accumulator.select[joinColumnName] = true;
|
||||
}
|
||||
|
||||
@@ -149,12 +149,11 @@ export class GraphqlQuerySelectedFieldsParser {
|
||||
continue;
|
||||
}
|
||||
|
||||
const joinColumnName = fieldMetadata.settings?.joinColumnName;
|
||||
const joinColumnName = computeMorphOrRelationFieldJoinColumnName({
|
||||
name: fieldMetadata.name,
|
||||
});
|
||||
|
||||
if (
|
||||
isDefined(joinColumnName) &&
|
||||
isDefined(graphqlSelectedFields[joinColumnName])
|
||||
) {
|
||||
if (isDefined(graphqlSelectedFields[joinColumnName])) {
|
||||
accumulator.select[joinColumnName] = true;
|
||||
}
|
||||
|
||||
|
||||
-50
@@ -365,54 +365,4 @@ describe('buildColumnsToSelect', () => {
|
||||
id: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle relation field without joinColumnName', () => {
|
||||
const noJoinColumnCompanyField = createMockField({
|
||||
id: companyFieldId,
|
||||
type: FieldMetadataType.RELATION,
|
||||
name: 'company',
|
||||
label: 'Company',
|
||||
objectMetadataId: personObjectId,
|
||||
defaultValue: null,
|
||||
settings: {
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
joinColumnName: null,
|
||||
},
|
||||
relationTargetObjectMetadataId: companyObjectId,
|
||||
});
|
||||
|
||||
const flatObjectMetadata = buildMockFlatObjectMetadata([
|
||||
nameFieldId,
|
||||
companyFieldId,
|
||||
]);
|
||||
const flatFieldMetadataMaps = buildFlatFieldMetadataMaps([
|
||||
nameField,
|
||||
noJoinColumnCompanyField,
|
||||
]);
|
||||
const flatObjectMetadataMaps = buildFlatObjectMetadataMaps([
|
||||
flatObjectMetadata,
|
||||
companyObjectMetadata,
|
||||
]);
|
||||
|
||||
const select = {
|
||||
nameFirstName: true,
|
||||
};
|
||||
|
||||
const relations = {
|
||||
company: {},
|
||||
};
|
||||
|
||||
const result = buildColumnsToSelect({
|
||||
select,
|
||||
relations,
|
||||
flatObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
nameFirstName: true,
|
||||
id: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+9
-8
@@ -1,6 +1,7 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
@@ -64,13 +65,14 @@ const getRequiredRelationColumns = (
|
||||
|
||||
if (
|
||||
!isDefined(relationValue) ||
|
||||
!isDefined(fieldMetadata?.settings?.joinColumnName) ||
|
||||
fieldMetadata.settings?.relationType !== RelationType.MANY_TO_ONE
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
requiredColumns.push(fieldMetadata.settings.joinColumnName);
|
||||
requiredColumns.push(
|
||||
computeMorphOrRelationFieldJoinColumnName({ name: fieldMetadata.name }),
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -84,7 +86,7 @@ const getRequiredRelationColumns = (
|
||||
: undefined;
|
||||
|
||||
if (
|
||||
!fieldMetadata.settings?.relationType ||
|
||||
fieldMetadata.settings?.relationType !== RelationType.MANY_TO_ONE ||
|
||||
!isDefined(targetObjectMetadata)
|
||||
) {
|
||||
continue;
|
||||
@@ -92,14 +94,13 @@ const getRequiredRelationColumns = (
|
||||
|
||||
const relationValue = relations[fieldMetadata.name];
|
||||
|
||||
if (
|
||||
!isDefined(relationValue) ||
|
||||
!isDefined(fieldMetadata?.settings?.joinColumnName)
|
||||
) {
|
||||
if (!isDefined(relationValue)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
requiredColumns.push(fieldMetadata.settings.joinColumnName);
|
||||
requiredColumns.push(
|
||||
computeMorphOrRelationFieldJoinColumnName({ name: fieldMetadata.name }),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-7
@@ -1,5 +1,6 @@
|
||||
import { type FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
|
||||
export const extractGraphQLRelationFieldNames = (
|
||||
@@ -7,14 +8,10 @@ export const extractGraphQLRelationFieldNames = (
|
||||
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
|
||||
>,
|
||||
) => {
|
||||
const settings = fieldMetadata.settings;
|
||||
const joinColumnName = settings?.joinColumnName;
|
||||
|
||||
if (!joinColumnName) {
|
||||
throw new Error('Join column name is not defined');
|
||||
}
|
||||
|
||||
const fieldMetadataName = fieldMetadata.name;
|
||||
const joinColumnName = computeMorphOrRelationFieldJoinColumnName({
|
||||
name: fieldMetadataName,
|
||||
});
|
||||
|
||||
return { joinColumnName, fieldMetadataName };
|
||||
};
|
||||
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
type ComputeRelationFieldJoinColumnNameArgs = {
|
||||
type ComputeMorphOrRelationFieldJoinColumnNameArgs = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export const computeMorphOrRelationFieldJoinColumnName = ({
|
||||
name,
|
||||
}: ComputeRelationFieldJoinColumnNameArgs) => {
|
||||
}: ComputeMorphOrRelationFieldJoinColumnNameArgs): string => {
|
||||
return `${name}Id`;
|
||||
};
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import { RelationType } from 'twenty-shared/types';
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
FieldMetadataException,
|
||||
FieldMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||
|
||||
type ComputeMorphRelationFlatFieldNameArgs = {
|
||||
fieldName: string;
|
||||
relationType: RelationType;
|
||||
targetObjectMetadataNameSingular: string;
|
||||
targetObjectMetadataNamePlural: string;
|
||||
};
|
||||
|
||||
export const computeMorphRelationFlatFieldName = ({
|
||||
fieldName,
|
||||
relationType,
|
||||
targetObjectMetadataNameSingular: nameSingular,
|
||||
targetObjectMetadataNamePlural: namePlural,
|
||||
}: ComputeMorphRelationFlatFieldNameArgs): string => {
|
||||
if (relationType === RelationType.MANY_TO_ONE) {
|
||||
return `${fieldName}${capitalize(nameSingular)}`;
|
||||
}
|
||||
|
||||
if (relationType === RelationType.ONE_TO_MANY) {
|
||||
return `${fieldName}${capitalize(namePlural)}`;
|
||||
}
|
||||
|
||||
throw new FieldMetadataException(
|
||||
`Invalid relation type (${relationType}) for field ${fieldName} on ${nameSingular}`,
|
||||
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
||||
);
|
||||
};
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { FieldMetadataType, RelationType } from 'twenty-shared/types';
|
||||
|
||||
import { getFlatFieldsFromFlatObjectMetadata } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-flat-fields-for-flat-object-metadata.util';
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { getFlatFieldsFromFlatObjectMetadata } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-flat-fields-for-flat-object-metadata.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 { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
|
||||
|
||||
+4
-3
@@ -2,10 +2,11 @@ import {
|
||||
FieldMetadataType,
|
||||
type RelationUpdatePayload,
|
||||
} from 'twenty-shared/types';
|
||||
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { computeMorphRelationFlatFieldName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-flat-field-name.util';
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
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 { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
@@ -89,7 +90,7 @@ export const computeFlatFieldToUpdateFromMorphRelationUpdatePayload = ({
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
const computedMorphName = computeMorphRelationFieldName({
|
||||
const computedMorphName = computeMorphRelationFlatFieldName({
|
||||
fieldName: morphNameWithoutObjectName,
|
||||
relationType: fieldMetadataToUpdate.settings.relationType,
|
||||
targetObjectMetadataNameSingular: newTargetObjectMetadata.nameSingular,
|
||||
|
||||
+4
-3
@@ -1,12 +1,13 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { computeMorphRelationFlatFieldName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-flat-field-name.util';
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
|
||||
import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
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 FieldInputTranspilationResult } from 'src/engine/metadata-modules/flat-field-metadata/types/field-input-transpilation-result.type';
|
||||
@@ -86,7 +87,7 @@ export const fromMorphRelationCreateFieldInputToFlatFieldMetadatas = async ({
|
||||
const morphId = v4();
|
||||
const flatFieldsAndIndexes = morphRelationCreationPayload.reduce(
|
||||
(acc, { relationCreationPayload, targetFlatObjectMetadata }) => {
|
||||
const currentMorphRelationFieldName = computeMorphRelationFieldName({
|
||||
const currentMorphRelationFieldName = computeMorphRelationFlatFieldName({
|
||||
fieldName: createFieldInput.name,
|
||||
relationType: relationCreationPayload.type,
|
||||
targetObjectMetadataNameSingular: targetFlatObjectMetadata.nameSingular,
|
||||
|
||||
+1
-1
@@ -2,10 +2,10 @@ import { msg } from '@lingui/core/macro';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
|
||||
import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
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 FieldInputTranspilationResult } from 'src/engine/metadata-modules/flat-field-metadata/types/field-input-transpilation-result.type';
|
||||
|
||||
+2
-1
@@ -4,12 +4,13 @@ import {
|
||||
RelationOnDeleteAction,
|
||||
RelationType,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
|
||||
import { type MorphOrRelationFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/types/morph-or-relation-field-metadata-type.type';
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { generateIndexForFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/generate-index-for-flat-field-metadata.util';
|
||||
import { getDefaultFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/get-default-flat-field-metadata-from-create-field-input.util';
|
||||
|
||||
+5
-4
@@ -1,6 +1,6 @@
|
||||
import { RelationType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { findManyFlatEntityByUniversalIdentifierInUniversalFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-universal-identifier-in-universal-flat-entity-maps.util';
|
||||
import { isMorphOrRelationUniversalFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util';
|
||||
import { type UniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-maps.type';
|
||||
@@ -39,15 +39,16 @@ export const getObjectFieldNamesAndJoinColumnNames = ({
|
||||
universalFlatFieldMetadata,
|
||||
) &&
|
||||
universalFlatFieldMetadata.universalSettings.relationType ===
|
||||
RelationType.MANY_TO_ONE &&
|
||||
isDefined(universalFlatFieldMetadata.universalSettings.joinColumnName)
|
||||
RelationType.MANY_TO_ONE
|
||||
) {
|
||||
return {
|
||||
...acc,
|
||||
fieldNames: [...acc.fieldNames, universalFlatFieldMetadata.name],
|
||||
joinColumnNames: [
|
||||
...acc.joinColumnNames,
|
||||
universalFlatFieldMetadata.universalSettings.joinColumnName,
|
||||
computeMorphOrRelationFieldJoinColumnName({
|
||||
name: universalFlatFieldMetadata.name,
|
||||
}),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
+2
-2
@@ -3,9 +3,9 @@ import {
|
||||
type FieldMetadataType,
|
||||
type FromTo,
|
||||
} from 'twenty-shared/types';
|
||||
import { computeMorphRelationFieldName } from 'twenty-shared/utils';
|
||||
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { computeMorphRelationFlatFieldName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-flat-field-name.util';
|
||||
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-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 { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
@@ -37,7 +37,7 @@ const updateMorphFlatFieldName = ({
|
||||
namePlural: fromRelationTargetFlatObjectMetadata.namePlural,
|
||||
});
|
||||
|
||||
const newMorphFieldName = computeMorphRelationFieldName({
|
||||
const newMorphFieldName = computeMorphRelationFlatFieldName({
|
||||
fieldName: initialMorphRelationFieldName,
|
||||
relationType: fromMorphFlatFieldMetadata.universalSettings.relationType,
|
||||
targetObjectMetadataNameSingular:
|
||||
|
||||
+11
-5
@@ -1,9 +1,11 @@
|
||||
import { RelationType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
FlatEntityMapsException,
|
||||
FlatEntityMapsExceptionCode,
|
||||
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { isMorphOrRelationUniversalFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util';
|
||||
import { generateDeterministicIndexNameV2 } from 'src/engine/metadata-modules/index-metadata/utils/generate-deterministic-index-name-v2';
|
||||
import { type UniversalFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-metadata.type';
|
||||
@@ -36,11 +38,15 @@ export const generateFlatIndexMetadataWithNameOrThrow = ({
|
||||
);
|
||||
}
|
||||
|
||||
const name = isMorphOrRelationUniversalFlatFieldMetadata(
|
||||
relatedFlatFieldMetadata,
|
||||
)
|
||||
? (relatedFlatFieldMetadata.universalSettings.joinColumnName ??
|
||||
relatedFlatFieldMetadata.name)
|
||||
const isManyToOneRelation =
|
||||
isMorphOrRelationUniversalFlatFieldMetadata(relatedFlatFieldMetadata) &&
|
||||
relatedFlatFieldMetadata.universalSettings?.relationType ===
|
||||
RelationType.MANY_TO_ONE;
|
||||
|
||||
const name = isManyToOneRelation
|
||||
? computeMorphOrRelationFieldJoinColumnName({
|
||||
name: relatedFlatFieldMetadata.name,
|
||||
})
|
||||
: relatedFlatFieldMetadata.name;
|
||||
|
||||
return {
|
||||
|
||||
+1
-1
@@ -5,10 +5,10 @@ import {
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.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 { generateMorphOrRelationFlatFieldMetadataPair } from 'src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-relation-flat-field-metadata-pair.util';
|
||||
|
||||
+4
-11
@@ -7,16 +7,13 @@ import {
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type ColumnType, type EntitySchemaColumnOptions } from 'typeorm';
|
||||
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
|
||||
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
import { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
|
||||
import { serializeDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/serialize-default-value';
|
||||
import {
|
||||
TwentyORMException,
|
||||
TwentyORMExceptionCode,
|
||||
} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception';
|
||||
import {
|
||||
type EntitySchemaFieldMetadata,
|
||||
type EntitySchemaFieldMetadataMaps,
|
||||
@@ -57,18 +54,14 @@ export class EntitySchemaColumnFactory {
|
||||
if (isRelation) {
|
||||
const isManyToOneRelation =
|
||||
fieldMetadata.settings?.relationType === RelationType.MANY_TO_ONE;
|
||||
const joinColumnName = fieldMetadata.settings?.joinColumnName;
|
||||
|
||||
if (!isManyToOneRelation) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isDefined(joinColumnName)) {
|
||||
throw new TwentyORMException(
|
||||
`Field ${fieldMetadata.id} of type ${fieldMetadata.type} is a many to one relation but does not have a join column name`,
|
||||
TwentyORMExceptionCode.MALFORMED_METADATA,
|
||||
);
|
||||
}
|
||||
const joinColumnName = computeMorphOrRelationFieldJoinColumnName({
|
||||
name: fieldMetadata.name,
|
||||
});
|
||||
|
||||
entitySchemaColumnMap[joinColumnName] = {
|
||||
name: joinColumnName,
|
||||
|
||||
+15
-3
@@ -1,4 +1,8 @@
|
||||
import { type FieldMetadataType } from 'twenty-shared/types';
|
||||
import {
|
||||
type FieldMetadataType,
|
||||
RelationType as TwentyRelationType,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import { type RelationType } from 'typeorm/metadata/types/RelationTypes';
|
||||
|
||||
import {
|
||||
@@ -10,6 +14,7 @@ import {
|
||||
type EntitySchemaFieldMetadataMaps,
|
||||
type EntitySchemaObjectMetadataMaps,
|
||||
} from 'src/engine/twenty-orm/global-workspace-datasource/types/entity-schema-metadata.type';
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { converRelationTypeToTypeORMRelationType } from 'src/engine/twenty-orm/utils/convert-relation-type-to-typeorm-relation-type.util';
|
||||
|
||||
interface RelationDetails {
|
||||
@@ -65,12 +70,19 @@ export function determineSchemaRelationDetails(
|
||||
throw new Error('Target field metadata not found');
|
||||
}
|
||||
|
||||
const isManyToOne =
|
||||
fieldMetadata.settings.relationType === TwentyRelationType.MANY_TO_ONE;
|
||||
|
||||
return {
|
||||
relationType,
|
||||
target: targetObjectMetadata.nameSingular,
|
||||
inverseSide: targetFieldMetadata.name,
|
||||
joinColumn: fieldMetadata.settings.joinColumnName
|
||||
? { name: fieldMetadata.settings.joinColumnName }
|
||||
joinColumn: isManyToOne
|
||||
? {
|
||||
name: computeMorphOrRelationFieldJoinColumnName({
|
||||
name: fieldMetadata.name,
|
||||
}),
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
+2
-6
@@ -1,8 +1,8 @@
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import {
|
||||
type FieldMetadataSettingsMapping,
|
||||
RelationType,
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const formatColumnNameForRelationField = (
|
||||
fieldName: string,
|
||||
@@ -13,11 +13,7 @@ export const formatColumnNameForRelationField = (
|
||||
}
|
||||
|
||||
if (fieldMetadataSettings.relationType === RelationType.MANY_TO_ONE) {
|
||||
if (!isDefined(fieldMetadataSettings.joinColumnName)) {
|
||||
throw new Error(`Join column name is not defined for field ${fieldName}`);
|
||||
}
|
||||
|
||||
return fieldMetadataSettings.joinColumnName;
|
||||
return computeMorphOrRelationFieldJoinColumnName({ name: fieldName });
|
||||
}
|
||||
|
||||
return fieldName;
|
||||
|
||||
+6
-7
@@ -47,6 +47,7 @@ import {
|
||||
isMatchingUUIDFilter,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { getFlatFieldsFromFlatObjectMetadata } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-flat-fields-for-flat-object-metadata.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';
|
||||
@@ -208,8 +209,8 @@ export const isRecordMatchingRLSRowLevelPermissionPredicate = ({
|
||||
objectFields.find(
|
||||
(field) =>
|
||||
field.type === FieldMetadataType.RELATION &&
|
||||
(field.settings as { joinColumnName?: string } | undefined)
|
||||
?.joinColumnName === filterKey,
|
||||
computeMorphOrRelationFieldJoinColumnName({ name: field.name }) ===
|
||||
filterKey,
|
||||
);
|
||||
|
||||
if (!isDefined(objectMetadataField)) {
|
||||
@@ -412,11 +413,9 @@ export const isRecordMatchingRLSRowLevelPermissionPredicate = ({
|
||||
}
|
||||
case FieldMetadataType.RELATION: {
|
||||
const isJoinColumn =
|
||||
(
|
||||
objectMetadataField.settings as
|
||||
| { joinColumnName?: string }
|
||||
| undefined
|
||||
)?.joinColumnName === filterKey;
|
||||
computeMorphOrRelationFieldJoinColumnName({
|
||||
name: objectMetadataField.name,
|
||||
}) === filterKey;
|
||||
|
||||
if (isJoinColumn) {
|
||||
return isMatchingUUIDFilter({
|
||||
|
||||
+1
-1
@@ -5,10 +5,10 @@ import {
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
|
||||
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||
|
||||
|
||||
+12
-7
@@ -1,5 +1,6 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { RelationType } from 'twenty-shared/types';
|
||||
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { findManyFlatEntityByUniversalIdentifierInUniversalFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-universal-identifier-in-universal-flat-entity-maps-or-throw.util';
|
||||
import { isMorphOrRelationUniversalFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util';
|
||||
import { type AllUniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/all-universal-flat-entity-maps.type';
|
||||
@@ -24,12 +25,16 @@ export const buildUniversalFlatObjectFieldByNameAndJoinColumnMaps = ({
|
||||
for (const field of objectFields) {
|
||||
fieldUniversalIdentifierByName[field.name] = field.universalIdentifier;
|
||||
|
||||
if (isMorphOrRelationUniversalFlatFieldMetadata(field)) {
|
||||
if (isDefined(field.universalSettings.joinColumnName)) {
|
||||
fieldUniversalIdentifierByJoinColumnName[
|
||||
field.universalSettings.joinColumnName
|
||||
] = field.universalIdentifier;
|
||||
}
|
||||
if (
|
||||
isMorphOrRelationUniversalFlatFieldMetadata(field) &&
|
||||
field.universalSettings.relationType === RelationType.MANY_TO_ONE
|
||||
) {
|
||||
const joinColumnName = computeMorphOrRelationFieldJoinColumnName({
|
||||
name: field.name,
|
||||
});
|
||||
|
||||
fieldUniversalIdentifierByJoinColumnName[joinColumnName] =
|
||||
field.universalIdentifier;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-7
@@ -5,6 +5,7 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
import { type QueryRunner } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||
|
||||
import { type MetadataFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity-maps.type';
|
||||
@@ -214,13 +215,9 @@ export class CreateFieldActionHandlerService extends WorkspaceMigrationRunnerAct
|
||||
targetFlatObjectMetadata,
|
||||
);
|
||||
|
||||
const joinColumnName = flatFieldMetadata.settings?.joinColumnName;
|
||||
|
||||
if (!isDefined(joinColumnName)) {
|
||||
throw new Error(
|
||||
'Join column name is not defined in a MANY_TO_ONE relation',
|
||||
);
|
||||
}
|
||||
const joinColumnName = computeMorphOrRelationFieldJoinColumnName({
|
||||
name: flatFieldMetadata.name,
|
||||
});
|
||||
|
||||
await this.workspaceSchemaManagerService.foreignKeyManager.createForeignKey(
|
||||
{
|
||||
|
||||
+29
-33
@@ -3,10 +3,12 @@ import { Injectable } from '@nestjs/common';
|
||||
import {
|
||||
FieldMetadataSettingsMapping,
|
||||
FieldMetadataType,
|
||||
RelationType,
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { ColumnType, type QueryRunner } from 'typeorm';
|
||||
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
@@ -223,35 +225,6 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct
|
||||
}
|
||||
|
||||
if (isDefined(update.settings)) {
|
||||
// Handle joinColumnName rename
|
||||
if (isMorphOrRelationFlatFieldMetadata(optimisticFlatFieldMetadata)) {
|
||||
const fromSettings = optimisticFlatFieldMetadata.settings;
|
||||
const toSettings = update.settings as
|
||||
| FieldMetadataSettingsMapping['MORPH_RELATION']
|
||||
| FieldMetadataSettingsMapping['RELATION'];
|
||||
|
||||
if (
|
||||
isDefined(fromSettings?.joinColumnName) &&
|
||||
isDefined(toSettings?.joinColumnName) &&
|
||||
fromSettings.joinColumnName !== toSettings.joinColumnName
|
||||
) {
|
||||
await this.workspaceSchemaManagerService.columnManager.renameColumn({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
tableName,
|
||||
oldColumnName: fromSettings.joinColumnName,
|
||||
newColumnName: toSettings.joinColumnName,
|
||||
});
|
||||
optimisticFlatFieldMetadata = {
|
||||
...optimisticFlatFieldMetadata,
|
||||
settings: {
|
||||
...optimisticFlatFieldMetadata.settings,
|
||||
joinColumnName: toSettings.joinColumnName,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Handle asExpression/generatedType change (for TS_VECTOR fields)
|
||||
if (
|
||||
isFlatFieldMetadataOfType(
|
||||
@@ -302,19 +275,26 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct
|
||||
| FieldMetadataSettingsMapping['MORPH_RELATION']
|
||||
| FieldMetadataSettingsMapping['RELATION'];
|
||||
|
||||
const isManyToOne =
|
||||
optimisticFlatFieldMetadata.settings?.relationType ===
|
||||
RelationType.MANY_TO_ONE;
|
||||
|
||||
if (
|
||||
isDefined(optimisticFlatFieldMetadata.settings.joinColumnName) &&
|
||||
isManyToOne &&
|
||||
isDefined(fromSettings?.onDelete) &&
|
||||
isDefined(toSettings?.onDelete) &&
|
||||
toSettings.onDelete !== fromSettings.onDelete
|
||||
) {
|
||||
const joinColumnName = computeMorphOrRelationFieldJoinColumnName({
|
||||
name: optimisticFlatFieldMetadata.name,
|
||||
});
|
||||
const foreignKeyName =
|
||||
await this.workspaceSchemaManagerService.foreignKeyManager.getForeignKeyName(
|
||||
{
|
||||
queryRunner,
|
||||
schemaName,
|
||||
tableName,
|
||||
columnName: optimisticFlatFieldMetadata.settings.joinColumnName,
|
||||
columnName: joinColumnName,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -351,7 +331,7 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct
|
||||
schemaName,
|
||||
foreignKey: {
|
||||
tableName,
|
||||
columnName: optimisticFlatFieldMetadata.settings.joinColumnName,
|
||||
columnName: joinColumnName,
|
||||
referencedTableName,
|
||||
referencedColumnName: 'id',
|
||||
onDelete:
|
||||
@@ -403,7 +383,23 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct
|
||||
newColumnName: toCompositeColumnName,
|
||||
});
|
||||
}
|
||||
} else if (!isMorphOrRelationFlatFieldMetadata(flatFieldMetadata)) {
|
||||
} else if (isMorphOrRelationFlatFieldMetadata(flatFieldMetadata)) {
|
||||
if (
|
||||
flatFieldMetadata.settings?.relationType === RelationType.MANY_TO_ONE
|
||||
) {
|
||||
await this.workspaceSchemaManagerService.columnManager.renameColumn({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
tableName,
|
||||
oldColumnName: computeMorphOrRelationFieldJoinColumnName({
|
||||
name: fromName,
|
||||
}),
|
||||
newColumnName: computeMorphOrRelationFieldJoinColumnName({
|
||||
name: toName,
|
||||
}),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
await this.workspaceSchemaManagerService.columnManager.renameColumn({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
|
||||
+9
-4
@@ -1,7 +1,8 @@
|
||||
import { compositeTypeDefinitions } from 'twenty-shared/types';
|
||||
import { compositeTypeDefinitions, RelationType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type QueryRunner } from 'typeorm';
|
||||
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
import {
|
||||
@@ -41,14 +42,18 @@ export const computeFlatIndexFieldColumnNames = ({
|
||||
}
|
||||
|
||||
if (isMorphOrRelationFlatFieldMetadata(flatFieldMetadata)) {
|
||||
if (!isDefined(flatFieldMetadata.settings?.joinColumnName)) {
|
||||
if (
|
||||
flatFieldMetadata.settings?.relationType !== RelationType.MANY_TO_ONE
|
||||
) {
|
||||
throw new FlatEntityMapsException(
|
||||
'Join column name is not defined for relation field',
|
||||
'Cannot index a relation field that has no join column',
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return flatFieldMetadata.settings.joinColumnName;
|
||||
return computeMorphOrRelationFieldJoinColumnName({
|
||||
name: flatFieldMetadata.name,
|
||||
});
|
||||
}
|
||||
|
||||
if (isCompositeFieldMetadataType(flatFieldMetadata.type)) {
|
||||
|
||||
+11
-6
@@ -1,6 +1,12 @@
|
||||
import { type CompositeProperty, FieldMetadataType } from 'twenty-shared/types';
|
||||
import {
|
||||
type CompositeProperty,
|
||||
FieldMetadataType,
|
||||
RelationType,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import { type ColumnType } from 'typeorm';
|
||||
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { type CompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/types/composite-field-metadata-type.type';
|
||||
import {
|
||||
computeColumnName,
|
||||
@@ -109,14 +115,13 @@ const generateRelationColumnDefinition = (
|
||||
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
|
||||
>,
|
||||
): WorkspaceSchemaColumnDefinition | null => {
|
||||
if (
|
||||
!flatFieldMetadata.settings ||
|
||||
!flatFieldMetadata.settings.joinColumnName
|
||||
) {
|
||||
if (flatFieldMetadata.settings?.relationType !== RelationType.MANY_TO_ONE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const joinColumnName = flatFieldMetadata.settings.joinColumnName;
|
||||
const joinColumnName = computeMorphOrRelationFieldJoinColumnName({
|
||||
name: flatFieldMetadata.name,
|
||||
});
|
||||
|
||||
return {
|
||||
name: joinColumnName,
|
||||
|
||||
+1
-1
@@ -2,8 +2,8 @@ import { isObject, isString } from '@sniptt/guards';
|
||||
import { FieldMetadataType, RelationType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { getFlatFieldsFromFlatObjectMetadata } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-flat-fields-for-flat-object-metadata.util';
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { getFlatFieldsFromFlatObjectMetadata } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-flat-fields-for-flat-object-metadata.util';
|
||||
import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
|
||||
import { type ObjectMetadataInfo } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
|
||||
|
||||
|
||||
+3
-2
@@ -6,9 +6,10 @@ import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-m
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { makeMetadataAPIRequestWithFileUpload } from 'test/integration/metadata/suites/utils/make-metadata-api-request-with-file-upload.util';
|
||||
import { FieldMetadataType, RelationType } from 'twenty-shared/types';
|
||||
import { computeMorphRelationFieldName } from 'twenty-shared/utils';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { computeMorphRelationFlatFieldName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-flat-field-name.util';
|
||||
|
||||
const TEST_OBJECT_METADATA_NAME_SINGULAR = 'apiInputValidationTestObject';
|
||||
const TEST_OBJECT_METADATA_NAME_PLURAL = 'apiInputValidationTestObjects';
|
||||
const TEST_TARGET_OBJECT_METADATA_NAME_SINGULAR_1 =
|
||||
@@ -29,7 +30,7 @@ export const TEST_TARGET_OBJECT_RECORD_ID_FIELD_VALUE =
|
||||
'20202020-b21e-4ec2-873b-de4264d89021';
|
||||
|
||||
export const joinColumnNameForManyToOneMorphRelationField1 =
|
||||
computeMorphRelationFieldName({
|
||||
computeMorphRelationFlatFieldName({
|
||||
fieldName: 'manyToOneMorphRelationField',
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
targetObjectMetadataNameSingular:
|
||||
|
||||
+5
-5
@@ -1,8 +1,8 @@
|
||||
import { computeMorphRelationFieldName } from '@/utils/fieldMetadata/compute-morph-relation-field-name';
|
||||
import { computeMorphRelationGqlFieldName } from '@/utils/fieldMetadata/compute-morph-relation-gql-field-name';
|
||||
|
||||
describe('computeMorphRelationFieldName', () => {
|
||||
describe('computeMorphRelationGqlFieldName', () => {
|
||||
it('should return singular-based name for MANY_TO_ONE', () => {
|
||||
const result = computeMorphRelationFieldName({
|
||||
const result = computeMorphRelationGqlFieldName({
|
||||
fieldName: 'assigned',
|
||||
relationType: 'MANY_TO_ONE' as any,
|
||||
targetObjectMetadataNameSingular: 'person',
|
||||
@@ -13,7 +13,7 @@ describe('computeMorphRelationFieldName', () => {
|
||||
});
|
||||
|
||||
it('should return plural-based name for ONE_TO_MANY', () => {
|
||||
const result = computeMorphRelationFieldName({
|
||||
const result = computeMorphRelationGqlFieldName({
|
||||
fieldName: 'assigned',
|
||||
relationType: 'ONE_TO_MANY' as any,
|
||||
targetObjectMetadataNameSingular: 'person',
|
||||
@@ -25,7 +25,7 @@ describe('computeMorphRelationFieldName', () => {
|
||||
|
||||
it('should throw for invalid relation type', () => {
|
||||
expect(() =>
|
||||
computeMorphRelationFieldName({
|
||||
computeMorphRelationGqlFieldName({
|
||||
fieldName: 'assigned',
|
||||
relationType: 'INVALID' as any,
|
||||
targetObjectMetadataNameSingular: 'person',
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
import {
|
||||
computeMorphRelationGqlFieldJoinColumnName,
|
||||
computeRelationGqlFieldJoinColumnName,
|
||||
} from '@/utils/fieldMetadata/compute-relation-gql-field-join-column-name';
|
||||
|
||||
describe('computeRelationGqlFieldJoinColumnName', () => {
|
||||
it('should append `Id` to a simple field name', () => {
|
||||
expect(computeRelationGqlFieldJoinColumnName({ name: 'company' })).toBe(
|
||||
'companyId',
|
||||
);
|
||||
});
|
||||
|
||||
it('should preserve camelCase field names', () => {
|
||||
expect(
|
||||
computeRelationGqlFieldJoinColumnName({ name: 'pointOfContact' }),
|
||||
).toBe('pointOfContactId');
|
||||
});
|
||||
|
||||
it('should append `Id` to a morph-aware field name', () => {
|
||||
expect(
|
||||
computeRelationGqlFieldJoinColumnName({ name: 'targetOpportunity' }),
|
||||
).toBe('targetOpportunityId');
|
||||
});
|
||||
|
||||
it('should not strip an existing trailing `Id` (it just appends)', () => {
|
||||
expect(computeRelationGqlFieldJoinColumnName({ name: 'companyId' })).toBe(
|
||||
'companyIdId',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('computeMorphRelationGqlFieldJoinColumnName', () => {
|
||||
it('should combine field name and capitalized singular target for MANY_TO_ONE', () => {
|
||||
expect(
|
||||
computeMorphRelationGqlFieldJoinColumnName({
|
||||
fieldName: 'target',
|
||||
relationType: 'MANY_TO_ONE' as any,
|
||||
targetObjectMetadataNameSingular: 'opportunity',
|
||||
targetObjectMetadataNamePlural: 'opportunities',
|
||||
}),
|
||||
).toBe('targetOpportunityId');
|
||||
});
|
||||
|
||||
it('should combine field name and capitalized plural target for ONE_TO_MANY', () => {
|
||||
expect(
|
||||
computeMorphRelationGqlFieldJoinColumnName({
|
||||
fieldName: 'caretaker',
|
||||
relationType: 'ONE_TO_MANY' as any,
|
||||
targetObjectMetadataNameSingular: 'person',
|
||||
targetObjectMetadataNamePlural: 'people',
|
||||
}),
|
||||
).toBe('caretakerPeopleId');
|
||||
});
|
||||
|
||||
it('should handle simple plurals (e.g. `companies`)', () => {
|
||||
expect(
|
||||
computeMorphRelationGqlFieldJoinColumnName({
|
||||
fieldName: 'parent',
|
||||
relationType: 'ONE_TO_MANY' as any,
|
||||
targetObjectMetadataNameSingular: 'company',
|
||||
targetObjectMetadataNamePlural: 'companies',
|
||||
}),
|
||||
).toBe('parentCompaniesId');
|
||||
});
|
||||
|
||||
it('should throw on an invalid relation type', () => {
|
||||
expect(() =>
|
||||
computeMorphRelationGqlFieldJoinColumnName({
|
||||
fieldName: 'target',
|
||||
relationType: 'INVALID' as any,
|
||||
targetObjectMetadataNameSingular: 'opportunity',
|
||||
targetObjectMetadataNamePlural: 'opportunities',
|
||||
}),
|
||||
).toThrow();
|
||||
});
|
||||
});
|
||||
+4
-4
@@ -6,19 +6,19 @@ enum RelationType {
|
||||
ONE_TO_MANY = 'ONE_TO_MANY',
|
||||
}
|
||||
|
||||
type ComputeMorphRelationFieldNameArgs = {
|
||||
type ComputeMorphRelationGqlFieldNameArgs = {
|
||||
fieldName: string;
|
||||
relationType: RelationType;
|
||||
targetObjectMetadataNameSingular: string;
|
||||
targetObjectMetadataNamePlural: string;
|
||||
};
|
||||
|
||||
export const computeMorphRelationFieldName = ({
|
||||
export const computeMorphRelationGqlFieldName = ({
|
||||
fieldName,
|
||||
relationType,
|
||||
targetObjectMetadataNameSingular: nameSingular,
|
||||
targetObjectMetadataNamePlural: namePlural,
|
||||
}: ComputeMorphRelationFieldNameArgs): string => {
|
||||
}: ComputeMorphRelationGqlFieldNameArgs): string => {
|
||||
if (relationType === RelationType.MANY_TO_ONE) {
|
||||
return `${fieldName}${capitalize(nameSingular)}`;
|
||||
}
|
||||
@@ -29,6 +29,6 @@ export const computeMorphRelationFieldName = ({
|
||||
|
||||
throw new CustomError(
|
||||
`Invalid relation type (${relationType}) for field ${fieldName} on ${nameSingular}`,
|
||||
'INVALID_RELATION_TYPE_FOR_COMPUTE_MORPH_RELATION_FIELD_NAME',
|
||||
'INVALID_RELATION_TYPE_FOR_COMPUTE_MORPH_RELATION_GQL_FIELD_NAME',
|
||||
);
|
||||
};
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import { computeMorphRelationGqlFieldName } from '@/utils/fieldMetadata/compute-morph-relation-gql-field-name';
|
||||
|
||||
enum RelationType {
|
||||
MANY_TO_ONE = 'MANY_TO_ONE',
|
||||
ONE_TO_MANY = 'ONE_TO_MANY',
|
||||
}
|
||||
|
||||
type ComputeRelationGqlFieldJoinColumnNameArgs = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export const computeRelationGqlFieldJoinColumnName = ({
|
||||
name,
|
||||
}: ComputeRelationGqlFieldJoinColumnNameArgs): string => {
|
||||
return `${name}Id`;
|
||||
};
|
||||
|
||||
type ComputeMorphRelationGqlFieldJoinColumnNameArgs = {
|
||||
fieldName: string;
|
||||
relationType: RelationType;
|
||||
targetObjectMetadataNameSingular: string;
|
||||
targetObjectMetadataNamePlural: string;
|
||||
};
|
||||
|
||||
export const computeMorphRelationGqlFieldJoinColumnName = ({
|
||||
fieldName,
|
||||
relationType,
|
||||
targetObjectMetadataNameSingular,
|
||||
targetObjectMetadataNamePlural,
|
||||
}: ComputeMorphRelationGqlFieldJoinColumnNameArgs): string => {
|
||||
const morphGqlFieldName = computeMorphRelationGqlFieldName({
|
||||
fieldName,
|
||||
relationType,
|
||||
targetObjectMetadataNameSingular,
|
||||
targetObjectMetadataNamePlural,
|
||||
});
|
||||
|
||||
return computeRelationGqlFieldJoinColumnName({ name: morphGqlFieldName });
|
||||
};
|
||||
@@ -44,7 +44,11 @@ export { deepMerge } from './deepMerge';
|
||||
export { CustomError } from './errors/CustomError';
|
||||
export { evalFromContext } from './evalFromContext';
|
||||
export { extractAndSanitizeObjectStringFields } from './extractAndSanitizeObjectStringFields';
|
||||
export { computeMorphRelationFieldName } from './fieldMetadata/compute-morph-relation-field-name';
|
||||
export { computeMorphRelationGqlFieldName } from './fieldMetadata/compute-morph-relation-gql-field-name';
|
||||
export {
|
||||
computeRelationGqlFieldJoinColumnName,
|
||||
computeMorphRelationGqlFieldJoinColumnName,
|
||||
} from './fieldMetadata/compute-relation-gql-field-join-column-name';
|
||||
export { isFieldMetadataArrayKind } from './fieldMetadata/isFieldMetadataArrayKind';
|
||||
export { isFieldMetadataDateKind } from './fieldMetadata/isFieldMetadataDateKind';
|
||||
export { isFieldMetadataEligibleForFieldsWidget } from './fieldMetadata/isFieldMetadataEligibleForFieldsWidget';
|
||||
|
||||
Reference in New Issue
Block a user