diff --git a/packages/twenty-front/src/modules/favorites/components/Favorites.tsx b/packages/twenty-front/src/modules/favorites/components/Favorites.tsx
index 1c14dc8b3..36d545c6f 100644
--- a/packages/twenty-front/src/modules/favorites/components/Favorites.tsx
+++ b/packages/twenty-front/src/modules/favorites/components/Favorites.tsx
@@ -1,6 +1,6 @@
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
-import { Avatar } from 'twenty-ui';
+import { Avatar, isDefined } from 'twenty-ui';
import { FavoritesSkeletonLoader } from '@/favorites/components/FavoritesSkeletonLoader';
import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
@@ -11,6 +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 { useFavorites } from '../hooks/useFavorites';
const StyledContainer = styled(NavigationDrawerSection)`
@@ -34,6 +35,8 @@ const StyledNavigationDrawerItem = styled(NavigationDrawerItem)`
`;
export const Favorites = () => {
+ const currentUser = useRecoilValue(currentUserState);
+
const { favorites, handleReorderFavorite } = useFavorites();
const loading = useIsPrefetchLoading();
@@ -41,7 +44,7 @@ export const Favorites = () => {
useNavigationSection('Favorites');
const isNavigationSectionOpen = useRecoilValue(isNavigationSectionOpenState);
- if (loading) {
+ if (loading && isDefined(currentUser)) {
return ;
}
diff --git a/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataNavItems.tsx b/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataNavItems.tsx
index ed14eaf67..f6d954441 100644
--- a/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataNavItems.tsx
+++ b/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataNavItems.tsx
@@ -2,8 +2,9 @@ import React from 'react';
import { useLocation } from 'react-router-dom';
import { useRecoilValue } from 'recoil';
-import { useIcons } from 'twenty-ui';
+import { isDefined, useIcons } from 'twenty-ui';
+import { currentUserState } from '@/auth/states/currentUserState';
import { ObjectMetadataNavItemsSkeletonLoader } from '@/object-metadata/components/ObjectMetadataNavItemsSkeletonLoader';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
@@ -18,6 +19,8 @@ import { View } from '@/views/types/View';
import { getObjectMetadataItemViews } from '@/views/utils/getObjectMetadataItemViews';
export const ObjectMetadataNavItems = ({ isRemote }: { isRemote: boolean }) => {
+ const currentUser = useRecoilValue(currentUserState);
+
const { toggleNavigationSection, isNavigationSectionOpenState } =
useNavigationSection('Objects' + (isRemote ? 'Remote' : 'Workspace'));
const isNavigationSectionOpen = useRecoilValue(isNavigationSectionOpenState);
@@ -33,7 +36,7 @@ export const ObjectMetadataNavItems = ({ isRemote }: { isRemote: boolean }) => {
const { records: views } = usePrefetchedData(PrefetchKey.AllViews);
const loading = useIsPrefetchLoading();
- if (loading) {
+ if (loading && isDefined(currentUser)) {
return ;
}
diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle.tsx
index 4f8386e12..d208dccf1 100644
--- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle.tsx
+++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle.tsx
@@ -1,7 +1,10 @@
import styled from '@emotion/styled';
+import { currentUserState } from '@/auth/states/currentUserState';
import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
import { NavigationDrawerSectionTitleSkeletonLoader } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitleSkeletonLoader';
+import { useRecoilValue } from 'recoil';
+import { isDefined } from 'twenty-ui';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
type NavigationDrawerSectionTitleProps = {
@@ -32,9 +35,10 @@ export const NavigationDrawerSectionTitle = ({
onClick,
label,
}: NavigationDrawerSectionTitleProps) => {
+ const currentUser = useRecoilValue(currentUserState);
const loading = useIsPrefetchLoading();
- if (loading) {
+ if (loading && isDefined(currentUser)) {
return ;
}
return {label};
diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts
index 7cc2e4ea1..141ed5f18 100644
--- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts
@@ -50,6 +50,7 @@ import {
CUSTOM_OBJECT_STANDARD_FIELD_IDS,
FAVORITE_STANDARD_FIELD_IDS,
NOTE_TARGET_STANDARD_FIELD_IDS,
+ TASK_TARGET_STANDARD_FIELD_IDS,
TIMELINE_ACTIVITY_STANDARD_FIELD_IDS,
} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
import {
@@ -255,13 +256,13 @@ export class ObjectMetadataService extends TypeOrmQueryService {
type: RelationMetadataType;
@@ -27,6 +27,7 @@ export function WorkspaceDynamicRelation(
target,
propertyKey.toString(),
) ?? false;
+
const gate = TypedReflect.getMetadata(
'workspace:gate-metadata-args',
target,
diff --git a/packages/twenty-server/src/engine/twenty-orm/factories/workspace-datasource.factory.ts b/packages/twenty-server/src/engine/twenty-orm/factories/workspace-datasource.factory.ts
index a3e4fd135..45c4d2a81 100644
--- a/packages/twenty-server/src/engine/twenty-orm/factories/workspace-datasource.factory.ts
+++ b/packages/twenty-server/src/engine/twenty-orm/factories/workspace-datasource.factory.ts
@@ -26,20 +26,20 @@ export class WorkspaceDatasourceFactory {
public async create(
workspaceId: string,
- cacheVersion: string | null,
+ workspaceSchemaVersion: string | null,
): Promise {
- const desiredCacheVersion =
- cacheVersion ??
+ const desiredWorkspaceSchemaVersion =
+ workspaceSchemaVersion ??
(await this.workspaceCacheVersionService.getVersion(workspaceId));
- if (!desiredCacheVersion) {
+ if (!desiredWorkspaceSchemaVersion) {
throw new Error('Cache version not found');
}
- const latestCacheVersion =
+ const latestWorkspaceSchemaVersion =
await this.workspaceCacheVersionService.getVersion(workspaceId);
- if (latestCacheVersion !== desiredCacheVersion) {
+ if (latestWorkspaceSchemaVersion !== desiredWorkspaceSchemaVersion) {
throw new Error('Cache version mismatch');
}
@@ -70,7 +70,7 @@ export class WorkspaceDatasourceFactory {
}
const workspaceDataSource = await workspaceDataSourceCacheInstance.execute(
- `${workspaceId}-${cacheVersion}`,
+ `${workspaceId}-${latestWorkspaceSchemaVersion}`,
async () => {
const dataSourceMetadata =
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceId(
diff --git a/packages/twenty-server/src/engine/twenty-orm/storage/cache-manager.storage.ts b/packages/twenty-server/src/engine/twenty-orm/storage/cache-manager.storage.ts
index 6fe25c595..6dee55b50 100644
--- a/packages/twenty-server/src/engine/twenty-orm/storage/cache-manager.storage.ts
+++ b/packages/twenty-server/src/engine/twenty-orm/storage/cache-manager.storage.ts
@@ -12,7 +12,6 @@ export class CacheManager {
): Promise {
const [workspaceId] = cacheKey.split('-');
- // If the cacheKey exists, return the cached value
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey)!;
}
diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts
index 8c33607e9..38e43ffdc 100644
--- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts
+++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts
@@ -203,6 +203,8 @@ export const FAVORITE_STANDARD_FIELD_IDS = {
company: '20202020-cff5-4682-8bf9-069169e08279',
opportunity: '20202020-dabc-48e1-8318-2781a2b32aa2',
workflow: '20202020-b11b-4dc8-999a-6bd0a947b463',
+ task: '20202020-1b1b-4b3b-8b1b-7f8d6a1d7d5c',
+ note: '20202020-1f25-43fe-8b00-af212fdde824',
custom: '20202020-855a-4bc8-9861-79deef37011f',
};
@@ -272,6 +274,7 @@ export const NOTE_STANDARD_FIELD_IDS = {
noteTargets: '20202020-1f25-43fe-8b00-af212fdde823',
attachments: '20202020-4986-4c92-bf19-39934b149b16',
timelineActivities: '20202020-7030-42f8-929c-1a57b25d6bce',
+ favorites: '20202020-4d1d-41ac-b13b-621631298d67',
};
export const NOTE_TARGET_STANDARD_FIELD_IDS = {
@@ -334,6 +337,7 @@ export const TASK_STANDARD_FIELD_IDS = {
attachments: '20202020-794d-4783-a8ff-cecdb15be139',
assignee: '20202020-065a-4f42-a906-e20422c1753f',
timelineActivities: '20202020-c778-4278-99ee-23a2837aee64',
+ favorites: '20202020-4d1d-41ac-b13b-621631298d65',
};
export const TASK_TARGET_STANDARD_FIELD_IDS = {
diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/factories/standard-field.factory.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/factories/standard-field.factory.ts
index eff6dedbd..44ba0da72 100644
--- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/factories/standard-field.factory.ts
+++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/factories/standard-field.factory.ts
@@ -1,22 +1,22 @@
import { Injectable } from '@nestjs/common';
-import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
+import { WorkspaceDynamicRelationMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-dynamic-relation-metadata-args.interface';
+import { WorkspaceEntityMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-entity-metadata-args.interface';
+import { WorkspaceFieldMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-field-metadata-args.interface';
+import { WorkspaceRelationMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-relation-metadata-args.interface';
import {
PartialComputedFieldMetadata,
PartialFieldMetadata,
} from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-field-metadata.interface';
-import { WorkspaceEntityMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-entity-metadata-args.interface';
-import { WorkspaceFieldMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-field-metadata-args.interface';
-import { WorkspaceDynamicRelationMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-dynamic-relation-metadata-args.interface';
-import { WorkspaceRelationMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-relation-metadata-args.interface';
+import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
-import { isGatedAndNotEnabled } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/is-gate-and-not-enabled.util';
-import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage';
-import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
-import { createDeterministicUuid } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/create-deterministic-uuid.util';
+import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
+import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage';
import { getJoinColumn } from 'src/engine/twenty-orm/utils/get-join-column.util';
+import { createDeterministicUuid } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/create-deterministic-uuid.util';
+import { isGatedAndNotEnabled } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/is-gate-and-not-enabled.util';
@Injectable()
export class StandardFieldFactory {
@@ -163,9 +163,7 @@ export class StandardFieldFactory {
workspaceId: context.workspaceId,
isNullable: workspaceFieldMetadataArgs.isNullable,
isCustom: workspaceFieldMetadataArgs.isDeprecated ? true : false,
- isSystem:
- workspaceEntityMetadataArgs?.isSystem ||
- workspaceFieldMetadataArgs.isSystem,
+ isSystem: workspaceFieldMetadataArgs.isSystem ?? false,
},
];
}
diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service.ts
index 4ef6a446e..117ca1e59 100644
--- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service.ts
+++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service.ts
@@ -84,7 +84,7 @@ export class WorkspaceSyncMetadataService {
workspaceFeatureFlagsMap,
);
- // 2 - Sync standard fields on custom objects
+ // 2 - Sync standard fields on standard and custom objects
const workspaceFieldMigrations =
await this.workspaceSyncFieldMetadataService.synchronize(
context,
diff --git a/packages/twenty-server/src/modules/favorite/standard-objects/favorite.workspace-entity.ts b/packages/twenty-server/src/modules/favorite/standard-objects/favorite.workspace-entity.ts
index e09681a23..a3cb30729 100644
--- a/packages/twenty-server/src/modules/favorite/standard-objects/favorite.workspace-entity.ts
+++ b/packages/twenty-server/src/modules/favorite/standard-objects/favorite.workspace-entity.ts
@@ -17,8 +17,10 @@ import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-re
import { FAVORITE_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
import { CompanyWorkspaceEntity } from 'src/modules/company/standard-objects/company.workspace-entity';
+import { NoteWorkspaceEntity } from 'src/modules/note/standard-objects/note.workspace-entity';
import { OpportunityWorkspaceEntity } from 'src/modules/opportunity/standard-objects/opportunity.workspace-entity';
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
+import { TaskWorkspaceEntity } from 'src/modules/task/standard-objects/task.workspace-entity';
import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
@@ -124,6 +126,36 @@ export class FavoriteWorkspaceEntity extends BaseWorkspaceEntity {
})
workflowId: string;
+ @WorkspaceRelation({
+ standardId: FAVORITE_STANDARD_FIELD_IDS.task,
+ type: RelationMetadataType.MANY_TO_ONE,
+ label: 'Task',
+ description: 'Favorite task',
+ icon: 'IconCheckbox',
+ inverseSideTarget: () => TaskWorkspaceEntity,
+ inverseSideFieldKey: 'favorites',
+ })
+ @WorkspaceIsNullable()
+ task: Relation | null;
+
+ @WorkspaceJoinColumn('task')
+ taskId: string;
+
+ @WorkspaceRelation({
+ standardId: FAVORITE_STANDARD_FIELD_IDS.note,
+ type: RelationMetadataType.MANY_TO_ONE,
+ label: 'Note',
+ description: 'Favorite note',
+ icon: 'IconNotes',
+ inverseSideTarget: () => NoteWorkspaceEntity,
+ inverseSideFieldKey: 'favorites',
+ })
+ @WorkspaceIsNullable()
+ note: Relation | null;
+
+ @WorkspaceJoinColumn('note')
+ noteId: string;
+
@WorkspaceDynamicRelation({
type: RelationMetadataType.MANY_TO_ONE,
argsFactory: (oppositeObjectMetadata) => ({
@@ -132,7 +164,7 @@ export class FavoriteWorkspaceEntity extends BaseWorkspaceEntity {
label: oppositeObjectMetadata.labelSingular,
description: `Favorite ${oppositeObjectMetadata.labelSingular}`,
joinColumn: `${oppositeObjectMetadata.nameSingular}Id`,
- icon: 'IconBuildingSkyscraper',
+ icon: 'IconHeart',
}),
inverseSideTarget: () => CustomWorkspaceEntity,
inverseSideFieldKey: 'favorites',
diff --git a/packages/twenty-server/src/modules/note/standard-objects/note.workspace-entity.ts b/packages/twenty-server/src/modules/note/standard-objects/note.workspace-entity.ts
index 33e48b1c4..7b5c96118 100644
--- a/packages/twenty-server/src/modules/note/standard-objects/note.workspace-entity.ts
+++ b/packages/twenty-server/src/modules/note/standard-objects/note.workspace-entity.ts
@@ -18,6 +18,7 @@ import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-re
import { NOTE_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity';
+import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity';
import { NoteTargetWorkspaceEntity } from 'src/modules/note/standard-objects/note-target.workspace-entity';
import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
@@ -110,4 +111,16 @@ export class NoteWorkspaceEntity extends BaseWorkspaceEntity {
})
@WorkspaceIsNullable()
timelineActivities: Relation;
+
+ @WorkspaceRelation({
+ standardId: NOTE_STANDARD_FIELD_IDS.favorites,
+ type: RelationMetadataType.ONE_TO_MANY,
+ label: 'Favorites',
+ description: 'Favorites linked to the note',
+ icon: 'IconHeart',
+ inverseSideTarget: () => FavoriteWorkspaceEntity,
+ onDelete: RelationOnDeleteAction.CASCADE,
+ })
+ @WorkspaceIsSystem()
+ favorites: Relation;
}
diff --git a/packages/twenty-server/src/modules/task/standard-objects/task.workspace-entity.ts b/packages/twenty-server/src/modules/task/standard-objects/task.workspace-entity.ts
index 40a77ddc5..03f0c8800 100644
--- a/packages/twenty-server/src/modules/task/standard-objects/task.workspace-entity.ts
+++ b/packages/twenty-server/src/modules/task/standard-objects/task.workspace-entity.ts
@@ -19,6 +19,7 @@ import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-re
import { TASK_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity';
+import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity';
import { TaskTargetWorkspaceEntity } from 'src/modules/task/standard-objects/task-target.workspace-entity';
import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
@@ -164,4 +165,16 @@ export class TaskWorkspaceEntity extends BaseWorkspaceEntity {
})
@WorkspaceIsNullable()
timelineActivities: Relation;
+
+ @WorkspaceRelation({
+ standardId: TASK_STANDARD_FIELD_IDS.favorites,
+ type: RelationMetadataType.ONE_TO_MANY,
+ label: 'Favorites',
+ description: 'Favorites linked to the task',
+ icon: 'IconHeart',
+ inverseSideTarget: () => FavoriteWorkspaceEntity,
+ onDelete: RelationOnDeleteAction.CASCADE,
+ })
+ @WorkspaceIsSystem()
+ favorites: Relation;
}
diff --git a/packages/twenty-server/src/modules/timeline/standard-objects/timeline-activity.workspace-entity.ts b/packages/twenty-server/src/modules/timeline/standard-objects/timeline-activity.workspace-entity.ts
index de4cfc6de..db6698bf8 100644
--- a/packages/twenty-server/src/modules/timeline/standard-objects/timeline-activity.workspace-entity.ts
+++ b/packages/twenty-server/src/modules/timeline/standard-objects/timeline-activity.workspace-entity.ts
@@ -212,7 +212,7 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
standardId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.custom,
name: oppositeObjectMetadata.nameSingular,
label: oppositeObjectMetadata.labelSingular,
- description: `Event ${oppositeObjectMetadata.labelSingular}`,
+ description: `Timeline Activity ${oppositeObjectMetadata.labelSingular}`,
joinColumn: `${oppositeObjectMetadata.nameSingular}Id`,
icon: 'IconTimeline',
}),