Skip to content

Commit 2b311b5

Browse files
Update standard fields (#6532)
In this PR: - adding Favorites to Tasks and Notes - fixing inconsistencies between custom object creation and sync of standard fields of custom objects - fixing workspaceCacheVersion not used to invalidate existing datasource
1 parent 0320402 commit 2b311b5

File tree

14 files changed

+112
-40
lines changed

14 files changed

+112
-40
lines changed

packages/twenty-front/src/modules/favorites/components/Favorites.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import styled from '@emotion/styled';
22
import { useRecoilValue } from 'recoil';
3-
import { Avatar } from 'twenty-ui';
3+
import { Avatar, isDefined } from 'twenty-ui';
44

55
import { FavoritesSkeletonLoader } from '@/favorites/components/FavoritesSkeletonLoader';
66
import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
@@ -11,6 +11,7 @@ import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/compo
1111
import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle';
1212
import { useNavigationSection } from '@/ui/navigation/navigation-drawer/hooks/useNavigationSection';
1313

14+
import { currentUserState } from '@/auth/states/currentUserState';
1415
import { useFavorites } from '../hooks/useFavorites';
1516

1617
const StyledContainer = styled(NavigationDrawerSection)`
@@ -34,14 +35,16 @@ const StyledNavigationDrawerItem = styled(NavigationDrawerItem)`
3435
`;
3536

3637
export const Favorites = () => {
38+
const currentUser = useRecoilValue(currentUserState);
39+
3740
const { favorites, handleReorderFavorite } = useFavorites();
3841
const loading = useIsPrefetchLoading();
3942

4043
const { toggleNavigationSection, isNavigationSectionOpenState } =
4144
useNavigationSection('Favorites');
4245
const isNavigationSectionOpen = useRecoilValue(isNavigationSectionOpenState);
4346

44-
if (loading) {
47+
if (loading && isDefined(currentUser)) {
4548
return <FavoritesSkeletonLoader />;
4649
}
4750

packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataNavItems.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import React from 'react';
22

33
import { useLocation } from 'react-router-dom';
44
import { useRecoilValue } from 'recoil';
5-
import { useIcons } from 'twenty-ui';
5+
import { isDefined, useIcons } from 'twenty-ui';
66

7+
import { currentUserState } from '@/auth/states/currentUserState';
78
import { ObjectMetadataNavItemsSkeletonLoader } from '@/object-metadata/components/ObjectMetadataNavItemsSkeletonLoader';
89
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
910
import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
@@ -18,6 +19,8 @@ import { View } from '@/views/types/View';
1819
import { getObjectMetadataItemViews } from '@/views/utils/getObjectMetadataItemViews';
1920

2021
export const ObjectMetadataNavItems = ({ isRemote }: { isRemote: boolean }) => {
22+
const currentUser = useRecoilValue(currentUserState);
23+
2124
const { toggleNavigationSection, isNavigationSectionOpenState } =
2225
useNavigationSection('Objects' + (isRemote ? 'Remote' : 'Workspace'));
2326
const isNavigationSectionOpen = useRecoilValue(isNavigationSectionOpenState);
@@ -33,7 +36,7 @@ export const ObjectMetadataNavItems = ({ isRemote }: { isRemote: boolean }) => {
3336
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews);
3437
const loading = useIsPrefetchLoading();
3538

36-
if (loading) {
39+
if (loading && isDefined(currentUser)) {
3740
return <ObjectMetadataNavItemsSkeletonLoader />;
3841
}
3942

packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import styled from '@emotion/styled';
22

3+
import { currentUserState } from '@/auth/states/currentUserState';
34
import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
45
import { NavigationDrawerSectionTitleSkeletonLoader } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitleSkeletonLoader';
6+
import { useRecoilValue } from 'recoil';
7+
import { isDefined } from 'twenty-ui';
58
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
69

710
type NavigationDrawerSectionTitleProps = {
@@ -32,9 +35,10 @@ export const NavigationDrawerSectionTitle = ({
3235
onClick,
3336
label,
3437
}: NavigationDrawerSectionTitleProps) => {
38+
const currentUser = useRecoilValue(currentUserState);
3539
const loading = useIsPrefetchLoading();
3640

37-
if (loading) {
41+
if (loading && isDefined(currentUser)) {
3842
return <NavigationDrawerSectionTitleSkeletonLoader />;
3943
}
4044
return <StyledTitle onClick={onClick}>{label}</StyledTitle>;

packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts

+13-11
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import {
5050
CUSTOM_OBJECT_STANDARD_FIELD_IDS,
5151
FAVORITE_STANDARD_FIELD_IDS,
5252
NOTE_TARGET_STANDARD_FIELD_IDS,
53+
TASK_TARGET_STANDARD_FIELD_IDS,
5354
TIMELINE_ACTIVITY_STANDARD_FIELD_IDS,
5455
} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
5556
import {
@@ -255,21 +256,21 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
255256
standardId: BASE_OBJECT_STANDARD_FIELD_IDS.updatedAt,
256257
type: FieldMetadataType.DATE_TIME,
257258
name: 'updatedAt',
258-
label: 'Update date',
259-
icon: 'IconCalendar',
260-
description: 'Update date',
259+
label: 'Last update',
260+
icon: 'IconCalendarClock',
261+
description: 'Last time the record was changed',
261262
isNullable: false,
262263
isActive: true,
263264
isCustom: false,
264-
isSystem: true,
265+
isSystem: false,
265266
workspaceId: objectMetadataInput.workspaceId,
266267
defaultValue: 'now',
267268
},
268269
{
269270
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.createdBy,
270271
type: FieldMetadataType.ACTOR,
271272
name: 'createdBy',
272-
label: 'Created By',
273+
label: 'Created by',
273274
icon: 'IconCreativeCommonsSa',
274275
description: 'The creator of the record',
275276
isNullable: false,
@@ -676,7 +677,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
676677
name: 'noteTargets',
677678
label: 'Notes',
678679
description: `Notes tied to the ${createdObjectMetadata.labelSingular}`,
679-
icon: 'IconCheckbox',
680+
icon: 'IconNotes',
680681
isNullable: true,
681682
},
682683
// TO
@@ -746,7 +747,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
746747
{
747748
standardId: createForeignKeyDeterministicUuid({
748749
objectId: createdObjectMetadata.id,
749-
standardId: NOTE_TARGET_STANDARD_FIELD_IDS.custom,
750+
standardId: TASK_TARGET_STANDARD_FIELD_IDS.custom,
750751
}),
751752
objectMetadataId: taskTargetObjectMetadata.id,
752753
workspaceId: workspaceId,
@@ -784,7 +785,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
784785
{
785786
standardId: createRelationDeterministicUuid({
786787
objectId: createdObjectMetadata.id,
787-
standardId: NOTE_TARGET_STANDARD_FIELD_IDS.custom,
788+
standardId: TASK_TARGET_STANDARD_FIELD_IDS.custom,
788789
}),
789790
objectMetadataId: taskTargetObjectMetadata.id,
790791
workspaceId: workspaceId,
@@ -979,8 +980,9 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
979980
name: 'timelineActivities',
980981
label: 'Timeline Activities',
981982
description: `Timeline Activities tied to the ${createdObjectMetadata.labelSingular}`,
982-
icon: 'IconTimeline',
983+
icon: 'IconIconTimelineEvent',
983984
isNullable: true,
985+
isSystem: true,
984986
},
985987
// TO
986988
{
@@ -996,7 +998,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
996998
name: createdObjectMetadata.nameSingular,
997999
label: createdObjectMetadata.labelSingular,
9981000
description: `Timeline Activity ${createdObjectMetadata.labelSingular}`,
999-
icon: 'IconBuildingSkyscraper',
1001+
icon: 'IconTimeline',
10001002
isNullable: true,
10011003
},
10021004
]);
@@ -1100,7 +1102,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
11001102
name: createdObjectMetadata.nameSingular,
11011103
label: createdObjectMetadata.labelSingular,
11021104
description: `Favorite ${createdObjectMetadata.labelSingular}`,
1103-
icon: 'IconBuildingSkyscraper',
1105+
icon: 'IconHeart',
11041106
isNullable: true,
11051107
},
11061108
]);

packages/twenty-server/src/engine/twenty-orm/decorators/workspace-dynamic-relation.decorator.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import { ObjectType } from 'typeorm';
22

33
import { WorkspaceDynamicRelationMetadataArgsFactory } from 'src/engine/twenty-orm/interfaces/workspace-dynamic-relation-metadata-args.interface';
44

5-
import { TypedReflect } from 'src/utils/typed-reflect';
65
import {
76
RelationMetadataType,
87
RelationOnDeleteAction,
98
} from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
109
import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage';
10+
import { TypedReflect } from 'src/utils/typed-reflect';
1111

1212
interface WorkspaceBaseDynamicRelationOptions<TClass> {
1313
type: RelationMetadataType;
@@ -27,6 +27,7 @@ export function WorkspaceDynamicRelation<TClass extends object>(
2727
target,
2828
propertyKey.toString(),
2929
) ?? false;
30+
3031
const gate = TypedReflect.getMetadata(
3132
'workspace:gate-metadata-args',
3233
target,

packages/twenty-server/src/engine/twenty-orm/factories/workspace-datasource.factory.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,20 @@ export class WorkspaceDatasourceFactory {
2626

2727
public async create(
2828
workspaceId: string,
29-
cacheVersion: string | null,
29+
workspaceSchemaVersion: string | null,
3030
): Promise<WorkspaceDataSource> {
31-
const desiredCacheVersion =
32-
cacheVersion ??
31+
const desiredWorkspaceSchemaVersion =
32+
workspaceSchemaVersion ??
3333
(await this.workspaceCacheVersionService.getVersion(workspaceId));
3434

35-
if (!desiredCacheVersion) {
35+
if (!desiredWorkspaceSchemaVersion) {
3636
throw new Error('Cache version not found');
3737
}
3838

39-
const latestCacheVersion =
39+
const latestWorkspaceSchemaVersion =
4040
await this.workspaceCacheVersionService.getVersion(workspaceId);
4141

42-
if (latestCacheVersion !== desiredCacheVersion) {
42+
if (latestWorkspaceSchemaVersion !== desiredWorkspaceSchemaVersion) {
4343
throw new Error('Cache version mismatch');
4444
}
4545

@@ -70,7 +70,7 @@ export class WorkspaceDatasourceFactory {
7070
}
7171

7272
const workspaceDataSource = await workspaceDataSourceCacheInstance.execute(
73-
`${workspaceId}-${cacheVersion}`,
73+
`${workspaceId}-${latestWorkspaceSchemaVersion}`,
7474
async () => {
7575
const dataSourceMetadata =
7676
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceId(

packages/twenty-server/src/engine/twenty-orm/storage/cache-manager.storage.ts

-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ export class CacheManager<T> {
1212
): Promise<T | null> {
1313
const [workspaceId] = cacheKey.split('-');
1414

15-
// If the cacheKey exists, return the cached value
1615
if (this.cache.has(cacheKey)) {
1716
return this.cache.get(cacheKey)!;
1817
}

packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts

+4
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,8 @@ export const FAVORITE_STANDARD_FIELD_IDS = {
203203
company: '20202020-cff5-4682-8bf9-069169e08279',
204204
opportunity: '20202020-dabc-48e1-8318-2781a2b32aa2',
205205
workflow: '20202020-b11b-4dc8-999a-6bd0a947b463',
206+
task: '20202020-1b1b-4b3b-8b1b-7f8d6a1d7d5c',
207+
note: '20202020-1f25-43fe-8b00-af212fdde824',
206208
custom: '20202020-855a-4bc8-9861-79deef37011f',
207209
};
208210

@@ -272,6 +274,7 @@ export const NOTE_STANDARD_FIELD_IDS = {
272274
noteTargets: '20202020-1f25-43fe-8b00-af212fdde823',
273275
attachments: '20202020-4986-4c92-bf19-39934b149b16',
274276
timelineActivities: '20202020-7030-42f8-929c-1a57b25d6bce',
277+
favorites: '20202020-4d1d-41ac-b13b-621631298d67',
275278
};
276279

277280
export const NOTE_TARGET_STANDARD_FIELD_IDS = {
@@ -334,6 +337,7 @@ export const TASK_STANDARD_FIELD_IDS = {
334337
attachments: '20202020-794d-4783-a8ff-cecdb15be139',
335338
assignee: '20202020-065a-4f42-a906-e20422c1753f',
336339
timelineActivities: '20202020-c778-4278-99ee-23a2837aee64',
340+
favorites: '20202020-4d1d-41ac-b13b-621631298d65',
337341
};
338342

339343
export const TASK_TARGET_STANDARD_FIELD_IDS = {

packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/factories/standard-field.factory.ts

+10-12
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
import { Injectable } from '@nestjs/common';
22

3-
import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
43
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
4+
import { WorkspaceDynamicRelationMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-dynamic-relation-metadata-args.interface';
5+
import { WorkspaceEntityMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-entity-metadata-args.interface';
6+
import { WorkspaceFieldMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-field-metadata-args.interface';
7+
import { WorkspaceRelationMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-relation-metadata-args.interface';
58
import {
69
PartialComputedFieldMetadata,
710
PartialFieldMetadata,
811
} from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-field-metadata.interface';
9-
import { WorkspaceEntityMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-entity-metadata-args.interface';
10-
import { WorkspaceFieldMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-field-metadata-args.interface';
11-
import { WorkspaceDynamicRelationMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-dynamic-relation-metadata-args.interface';
12-
import { WorkspaceRelationMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-relation-metadata-args.interface';
12+
import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
1313

14-
import { isGatedAndNotEnabled } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/is-gate-and-not-enabled.util';
15-
import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage';
16-
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
1714
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
18-
import { createDeterministicUuid } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/create-deterministic-uuid.util';
15+
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
16+
import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage';
1917
import { getJoinColumn } from 'src/engine/twenty-orm/utils/get-join-column.util';
18+
import { createDeterministicUuid } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/create-deterministic-uuid.util';
19+
import { isGatedAndNotEnabled } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/is-gate-and-not-enabled.util';
2020

2121
@Injectable()
2222
export class StandardFieldFactory {
@@ -163,9 +163,7 @@ export class StandardFieldFactory {
163163
workspaceId: context.workspaceId,
164164
isNullable: workspaceFieldMetadataArgs.isNullable,
165165
isCustom: workspaceFieldMetadataArgs.isDeprecated ? true : false,
166-
isSystem:
167-
workspaceEntityMetadataArgs?.isSystem ||
168-
workspaceFieldMetadataArgs.isSystem,
166+
isSystem: workspaceFieldMetadataArgs.isSystem ?? false,
169167
},
170168
];
171169
}

packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export class WorkspaceSyncMetadataService {
8484
workspaceFeatureFlagsMap,
8585
);
8686

87-
// 2 - Sync standard fields on custom objects
87+
// 2 - Sync standard fields on standard and custom objects
8888
const workspaceFieldMigrations =
8989
await this.workspaceSyncFieldMetadataService.synchronize(
9090
context,

packages/twenty-server/src/modules/favorite/standard-objects/favorite.workspace-entity.ts

+33-1
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-re
1717
import { FAVORITE_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
1818
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
1919
import { CompanyWorkspaceEntity } from 'src/modules/company/standard-objects/company.workspace-entity';
20+
import { NoteWorkspaceEntity } from 'src/modules/note/standard-objects/note.workspace-entity';
2021
import { OpportunityWorkspaceEntity } from 'src/modules/opportunity/standard-objects/opportunity.workspace-entity';
2122
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
23+
import { TaskWorkspaceEntity } from 'src/modules/task/standard-objects/task.workspace-entity';
2224
import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
2325
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
2426

@@ -124,6 +126,36 @@ export class FavoriteWorkspaceEntity extends BaseWorkspaceEntity {
124126
})
125127
workflowId: string;
126128

129+
@WorkspaceRelation({
130+
standardId: FAVORITE_STANDARD_FIELD_IDS.task,
131+
type: RelationMetadataType.MANY_TO_ONE,
132+
label: 'Task',
133+
description: 'Favorite task',
134+
icon: 'IconCheckbox',
135+
inverseSideTarget: () => TaskWorkspaceEntity,
136+
inverseSideFieldKey: 'favorites',
137+
})
138+
@WorkspaceIsNullable()
139+
task: Relation<TaskWorkspaceEntity> | null;
140+
141+
@WorkspaceJoinColumn('task')
142+
taskId: string;
143+
144+
@WorkspaceRelation({
145+
standardId: FAVORITE_STANDARD_FIELD_IDS.note,
146+
type: RelationMetadataType.MANY_TO_ONE,
147+
label: 'Note',
148+
description: 'Favorite note',
149+
icon: 'IconNotes',
150+
inverseSideTarget: () => NoteWorkspaceEntity,
151+
inverseSideFieldKey: 'favorites',
152+
})
153+
@WorkspaceIsNullable()
154+
note: Relation<NoteWorkspaceEntity> | null;
155+
156+
@WorkspaceJoinColumn('note')
157+
noteId: string;
158+
127159
@WorkspaceDynamicRelation({
128160
type: RelationMetadataType.MANY_TO_ONE,
129161
argsFactory: (oppositeObjectMetadata) => ({
@@ -132,7 +164,7 @@ export class FavoriteWorkspaceEntity extends BaseWorkspaceEntity {
132164
label: oppositeObjectMetadata.labelSingular,
133165
description: `Favorite ${oppositeObjectMetadata.labelSingular}`,
134166
joinColumn: `${oppositeObjectMetadata.nameSingular}Id`,
135-
icon: 'IconBuildingSkyscraper',
167+
icon: 'IconHeart',
136168
}),
137169
inverseSideTarget: () => CustomWorkspaceEntity,
138170
inverseSideFieldKey: 'favorites',

0 commit comments

Comments
 (0)