Morph many to one picker (#14155)
This PR follows the multiSelect PR merged previously. It will enable morph relation Many to One to be handled from the table, using a singleSelect picker Main point : I decided to change the singleSelect API to take an array of **objectMetadataName** instead of only one to deal with both our usecases. --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
+3
-3
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import omit from 'lodash.omit';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
|
||||
import { type Repository } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||
import { FieldMetadataRelationService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata-relation.service';
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { computeMorphRelationFieldName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-field-name.util';
|
||||
import { prepareCustomFieldMetadataForCreation } from 'src/engine/metadata-modules/field-metadata/utils/prepare-field-metadata-for-creation.util';
|
||||
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { type ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
@@ -75,7 +74,8 @@ export class FieldMetadataMorphRelationService {
|
||||
const currentMorphRelationFieldName = computeMorphRelationFieldName({
|
||||
fieldName: fieldMetadataForCreate.name,
|
||||
relationType: relationCreationPayload.type,
|
||||
targetObjectMetadata,
|
||||
targetObjectMetadataNameSingular: targetObjectMetadata.nameSingular,
|
||||
targetObjectMetadataNamePlural: targetObjectMetadata.namePlural,
|
||||
});
|
||||
const relationFieldMetadataForCreate =
|
||||
this.fieldMetadataRelationService.computeCustomRelationFieldMetadataForCreation(
|
||||
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
|
||||
type ComputeMorphRelationFieldNameArgs = {
|
||||
fieldName: string;
|
||||
relationType: RelationType;
|
||||
targetObjectMetadata: Pick<
|
||||
ObjectMetadataEntity,
|
||||
'nameSingular' | 'namePlural'
|
||||
>;
|
||||
};
|
||||
|
||||
export const computeMorphRelationFieldName = ({
|
||||
fieldName,
|
||||
relationType,
|
||||
targetObjectMetadata,
|
||||
}: ComputeMorphRelationFieldNameArgs): string => {
|
||||
if (relationType === RelationType.MANY_TO_ONE) {
|
||||
return `${fieldName}${capitalize(targetObjectMetadata.nameSingular)}`;
|
||||
}
|
||||
|
||||
return `${fieldName}${capitalize(targetObjectMetadata.namePlural)}`;
|
||||
};
|
||||
+3
-3
@@ -1,13 +1,12 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { type FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.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 { computeMorphRelationFieldName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-field-name.util';
|
||||
import { type FieldInputTranspilationResult } from 'src/engine/metadata-modules/flat-field-metadata/types/field-input-transpilation-result.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { generateMorphOrRelationFlatFieldMetadataPair } from 'src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-relation-flat-field-metadata-pair.util';
|
||||
@@ -71,7 +70,8 @@ export const fromMorphRelationCreateFieldInputToFlatFieldMetadatas = async ({
|
||||
const currentMorphRelationFieldName = computeMorphRelationFieldName({
|
||||
fieldName: createFieldInput.name,
|
||||
relationType: relationCreationPayload.type,
|
||||
targetObjectMetadata: targetFlatObjectMetadata,
|
||||
targetObjectMetadataNameSingular: targetFlatObjectMetadata.nameSingular,
|
||||
targetObjectMetadataNamePlural: targetFlatObjectMetadata.namePlural,
|
||||
});
|
||||
const sourceFlatObjectMetadataJoinColumnName =
|
||||
computeMorphOrRelationFieldJoinColumnName({
|
||||
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
import { FieldMetadataType, RelationType } from 'twenty-shared/types';
|
||||
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { getMorphNameFromMorphFieldMetadataName } from 'src/engine/metadata-modules/flat-object-metadata/utils/get-morph-name-from-morph-field-metadata-name.util';
|
||||
|
||||
describe('getMorphNameFromMorphFieldMetadataName', () => {
|
||||
const createMorphRelationFlatFieldMetadataMock = (
|
||||
name: string,
|
||||
relationType: RelationType,
|
||||
) =>
|
||||
({
|
||||
name,
|
||||
type: FieldMetadataType.MORPH_RELATION,
|
||||
label: name,
|
||||
settings: { relationType },
|
||||
}) as unknown as FlatFieldMetadata<FieldMetadataType.MORPH_RELATION>;
|
||||
|
||||
it('should extract base morph field name for MANY_TO_ONE (removes singular suffix)', () => {
|
||||
const morphRelationFlatFieldMetadata =
|
||||
createMorphRelationFlatFieldMetadataMock(
|
||||
'contactDeal',
|
||||
RelationType.MANY_TO_ONE,
|
||||
);
|
||||
|
||||
const result = getMorphNameFromMorphFieldMetadataName({
|
||||
morphRelationFlatFieldMetadata,
|
||||
nameSingular: 'Deal',
|
||||
namePlural: 'Deals',
|
||||
});
|
||||
|
||||
expect(result).toBe('contact');
|
||||
});
|
||||
|
||||
it('should also work extract base morph field name for MANY_TO_ONE if casing is not correct', () => {
|
||||
const morphRelationFlatFieldMetadata =
|
||||
createMorphRelationFlatFieldMetadataMock(
|
||||
'contactDeal',
|
||||
RelationType.MANY_TO_ONE,
|
||||
);
|
||||
|
||||
const result = getMorphNameFromMorphFieldMetadataName({
|
||||
morphRelationFlatFieldMetadata,
|
||||
nameSingular: 'deal',
|
||||
namePlural: 'deals',
|
||||
});
|
||||
|
||||
expect(result).toBe('contact');
|
||||
});
|
||||
|
||||
it('should extract base morph field name for ONE_TO_MANY (removes plural suffix)', () => {
|
||||
const morphRelationFlatFieldMetadata =
|
||||
createMorphRelationFlatFieldMetadataMock(
|
||||
'contactDeals',
|
||||
RelationType.ONE_TO_MANY,
|
||||
);
|
||||
|
||||
const result = getMorphNameFromMorphFieldMetadataName({
|
||||
morphRelationFlatFieldMetadata,
|
||||
nameSingular: 'Deal',
|
||||
namePlural: 'Deals',
|
||||
});
|
||||
|
||||
expect(result).toBe('contact');
|
||||
});
|
||||
|
||||
it('should return the original name when suffix does not match', () => {
|
||||
const morphRelationFlatFieldMetadata =
|
||||
createMorphRelationFlatFieldMetadataMock(
|
||||
'randomName',
|
||||
RelationType.MANY_TO_ONE,
|
||||
);
|
||||
|
||||
const result = getMorphNameFromMorphFieldMetadataName({
|
||||
morphRelationFlatFieldMetadata,
|
||||
nameSingular: 'account',
|
||||
namePlural: 'accounts',
|
||||
});
|
||||
|
||||
expect(result).toBe('randomName');
|
||||
});
|
||||
});
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
import { type FieldMetadataType, RelationType } from 'twenty-shared/types';
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { searchAndReplaceLast } from 'src/engine/metadata-modules/flat-object-metadata/utils/search-and-replace-last.util';
|
||||
|
||||
export const getMorphNameFromMorphFieldMetadataName = ({
|
||||
morphRelationFlatFieldMetadata,
|
||||
nameSingular,
|
||||
namePlural,
|
||||
}: {
|
||||
morphRelationFlatFieldMetadata: Pick<
|
||||
FlatFieldMetadata<FieldMetadataType.MORPH_RELATION>,
|
||||
'name' | 'settings'
|
||||
>;
|
||||
nameSingular: string;
|
||||
namePlural: string;
|
||||
}): string => {
|
||||
const isManyToOneRelationType =
|
||||
morphRelationFlatFieldMetadata.settings.relationType ===
|
||||
RelationType.MANY_TO_ONE;
|
||||
|
||||
return searchAndReplaceLast({
|
||||
source: morphRelationFlatFieldMetadata.name,
|
||||
replace: '',
|
||||
search: isManyToOneRelationType
|
||||
? capitalize(nameSingular)
|
||||
: capitalize(namePlural),
|
||||
});
|
||||
};
|
||||
+11
-29
@@ -3,34 +3,15 @@ import {
|
||||
type FieldMetadataType,
|
||||
type FromTo,
|
||||
} from 'twenty-shared/types';
|
||||
import { computeMorphRelationFieldName } from 'twenty-shared/utils';
|
||||
|
||||
import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { computeMorphRelationFieldName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-field-name.util';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { findObjectFlatFieldMetadatasOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-object-fields-in-flat-field-metadata-maps-or-throw.util';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { getFlatObjectMetadataTargetMorphRelationFlatFieldMetadatasOrThrow } from 'src/engine/metadata-modules/flat-object-metadata/utils/get-flat-object-metadata-many-to-one-target-morph-relation-flat-field-metadatas-or-throw.util';
|
||||
|
||||
const searchAndReplaceLast = ({
|
||||
replace,
|
||||
search,
|
||||
source,
|
||||
}: {
|
||||
source: string;
|
||||
search: string;
|
||||
replace: string;
|
||||
}) => {
|
||||
const lastIndex = source.lastIndexOf(search);
|
||||
|
||||
if (lastIndex === -1) return source;
|
||||
|
||||
return (
|
||||
source.slice(0, lastIndex) +
|
||||
replace +
|
||||
source.slice(lastIndex + search.length)
|
||||
);
|
||||
};
|
||||
import { getMorphNameFromMorphFieldMetadataName } from 'src/engine/metadata-modules/flat-object-metadata/utils/get-morph-name-from-morph-field-metadata-name.util';
|
||||
|
||||
type RenameRelatedMorphFieldOnObjectNamesUpdateArgs = FromTo<
|
||||
FlatObjectMetadata,
|
||||
@@ -59,17 +40,18 @@ export const renameRelatedMorphFieldOnObjectNamesUpdate = ({
|
||||
const isManyToOneRelationType =
|
||||
morphRelationFlatFieldMetadata.settings.relationType ===
|
||||
RelationType.MANY_TO_ONE;
|
||||
const initialMorphRelationFieldName = searchAndReplaceLast({
|
||||
source: morphRelationFlatFieldMetadata.name,
|
||||
replace: '',
|
||||
search: isManyToOneRelationType
|
||||
? fromFlatObjectMetadata.nameSingular
|
||||
: fromFlatObjectMetadata.namePlural,
|
||||
});
|
||||
const initialMorphRelationFieldName =
|
||||
getMorphNameFromMorphFieldMetadataName({
|
||||
morphRelationFlatFieldMetadata,
|
||||
nameSingular: fromFlatObjectMetadata.nameSingular,
|
||||
namePlural: fromFlatObjectMetadata.namePlural,
|
||||
});
|
||||
|
||||
const newMorphFieldName = computeMorphRelationFieldName({
|
||||
fieldName: initialMorphRelationFieldName,
|
||||
relationType: morphRelationFlatFieldMetadata.settings.relationType,
|
||||
targetObjectMetadata: toFlatObjectMetadata,
|
||||
targetObjectMetadataNameSingular: toFlatObjectMetadata.nameSingular,
|
||||
targetObjectMetadataNamePlural: toFlatObjectMetadata.namePlural,
|
||||
});
|
||||
const newJoinColumnName = isManyToOneRelationType
|
||||
? computeMorphOrRelationFieldJoinColumnName({
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
export const searchAndReplaceLast = ({
|
||||
replace,
|
||||
search,
|
||||
source,
|
||||
}: {
|
||||
source: string;
|
||||
search: string;
|
||||
replace: string;
|
||||
}) => {
|
||||
const lastIndex = source.lastIndexOf(search);
|
||||
|
||||
if (lastIndex === -1) return source;
|
||||
|
||||
return (
|
||||
source.slice(0, lastIndex) +
|
||||
replace +
|
||||
source.slice(lastIndex + search.length)
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user