Introducing view field group syncable entity (#17867)

## Context
Introduces a new viewFieldGroup entity that allows grouping view fields
into sections (e.g. "General", "Additional", "Other") within a view.

The page layout fields widget needs a way to organize fields into
sections. Today, views have no concept of field grouping. This PR
introduces the viewFieldGroup entity which sits between a view and its
viewFields, enabling section-based organization.

<img width="401" height="724" alt="Layout - V2 (customize visibility)"
src="https://github.com/user-attachments/assets/6376e2ab-44db-42bf-9d2c-758f56f6b548"
/>

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Weiko
2026-02-11 22:02:58 +01:00
committed by GitHub
parent ed66fbd71b
commit 6aca1dd013
126 changed files with 3593 additions and 115 deletions
@@ -0,0 +1,61 @@
import { type MigrationInterface, type QueryRunner } from 'typeorm';
export class AddViewFieldGroup1770818941843 implements MigrationInterface {
name = 'AddViewFieldGroup1770818941843';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "core"."viewFieldGroup" ("workspaceId" uuid NOT NULL, "universalIdentifier" uuid NOT NULL, "applicationId" uuid NOT NULL, "id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" text NOT NULL, "position" double precision NOT NULL DEFAULT '0', "isVisible" boolean NOT NULL DEFAULT true, "viewId" uuid NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP WITH TIME ZONE, CONSTRAINT "PK_006f1cb78ab9eeef56c3e305009" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE UNIQUE INDEX "IDX_e88d35604c4445b16e682edb30" ON "core"."viewFieldGroup" ("workspaceId", "universalIdentifier") `,
);
await queryRunner.query(
`CREATE INDEX "IDX_VIEW_FIELD_GROUP_VIEW_ID" ON "core"."viewFieldGroup" ("viewId") `,
);
await queryRunner.query(
`CREATE INDEX "IDX_VIEW_FIELD_GROUP_WORKSPACE_ID_VIEW_ID" ON "core"."viewFieldGroup" ("workspaceId", "viewId") `,
);
await queryRunner.query(
`ALTER TABLE "core"."viewField" ADD "viewFieldGroupId" uuid`,
);
await queryRunner.query(
`ALTER TABLE "core"."viewFieldGroup" ADD CONSTRAINT "FK_118208b32ebf53be5aaede9c9cf" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "core"."viewFieldGroup" ADD CONSTRAINT "FK_38ec9201914a42386e5cdaa6521" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "core"."viewFieldGroup" ADD CONSTRAINT "FK_15c7197294c08e6e780d9734c99" FOREIGN KEY ("viewId") REFERENCES "core"."view"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "core"."viewField" ADD CONSTRAINT "FK_d6f7c88260b1d4eaa8ad0f13c26" FOREIGN KEY ("viewFieldGroupId") REFERENCES "core"."viewFieldGroup"("id") ON DELETE SET NULL ON UPDATE NO ACTION`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "core"."viewField" DROP CONSTRAINT "FK_d6f7c88260b1d4eaa8ad0f13c26"`,
);
await queryRunner.query(
`ALTER TABLE "core"."viewFieldGroup" DROP CONSTRAINT "FK_15c7197294c08e6e780d9734c99"`,
);
await queryRunner.query(
`ALTER TABLE "core"."viewFieldGroup" DROP CONSTRAINT "FK_38ec9201914a42386e5cdaa6521"`,
);
await queryRunner.query(
`ALTER TABLE "core"."viewFieldGroup" DROP CONSTRAINT "FK_118208b32ebf53be5aaede9c9cf"`,
);
await queryRunner.query(
`ALTER TABLE "core"."viewField" DROP COLUMN "viewFieldGroupId"`,
);
await queryRunner.query(
`DROP INDEX "core"."IDX_VIEW_FIELD_GROUP_WORKSPACE_ID_VIEW_ID"`,
);
await queryRunner.query(`DROP INDEX "core"."IDX_VIEW_FIELD_GROUP_VIEW_ID"`);
await queryRunner.query(
`DROP INDEX "core"."IDX_e88d35604c4445b16e682edb30"`,
);
await queryRunner.query(`DROP TABLE "core"."viewFieldGroup"`);
}
}
@@ -7,12 +7,16 @@ import {
type MorphRelationLoaderPayload,
type ObjectMetadataLoaderPayload,
type RelationLoaderPayload,
type ViewFieldGroupsByViewIdLoaderPayload,
type ViewFieldsByViewFieldGroupIdLoaderPayload,
} from 'src/engine/dataloaders/dataloader.service';
import { type FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto';
import { type RelationDTO } from 'src/engine/metadata-modules/field-metadata/dtos/relation.dto';
import { type IndexFieldMetadataDTO } from 'src/engine/metadata-modules/index-metadata/dtos/index-field-metadata.dto';
import { type IndexMetadataDTO } from 'src/engine/metadata-modules/index-metadata/dtos/index-metadata.dto';
import { type ObjectMetadataDTO } from 'src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto';
import { type ViewFieldGroupDTO } from 'src/engine/metadata-modules/view-field-group/dtos/view-field-group.dto';
import { type ViewFieldDTO } from 'src/engine/metadata-modules/view-field/dtos/view-field.dto';
export interface IDataloaders {
relationLoader: DataLoader<RelationLoaderPayload, RelationDTO | null>;
@@ -41,4 +45,14 @@ export interface IDataloaders {
ObjectMetadataLoaderPayload,
ObjectMetadataDTO | null
>;
viewFieldGroupsByViewIdLoader: DataLoader<
ViewFieldGroupsByViewIdLoaderPayload,
ViewFieldGroupDTO[]
>;
viewFieldsByViewFieldGroupIdLoader: DataLoader<
ViewFieldsByViewFieldGroupIdLoaderPayload,
ViewFieldDTO[]
>;
}
@@ -18,6 +18,7 @@ import { resolveFieldMetadataStandardOverride } from 'src/engine/metadata-module
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { findManyFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps.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 { findAllOthersMorphRelationFlatFieldMetadatasOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-all-others-morph-relation-flat-field-metadatas-or-throw.util';
import { fromFlatFieldMetadataToFieldMetadataDto } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-flat-field-metadata-to-field-metadata-dto.util';
@@ -26,6 +27,8 @@ import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-fiel
import { isMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util';
import { fromFlatObjectMetadataToObjectMetadataDto } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-to-object-metadata-dto.util';
import { getMorphNameFromMorphFieldMetadataName } from 'src/engine/metadata-modules/flat-object-metadata/utils/get-morph-name-from-morph-field-metadata-name.util';
import { fromFlatViewFieldGroupToViewFieldGroupDto } from 'src/engine/metadata-modules/view-field-group/utils/from-flat-view-field-group-to-view-field-group-dto.util';
import { fromFlatViewFieldToViewFieldDto } from 'src/engine/metadata-modules/view-field/utils/from-flat-view-field-to-view-field-dto.util';
import { type IndexFieldMetadataDTO } from 'src/engine/metadata-modules/index-metadata/dtos/index-field-metadata.dto';
import { type IndexMetadataDTO } from 'src/engine/metadata-modules/index-metadata/dtos/index-metadata.dto';
import { ObjectMetadataDTO } from 'src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto';
@@ -70,6 +73,16 @@ export type ObjectMetadataLoaderPayload = {
objectMetadataId: string;
};
export type ViewFieldGroupsByViewIdLoaderPayload = {
workspaceId: string;
viewId: string;
};
export type ViewFieldsByViewFieldGroupIdLoaderPayload = {
workspaceId: string;
viewFieldGroupId: string;
};
@Injectable()
export class DataloaderService {
constructor(
@@ -84,6 +97,10 @@ export class DataloaderService {
const indexMetadataLoader = this.createIndexMetadataLoader();
const indexFieldMetadataLoader = this.createIndexFieldMetadataLoader();
const objectMetadataLoader = this.createObjectMetadataLoader();
const viewFieldGroupsByViewIdLoader =
this.createViewFieldGroupsByViewIdLoader();
const viewFieldsByViewFieldGroupIdLoader =
this.createViewFieldsByViewFieldGroupIdLoader();
return {
relationLoader,
@@ -92,6 +109,8 @@ export class DataloaderService {
indexMetadataLoader,
indexFieldMetadataLoader,
objectMetadataLoader,
viewFieldGroupsByViewIdLoader,
viewFieldsByViewFieldGroupIdLoader,
};
}
@@ -515,4 +534,74 @@ export class DataloaderService {
});
});
}
private createViewFieldGroupsByViewIdLoader() {
return new DataLoader<
ViewFieldGroupsByViewIdLoaderPayload,
ReturnType<typeof fromFlatViewFieldGroupToViewFieldGroupDto>[]
>(async (dataLoaderParams: ViewFieldGroupsByViewIdLoaderPayload[]) => {
const workspaceId = dataLoaderParams[0].workspaceId;
const { flatViewMaps, flatViewFieldGroupMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatViewMaps', 'flatViewFieldGroupMaps'],
},
);
return dataLoaderParams.map(({ viewId }) => {
const flatView = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: viewId,
flatEntityMaps: flatViewMaps,
});
if (!isDefined(flatView)) {
return [];
}
return findManyFlatEntityByIdInFlatEntityMaps({
flatEntityIds: flatView.viewFieldGroupIds,
flatEntityMaps: flatViewFieldGroupMaps,
})
.filter((flatViewFieldGroup) => flatViewFieldGroup.deletedAt === null)
.map(fromFlatViewFieldGroupToViewFieldGroupDto);
});
});
}
private createViewFieldsByViewFieldGroupIdLoader() {
return new DataLoader<
ViewFieldsByViewFieldGroupIdLoaderPayload,
ReturnType<typeof fromFlatViewFieldToViewFieldDto>[]
>(async (dataLoaderParams: ViewFieldsByViewFieldGroupIdLoaderPayload[]) => {
const workspaceId = dataLoaderParams[0].workspaceId;
const { flatViewFieldGroupMaps, flatViewFieldMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatViewFieldGroupMaps', 'flatViewFieldMaps'],
},
);
return dataLoaderParams.map(({ viewFieldGroupId }) => {
const flatViewFieldGroup = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: viewFieldGroupId,
flatEntityMaps: flatViewFieldGroupMaps,
});
if (!isDefined(flatViewFieldGroup)) {
return [];
}
return findManyFlatEntityByIdInFlatEntityMaps({
flatEntityIds: flatViewFieldGroup.viewFieldIds,
flatEntityMaps: flatViewFieldMaps,
})
.filter((flatViewField) => flatViewField.deletedAt === null)
.map(fromFlatViewFieldToViewFieldDto);
});
});
}
}
@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`ALL_UNIVERSAL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY should match snapshot 1`] = `
{
@@ -246,6 +246,16 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY should ma
"size",
"position",
"aggregateOperation",
"viewFieldGroupUniversalIdentifier",
"deletedAt",
],
"propertiesToStringify": [],
},
"viewFieldGroup": {
"propertiesToCompare": [
"name",
"position",
"isVisible",
"deletedAt",
],
"propertiesToStringify": [],
@@ -112,6 +112,16 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
size: { toStringify: false, universalProperty: undefined },
position: { toStringify: false, universalProperty: undefined },
aggregateOperation: { toStringify: false, universalProperty: undefined },
viewFieldGroupId: {
toStringify: false,
universalProperty: 'viewFieldGroupUniversalIdentifier',
},
deletedAt: { toStringify: false, universalProperty: undefined },
},
viewFieldGroup: {
name: { toStringify: false, universalProperty: undefined },
position: { toStringify: false, universalProperty: undefined },
isVisible: { toStringify: false, universalProperty: undefined },
deletedAt: { toStringify: false, universalProperty: undefined },
},
viewGroup: {
@@ -18,6 +18,7 @@ import { RowLevelPermissionPredicateGroupEntity } from 'src/engine/metadata-modu
import { RowLevelPermissionPredicateEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate.entity';
import { LogicFunctionEntity } from 'src/engine/metadata-modules/logic-function/logic-function.entity';
import { SkillEntity } from 'src/engine/metadata-modules/skill/entities/skill.entity';
import { ViewFieldGroupEntity } from 'src/engine/metadata-modules/view-field-group/entities/view-field-group.entity';
import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
import { ViewFilterGroupEntity } from 'src/engine/metadata-modules/view-filter-group/entities/view-filter-group.entity';
import { ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity';
@@ -26,6 +27,7 @@ import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entit
export const ALL_METADATA_ENTITY_BY_METADATA_NAME = {
viewField: ViewFieldEntity,
viewFieldGroup: ViewFieldGroupEntity,
viewFilter: ViewFilterEntity,
viewGroup: ViewGroupEntity,
viewFilterGroup: ViewFilterGroupEntity,
@@ -221,6 +221,7 @@ export const ALL_METADATA_RELATIONS = {
metadataName: 'viewFilterGroup',
},
viewGroups: { metadataName: 'viewGroup' },
viewFieldGroups: { metadataName: 'viewFieldGroup' },
// @ts-expect-error TODO migrate viewSort to v2
viewSorts: null,
},
@@ -239,11 +240,32 @@ export const ALL_METADATA_RELATIONS = {
foreignKey: 'viewId',
isNullable: false,
},
viewFieldGroup: {
metadataName: 'viewFieldGroup',
flatEntityForeignKeyAggregator: 'viewFieldIds',
foreignKey: 'viewFieldGroupId',
isNullable: true,
},
workspace: null,
application: null,
},
oneToMany: {},
},
viewFieldGroup: {
manyToOne: {
view: {
metadataName: 'view',
flatEntityForeignKeyAggregator: 'viewFieldGroupIds',
foreignKey: 'viewId',
isNullable: false,
},
workspace: null,
application: null,
},
oneToMany: {
viewFields: { metadataName: 'viewField' },
},
},
viewFilter: {
manyToOne: {
fieldMetadata: {
@@ -27,6 +27,10 @@ export const ALL_METADATA_REQUIRED_METADATA_FOR_VALIDATION = {
view: true,
fieldMetadata: true,
objectMetadata: true,
viewFieldGroup: true,
},
viewFieldGroup: {
view: true,
},
index: {
objectMetadata: true,
@@ -12,6 +12,7 @@ import { WorkspaceFlatPageLayoutWidgetMapCacheService } from 'src/engine/metadat
import { WorkspaceFlatPageLayoutMapCacheService } from 'src/engine/metadata-modules/flat-page-layout/services/workspace-flat-page-layout-map-cache.service';
import { WorkspaceFlatRowLevelPermissionPredicateGroupMapCacheService } from 'src/engine/metadata-modules/flat-row-level-permission-predicate/services/workspace-flat-row-level-permission-predicate-group-map-cache.service';
import { WorkspaceFlatRowLevelPermissionPredicateMapCacheService } from 'src/engine/metadata-modules/flat-row-level-permission-predicate/services/workspace-flat-row-level-permission-predicate-map-cache.service';
import { WorkspaceFlatViewFieldGroupMapCacheService } from 'src/engine/metadata-modules/flat-view-field-group/services/workspace-flat-view-field-group-map-cache.service';
import { WorkspaceFlatViewFieldMapCacheService } from 'src/engine/metadata-modules/flat-view-field/services/workspace-flat-view-field-map-cache.service';
import { WorkspaceFlatViewFilterGroupMapCacheService } from 'src/engine/metadata-modules/flat-view-filter-group/services/workspace-flat-view-filter-group-map-cache.service';
import { WorkspaceFlatViewFilterMapCacheService } from 'src/engine/metadata-modules/flat-view-filter/services/workspace-flat-view-filter-map-cache.service';
@@ -26,6 +27,7 @@ import { PageLayoutEntity } from 'src/engine/metadata-modules/page-layout/entiti
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
import { RowLevelPermissionPredicateGroupEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate-group.entity';
import { RowLevelPermissionPredicateEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate.entity';
import { ViewFieldGroupEntity } from 'src/engine/metadata-modules/view-field-group/entities/view-field-group.entity';
import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
import { ViewFilterGroupEntity } from 'src/engine/metadata-modules/view-filter-group/entities/view-filter-group.entity';
import { ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity';
@@ -39,6 +41,7 @@ import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache
TypeOrmModule.forFeature([
ViewEntity,
ViewFieldEntity,
ViewFieldGroupEntity,
ViewFilterEntity,
ViewFilterGroupEntity,
ViewGroupEntity,
@@ -60,6 +63,7 @@ import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache
WorkspaceFlatObjectMetadataMapCacheService,
WorkspaceFlatViewMapCacheService,
WorkspaceFlatViewFieldMapCacheService,
WorkspaceFlatViewFieldGroupMapCacheService,
WorkspaceFlatViewFilterMapCacheService,
WorkspaceFlatViewFilterGroupMapCacheService,
WorkspaceFlatIndexMapCacheService,
@@ -76,6 +80,7 @@ import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache
WorkspaceFlatObjectMetadataMapCacheService,
WorkspaceFlatViewMapCacheService,
WorkspaceFlatViewFieldMapCacheService,
WorkspaceFlatViewFieldGroupMapCacheService,
WorkspaceFlatViewFilterMapCacheService,
WorkspaceFlatViewFilterGroupMapCacheService,
WorkspaceFlatIndexMapCacheService,
@@ -23,6 +23,8 @@ import { type FlatRoleTarget } from 'src/engine/metadata-modules/flat-role-targe
import { type FlatRole } from 'src/engine/metadata-modules/flat-role/types/flat-role.type';
import { type FlatSkillMaps } from 'src/engine/metadata-modules/flat-skill/types/flat-skill-maps.type';
import { type FlatSkill } from 'src/engine/metadata-modules/flat-skill/types/flat-skill.type';
import { type FlatViewFieldGroupMaps } from 'src/engine/metadata-modules/flat-view-field-group/types/flat-view-field-group-maps.type';
import { type FlatViewFieldGroup } from 'src/engine/metadata-modules/flat-view-field-group/types/flat-view-field-group.type';
import { type FlatViewFieldMaps } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field-maps.type';
import { type FlatViewField } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field.type';
import { type FlatViewFilterGroupMaps } from 'src/engine/metadata-modules/flat-view-filter-group/types/flat-view-filter-group-maps.type';
@@ -57,6 +59,7 @@ import { type UniversalFlatRoleTarget } from 'src/engine/workspace-manager/works
import { type UniversalFlatRowLevelPermissionPredicate } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-row-level-permission-predicate.type';
import { type UniversalFlatRowLevelPermissionPredicateGroup } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-row-level-permission-predicate-group.type';
import { type UniversalFlatSkill } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-skill.type';
import { type UniversalFlatViewFieldGroup } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-field-group.type';
import { type UniversalFlatViewField } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-field.type';
import { type UniversalFlatViewFilterGroup } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-filter-group.type';
import { type UniversalFlatViewFilter } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-filter.type';
@@ -199,6 +202,14 @@ import {
type UniversalDeleteViewFieldAction,
type UniversalUpdateViewFieldAction,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-field/types/workspace-migration-view-field-action.type';
import {
type FlatCreateViewFieldGroupAction,
type FlatDeleteViewFieldGroupAction,
type FlatUpdateViewFieldGroupAction,
type UniversalCreateViewFieldGroupAction,
type UniversalDeleteViewFieldGroupAction,
type UniversalUpdateViewFieldGroupAction,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-field-group/types/workspace-migration-view-field-group-action.type';
import {
type FlatCreateViewFilterGroupAction,
type FlatDeleteViewFilterGroupAction,
@@ -308,6 +319,22 @@ export type AllFlatEntityTypesByMetadataName = {
universalFlatEntity: UniversalFlatViewField;
entity: MetadataEntity<'viewField'>;
};
viewFieldGroup: {
flatEntityMaps: FlatViewFieldGroupMaps;
universalActions: {
create: UniversalCreateViewFieldGroupAction;
update: UniversalUpdateViewFieldGroupAction;
delete: UniversalDeleteViewFieldGroupAction;
};
flatActions: {
create: FlatCreateViewFieldGroupAction;
update: FlatUpdateViewFieldGroupAction;
delete: FlatDeleteViewFieldGroupAction;
};
flatEntity: FlatViewFieldGroup;
universalFlatEntity: UniversalFlatViewFieldGroup;
entity: MetadataEntity<'viewFieldGroup'>;
};
viewGroup: {
flatEntityMaps: FlatViewGroupMaps;
universalActions: {
@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`getMetadataRelatedMetadataNames should return related metadata names for agent 1`] = `[]`;
@@ -108,6 +108,7 @@ exports[`getMetadataRelatedMetadataNames should return related metadata names fo
"viewFilter",
"viewFilterGroup",
"viewGroup",
"viewFieldGroup",
]
`;
@@ -115,6 +116,14 @@ exports[`getMetadataRelatedMetadataNames should return related metadata names fo
[
"fieldMetadata",
"view",
"viewFieldGroup",
]
`;
exports[`getMetadataRelatedMetadataNames should return related metadata names for viewFieldGroup 1`] = `
[
"view",
"viewField",
]
`;
@@ -4,10 +4,10 @@ exports[`sortMetadataNamesChildrenFirst should return metadata names sorted with
[
"rowLevelPermissionPredicate",
"navigationMenuItem",
"viewField",
"viewFilter",
"commandMenuItem",
"pageLayoutWidget",
"viewField",
"index",
"pageLayout",
"roleTarget",
@@ -18,9 +18,10 @@ exports[`sortMetadataNamesChildrenFirst should return metadata names sorted with
"logicFunction",
"pageLayoutTab",
"skill",
"view",
"viewFieldGroup",
"viewFilterGroup",
"webhook",
"view",
"fieldMetadata",
"role",
"objectMetadata",
@@ -80,6 +80,8 @@ export const recomputeViewFieldIdentifierAfterFlatObjectIdentifierUpdate = ({
deletedAt: null,
universalIdentifier: viewFieldId,
aggregateOperation: null,
viewFieldGroupId: null,
viewFieldGroupUniversalIdentifier: null,
applicationId: existingFlatObjectMetadata.applicationId,
applicationUniversalIdentifier:
existingFlatObjectMetadata.applicationUniversalIdentifier,
@@ -0,0 +1,8 @@
import { type MetadataEntityPropertyName } from 'src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant';
export const FLAT_VIEW_FIELD_GROUP_EDITABLE_PROPERTIES = [
'name',
'position',
'isVisible',
'deletedAt',
] as const satisfies MetadataEntityPropertyName<'viewFieldGroup'>[];
@@ -0,0 +1,92 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { WorkspaceCacheProvider } from 'src/engine/workspace-cache/interfaces/workspace-cache-provider.service';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
import { FlatViewFieldGroupMaps } from 'src/engine/metadata-modules/flat-view-field-group/types/flat-view-field-group-maps.type';
import { fromViewFieldGroupEntityToFlatViewFieldGroup } from 'src/engine/metadata-modules/flat-view-field-group/utils/from-view-field-group-entity-to-flat-view-field-group.util';
import { ViewFieldGroupEntity } from 'src/engine/metadata-modules/view-field-group/entities/view-field-group.entity';
import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator';
import { createIdToUniversalIdentifierMap } from 'src/engine/workspace-cache/utils/create-id-to-universal-identifier-map.util';
import { regroupEntitiesByRelatedEntityId } from 'src/engine/workspace-cache/utils/regroup-entities-by-related-entity-id';
import { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
@Injectable()
@WorkspaceCache('flatViewFieldGroupMaps')
export class WorkspaceFlatViewFieldGroupMapCacheService extends WorkspaceCacheProvider<FlatViewFieldGroupMaps> {
constructor(
@InjectRepository(ViewFieldGroupEntity)
private readonly viewFieldGroupRepository: Repository<ViewFieldGroupEntity>,
@InjectRepository(ApplicationEntity)
private readonly applicationRepository: Repository<ApplicationEntity>,
@InjectRepository(ViewEntity)
private readonly viewRepository: Repository<ViewEntity>,
@InjectRepository(ViewFieldEntity)
private readonly viewFieldRepository: Repository<ViewFieldEntity>,
) {
super();
}
async computeForCache(workspaceId: string): Promise<FlatViewFieldGroupMaps> {
const [viewFieldGroups, applications, views, viewFields] =
await Promise.all([
this.viewFieldGroupRepository.find({
where: { workspaceId },
withDeleted: true,
}),
this.applicationRepository.find({
where: { workspaceId },
select: ['id', 'universalIdentifier'],
withDeleted: true,
}),
this.viewRepository.find({
where: { workspaceId },
select: ['id', 'universalIdentifier'],
withDeleted: true,
}),
this.viewFieldRepository.find({
where: { workspaceId },
select: ['id', 'universalIdentifier', 'viewFieldGroupId'],
withDeleted: true,
}),
]);
const applicationIdToUniversalIdentifierMap =
createIdToUniversalIdentifierMap(applications);
const viewIdToUniversalIdentifierMap =
createIdToUniversalIdentifierMap(views);
const viewFieldsByViewFieldGroupId =
regroupEntitiesByRelatedEntityId<'viewField'>({
entities: viewFields,
foreignKey: 'viewFieldGroupId',
});
const flatViewFieldGroupMaps = createEmptyFlatEntityMaps();
for (const viewFieldGroupEntity of viewFieldGroups) {
const flatViewFieldGroup = fromViewFieldGroupEntityToFlatViewFieldGroup({
entity: {
...viewFieldGroupEntity,
viewFields:
viewFieldsByViewFieldGroupId.get(viewFieldGroupEntity.id) || [],
},
applicationIdToUniversalIdentifierMap,
viewIdToUniversalIdentifierMap,
});
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
flatEntity: flatViewFieldGroup,
flatEntityMapsToMutate: flatViewFieldGroupMaps,
});
}
return flatViewFieldGroupMaps;
}
}
@@ -0,0 +1,4 @@
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FlatViewFieldGroup } from 'src/engine/metadata-modules/flat-view-field-group/types/flat-view-field-group.type';
export type FlatViewFieldGroupMaps = FlatEntityMaps<FlatViewFieldGroup>;
@@ -0,0 +1,4 @@
import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type';
import { type ViewFieldGroupEntity } from 'src/engine/metadata-modules/view-field-group/entities/view-field-group.entity';
export type FlatViewFieldGroup = FlatEntityFrom<ViewFieldGroupEntity>;
@@ -0,0 +1,52 @@
import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util';
import { type CreateViewFieldGroupInput } from 'src/engine/metadata-modules/view-field-group/dtos/inputs/create-view-field-group.input';
import { type UniversalFlatViewFieldGroup } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-field-group.type';
export type FromCreateViewFieldGroupInputToFlatViewFieldGroupToCreateArgs = {
createViewFieldGroupInput: CreateViewFieldGroupInput;
flatApplication: FlatApplication;
} & Pick<AllFlatEntityMaps, 'flatViewMaps'>;
export const fromCreateViewFieldGroupInputToFlatViewFieldGroupToCreate = ({
createViewFieldGroupInput: rawCreateViewFieldGroupInput,
flatApplication,
flatViewMaps,
}: FromCreateViewFieldGroupInputToFlatViewFieldGroupToCreateArgs): UniversalFlatViewFieldGroup & {
id: string;
} => {
const { viewId, ...createViewFieldGroupInput } =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
rawCreateViewFieldGroupInput,
['id', 'name', 'viewId'],
);
const createdAt = new Date().toISOString();
const viewFieldGroupId = createViewFieldGroupInput.id ?? v4();
const { viewUniversalIdentifier } = resolveEntityRelationUniversalIdentifiers(
{
metadataName: 'viewFieldGroup',
foreignKeyValues: { viewId },
flatEntityMaps: { flatViewMaps },
},
);
return {
id: viewFieldGroupId,
name: createViewFieldGroupInput.name,
viewUniversalIdentifier,
createdAt,
updatedAt: createdAt,
deletedAt: null,
universalIdentifier: createViewFieldGroupInput.universalIdentifier ?? v4(),
position: createViewFieldGroupInput.position ?? 0,
isVisible: createViewFieldGroupInput.isVisible ?? true,
viewFieldUniversalIdentifiers: [],
applicationUniversalIdentifier: flatApplication.universalIdentifier,
};
};
@@ -0,0 +1,46 @@
import { t } from '@lingui/core/macro';
import {
extractAndSanitizeObjectStringFields,
isDefined,
} from 'twenty-shared/utils';
import { type FlatViewFieldGroupMaps } from 'src/engine/metadata-modules/flat-view-field-group/types/flat-view-field-group-maps.type';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { type DeleteViewFieldGroupInput } from 'src/engine/metadata-modules/view-field-group/dtos/inputs/delete-view-field-group.input';
import {
ViewFieldGroupException,
ViewFieldGroupExceptionCode,
} from 'src/engine/metadata-modules/view-field-group/exceptions/view-field-group.exception';
import { type UniversalFlatViewFieldGroup } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-field-group.type';
export const fromDeleteViewFieldGroupInputToFlatViewFieldGroupOrThrow = ({
deleteViewFieldGroupInput: rawDeleteViewFieldGroupInput,
flatViewFieldGroupMaps,
}: {
deleteViewFieldGroupInput: DeleteViewFieldGroupInput;
flatViewFieldGroupMaps: FlatViewFieldGroupMaps;
}): UniversalFlatViewFieldGroup => {
const { id: viewFieldGroupId } = extractAndSanitizeObjectStringFields(
rawDeleteViewFieldGroupInput,
['id'],
);
const existingFlatViewFieldGroupToDelete = findFlatEntityByIdInFlatEntityMaps(
{
flatEntityId: viewFieldGroupId,
flatEntityMaps: flatViewFieldGroupMaps,
},
);
if (!isDefined(existingFlatViewFieldGroupToDelete)) {
throw new ViewFieldGroupException(
t`View field group to delete not found`,
ViewFieldGroupExceptionCode.VIEW_FIELD_GROUP_NOT_FOUND,
);
}
return {
...existingFlatViewFieldGroupToDelete,
deletedAt: new Date().toISOString(),
};
};
@@ -0,0 +1,42 @@
import { t } from '@lingui/core/macro';
import {
extractAndSanitizeObjectStringFields,
isDefined,
} from 'twenty-shared/utils';
import { type FlatViewFieldGroupMaps } from 'src/engine/metadata-modules/flat-view-field-group/types/flat-view-field-group-maps.type';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { type DestroyViewFieldGroupInput } from 'src/engine/metadata-modules/view-field-group/dtos/inputs/destroy-view-field-group.input';
import {
ViewFieldGroupException,
ViewFieldGroupExceptionCode,
} from 'src/engine/metadata-modules/view-field-group/exceptions/view-field-group.exception';
import { type UniversalFlatViewFieldGroup } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-field-group.type';
export const fromDestroyViewFieldGroupInputToFlatViewFieldGroupOrThrow = ({
destroyViewFieldGroupInput,
flatViewFieldGroupMaps,
}: {
destroyViewFieldGroupInput: DestroyViewFieldGroupInput;
flatViewFieldGroupMaps: FlatViewFieldGroupMaps;
}): UniversalFlatViewFieldGroup => {
const { id: viewFieldGroupId } = extractAndSanitizeObjectStringFields(
destroyViewFieldGroupInput,
['id'],
);
const existingFlatViewFieldGroupToDestroy =
findFlatEntityByIdInFlatEntityMaps({
flatEntityId: viewFieldGroupId,
flatEntityMaps: flatViewFieldGroupMaps,
});
if (!isDefined(existingFlatViewFieldGroupToDestroy)) {
throw new ViewFieldGroupException(
t`View field group to destroy not found`,
ViewFieldGroupExceptionCode.VIEW_FIELD_GROUP_NOT_FOUND,
);
}
return existingFlatViewFieldGroupToDestroy;
};
@@ -0,0 +1,56 @@
import { t } from '@lingui/core/macro';
import {
extractAndSanitizeObjectStringFields,
isDefined,
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
} from 'twenty-shared/utils';
import { FLAT_VIEW_FIELD_GROUP_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-view-field-group/constants/flat-view-field-group-editable-properties.constant';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { type FlatViewFieldGroupMaps } from 'src/engine/metadata-modules/flat-view-field-group/types/flat-view-field-group-maps.type';
import { type UpdateViewFieldGroupInput } from 'src/engine/metadata-modules/view-field-group/dtos/inputs/update-view-field-group.input';
import {
ViewFieldGroupException,
ViewFieldGroupExceptionCode,
} from 'src/engine/metadata-modules/view-field-group/exceptions/view-field-group.exception';
import { type UniversalFlatViewFieldGroup } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-field-group.type';
import { mergeUpdateInExistingRecord } from 'src/utils/merge-update-in-existing-record.util';
export const fromUpdateViewFieldGroupInputToFlatViewFieldGroupToUpdateOrThrow =
({
updateViewFieldGroupInput: rawUpdateViewFieldGroupInput,
flatViewFieldGroupMaps,
}: {
updateViewFieldGroupInput: UpdateViewFieldGroupInput;
flatViewFieldGroupMaps: FlatViewFieldGroupMaps;
}): UniversalFlatViewFieldGroup => {
const { id: viewFieldGroupToUpdateId } =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
rawUpdateViewFieldGroupInput,
['id'],
);
const existingFlatViewFieldGroupToUpdate =
findFlatEntityByIdInFlatEntityMaps({
flatEntityId: viewFieldGroupToUpdateId,
flatEntityMaps: flatViewFieldGroupMaps,
});
if (!isDefined(existingFlatViewFieldGroupToUpdate)) {
throw new ViewFieldGroupException(
t`View field group to update not found`,
ViewFieldGroupExceptionCode.VIEW_FIELD_GROUP_NOT_FOUND,
);
}
const updatedEditableProperties = extractAndSanitizeObjectStringFields(
rawUpdateViewFieldGroupInput.update,
FLAT_VIEW_FIELD_GROUP_EDITABLE_PROPERTIES,
);
return mergeUpdateInExistingRecord({
existing: existingFlatViewFieldGroupToUpdate,
properties: FLAT_VIEW_FIELD_GROUP_EDITABLE_PROPERTIES,
update: updatedEditableProperties,
});
};
@@ -0,0 +1,60 @@
import { isDefined, removePropertiesFromRecord } from 'twenty-shared/utils';
import {
FlatEntityMapsException,
FlatEntityMapsExceptionCode,
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
import { getMetadataEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-entity-relation-properties.util';
import { type FlatViewFieldGroup } from 'src/engine/metadata-modules/flat-view-field-group/types/flat-view-field-group.type';
import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type';
export const fromViewFieldGroupEntityToFlatViewFieldGroup = ({
entity: viewFieldGroupEntity,
applicationIdToUniversalIdentifierMap,
viewIdToUniversalIdentifierMap,
}: FromEntityToFlatEntityArgs<'viewFieldGroup'>): FlatViewFieldGroup => {
const viewFieldGroupEntityWithoutRelations = removePropertiesFromRecord(
viewFieldGroupEntity,
getMetadataEntityRelationProperties('viewFieldGroup'),
);
const applicationUniversalIdentifier =
applicationIdToUniversalIdentifierMap.get(
viewFieldGroupEntity.applicationId,
);
if (!isDefined(applicationUniversalIdentifier)) {
throw new FlatEntityMapsException(
`Application with id ${viewFieldGroupEntity.applicationId} not found for viewFieldGroup ${viewFieldGroupEntity.id}`,
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
);
}
const viewUniversalIdentifier = viewIdToUniversalIdentifierMap.get(
viewFieldGroupEntity.viewId,
);
if (!isDefined(viewUniversalIdentifier)) {
throw new FlatEntityMapsException(
`View with id ${viewFieldGroupEntity.viewId} not found for viewFieldGroup ${viewFieldGroupEntity.id}`,
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
);
}
return {
...viewFieldGroupEntityWithoutRelations,
createdAt: viewFieldGroupEntity.createdAt.toISOString(),
updatedAt: viewFieldGroupEntity.updatedAt.toISOString(),
deletedAt: viewFieldGroupEntity.deletedAt?.toISOString() ?? null,
universalIdentifier:
viewFieldGroupEntityWithoutRelations.universalIdentifier,
applicationUniversalIdentifier,
viewUniversalIdentifier,
viewFieldIds:
viewFieldGroupEntity.viewFields?.map((viewField) => viewField.id) ?? [],
viewFieldUniversalIdentifiers:
viewFieldGroupEntity.viewFields?.map(
(viewField) => viewField.universalIdentifier,
) ?? [],
};
};
@@ -5,4 +5,5 @@ export const FLAT_VIEW_FIELD_EDITABLE_PROPERTIES = [
'size',
'position',
'aggregateOperation',
'viewFieldGroupId',
] as const satisfies MetadataEntityPropertyName<'viewField'>[];
@@ -10,6 +10,7 @@ import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
import { FlatViewFieldMaps } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field-maps.type';
import { fromViewFieldEntityToFlatViewField } from 'src/engine/metadata-modules/flat-view-field/utils/from-view-field-entity-to-flat-view-field.util';
import { ViewFieldGroupEntity } from 'src/engine/metadata-modules/view-field-group/entities/view-field-group.entity';
import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator';
@@ -28,13 +29,15 @@ export class WorkspaceFlatViewFieldMapCacheService extends WorkspaceCacheProvide
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
@InjectRepository(ViewEntity)
private readonly viewRepository: Repository<ViewEntity>,
@InjectRepository(ViewFieldGroupEntity)
private readonly viewFieldGroupRepository: Repository<ViewFieldGroupEntity>,
) {
super();
}
async computeForCache(workspaceId: string): Promise<FlatViewFieldMaps> {
const [viewFields, applications, fieldMetadatas, views] = await Promise.all(
[
const [viewFields, applications, fieldMetadatas, views, viewFieldGroups] =
await Promise.all([
this.viewFieldRepository.find({
where: { workspaceId },
withDeleted: true,
@@ -54,8 +57,12 @@ export class WorkspaceFlatViewFieldMapCacheService extends WorkspaceCacheProvide
select: ['id', 'universalIdentifier'],
withDeleted: true,
}),
],
);
this.viewFieldGroupRepository.find({
where: { workspaceId },
select: ['id', 'universalIdentifier'],
withDeleted: true,
}),
]);
const applicationIdToUniversalIdentifierMap =
createIdToUniversalIdentifierMap(applications);
@@ -63,6 +70,8 @@ export class WorkspaceFlatViewFieldMapCacheService extends WorkspaceCacheProvide
createIdToUniversalIdentifierMap(fieldMetadatas);
const viewIdToUniversalIdentifierMap =
createIdToUniversalIdentifierMap(views);
const viewFieldGroupIdToUniversalIdentifierMap =
createIdToUniversalIdentifierMap(viewFieldGroups);
const flatViewFieldMaps = createEmptyFlatEntityMaps();
@@ -72,6 +81,7 @@ export class WorkspaceFlatViewFieldMapCacheService extends WorkspaceCacheProvide
applicationIdToUniversalIdentifierMap,
fieldMetadataIdToUniversalIdentifierMap,
viewIdToUniversalIdentifierMap,
viewFieldGroupIdToUniversalIdentifierMap,
});
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
@@ -11,31 +11,48 @@ import { type UniversalFlatViewField } from 'src/engine/workspace-manager/worksp
export type FromCreateViewFieldInputToFlatViewFieldToCreateArgs = {
createViewFieldInput: CreateViewFieldInput;
flatApplication: FlatApplication;
} & Pick<AllFlatEntityMaps, 'flatFieldMetadataMaps' | 'flatViewMaps'>;
} & Pick<
AllFlatEntityMaps,
'flatFieldMetadataMaps' | 'flatViewMaps' | 'flatViewFieldGroupMaps'
>;
export const fromCreateViewFieldInputToFlatViewFieldToCreate = ({
createViewFieldInput: rawCreateViewFieldInput,
flatApplication,
flatFieldMetadataMaps,
flatViewMaps,
flatViewFieldGroupMaps,
}: FromCreateViewFieldInputToFlatViewFieldToCreateArgs): UniversalFlatViewField & {
id: string;
} => {
const { fieldMetadataId, viewId, ...createViewFieldInput } =
const { fieldMetadataId, viewId, viewFieldGroupId, ...createViewFieldInput } =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
rawCreateViewFieldInput,
['aggregateOperation', 'fieldMetadataId', 'id', 'viewId'],
[
'aggregateOperation',
'fieldMetadataId',
'id',
'viewId',
'viewFieldGroupId',
],
);
const createdAt = new Date().toISOString();
const viewFieldId = createViewFieldInput.id ?? v4();
const { fieldMetadataUniversalIdentifier, viewUniversalIdentifier } =
resolveEntityRelationUniversalIdentifiers({
metadataName: 'viewField',
foreignKeyValues: { fieldMetadataId, viewId },
flatEntityMaps: { flatFieldMetadataMaps, flatViewMaps },
});
const {
fieldMetadataUniversalIdentifier,
viewUniversalIdentifier,
viewFieldGroupUniversalIdentifier,
} = resolveEntityRelationUniversalIdentifiers({
metadataName: 'viewField',
foreignKeyValues: { fieldMetadataId, viewId, viewFieldGroupId },
flatEntityMaps: {
flatFieldMetadataMaps,
flatViewMaps,
flatViewFieldGroupMaps,
},
});
return {
id: viewFieldId,
@@ -49,6 +66,7 @@ export const fromCreateViewFieldInputToFlatViewFieldToCreate = ({
size: createViewFieldInput.size ?? DEFAULT_VIEW_FIELD_SIZE,
position: createViewFieldInput.position ?? 0,
aggregateOperation: createViewFieldInput.aggregateOperation ?? null,
viewFieldGroupUniversalIdentifier,
applicationUniversalIdentifier: flatApplication.universalIdentifier,
};
};
@@ -5,8 +5,10 @@ import {
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
} from 'twenty-shared/utils';
import { FLAT_VIEW_FIELD_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-view-field/constants/flat-view-field-editable-properties.constant';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util';
import { FLAT_VIEW_FIELD_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-view-field/constants/flat-view-field-editable-properties.constant';
import { type FlatViewFieldMaps } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field-maps.type';
import { type UpdateViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/update-view-field.input';
import {
@@ -19,10 +21,14 @@ import { mergeUpdateInExistingRecord } from 'src/utils/merge-update-in-existing-
export const fromUpdateViewFieldInputToFlatViewFieldToUpdateOrThrow = ({
updateViewFieldInput: rawUpdateViewFieldInput,
flatViewFieldMaps,
flatViewFieldGroupMaps,
}: {
updateViewFieldInput: UpdateViewFieldInput;
flatViewFieldMaps: FlatViewFieldMaps;
}): UniversalFlatViewField => {
} & Pick<
AllFlatEntityMaps,
'flatViewFieldGroupMaps'
>): UniversalFlatViewField => {
const { id: viewFieldToUpdateId } =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
rawUpdateViewFieldInput,
@@ -40,14 +46,31 @@ export const fromUpdateViewFieldInputToFlatViewFieldToUpdateOrThrow = ({
ViewFieldExceptionCode.VIEW_FIELD_NOT_FOUND,
);
}
const updatedEditableFieldProperties = extractAndSanitizeObjectStringFields(
rawUpdateViewFieldInput.update,
FLAT_VIEW_FIELD_EDITABLE_PROPERTIES,
);
return mergeUpdateInExistingRecord({
const flatViewFieldToUpdate = mergeUpdateInExistingRecord({
existing: existingFlatViewFieldToUpdate,
properties: FLAT_VIEW_FIELD_EDITABLE_PROPERTIES,
update: updatedEditableFieldProperties,
});
if (updatedEditableFieldProperties.viewFieldGroupId !== undefined) {
const { viewFieldGroupUniversalIdentifier } =
resolveEntityRelationUniversalIdentifiers({
metadataName: 'viewField',
foreignKeyValues: {
viewFieldGroupId: flatViewFieldToUpdate.viewFieldGroupId,
},
flatEntityMaps: { flatViewFieldGroupMaps },
});
flatViewFieldToUpdate.viewFieldGroupUniversalIdentifier =
viewFieldGroupUniversalIdentifier;
}
return flatViewFieldToUpdate;
};
@@ -13,6 +13,7 @@ export const fromViewFieldEntityToFlatViewField = ({
applicationIdToUniversalIdentifierMap,
fieldMetadataIdToUniversalIdentifierMap,
viewIdToUniversalIdentifierMap,
viewFieldGroupIdToUniversalIdentifierMap,
}: FromEntityToFlatEntityArgs<'viewField'>): FlatViewField => {
const viewFieldEntityWithoutRelations = removePropertiesFromRecord(
viewFieldEntity,
@@ -52,6 +53,22 @@ export const fromViewFieldEntityToFlatViewField = ({
);
}
let viewFieldGroupUniversalIdentifier: string | null = null;
if (isDefined(viewFieldEntity.viewFieldGroupId)) {
viewFieldGroupUniversalIdentifier =
viewFieldGroupIdToUniversalIdentifierMap.get(
viewFieldEntity.viewFieldGroupId,
) ?? null;
if (!isDefined(viewFieldGroupUniversalIdentifier)) {
throw new FlatEntityMapsException(
`ViewFieldGroup with id ${viewFieldEntity.viewFieldGroupId} not found for viewField ${viewFieldEntity.id}`,
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
);
}
}
return {
...viewFieldEntityWithoutRelations,
createdAt: viewFieldEntity.createdAt.toISOString(),
@@ -61,5 +78,6 @@ export const fromViewFieldEntityToFlatViewField = ({
applicationUniversalIdentifier,
fieldMetadataUniversalIdentifier,
viewUniversalIdentifier,
viewFieldGroupUniversalIdentifier,
};
};
@@ -87,6 +87,7 @@ export const fromCreateViewInputToFlatViewToCreate = ({
viewFieldUniversalIdentifiers: [],
viewFilterUniversalIdentifiers: [],
viewGroupUniversalIdentifiers: [],
viewFieldGroupUniversalIdentifiers: [],
viewFilterGroupUniversalIdentifiers: [],
applicationUniversalIdentifier: flatApplication.universalIdentifier,
};
@@ -95,6 +95,7 @@ export const fromViewEntityToFlatView = ({
deletedAt: viewEntity.deletedAt?.toISOString() ?? null,
universalIdentifier: viewEntityWithoutRelations.universalIdentifier,
viewFieldIds: viewEntity.viewFields.map(({ id }) => id),
viewFieldGroupIds: viewEntity.viewFieldGroups?.map(({ id }) => id) ?? [],
viewFilterIds: viewEntity.viewFilters.map(({ id }) => id),
viewGroupIds: viewEntity.viewGroups.map(({ id }) => id),
viewFilterGroupIds: viewEntity.viewFilterGroups?.map(({ id }) => id) ?? [],
@@ -106,6 +107,10 @@ export const fromViewEntityToFlatView = ({
viewFieldUniversalIdentifiers: viewEntity.viewFields.map(
({ universalIdentifier }) => universalIdentifier,
),
viewFieldGroupUniversalIdentifiers:
viewEntity.viewFieldGroups?.map(
({ universalIdentifier }) => universalIdentifier,
) ?? [],
viewFilterUniversalIdentifiers: viewEntity.viewFilters.map(
({ universalIdentifier }) => universalIdentifier,
),
@@ -549,6 +549,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
visibility: ViewVisibility.WORKSPACE,
createdByUserWorkspaceId: null,
viewFieldUniversalIdentifiers: [],
viewFieldGroupUniversalIdentifiers: [],
viewFilterUniversalIdentifiers: [],
viewGroupUniversalIdentifiers: [],
viewFilterGroupUniversalIdentifiers: [],
@@ -580,6 +581,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
.map<UniversalFlatViewField>((field, index) => ({
fieldMetadataUniversalIdentifier: field.universalIdentifier,
viewUniversalIdentifier,
viewFieldGroupUniversalIdentifier: null,
createdAt,
updatedAt: createdAt,
deletedAt: null,
@@ -0,0 +1,45 @@
import { Field, HideField, InputType } from '@nestjs/graphql';
import {
IsBoolean,
IsNotEmpty,
IsNumber,
IsOptional,
IsString,
IsUUID,
} from 'class-validator';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
@InputType()
export class CreateViewFieldGroupInput {
@IsOptional()
@IsUUID()
@Field(() => UUIDScalarType, { nullable: true })
id?: string;
@IsNotEmpty()
@IsString()
@Field({ nullable: false })
name: string;
@IsUUID()
@Field(() => UUIDScalarType, { nullable: false })
viewId: string;
@IsOptional()
@IsNumber()
@Field({ nullable: true, defaultValue: 0 })
position?: number;
@IsOptional()
@IsBoolean()
@Field({ nullable: true, defaultValue: true })
isVisible?: boolean;
@HideField()
universalIdentifier?: string;
@HideField()
applicationId?: string;
}
@@ -0,0 +1,15 @@
import { InputType } from '@nestjs/graphql';
import { IDField } from '@ptc-org/nestjs-query-graphql';
import { IsUUID } from 'class-validator';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
@InputType()
export class DeleteViewFieldGroupInput {
@IDField(() => UUIDScalarType, {
description: 'The id of the view field group to delete.',
})
@IsUUID()
id: string;
}
@@ -0,0 +1,15 @@
import { InputType } from '@nestjs/graphql';
import { IDField } from '@ptc-org/nestjs-query-graphql';
import { IsUUID } from 'class-validator';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
@InputType()
export class DestroyViewFieldGroupInput {
@IDField(() => UUIDScalarType, {
description: 'The id of the view field group to destroy.',
})
@IsUUID()
id: string;
}
@@ -0,0 +1,54 @@
import { Field, InputType } from '@nestjs/graphql';
import { Type } from 'class-transformer';
import {
IsBoolean,
IsNotEmpty,
IsNumber,
IsOptional,
IsString,
IsUUID,
ValidateNested,
} from 'class-validator';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
@InputType()
class UpdateViewFieldGroupInputUpdates {
@IsOptional()
@IsString()
@Field({ nullable: true })
name?: string;
@IsOptional()
@IsNumber()
@Field({ nullable: true })
position?: number;
@IsOptional()
@IsBoolean()
@Field({ nullable: true })
isVisible?: boolean;
@IsOptional()
@IsString()
@Field({ nullable: true })
deletedAt?: string;
}
@InputType()
export class UpdateViewFieldGroupInput {
@IsUUID()
@IsNotEmpty()
@Field(() => UUIDScalarType, {
description: 'The id of the view field group to update',
})
id: string;
@Type(() => UpdateViewFieldGroupInputUpdates)
@ValidateNested()
@Field(() => UpdateViewFieldGroupInputUpdates, {
description: 'The view field group to update',
})
update: UpdateViewFieldGroupInputUpdates;
}
@@ -0,0 +1,39 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { IDField } from '@ptc-org/nestjs-query-graphql';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { ViewFieldDTO } from 'src/engine/metadata-modules/view-field/dtos/view-field.dto';
@ObjectType('CoreViewFieldGroup')
export class ViewFieldGroupDTO {
@IDField(() => UUIDScalarType)
id: string;
@Field({ nullable: false })
name: string;
@Field({ nullable: false, defaultValue: 0 })
position: number;
@Field({ nullable: false, defaultValue: true })
isVisible: boolean;
@Field(() => UUIDScalarType, { nullable: false })
viewId: string;
@Field(() => UUIDScalarType, { nullable: false })
workspaceId: string;
@Field()
createdAt: Date;
@Field()
updatedAt: Date;
@Field(() => Date, { nullable: true })
deletedAt?: Date | null;
@Field(() => [ViewFieldDTO])
viewFields?: ViewFieldDTO[];
}
@@ -0,0 +1,58 @@
import {
Column,
CreateDateColumn,
DeleteDateColumn,
Entity,
Index,
JoinColumn,
ManyToOne,
OneToMany,
PrimaryGeneratedColumn,
type Relation,
UpdateDateColumn,
} from 'typeorm';
import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
@Entity({ name: 'viewFieldGroup', schema: 'core' })
@Index('IDX_VIEW_FIELD_GROUP_WORKSPACE_ID_VIEW_ID', ['workspaceId', 'viewId'])
@Index('IDX_VIEW_FIELD_GROUP_VIEW_ID', ['viewId'])
export class ViewFieldGroupEntity
extends SyncableEntity
implements Required<ViewFieldGroupEntity>
{
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ nullable: false, type: 'text' })
name: string;
@Column({ nullable: false, type: 'double precision', default: 0 })
position: number;
@Column({ nullable: false, default: true })
isVisible: boolean;
@Column({ nullable: false, type: 'uuid' })
viewId: string;
@CreateDateColumn({ type: 'timestamptz' })
createdAt: Date;
@UpdateDateColumn({ type: 'timestamptz' })
updatedAt: Date;
@DeleteDateColumn({ type: 'timestamptz' })
deletedAt: Date | null;
@ManyToOne(() => ViewEntity, (view) => view.viewFieldGroups, {
onDelete: 'CASCADE',
})
@JoinColumn({ name: 'viewId' })
view: Relation<ViewEntity>;
@OneToMany(() => ViewFieldEntity, (viewField) => viewField.viewFieldGroup)
viewFields: Relation<ViewFieldEntity[]>;
}
@@ -0,0 +1,23 @@
import { type MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import { CustomException } from 'src/utils/custom-exception';
export class ViewFieldGroupException extends CustomException<ViewFieldGroupExceptionCode> {
constructor(
message: string,
code: ViewFieldGroupExceptionCode,
{ userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {},
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? msg`A view field group error occurred.`,
});
}
}
export enum ViewFieldGroupExceptionCode {
VIEW_FIELD_GROUP_NOT_FOUND = 'VIEW_FIELD_GROUP_NOT_FOUND',
VIEW_NOT_FOUND = 'VIEW_NOT_FOUND',
INVALID_VIEW_FIELD_GROUP_DATA = 'INVALID_VIEW_FIELD_GROUP_DATA',
}
@@ -0,0 +1,131 @@
import { UseFilters, UseGuards } from '@nestjs/common';
import {
Args,
Context,
Mutation,
Parent,
Query,
ResolveField,
} from '@nestjs/graphql';
import { isArray } from '@sniptt/guards';
import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { type IDataloaders } from 'src/engine/dataloaders/dataloader.interface';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { CreateViewFieldGroupInput } from 'src/engine/metadata-modules/view-field-group/dtos/inputs/create-view-field-group.input';
import { DeleteViewFieldGroupInput } from 'src/engine/metadata-modules/view-field-group/dtos/inputs/delete-view-field-group.input';
import { DestroyViewFieldGroupInput } from 'src/engine/metadata-modules/view-field-group/dtos/inputs/destroy-view-field-group.input';
import { UpdateViewFieldGroupInput } from 'src/engine/metadata-modules/view-field-group/dtos/inputs/update-view-field-group.input';
import { ViewFieldGroupDTO } from 'src/engine/metadata-modules/view-field-group/dtos/view-field-group.dto';
import { ViewFieldGroupEntity } from 'src/engine/metadata-modules/view-field-group/entities/view-field-group.entity';
import { ViewFieldGroupService } from 'src/engine/metadata-modules/view-field-group/services/view-field-group.service';
import { ViewFieldDTO } from 'src/engine/metadata-modules/view-field/dtos/view-field.dto';
import { ViewGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/view/utils/view-graphql-api-exception.filter';
@MetadataResolver(() => ViewFieldGroupDTO)
@UseFilters(ViewGraphqlApiExceptionFilter)
@UseGuards(WorkspaceAuthGuard)
export class ViewFieldGroupResolver {
constructor(private readonly viewFieldGroupService: ViewFieldGroupService) {}
@Query(() => [ViewFieldGroupDTO])
@UseGuards(NoPermissionGuard)
async getCoreViewFieldGroups(
@Args('viewId', { type: () => String }) viewId: string,
@AuthWorkspace() workspace: WorkspaceEntity,
): Promise<ViewFieldGroupEntity[]> {
return this.viewFieldGroupService.findByViewId(workspace.id, viewId);
}
@Query(() => ViewFieldGroupDTO, { nullable: true })
@UseGuards(NoPermissionGuard)
async getCoreViewFieldGroup(
@Args('id', { type: () => String }) id: string,
@AuthWorkspace() workspace: WorkspaceEntity,
): Promise<ViewFieldGroupEntity | null> {
return this.viewFieldGroupService.findById(id, workspace.id);
}
@Mutation(() => ViewFieldGroupDTO)
@UseGuards(NoPermissionGuard)
async updateCoreViewFieldGroup(
@Args('input') updateViewFieldGroupInput: UpdateViewFieldGroupInput,
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
): Promise<ViewFieldGroupDTO> {
return await this.viewFieldGroupService.updateOne({
updateViewFieldGroupInput,
workspaceId,
});
}
@Mutation(() => ViewFieldGroupDTO)
@UseGuards(NoPermissionGuard)
async createCoreViewFieldGroup(
@Args('input')
createViewFieldGroupInput: CreateViewFieldGroupInput,
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
): Promise<ViewFieldGroupDTO> {
return await this.viewFieldGroupService.createOne({
createViewFieldGroupInput,
workspaceId,
});
}
@Mutation(() => [ViewFieldGroupDTO])
@UseGuards(NoPermissionGuard)
async createManyCoreViewFieldGroups(
@Args('inputs', { type: () => [CreateViewFieldGroupInput] })
createViewFieldGroupInputs: CreateViewFieldGroupInput[],
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
): Promise<ViewFieldGroupDTO[]> {
return await this.viewFieldGroupService.createMany({
createViewFieldGroupInputs,
workspaceId,
});
}
@Mutation(() => ViewFieldGroupDTO)
@UseGuards(NoPermissionGuard)
async deleteCoreViewFieldGroup(
@Args('input') deleteViewFieldGroupInput: DeleteViewFieldGroupInput,
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
): Promise<ViewFieldGroupDTO> {
return await this.viewFieldGroupService.deleteOne({
deleteViewFieldGroupInput,
workspaceId,
});
}
@Mutation(() => ViewFieldGroupDTO)
@UseGuards(NoPermissionGuard)
async destroyCoreViewFieldGroup(
@Args('input')
destroyViewFieldGroupInput: DestroyViewFieldGroupInput,
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
): Promise<ViewFieldGroupDTO> {
return await this.viewFieldGroupService.destroyOne({
destroyViewFieldGroupInput,
workspaceId,
});
}
@ResolveField(() => [ViewFieldDTO])
async viewFields(
@Parent() viewFieldGroup: ViewFieldGroupDTO,
@Context() context: { loaders: IDataloaders },
@AuthWorkspace() workspace: WorkspaceEntity,
) {
if (isArray(viewFieldGroup.viewFields)) {
return viewFieldGroup.viewFields;
}
return context.loaders.viewFieldsByViewFieldGroupIdLoader.load({
workspaceId: workspace.id,
viewFieldGroupId: viewFieldGroup.id,
});
}
}
@@ -0,0 +1,366 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { isDefined } from 'twenty-shared/utils';
import { IsNull, Repository } from 'typeorm';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-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 { fromCreateViewFieldGroupInputToFlatViewFieldGroupToCreate } from 'src/engine/metadata-modules/flat-view-field-group/utils/from-create-view-field-group-input-to-flat-view-field-group-to-create.util';
import { fromDeleteViewFieldGroupInputToFlatViewFieldGroupOrThrow } from 'src/engine/metadata-modules/flat-view-field-group/utils/from-delete-view-field-group-input-to-flat-view-field-group-or-throw.util';
import { fromDestroyViewFieldGroupInputToFlatViewFieldGroupOrThrow } from 'src/engine/metadata-modules/flat-view-field-group/utils/from-destroy-view-field-group-input-to-flat-view-field-group-or-throw.util';
import { fromUpdateViewFieldGroupInputToFlatViewFieldGroupToUpdateOrThrow } from 'src/engine/metadata-modules/flat-view-field-group/utils/from-update-view-field-group-input-to-flat-view-field-group-to-update-or-throw.util';
import { CreateViewFieldGroupInput } from 'src/engine/metadata-modules/view-field-group/dtos/inputs/create-view-field-group.input';
import { DeleteViewFieldGroupInput } from 'src/engine/metadata-modules/view-field-group/dtos/inputs/delete-view-field-group.input';
import { DestroyViewFieldGroupInput } from 'src/engine/metadata-modules/view-field-group/dtos/inputs/destroy-view-field-group.input';
import { UpdateViewFieldGroupInput } from 'src/engine/metadata-modules/view-field-group/dtos/inputs/update-view-field-group.input';
import { ViewFieldGroupDTO } from 'src/engine/metadata-modules/view-field-group/dtos/view-field-group.dto';
import { ViewFieldGroupEntity } from 'src/engine/metadata-modules/view-field-group/entities/view-field-group.entity';
import {
ViewFieldGroupException,
ViewFieldGroupExceptionCode,
} from 'src/engine/metadata-modules/view-field-group/exceptions/view-field-group.exception';
import { fromFlatViewFieldGroupToViewFieldGroupDto } from 'src/engine/metadata-modules/view-field-group/utils/from-flat-view-field-group-to-view-field-group-dto.util';
import { WorkspaceMigrationBuilderException } from 'src/engine/workspace-manager/workspace-migration/exceptions/workspace-migration-builder-exception';
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
@Injectable()
export class ViewFieldGroupService {
constructor(
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
@InjectRepository(ViewFieldGroupEntity)
private readonly viewFieldGroupRepository: Repository<ViewFieldGroupEntity>,
private readonly applicationService: ApplicationService,
) {}
async createOne({
createViewFieldGroupInput,
workspaceId,
}: {
createViewFieldGroupInput: CreateViewFieldGroupInput;
workspaceId: string;
}): Promise<ViewFieldGroupDTO> {
const [createdViewFieldGroup] = await this.createMany({
workspaceId,
createViewFieldGroupInputs: [createViewFieldGroupInput],
});
if (!isDefined(createdViewFieldGroup)) {
throw new ViewFieldGroupException(
'Failed to create view field group',
ViewFieldGroupExceptionCode.INVALID_VIEW_FIELD_GROUP_DATA,
);
}
return createdViewFieldGroup;
}
async createMany({
createViewFieldGroupInputs,
workspaceId,
}: {
createViewFieldGroupInputs: CreateViewFieldGroupInput[];
workspaceId: string;
}): Promise<ViewFieldGroupDTO[]> {
if (createViewFieldGroupInputs.length === 0) {
return [];
}
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
const { flatViewMaps } =
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatViewMaps'],
},
);
const flatViewFieldGroupsToCreate = createViewFieldGroupInputs.map(
(createViewFieldGroupInput) =>
fromCreateViewFieldGroupInputToFlatViewFieldGroupToCreate({
createViewFieldGroupInput,
flatApplication: workspaceCustomFlatApplication,
flatViewMaps,
}),
);
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
allFlatEntityOperationByMetadataName: {
viewFieldGroup: {
flatEntityToCreate: flatViewFieldGroupsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
},
workspaceId,
isSystemBuild: false,
applicationUniversalIdentifier:
workspaceCustomFlatApplication.universalIdentifier,
},
);
if (isDefined(validateAndBuildResult)) {
throw new WorkspaceMigrationBuilderException(
validateAndBuildResult,
'Multiple validation errors occurred while creating view field groups',
);
}
const { flatViewFieldGroupMaps: recomputedExistingFlatViewFieldGroupMaps } =
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatViewFieldGroupMaps'],
},
);
return findManyFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityIds: flatViewFieldGroupsToCreate.map((entity) => entity.id),
flatEntityMaps: recomputedExistingFlatViewFieldGroupMaps,
}).map(fromFlatViewFieldGroupToViewFieldGroupDto);
}
async updateOne({
updateViewFieldGroupInput,
workspaceId,
}: {
workspaceId: string;
updateViewFieldGroupInput: UpdateViewFieldGroupInput;
}): Promise<ViewFieldGroupDTO> {
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
const { flatViewFieldGroupMaps: existingFlatViewFieldGroupMaps } =
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatViewFieldGroupMaps'],
},
);
const optimisticallyUpdatedFlatViewFieldGroup =
fromUpdateViewFieldGroupInputToFlatViewFieldGroupToUpdateOrThrow({
flatViewFieldGroupMaps: existingFlatViewFieldGroupMaps,
updateViewFieldGroupInput,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
allFlatEntityOperationByMetadataName: {
viewFieldGroup: {
flatEntityToCreate: [],
flatEntityToDelete: [],
flatEntityToUpdate: [optimisticallyUpdatedFlatViewFieldGroup],
},
},
workspaceId,
isSystemBuild: false,
applicationUniversalIdentifier:
workspaceCustomFlatApplication.universalIdentifier,
},
);
if (isDefined(validateAndBuildResult)) {
throw new WorkspaceMigrationBuilderException(
validateAndBuildResult,
'Multiple validation errors occurred while updating view field group',
);
}
const { flatViewFieldGroupMaps: recomputedExistingFlatViewFieldGroupMaps } =
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatViewFieldGroupMaps'],
},
);
return fromFlatViewFieldGroupToViewFieldGroupDto(
findFlatEntityByUniversalIdentifierOrThrow({
universalIdentifier:
optimisticallyUpdatedFlatViewFieldGroup.universalIdentifier,
flatEntityMaps: recomputedExistingFlatViewFieldGroupMaps,
}),
);
}
async deleteOne({
deleteViewFieldGroupInput,
workspaceId,
}: {
deleteViewFieldGroupInput: DeleteViewFieldGroupInput;
workspaceId: string;
}): Promise<ViewFieldGroupDTO> {
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
const { flatViewFieldGroupMaps: existingFlatViewFieldGroupMaps } =
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatViewFieldGroupMaps'],
},
);
const optimisticallyUpdatedFlatViewFieldGroupWithDeletedAt =
fromDeleteViewFieldGroupInputToFlatViewFieldGroupOrThrow({
flatViewFieldGroupMaps: existingFlatViewFieldGroupMaps,
deleteViewFieldGroupInput,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
allFlatEntityOperationByMetadataName: {
viewFieldGroup: {
flatEntityToCreate: [],
flatEntityToDelete: [],
flatEntityToUpdate: [
optimisticallyUpdatedFlatViewFieldGroupWithDeletedAt,
],
},
},
workspaceId,
isSystemBuild: false,
applicationUniversalIdentifier:
workspaceCustomFlatApplication.universalIdentifier,
},
);
if (isDefined(validateAndBuildResult)) {
throw new WorkspaceMigrationBuilderException(
validateAndBuildResult,
'Multiple validation errors occurred while deleting view field group',
);
}
const { flatViewFieldGroupMaps: recomputedExistingFlatViewFieldGroupMaps } =
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatViewFieldGroupMaps'],
},
);
return fromFlatViewFieldGroupToViewFieldGroupDto(
findFlatEntityByUniversalIdentifierOrThrow({
universalIdentifier:
optimisticallyUpdatedFlatViewFieldGroupWithDeletedAt.universalIdentifier,
flatEntityMaps: recomputedExistingFlatViewFieldGroupMaps,
}),
);
}
async destroyOne({
destroyViewFieldGroupInput,
workspaceId,
}: {
destroyViewFieldGroupInput: DestroyViewFieldGroupInput;
workspaceId: string;
}): Promise<ViewFieldGroupDTO> {
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
const { flatViewFieldGroupMaps: existingFlatViewFieldGroupMaps } =
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatViewFieldGroupMaps'],
},
);
const existingViewFieldGroupToDelete =
fromDestroyViewFieldGroupInputToFlatViewFieldGroupOrThrow({
destroyViewFieldGroupInput,
flatViewFieldGroupMaps: existingFlatViewFieldGroupMaps,
});
const existingFlatViewFieldGroup =
findFlatEntityByUniversalIdentifierOrThrow({
universalIdentifier: existingViewFieldGroupToDelete.universalIdentifier,
flatEntityMaps: existingFlatViewFieldGroupMaps,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
allFlatEntityOperationByMetadataName: {
viewFieldGroup: {
flatEntityToCreate: [],
flatEntityToDelete: [existingViewFieldGroupToDelete],
flatEntityToUpdate: [],
},
},
workspaceId,
isSystemBuild: false,
applicationUniversalIdentifier:
workspaceCustomFlatApplication.universalIdentifier,
},
);
if (isDefined(validateAndBuildResult)) {
throw new WorkspaceMigrationBuilderException(
validateAndBuildResult,
'Multiple validation errors occurred while destroying view field group',
);
}
return fromFlatViewFieldGroupToViewFieldGroupDto({
...existingFlatViewFieldGroup,
deletedAt: new Date().toISOString(),
});
}
async findByViewId(
workspaceId: string,
viewId: string,
): Promise<ViewFieldGroupEntity[]> {
return this.viewFieldGroupRepository.find({
where: {
workspaceId,
viewId,
deletedAt: IsNull(),
},
order: { position: 'ASC' },
});
}
async findById(
id: string,
workspaceId: string,
): Promise<ViewFieldGroupEntity | null> {
const viewFieldGroup = await this.viewFieldGroupRepository.findOne({
where: {
id,
workspaceId,
deletedAt: IsNull(),
},
});
return viewFieldGroup || null;
}
}
@@ -0,0 +1,15 @@
import { type FlatViewFieldGroup } from 'src/engine/metadata-modules/flat-view-field-group/types/flat-view-field-group.type';
import { type ViewFieldGroupDTO } from 'src/engine/metadata-modules/view-field-group/dtos/view-field-group.dto';
export const fromFlatViewFieldGroupToViewFieldGroupDto = (
flatViewFieldGroup: FlatViewFieldGroup,
): ViewFieldGroupDTO => {
const { createdAt, updatedAt, deletedAt, ...rest } = flatViewFieldGroup;
return {
...rest,
createdAt: new Date(createdAt),
updatedAt: new Date(updatedAt),
deletedAt: deletedAt ? new Date(deletedAt) : null,
};
};
@@ -0,0 +1,26 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
import { ViewFieldGroupEntity } from 'src/engine/metadata-modules/view-field-group/entities/view-field-group.entity';
import { ViewFieldGroupResolver } from 'src/engine/metadata-modules/view-field-group/resolvers/view-field-group.resolver';
import { ViewFieldGroupService } from 'src/engine/metadata-modules/view-field-group/services/view-field-group.service';
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module';
@Module({
imports: [
TypeOrmModule.forFeature([ViewFieldGroupEntity, ViewEntity]),
WorkspaceCacheStorageModule,
ApplicationModule,
PermissionsModule,
WorkspaceMigrationModule,
WorkspaceManyOrAllFlatEntityMapsCacheModule,
],
providers: [ViewFieldGroupResolver, ViewFieldGroupService],
exports: [ViewFieldGroupService],
})
export class ViewFieldGroupModule {}
@@ -29,7 +29,7 @@ import {
ViewFieldExceptionMessageKey,
} from 'src/engine/metadata-modules/view-field/exceptions/view-field.exception';
import { ViewFieldRestApiExceptionFilter } from 'src/engine/metadata-modules/view-field/filters/view-field-rest-api-exception.filter';
import { ViewFieldV2Service } from 'src/engine/metadata-modules/view-field/services/view-field-v2.service';
import { ViewFieldService } from 'src/engine/metadata-modules/view-field/services/view-field.service';
import { CreateViewFieldPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/create-view-field-permission.guard';
import { DeleteViewFieldPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/delete-view-field-permission.guard';
import { UpdateViewFieldPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/update-view-field-permission.guard';
@@ -38,7 +38,7 @@ import { UpdateViewFieldPermissionGuard } from 'src/engine/metadata-modules/view
@UseGuards(WorkspaceAuthGuard)
@UseFilters(ViewFieldRestApiExceptionFilter)
export class ViewFieldController {
constructor(private readonly viewFieldV2Service: ViewFieldV2Service) {}
constructor(private readonly viewFieldService: ViewFieldService) {}
@Get()
@UseGuards(NoPermissionGuard)
@@ -47,10 +47,10 @@ export class ViewFieldController {
@Query('viewId') viewId?: string,
): Promise<ViewFieldEntity[]> {
if (viewId) {
return this.viewFieldV2Service.findByViewId(workspace.id, viewId);
return this.viewFieldService.findByViewId(workspace.id, viewId);
}
return this.viewFieldV2Service.findByWorkspaceId(workspace.id);
return this.viewFieldService.findByWorkspaceId(workspace.id);
}
@Get(':id')
@@ -59,7 +59,7 @@ export class ViewFieldController {
@Param('id') id: string,
@AuthWorkspace() workspace: WorkspaceEntity,
): Promise<ViewFieldEntity> {
const viewField = await this.viewFieldV2Service.findById(id, workspace.id);
const viewField = await this.viewFieldService.findById(id, workspace.id);
if (!isDefined(viewField)) {
throw new ViewFieldException(
@@ -86,7 +86,7 @@ export class ViewFieldController {
@Body() input: UpdateViewFieldInput['update'],
@AuthWorkspace() workspace: WorkspaceEntity,
): Promise<ViewFieldDTO> {
return await this.viewFieldV2Service.updateOne({
return await this.viewFieldService.updateOne({
updateViewFieldInput: { id, update: input },
workspaceId: workspace.id,
});
@@ -98,7 +98,7 @@ export class ViewFieldController {
@Body() input: CreateViewFieldInput,
@AuthWorkspace() workspace: WorkspaceEntity,
): Promise<ViewFieldDTO> {
return await this.viewFieldV2Service.createOne({
return await this.viewFieldService.createOne({
createViewFieldInput: input,
workspaceId: workspace.id,
});
@@ -110,7 +110,7 @@ export class ViewFieldController {
@Param('id') id: string,
@AuthWorkspace() workspace: WorkspaceEntity,
): Promise<{ success: boolean }> {
const deletedViewField = await this.viewFieldV2Service.deleteOne({
const deletedViewField = await this.viewFieldService.deleteOne({
deleteViewFieldInput: { id },
workspaceId: workspace.id,
});
@@ -46,6 +46,11 @@ export class CreateViewFieldInput {
@Field(() => AggregateOperations, { nullable: true })
aggregateOperation?: AggregateOperations;
@IsOptional()
@IsUUID()
@Field(() => UUIDScalarType, { nullable: true })
viewFieldGroupId?: string;
@HideField()
universalIdentifier?: string;
@@ -35,6 +35,11 @@ class UpdateViewFieldInputUpdates {
@IsEnum(AggregateOperations)
@Field(() => AggregateOperations, { nullable: true })
aggregateOperation?: AggregateOperations;
@IsOptional()
@IsUUID()
@Field(() => UUIDScalarType, { nullable: true })
viewFieldGroupId?: string | null;
}
@InputType()
@@ -13,6 +13,7 @@ import {
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ViewFieldGroupEntity } from 'src/engine/metadata-modules/view-field-group/entities/view-field-group.entity';
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
@@ -64,6 +65,9 @@ export class ViewFieldEntity
@Column({ nullable: false, type: 'uuid' })
viewId: string;
@Column({ nullable: true, type: 'uuid' })
viewFieldGroupId: string | null;
@CreateDateColumn({ type: 'timestamptz' })
createdAt: Date;
@@ -78,4 +82,15 @@ export class ViewFieldEntity
})
@JoinColumn({ name: 'viewId' })
view: Relation<ViewEntity>;
@ManyToOne(
() => ViewFieldGroupEntity,
(viewFieldGroup) => viewFieldGroup.viewFields,
{
onDelete: 'SET NULL',
nullable: true,
},
)
@JoinColumn({ name: 'viewFieldGroupId' })
viewFieldGroup: Relation<ViewFieldGroupEntity> | null;
}
@@ -1,9 +1,9 @@
import { UseFilters, UseGuards } from '@nestjs/common';
import { Args, Mutation, Query } from '@nestjs/graphql';
import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { CreateViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/create-view-field.input';
@@ -12,7 +12,7 @@ import { DestroyViewFieldInput } from 'src/engine/metadata-modules/view-field/dt
import { UpdateViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/update-view-field.input';
import { ViewFieldDTO } from 'src/engine/metadata-modules/view-field/dtos/view-field.dto';
import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
import { ViewFieldV2Service } from 'src/engine/metadata-modules/view-field/services/view-field-v2.service';
import { ViewFieldService } from 'src/engine/metadata-modules/view-field/services/view-field.service';
import { CreateViewFieldPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/create-view-field-permission.guard';
import { DeleteViewFieldPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/delete-view-field-permission.guard';
import { DestroyViewFieldPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/destroy-view-field-permission.guard';
@@ -23,7 +23,7 @@ import { ViewGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/view/
@UseFilters(ViewGraphqlApiExceptionFilter)
@UseGuards(WorkspaceAuthGuard)
export class ViewFieldResolver {
constructor(private readonly viewFieldV2Service: ViewFieldV2Service) {}
constructor(private readonly viewFieldService: ViewFieldService) {}
@Query(() => [ViewFieldDTO])
@UseGuards(NoPermissionGuard)
@@ -31,7 +31,7 @@ export class ViewFieldResolver {
@Args('viewId', { type: () => String }) viewId: string,
@AuthWorkspace() workspace: WorkspaceEntity,
): Promise<ViewFieldEntity[]> {
return this.viewFieldV2Service.findByViewId(workspace.id, viewId);
return this.viewFieldService.findByViewId(workspace.id, viewId);
}
@Query(() => ViewFieldDTO, { nullable: true })
@@ -40,7 +40,7 @@ export class ViewFieldResolver {
@Args('id', { type: () => String }) id: string,
@AuthWorkspace() workspace: WorkspaceEntity,
): Promise<ViewFieldEntity | null> {
return this.viewFieldV2Service.findById(id, workspace.id);
return this.viewFieldService.findById(id, workspace.id);
}
@Mutation(() => ViewFieldDTO)
@@ -49,7 +49,7 @@ export class ViewFieldResolver {
@Args('input') updateViewFieldInput: UpdateViewFieldInput,
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
): Promise<ViewFieldDTO> {
return await this.viewFieldV2Service.updateOne({
return await this.viewFieldService.updateOne({
updateViewFieldInput,
workspaceId,
});
@@ -61,7 +61,7 @@ export class ViewFieldResolver {
@Args('input') createViewFieldInput: CreateViewFieldInput,
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
): Promise<ViewFieldDTO> {
return await this.viewFieldV2Service.createOne({
return await this.viewFieldService.createOne({
createViewFieldInput,
workspaceId,
});
@@ -74,7 +74,7 @@ export class ViewFieldResolver {
createViewFieldInputs: CreateViewFieldInput[],
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
): Promise<ViewFieldDTO[]> {
return await this.viewFieldV2Service.createMany({
return await this.viewFieldService.createMany({
createViewFieldInputs,
workspaceId,
});
@@ -86,7 +86,7 @@ export class ViewFieldResolver {
@Args('input') deleteViewFieldInput: DeleteViewFieldInput,
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
): Promise<ViewFieldDTO> {
return await this.viewFieldV2Service.deleteOne({
return await this.viewFieldService.deleteOne({
deleteViewFieldInput,
workspaceId,
});
@@ -98,7 +98,7 @@ export class ViewFieldResolver {
@Args('input') destroyViewFieldInput: DestroyViewFieldInput,
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
): Promise<ViewFieldDTO> {
return await this.viewFieldV2Service.destroyOne({
return await this.viewFieldService.destroyOne({
destroyViewFieldInput,
workspaceId,
});
@@ -27,7 +27,7 @@ import { WorkspaceMigrationBuilderException } from 'src/engine/workspace-manager
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
@Injectable()
export class ViewFieldV2Service {
export class ViewFieldService {
constructor(
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
@@ -76,11 +76,15 @@ export class ViewFieldV2Service {
},
);
const { flatFieldMetadataMaps, flatViewMaps } =
const { flatFieldMetadataMaps, flatViewMaps, flatViewFieldGroupMaps } =
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatFieldMetadataMaps', 'flatViewMaps'],
flatMapsKeys: [
'flatFieldMetadataMaps',
'flatViewMaps',
'flatViewFieldGroupMaps',
],
},
);
@@ -91,6 +95,7 @@ export class ViewFieldV2Service {
flatApplication: workspaceCustomFlatApplication,
flatFieldMetadataMaps,
flatViewMaps,
flatViewFieldGroupMaps,
}),
);
@@ -146,17 +151,21 @@ export class ViewFieldV2Service {
},
);
const { flatViewFieldMaps: existingFlatViewFieldMaps } =
const {
flatViewFieldMaps: existingFlatViewFieldMaps,
flatViewFieldGroupMaps,
} =
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatViewFieldMaps'],
flatMapsKeys: ['flatViewFieldMaps', 'flatViewFieldGroupMaps'],
},
);
const optimisticallyUpdatedFlatView =
fromUpdateViewFieldInputToFlatViewFieldToUpdateOrThrow({
flatViewFieldMaps: existingFlatViewFieldMaps,
flatViewFieldGroupMaps,
updateViewFieldInput,
});
@@ -7,7 +7,7 @@ import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permi
import { ViewFieldController } from 'src/engine/metadata-modules/view-field/controllers/view-field.controller';
import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
import { ViewFieldResolver } from 'src/engine/metadata-modules/view-field/resolvers/view-field.resolver';
import { ViewFieldV2Service } from 'src/engine/metadata-modules/view-field/services/view-field-v2.service';
import { ViewFieldService } from 'src/engine/metadata-modules/view-field/services/view-field.service';
import { ViewPermissionsModule } from 'src/engine/metadata-modules/view-permissions/view-permissions.module';
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
@@ -24,7 +24,7 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
ViewPermissionsModule,
],
controllers: [ViewFieldController],
providers: [ViewFieldResolver, ViewFieldV2Service],
exports: [ViewFieldV2Service],
providers: [ViewFieldResolver, ViewFieldService],
exports: [ViewFieldService],
})
export class ViewFieldModule {}
@@ -4,6 +4,7 @@ import { IDField } from '@ptc-org/nestjs-query-graphql';
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { ViewFieldGroupDTO } from 'src/engine/metadata-modules/view-field-group/dtos/view-field-group.dto';
import { ViewFieldDTO } from 'src/engine/metadata-modules/view-field/dtos/view-field.dto';
import { ViewFilterGroupDTO } from 'src/engine/metadata-modules/view-filter-group/dtos/view-filter-group.dto';
import { ViewFilterDTO } from 'src/engine/metadata-modules/view-filter/dtos/view-filter.dto';
@@ -104,6 +105,9 @@ export class ViewDTO {
@Field(() => [ViewGroupDTO])
viewGroups?: ViewGroupDTO[];
@Field(() => [ViewFieldGroupDTO])
viewFieldGroups?: ViewFieldGroupDTO[];
@Field(() => ViewVisibility, {
nullable: false,
})
@@ -17,6 +17,7 @@ import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { ViewFieldGroupEntity } from 'src/engine/metadata-modules/view-field-group/entities/view-field-group.entity';
import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
import { ViewFilterGroupEntity } from 'src/engine/metadata-modules/view-filter-group/entities/view-filter-group.entity';
import { ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity';
@@ -191,6 +192,12 @@ export class ViewEntity extends SyncableEntity implements Required<ViewEntity> {
@OneToMany(() => ViewFieldEntity, (viewField) => viewField.view)
viewFields: Relation<ViewFieldEntity[]>;
@OneToMany(
() => ViewFieldGroupEntity,
(viewFieldGroup) => viewFieldGroup.view,
)
viewFieldGroups: Relation<ViewFieldGroupEntity[]>;
@OneToMany(() => ViewFilterEntity, (viewFilter) => viewFilter.view)
viewFilters: Relation<ViewFilterEntity[]>;
@@ -11,19 +11,20 @@ import {
import { isArray } from '@sniptt/guards';
import { isDefined } from 'twenty-shared/utils';
import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
import { I18nService } from 'src/engine/core-modules/i18n/i18n.service';
import { type I18nContext } from 'src/engine/core-modules/i18n/types/i18n-context.type';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { type IDataloaders } from 'src/engine/dataloaders/dataloader.interface';
import { AuthUserWorkspaceId } from 'src/engine/decorators/auth/auth-user-workspace-id.decorator';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
import { CustomPermissionGuard } from 'src/engine/guards/custom-permission.guard';
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { resolveObjectMetadataStandardOverride } from 'src/engine/metadata-modules/object-metadata/utils/resolve-object-metadata-standard-override.util';
import { ViewFieldGroupDTO } from 'src/engine/metadata-modules/view-field-group/dtos/view-field-group.dto';
import { ViewFieldDTO } from 'src/engine/metadata-modules/view-field/dtos/view-field.dto';
import { ViewFieldV2Service } from 'src/engine/metadata-modules/view-field/services/view-field-v2.service';
import { ViewFieldService } from 'src/engine/metadata-modules/view-field/services/view-field.service';
import { ViewFilterGroupDTO } from 'src/engine/metadata-modules/view-filter-group/dtos/view-filter-group.dto';
import { ViewFilterGroupService } from 'src/engine/metadata-modules/view-filter-group/services/view-filter-group.service';
import { ViewFilterDTO } from 'src/engine/metadata-modules/view-filter/dtos/view-filter.dto';
@@ -56,7 +57,7 @@ export class ViewResolver {
private readonly viewGroupService: ViewGroupService,
private readonly i18nService: I18nService,
private readonly viewFieldV2Service: ViewFieldV2Service,
private readonly viewFieldService: ViewFieldService,
) {}
@ResolveField(() => String)
@@ -209,7 +210,7 @@ export class ViewResolver {
return view.viewFields;
}
return this.viewFieldV2Service.findByViewId(workspace.id, view.id);
return this.viewFieldService.findByViewId(workspace.id, view.id);
}
@ResolveField(() => [ViewFilterDTO])
@@ -259,4 +260,20 @@ export class ViewResolver {
return this.viewGroupService.findByViewId(workspace.id, view.id);
}
@ResolveField(() => [ViewFieldGroupDTO])
async viewFieldGroups(
@Parent() view: ViewDTO,
@Context() context: { loaders: IDataloaders },
@AuthWorkspace() workspace: WorkspaceEntity,
) {
if (isArray(view.viewFieldGroups)) {
return view.viewFieldGroups;
}
return context.loaders.viewFieldGroupsByViewIdLoader.load({
workspaceId: workspace.id,
viewId: view.id,
});
}
}
@@ -7,6 +7,10 @@ import {
NotFoundError,
UserInputError,
} from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import {
ViewFieldGroupException,
ViewFieldGroupExceptionCode,
} from 'src/engine/metadata-modules/view-field-group/exceptions/view-field-group.exception';
import {
ViewFieldException,
ViewFieldExceptionCode,
@@ -77,6 +81,22 @@ export const viewGraphqlApiExceptionHandler = (error: Error, i18n: I18n) => {
}
}
if (error instanceof ViewFieldGroupException) {
switch (error.code) {
case ViewFieldGroupExceptionCode.VIEW_FIELD_GROUP_NOT_FOUND:
throw new NotFoundError(error.message);
case ViewFieldGroupExceptionCode.VIEW_NOT_FOUND:
throw new NotFoundError(error.message);
case ViewFieldGroupExceptionCode.INVALID_VIEW_FIELD_GROUP_DATA:
throw new UserInputError(error.message, {
userFriendlyMessage: error.userFriendlyMessage,
});
default: {
return assertUnreachable(error.code);
}
}
}
if (error instanceof ViewFilterException) {
switch (error.code) {
case ViewFilterExceptionCode.VIEW_FILTER_NOT_FOUND:
@@ -9,6 +9,7 @@ import { GqlExecutionContext } from '@nestjs/graphql';
import { SOURCE_LOCALE } from 'twenty-shared/translations';
import { I18nService } from 'src/engine/core-modules/i18n/i18n.service';
import { ViewFieldGroupException } from 'src/engine/metadata-modules/view-field-group/exceptions/view-field-group.exception';
import { ViewFieldException } from 'src/engine/metadata-modules/view-field/exceptions/view-field.exception';
import { ViewFilterGroupException } from 'src/engine/metadata-modules/view-filter-group/exceptions/view-filter-group.exception';
import { ViewFilterException } from 'src/engine/metadata-modules/view-filter/exceptions/view-filter.exception';
@@ -35,6 +36,7 @@ export class ViewGraphqlApiExceptionFilter implements ExceptionFilter {
exception:
| ViewException
| ViewFieldException
| ViewFieldGroupException
| ViewFilterException
| ViewFilterGroupException
| ViewGroupException
@@ -7,6 +7,7 @@ import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata
import { FlatViewModule } from 'src/engine/metadata-modules/flat-view/flat-view.module';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module';
import { ViewFieldGroupModule } from 'src/engine/metadata-modules/view-field-group/view-field-group.module';
import { ViewFieldModule } from 'src/engine/metadata-modules/view-field/view-field.module';
import { ViewFilterGroupModule } from 'src/engine/metadata-modules/view-filter-group/view-filter-group.module';
import { ViewFilterModule } from 'src/engine/metadata-modules/view-filter/view-filter.module';
@@ -27,6 +28,7 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
TypeOrmModule.forFeature([ViewEntity]),
ViewPermissionsModule,
ViewFieldModule,
ViewFieldGroupModule,
ViewFilterModule,
ViewFilterGroupModule,
ViewGroupModule,
@@ -17,6 +17,7 @@ export const WORKSPACE_CACHE_KEYS_V2 = {
flatIndexMaps: 'flat-maps:index',
flatViewMaps: 'flat-maps:view',
flatViewFieldMaps: 'flat-maps:view-field',
flatViewFieldGroupMaps: 'flat-maps:view-field-group',
flatViewGroupMaps: 'flat-maps:view-group',
flatViewFilterMaps: 'flat-maps:view-filter',
flatViewFilterGroupMaps: 'flat-maps:view-filter-group',
@@ -5,6 +5,7 @@ export const TWENTY_STANDARD_ALL_METADATA_NAME = [
'objectMetadata',
'fieldMetadata',
'viewField',
'viewFieldGroup',
'viewFilter',
'viewGroup',
'view',
@@ -97,6 +97,8 @@ export const computeTwentyStandardApplicationAllFlatEntityMaps = ({
const flatViewFilterGroupMaps = createEmptyFlatEntityMaps();
const flatViewFieldGroupMaps = createEmptyFlatEntityMaps();
const flatViewFilterMaps = buildStandardFlatViewFilterMetadataMaps({
dependencyFlatEntityMaps: {
flatFieldMetadataMaps,
@@ -114,6 +116,7 @@ export const computeTwentyStandardApplicationAllFlatEntityMaps = ({
flatObjectMetadataMaps,
flatFieldMetadataMaps,
flatViewMaps,
flatViewFieldGroupMaps,
},
now,
standardObjectMetadataRelatedEntityIds,
@@ -188,6 +191,7 @@ export const computeTwentyStandardApplicationAllFlatEntityMaps = ({
const allFlatEntityMaps: TwentyStandardAllFlatEntityMaps = {
flatViewFieldMaps,
flatViewFieldGroupMaps,
flatViewFilterMaps,
flatViewGroupMaps,
flatViewMaps,
@@ -88,6 +88,8 @@ export const createStandardViewFieldFlatMetadata = <
fieldMetadataId:
standardObjectMetadataRelatedEntityIds[objectName].fields[fieldName].id,
fieldMetadataUniversalIdentifier: fieldDefinition.universalIdentifier,
viewFieldGroupId: null,
viewFieldGroupUniversalIdentifier: null,
position,
isVisible,
size,
@@ -142,6 +142,8 @@ export const createStandardViewFlatMetadata = <
createdByUserWorkspaceId: null,
viewFieldIds: [],
viewFieldUniversalIdentifiers: [],
viewFieldGroupIds: [],
viewFieldGroupUniversalIdentifiers: [],
viewFilterIds: [],
viewFilterUniversalIdentifiers: [],
viewGroupIds: [],
@@ -143,6 +143,7 @@ export const enrichCreateWorkspaceMigrationActionsWithIds = ({
case 'view':
case 'viewField':
case 'viewGroup':
case 'viewFieldGroup':
case 'rowLevelPermissionPredicate':
case 'rowLevelPermissionPredicateGroup':
case 'viewFilterGroup':
@@ -31,6 +31,7 @@ import { WorkspaceMigrationRowLevelPermissionPredicateGroupActionsBuilderService
import { WorkspaceMigrationRowLevelPermissionPredicateActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/row-level-permission-predicate/workspace-migration-row-level-permission-predicate-actions-builder.service';
import { WorkspaceMigrationSkillActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/skill/workspace-migration-skill-actions-builder.service';
import { WorkspaceMigrationViewFieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-field/workspace-migration-view-field-actions-builder.service';
import { WorkspaceMigrationViewFieldGroupActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-field-group/workspace-migration-view-field-group-actions-builder.service';
import { WorkspaceMigrationViewFilterGroupActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-filter-group/workspace-migration-view-filter-group-actions-builder.service';
import { WorkspaceMigrationViewFilterActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-filter/workspace-migration-view-filter-actions-builder.service';
import { WorkspaceMigrationViewGroupActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-group/workspace-migration-view-group-actions-builder.service';
@@ -47,6 +48,7 @@ export class WorkspaceMigrationBuildOrchestratorService {
private readonly workspaceMigrationViewFilterActionsBuilderService: WorkspaceMigrationViewFilterActionsBuilderService,
private readonly workspaceMigrationViewFilterGroupActionsBuilderService: WorkspaceMigrationViewFilterGroupActionsBuilderService,
private readonly workspaceMigrationViewGroupActionsBuilderService: WorkspaceMigrationViewGroupActionsBuilderService,
private readonly workspaceMigrationViewFieldGroupActionsBuilderService: WorkspaceMigrationViewFieldGroupActionsBuilderService,
private readonly workspaceMigrationLogicFunctionActionsBuilderService: WorkspaceMigrationLogicFunctionActionsBuilderService,
private readonly workspaceMigrationRoleTargetActionsBuilderService: WorkspaceMigrationRoleTargetActionsBuilderService,
private readonly workspaceMigrationFieldActionsBuilderService: WorkspaceMigrationFieldActionsBuilderService,
@@ -150,6 +152,7 @@ export class WorkspaceMigrationBuildOrchestratorService {
flatViewFilterMaps,
flatViewFilterGroupMaps,
flatViewGroupMaps,
flatViewFieldGroupMaps,
flatRowLevelPermissionPredicateMaps,
flatRowLevelPermissionPredicateGroupMaps,
flatRoleMaps,
@@ -324,6 +327,8 @@ export class WorkspaceMigrationBuildOrchestratorService {
flatFieldMetadataMaps:
optimisticAllFlatEntityMaps.flatFieldMetadataMaps,
flatViewMaps: optimisticAllFlatEntityMaps.flatViewMaps,
flatViewFieldGroupMaps:
optimisticAllFlatEntityMaps.flatViewFieldGroupMaps,
},
workspaceId,
},
@@ -452,6 +457,41 @@ export class WorkspaceMigrationBuildOrchestratorService {
}
}
if (isDefined(flatViewFieldGroupMaps)) {
const { from: fromFlatViewFieldGroupMaps, to: toFlatViewFieldGroupMaps } =
flatViewFieldGroupMaps;
const viewFieldGroupResult =
await this.workspaceMigrationViewFieldGroupActionsBuilderService.validateAndBuild(
{
additionalCacheDataMaps,
from: fromFlatViewFieldGroupMaps,
to: toFlatViewFieldGroupMaps,
buildOptions,
dependencyOptimisticFlatEntityMaps: {
flatViewMaps: optimisticAllFlatEntityMaps.flatViewMaps,
},
workspaceId,
},
);
this.mergeFlatEntityMapsAndRelatedFlatEntityMapsInAllFlatEntityMapsThroughMutation(
{
allFlatEntityMaps: optimisticAllFlatEntityMaps,
flatEntityMapsAndRelatedFlatEntityMaps:
viewFieldGroupResult.optimisticFlatEntityMapsAndRelatedFlatEntityMaps,
},
);
if (viewFieldGroupResult.status === 'fail') {
orchestratorFailureReport.viewFieldGroup.push(
...viewFieldGroupResult.errors,
);
} else {
orchestratorActionsReport.viewFieldGroup = viewFieldGroupResult.actions;
}
}
if (isDefined(flatRowLevelPermissionPredicateGroupMaps)) {
const {
from: fromFlatRowLevelPermissionPredicateGroupMaps,
@@ -1003,6 +1043,9 @@ export class WorkspaceMigrationBuildOrchestratorService {
...aggregatedOrchestratorActionsReport.viewGroup.delete,
...aggregatedOrchestratorActionsReport.viewGroup.create,
...aggregatedOrchestratorActionsReport.viewGroup.update,
...aggregatedOrchestratorActionsReport.viewFieldGroup.delete,
...aggregatedOrchestratorActionsReport.viewFieldGroup.create,
...aggregatedOrchestratorActionsReport.viewFieldGroup.update,
///
// Logic functions
@@ -15,6 +15,7 @@ export const ALL_JSONB_PROPERTIES_WITH_SERIALIZED_RELATION_BY_METADATA_NAME = {
view: {},
viewField: {},
viewGroup: {},
viewFieldGroup: {},
viewFilter: {},
viewFilterGroup: {},
index: {},
@@ -202,6 +202,7 @@ export const ALL_UNIVERSAL_METADATA_RELATIONS = {
viewFilters: { metadataName: 'viewFilter' },
viewFilterGroups: { metadataName: 'viewFilterGroup' },
viewGroups: { metadataName: 'viewGroup' },
viewFieldGroups: { metadataName: 'viewFieldGroup' },
// TODO migrate viewSort to v2
viewSorts: null,
},
@@ -224,11 +225,36 @@ export const ALL_UNIVERSAL_METADATA_RELATIONS = {
universalForeignKey: 'viewUniversalIdentifier',
isNullable: false,
},
viewFieldGroup: {
metadataName: 'viewFieldGroup',
foreignKey: 'viewFieldGroupId',
universalFlatEntityForeignKeyAggregator:
'viewFieldUniversalIdentifiers',
universalForeignKey: 'viewFieldGroupUniversalIdentifier',
isNullable: true,
},
workspace: null,
application: null,
},
oneToMany: {},
},
viewFieldGroup: {
manyToOne: {
view: {
metadataName: 'view',
foreignKey: 'viewId',
universalFlatEntityForeignKeyAggregator:
'viewFieldGroupUniversalIdentifiers',
universalForeignKey: 'viewUniversalIdentifier',
isNullable: false,
},
workspace: null,
application: null,
},
oneToMany: {
viewFields: { metadataName: 'viewField' },
},
},
viewFilter: {
manyToOne: {
fieldMetadata: {
@@ -0,0 +1,7 @@
import { type ViewFieldGroupEntity } from 'src/engine/metadata-modules/view-field-group/entities/view-field-group.entity';
import { type UniversalFlatEntityFrom } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-from.type';
export type UniversalFlatViewFieldGroup = UniversalFlatEntityFrom<
ViewFieldGroupEntity,
'viewFieldGroup'
>;
@@ -0,0 +1,24 @@
import { type BaseFlatCreateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-flat-create-workspace-migration-action.type';
import { type BaseFlatDeleteWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-flat-delete-workspace-migration-action.type';
import { type BaseFlatUpdateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-flat-update-workspace-migration-action.type';
import { type BaseUniversalCreateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-universal-create-workspace-migration-action.type';
import { type BaseUniversalDeleteWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-universal-delete-workspace-migration-action.type';
import { type BaseUniversalUpdateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-universal-update-workspace-migration-action.type';
export type UniversalCreateViewFieldGroupAction =
BaseUniversalCreateWorkspaceMigrationAction<'viewFieldGroup'>;
export type FlatCreateViewFieldGroupAction =
BaseFlatCreateWorkspaceMigrationAction<'viewFieldGroup'>;
export type FlatUpdateViewFieldGroupAction =
BaseFlatUpdateWorkspaceMigrationAction<'viewFieldGroup'>;
export type UniversalUpdateViewFieldGroupAction =
BaseUniversalUpdateWorkspaceMigrationAction<'viewFieldGroup'>;
export type UniversalDeleteViewFieldGroupAction =
BaseUniversalDeleteWorkspaceMigrationAction<'viewFieldGroup'>;
export type FlatDeleteViewFieldGroupAction =
BaseFlatDeleteWorkspaceMigrationAction<'viewFieldGroup'>;
@@ -0,0 +1,116 @@
import { Injectable } from '@nestjs/common';
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
import { UniversalUpdateViewFieldGroupAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-field-group/types/workspace-migration-view-field-group-action.type';
import { WorkspaceEntityMigrationBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/services/workspace-entity-migration-builder.service';
import { FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-update-validation-args.type';
import { UniversalFlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-args.type';
import { UniversalFlatEntityValidationReturnType } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-result.type';
import { FlatViewFieldGroupValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-field-group-validator.service';
@Injectable()
export class WorkspaceMigrationViewFieldGroupActionsBuilderService extends WorkspaceEntityMigrationBuilderService<
typeof ALL_METADATA_NAME.viewFieldGroup
> {
constructor(
private readonly flatViewFieldGroupValidatorService: FlatViewFieldGroupValidatorService,
) {
super(ALL_METADATA_NAME.viewFieldGroup);
}
protected validateFlatEntityCreation(
args: UniversalFlatEntityValidationArgs<
typeof ALL_METADATA_NAME.viewFieldGroup
>,
): UniversalFlatEntityValidationReturnType<
typeof ALL_METADATA_NAME.viewFieldGroup,
'create'
> {
const validationResult =
this.flatViewFieldGroupValidatorService.validateFlatViewFieldGroupCreation(
args,
);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
return {
status: 'success',
action: {
type: 'create',
metadataName: 'viewFieldGroup',
flatEntity: args.flatEntityToValidate,
},
};
}
protected validateFlatEntityDeletion(
args: UniversalFlatEntityValidationArgs<
typeof ALL_METADATA_NAME.viewFieldGroup
>,
): UniversalFlatEntityValidationReturnType<
typeof ALL_METADATA_NAME.viewFieldGroup,
'delete'
> {
const validationResult =
this.flatViewFieldGroupValidatorService.validateFlatViewFieldGroupDeletion(
args,
);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
return {
status: 'success',
action: {
type: 'delete',
metadataName: 'viewFieldGroup',
universalIdentifier: args.flatEntityToValidate.universalIdentifier,
},
};
}
protected validateFlatEntityUpdate(
args: FlatEntityUpdateValidationArgs<
typeof ALL_METADATA_NAME.viewFieldGroup
>,
): UniversalFlatEntityValidationReturnType<
typeof ALL_METADATA_NAME.viewFieldGroup,
'update'
> {
const validationResult =
this.flatViewFieldGroupValidatorService.validateFlatViewFieldGroupUpdate(
args,
);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
const { universalIdentifier, flatEntityUpdate } = args;
const updateAction: UniversalUpdateViewFieldGroupAction = {
type: 'update',
metadataName: 'viewFieldGroup',
universalIdentifier,
update: flatEntityUpdate,
};
return {
status: 'success',
action: updateAction,
};
}
}
@@ -0,0 +1,143 @@
import { Injectable } from '@nestjs/common';
import { msg, t } from '@lingui/core/macro';
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
import { isDefined } from 'twenty-shared/utils';
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
import { ViewExceptionCode } from 'src/engine/metadata-modules/view/exceptions/view.exception';
import { FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/types/failed-flat-entity-validation.type';
import { getEmptyFlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/utils/get-flat-entity-validation-error.util';
import { FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-update-validation-args.type';
import { UniversalFlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-args.type';
@Injectable()
export class FlatViewFieldGroupValidatorService {
public validateFlatViewFieldGroupCreation({
flatEntityToValidate: flatViewFieldGroupToValidate,
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatViewFieldGroupMaps: optimisticFlatViewFieldGroupMaps,
flatViewMaps,
},
}: UniversalFlatEntityValidationArgs<
typeof ALL_METADATA_NAME.viewFieldGroup
>): FailedFlatEntityValidation<'viewFieldGroup', 'create'> {
const validationResult = getEmptyFlatEntityValidationError({
flatEntityMinimalInformation: {
universalIdentifier: flatViewFieldGroupToValidate.universalIdentifier,
viewUniversalIdentifier:
flatViewFieldGroupToValidate.viewUniversalIdentifier,
},
metadataName: 'viewFieldGroup',
type: 'create',
});
const existingFlatViewFieldGroup = findFlatEntityByUniversalIdentifier({
universalIdentifier: flatViewFieldGroupToValidate.universalIdentifier,
flatEntityMaps: optimisticFlatViewFieldGroupMaps,
});
if (isDefined(existingFlatViewFieldGroup)) {
validationResult.errors.push({
code: ViewExceptionCode.INVALID_VIEW_DATA,
message: t`View field group with this universal identifier already exists`,
userFriendlyMessage: msg`View field group already exists`,
});
}
const flatView = findFlatEntityByUniversalIdentifier({
universalIdentifier: flatViewFieldGroupToValidate.viewUniversalIdentifier,
flatEntityMaps: flatViewMaps,
});
if (!isDefined(flatView)) {
validationResult.errors.push({
code: ViewExceptionCode.INVALID_VIEW_DATA,
message: t`View not found`,
userFriendlyMessage: msg`View not found`,
});
}
return validationResult;
}
public validateFlatViewFieldGroupDeletion({
flatEntityToValidate: { universalIdentifier },
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatViewFieldGroupMaps: optimisticFlatViewFieldGroupMaps,
},
}: UniversalFlatEntityValidationArgs<
typeof ALL_METADATA_NAME.viewFieldGroup
>): FailedFlatEntityValidation<'viewFieldGroup', 'delete'> {
const validationResult = getEmptyFlatEntityValidationError({
flatEntityMinimalInformation: {
universalIdentifier,
},
metadataName: 'viewFieldGroup',
type: 'delete',
});
const existingFlatViewFieldGroup = findFlatEntityByUniversalIdentifier({
universalIdentifier,
flatEntityMaps: optimisticFlatViewFieldGroupMaps,
});
if (!isDefined(existingFlatViewFieldGroup)) {
validationResult.errors.push({
code: ViewExceptionCode.INVALID_VIEW_DATA,
message: t`View field group to delete not found`,
userFriendlyMessage: msg`View field group to delete not found`,
});
}
return validationResult;
}
public validateFlatViewFieldGroupUpdate({
universalIdentifier,
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatViewFieldGroupMaps: optimisticFlatViewFieldGroupMaps,
flatViewMaps,
},
}: FlatEntityUpdateValidationArgs<
typeof ALL_METADATA_NAME.viewFieldGroup
>): FailedFlatEntityValidation<'viewFieldGroup', 'update'> {
const existingFlatViewFieldGroup = findFlatEntityByUniversalIdentifier({
universalIdentifier,
flatEntityMaps: optimisticFlatViewFieldGroupMaps,
});
const validationResult = getEmptyFlatEntityValidationError({
flatEntityMinimalInformation: {
universalIdentifier,
},
metadataName: 'viewFieldGroup',
type: 'update',
});
if (!isDefined(existingFlatViewFieldGroup)) {
validationResult.errors.push({
code: ViewExceptionCode.INVALID_VIEW_DATA,
message: t`View field group to update not found`,
userFriendlyMessage: msg`View field group to update not found`,
});
return validationResult;
}
const flatView = findFlatEntityByUniversalIdentifier({
universalIdentifier: existingFlatViewFieldGroup.viewUniversalIdentifier,
flatEntityMaps: flatViewMaps,
});
if (!isDefined(flatView)) {
validationResult.errors.push({
code: ViewExceptionCode.INVALID_VIEW_DATA,
message: t`View field group parent view not found`,
userFriendlyMessage: msg`View field group parent view not found`,
});
}
return validationResult;
}
}
@@ -19,6 +19,7 @@ import { FlatRowLevelPermissionPredicateGroupValidatorService } from 'src/engine
import { FlatRowLevelPermissionPredicateValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-row-level-permission-predicate-validator.service';
import { FlatLogicFunctionValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-logic-function-validator.service';
import { FlatSkillValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-skill-validator.service';
import { FlatViewFieldGroupValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-field-group-validator.service';
import { FlatViewFieldValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-field-validator.service';
import { FlatViewFilterGroupValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-filter-group-validator.service';
import { FlatViewFilterValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-filter-validator.service';
@@ -34,6 +35,7 @@ import { FlatWebhookValidatorService } from 'src/engine/workspace-manager/worksp
FlatViewFilterValidatorService,
FlatViewFilterGroupValidatorService,
FlatViewGroupValidatorService,
FlatViewFieldGroupValidatorService,
FlatIndexValidatorService,
FlatFieldMetadataValidatorService,
FlatObjectMetadataValidatorService,
@@ -60,6 +62,7 @@ import { FlatWebhookValidatorService } from 'src/engine/workspace-manager/worksp
FlatViewFilterValidatorService,
FlatViewFilterGroupValidatorService,
FlatViewGroupValidatorService,
FlatViewFieldGroupValidatorService,
FlatIndexValidatorService,
FlatFieldMetadataValidatorService,
FlatObjectMetadataValidatorService,
@@ -19,6 +19,7 @@ import { WorkspaceMigrationLogicFunctionActionsBuilderService } from 'src/engine
import { WorkspaceMigrationSkillActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/skill/workspace-migration-skill-actions-builder.service';
import { WorkspaceMigrationFrontComponentActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/front-component/workspace-migration-front-component-actions-builder.service';
import { WorkspaceMigrationViewFieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-field/workspace-migration-view-field-actions-builder.service';
import { WorkspaceMigrationViewFieldGroupActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-field-group/workspace-migration-view-field-group-actions-builder.service';
import { WorkspaceMigrationWebhookActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/webhook/workspace-migration-webhook-actions-builder.service';
import { WorkspaceMigrationViewFilterGroupActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-filter-group/workspace-migration-view-filter-group-actions-builder.service';
import { WorkspaceMigrationViewFilterActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-filter/workspace-migration-view-filter-actions-builder.service';
@@ -36,6 +37,7 @@ import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-
WorkspaceMigrationViewFilterActionsBuilderService,
WorkspaceMigrationViewFilterGroupActionsBuilderService,
WorkspaceMigrationViewGroupActionsBuilderService,
WorkspaceMigrationViewFieldGroupActionsBuilderService,
WorkspaceMigrationIndexActionsBuilderService,
WorkspaceMigrationLogicFunctionActionsBuilderService,
WorkspaceMigrationFieldActionsBuilderService,
@@ -61,6 +63,7 @@ import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-
WorkspaceMigrationViewFilterActionsBuilderService,
WorkspaceMigrationViewFilterGroupActionsBuilderService,
WorkspaceMigrationViewGroupActionsBuilderService,
WorkspaceMigrationViewFieldGroupActionsBuilderService,
WorkspaceMigrationFieldActionsBuilderService,
WorkspaceMigrationLogicFunctionActionsBuilderService,
WorkspaceMigrationRoleActionsBuilderService,
@@ -0,0 +1,71 @@
import { Injectable } from '@nestjs/common';
import { v4 } from 'uuid';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
import { ViewFieldGroupEntity } from 'src/engine/metadata-modules/view-field-group/entities/view-field-group.entity';
import { resolveUniversalRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-relation-identifiers-to-ids.util';
import {
FlatCreateViewFieldGroupAction,
UniversalCreateViewFieldGroupAction,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-field-group/types/workspace-migration-view-field-group-action.type';
import {
WorkspaceMigrationActionRunnerArgs,
WorkspaceMigrationActionRunnerContext,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
@Injectable()
export class CreateViewFieldGroupActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'create',
'viewFieldGroup',
) {
override async transpileUniversalActionToFlatAction({
action,
allFlatEntityMaps,
flatApplication,
workspaceId,
}: WorkspaceMigrationActionRunnerArgs<UniversalCreateViewFieldGroupAction>): Promise<FlatCreateViewFieldGroupAction> {
const { viewId } = resolveUniversalRelationIdentifiersToIds({
flatEntityMaps: allFlatEntityMaps,
metadataName: action.metadataName,
universalForeignKeyValues: action.flatEntity,
});
return {
...action,
flatEntity: {
...action.flatEntity,
viewId,
viewFieldIds: [],
viewFieldUniversalIdentifiers: [],
id: action.id ?? v4(),
applicationId: flatApplication.id,
workspaceId,
},
};
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerContext<FlatCreateViewFieldGroupAction>,
): Promise<void> {
const { flatAction, queryRunner, workspaceId } = context;
const { flatEntity } = flatAction;
const repository =
queryRunner.manager.getRepository<ViewFieldGroupEntity>(
ViewFieldGroupEntity,
);
await repository.insert({
...flatEntity,
workspaceId,
});
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerContext<FlatCreateViewFieldGroupAction>,
): Promise<void> {
return;
}
}
@@ -0,0 +1,47 @@
import { Injectable } from '@nestjs/common';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
import { ViewFieldGroupEntity } from 'src/engine/metadata-modules/view-field-group/entities/view-field-group.entity';
import {
FlatDeleteViewFieldGroupAction,
UniversalDeleteViewFieldGroupAction,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-field-group/types/workspace-migration-view-field-group-action.type';
import {
WorkspaceMigrationActionRunnerArgs,
WorkspaceMigrationActionRunnerContext,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
@Injectable()
export class DeleteViewFieldGroupActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'delete',
'viewFieldGroup',
) {
override async transpileUniversalActionToFlatAction(
context: WorkspaceMigrationActionRunnerArgs<UniversalDeleteViewFieldGroupAction>,
): Promise<FlatDeleteViewFieldGroupAction> {
return this.transpileUniversalDeleteActionToFlatDeleteAction(context);
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerContext<FlatDeleteViewFieldGroupAction>,
): Promise<void> {
const { flatAction, queryRunner, workspaceId } = context;
const repository =
queryRunner.manager.getRepository<ViewFieldGroupEntity>(
ViewFieldGroupEntity,
);
await repository.delete({
id: flatAction.entityId,
workspaceId,
});
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerContext<FlatDeleteViewFieldGroupAction>,
): Promise<void> {
return;
}
}
@@ -0,0 +1,65 @@
import { Injectable } from '@nestjs/common';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util';
import { ViewFieldGroupEntity } from 'src/engine/metadata-modules/view-field-group/entities/view-field-group.entity';
import { resolveUniversalUpdateRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-update-relation-identifiers-to-ids.util';
import {
FlatUpdateViewFieldGroupAction,
UniversalUpdateViewFieldGroupAction,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-field-group/types/workspace-migration-view-field-group-action.type';
import {
WorkspaceMigrationActionRunnerArgs,
WorkspaceMigrationActionRunnerContext,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
@Injectable()
export class UpdateViewFieldGroupActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'update',
'viewFieldGroup',
) {
override async transpileUniversalActionToFlatAction(
context: WorkspaceMigrationActionRunnerArgs<UniversalUpdateViewFieldGroupAction>,
): Promise<FlatUpdateViewFieldGroupAction> {
const { action, allFlatEntityMaps } = context;
const flatViewFieldGroup = findFlatEntityByUniversalIdentifierOrThrow({
flatEntityMaps: allFlatEntityMaps.flatViewFieldGroupMaps,
universalIdentifier: action.universalIdentifier,
});
const update = resolveUniversalUpdateRelationIdentifiersToIds({
metadataName: 'viewFieldGroup',
universalUpdate: action.update,
allFlatEntityMaps,
});
return {
type: 'update',
metadataName: 'viewFieldGroup',
entityId: flatViewFieldGroup.id,
update,
};
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerContext<FlatUpdateViewFieldGroupAction>,
): Promise<void> {
const { flatAction, queryRunner, workspaceId } = context;
const { entityId, update } = flatAction;
const repository =
queryRunner.manager.getRepository<ViewFieldGroupEntity>(
ViewFieldGroupEntity,
);
await repository.update({ id: entityId, workspaceId }, update);
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerContext<FlatUpdateViewFieldGroupAction>,
): Promise<void> {
return;
}
}
@@ -30,7 +30,7 @@ export class CreateViewFieldActionHandlerService extends WorkspaceMigrationRunne
flatApplication,
workspaceId,
}: WorkspaceMigrationActionRunnerArgs<UniversalCreateViewFieldAction>): Promise<FlatCreateViewFieldAction> {
const { fieldMetadataId, viewId } =
const { fieldMetadataId, viewId, viewFieldGroupId } =
resolveUniversalRelationIdentifiersToIds({
flatEntityMaps: allFlatEntityMaps,
metadataName: action.metadataName,
@@ -43,6 +43,7 @@ export class CreateViewFieldActionHandlerService extends WorkspaceMigrationRunne
...action.flatEntity,
fieldMetadataId,
viewId,
viewFieldGroupId,
id: action.id ?? v4(),
applicationId: flatApplication.id,
workspaceId,
@@ -53,6 +53,7 @@ export class CreateViewActionHandlerService extends WorkspaceMigrationRunnerActi
applicationId: flatApplication.id,
workspaceId,
viewFieldIds: [],
viewFieldGroupIds: [],
viewGroupIds: [],
viewFilterIds: [],
viewFilterGroupIds: [],
@@ -54,6 +54,9 @@ import { UpdateSkillActionHandlerService } from 'src/engine/workspace-manager/wo
import { CreateWebhookActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/webhook/services/create-webhook-action-handler.service';
import { DeleteWebhookActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/webhook/services/delete-webhook-action-handler.service';
import { UpdateWebhookActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/webhook/services/update-webhook-action-handler.service';
import { CreateViewFieldGroupActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view-field-group/services/create-view-field-group-action-handler.service';
import { DeleteViewFieldGroupActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view-field-group/services/delete-view-field-group-action-handler.service';
import { UpdateViewFieldGroupActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view-field-group/services/update-view-field-group-action-handler.service';
import { CreateViewFieldActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view-field/services/create-view-field-action-handler.service';
import { DeleteViewFieldActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view-field/services/delete-view-field-action-handler.service';
import { UpdateViewFieldActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view-field/services/update-view-field-action-handler.service';
@@ -108,6 +111,10 @@ import { UpdateViewActionHandlerService } from 'src/engine/workspace-manager/wor
UpdateViewGroupActionHandlerService,
DeleteViewGroupActionHandlerService,
CreateViewFieldGroupActionHandlerService,
UpdateViewFieldGroupActionHandlerService,
DeleteViewFieldGroupActionHandlerService,
CreateLogicFunctionActionHandlerService,
DeleteLogicFunctionActionHandlerService,
UpdateLogicFunctionActionHandlerService,
@@ -45,6 +45,7 @@ export const deriveMetadataEventsFromCreateAction = (
case 'view':
case 'viewField':
case 'viewGroup':
case 'viewFieldGroup':
case 'rowLevelPermissionPredicate':
case 'rowLevelPermissionPredicateGroup':
case 'viewFilterGroup':
@@ -22,6 +22,7 @@ export const deriveMetadataEventsFromDeleteAction = ({
case 'view':
case 'viewField':
case 'viewGroup':
case 'viewFieldGroup':
case 'rowLevelPermissionPredicate':
case 'rowLevelPermissionPredicateGroup':
case 'viewFilterGroup':
@@ -89,6 +89,7 @@ export const deriveMetadataEventsFromUpdateAction = ({
case 'view':
case 'viewField':
case 'viewGroup':
case 'viewFieldGroup':
case 'rowLevelPermissionPredicate':
case 'rowLevelPermissionPredicateGroup':
case 'viewFilterGroup':
@@ -45,6 +45,7 @@ export const optimisticallyApplyCreateActionOnAllFlatEntityMaps = ({
case 'view':
case 'viewField':
case 'viewGroup':
case 'viewFieldGroup':
case 'rowLevelPermissionPredicate':
case 'rowLevelPermissionPredicateGroup':
case 'viewFilterGroup':
@@ -22,6 +22,7 @@ export const optimisticallyApplyDeleteActionOnAllFlatEntityMaps = ({
case 'view':
case 'viewField':
case 'viewGroup':
case 'viewFieldGroup':
case 'rowLevelPermissionPredicate':
case 'rowLevelPermissionPredicateGroup':
case 'viewFilterGroup':
@@ -44,6 +44,7 @@ export const optimisticallyApplyUpdateActionOnAllFlatEntityMaps = ({
case 'view':
case 'viewField':
case 'viewGroup':
case 'viewFieldGroup':
case 'rowLevelPermissionPredicate':
case 'rowLevelPermissionPredicateGroup':
case 'viewFilterGroup':