diff --git a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewField.ts b/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewField.ts index fec069e00d..8baa63d21e 100644 --- a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewField.ts +++ b/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewField.ts @@ -4,18 +4,14 @@ import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetad import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useTriggerViewFieldOptimisticEffect } from '@/views/optimistic-effects/hooks/useTriggerViewFieldOptimisticEffect'; -import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { ApolloError } from '@apollo/client'; import { t } from '@lingui/core/macro'; import { isDefined } from 'twenty-shared/utils'; import { - type CreateCoreViewFieldMutationVariables, type CreateManyCoreViewFieldsMutationVariables, type DeleteCoreViewFieldMutationVariables, type DestroyCoreViewFieldMutationVariables, - FeatureFlagKey, type UpdateCoreViewFieldMutationVariables, - useCreateCoreViewFieldMutation, useCreateManyCoreViewFieldsMutation, useDeleteCoreViewFieldMutation, useDestroyCoreViewFieldMutation, @@ -25,11 +21,6 @@ export const usePersistViewField = () => { const { triggerViewFieldOptimisticEffect } = useTriggerViewFieldOptimisticEffect(); - const isWorkspaceMigrationV2Enabled = useIsFeatureEnabled( - FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - ); - - const [createCoreViewFieldMutation] = useCreateCoreViewFieldMutation(); const [createManyCoreViewFieldsMutation] = useCreateManyCoreViewFieldsMutation(); const [updateCoreViewFieldMutation] = useUpdateCoreViewFieldMutation(); @@ -39,77 +30,13 @@ export const usePersistViewField = () => { const { handleMetadataError } = useMetadataErrorHandler(); const { enqueueErrorSnackBar } = useSnackBar(); - const oldCreateViewFields = useCallback( - async ( - createCoreViewFieldInputs: CreateCoreViewFieldMutationVariables[], - ): Promise< - MetadataRequestResult< - Awaited>[] - > - > => { - if (createCoreViewFieldInputs.length === 0) { - return { - status: 'successful', - response: [], - }; - } - - try { - const results = await Promise.all( - createCoreViewFieldInputs.map(async (variables) => - createCoreViewFieldMutation({ - variables, - update: (_cache, { data }) => { - const createdViewField = data?.createCoreViewField; - if (!isDefined(createdViewField)) { - return; - } - - triggerViewFieldOptimisticEffect({ - createdViewFields: [createdViewField], - }); - }, - }), - ), - ); - - return { - status: 'successful', - response: results, - }; - } catch (error) { - if (error instanceof ApolloError) { - handleMetadataError(error, { - primaryMetadataName: 'viewField', - }); - } else { - enqueueErrorSnackBar({ message: t`An error occurred.` }); - } - - return { - status: 'failed', - error, - }; - } - }, - [ - triggerViewFieldOptimisticEffect, - createCoreViewFieldMutation, - handleMetadataError, - enqueueErrorSnackBar, - ], - ); - const createViewFields = useCallback( async ( createCoreViewFieldInputs: CreateManyCoreViewFieldsMutationVariables, ): Promise< - | MetadataRequestResult - > | null> - | MetadataRequestResult< - Awaited>[] - > + MetadataRequestResult + > | null> > => { if ( !Array.isArray(createCoreViewFieldInputs.inputs) || @@ -121,12 +48,6 @@ export const usePersistViewField = () => { }; } - if (!isWorkspaceMigrationV2Enabled) { - const oldFormatInputs: CreateCoreViewFieldMutationVariables[] = - createCoreViewFieldInputs.inputs.map((input) => ({ input })); - return await oldCreateViewFields(oldFormatInputs); - } - try { const result = await createManyCoreViewFieldsMutation({ variables: createCoreViewFieldInputs, @@ -162,8 +83,6 @@ export const usePersistViewField = () => { } }, [ - isWorkspaceMigrationV2Enabled, - oldCreateViewFields, triggerViewFieldOptimisticEffect, createManyCoreViewFieldsMutation, handleMetadataError, diff --git a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewGroup.ts b/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewGroup.ts index 13919b7fb1..034083d9a6 100644 --- a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewGroup.ts +++ b/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewGroup.ts @@ -4,18 +4,14 @@ import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetad import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useTriggerViewGroupOptimisticEffect } from '@/views/optimistic-effects/hooks/useTriggerViewGroupOptimisticEffect'; -import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { ApolloError } from '@apollo/client'; import { t } from '@lingui/core/macro'; import { isDefined } from 'twenty-shared/utils'; import { - type CreateCoreViewGroupMutationVariables, type CreateManyCoreViewGroupsMutationVariables, type DeleteCoreViewGroupMutationVariables, type DestroyCoreViewGroupMutationVariables, - FeatureFlagKey, type UpdateCoreViewGroupMutationVariables, - useCreateCoreViewGroupMutation, useCreateManyCoreViewGroupsMutation, useDeleteCoreViewGroupMutation, useDestroyCoreViewGroupMutation, @@ -26,11 +22,6 @@ export const usePersistViewGroupRecords = () => { const { triggerViewGroupOptimisticEffect } = useTriggerViewGroupOptimisticEffect(); - const isWorkspaceMigrationV2Enabled = useIsFeatureEnabled( - FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - ); - - const [createCoreViewGroupMutation] = useCreateCoreViewGroupMutation(); const [createManyCoreViewGroupsMutation] = useCreateManyCoreViewGroupsMutation(); const [updateCoreViewGroupMutation] = useUpdateCoreViewGroupMutation(); @@ -40,77 +31,13 @@ export const usePersistViewGroupRecords = () => { const { handleMetadataError } = useMetadataErrorHandler(); const { enqueueErrorSnackBar } = useSnackBar(); - const oldCreateViewGroups = useCallback( - async ( - createCoreViewGroupInputs: CreateCoreViewGroupMutationVariables[], - ): Promise< - MetadataRequestResult< - Awaited>[] - > - > => { - if (createCoreViewGroupInputs.length === 0) { - return { - status: 'successful', - response: [], - }; - } - - try { - const results = await Promise.all( - createCoreViewGroupInputs.map(async (variables) => - createCoreViewGroupMutation({ - variables, - update: (_cache, { data }) => { - const createdViewGroup = data?.createCoreViewGroup; - if (!isDefined(createdViewGroup)) { - return; - } - - triggerViewGroupOptimisticEffect({ - createdViewGroups: [createdViewGroup], - }); - }, - }), - ), - ); - - return { - status: 'successful', - response: results, - }; - } catch (error) { - if (error instanceof ApolloError) { - handleMetadataError(error, { - primaryMetadataName: 'viewGroup', - }); - } else { - enqueueErrorSnackBar({ message: t`An error occurred.` }); - } - - return { - status: 'failed', - error, - }; - } - }, - [ - triggerViewGroupOptimisticEffect, - createCoreViewGroupMutation, - handleMetadataError, - enqueueErrorSnackBar, - ], - ); - const createViewGroups = useCallback( async ( createCoreViewGroupInputs: CreateManyCoreViewGroupsMutationVariables, ): Promise< - | MetadataRequestResult - > | null> - | MetadataRequestResult< - Awaited>[] - > + MetadataRequestResult + > | null> > => { if ( !Array.isArray(createCoreViewGroupInputs.inputs) || @@ -122,12 +49,6 @@ export const usePersistViewGroupRecords = () => { }; } - if (!isWorkspaceMigrationV2Enabled) { - const oldFormatInputs: CreateCoreViewGroupMutationVariables[] = - createCoreViewGroupInputs.inputs.map((input) => ({ input })); - return await oldCreateViewGroups(oldFormatInputs); - } - try { const result = await createManyCoreViewGroupsMutation({ variables: createCoreViewGroupInputs, @@ -163,8 +84,6 @@ export const usePersistViewGroupRecords = () => { } }, [ - isWorkspaceMigrationV2Enabled, - oldCreateViewGroups, triggerViewGroupOptimisticEffect, createManyCoreViewGroupsMutation, handleMetadataError, diff --git a/packages/twenty-server/src/engine/metadata-modules/view-group/controllers/view-group.controller.ts b/packages/twenty-server/src/engine/metadata-modules/view-group/controllers/view-group.controller.ts index ca0fa9ec7b..6e3fea8e97 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-group/controllers/view-group.controller.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-group/controllers/view-group.controller.ts @@ -13,8 +13,6 @@ import { import { isDefined } from 'twenty-shared/utils'; -import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; -import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard'; @@ -30,7 +28,6 @@ import { ViewGroupExceptionMessageKey, } from 'src/engine/metadata-modules/view-group/exceptions/view-group.exception'; import { ViewGroupRestApiExceptionFilter } from 'src/engine/metadata-modules/view-group/filters/view-group-rest-api-exception.filter'; -import { ViewGroupV2Service } from 'src/engine/metadata-modules/view-group/services/view-group-v2.service'; import { ViewGroupService } from 'src/engine/metadata-modules/view-group/services/view-group.service'; import { CreateViewGroupPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/create-view-group-permission.guard'; import { DeleteViewGroupPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/delete-view-group-permission.guard'; @@ -40,11 +37,7 @@ import { UpdateViewGroupPermissionGuard } from 'src/engine/metadata-modules/view @UseGuards(WorkspaceAuthGuard) @UseFilters(ViewGroupRestApiExceptionFilter) export class ViewGroupController { - constructor( - private readonly viewGroupService: ViewGroupService, - private readonly viewGroupV2Service: ViewGroupV2Service, - private readonly featureFlagService: FeatureFlagService, - ) {} + constructor(private readonly viewGroupService: ViewGroupService) {} @Get() @UseGuards(NoPermissionGuard) @@ -91,21 +84,8 @@ export class ViewGroupController { @Body() input: CreateViewGroupInput, @AuthWorkspace() workspace: WorkspaceEntity, ): Promise { - const isWorkspaceMigrationV2Enabled = - await this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - workspace.id, - ); - - if (isWorkspaceMigrationV2Enabled) { - return await this.viewGroupV2Service.createOne({ - createViewGroupInput: input, - workspaceId: workspace.id, - }); - } - - return this.viewGroupService.create({ - ...input, + return await this.viewGroupService.createOne({ + createViewGroupInput: input, workspaceId: workspace.id, }); } @@ -117,31 +97,15 @@ export class ViewGroupController { @Body() input: UpdateViewGroupInput, @AuthWorkspace() workspace: WorkspaceEntity, ): Promise { - const isWorkspaceMigrationV2Enabled = - await this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - workspace.id, - ); - - if (isWorkspaceMigrationV2Enabled) { - const updateInput = { - id, - update: input.update ?? input, - }; - - return await this.viewGroupV2Service.updateOne({ - updateViewGroupInput: updateInput, - workspaceId: workspace.id, - }); - } - - const updatedViewGroup = await this.viewGroupService.update( + const updateInput = { id, - workspace.id, - input, - ); + update: input.update ?? input, + }; - return updatedViewGroup; + return await this.viewGroupService.updateOne({ + updateViewGroupInput: updateInput, + workspaceId: workspace.id, + }); } @Delete(':id') @@ -150,25 +114,10 @@ export class ViewGroupController { @Param('id') id: string, @AuthWorkspace() workspace: WorkspaceEntity, ): Promise<{ success: boolean }> { - const isWorkspaceMigrationV2Enabled = - await this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - workspace.id, - ); - - if (isWorkspaceMigrationV2Enabled) { - const deletedViewGroup = await this.viewGroupV2Service.deleteOne({ - deleteViewGroupInput: { id }, - workspaceId: workspace.id, - }); - - return { success: isDefined(deletedViewGroup) }; - } - - const deletedViewGroup = await this.viewGroupService.delete( - id, - workspace.id, - ); + const deletedViewGroup = await this.viewGroupService.deleteOne({ + deleteViewGroupInput: { id }, + workspaceId: workspace.id, + }); return { success: isDefined(deletedViewGroup) }; } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-group/resolvers/view-group.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/view-group/resolvers/view-group.resolver.ts index 18d5029891..f3f6b5f2b0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-group/resolvers/view-group.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-group/resolvers/view-group.resolver.ts @@ -1,8 +1,6 @@ import { UseFilters, UseGuards } from '@nestjs/common'; import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; -import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; -import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard'; @@ -12,11 +10,6 @@ import { DeleteViewGroupInput } from 'src/engine/metadata-modules/view-group/dto import { DestroyViewGroupInput } from 'src/engine/metadata-modules/view-group/dtos/inputs/destroy-view-group.input'; import { UpdateViewGroupInput } from 'src/engine/metadata-modules/view-group/dtos/inputs/update-view-group.input'; import { ViewGroupDTO } from 'src/engine/metadata-modules/view-group/dtos/view-group.dto'; -import { - ViewGroupException, - ViewGroupExceptionCode, -} from 'src/engine/metadata-modules/view-group/exceptions/view-group.exception'; -import { ViewGroupV2Service } from 'src/engine/metadata-modules/view-group/services/view-group-v2.service'; import { ViewGroupService } from 'src/engine/metadata-modules/view-group/services/view-group.service'; import { CreateViewGroupPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/create-view-group-permission.guard'; import { DeleteViewGroupPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/delete-view-group-permission.guard'; @@ -28,11 +21,7 @@ import { ViewGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/view/ @UseFilters(ViewGraphqlApiExceptionFilter) @UseGuards(WorkspaceAuthGuard) export class ViewGroupResolver { - constructor( - private readonly viewGroupService: ViewGroupService, - private readonly featureFlagService: FeatureFlagService, - private readonly viewGroupV2Service: ViewGroupV2Service, - ) {} + constructor(private readonly viewGroupService: ViewGroupService) {} @Query(() => [ViewGroupDTO]) @UseGuards(NoPermissionGuard) @@ -63,21 +52,8 @@ export class ViewGroupResolver { @Args('input') createViewGroupInput: CreateViewGroupInput, @AuthWorkspace() { id: workspaceId }: WorkspaceEntity, ): Promise { - const isWorkspaceMigrationV2Enabled = - await this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - workspaceId, - ); - - if (isWorkspaceMigrationV2Enabled) { - return await this.viewGroupV2Service.createOne({ - createViewGroupInput, - workspaceId, - }); - } - - return this.viewGroupService.create({ - ...createViewGroupInput, + return await this.viewGroupService.createOne({ + createViewGroupInput, workspaceId, }); } @@ -89,20 +65,7 @@ export class ViewGroupResolver { createViewGroupInputs: CreateViewGroupInput[], @AuthWorkspace() { id: workspaceId }: WorkspaceEntity, ): Promise { - const isWorkspaceMigrationV2Enabled = - await this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - workspaceId, - ); - - if (!isWorkspaceMigrationV2Enabled) { - throw new ViewGroupException( - 'Not implemented in v1, please active IS_WORKSPACE_MIGRATION_V2_ENABLED', - ViewGroupExceptionCode.INVALID_VIEW_GROUP_DATA, - ); - } - - return await this.viewGroupV2Service.createMany({ + return await this.viewGroupService.createMany({ createViewGroupInputs, workspaceId, }); @@ -114,24 +77,10 @@ export class ViewGroupResolver { @Args('input') updateViewGroupInput: UpdateViewGroupInput, @AuthWorkspace() { id: workspaceId }: WorkspaceEntity, ): Promise { - const isWorkspaceMigrationV2Enabled = - await this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - workspaceId, - ); - - if (isWorkspaceMigrationV2Enabled) { - return await this.viewGroupV2Service.updateOne({ - updateViewGroupInput, - workspaceId, - }); - } - - return this.viewGroupService.update( - updateViewGroupInput.id, + return await this.viewGroupService.updateOne({ + updateViewGroupInput, workspaceId, - updateViewGroupInput.update, - ); + }); } @Mutation(() => ViewGroupDTO) @@ -140,20 +89,10 @@ export class ViewGroupResolver { @Args('input') deleteViewGroupInput: DeleteViewGroupInput, @AuthWorkspace() { id: workspaceId }: WorkspaceEntity, ): Promise { - const isWorkspaceMigrationV2Enabled = - await this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - workspaceId, - ); - - if (isWorkspaceMigrationV2Enabled) { - return await this.viewGroupV2Service.deleteOne({ - deleteViewGroupInput, - workspaceId, - }); - } - - return this.viewGroupService.delete(deleteViewGroupInput.id, workspaceId); + return await this.viewGroupService.deleteOne({ + deleteViewGroupInput, + workspaceId, + }); } @Mutation(() => ViewGroupDTO) @@ -162,19 +101,9 @@ export class ViewGroupResolver { @Args('input') destroyViewGroupInput: DestroyViewGroupInput, @AuthWorkspace() { id: workspaceId }: WorkspaceEntity, ): Promise { - const isWorkspaceMigrationV2Enabled = - await this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, - workspaceId, - ); - - if (isWorkspaceMigrationV2Enabled) { - return await this.viewGroupV2Service.destroyOne({ - destroyViewGroupInput, - workspaceId, - }); - } - - return this.viewGroupService.destroy(destroyViewGroupInput.id, workspaceId); + return await this.viewGroupService.destroyOne({ + destroyViewGroupInput, + workspaceId, + }); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-group/services/tests/view-group.service.spec.ts b/packages/twenty-server/src/engine/metadata-modules/view-group/services/tests/view-group.service.spec.ts deleted file mode 100644 index 6f6feb1652..0000000000 --- a/packages/twenty-server/src/engine/metadata-modules/view-group/services/tests/view-group.service.spec.ts +++ /dev/null @@ -1,327 +0,0 @@ -import { Test, type TestingModule } from '@nestjs/testing'; -import { getRepositoryToken } from '@nestjs/typeorm'; - -import { type Repository } from 'typeorm'; - -import { ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity'; -import { - ViewGroupException, - ViewGroupExceptionCode, - ViewGroupExceptionMessageKey, - generateViewGroupExceptionMessage, - generateViewGroupUserFriendlyExceptionMessage, -} from 'src/engine/metadata-modules/view-group/exceptions/view-group.exception'; -import { ViewGroupService } from 'src/engine/metadata-modules/view-group/services/view-group.service'; -import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; - -describe('ViewGroupService', () => { - let viewGroupService: ViewGroupService; - let viewGroupRepository: Repository; - - const mockViewGroup = { - id: 'view-group-id', - fieldMetadataId: 'field-id', - viewId: 'view-id', - workspaceId: 'workspace-id', - fieldValue: 'group-value', - isVisible: true, - position: 0, - createdAt: new Date(), - updatedAt: new Date(), - deletedAt: null, - } as ViewGroupEntity; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - ViewGroupService, - { - provide: getRepositoryToken(ViewGroupEntity), - useValue: { - find: jest.fn(), - findOne: jest.fn(), - create: jest.fn(), - save: jest.fn(), - softDelete: jest.fn(), - delete: jest.fn(), - }, - }, - { - provide: WorkspaceCacheStorageService, - useValue: { - flushGraphQLOperation: jest.fn(), - }, - }, - ], - }).compile(); - - viewGroupService = module.get(ViewGroupService); - viewGroupRepository = module.get>( - getRepositoryToken(ViewGroupEntity), - ); - }); - - it('should be defined', () => { - expect(viewGroupService).toBeDefined(); - }); - - describe('findByWorkspaceId', () => { - it('should return view groups for a workspace', async () => { - const workspaceId = 'workspace-id'; - const expectedViewGroups = [mockViewGroup]; - - jest - .spyOn(viewGroupRepository, 'find') - .mockResolvedValue(expectedViewGroups); - - const result = await viewGroupService.findByWorkspaceId(workspaceId); - - expect(viewGroupRepository.find).toHaveBeenCalledWith({ - where: { - workspaceId, - deletedAt: expect.anything(), - }, - order: { position: 'ASC' }, - relations: ['workspace', 'view'], - }); - expect(result).toEqual(expectedViewGroups); - }); - }); - - describe('findByViewId', () => { - it('should return view groups for a view', async () => { - const workspaceId = 'workspace-id'; - const viewId = 'view-id'; - const expectedViewGroups = [mockViewGroup]; - - jest - .spyOn(viewGroupRepository, 'find') - .mockResolvedValue(expectedViewGroups); - - const result = await viewGroupService.findByViewId(workspaceId, viewId); - - expect(viewGroupRepository.find).toHaveBeenCalledWith({ - where: { - workspaceId, - viewId, - deletedAt: expect.anything(), - }, - order: { position: 'ASC' }, - relations: ['workspace', 'view'], - }); - expect(result).toEqual(expectedViewGroups); - }); - }); - - describe('findById', () => { - it('should return a view group by id', async () => { - const id = 'view-group-id'; - const workspaceId = 'workspace-id'; - - jest - .spyOn(viewGroupRepository, 'findOne') - .mockResolvedValue(mockViewGroup); - - const result = await viewGroupService.findById(id, workspaceId); - - expect(viewGroupRepository.findOne).toHaveBeenCalledWith({ - where: { - id, - workspaceId, - deletedAt: expect.anything(), - }, - relations: ['workspace', 'view'], - }); - expect(result).toEqual(mockViewGroup); - }); - - it('should return null when view group is not found', async () => { - const id = 'non-existent-id'; - const workspaceId = 'workspace-id'; - - jest.spyOn(viewGroupRepository, 'findOne').mockResolvedValue(null); - - const result = await viewGroupService.findById(id, workspaceId); - - expect(result).toBeNull(); - }); - }); - - describe('create', () => { - const validViewGroupData = { - fieldMetadataId: 'field-id', - viewId: 'view-id', - workspaceId: 'workspace-id', - fieldValue: 'group-value', - isVisible: true, - position: 0, - }; - - it('should create a view group successfully', async () => { - jest.spyOn(viewGroupRepository, 'create').mockReturnValue(mockViewGroup); - jest.spyOn(viewGroupRepository, 'save').mockResolvedValue(mockViewGroup); - - const result = await viewGroupService.create(validViewGroupData); - - expect(viewGroupRepository.create).toHaveBeenCalledWith( - validViewGroupData, - ); - expect(viewGroupRepository.save).toHaveBeenCalledWith(mockViewGroup); - expect(result).toEqual(mockViewGroup); - }); - - it('should throw exception when workspaceId is missing', async () => { - const invalidData = { ...validViewGroupData, workspaceId: undefined }; - - await expect(viewGroupService.create(invalidData)).rejects.toThrow( - new ViewGroupException( - generateViewGroupExceptionMessage( - ViewGroupExceptionMessageKey.WORKSPACE_ID_REQUIRED, - ), - ViewGroupExceptionCode.INVALID_VIEW_GROUP_DATA, - { - userFriendlyMessage: generateViewGroupUserFriendlyExceptionMessage( - ViewGroupExceptionMessageKey.WORKSPACE_ID_REQUIRED, - ), - }, - ), - ); - }); - - it('should throw exception when viewId is missing', async () => { - const invalidData = { ...validViewGroupData, viewId: undefined }; - - await expect(viewGroupService.create(invalidData)).rejects.toThrow( - new ViewGroupException( - generateViewGroupExceptionMessage( - ViewGroupExceptionMessageKey.VIEW_ID_REQUIRED, - ), - ViewGroupExceptionCode.INVALID_VIEW_GROUP_DATA, - { - userFriendlyMessage: generateViewGroupUserFriendlyExceptionMessage( - ViewGroupExceptionMessageKey.VIEW_ID_REQUIRED, - ), - }, - ), - ); - }); - - it('should throw exception when fieldMetadataId is missing', async () => { - const invalidData = { ...validViewGroupData, fieldMetadataId: undefined }; - - await expect(viewGroupService.create(invalidData)).rejects.toThrow( - new ViewGroupException( - generateViewGroupExceptionMessage( - ViewGroupExceptionMessageKey.FIELD_METADATA_ID_REQUIRED, - ), - ViewGroupExceptionCode.INVALID_VIEW_GROUP_DATA, - { - userFriendlyMessage: generateViewGroupUserFriendlyExceptionMessage( - ViewGroupExceptionMessageKey.FIELD_METADATA_ID_REQUIRED, - ), - }, - ), - ); - }); - }); - - describe('update', () => { - it('should update a view group successfully', async () => { - const id = 'view-group-id'; - const workspaceId = 'workspace-id'; - const updateData = { isVisible: false }; - const updatedViewGroup = { ...mockViewGroup, ...updateData }; - - jest.spyOn(viewGroupService, 'findById').mockResolvedValue(mockViewGroup); - jest - .spyOn(viewGroupRepository, 'save') - .mockResolvedValue(updatedViewGroup); - - const result = await viewGroupService.update(id, workspaceId, updateData); - - expect(viewGroupService.findById).toHaveBeenCalledWith(id, workspaceId); - expect(viewGroupRepository.save).toHaveBeenCalledWith({ - id, - ...updateData, - }); - expect(result).toEqual({ ...mockViewGroup, ...updatedViewGroup }); - }); - - it('should throw exception when view group is not found', async () => { - const id = 'non-existent-id'; - const workspaceId = 'workspace-id'; - const updateData = { isVisible: false }; - - jest.spyOn(viewGroupService, 'findById').mockResolvedValue(null); - - await expect( - viewGroupService.update(id, workspaceId, updateData), - ).rejects.toThrow( - new ViewGroupException( - generateViewGroupExceptionMessage( - ViewGroupExceptionMessageKey.VIEW_GROUP_NOT_FOUND, - id, - ), - ViewGroupExceptionCode.VIEW_GROUP_NOT_FOUND, - ), - ); - }); - }); - - describe('delete', () => { - it('should delete a view group successfully', async () => { - const id = 'view-group-id'; - const workspaceId = 'workspace-id'; - - jest.spyOn(viewGroupService, 'findById').mockResolvedValue(mockViewGroup); - jest - .spyOn(viewGroupRepository, 'softDelete') - .mockResolvedValue({} as any); - - const result = await viewGroupService.delete(id, workspaceId); - - expect(viewGroupService.findById).toHaveBeenCalledWith(id, workspaceId); - expect(viewGroupRepository.softDelete).toHaveBeenCalledWith(id); - expect(result).toEqual(mockViewGroup); - }); - - it('should throw exception when view group is not found', async () => { - const id = 'non-existent-id'; - const workspaceId = 'workspace-id'; - - jest.spyOn(viewGroupService, 'findById').mockResolvedValue(null); - - await expect(viewGroupService.delete(id, workspaceId)).rejects.toThrow( - new ViewGroupException( - generateViewGroupExceptionMessage( - ViewGroupExceptionMessageKey.VIEW_GROUP_NOT_FOUND, - id, - ), - ViewGroupExceptionCode.VIEW_GROUP_NOT_FOUND, - ), - ); - }); - }); - - describe('destroy', () => { - it('should destroy a view group successfully', async () => { - const id = 'view-group-id'; - const workspaceId = 'workspace-id'; - - jest - .spyOn(viewGroupRepository, 'findOne') - .mockResolvedValue(mockViewGroup); - jest.spyOn(viewGroupRepository, 'delete').mockResolvedValue({} as any); - - const result = await viewGroupService.destroy(id, workspaceId); - - expect(viewGroupRepository.findOne).toHaveBeenCalledWith({ - where: { id, workspaceId }, - relations: ['workspace', 'view'], - withDeleted: true, - }); - expect(viewGroupRepository.delete).toHaveBeenCalledWith(id); - expect(result).toEqual(mockViewGroup); - }); - }); -}); diff --git a/packages/twenty-server/src/engine/metadata-modules/view-group/services/view-group-v2.service.ts b/packages/twenty-server/src/engine/metadata-modules/view-group/services/view-group-v2.service.ts deleted file mode 100644 index 7b7ffaa89c..0000000000 --- a/packages/twenty-server/src/engine/metadata-modules/view-group/services/view-group-v2.service.ts +++ /dev/null @@ -1,347 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { isDefined } from 'twenty-shared/utils'; - -import { ApplicationService } from 'src/engine/core-modules/application/application.service'; -import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; -import { computeFlatEntityMapsFromTo } from 'src/engine/metadata-modules/flat-entity/utils/compute-flat-entity-maps-from-to.util'; -import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util'; -import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util'; -import { fromCreateViewGroupInputToFlatViewGroupToCreate } from 'src/engine/metadata-modules/flat-view-group/utils/from-create-view-group-input-to-flat-view-group-to-create.util'; -import { fromDeleteViewGroupInputToFlatViewGroupOrThrow } from 'src/engine/metadata-modules/flat-view-group/utils/from-delete-view-group-input-to-flat-view-group-or-throw.util'; -import { fromDestroyViewGroupInputToFlatViewGroupOrThrow } from 'src/engine/metadata-modules/flat-view-group/utils/from-destroy-view-group-input-to-flat-view-group-or-throw.util'; -import { fromUpdateViewGroupInputToFlatViewGroupToUpdateOrThrow } from 'src/engine/metadata-modules/flat-view-group/utils/from-update-view-group-input-to-flat-view-group-to-update-or-throw.util'; -import { CreateViewGroupInput } from 'src/engine/metadata-modules/view-group/dtos/inputs/create-view-group.input'; -import { DeleteViewGroupInput } from 'src/engine/metadata-modules/view-group/dtos/inputs/delete-view-group.input'; -import { DestroyViewGroupInput } from 'src/engine/metadata-modules/view-group/dtos/inputs/destroy-view-group.input'; -import { UpdateViewGroupInput } from 'src/engine/metadata-modules/view-group/dtos/inputs/update-view-group.input'; -import { ViewGroupDTO } from 'src/engine/metadata-modules/view-group/dtos/view-group.dto'; -import { - ViewGroupException, - ViewGroupExceptionCode, -} from 'src/engine/metadata-modules/view-group/exceptions/view-group.exception'; -import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2'; -import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service'; - -@Injectable() -export class ViewGroupV2Service { - constructor( - private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService, - private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, - private readonly applicationService: ApplicationService, - ) {} - - async createOne({ - createViewGroupInput, - workspaceId, - }: { - createViewGroupInput: CreateViewGroupInput; - workspaceId: string; - }): Promise { - const [createdViewGroup] = await this.createMany({ - workspaceId, - createViewGroupInputs: [createViewGroupInput], - }); - - if (!isDefined(createdViewGroup)) { - throw new ViewGroupException( - 'Failed to create view group', - ViewGroupExceptionCode.INVALID_VIEW_GROUP_DATA, - ); - } - - return createdViewGroup; - } - - async createMany({ - createViewGroupInputs, - workspaceId, - }: { - createViewGroupInputs: CreateViewGroupInput[]; - workspaceId: string; - }): Promise { - if (createViewGroupInputs.length === 0) { - return []; - } - - const { workspaceCustomFlatApplication } = - await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( - { - workspaceId, - }, - ); - - const { - flatViewGroupMaps: existingFlatViewGroupMaps, - flatViewMaps, - flatFieldMetadataMaps, - } = await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( - { - workspaceId, - flatMapsKeys: [ - 'flatViewGroupMaps', - 'flatViewMaps', - 'flatFieldMetadataMaps', - ], - }, - ); - - const flatViewGroupsToCreate = createViewGroupInputs.map( - (createViewGroupInput) => - fromCreateViewGroupInputToFlatViewGroupToCreate({ - createViewGroupInput, - workspaceId, - workspaceCustomApplicationId: workspaceCustomFlatApplication.id, - }), - ); - - const validateAndBuildResult = - await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( - { - fromToAllFlatEntityMaps: { - flatViewGroupMaps: computeFlatEntityMapsFromTo({ - flatEntityMaps: existingFlatViewGroupMaps, - flatEntityToCreate: flatViewGroupsToCreate, - flatEntityToDelete: [], - flatEntityToUpdate: [], - }), - }, - dependencyAllFlatEntityMaps: { - flatFieldMetadataMaps, - flatViewMaps, - }, - buildOptions: { - isSystemBuild: false, - }, - workspaceId, - }, - ); - - if (isDefined(validateAndBuildResult)) { - throw new WorkspaceMigrationBuilderExceptionV2( - validateAndBuildResult, - 'Multiple validation errors occurred while creating view groups', - ); - } - - const { flatViewGroupMaps: recomputedExistingFlatViewGroupMaps } = - await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( - { - workspaceId, - flatMapsKeys: ['flatViewGroupMaps'], - }, - ); - - return findManyFlatEntityByIdInFlatEntityMapsOrThrow({ - flatEntityIds: flatViewGroupsToCreate.map((el) => el.id), - flatEntityMaps: recomputedExistingFlatViewGroupMaps, - }); - } - - async updateOne({ - updateViewGroupInput, - workspaceId, - }: { - workspaceId: string; - updateViewGroupInput: UpdateViewGroupInput; - }): Promise { - const { - flatViewGroupMaps: existingFlatViewGroupMaps, - flatViewMaps, - flatFieldMetadataMaps, - } = await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( - { - workspaceId, - flatMapsKeys: [ - 'flatViewGroupMaps', - 'flatViewMaps', - 'flatFieldMetadataMaps', - ], - }, - ); - - const optimisticallyUpdatedFlatViewGroup = - fromUpdateViewGroupInputToFlatViewGroupToUpdateOrThrow({ - flatViewGroupMaps: existingFlatViewGroupMaps, - updateViewGroupInput, - }); - - const validateAndBuildResult = - await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( - { - fromToAllFlatEntityMaps: { - flatViewGroupMaps: computeFlatEntityMapsFromTo({ - flatEntityMaps: existingFlatViewGroupMaps, - flatEntityToCreate: [], - flatEntityToDelete: [], - flatEntityToUpdate: [optimisticallyUpdatedFlatViewGroup], - }), - }, - dependencyAllFlatEntityMaps: { - flatViewMaps, - flatFieldMetadataMaps, - }, - buildOptions: { - isSystemBuild: false, - }, - workspaceId, - }, - ); - - if (isDefined(validateAndBuildResult)) { - throw new WorkspaceMigrationBuilderExceptionV2( - validateAndBuildResult, - 'Multiple validation errors occurred while updating view group', - ); - } - - const { flatViewGroupMaps: recomputedExistingFlatViewGroupMaps } = - await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( - { - workspaceId, - flatMapsKeys: ['flatViewGroupMaps'], - }, - ); - - return findFlatEntityByIdInFlatEntityMapsOrThrow({ - flatEntityId: optimisticallyUpdatedFlatViewGroup.id, - flatEntityMaps: recomputedExistingFlatViewGroupMaps, - }); - } - - async deleteOne({ - deleteViewGroupInput, - workspaceId, - }: { - deleteViewGroupInput: DeleteViewGroupInput; - workspaceId: string; - }): Promise { - const { - flatViewGroupMaps: existingFlatViewGroupMaps, - flatViewMaps, - flatFieldMetadataMaps, - } = await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( - { - workspaceId, - flatMapsKeys: [ - 'flatViewGroupMaps', - 'flatViewMaps', - 'flatFieldMetadataMaps', - ], - }, - ); - - const optimisticallyUpdatedFlatViewGroupWithDeletedAt = - fromDeleteViewGroupInputToFlatViewGroupOrThrow({ - flatViewGroupMaps: existingFlatViewGroupMaps, - deleteViewGroupInput, - }); - - const validateAndBuildResult = - await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( - { - fromToAllFlatEntityMaps: { - flatViewGroupMaps: computeFlatEntityMapsFromTo({ - flatEntityMaps: existingFlatViewGroupMaps, - flatEntityToCreate: [], - flatEntityToDelete: [], - flatEntityToUpdate: [ - optimisticallyUpdatedFlatViewGroupWithDeletedAt, - ], - }), - }, - dependencyAllFlatEntityMaps: { - flatFieldMetadataMaps, - flatViewMaps, - }, - buildOptions: { - isSystemBuild: false, - }, - workspaceId, - }, - ); - - if (isDefined(validateAndBuildResult)) { - throw new WorkspaceMigrationBuilderExceptionV2( - validateAndBuildResult, - 'Multiple validation errors occurred while deleting view group', - ); - } - - const { flatViewGroupMaps: recomputedExistingFlatViewGroupMaps } = - await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( - { - workspaceId, - flatMapsKeys: ['flatViewGroupMaps'], - }, - ); - - return findFlatEntityByIdInFlatEntityMapsOrThrow({ - flatEntityId: optimisticallyUpdatedFlatViewGroupWithDeletedAt.id, - flatEntityMaps: recomputedExistingFlatViewGroupMaps, - }); - } - - async destroyOne({ - destroyViewGroupInput, - workspaceId, - }: { - destroyViewGroupInput: DestroyViewGroupInput; - workspaceId: string; - }): Promise { - const { - flatViewGroupMaps: existingFlatViewGroupMaps, - flatViewMaps: existingFlatViewMaps, - flatFieldMetadataMaps: existingFlatFieldMetadataMaps, - } = await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( - { - workspaceId, - flatMapsKeys: [ - 'flatViewGroupMaps', - 'flatViewMaps', - 'flatFieldMetadataMaps', - ], - }, - ); - - const existingViewGroupToDelete = - fromDestroyViewGroupInputToFlatViewGroupOrThrow({ - destroyViewGroupInput, - flatViewGroupMaps: existingFlatViewGroupMaps, - }); - - const validateAndBuildResult = - await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( - { - fromToAllFlatEntityMaps: { - flatViewGroupMaps: computeFlatEntityMapsFromTo({ - flatEntityMaps: existingFlatViewGroupMaps, - flatEntityToCreate: [], - flatEntityToDelete: [existingViewGroupToDelete], - flatEntityToUpdate: [], - }), - }, - dependencyAllFlatEntityMaps: { - flatViewMaps: existingFlatViewMaps, - flatFieldMetadataMaps: existingFlatFieldMetadataMaps, - }, - buildOptions: { - isSystemBuild: false, - inferDeletionFromMissingEntities: { - viewGroup: true, - }, - }, - workspaceId, - }, - ); - - if (isDefined(validateAndBuildResult)) { - throw new WorkspaceMigrationBuilderExceptionV2( - validateAndBuildResult, - 'Multiple validation errors occurred while destroying view group', - ); - } - - return existingViewGroupToDelete; - } -} diff --git a/packages/twenty-server/src/engine/metadata-modules/view-group/services/view-group.service.ts b/packages/twenty-server/src/engine/metadata-modules/view-group/services/view-group.service.ts index 33e08ab332..893cf9d17e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-group/services/view-group.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-group/services/view-group.service.ts @@ -4,25 +4,352 @@ import { InjectRepository } from '@nestjs/typeorm'; import { isDefined } from 'twenty-shared/utils'; import { IsNull, Repository } from 'typeorm'; +import { ApplicationService } from 'src/engine/core-modules/application/application.service'; +import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; +import { computeFlatEntityMapsFromTo } from 'src/engine/metadata-modules/flat-entity/utils/compute-flat-entity-maps-from-to.util'; +import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util'; +import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util'; +import { fromCreateViewGroupInputToFlatViewGroupToCreate } from 'src/engine/metadata-modules/flat-view-group/utils/from-create-view-group-input-to-flat-view-group-to-create.util'; +import { fromDeleteViewGroupInputToFlatViewGroupOrThrow } from 'src/engine/metadata-modules/flat-view-group/utils/from-delete-view-group-input-to-flat-view-group-or-throw.util'; +import { fromDestroyViewGroupInputToFlatViewGroupOrThrow } from 'src/engine/metadata-modules/flat-view-group/utils/from-destroy-view-group-input-to-flat-view-group-or-throw.util'; +import { fromUpdateViewGroupInputToFlatViewGroupToUpdateOrThrow } from 'src/engine/metadata-modules/flat-view-group/utils/from-update-view-group-input-to-flat-view-group-to-update-or-throw.util'; +import { CreateViewGroupInput } from 'src/engine/metadata-modules/view-group/dtos/inputs/create-view-group.input'; +import { DeleteViewGroupInput } from 'src/engine/metadata-modules/view-group/dtos/inputs/delete-view-group.input'; +import { DestroyViewGroupInput } from 'src/engine/metadata-modules/view-group/dtos/inputs/destroy-view-group.input'; +import { UpdateViewGroupInput } from 'src/engine/metadata-modules/view-group/dtos/inputs/update-view-group.input'; +import { ViewGroupDTO } from 'src/engine/metadata-modules/view-group/dtos/view-group.dto'; import { ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity'; import { ViewGroupException, ViewGroupExceptionCode, - ViewGroupExceptionMessageKey, - generateViewGroupExceptionMessage, - generateViewGroupUserFriendlyExceptionMessage, } from 'src/engine/metadata-modules/view-group/exceptions/view-group.exception'; -import { FIND_ALL_CORE_VIEWS_GRAPHQL_OPERATION } from 'src/engine/metadata-modules/view/constants/find-all-core-views-graphql-operation.constant'; -import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; +import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2'; +import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service'; @Injectable() export class ViewGroupService { constructor( @InjectRepository(ViewGroupEntity) private readonly viewGroupRepository: Repository, - private readonly workspaceCacheStorageService: WorkspaceCacheStorageService, + private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService, + private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, + private readonly applicationService: ApplicationService, ) {} + async createOne({ + createViewGroupInput, + workspaceId, + }: { + createViewGroupInput: CreateViewGroupInput; + workspaceId: string; + }): Promise { + const [createdViewGroup] = await this.createMany({ + workspaceId, + createViewGroupInputs: [createViewGroupInput], + }); + + if (!isDefined(createdViewGroup)) { + throw new ViewGroupException( + 'Failed to create view group', + ViewGroupExceptionCode.INVALID_VIEW_GROUP_DATA, + ); + } + + return createdViewGroup; + } + + async createMany({ + createViewGroupInputs, + workspaceId, + }: { + createViewGroupInputs: CreateViewGroupInput[]; + workspaceId: string; + }): Promise { + if (createViewGroupInputs.length === 0) { + return []; + } + + const { workspaceCustomFlatApplication } = + await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( + { + workspaceId, + }, + ); + + const { + flatViewGroupMaps: existingFlatViewGroupMaps, + flatViewMaps, + flatFieldMetadataMaps, + } = await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: [ + 'flatViewGroupMaps', + 'flatViewMaps', + 'flatFieldMetadataMaps', + ], + }, + ); + + const flatViewGroupsToCreate = createViewGroupInputs.map( + (createViewGroupInput) => + fromCreateViewGroupInputToFlatViewGroupToCreate({ + createViewGroupInput, + workspaceId, + workspaceCustomApplicationId: workspaceCustomFlatApplication.id, + }), + ); + + const validateAndBuildResult = + await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( + { + fromToAllFlatEntityMaps: { + flatViewGroupMaps: computeFlatEntityMapsFromTo({ + flatEntityMaps: existingFlatViewGroupMaps, + flatEntityToCreate: flatViewGroupsToCreate, + flatEntityToDelete: [], + flatEntityToUpdate: [], + }), + }, + dependencyAllFlatEntityMaps: { + flatFieldMetadataMaps, + flatViewMaps, + }, + buildOptions: { + isSystemBuild: false, + }, + workspaceId, + }, + ); + + if (isDefined(validateAndBuildResult)) { + throw new WorkspaceMigrationBuilderExceptionV2( + validateAndBuildResult, + 'Multiple validation errors occurred while creating view groups', + ); + } + + const { flatViewGroupMaps: recomputedExistingFlatViewGroupMaps } = + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: ['flatViewGroupMaps'], + }, + ); + + return findManyFlatEntityByIdInFlatEntityMapsOrThrow({ + flatEntityIds: flatViewGroupsToCreate.map((el) => el.id), + flatEntityMaps: recomputedExistingFlatViewGroupMaps, + }); + } + + async updateOne({ + updateViewGroupInput, + workspaceId, + }: { + workspaceId: string; + updateViewGroupInput: UpdateViewGroupInput; + }): Promise { + const { + flatViewGroupMaps: existingFlatViewGroupMaps, + flatViewMaps, + flatFieldMetadataMaps, + } = await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: [ + 'flatViewGroupMaps', + 'flatViewMaps', + 'flatFieldMetadataMaps', + ], + }, + ); + + const optimisticallyUpdatedFlatViewGroup = + fromUpdateViewGroupInputToFlatViewGroupToUpdateOrThrow({ + flatViewGroupMaps: existingFlatViewGroupMaps, + updateViewGroupInput, + }); + + const validateAndBuildResult = + await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( + { + fromToAllFlatEntityMaps: { + flatViewGroupMaps: computeFlatEntityMapsFromTo({ + flatEntityMaps: existingFlatViewGroupMaps, + flatEntityToCreate: [], + flatEntityToDelete: [], + flatEntityToUpdate: [optimisticallyUpdatedFlatViewGroup], + }), + }, + dependencyAllFlatEntityMaps: { + flatViewMaps, + flatFieldMetadataMaps, + }, + buildOptions: { + isSystemBuild: false, + }, + workspaceId, + }, + ); + + if (isDefined(validateAndBuildResult)) { + throw new WorkspaceMigrationBuilderExceptionV2( + validateAndBuildResult, + 'Multiple validation errors occurred while updating view group', + ); + } + + const { flatViewGroupMaps: recomputedExistingFlatViewGroupMaps } = + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: ['flatViewGroupMaps'], + }, + ); + + return findFlatEntityByIdInFlatEntityMapsOrThrow({ + flatEntityId: optimisticallyUpdatedFlatViewGroup.id, + flatEntityMaps: recomputedExistingFlatViewGroupMaps, + }); + } + + async deleteOne({ + deleteViewGroupInput, + workspaceId, + }: { + deleteViewGroupInput: DeleteViewGroupInput; + workspaceId: string; + }): Promise { + const { + flatViewGroupMaps: existingFlatViewGroupMaps, + flatViewMaps, + flatFieldMetadataMaps, + } = await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: [ + 'flatViewGroupMaps', + 'flatViewMaps', + 'flatFieldMetadataMaps', + ], + }, + ); + + const optimisticallyUpdatedFlatViewGroupWithDeletedAt = + fromDeleteViewGroupInputToFlatViewGroupOrThrow({ + flatViewGroupMaps: existingFlatViewGroupMaps, + deleteViewGroupInput, + }); + + const validateAndBuildResult = + await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( + { + fromToAllFlatEntityMaps: { + flatViewGroupMaps: computeFlatEntityMapsFromTo({ + flatEntityMaps: existingFlatViewGroupMaps, + flatEntityToCreate: [], + flatEntityToDelete: [], + flatEntityToUpdate: [ + optimisticallyUpdatedFlatViewGroupWithDeletedAt, + ], + }), + }, + dependencyAllFlatEntityMaps: { + flatFieldMetadataMaps, + flatViewMaps, + }, + buildOptions: { + isSystemBuild: false, + }, + workspaceId, + }, + ); + + if (isDefined(validateAndBuildResult)) { + throw new WorkspaceMigrationBuilderExceptionV2( + validateAndBuildResult, + 'Multiple validation errors occurred while deleting view group', + ); + } + + const { flatViewGroupMaps: recomputedExistingFlatViewGroupMaps } = + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: ['flatViewGroupMaps'], + }, + ); + + return findFlatEntityByIdInFlatEntityMapsOrThrow({ + flatEntityId: optimisticallyUpdatedFlatViewGroupWithDeletedAt.id, + flatEntityMaps: recomputedExistingFlatViewGroupMaps, + }); + } + + async destroyOne({ + destroyViewGroupInput, + workspaceId, + }: { + destroyViewGroupInput: DestroyViewGroupInput; + workspaceId: string; + }): Promise { + const { + flatViewGroupMaps: existingFlatViewGroupMaps, + flatViewMaps: existingFlatViewMaps, + flatFieldMetadataMaps: existingFlatFieldMetadataMaps, + } = await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: [ + 'flatViewGroupMaps', + 'flatViewMaps', + 'flatFieldMetadataMaps', + ], + }, + ); + + const existingViewGroupToDelete = + fromDestroyViewGroupInputToFlatViewGroupOrThrow({ + destroyViewGroupInput, + flatViewGroupMaps: existingFlatViewGroupMaps, + }); + + const validateAndBuildResult = + await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( + { + fromToAllFlatEntityMaps: { + flatViewGroupMaps: computeFlatEntityMapsFromTo({ + flatEntityMaps: existingFlatViewGroupMaps, + flatEntityToCreate: [], + flatEntityToDelete: [existingViewGroupToDelete], + flatEntityToUpdate: [], + }), + }, + dependencyAllFlatEntityMaps: { + flatViewMaps: existingFlatViewMaps, + flatFieldMetadataMaps: existingFlatFieldMetadataMaps, + }, + buildOptions: { + isSystemBuild: false, + inferDeletionFromMissingEntities: { + viewGroup: true, + }, + }, + workspaceId, + }, + ); + + if (isDefined(validateAndBuildResult)) { + throw new WorkspaceMigrationBuilderExceptionV2( + validateAndBuildResult, + 'Multiple validation errors occurred while destroying view group', + ); + } + + return existingViewGroupToDelete; + } + async findByWorkspaceId(workspaceId: string): Promise { return this.viewGroupRepository.find({ where: { @@ -64,141 +391,4 @@ export class ViewGroupService { return viewGroup || null; } - - async create( - viewGroupData: Partial, - ): Promise { - if (!isDefined(viewGroupData.workspaceId)) { - throw new ViewGroupException( - generateViewGroupExceptionMessage( - ViewGroupExceptionMessageKey.WORKSPACE_ID_REQUIRED, - ), - ViewGroupExceptionCode.INVALID_VIEW_GROUP_DATA, - { - userFriendlyMessage: generateViewGroupUserFriendlyExceptionMessage( - ViewGroupExceptionMessageKey.WORKSPACE_ID_REQUIRED, - ), - }, - ); - } - - if (!isDefined(viewGroupData.viewId)) { - throw new ViewGroupException( - generateViewGroupExceptionMessage( - ViewGroupExceptionMessageKey.VIEW_ID_REQUIRED, - ), - ViewGroupExceptionCode.INVALID_VIEW_GROUP_DATA, - { - userFriendlyMessage: generateViewGroupUserFriendlyExceptionMessage( - ViewGroupExceptionMessageKey.VIEW_ID_REQUIRED, - ), - }, - ); - } - - if (!isDefined(viewGroupData.fieldMetadataId)) { - throw new ViewGroupException( - generateViewGroupExceptionMessage( - ViewGroupExceptionMessageKey.FIELD_METADATA_ID_REQUIRED, - ), - ViewGroupExceptionCode.INVALID_VIEW_GROUP_DATA, - { - userFriendlyMessage: generateViewGroupUserFriendlyExceptionMessage( - ViewGroupExceptionMessageKey.FIELD_METADATA_ID_REQUIRED, - ), - }, - ); - } - - const viewGroup = this.viewGroupRepository.create(viewGroupData); - - await this.flushGraphQLCache(viewGroupData.workspaceId); - - const savedViewGroup = await this.viewGroupRepository.save(viewGroup); - - await this.flushGraphQLCache(viewGroupData.workspaceId); - - return savedViewGroup; - } - - async update( - id: string, - workspaceId: string, - updateData: Partial, - ): Promise { - const existingViewGroup = await this.findById(id, workspaceId); - - if (!isDefined(existingViewGroup)) { - throw new ViewGroupException( - generateViewGroupExceptionMessage( - ViewGroupExceptionMessageKey.VIEW_GROUP_NOT_FOUND, - id, - ), - ViewGroupExceptionCode.VIEW_GROUP_NOT_FOUND, - ); - } - - const updatedViewGroup = await this.viewGroupRepository.save({ - id, - ...updateData, - }); - - await this.flushGraphQLCache(workspaceId); - - return { ...existingViewGroup, ...updatedViewGroup }; - } - - async delete(id: string, workspaceId: string): Promise { - const viewGroup = await this.findById(id, workspaceId); - - if (!isDefined(viewGroup)) { - throw new ViewGroupException( - generateViewGroupExceptionMessage( - ViewGroupExceptionMessageKey.VIEW_GROUP_NOT_FOUND, - id, - ), - ViewGroupExceptionCode.VIEW_GROUP_NOT_FOUND, - ); - } - - await this.viewGroupRepository.softDelete(id); - - await this.flushGraphQLCache(workspaceId); - - return viewGroup; - } - - async destroy(id: string, workspaceId: string): Promise { - const viewGroup = await this.viewGroupRepository.findOne({ - where: { - id, - workspaceId, - }, - relations: ['workspace', 'view'], - withDeleted: true, - }); - - if (!isDefined(viewGroup)) { - throw new ViewGroupException( - generateViewGroupExceptionMessage( - ViewGroupExceptionMessageKey.VIEW_GROUP_NOT_FOUND, - id, - ), - ViewGroupExceptionCode.VIEW_GROUP_NOT_FOUND, - ); - } - - await this.viewGroupRepository.delete(id); - - await this.flushGraphQLCache(workspaceId); - - return viewGroup; - } - - private async flushGraphQLCache(workspaceId: string): Promise { - await this.workspaceCacheStorageService.flushGraphQLOperation({ - operationName: FIND_ALL_CORE_VIEWS_GRAPHQL_OPERATION, - workspaceId, - }); - } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-group/view-group.module.ts b/packages/twenty-server/src/engine/metadata-modules/view-group/view-group.module.ts index 9fe5ae2de8..ce2cf9ae34 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-group/view-group.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-group/view-group.module.ts @@ -8,7 +8,6 @@ import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permi import { ViewGroupController } from 'src/engine/metadata-modules/view-group/controllers/view-group.controller'; import { ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity'; import { ViewGroupResolver } from 'src/engine/metadata-modules/view-group/resolvers/view-group.resolver'; -import { ViewGroupV2Service } from 'src/engine/metadata-modules/view-group/services/view-group-v2.service'; import { ViewGroupService } from 'src/engine/metadata-modules/view-group/services/view-group.service'; import { ViewPermissionsModule } from 'src/engine/metadata-modules/view-permissions/view-permissions.module'; import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity'; @@ -27,7 +26,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa ViewPermissionsModule, ], controllers: [ViewGroupController], - providers: [ViewGroupService, ViewGroupV2Service, ViewGroupResolver], + providers: [ViewGroupService, ViewGroupResolver], exports: [ViewGroupService], }) export class ViewGroupModule {}