Optimize metadata queries (#7013)
In this PR: 1. Refactor guards to avoid duplicated queries: WorkspaceAuthGuard and UserAuthGuard only check for existence of workspace and user in the request without querying the database
This commit is contained in:
committed by
Charles Bochet
parent
cf8b1161cc
commit
523df5398a
@ -1,62 +0,0 @@
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { RelationType } from '@/settings/data-model/types/RelationType';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
RelationMetadataType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const getRelationDefinition = ({
|
||||
objectMetadataItems,
|
||||
fieldMetadataItemOnSourceRecord,
|
||||
}: {
|
||||
objectMetadataItems: ObjectMetadataItem[];
|
||||
fieldMetadataItemOnSourceRecord: FieldMetadataItem;
|
||||
}) => {
|
||||
if (fieldMetadataItemOnSourceRecord.type !== FieldMetadataType.Relation) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const relationMetadataItem =
|
||||
fieldMetadataItemOnSourceRecord.fromRelationMetadata ||
|
||||
fieldMetadataItemOnSourceRecord.toRelationMetadata;
|
||||
|
||||
if (!relationMetadataItem) return null;
|
||||
|
||||
const relationSourceFieldMetadataItemId =
|
||||
'toFieldMetadataId' in relationMetadataItem
|
||||
? relationMetadataItem.toFieldMetadataId
|
||||
: relationMetadataItem.fromFieldMetadataId;
|
||||
|
||||
if (!relationSourceFieldMetadataItemId) return null;
|
||||
|
||||
// TODO: precise naming, is it relationTypeFromTargetPointOfView or relationTypeFromSourcePointOfView ?
|
||||
const relationType =
|
||||
relationMetadataItem.relationType === RelationMetadataType.OneToMany &&
|
||||
fieldMetadataItemOnSourceRecord.toRelationMetadata
|
||||
? ('MANY_TO_ONE' satisfies RelationType)
|
||||
: (relationMetadataItem.relationType as RelationType);
|
||||
|
||||
const targetObjectMetadataNameSingular =
|
||||
'toObjectMetadata' in relationMetadataItem
|
||||
? relationMetadataItem.toObjectMetadata.nameSingular
|
||||
: relationMetadataItem.fromObjectMetadata.nameSingular;
|
||||
|
||||
const targetObjectMetadataItem = objectMetadataItems.find(
|
||||
(item) => item.nameSingular === targetObjectMetadataNameSingular,
|
||||
);
|
||||
|
||||
if (!targetObjectMetadataItem) return null;
|
||||
|
||||
const fieldMetadataItemOnTargetRecord = targetObjectMetadataItem.fields.find(
|
||||
(field) => field.id === relationSourceFieldMetadataItemId,
|
||||
);
|
||||
|
||||
if (!fieldMetadataItemOnTargetRecord) return null;
|
||||
|
||||
return {
|
||||
fieldMetadataItemOnTargetRecord,
|
||||
targetObjectMetadataItem,
|
||||
relationType,
|
||||
};
|
||||
};
|
||||
@ -1,6 +1,5 @@
|
||||
import { ApolloCache } from '@apollo/client';
|
||||
|
||||
import { getRelationDefinition } from '@/apollo/optimistic-effect/utils/getRelationDefinition';
|
||||
import { triggerAttachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerAttachRelationOptimisticEffect';
|
||||
import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect';
|
||||
import { triggerDetachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDetachRelationOptimisticEffect';
|
||||
@ -45,16 +44,23 @@ export const triggerUpdateRelationsOptimisticEffect = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const relationDefinition = getRelationDefinition({
|
||||
fieldMetadataItemOnSourceRecord,
|
||||
objectMetadataItems,
|
||||
});
|
||||
const relationDefinition =
|
||||
fieldMetadataItemOnSourceRecord.relationDefinition;
|
||||
|
||||
if (!relationDefinition) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { targetObjectMetadataItem, fieldMetadataItemOnTargetRecord } =
|
||||
relationDefinition;
|
||||
const { targetObjectMetadata, targetFieldMetadata } = relationDefinition;
|
||||
|
||||
const fullTargetObjectMetadataItem = objectMetadataItems.find(
|
||||
({ nameSingular }) =>
|
||||
nameSingular === targetObjectMetadata.nameSingular,
|
||||
);
|
||||
|
||||
if (!fullTargetObjectMetadataItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentFieldValueOnSourceRecord:
|
||||
| RecordGqlConnection
|
||||
@ -80,7 +86,7 @@ export const triggerUpdateRelationsOptimisticEffect = ({
|
||||
// it's an object record connection (we can still check it though as a safeguard)
|
||||
const currentFieldValueOnSourceRecordIsARecordConnection =
|
||||
isObjectRecordConnection(
|
||||
targetObjectMetadataItem.nameSingular,
|
||||
targetObjectMetadata.nameSingular,
|
||||
currentFieldValueOnSourceRecord,
|
||||
);
|
||||
|
||||
@ -93,7 +99,7 @@ export const triggerUpdateRelationsOptimisticEffect = ({
|
||||
|
||||
const updatedFieldValueOnSourceRecordIsARecordConnection =
|
||||
isObjectRecordConnection(
|
||||
targetObjectMetadataItem.nameSingular,
|
||||
targetObjectMetadata.nameSingular,
|
||||
updatedFieldValueOnSourceRecord,
|
||||
);
|
||||
|
||||
@ -112,13 +118,13 @@ export const triggerUpdateRelationsOptimisticEffect = ({
|
||||
// Instead of hardcoding it here
|
||||
const shouldCascadeDeleteTargetRecords =
|
||||
CORE_OBJECT_NAMES_TO_DELETE_ON_TRIGGER_RELATION_DETACH.includes(
|
||||
targetObjectMetadataItem.nameSingular as CoreObjectNameSingular,
|
||||
targetObjectMetadata.nameSingular as CoreObjectNameSingular,
|
||||
);
|
||||
|
||||
if (shouldCascadeDeleteTargetRecords) {
|
||||
triggerDeleteRecordsOptimisticEffect({
|
||||
cache,
|
||||
objectMetadataItem: targetObjectMetadataItem,
|
||||
objectMetadataItem: fullTargetObjectMetadataItem,
|
||||
recordsToDelete: targetRecordsToDetachFrom,
|
||||
objectMetadataItems,
|
||||
});
|
||||
@ -128,8 +134,8 @@ export const triggerUpdateRelationsOptimisticEffect = ({
|
||||
cache,
|
||||
sourceObjectNameSingular: sourceObjectMetadataItem.nameSingular,
|
||||
sourceRecordId: currentSourceRecord.id,
|
||||
fieldNameOnTargetRecord: fieldMetadataItemOnTargetRecord.name,
|
||||
targetObjectNameSingular: targetObjectMetadataItem.nameSingular,
|
||||
fieldNameOnTargetRecord: targetFieldMetadata.name,
|
||||
targetObjectNameSingular: targetObjectMetadata.nameSingular,
|
||||
targetRecordId: targetRecordToDetachFrom.id,
|
||||
});
|
||||
});
|
||||
@ -145,8 +151,8 @@ export const triggerUpdateRelationsOptimisticEffect = ({
|
||||
cache,
|
||||
sourceObjectNameSingular: sourceObjectMetadataItem.nameSingular,
|
||||
sourceRecordId: updatedSourceRecord.id,
|
||||
fieldNameOnTargetRecord: fieldMetadataItemOnTargetRecord.name,
|
||||
targetObjectNameSingular: targetObjectMetadataItem.nameSingular,
|
||||
fieldNameOnTargetRecord: targetFieldMetadata.name,
|
||||
targetObjectNameSingular: targetObjectMetadata.nameSingular,
|
||||
targetRecordId: targetRecordToAttachTo.id,
|
||||
}),
|
||||
);
|
||||
|
||||
@ -11,7 +11,7 @@ import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/compo
|
||||
import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle';
|
||||
import { useNavigationSection } from '@/ui/navigation/navigation-drawer/hooks/useNavigationSection';
|
||||
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { useFavorites } from '../hooks/useFavorites';
|
||||
|
||||
const StyledContainer = styled(NavigationDrawerSection)`
|
||||
@ -35,7 +35,7 @@ const StyledNavigationDrawerItem = styled(NavigationDrawerItem)`
|
||||
`;
|
||||
|
||||
export const CurrentWorkspaceMemberFavorites = () => {
|
||||
const currentUser = useRecoilValue(currentUserState);
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
|
||||
const { favorites, handleReorderFavorite } = useFavorites();
|
||||
const loading = useIsPrefetchLoading();
|
||||
@ -44,12 +44,12 @@ export const CurrentWorkspaceMemberFavorites = () => {
|
||||
useNavigationSection('Favorites');
|
||||
const isNavigationSectionOpen = useRecoilValue(isNavigationSectionOpenState);
|
||||
|
||||
if (loading && isDefined(currentUser)) {
|
||||
if (loading && isDefined(currentWorkspaceMember)) {
|
||||
return <FavoritesSkeletonLoader />;
|
||||
}
|
||||
|
||||
const currentWorkspaceMemberFavorites = favorites.filter(
|
||||
(favorite) => favorite.workspaceMemberId === currentUser?.id,
|
||||
(favorite) => favorite.workspaceMemberId === currentWorkspaceMember?.id,
|
||||
);
|
||||
|
||||
if (
|
||||
|
||||
@ -9,5 +9,6 @@ export type Favorite = {
|
||||
avatarType: AvatarType;
|
||||
link: string;
|
||||
recordId: string;
|
||||
workspaceMemberId: string;
|
||||
__typename: 'Favorite';
|
||||
};
|
||||
|
||||
@ -19,8 +19,8 @@ export const sortFavorites = (
|
||||
const relationObject = favorite[relationField.name];
|
||||
|
||||
const relationObjectNameSingular =
|
||||
relationField.toRelationMetadata?.fromObjectMetadata.nameSingular ??
|
||||
'';
|
||||
relationField.relationDefinition?.targetObjectMetadata
|
||||
.nameSingular ?? '';
|
||||
|
||||
const objectRecordIdentifier =
|
||||
getObjectRecordIdentifierByNameSingular(
|
||||
@ -38,6 +38,7 @@ export const sortFavorites = (
|
||||
link: hasLinkToShowPage
|
||||
? objectRecordIdentifier.linkToShowPage
|
||||
: '',
|
||||
workspaceMemberId: favorite.workspaceMemberId,
|
||||
} as Favorite;
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,32 +39,6 @@ export const FIND_MANY_OBJECT_METADATA_ITEMS = gql`
|
||||
isNullable
|
||||
createdAt
|
||||
updatedAt
|
||||
fromRelationMetadata {
|
||||
id
|
||||
relationType
|
||||
toObjectMetadata {
|
||||
id
|
||||
dataSourceId
|
||||
nameSingular
|
||||
namePlural
|
||||
isSystem
|
||||
isRemote
|
||||
}
|
||||
toFieldMetadataId
|
||||
}
|
||||
toRelationMetadata {
|
||||
id
|
||||
relationType
|
||||
fromObjectMetadata {
|
||||
id
|
||||
dataSourceId
|
||||
nameSingular
|
||||
namePlural
|
||||
isSystem
|
||||
isRemote
|
||||
}
|
||||
fromFieldMetadataId
|
||||
}
|
||||
defaultValue
|
||||
options
|
||||
relationDefinition {
|
||||
|
||||
@ -39,29 +39,27 @@ export const query = gql`
|
||||
isNullable
|
||||
createdAt
|
||||
updatedAt
|
||||
fromRelationMetadata {
|
||||
id
|
||||
relationType
|
||||
toObjectMetadata {
|
||||
relationDefinition {
|
||||
relationId
|
||||
direction
|
||||
sourceObjectMetadata {
|
||||
id
|
||||
dataSourceId
|
||||
nameSingular
|
||||
namePlural
|
||||
isSystem
|
||||
}
|
||||
toFieldMetadataId
|
||||
}
|
||||
toRelationMetadata {
|
||||
id
|
||||
relationType
|
||||
fromObjectMetadata {
|
||||
sourceFieldMetadata {
|
||||
id
|
||||
name
|
||||
}
|
||||
targetObjectMetadata {
|
||||
id
|
||||
dataSourceId
|
||||
nameSingular
|
||||
namePlural
|
||||
isSystem
|
||||
}
|
||||
fromFieldMetadataId
|
||||
targetFieldMetadata {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
defaultValue
|
||||
options
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { ReactNode } from 'react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
|
||||
import { useCreateOneRelationMetadataItem } from '@/object-metadata/hooks/useCreateOneRelationMetadataItem';
|
||||
import { RelationMetadataType } from '~/generated/graphql';
|
||||
import { RelationDefinitionType } from '~/generated/graphql';
|
||||
|
||||
import {
|
||||
query,
|
||||
@ -42,7 +42,7 @@ describe('useCreateOneRelationMetadataItem', () => {
|
||||
|
||||
await act(async () => {
|
||||
const res = await result.current.createOneRelationMetadataItem({
|
||||
relationType: RelationMetadataType.OneToOne,
|
||||
relationType: RelationDefinitionType.OneToOne,
|
||||
field: {
|
||||
label: 'label',
|
||||
},
|
||||
|
||||
@ -1,11 +1,7 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector';
|
||||
import { RelationType } from '@/settings/data-model/types/RelationType';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
RelationMetadataType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
import { FieldMetadataItem } from '../types/FieldMetadataItem';
|
||||
|
||||
@ -17,39 +13,19 @@ export const useGetRelationMetadata = () =>
|
||||
}: {
|
||||
fieldMetadataItem: Pick<
|
||||
FieldMetadataItem,
|
||||
'fromRelationMetadata' | 'toRelationMetadata' | 'type'
|
||||
'type' | 'relationDefinition'
|
||||
>;
|
||||
}) => {
|
||||
if (fieldMetadataItem.type !== FieldMetadataType.Relation) return null;
|
||||
|
||||
const relationMetadata =
|
||||
fieldMetadataItem.fromRelationMetadata ||
|
||||
fieldMetadataItem.toRelationMetadata;
|
||||
const relationDefinition = fieldMetadataItem.relationDefinition;
|
||||
|
||||
if (!relationMetadata) return null;
|
||||
|
||||
const relationFieldMetadataId =
|
||||
'toFieldMetadataId' in relationMetadata
|
||||
? relationMetadata.toFieldMetadataId
|
||||
: relationMetadata.fromFieldMetadataId;
|
||||
|
||||
if (!relationFieldMetadataId) return null;
|
||||
|
||||
const relationType =
|
||||
relationMetadata.relationType === RelationMetadataType.OneToMany &&
|
||||
fieldMetadataItem.toRelationMetadata
|
||||
? 'MANY_TO_ONE'
|
||||
: (relationMetadata.relationType as RelationType);
|
||||
|
||||
const relationObjectMetadataNameSingular =
|
||||
'toObjectMetadata' in relationMetadata
|
||||
? relationMetadata.toObjectMetadata.nameSingular
|
||||
: relationMetadata.fromObjectMetadata.nameSingular;
|
||||
if (!relationDefinition) return null;
|
||||
|
||||
const relationObjectMetadataItem = snapshot
|
||||
.getLoadable(
|
||||
objectMetadataItemFamilySelector({
|
||||
objectName: relationObjectMetadataNameSingular,
|
||||
objectName: relationDefinition.targetObjectMetadata.nameSingular,
|
||||
objectNameType: 'singular',
|
||||
}),
|
||||
)
|
||||
@ -59,7 +35,7 @@ export const useGetRelationMetadata = () =>
|
||||
|
||||
const relationFieldMetadataItem =
|
||||
relationObjectMetadataItem.fields.find(
|
||||
(field) => field.id === relationFieldMetadataId,
|
||||
(field) => field.id === relationDefinition.targetFieldMetadata.id,
|
||||
);
|
||||
|
||||
if (!relationFieldMetadataItem) return null;
|
||||
@ -67,7 +43,7 @@ export const useGetRelationMetadata = () =>
|
||||
return {
|
||||
relationFieldMetadataItem,
|
||||
relationObjectMetadataItem,
|
||||
relationType,
|
||||
relationType: relationDefinition.direction,
|
||||
};
|
||||
},
|
||||
[],
|
||||
|
||||
@ -3,7 +3,6 @@ import { ThemeColor } from 'twenty-ui';
|
||||
import {
|
||||
Field,
|
||||
Object as MetadataObject,
|
||||
Relation,
|
||||
RelationDefinition,
|
||||
RelationDefinitionType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
@ -18,31 +17,9 @@ export type FieldMetadataItemOption = {
|
||||
|
||||
export type FieldMetadataItem = Omit<
|
||||
Field,
|
||||
| '__typename'
|
||||
| 'fromRelationMetadata'
|
||||
| 'toRelationMetadata'
|
||||
| 'defaultValue'
|
||||
| 'options'
|
||||
| 'settings'
|
||||
| 'relationDefinition'
|
||||
'__typename' | 'defaultValue' | 'options' | 'settings' | 'relationDefinition'
|
||||
> & {
|
||||
__typename?: string;
|
||||
fromRelationMetadata?:
|
||||
| (Pick<Relation, 'id' | 'toFieldMetadataId' | 'relationType'> & {
|
||||
toObjectMetadata: Pick<
|
||||
Relation['toObjectMetadata'],
|
||||
'id' | 'nameSingular' | 'namePlural' | 'isSystem' | 'isRemote'
|
||||
>;
|
||||
})
|
||||
| null;
|
||||
toRelationMetadata?:
|
||||
| (Pick<Relation, 'id' | 'fromFieldMetadataId' | 'relationType'> & {
|
||||
fromObjectMetadata: Pick<
|
||||
Relation['fromObjectMetadata'],
|
||||
'id' | 'nameSingular' | 'namePlural' | 'isSystem' | 'isRemote'
|
||||
>;
|
||||
})
|
||||
| null;
|
||||
defaultValue?: any;
|
||||
options?: FieldMetadataItemOption[] | null;
|
||||
relationDefinition?: {
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { parseFieldRelationType } from '@/object-metadata/utils/parseFieldRelationType';
|
||||
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { getFieldButtonIcon } from '@/object-record/record-field/utils/getFieldButtonIcon';
|
||||
@ -20,17 +19,15 @@ export const formatFieldMetadataItemAsFieldDefinition = ({
|
||||
labelWidth,
|
||||
}: FieldMetadataItemAsFieldDefinitionProps): FieldDefinition<FieldMetadata> => {
|
||||
const relationObjectMetadataItem =
|
||||
field.toRelationMetadata?.fromObjectMetadata ||
|
||||
field.fromRelationMetadata?.toObjectMetadata;
|
||||
field.relationDefinition?.targetObjectMetadata;
|
||||
|
||||
const relationFieldMetadataId =
|
||||
field.toRelationMetadata?.fromFieldMetadataId ||
|
||||
field.fromRelationMetadata?.toFieldMetadataId;
|
||||
field.relationDefinition?.targetFieldMetadata.id;
|
||||
|
||||
const fieldDefintionMetadata = {
|
||||
fieldName: field.name,
|
||||
placeHolder: field.label,
|
||||
relationType: parseFieldRelationType(field),
|
||||
relationType: field.relationDefinition?.direction,
|
||||
relationFieldMetadataId,
|
||||
relationObjectMetadataNameSingular:
|
||||
relationObjectMetadataItem?.nameSingular ?? '',
|
||||
|
||||
@ -2,6 +2,7 @@ import { RelationType } from '@/settings/data-model/types/RelationType';
|
||||
import {
|
||||
CreateRelationInput,
|
||||
Field,
|
||||
RelationDefinitionType,
|
||||
RelationMetadataType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
@ -24,8 +25,8 @@ export const formatRelationMetadataInput = (
|
||||
// => Transform into ONE_TO_MANY and invert "from" and "to" data.
|
||||
const isManyToOne = input.relationType === 'MANY_TO_ONE';
|
||||
const relationType = isManyToOne
|
||||
? RelationMetadataType.OneToMany
|
||||
: (input.relationType as RelationMetadataType);
|
||||
? RelationDefinitionType.OneToMany
|
||||
: (input.relationType as RelationDefinitionType);
|
||||
const { field: fromField, objectMetadataId: fromObjectMetadataId } =
|
||||
isManyToOne ? input.connect : input;
|
||||
const { field: toField, objectMetadataId: toObjectMetadataId } = isManyToOne
|
||||
@ -51,7 +52,7 @@ export const formatRelationMetadataInput = (
|
||||
fromLabel,
|
||||
fromName,
|
||||
fromObjectMetadataId,
|
||||
relationType,
|
||||
relationType: relationType as unknown as RelationMetadataType,
|
||||
toDescription,
|
||||
toIcon,
|
||||
toLabel,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -4,7 +4,7 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
RelationMetadataType,
|
||||
RelationDefinitionType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
import { FieldMetadataItem } from '../types/FieldMetadataItem';
|
||||
@ -17,10 +17,7 @@ export const mapFieldMetadataToGraphQLQuery = ({
|
||||
computeReferences = false,
|
||||
}: {
|
||||
objectMetadataItems: ObjectMetadataItem[];
|
||||
field: Pick<
|
||||
FieldMetadataItem,
|
||||
'name' | 'type' | 'toRelationMetadata' | 'fromRelationMetadata'
|
||||
>;
|
||||
field: Pick<FieldMetadataItem, 'name' | 'type' | 'relationDefinition'>;
|
||||
relationrecordFields?: Record<string, any>;
|
||||
computeReferences?: boolean;
|
||||
}): any => {
|
||||
@ -49,12 +46,12 @@ export const mapFieldMetadataToGraphQLQuery = ({
|
||||
|
||||
if (
|
||||
fieldType === FieldMetadataType.Relation &&
|
||||
field.toRelationMetadata?.relationType === RelationMetadataType.OneToMany
|
||||
field.relationDefinition?.direction === RelationDefinitionType.ManyToOne
|
||||
) {
|
||||
const relationMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.id ===
|
||||
(field.toRelationMetadata as any)?.fromObjectMetadata?.id,
|
||||
field.relationDefinition?.targetObjectMetadata.id,
|
||||
);
|
||||
|
||||
if (isUndefined(relationMetadataItem)) {
|
||||
@ -73,12 +70,12 @@ ${mapObjectMetadataToGraphQLQuery({
|
||||
|
||||
if (
|
||||
fieldType === FieldMetadataType.Relation &&
|
||||
field.fromRelationMetadata?.relationType === RelationMetadataType.OneToMany
|
||||
field.relationDefinition?.direction === RelationDefinitionType.OneToMany
|
||||
) {
|
||||
const relationMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.id ===
|
||||
(field.fromRelationMetadata as any)?.toObjectMetadata?.id,
|
||||
field.relationDefinition?.targetObjectMetadata.id,
|
||||
);
|
||||
|
||||
if (isUndefined(relationMetadataItem)) {
|
||||
|
||||
@ -1,55 +0,0 @@
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { FieldDefinitionRelationType } from '@/object-record/record-field/types/FieldDefinition';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
RelationMetadataType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const parseFieldRelationType = (
|
||||
field: FieldMetadataItem | undefined,
|
||||
): FieldDefinitionRelationType | undefined => {
|
||||
if (!field || field.type !== FieldMetadataType.Relation) return;
|
||||
|
||||
const config: Record<
|
||||
RelationMetadataType,
|
||||
{ from: FieldDefinitionRelationType; to: FieldDefinitionRelationType }
|
||||
> = {
|
||||
[RelationMetadataType.ManyToMany]: {
|
||||
from: 'FROM_MANY_OBJECTS',
|
||||
to: 'TO_MANY_OBJECTS',
|
||||
},
|
||||
[RelationMetadataType.OneToMany]: {
|
||||
from: 'FROM_MANY_OBJECTS',
|
||||
to: 'TO_ONE_OBJECT',
|
||||
},
|
||||
[RelationMetadataType.ManyToOne]: {
|
||||
from: 'TO_ONE_OBJECT',
|
||||
to: 'FROM_MANY_OBJECTS',
|
||||
},
|
||||
[RelationMetadataType.OneToOne]: {
|
||||
from: 'FROM_ONE_OBJECT',
|
||||
to: 'TO_ONE_OBJECT',
|
||||
},
|
||||
};
|
||||
|
||||
if (
|
||||
isDefined(field.fromRelationMetadata) &&
|
||||
field.fromRelationMetadata.relationType in config
|
||||
) {
|
||||
return config[field.fromRelationMetadata.relationType].from;
|
||||
}
|
||||
|
||||
if (
|
||||
isDefined(field.toRelationMetadata) &&
|
||||
field.toRelationMetadata.relationType in config
|
||||
) {
|
||||
return config[field.toRelationMetadata.relationType].to;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Cannot determine field relation type for field : ${JSON.stringify(
|
||||
field,
|
||||
)}.`,
|
||||
);
|
||||
};
|
||||
@ -6,7 +6,6 @@ import { metadataLabelSchema } from '@/object-metadata/validation-schemas/metada
|
||||
import {
|
||||
FieldMetadataType,
|
||||
RelationDefinitionType,
|
||||
RelationMetadataType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { camelCaseStringSchema } from '~/utils/validation-schemas/camelCaseStringSchema';
|
||||
|
||||
@ -16,24 +15,6 @@ export const fieldMetadataItemSchema = (existingLabels?: string[]) => {
|
||||
createdAt: z.string().datetime(),
|
||||
defaultValue: z.any().optional(),
|
||||
description: z.string().trim().nullable().optional(),
|
||||
fromRelationMetadata: z
|
||||
.object({
|
||||
__typename: z.literal('relation').optional(),
|
||||
id: z.string().uuid(),
|
||||
relationType: z.nativeEnum(RelationMetadataType),
|
||||
toFieldMetadataId: z.string().uuid(),
|
||||
toObjectMetadata: z.object({
|
||||
__typename: z.literal('object').optional(),
|
||||
dataSourceId: z.string().uuid(),
|
||||
id: z.string().uuid(),
|
||||
isRemote: z.boolean(),
|
||||
isSystem: z.boolean(),
|
||||
namePlural: z.string().trim().min(1),
|
||||
nameSingular: z.string().trim().min(1),
|
||||
}),
|
||||
})
|
||||
.nullable()
|
||||
.optional(),
|
||||
icon: z.string().startsWith('Icon').trim().nullable(),
|
||||
id: z.string().uuid(),
|
||||
isActive: z.boolean(),
|
||||
@ -84,24 +65,6 @@ export const fieldMetadataItemSchema = (existingLabels?: string[]) => {
|
||||
})
|
||||
.nullable()
|
||||
.optional(),
|
||||
toRelationMetadata: z
|
||||
.object({
|
||||
__typename: z.literal('relation').optional(),
|
||||
id: z.string().uuid(),
|
||||
relationType: z.nativeEnum(RelationMetadataType),
|
||||
fromFieldMetadataId: z.string().uuid(),
|
||||
fromObjectMetadata: z.object({
|
||||
__typename: z.literal('object').optional(),
|
||||
id: z.string().uuid(),
|
||||
dataSourceId: z.string().uuid(),
|
||||
isRemote: z.boolean(),
|
||||
isSystem: z.boolean(),
|
||||
namePlural: z.string().trim().min(1),
|
||||
nameSingular: z.string().trim().min(1),
|
||||
}),
|
||||
})
|
||||
.nullable()
|
||||
.optional(),
|
||||
type: z.nativeEnum(FieldMetadataType),
|
||||
updatedAt: z.string().datetime(),
|
||||
}) satisfies z.ZodType<FieldMetadataItem>;
|
||||
|
||||
@ -4,17 +4,6 @@ import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
import { FieldMetadata } from './FieldMetadata';
|
||||
|
||||
export type FieldDefinitionRelationType =
|
||||
| 'FROM_MANY_OBJECTS'
|
||||
| 'FROM_ONE_OBJECT'
|
||||
| 'TO_MANY_OBJECTS'
|
||||
| 'TO_ONE_OBJECT';
|
||||
|
||||
export type RelationDirections = {
|
||||
from: FieldDefinitionRelationType;
|
||||
to: FieldDefinitionRelationType;
|
||||
};
|
||||
|
||||
export type FieldDefinition<T extends FieldMetadata> = {
|
||||
fieldMetadataId: string;
|
||||
label: string;
|
||||
|
||||
@ -3,8 +3,8 @@ import { ThemeColor } from 'twenty-ui';
|
||||
import { RATING_VALUES } from '@/object-record/record-field/meta-types/constants/RatingValues';
|
||||
import { ZodHelperLiteral } from '@/object-record/record-field/types/ZodHelperLiteral';
|
||||
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
|
||||
import { WithNarrowedStringLiteralProperty } from '~/types/WithNarrowedStringLiteralProperty';
|
||||
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
import { CurrencyCode } from './CurrencyCode';
|
||||
|
||||
export type FieldUuidMetadata = {
|
||||
@ -110,35 +110,17 @@ export type FieldPositionMetadata = {
|
||||
fieldName: string;
|
||||
};
|
||||
|
||||
export type FieldDefinitionRelationType =
|
||||
| 'FROM_MANY_OBJECTS'
|
||||
| 'FROM_ONE_OBJECT'
|
||||
| 'TO_MANY_OBJECTS'
|
||||
| 'TO_ONE_OBJECT';
|
||||
|
||||
export type FieldRelationMetadata = {
|
||||
fieldName: string;
|
||||
objectMetadataNameSingular?: string;
|
||||
relationFieldMetadataId: string;
|
||||
relationObjectMetadataNamePlural: string;
|
||||
relationObjectMetadataNameSingular: string;
|
||||
relationType?: FieldDefinitionRelationType;
|
||||
relationType?: RelationDefinitionType;
|
||||
targetFieldMetadataName?: string;
|
||||
useEditButton?: boolean;
|
||||
};
|
||||
|
||||
export type FieldRelationOneMetadata = WithNarrowedStringLiteralProperty<
|
||||
FieldRelationMetadata,
|
||||
'relationType',
|
||||
'TO_ONE_OBJECT'
|
||||
>;
|
||||
|
||||
export type FieldRelationManyMetadata = WithNarrowedStringLiteralProperty<
|
||||
FieldRelationMetadata,
|
||||
'relationType',
|
||||
'FROM_MANY_OBJECTS'
|
||||
>;
|
||||
|
||||
export type FieldSelectMetadata = {
|
||||
objectMetadataNameSingular?: string;
|
||||
fieldName: string;
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation';
|
||||
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
import { FieldDefinition } from '../FieldDefinition';
|
||||
import { FieldMetadata, FieldRelationManyMetadata } from '../FieldMetadata';
|
||||
import { FieldMetadata } from '../FieldMetadata';
|
||||
|
||||
export const isFieldRelationFromManyObjects = (
|
||||
field: Pick<FieldDefinition<FieldMetadata>, 'type' | 'metadata'>,
|
||||
): field is FieldDefinition<FieldRelationManyMetadata> =>
|
||||
isFieldRelation(field) && field.metadata.relationType === 'FROM_MANY_OBJECTS';
|
||||
): field is FieldDefinition<FieldMetadata> =>
|
||||
isFieldRelation(field) &&
|
||||
field.metadata.relationType === RelationDefinitionType.OneToMany;
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation';
|
||||
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
import { FieldDefinition } from '../FieldDefinition';
|
||||
import { FieldMetadata, FieldRelationOneMetadata } from '../FieldMetadata';
|
||||
import { FieldMetadata } from '../FieldMetadata';
|
||||
|
||||
export const isFieldRelationToOneObject = (
|
||||
field: Pick<FieldDefinition<FieldMetadata>, 'type' | 'metadata'>,
|
||||
): field is FieldDefinition<FieldRelationOneMetadata> =>
|
||||
isFieldRelation(field) && field.metadata.relationType === 'TO_ONE_OBJECT';
|
||||
): field is FieldDefinition<FieldMetadata> =>
|
||||
isFieldRelation(field) &&
|
||||
field.metadata.relationType === RelationDefinitionType.ManyToOne;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
import {
|
||||
csvDownloader,
|
||||
displayedExportProgress,
|
||||
@ -35,7 +36,10 @@ describe('generateCsv', () => {
|
||||
{ label: 'Nested', metadata: { fieldName: 'nested' } },
|
||||
{
|
||||
label: 'Relation',
|
||||
metadata: { fieldName: 'relation', relationType: 'TO_ONE_OBJECT' },
|
||||
metadata: {
|
||||
fieldName: 'relation',
|
||||
relationType: RelationDefinitionType.ManyToOne,
|
||||
},
|
||||
},
|
||||
] as ColumnDefinition<FieldMetadata>[];
|
||||
const rows = [
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
} from '@/object-record/record-index/options/hooks/useTableData';
|
||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
@ -43,7 +44,7 @@ export const generateCsv: GenerateExport = ({
|
||||
const columnsToExport = columns.filter(
|
||||
(col) =>
|
||||
!('relationType' in col.metadata && col.metadata.relationType) ||
|
||||
col.metadata.relationType === 'TO_ONE_OBJECT',
|
||||
col.metadata.relationType === RelationDefinitionType.ManyToOne,
|
||||
);
|
||||
|
||||
const objectIdColumn: ColumnDefinition<FieldMetadata> = {
|
||||
|
||||
@ -7,6 +7,7 @@ import { Task } from '@/activities/types/Task';
|
||||
import { InformationBannerDeletedRecord } from '@/information-banner/components/deleted-record/InformationBannerDeletedRecord';
|
||||
import { useLabelIdentifierFieldMetadataItem } from '@/object-metadata/hooks/useLabelIdentifierFieldMetadataItem';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
@ -56,6 +57,8 @@ export const RecordShowContainer = ({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
const { labelIdentifierFieldMetadataItem } =
|
||||
useLabelIdentifierFieldMetadataItem({
|
||||
objectNameSingular,
|
||||
@ -119,7 +122,7 @@ export const RecordShowContainer = ({
|
||||
const availableFieldMetadataItems = objectMetadataItem.fields
|
||||
.filter(
|
||||
(fieldMetadataItem) =>
|
||||
isFieldCellSupported(fieldMetadataItem) &&
|
||||
isFieldCellSupported(fieldMetadataItem, objectMetadataItems) &&
|
||||
fieldMetadataItem.id !== labelIdentifierFieldMetadataItem?.id,
|
||||
)
|
||||
.sort((fieldMetadataItemA, fieldMetadataItemB) =>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useCallback, useContext } from 'react';
|
||||
import { css } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useCallback, useContext } from 'react';
|
||||
import {
|
||||
IconChevronDown,
|
||||
IconComponent,
|
||||
@ -11,6 +11,7 @@ import {
|
||||
} from 'twenty-ui';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||
import { RecordChip } from '@/object-record/components/RecordChip';
|
||||
@ -37,6 +38,7 @@ import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
import { AnimatedEaseInOut } from '@/ui/utilities/animation/components/AnimatedEaseInOut';
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledListItem = styled(RecordDetailRecordsListItem)<{
|
||||
isDropdownOpen?: boolean;
|
||||
@ -89,12 +91,14 @@ export const RecordDetailRelationRecordsListItem = ({
|
||||
relationType,
|
||||
} = fieldDefinition.metadata as FieldRelationMetadata;
|
||||
|
||||
const isToOneObject = relationType === 'TO_ONE_OBJECT';
|
||||
const isToOneObject = relationType === RelationDefinitionType.ManyToOne;
|
||||
const { objectMetadataItem: relationObjectMetadataItem } =
|
||||
useObjectMetadataItem({
|
||||
objectNameSingular: relationObjectMetadataNameSingular,
|
||||
});
|
||||
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
const persistField = usePersistField();
|
||||
|
||||
const { updateOneRecord: updateOneRelationRecord } = useUpdateOneRecord({
|
||||
@ -111,7 +115,7 @@ export const RecordDetailRelationRecordsListItem = ({
|
||||
const availableRelationFieldMetadataItems = relationObjectMetadataItem.fields
|
||||
.filter(
|
||||
(fieldMetadataItem) =>
|
||||
isFieldCellSupported(fieldMetadataItem) &&
|
||||
isFieldCellSupported(fieldMetadataItem, objectMetadataItems) &&
|
||||
fieldMetadataItem.id !==
|
||||
relationObjectMetadataItem.labelIdentifierFieldMetadataId &&
|
||||
fieldMetadataItem.id !== relationFieldMetadataId,
|
||||
|
||||
@ -32,6 +32,7 @@ import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
||||
import { FilterQueryParams } from '@/views/hooks/internal/useViewFromQueryParams';
|
||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
|
||||
type RecordDetailRelationSectionProps = {
|
||||
loading: boolean;
|
||||
@ -67,8 +68,8 @@ export const RecordDetailRelationSection = ({
|
||||
>(recordStoreFamilySelector({ recordId, fieldName }));
|
||||
|
||||
// TODO: use new relation type
|
||||
const isToOneObject = relationType === 'TO_ONE_OBJECT';
|
||||
const isFromManyObjects = relationType === 'FROM_MANY_OBJECTS';
|
||||
const isToOneObject = relationType === RelationDefinitionType.ManyToOne;
|
||||
const isToManyObjects = RelationDefinitionType.OneToMany;
|
||||
|
||||
const relationRecords: ObjectRecord[] =
|
||||
fieldValue && isToOneObject
|
||||
@ -160,7 +161,7 @@ export const RecordDetailRelationSection = ({
|
||||
<RecordDetailSectionHeader
|
||||
title={fieldDefinition.label}
|
||||
link={
|
||||
isFromManyObjects
|
||||
isToManyObjects
|
||||
? {
|
||||
to: filterLinkHref,
|
||||
label: `All (${relationRecords.length})`,
|
||||
|
||||
@ -34,22 +34,6 @@ export const mockPerformance = {
|
||||
isNullable: true,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: {
|
||||
__typename: 'relation',
|
||||
id: '0cf72416-3d94-4d94-abf3-7dc9d734435b',
|
||||
relationType: 'ONE_TO_MANY',
|
||||
fromObjectMetadata: {
|
||||
__typename: 'object',
|
||||
id: '79c2d29c-76f6-432f-91c9-df1259b73d95',
|
||||
dataSourceId: '0fd9fd54-0e8d-4f78-911c-76b33436a768',
|
||||
nameSingular: 'company',
|
||||
namePlural: 'companies',
|
||||
isSystem: false,
|
||||
isRemote: false,
|
||||
},
|
||||
fromFieldMetadataId: '7b281010-5f47-4771-b3f5-f4bcd24ed1b5',
|
||||
},
|
||||
defaultValue: null,
|
||||
options: null,
|
||||
relationDefinition: {
|
||||
@ -94,8 +78,6 @@ export const mockPerformance = {
|
||||
isNullable: false,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: "''",
|
||||
options: null,
|
||||
relationDefinition: null,
|
||||
@ -114,8 +96,6 @@ export const mockPerformance = {
|
||||
isNullable: false,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: "''",
|
||||
options: null,
|
||||
relationDefinition: null,
|
||||
@ -134,22 +114,6 @@ export const mockPerformance = {
|
||||
isNullable: true,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: {
|
||||
__typename: 'relation',
|
||||
id: 'd76f949d-023d-4b45-a71e-f39e3b1562ba',
|
||||
relationType: 'ONE_TO_MANY',
|
||||
toObjectMetadata: {
|
||||
__typename: 'object',
|
||||
id: '82222ca2-dd40-44ec-b8c5-eb0eca9ec625',
|
||||
dataSourceId: '0fd9fd54-0e8d-4f78-911c-76b33436a768',
|
||||
nameSingular: 'activityTarget',
|
||||
namePlural: 'activityTargets',
|
||||
isSystem: true,
|
||||
isRemote: false,
|
||||
},
|
||||
toFieldMetadataId: 'f5f515cc-6d8a-44c3-b2d4-f04b9868a9c5',
|
||||
},
|
||||
toRelationMetadata: null,
|
||||
defaultValue: null,
|
||||
options: null,
|
||||
relationDefinition: {
|
||||
@ -194,22 +158,6 @@ export const mockPerformance = {
|
||||
isNullable: true,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: {
|
||||
__typename: 'relation',
|
||||
id: 'a5a61d23-8ac9-4014-9441-ec3a1781a661',
|
||||
relationType: 'ONE_TO_MANY',
|
||||
toObjectMetadata: {
|
||||
__typename: 'object',
|
||||
id: '494b9b7c-a44e-4d52-b274-cdfb0e322165',
|
||||
dataSourceId: '0fd9fd54-0e8d-4f78-911c-76b33436a768',
|
||||
nameSingular: 'opportunity',
|
||||
namePlural: 'opportunities',
|
||||
isSystem: false,
|
||||
isRemote: false,
|
||||
},
|
||||
toFieldMetadataId: '86559a6f-6afc-4d5c-9bed-fc74d063791b',
|
||||
},
|
||||
toRelationMetadata: null,
|
||||
defaultValue: null,
|
||||
options: null,
|
||||
relationDefinition: {
|
||||
@ -254,22 +202,6 @@ export const mockPerformance = {
|
||||
isNullable: true,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: {
|
||||
__typename: 'relation',
|
||||
id: '456f7875-b48c-4795-a0c7-a69d7339afee',
|
||||
relationType: 'ONE_TO_MANY',
|
||||
toObjectMetadata: {
|
||||
__typename: 'object',
|
||||
id: 'eba13fca-57b7-470c-8c23-a0e640e04ffb',
|
||||
dataSourceId: '0fd9fd54-0e8d-4f78-911c-76b33436a768',
|
||||
nameSingular: 'calendarEventParticipant',
|
||||
namePlural: 'calendarEventParticipants',
|
||||
isSystem: true,
|
||||
isRemote: false,
|
||||
},
|
||||
toFieldMetadataId: 'c1cdebda-b514-4487-9b9c-aa59d8fca8eb',
|
||||
},
|
||||
toRelationMetadata: null,
|
||||
defaultValue: null,
|
||||
options: null,
|
||||
relationDefinition: {
|
||||
@ -314,8 +246,6 @@ export const mockPerformance = {
|
||||
isNullable: false,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: 'now',
|
||||
options: null,
|
||||
relationDefinition: null,
|
||||
@ -334,22 +264,6 @@ export const mockPerformance = {
|
||||
isNullable: true,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: {
|
||||
__typename: 'relation',
|
||||
id: '31542774-fb15-4d01-b00b-8fc94887f458',
|
||||
relationType: 'ONE_TO_MANY',
|
||||
toObjectMetadata: {
|
||||
__typename: 'object',
|
||||
id: 'f08422e2-14cd-4966-9cd3-bce0302cc56f',
|
||||
dataSourceId: '0fd9fd54-0e8d-4f78-911c-76b33436a768',
|
||||
nameSingular: 'favorite',
|
||||
namePlural: 'favorites',
|
||||
isSystem: true,
|
||||
isRemote: false,
|
||||
},
|
||||
toFieldMetadataId: '67d28b17-ff3c-49b4-a6da-1354be9634b0',
|
||||
},
|
||||
toRelationMetadata: null,
|
||||
defaultValue: null,
|
||||
options: null,
|
||||
relationDefinition: {
|
||||
@ -394,8 +308,6 @@ export const mockPerformance = {
|
||||
isNullable: true,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: {
|
||||
primaryLinkUrl: "''",
|
||||
primaryLinkLabel: "''",
|
||||
@ -417,22 +329,6 @@ export const mockPerformance = {
|
||||
isNullable: true,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: {
|
||||
__typename: 'relation',
|
||||
id: 'c0cc3456-afa4-46e0-820d-2db0b63a8273',
|
||||
relationType: 'ONE_TO_MANY',
|
||||
toObjectMetadata: {
|
||||
__typename: 'object',
|
||||
id: '0e3c9a9d-8a60-4671-a466-7b840a422da2',
|
||||
dataSourceId: '0fd9fd54-0e8d-4f78-911c-76b33436a768',
|
||||
nameSingular: 'attachment',
|
||||
namePlural: 'attachments',
|
||||
isSystem: true,
|
||||
isRemote: false,
|
||||
},
|
||||
toFieldMetadataId: 'a920a0d6-8e71-4ab8-90b9-ab540e04732a',
|
||||
},
|
||||
toRelationMetadata: null,
|
||||
defaultValue: null,
|
||||
options: null,
|
||||
relationDefinition: {
|
||||
@ -477,8 +373,6 @@ export const mockPerformance = {
|
||||
isNullable: false,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: "''",
|
||||
options: null,
|
||||
relationDefinition: null,
|
||||
@ -497,8 +391,6 @@ export const mockPerformance = {
|
||||
isNullable: true,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: null,
|
||||
options: null,
|
||||
relationDefinition: null,
|
||||
@ -517,8 +409,6 @@ export const mockPerformance = {
|
||||
isNullable: false,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: "''",
|
||||
options: null,
|
||||
relationDefinition: null,
|
||||
@ -537,8 +427,6 @@ export const mockPerformance = {
|
||||
isNullable: false,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: "''",
|
||||
options: null,
|
||||
relationDefinition: null,
|
||||
@ -557,8 +445,6 @@ export const mockPerformance = {
|
||||
isNullable: false,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: 'now',
|
||||
options: null,
|
||||
relationDefinition: null,
|
||||
@ -577,8 +463,6 @@ export const mockPerformance = {
|
||||
isNullable: true,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: null,
|
||||
options: null,
|
||||
relationDefinition: null,
|
||||
@ -597,22 +481,6 @@ export const mockPerformance = {
|
||||
isNullable: true,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: {
|
||||
__typename: 'relation',
|
||||
id: '25150feb-fcd7-407e-b5fa-ffe58a0450ac',
|
||||
relationType: 'ONE_TO_MANY',
|
||||
toObjectMetadata: {
|
||||
__typename: 'object',
|
||||
id: '83b5ff3e-975e-4dc9-ba4d-c645a0d8afb2',
|
||||
dataSourceId: '0fd9fd54-0e8d-4f78-911c-76b33436a768',
|
||||
nameSingular: 'timelineActivity',
|
||||
namePlural: 'timelineActivities',
|
||||
isSystem: true,
|
||||
isRemote: false,
|
||||
},
|
||||
toFieldMetadataId: '556a12d4-ef0a-4232-963f-0f317f4c5ef5',
|
||||
},
|
||||
toRelationMetadata: null,
|
||||
defaultValue: null,
|
||||
options: null,
|
||||
relationDefinition: {
|
||||
@ -657,8 +525,6 @@ export const mockPerformance = {
|
||||
isNullable: true,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: {
|
||||
lastName: "''",
|
||||
firstName: "''",
|
||||
@ -680,8 +546,6 @@ export const mockPerformance = {
|
||||
isNullable: true,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: {
|
||||
primaryLinkUrl: "''",
|
||||
primaryLinkLabel: "''",
|
||||
@ -703,22 +567,6 @@ export const mockPerformance = {
|
||||
isNullable: true,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: {
|
||||
__typename: 'relation',
|
||||
id: 'e2eb7156-6e65-4bf8-922b-670179744f27',
|
||||
relationType: 'ONE_TO_MANY',
|
||||
toObjectMetadata: {
|
||||
__typename: 'object',
|
||||
id: 'ffd8e640-84b7-4ed6-99e9-14def0f9d82b',
|
||||
dataSourceId: '0fd9fd54-0e8d-4f78-911c-76b33436a768',
|
||||
nameSingular: 'messageParticipant',
|
||||
namePlural: 'messageParticipants',
|
||||
isSystem: true,
|
||||
isRemote: false,
|
||||
},
|
||||
toFieldMetadataId: '8c4593a1-ad40-4681-92fe-43ad4fe60205',
|
||||
},
|
||||
toRelationMetadata: null,
|
||||
defaultValue: null,
|
||||
options: null,
|
||||
relationDefinition: {
|
||||
@ -763,8 +611,6 @@ export const mockPerformance = {
|
||||
isNullable: false,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: 'uuid',
|
||||
options: null,
|
||||
relationDefinition: null,
|
||||
|
||||
@ -6,7 +6,10 @@ import { useOpenSpreadsheetImportDialog } from '@/spreadsheet-import/hooks/useOp
|
||||
import { SpreadsheetImportDialogOptions } from '@/spreadsheet-import/types';
|
||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
RelationDefinitionType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const useOpenObjectRecordsSpreasheetImportDialog = (
|
||||
objectNameSingular: string,
|
||||
@ -37,7 +40,8 @@ export const useOpenObjectRecordsSpreasheetImportDialog = (
|
||||
(!fieldMetadataItem.isSystem || fieldMetadataItem.name === 'id') &&
|
||||
fieldMetadataItem.name !== 'createdAt' &&
|
||||
(fieldMetadataItem.type !== FieldMetadataType.Relation ||
|
||||
fieldMetadataItem.toRelationMetadata),
|
||||
fieldMetadataItem.relationDefinition?.direction ===
|
||||
RelationDefinitionType.ManyToOne),
|
||||
)
|
||||
.sort((fieldMetadataItemA, fieldMetadataItemB) =>
|
||||
fieldMetadataItemA.name.localeCompare(fieldMetadataItemB.name),
|
||||
|
||||
@ -2,14 +2,14 @@ import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'
|
||||
import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation';
|
||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||
import { TABLE_COLUMNS_DENY_LIST } from '@/object-record/relation-picker/constants/TableColumnsDenyList';
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const filterAvailableTableColumns = (
|
||||
columnDefinition: ColumnDefinition<FieldMetadata>,
|
||||
): boolean => {
|
||||
if (
|
||||
isFieldRelation(columnDefinition) &&
|
||||
columnDefinition.metadata?.relationType !== 'TO_ONE_OBJECT' &&
|
||||
columnDefinition.metadata?.relationType !== 'FROM_MANY_OBJECTS'
|
||||
columnDefinition.metadata?.relationType !== RelationDefinitionType.ManyToOne
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -4,10 +4,7 @@ import { generateEmptyFieldValue } from '@/object-record/utils/generateEmptyFiel
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
export const generateDefaultFieldValue = (
|
||||
fieldMetadataItem: Pick<
|
||||
FieldMetadataItem,
|
||||
'defaultValue' | 'type' | 'fromRelationMetadata'
|
||||
>,
|
||||
fieldMetadataItem: Pick<FieldMetadataItem, 'defaultValue' | 'type'>,
|
||||
) => {
|
||||
const defaultValue = isFieldValueEmpty({
|
||||
fieldValue: fieldMetadataItem.defaultValue,
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
RelationDefinitionType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const generateEmptyFieldValue = (
|
||||
fieldMetadataItem: Pick<FieldMetadataItem, 'type' | 'fromRelationMetadata'>,
|
||||
fieldMetadataItem: Pick<FieldMetadataItem, 'type' | 'relationDefinition'>,
|
||||
) => {
|
||||
switch (fieldMetadataItem.type) {
|
||||
case FieldMetadataType.Email:
|
||||
@ -62,10 +63,8 @@ export const generateEmptyFieldValue = (
|
||||
}
|
||||
case FieldMetadataType.Relation: {
|
||||
if (
|
||||
!isNonEmptyString(
|
||||
fieldMetadataItem.fromRelationMetadata?.toObjectMetadata
|
||||
?.nameSingular,
|
||||
)
|
||||
fieldMetadataItem.relationDefinition?.direction ===
|
||||
RelationDefinitionType.ManyToOne
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -1,12 +1,16 @@
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { isObjectMetadataAvailableForRelation } from '@/object-metadata/utils/isObjectMetadataAvailableForRelation';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
RelationMetadataType,
|
||||
RelationDefinitionType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const isFieldCellSupported = (fieldMetadataItem: FieldMetadataItem) => {
|
||||
export const isFieldCellSupported = (
|
||||
fieldMetadataItem: FieldMetadataItem,
|
||||
objectMetadataItems: ObjectMetadataItem[],
|
||||
) => {
|
||||
if (
|
||||
[
|
||||
FieldMetadataType.Uuid,
|
||||
@ -18,17 +22,17 @@ export const isFieldCellSupported = (fieldMetadataItem: FieldMetadataItem) => {
|
||||
}
|
||||
|
||||
if (fieldMetadataItem.type === FieldMetadataType.Relation) {
|
||||
const relationMetadata =
|
||||
fieldMetadataItem.fromRelationMetadata ??
|
||||
fieldMetadataItem.toRelationMetadata;
|
||||
const relationObjectMetadataItem =
|
||||
fieldMetadataItem.fromRelationMetadata?.toObjectMetadata ??
|
||||
fieldMetadataItem.toRelationMetadata?.fromObjectMetadata;
|
||||
const relationObjectMetadataItemId =
|
||||
fieldMetadataItem.relationDefinition?.targetObjectMetadata.id;
|
||||
|
||||
const relationObjectMetadataItem = objectMetadataItems.find(
|
||||
(item) => item.id === relationObjectMetadataItemId,
|
||||
);
|
||||
|
||||
// Hack to display targets on Notes and Tasks
|
||||
if (
|
||||
fieldMetadataItem.fromRelationMetadata?.toObjectMetadata?.nameSingular ===
|
||||
CoreObjectNameSingular.NoteTarget &&
|
||||
fieldMetadataItem.relationDefinition?.targetObjectMetadata
|
||||
?.nameSingular === CoreObjectNameSingular.NoteTarget &&
|
||||
fieldMetadataItem.relationDefinition?.sourceObjectMetadata
|
||||
.nameSingular === CoreObjectNameSingular.Note
|
||||
) {
|
||||
@ -36,8 +40,8 @@ export const isFieldCellSupported = (fieldMetadataItem: FieldMetadataItem) => {
|
||||
}
|
||||
|
||||
if (
|
||||
fieldMetadataItem.fromRelationMetadata?.toObjectMetadata?.nameSingular ===
|
||||
CoreObjectNameSingular.TaskTarget &&
|
||||
fieldMetadataItem.relationDefinition?.targetObjectMetadata
|
||||
?.nameSingular === CoreObjectNameSingular.TaskTarget &&
|
||||
fieldMetadataItem.relationDefinition?.sourceObjectMetadata
|
||||
.nameSingular === CoreObjectNameSingular.Task
|
||||
) {
|
||||
@ -45,9 +49,10 @@ export const isFieldCellSupported = (fieldMetadataItem: FieldMetadataItem) => {
|
||||
}
|
||||
|
||||
if (
|
||||
!relationMetadata ||
|
||||
!fieldMetadataItem.relationDefinition ||
|
||||
// TODO: Many to many relations are not supported yet.
|
||||
relationMetadata.relationType === RelationMetadataType.ManyToMany ||
|
||||
fieldMetadataItem.relationDefinition.direction ===
|
||||
RelationDefinitionType.ManyToMany ||
|
||||
!relationObjectMetadataItem ||
|
||||
!isObjectMetadataAvailableForRelation(relationObjectMetadataItem)
|
||||
) {
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { isString } from '@sniptt/guards';
|
||||
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { isFieldRelationToOneValue } from '@/object-record/record-field/types/guards/isFieldRelationToOneValue';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { getUrlHostName } from '~/utils/url/getUrlHostName';
|
||||
@ -29,7 +29,8 @@ export const sanitizeRecordInput = ({
|
||||
|
||||
if (
|
||||
fieldMetadataItem.type === FieldMetadataType.Relation &&
|
||||
isFieldRelationToOneValue(fieldValue)
|
||||
fieldMetadataItem.relationDefinition?.direction ===
|
||||
RelationDefinitionType.ManyToOne
|
||||
) {
|
||||
const relationIdFieldName = `${fieldMetadataItem.name}Id`;
|
||||
const relationIdFieldMetadataItem = objectMetadataItem.fields.find(
|
||||
@ -41,6 +42,14 @@ export const sanitizeRecordInput = ({
|
||||
: undefined;
|
||||
}
|
||||
|
||||
if (
|
||||
fieldMetadataItem.type === FieldMetadataType.Relation &&
|
||||
fieldMetadataItem.relationDefinition?.direction ===
|
||||
RelationDefinitionType.OneToMany
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return [fieldName, fieldValue];
|
||||
})
|
||||
.filter(isDefined),
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import {
|
||||
IconComponent,
|
||||
IconRelationManyToMany,
|
||||
IconRelationManyToOne,
|
||||
IconRelationOneToMany,
|
||||
IconRelationOneToOne,
|
||||
} from 'twenty-ui';
|
||||
|
||||
import { RelationMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
import OneToManySvg from '../assets/OneToMany.svg';
|
||||
import OneToOneSvg from '../assets/OneToOne.svg';
|
||||
import { RelationType } from '../types/RelationType';
|
||||
@ -20,20 +20,27 @@ export const RELATION_TYPES: Record<
|
||||
isImageFlipped?: boolean;
|
||||
}
|
||||
> = {
|
||||
[RelationMetadataType.OneToMany]: {
|
||||
[RelationDefinitionType.OneToMany]: {
|
||||
label: 'Has many',
|
||||
Icon: IconRelationOneToMany,
|
||||
imageSrc: OneToManySvg,
|
||||
},
|
||||
[RelationMetadataType.OneToOne]: {
|
||||
[RelationDefinitionType.OneToOne]: {
|
||||
label: 'Has one',
|
||||
Icon: IconRelationOneToOne,
|
||||
imageSrc: OneToOneSvg,
|
||||
},
|
||||
MANY_TO_ONE: {
|
||||
[RelationDefinitionType.ManyToOne]: {
|
||||
label: 'Belongs to one',
|
||||
Icon: IconRelationManyToOne,
|
||||
imageSrc: OneToManySvg,
|
||||
isImageFlipped: true,
|
||||
},
|
||||
// Not supported yet
|
||||
[RelationDefinitionType.ManyToMany]: {
|
||||
label: 'Belongs to many',
|
||||
Icon: IconRelationManyToMany,
|
||||
imageSrc: OneToManySvg,
|
||||
isImageFlipped: true,
|
||||
},
|
||||
};
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import styled from '@emotion/styled';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { useIcons } from 'twenty-ui';
|
||||
import { z } from 'zod';
|
||||
|
||||
@ -14,6 +14,7 @@ import { RelationType } from '@/settings/data-model/types/RelationType';
|
||||
import { IconPicker } from '@/ui/input/components/IconPicker';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const settingsDataModelFieldRelationFormSchema = z.object({
|
||||
relation: z.object({
|
||||
@ -23,7 +24,10 @@ export const settingsDataModelFieldRelationFormSchema = z.object({
|
||||
}),
|
||||
objectMetadataId: z.string().uuid(),
|
||||
type: z.enum(
|
||||
Object.keys(RELATION_TYPES) as [RelationType, ...RelationType[]],
|
||||
Object.keys(RELATION_TYPES) as [
|
||||
RelationDefinitionType,
|
||||
...RelationDefinitionType[],
|
||||
],
|
||||
),
|
||||
}),
|
||||
});
|
||||
@ -33,10 +37,7 @@ export type SettingsDataModelFieldRelationFormValues = z.infer<
|
||||
>;
|
||||
|
||||
type SettingsDataModelFieldRelationFormProps = {
|
||||
fieldMetadataItem: Pick<
|
||||
FieldMetadataItem,
|
||||
'fromRelationMetadata' | 'toRelationMetadata' | 'type'
|
||||
>;
|
||||
fieldMetadataItem: Pick<FieldMetadataItem, 'type'>;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import styled from '@emotion/styled';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
|
||||
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
|
||||
@ -5,15 +5,12 @@ import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMe
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { isObjectMetadataAvailableForRelation } from '@/object-metadata/utils/isObjectMetadataAvailableForRelation';
|
||||
import { RelationMetadataType } from '~/generated-metadata/graphql';
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const useRelationSettingsFormInitialValues = ({
|
||||
fieldMetadataItem,
|
||||
}: {
|
||||
fieldMetadataItem?: Pick<
|
||||
FieldMetadataItem,
|
||||
'fromRelationMetadata' | 'toRelationMetadata' | 'type'
|
||||
>;
|
||||
fieldMetadataItem?: Pick<FieldMetadataItem, 'type' | 'relationDefinition'>;
|
||||
}) => {
|
||||
const { objectMetadataItems } = useFilteredObjectMetadataItems();
|
||||
|
||||
@ -39,7 +36,7 @@ export const useRelationSettingsFormInitialValues = ({
|
||||
);
|
||||
|
||||
const initialRelationType =
|
||||
relationTypeFromFieldMetadata ?? RelationMetadataType.OneToMany;
|
||||
relationTypeFromFieldMetadata ?? RelationDefinitionType.OneToMany;
|
||||
|
||||
return {
|
||||
disableFieldEdition:
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Edge, Node } from 'reactflow';
|
||||
import dagre from '@dagrejs/dagre';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { useEffect } from 'react';
|
||||
import { Edge, Node } from 'reactflow';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
@ -43,10 +43,10 @@ export const SettingsDataModelOverviewEffect = ({
|
||||
|
||||
for (const field of object.fields) {
|
||||
if (
|
||||
isDefined(field.toRelationMetadata) &&
|
||||
isDefined(field.relationDefinition) &&
|
||||
isDefined(
|
||||
items.find(
|
||||
(x) => x.id === field.toRelationMetadata?.fromObjectMetadata.id,
|
||||
(x) => x.id === field.relationDefinition?.targetObjectMetadata.id,
|
||||
),
|
||||
)
|
||||
) {
|
||||
@ -59,8 +59,8 @@ export const SettingsDataModelOverviewEffect = ({
|
||||
id: `${sourceObj}-${targetObj}`,
|
||||
source: object.namePlural,
|
||||
sourceHandle: `${field.id}-right`,
|
||||
target: field.toRelationMetadata.fromObjectMetadata.namePlural,
|
||||
targetHandle: `${field.toRelationMetadata.fromFieldMetadataId}-left`,
|
||||
target: field.relationDefinition.targetObjectMetadata.namePlural,
|
||||
targetHandle: `${field.relationDefinition.targetObjectMetadata}-left`,
|
||||
type: 'smoothstep',
|
||||
style: {
|
||||
strokeWidth: 1,
|
||||
@ -70,8 +70,8 @@ export const SettingsDataModelOverviewEffect = ({
|
||||
markerStart: 'marker',
|
||||
data: {
|
||||
sourceField: field.id,
|
||||
targetField: field.toRelationMetadata.fromFieldMetadataId,
|
||||
relation: field.toRelationMetadata.relationType,
|
||||
targetField: field.relationDefinition.targetFieldMetadata.id,
|
||||
relation: field.relationDefinition.direction,
|
||||
sourceObject: sourceObj,
|
||||
targetObject: targetObj,
|
||||
},
|
||||
|
||||
@ -6,6 +6,7 @@ import { useIcons } from 'twenty-ui';
|
||||
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
|
||||
type ObjectFieldRowProps = {
|
||||
field: FieldMetadataItem;
|
||||
@ -42,21 +43,33 @@ export const ObjectFieldRow = ({ field }: ObjectFieldRowProps) => {
|
||||
{Icon && <Icon size={theme.icon.size.md} />}
|
||||
<StyledFieldName>{relatedObject?.labelPlural ?? ''}</StyledFieldName>
|
||||
<Handle
|
||||
type={field.toRelationMetadata ? 'source' : 'target'}
|
||||
type={
|
||||
field.relationDefinition?.direction ===
|
||||
RelationDefinitionType.OneToMany
|
||||
? 'source'
|
||||
: 'target'
|
||||
}
|
||||
position={Position.Right}
|
||||
id={`${field.id}-right`}
|
||||
className={
|
||||
field.fromRelationMetadata
|
||||
field.relationDefinition?.direction ===
|
||||
RelationDefinitionType.OneToMany
|
||||
? 'right-handle source-handle'
|
||||
: 'right-handle target-handle'
|
||||
}
|
||||
/>
|
||||
<Handle
|
||||
type={field.toRelationMetadata ? 'source' : 'target'}
|
||||
type={
|
||||
field.relationDefinition?.direction ===
|
||||
RelationDefinitionType.OneToMany
|
||||
? 'source'
|
||||
: 'target'
|
||||
}
|
||||
position={Position.Left}
|
||||
id={`${field.id}-left`}
|
||||
className={
|
||||
field.fromRelationMetadata
|
||||
field.relationDefinition?.direction ===
|
||||
RelationDefinitionType.OneToMany
|
||||
? 'left-handle source-handle'
|
||||
: 'left-handle target-handle'
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMe
|
||||
import { View } from '@/views/types/View';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { RelationMetadataType } from '~/generated-metadata/graphql';
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
import { SettingsObjectDetailTableItem } from '~/pages/settings/data-model/types/SettingsObjectDetailTableItem';
|
||||
import { SettingsObjectFieldDataType } from './SettingsObjectFieldDataType';
|
||||
|
||||
@ -224,8 +224,8 @@ export const SettingsObjectFieldItemTableRow = ({
|
||||
<SettingsObjectFieldDataType
|
||||
Icon={RelationIcon}
|
||||
label={
|
||||
relationType === RelationMetadataType.ManyToOne ||
|
||||
relationType === RelationMetadataType.OneToOne
|
||||
relationType === RelationDefinitionType.ManyToOne ||
|
||||
relationType === RelationDefinitionType.OneToOne
|
||||
? relationObjectMetadataItem?.labelSingular
|
||||
: relationObjectMetadataItem?.labelPlural
|
||||
}
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import { RelationMetadataType } from '~/generated-metadata/graphql';
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
|
||||
export type RelationType =
|
||||
| Exclude<RelationMetadataType, 'MANY_TO_MANY'>
|
||||
| 'MANY_TO_ONE';
|
||||
export type RelationType = RelationDefinitionType;
|
||||
|
||||
@ -2,7 +2,10 @@ import { COMPANY_LABEL_IDENTIFIER_FIELD_METADATA_ID } from '@/object-metadata/ut
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||
import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
RelationDefinitionType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS = (
|
||||
[
|
||||
@ -65,7 +68,7 @@ export const SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS = (
|
||||
type: FieldMetadataType.Relation,
|
||||
metadata: {
|
||||
fieldName: 'favorites',
|
||||
relationType: 'FROM_MANY_OBJECTS',
|
||||
relationType: RelationDefinitionType.OneToMany,
|
||||
relationObjectMetadataNameSingular: '',
|
||||
relationObjectMetadataNamePlural: '',
|
||||
objectMetadataNameSingular: 'company',
|
||||
@ -99,7 +102,7 @@ export const SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS = (
|
||||
type: FieldMetadataType.Relation,
|
||||
metadata: {
|
||||
fieldName: 'accountOwner',
|
||||
relationType: 'TO_ONE_OBJECT',
|
||||
relationType: RelationDefinitionType.ManyToOne,
|
||||
relationObjectMetadataNameSingular: 'workspaceMember',
|
||||
relationObjectMetadataNamePlural: 'workspaceMembers',
|
||||
objectMetadataNameSingular: 'company',
|
||||
@ -116,7 +119,7 @@ export const SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS = (
|
||||
type: FieldMetadataType.Relation,
|
||||
metadata: {
|
||||
fieldName: 'people',
|
||||
relationType: 'FROM_MANY_OBJECTS',
|
||||
relationType: RelationDefinitionType.OneToMany,
|
||||
relationObjectMetadataNameSingular: '',
|
||||
relationObjectMetadataNamePlural: '',
|
||||
objectMetadataNameSingular: 'company',
|
||||
@ -133,7 +136,7 @@ export const SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS = (
|
||||
type: FieldMetadataType.Relation,
|
||||
metadata: {
|
||||
fieldName: 'attachments',
|
||||
relationType: 'FROM_MANY_OBJECTS',
|
||||
relationType: RelationDefinitionType.OneToMany,
|
||||
relationObjectMetadataNameSingular: '',
|
||||
relationObjectMetadataNamePlural: '',
|
||||
objectMetadataNameSingular: 'company',
|
||||
@ -201,7 +204,7 @@ export const SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS = (
|
||||
type: FieldMetadataType.Relation,
|
||||
metadata: {
|
||||
fieldName: 'opportunities',
|
||||
relationType: 'FROM_MANY_OBJECTS',
|
||||
relationType: RelationDefinitionType.OneToMany,
|
||||
relationObjectMetadataNameSingular: '',
|
||||
relationObjectMetadataNamePlural: '',
|
||||
objectMetadataNameSingular: 'company',
|
||||
@ -235,7 +238,7 @@ export const SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS = (
|
||||
type: FieldMetadataType.Relation,
|
||||
metadata: {
|
||||
fieldName: 'activityTargets',
|
||||
relationType: 'FROM_MANY_OBJECTS',
|
||||
relationType: RelationDefinitionType.OneToMany,
|
||||
relationObjectMetadataNameSingular: '',
|
||||
relationObjectMetadataNamePlural: '',
|
||||
objectMetadataNameSingular: 'company',
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useParams, useSearchParams } from 'react-router-dom';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import qs from 'qs';
|
||||
import { useMemo } from 'react';
|
||||
import { useParams, useSearchParams } from 'react-router-dom';
|
||||
import { useRecoilCallback, useRecoilValue } from 'recoil';
|
||||
import z from 'zod';
|
||||
|
||||
@ -92,12 +92,12 @@ export const useViewFromQueryParams = () => {
|
||||
if (isUndefinedOrNull(filterDefinition)) return null;
|
||||
|
||||
const relationObjectMetadataNameSingular =
|
||||
fieldMetadataItem.toRelationMetadata?.fromObjectMetadata
|
||||
.nameSingular;
|
||||
fieldMetadataItem.relationDefinition?.targetObjectMetadata
|
||||
?.nameSingular;
|
||||
|
||||
const relationObjectMetadataNamePlural =
|
||||
fieldMetadataItem.toRelationMetadata?.fromObjectMetadata
|
||||
.namePlural;
|
||||
fieldMetadataItem.relationDefinition?.targetObjectMetadata
|
||||
?.namePlural;
|
||||
|
||||
const relationObjectMetadataItem =
|
||||
relationObjectMetadataNameSingular
|
||||
|
||||
Reference in New Issue
Block a user