[REQUIRES_FULL_CACHE_FLUSH_WHEN_RELEASED] Refactor FlatEntity to be UniversalFlatEntity superset (#17452)
# Introduction
In this PR we're refactoring the `FlatEntity` type to become a superset
of the `UniversalFlatEntity`.
Right now we're storing all the extra properties in `__universal`
property, at some point it might just be sibling to other entity and we
might rely on the `propertiesToCompare` constants and TypeScript
allowing passing a superset type into a smaller subset type
## FromTo utils
The entity to flat entity method now computes the universal information,
standardized a typing and pattern to do
## Example
Also strictly type
```ts
"bbb019ea-6205-498c-aea5-67bc53bce8a9": {
"workspaceId": "20202020-1c25-4d02-bf25-6aeccf7ea419",
"universalIdentifier": "20202020-d111-4d11-8d11-da5ab0a11002",
"applicationId": "d01b010d-b984-465b-b40b-370e954e5188",
"id": "bbb019ea-6205-498c-aea5-67bc53bce8a9",
"pageLayoutTabId": "791a512f-169f-4209-b731-aa86716668c6",
"title": "Deals by Company",
"type": "GRAPH",
"objectMetadataId": "9e14efea-df5b-4c0e-aba9-cfe455f32397",
"gridPosition": { "row": 0, "column": 6, "rowSpan": 6, "columnSpan": 6 },
"configuration": {
"color": "orange",
"orderBy": "FIELD_ASC",
"timezone": "UTC",
"displayLegend": true,
"displayDataLabel": false,
"showCenterMetric": true,
"configurationType": "PIE_CHART",
"firstDayOfTheWeek": 0,
"aggregateOperation": "COUNT",
"groupBySubFieldName": "name",
"groupByFieldMetadataId": "6673ff18-63d2-47a1-8f85-2b9b09ca27a5",
"aggregateFieldMetadataId": "8d64ee41-5dd4-4de6-945a-7c0c18399715"
},
"createdAt": "2026-01-28T14:08:52.140Z",
"updatedAt": "2026-01-28T14:08:52.140Z",
"deletedAt": null,
"__universal": {
"universalIdentifier": "20202020-d111-4d11-8d11-da5ab0a11002",
"applicationUniversalIdentifier": "20202020-64aa-4b6f-b003-9c74b97cee20",
"pageLayoutTabUniversalIdentifier": "20202020-d011-4d11-8d11-da5ab0a01001",
"objectMetadataUniversalIdentifier": "20202020-9549-49dd-b2b2-883999db8938",
"gridPosition": {
"row": 0,
"column": 6,
"rowSpan": 6,
"columnSpan": 6
},
"configuration": {
"color": "orange",
"orderBy": "FIELD_ASC",
"timezone": "UTC",
"displayLegend": true,
"displayDataLabel": false,
"showCenterMetric": true,
"configurationType": "PIE_CHART",
"firstDayOfTheWeek": 0,
"aggregateOperation": "COUNT",
"groupBySubFieldName": "name",
"aggregateFieldMetadataUniversalIdentifier": "20202020-d01a-4131-8a31-f123456789ab",
"groupByFieldMetadataUniversalIdentifier": "20202020-cbac-457e-b565-adece5fc815f"
}
}
},
```
This commit is contained in:
+25
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
type AllFieldMetadataSettings,
|
||||
type FieldMetadataSettingsMapping,
|
||||
FieldMetadataType,
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const isFieldMetadataSettingsOfType = <
|
||||
T extends keyof FieldMetadataSettingsMapping,
|
||||
>(
|
||||
settings: AllFieldMetadataSettings | null,
|
||||
fieldMetadataType: T,
|
||||
): settings is FieldMetadataSettingsMapping[T] => {
|
||||
// Settings don't have a discriminator - the type is determined by fieldMetadataType
|
||||
// For required settings types (RELATION, MORPH_RELATION, FILES), ensure settings is defined
|
||||
if (
|
||||
fieldMetadataType === FieldMetadataType.RELATION ||
|
||||
fieldMetadataType === FieldMetadataType.MORPH_RELATION ||
|
||||
fieldMetadataType === FieldMetadataType.FILES
|
||||
) {
|
||||
return isDefined(settings);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
@@ -1,15 +1,22 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { WorkspaceFlatAgentMapCacheService } from 'src/engine/metadata-modules/flat-agent/services/workspace-flat-agent-map-cache.service';
|
||||
import { WorkspaceFlatRoleTargetByAgentIdService } from 'src/engine/metadata-modules/flat-agent/services/workspace-flat-role-target-by-agent-id.service';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([AgentEntity, RoleTargetEntity]),
|
||||
TypeOrmModule.forFeature([
|
||||
AgentEntity,
|
||||
ApplicationEntity,
|
||||
RoleEntity,
|
||||
RoleTargetEntity,
|
||||
]),
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
],
|
||||
providers: [
|
||||
|
||||
+22
-5
@@ -5,11 +5,13 @@ 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 { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { type FlatAgentMaps } from 'src/engine/metadata-modules/flat-agent/types/flat-agent-maps.type';
|
||||
import { transformAgentEntityToFlatAgent } from 'src/engine/metadata-modules/flat-agent/utils/transform-agent-entity-to-flat-agent.util';
|
||||
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
|
||||
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 { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
|
||||
|
||||
@Injectable()
|
||||
@@ -18,20 +20,35 @@ export class WorkspaceFlatAgentMapCacheService extends WorkspaceCacheProvider<Fl
|
||||
constructor(
|
||||
@InjectRepository(AgentEntity)
|
||||
private readonly agentRepository: Repository<AgentEntity>,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async computeForCache(workspaceId: string): Promise<FlatAgentMaps> {
|
||||
const agents = await this.agentRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
});
|
||||
const [agents, applications] = await Promise.all([
|
||||
this.agentRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.applicationRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
const applicationIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(applications);
|
||||
|
||||
const flatAgentMaps = createEmptyFlatEntityMaps();
|
||||
|
||||
for (const agentEntity of agents) {
|
||||
const flatAgent = transformAgentEntityToFlatAgent(agentEntity);
|
||||
const flatAgent = transformAgentEntityToFlatAgent({
|
||||
entity: agentEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
});
|
||||
|
||||
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
|
||||
flatEntity: flatAgent,
|
||||
|
||||
+37
-10
@@ -6,10 +6,13 @@ import { IsNull, Not, 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 { FlatRoleTargetByAgentIdMaps } from 'src/engine/metadata-modules/flat-agent/types/flat-role-target-by-agent-id-maps.type';
|
||||
import { fromRoleTargetsEntityToFlatRoleTarget } from 'src/engine/metadata-modules/flat-role-target/utils/from-role-target-entity-to-flat-role-target.util';
|
||||
import { fromRoleTargetEntityToFlatRoleTarget } from 'src/engine/metadata-modules/flat-role-target/utils/from-role-target-entity-to-flat-role-target.util';
|
||||
import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.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';
|
||||
|
||||
@Injectable()
|
||||
@WorkspaceCache('flatRoleTargetByAgentIdMaps')
|
||||
@@ -17,6 +20,10 @@ export class WorkspaceFlatRoleTargetByAgentIdService extends WorkspaceCacheProvi
|
||||
constructor(
|
||||
@InjectRepository(RoleTargetEntity)
|
||||
private readonly roleTargetRepository: Repository<RoleTargetEntity>,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
@InjectRepository(RoleEntity)
|
||||
private readonly roleRepository: Repository<RoleEntity>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -24,13 +31,30 @@ export class WorkspaceFlatRoleTargetByAgentIdService extends WorkspaceCacheProvi
|
||||
async computeForCache(
|
||||
workspaceId: string,
|
||||
): Promise<FlatRoleTargetByAgentIdMaps> {
|
||||
const roleTargetEntities = await this.roleTargetRepository.find({
|
||||
where: {
|
||||
workspaceId,
|
||||
agentId: Not(IsNull()),
|
||||
},
|
||||
withDeleted: true,
|
||||
});
|
||||
const [roleTargetEntities, applications, roles] = await Promise.all([
|
||||
this.roleTargetRepository.find({
|
||||
where: {
|
||||
workspaceId,
|
||||
agentId: Not(IsNull()),
|
||||
},
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.applicationRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.roleRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
const applicationIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(applications);
|
||||
const roleIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(roles);
|
||||
|
||||
const flatRoleTargetByAgentIdMaps: FlatRoleTargetByAgentIdMaps = {};
|
||||
|
||||
@@ -38,8 +62,11 @@ export class WorkspaceFlatRoleTargetByAgentIdService extends WorkspaceCacheProvi
|
||||
Omit<RoleTargetEntity, 'agentId'> &
|
||||
NonNullableRequired<Pick<RoleTargetEntity, 'agentId'>>
|
||||
>) {
|
||||
const flatRoleTarget =
|
||||
fromRoleTargetsEntityToFlatRoleTarget(roleTargetEntity);
|
||||
const flatRoleTarget = fromRoleTargetEntityToFlatRoleTarget({
|
||||
entity: roleTargetEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
roleIdToUniversalIdentifierMap,
|
||||
});
|
||||
|
||||
flatRoleTargetByAgentIdMaps[roleTargetEntity.agentId] = flatRoleTarget;
|
||||
}
|
||||
|
||||
+28
-5
@@ -1,9 +1,26 @@
|
||||
import { type AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type';
|
||||
import {
|
||||
FlatEntityMapsException,
|
||||
FlatEntityMapsExceptionCode,
|
||||
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
|
||||
import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type';
|
||||
|
||||
export const transformAgentEntityToFlatAgent = ({
|
||||
entity: agentEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
}: FromEntityToFlatEntityArgs<'agent'>): FlatAgent => {
|
||||
const applicationUniversalIdentifier =
|
||||
applicationIdToUniversalIdentifierMap.get(agentEntity.applicationId);
|
||||
|
||||
if (!isDefined(applicationUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Application with id ${agentEntity.applicationId} not found for agent ${agentEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
export const transformAgentEntityToFlatAgent = (
|
||||
agentEntity: AgentEntity,
|
||||
): FlatAgent => {
|
||||
return {
|
||||
createdAt: agentEntity.createdAt.toISOString(),
|
||||
deletedAt: agentEntity.deletedAt?.toISOString() ?? null,
|
||||
@@ -23,5 +40,11 @@ export const transformAgentEntityToFlatAgent = (
|
||||
applicationId: agentEntity.applicationId,
|
||||
modelConfiguration: agentEntity.modelConfiguration,
|
||||
evaluationInputs: agentEntity.evaluationInputs,
|
||||
__universal: {
|
||||
universalIdentifier: agentEntity.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
responseFormat: agentEntity.responseFormat,
|
||||
modelConfiguration: agentEntity.modelConfiguration,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
+7
-1
@@ -1,13 +1,19 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { CommandMenuItemEntity } from 'src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity';
|
||||
import { WorkspaceFlatCommandMenuItemMapCacheService } from 'src/engine/metadata-modules/flat-command-menu-item/services/workspace-flat-command-menu-item-map-cache.service';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([CommandMenuItemEntity]),
|
||||
TypeOrmModule.forFeature([
|
||||
CommandMenuItemEntity,
|
||||
ApplicationEntity,
|
||||
ObjectMetadataEntity,
|
||||
]),
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
],
|
||||
providers: [WorkspaceFlatCommandMenuItemMapCacheService],
|
||||
|
||||
+35
-5
@@ -5,11 +5,14 @@ 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 { CommandMenuItemEntity } from 'src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity';
|
||||
import { type FlatCommandMenuItemMaps } from 'src/engine/metadata-modules/flat-command-menu-item/types/flat-command-menu-item-maps.type';
|
||||
import { fromCommandMenuItemEntityToFlatCommandMenuItem } from 'src/engine/metadata-modules/flat-command-menu-item/utils/from-command-menu-item-entity-to-flat-command-menu-item.util';
|
||||
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.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 { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
|
||||
|
||||
@Injectable()
|
||||
@@ -18,21 +21,48 @@ export class WorkspaceFlatCommandMenuItemMapCacheService extends WorkspaceCacheP
|
||||
constructor(
|
||||
@InjectRepository(CommandMenuItemEntity)
|
||||
private readonly commandMenuItemRepository: Repository<CommandMenuItemEntity>,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
@InjectRepository(ObjectMetadataEntity)
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async computeForCache(workspaceId: string): Promise<FlatCommandMenuItemMaps> {
|
||||
const commandMenuItems = await this.commandMenuItemRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
});
|
||||
const [commandMenuItems, applications, objectMetadatas] = await Promise.all(
|
||||
[
|
||||
this.commandMenuItemRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.applicationRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.objectMetadataRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
],
|
||||
);
|
||||
|
||||
const applicationIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(applications);
|
||||
const objectMetadataIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(objectMetadatas);
|
||||
|
||||
const flatCommandMenuItemMaps = createEmptyFlatEntityMaps();
|
||||
|
||||
for (const commandMenuItemEntity of commandMenuItems) {
|
||||
const flatCommandMenuItem =
|
||||
fromCommandMenuItemEntityToFlatCommandMenuItem(commandMenuItemEntity);
|
||||
fromCommandMenuItemEntityToFlatCommandMenuItem({
|
||||
entity: commandMenuItemEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
objectMetadataIdToUniversalIdentifierMap,
|
||||
});
|
||||
|
||||
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
|
||||
flatEntity: flatCommandMenuItem,
|
||||
|
||||
+46
-5
@@ -1,9 +1,45 @@
|
||||
import { type CommandMenuItemEntity } from 'src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity';
|
||||
import { type FlatCommandMenuItem } from 'src/engine/metadata-modules/flat-command-menu-item/types/flat-command-menu-item.type';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatCommandMenuItem } from 'src/engine/metadata-modules/flat-command-menu-item/types/flat-command-menu-item.type';
|
||||
import {
|
||||
FlatEntityMapsException,
|
||||
FlatEntityMapsExceptionCode,
|
||||
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
|
||||
import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type';
|
||||
|
||||
export const fromCommandMenuItemEntityToFlatCommandMenuItem = ({
|
||||
entity: commandMenuItemEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
objectMetadataIdToUniversalIdentifierMap,
|
||||
}: FromEntityToFlatEntityArgs<'commandMenuItem'>): FlatCommandMenuItem => {
|
||||
const applicationUniversalIdentifier =
|
||||
applicationIdToUniversalIdentifierMap.get(
|
||||
commandMenuItemEntity.applicationId,
|
||||
);
|
||||
|
||||
if (!isDefined(applicationUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Application with id ${commandMenuItemEntity.applicationId} not found for commandMenuItem ${commandMenuItemEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
let availabilityObjectMetadataUniversalIdentifier: string | null = null;
|
||||
|
||||
if (isDefined(commandMenuItemEntity.availabilityObjectMetadataId)) {
|
||||
availabilityObjectMetadataUniversalIdentifier =
|
||||
objectMetadataIdToUniversalIdentifierMap.get(
|
||||
commandMenuItemEntity.availabilityObjectMetadataId,
|
||||
) ?? null;
|
||||
|
||||
if (!isDefined(availabilityObjectMetadataUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`ObjectMetadata with id ${commandMenuItemEntity.availabilityObjectMetadataId} not found for commandMenuItem ${commandMenuItemEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const fromCommandMenuItemEntityToFlatCommandMenuItem = (
|
||||
commandMenuItemEntity: CommandMenuItemEntity,
|
||||
): FlatCommandMenuItem => {
|
||||
return {
|
||||
id: commandMenuItemEntity.id,
|
||||
workflowVersionId: commandMenuItemEntity.workflowVersionId,
|
||||
@@ -18,5 +54,10 @@ export const fromCommandMenuItemEntityToFlatCommandMenuItem = (
|
||||
applicationId: commandMenuItemEntity.applicationId,
|
||||
createdAt: commandMenuItemEntity.createdAt.toISOString(),
|
||||
updatedAt: commandMenuItemEntity.updatedAt.toISOString(),
|
||||
__universal: {
|
||||
universalIdentifier: commandMenuItemEntity.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
availabilityObjectMetadataUniversalIdentifier,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
+7
-10
@@ -1,16 +1,13 @@
|
||||
import { type AllMetadataName } from 'twenty-shared/metadata';
|
||||
import { type Expect } from 'twenty-shared/testing';
|
||||
import {
|
||||
type ExtractPropertiesThatEndsWithId,
|
||||
type ExtractPropertiesThatEndsWithIds,
|
||||
} from 'twenty-shared/types';
|
||||
import { type ExtractPropertiesThatEndsWithId } from 'twenty-shared/types';
|
||||
import { type Relation } from 'typeorm';
|
||||
|
||||
import { type AddSuffixToEntityOneToManyProperties } from 'src/engine/metadata-modules/flat-entity/types/add-suffix-to-entity-one-to-many-properties.type';
|
||||
import { type ExtractEntityManyToOneEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-many-to-one-entity-relation-properties.type';
|
||||
import { type ExtractEntityOneToManyEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-one-to-many-entity-relation-properties.type';
|
||||
import { type FromMetadataEntityToMetadataName } from 'src/engine/metadata-modules/flat-entity/types/from-metadata-entity-to-metadata-name.type';
|
||||
import { type MetadataEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-entity.type';
|
||||
import { type MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity.type';
|
||||
import { type SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
|
||||
|
||||
type ManyToOneRelationValue<
|
||||
@@ -24,12 +21,12 @@ type ManyToOneRelationValue<
|
||||
> extends Relation<infer TTargetEntity extends SyncableEntity>
|
||||
? {
|
||||
metadataName: FromMetadataEntityToMetadataName<TTargetEntity>;
|
||||
flatEntityForeignKeyAggregator: ExtractPropertiesThatEndsWithIds<
|
||||
MetadataFlatEntity<FromMetadataEntityToMetadataName<TTargetEntity>>
|
||||
// Note: In the best of the world should not be nullable, entities should always declare inverside keys
|
||||
> | null;
|
||||
flatEntityForeignKeyAggregator:
|
||||
| keyof AddSuffixToEntityOneToManyProperties<TTargetEntity, 'ids'>
|
||||
| null;
|
||||
// Note: In the best of the world should not be nullable, entities should always declare inverside keys
|
||||
foreignKey: ExtractPropertiesThatEndsWithId<
|
||||
MetadataFlatEntity<TSourceMetadataName>,
|
||||
MetadataEntity<TSourceMetadataName>,
|
||||
'id' | 'workspaceId'
|
||||
>;
|
||||
}
|
||||
|
||||
+4
@@ -1,6 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { WorkspaceFlatFieldMetadataMapCacheService } from 'src/engine/metadata-modules/flat-field-metadata/services/workspace-flat-field-metadata-map-cache.service';
|
||||
@@ -22,6 +23,7 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat
|
||||
import { PageLayoutTabEntity } from 'src/engine/metadata-modules/page-layout-tab/entities/page-layout-tab.entity';
|
||||
import { PageLayoutWidgetEntity } from 'src/engine/metadata-modules/page-layout-widget/entities/page-layout-widget.entity';
|
||||
import { PageLayoutEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout.entity';
|
||||
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 { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
|
||||
@@ -49,6 +51,8 @@ import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache
|
||||
PageLayoutWidgetEntity,
|
||||
RowLevelPermissionPredicateEntity,
|
||||
RowLevelPermissionPredicateGroupEntity,
|
||||
ApplicationEntity,
|
||||
RoleEntity,
|
||||
]),
|
||||
],
|
||||
providers: [
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import { type AllMetadataName } from 'twenty-shared/metadata';
|
||||
|
||||
import { type MetadataManyToOneJoinColumn } from 'src/engine/metadata-modules/flat-entity/types/metadata-many-to-one-join-column.type';
|
||||
import { type RemoveSuffix } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/remove-suffix.type';
|
||||
|
||||
export type AddSuffixToEntityManyToOneProperties<
|
||||
TEntity,
|
||||
TMetadataName extends AllMetadataName,
|
||||
TSuffix extends string,
|
||||
> = {
|
||||
[P in Extract<MetadataManyToOneJoinColumn<TMetadataName>, keyof TEntity> &
|
||||
string as `${RemoveSuffix<P, 'Id'>}${Capitalize<TSuffix>}`]: TEntity[P];
|
||||
};
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import { type ExtractEntityOneToManyEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-one-to-many-entity-relation-properties.type';
|
||||
import { type SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
|
||||
import { type RemoveSuffix } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/remove-suffix.type';
|
||||
|
||||
export type AddSuffixToEntityOneToManyProperties<
|
||||
TEntity,
|
||||
TSuffix extends string,
|
||||
> = {
|
||||
[P in ExtractEntityOneToManyEntityRelationProperties<
|
||||
TEntity,
|
||||
SyncableEntity
|
||||
> &
|
||||
string as `${RemoveSuffix<P, 's'>}${Capitalize<TSuffix>}`]: string[];
|
||||
};
|
||||
+33
-17
@@ -1,26 +1,42 @@
|
||||
import { type CastRecordTypeOrmDatePropertiesToString } from 'src/engine/metadata-modules/flat-entity/types/cast-record-typeorm-date-properties-to-string.type';
|
||||
import { type ExtractEntityOneToManyEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-one-to-many-entity-relation-properties.type';
|
||||
import { type ExtractEntityRelatedEntityProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-related-entity-properties.type';
|
||||
import { type SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
|
||||
import { type RemoveSuffix } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/remove-suffix.type';
|
||||
import { type NonNullableProperties } from 'src/types/non-nullable-properties.type';
|
||||
import { type AllMetadataName } from 'twenty-shared/metadata';
|
||||
|
||||
export type SyncableFlatEntity = NonNullableProperties<
|
||||
Omit<SyncableEntity, 'application' | 'applicationId' | 'workspace'>
|
||||
import { type AddSuffixToEntityOneToManyProperties } from 'src/engine/metadata-modules/flat-entity/types/add-suffix-to-entity-one-to-many-properties.type';
|
||||
import { type CastRecordTypeOrmDatePropertiesToString } from 'src/engine/metadata-modules/flat-entity/types/cast-record-typeorm-date-properties-to-string.type';
|
||||
import { type ExtractEntityRelatedEntityProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-related-entity-properties.type';
|
||||
import { type FromMetadataEntityToMetadataName } from 'src/engine/metadata-modules/flat-entity/types/from-metadata-entity-to-metadata-name.type';
|
||||
import { type SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
|
||||
import { type UniversalFlatEntityExtraProperties } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-from.type';
|
||||
|
||||
export type SyncableFlatEntity = Omit<
|
||||
SyncableEntity,
|
||||
'application' | 'workspace'
|
||||
> & {
|
||||
id: string;
|
||||
applicationId: string | null;
|
||||
};
|
||||
|
||||
export type FlatEntityFrom<TEntity> = Omit<
|
||||
export type FlatEntityFrom<
|
||||
TEntity,
|
||||
TMetadataName extends AllMetadataName | undefined = undefined,
|
||||
> = Omit<
|
||||
TEntity,
|
||||
| ExtractEntityRelatedEntityProperties<TEntity>
|
||||
| keyof CastRecordTypeOrmDatePropertiesToString<TEntity>
|
||||
> &
|
||||
CastRecordTypeOrmDatePropertiesToString<TEntity> & {
|
||||
[P in ExtractEntityOneToManyEntityRelationProperties<
|
||||
TEntity,
|
||||
SyncableEntity
|
||||
> &
|
||||
string as `${RemoveSuffix<P, 's'>}Ids`]: string[];
|
||||
};
|
||||
CastRecordTypeOrmDatePropertiesToString<TEntity> &
|
||||
AddSuffixToEntityOneToManyProperties<TEntity, 'ids'> &
|
||||
(TEntity extends SyncableEntity
|
||||
? {
|
||||
/**
|
||||
* /!\ Under migration the idea is at some point to replace FlatEntity by UniversalFlatEntity /!\
|
||||
* Please avoid any usage or contact me ( prastoin ) before doing so
|
||||
* TODO remove with FlatEntity once it has been fully migrated
|
||||
*/
|
||||
__universal?: UniversalFlatEntityExtraProperties<
|
||||
TEntity,
|
||||
TMetadataName extends undefined
|
||||
? FromMetadataEntityToMetadataName<TEntity>
|
||||
: TMetadataName
|
||||
> & { universalIdentifier: string };
|
||||
}
|
||||
: // eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
{});
|
||||
|
||||
+2
@@ -45,6 +45,8 @@ export const getFlatFieldMetadataMock = <T extends FieldMetadataType>(
|
||||
applicationId: faker.string.uuid(),
|
||||
relationTargetFieldMetadataId: null,
|
||||
relationTargetObjectMetadataId: null,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
__universal: {} as any,
|
||||
...overrides,
|
||||
};
|
||||
};
|
||||
|
||||
+1
@@ -65,5 +65,6 @@ export const getRelationTargetFlatFieldMetadataMock = ({
|
||||
defaultValue: null,
|
||||
options: null,
|
||||
applicationId: faker.string.uuid(),
|
||||
__universal: undefined,
|
||||
};
|
||||
};
|
||||
|
||||
+52
-13
@@ -5,16 +5,19 @@ 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 { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
|
||||
import { FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { fromFieldMetadataEntityToFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-field-metadata-entity-to-flat-field-metadata.util';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
|
||||
import { ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity';
|
||||
import { ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.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';
|
||||
|
||||
@@ -26,6 +29,10 @@ export class WorkspaceFlatFieldMetadataMapCacheService extends WorkspaceCachePro
|
||||
constructor(
|
||||
@InjectRepository(FieldMetadataEntity)
|
||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||
@InjectRepository(ObjectMetadataEntity)
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
@InjectRepository(ViewFieldEntity)
|
||||
private readonly viewFieldRepository: Repository<ViewFieldEntity>,
|
||||
@InjectRepository(ViewFilterEntity)
|
||||
@@ -41,25 +48,43 @@ export class WorkspaceFlatFieldMetadataMapCacheService extends WorkspaceCachePro
|
||||
async computeForCache(
|
||||
workspaceId: string,
|
||||
): Promise<FlatEntityMaps<FlatFieldMetadata>> {
|
||||
const [fieldMetadatas, viewFields, viewFilters, views] = await Promise.all([
|
||||
const [
|
||||
fieldMetadatas,
|
||||
objectMetadatas,
|
||||
applications,
|
||||
viewFields,
|
||||
viewFilters,
|
||||
views,
|
||||
] = await Promise.all([
|
||||
this.fieldMetadataRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.objectMetadataRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.applicationRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.viewFieldRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'fieldMetadataId'],
|
||||
select: ['id', 'universalIdentifier', 'fieldMetadataId'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.viewFilterRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'fieldMetadataId'],
|
||||
select: ['id', 'universalIdentifier', 'fieldMetadataId'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.viewRepository.find({
|
||||
where: { workspaceId },
|
||||
select: [
|
||||
'id',
|
||||
'universalIdentifier',
|
||||
'kanbanAggregateOperationFieldMetadataId',
|
||||
'calendarFieldMetadataId',
|
||||
'mainGroupByFieldMetadataId',
|
||||
@@ -99,20 +124,34 @@ export class WorkspaceFlatFieldMetadataMapCacheService extends WorkspaceCachePro
|
||||
] as const
|
||||
).map(regroupEntitiesByRelatedEntityId);
|
||||
|
||||
const fieldMetadataIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(fieldMetadatas);
|
||||
const objectMetadataIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(objectMetadatas);
|
||||
const applicationIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(applications);
|
||||
|
||||
const flatFieldMetadataMaps = createEmptyFlatEntityMaps();
|
||||
|
||||
for (const fieldMetadataEntity of fieldMetadatas) {
|
||||
const flatFieldMetadata = fromFieldMetadataEntityToFlatFieldMetadata({
|
||||
...fieldMetadataEntity,
|
||||
viewFields: viewFieldsByFieldId.get(fieldMetadataEntity.id) || [],
|
||||
viewFilters: viewFiltersByFieldId.get(fieldMetadataEntity.id) || [],
|
||||
kanbanAggregateOperationViews:
|
||||
kanbanViewsByFieldId.get(fieldMetadataEntity.id) || [],
|
||||
calendarViews: calendarViewsByFieldId.get(fieldMetadataEntity.id) || [],
|
||||
mainGroupByFieldMetadataViews:
|
||||
mainGroupByFieldMetadataViewsByFieldId.get(fieldMetadataEntity.id) ||
|
||||
[],
|
||||
} as FieldMetadataEntity);
|
||||
entity: {
|
||||
...fieldMetadataEntity,
|
||||
viewFields: viewFieldsByFieldId.get(fieldMetadataEntity.id) || [],
|
||||
viewFilters: viewFiltersByFieldId.get(fieldMetadataEntity.id) || [],
|
||||
kanbanAggregateOperationViews:
|
||||
kanbanViewsByFieldId.get(fieldMetadataEntity.id) || [],
|
||||
calendarViews:
|
||||
calendarViewsByFieldId.get(fieldMetadataEntity.id) || [],
|
||||
mainGroupByFieldMetadataViews:
|
||||
mainGroupByFieldMetadataViewsByFieldId.get(
|
||||
fieldMetadataEntity.id,
|
||||
) || [],
|
||||
},
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
objectMetadataIdToUniversalIdentifierMap,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
});
|
||||
|
||||
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
|
||||
flatEntity: flatFieldMetadata,
|
||||
|
||||
+2
-1
@@ -8,5 +8,6 @@ export type FlatFieldMetadata<T extends FieldMetadataType = FieldMetadataType> =
|
||||
Omit<
|
||||
FieldMetadataEntity<T>,
|
||||
'relationTargetFieldMetadata' | 'relationTargetObjectMetadata'
|
||||
>
|
||||
>,
|
||||
'fieldMetadata'
|
||||
>;
|
||||
|
||||
+114
-9
@@ -1,20 +1,97 @@
|
||||
import { type FieldMetadataType } from 'twenty-shared/types';
|
||||
import { removePropertiesFromRecord } from 'twenty-shared/utils';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined, removePropertiesFromRecord } from 'twenty-shared/utils';
|
||||
|
||||
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { isFieldMetadataSettingsOfType } from 'src/engine/metadata-modules/field-metadata/utils/is-field-metadata-settings-of-type.util';
|
||||
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 FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type';
|
||||
|
||||
export const fromFieldMetadataEntityToFlatFieldMetadata = <
|
||||
T extends FieldMetadataType,
|
||||
>(
|
||||
fieldMetadataEntity: FieldMetadataEntity<T>,
|
||||
// This is intended to be abstract
|
||||
): FlatFieldMetadata => {
|
||||
export const fromFieldMetadataEntityToFlatFieldMetadata = ({
|
||||
entity: fieldMetadataEntity,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
objectMetadataIdToUniversalIdentifierMap,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
}: FromEntityToFlatEntityArgs<'fieldMetadata'>): FlatFieldMetadata => {
|
||||
const fieldMetadataWithoutRelations = removePropertiesFromRecord(
|
||||
fieldMetadataEntity,
|
||||
getMetadataEntityRelationProperties('fieldMetadata'),
|
||||
);
|
||||
const applicationUniversalIdentifier =
|
||||
applicationIdToUniversalIdentifierMap.get(
|
||||
fieldMetadataEntity.applicationId,
|
||||
);
|
||||
|
||||
if (!isDefined(applicationUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Application with id ${fieldMetadataEntity.applicationId} not found when building flat field metadata for field ${fieldMetadataEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const objectMetadataUniversalIdentifier =
|
||||
objectMetadataIdToUniversalIdentifierMap.get(
|
||||
fieldMetadataEntity.objectMetadataId,
|
||||
);
|
||||
|
||||
if (!isDefined(objectMetadataUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Object metadata with id ${fieldMetadataEntity.objectMetadataId} not found when building flat field metadata for field ${fieldMetadataEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
let relationTargetObjectMetadataUniversalIdentifier: string | null = null;
|
||||
|
||||
if (isDefined(fieldMetadataEntity.relationTargetObjectMetadataId)) {
|
||||
relationTargetObjectMetadataUniversalIdentifier =
|
||||
objectMetadataIdToUniversalIdentifierMap.get(
|
||||
fieldMetadataEntity.relationTargetObjectMetadataId,
|
||||
) ?? null;
|
||||
|
||||
if (!isDefined(relationTargetObjectMetadataUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Relation target object metadata with id ${fieldMetadataEntity.relationTargetObjectMetadataId} not found when building flat field metadata for field ${fieldMetadataEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let relationTargetFieldMetadataUniversalIdentifier: string | null = null;
|
||||
|
||||
if (isDefined(fieldMetadataEntity.relationTargetFieldMetadataId)) {
|
||||
relationTargetFieldMetadataUniversalIdentifier =
|
||||
fieldMetadataIdToUniversalIdentifierMap.get(
|
||||
fieldMetadataEntity.relationTargetFieldMetadataId,
|
||||
) ?? null;
|
||||
|
||||
if (!isDefined(relationTargetFieldMetadataUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Relation target field metadata with id ${fieldMetadataEntity.relationTargetFieldMetadataId} not found when building flat field metadata for field ${fieldMetadataEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const settings = fieldMetadataEntity.settings;
|
||||
const isRelationSettings =
|
||||
isFieldMetadataSettingsOfType(settings, FieldMetadataType.RELATION) ||
|
||||
isFieldMetadataSettingsOfType(settings, FieldMetadataType.MORPH_RELATION);
|
||||
|
||||
const settingsWithUniversalIdentifiers = isRelationSettings
|
||||
? {
|
||||
...settings,
|
||||
...(isDefined(settings.junctionTargetFieldId) && {
|
||||
junctionTargetFieldUniversalIdentifier:
|
||||
fieldMetadataIdToUniversalIdentifierMap.get(
|
||||
settings.junctionTargetFieldId,
|
||||
),
|
||||
}),
|
||||
}
|
||||
: settings;
|
||||
|
||||
return {
|
||||
...fieldMetadataWithoutRelations,
|
||||
@@ -29,5 +106,33 @@ export const fromFieldMetadataEntityToFlatFieldMetadata = <
|
||||
[],
|
||||
viewFieldIds: fieldMetadataEntity.viewFields.map(({ id }) => id),
|
||||
viewFilterIds: fieldMetadataEntity.viewFilters.map(({ id }) => id),
|
||||
__universal: {
|
||||
universalIdentifier: fieldMetadataWithoutRelations.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
objectMetadataUniversalIdentifier,
|
||||
relationTargetObjectMetadataUniversalIdentifier,
|
||||
relationTargetFieldMetadataUniversalIdentifier,
|
||||
viewFieldUniversalIdentifiers: fieldMetadataEntity.viewFields.map(
|
||||
({ universalIdentifier }) => universalIdentifier,
|
||||
),
|
||||
viewFilterUniversalIdentifiers: fieldMetadataEntity.viewFilters.map(
|
||||
({ universalIdentifier }) => universalIdentifier,
|
||||
),
|
||||
kanbanAggregateOperationViewUniversalIdentifiers:
|
||||
fieldMetadataEntity.kanbanAggregateOperationViews.map(
|
||||
({ universalIdentifier }) => universalIdentifier,
|
||||
),
|
||||
calendarViewUniversalIdentifiers: fieldMetadataEntity.calendarViews.map(
|
||||
({ universalIdentifier }) => universalIdentifier,
|
||||
),
|
||||
mainGroupByFieldMetadataViewUniversalIdentifiers:
|
||||
fieldMetadataEntity.mainGroupByFieldMetadataViews?.map(
|
||||
({ universalIdentifier }) => universalIdentifier,
|
||||
) ?? [],
|
||||
settings: settingsWithUniversalIdentifiers,
|
||||
defaultValue: fieldMetadataWithoutRelations.defaultValue,
|
||||
options: fieldMetadataWithoutRelations.options,
|
||||
standardOverrides: fieldMetadataWithoutRelations.standardOverrides,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
+2
-1
@@ -1,13 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
|
||||
import { WorkspaceFlatFrontComponentMapCacheService } from 'src/engine/metadata-modules/flat-front-component/services/workspace-flat-front-component-map-cache.service';
|
||||
import { FrontComponentEntity } from 'src/engine/metadata-modules/front-component/entities/front-component.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([FrontComponentEntity]),
|
||||
TypeOrmModule.forFeature([ApplicationEntity, FrontComponentEntity]),
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
],
|
||||
providers: [WorkspaceFlatFrontComponentMapCacheService],
|
||||
|
||||
+21
-5
@@ -5,11 +5,13 @@ 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 { type FlatFrontComponentMaps } from 'src/engine/metadata-modules/flat-front-component/types/flat-front-component-maps.type';
|
||||
import { fromFrontComponentEntityToFlatFrontComponent } from 'src/engine/metadata-modules/flat-front-component/utils/from-front-component-entity-to-flat-front-component.util';
|
||||
import { FrontComponentEntity } from 'src/engine/metadata-modules/front-component/entities/front-component.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 { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
|
||||
|
||||
@Injectable()
|
||||
@@ -18,20 +20,34 @@ export class WorkspaceFlatFrontComponentMapCacheService extends WorkspaceCachePr
|
||||
constructor(
|
||||
@InjectRepository(FrontComponentEntity)
|
||||
private readonly frontComponentRepository: Repository<FrontComponentEntity>,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async computeForCache(workspaceId: string): Promise<FlatFrontComponentMaps> {
|
||||
const frontComponents = await this.frontComponentRepository.find({
|
||||
where: { workspaceId },
|
||||
});
|
||||
const [frontComponents, applications] = await Promise.all([
|
||||
this.frontComponentRepository.find({
|
||||
where: { workspaceId },
|
||||
}),
|
||||
this.applicationRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
const applicationIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(applications);
|
||||
|
||||
const flatFrontComponentMaps = createEmptyFlatEntityMaps();
|
||||
|
||||
for (const frontComponentEntity of frontComponents) {
|
||||
const flatFrontComponent =
|
||||
fromFrontComponentEntityToFlatFrontComponent(frontComponentEntity);
|
||||
const flatFrontComponent = fromFrontComponentEntityToFlatFrontComponent({
|
||||
entity: frontComponentEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
});
|
||||
|
||||
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
|
||||
flatEntity: flatFrontComponent,
|
||||
|
||||
+28
-5
@@ -1,9 +1,28 @@
|
||||
import { type FlatFrontComponent } from 'src/engine/metadata-modules/flat-front-component/types/flat-front-component.type';
|
||||
import { type FrontComponentEntity } from 'src/engine/metadata-modules/front-component/entities/front-component.entity';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
FlatEntityMapsException,
|
||||
FlatEntityMapsExceptionCode,
|
||||
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
|
||||
import { type FlatFrontComponent } from 'src/engine/metadata-modules/flat-front-component/types/flat-front-component.type';
|
||||
import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type';
|
||||
|
||||
export const fromFrontComponentEntityToFlatFrontComponent = ({
|
||||
entity: frontComponentEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
}: FromEntityToFlatEntityArgs<'frontComponent'>): FlatFrontComponent => {
|
||||
const applicationUniversalIdentifier =
|
||||
applicationIdToUniversalIdentifierMap.get(
|
||||
frontComponentEntity.applicationId,
|
||||
);
|
||||
|
||||
if (!isDefined(applicationUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Application with id ${frontComponentEntity.applicationId} not found for frontComponent ${frontComponentEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
export const fromFrontComponentEntityToFlatFrontComponent = (
|
||||
frontComponentEntity: FrontComponentEntity,
|
||||
): FlatFrontComponent => {
|
||||
return {
|
||||
id: frontComponentEntity.id,
|
||||
name: frontComponentEntity.name,
|
||||
@@ -12,5 +31,9 @@ export const fromFrontComponentEntityToFlatFrontComponent = (
|
||||
applicationId: frontComponentEntity.applicationId,
|
||||
createdAt: frontComponentEntity.createdAt.toISOString(),
|
||||
updatedAt: frontComponentEntity.updatedAt.toISOString(),
|
||||
__universal: {
|
||||
universalIdentifier: frontComponentEntity.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
+40
-13
@@ -5,12 +5,15 @@ 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 { FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
|
||||
import { fromIndexMetadataEntityToFlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/utils/from-index-metadata-entity-to-flat-index-metadata.util';
|
||||
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.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 { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
|
||||
|
||||
@Injectable()
|
||||
@@ -21,6 +24,10 @@ export class WorkspaceFlatIndexMapCacheService extends WorkspaceCacheProvider<
|
||||
constructor(
|
||||
@InjectRepository(IndexMetadataEntity)
|
||||
private readonly indexMetadataRepository: Repository<IndexMetadataEntity>,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
@InjectRepository(ObjectMetadataEntity)
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -28,23 +35,43 @@ export class WorkspaceFlatIndexMapCacheService extends WorkspaceCacheProvider<
|
||||
async computeForCache(
|
||||
workspaceId: string,
|
||||
): Promise<FlatEntityMaps<FlatIndexMetadata>> {
|
||||
const indexes = await this.indexMetadataRepository.find({
|
||||
where: {
|
||||
workspaceId,
|
||||
},
|
||||
withDeleted: true,
|
||||
relationLoadStrategy: 'join',
|
||||
select: {
|
||||
// Note: We need all IndexFieldMetadataEntity in order to build a FlatIndex
|
||||
indexFieldMetadatas: true,
|
||||
},
|
||||
relations: ['indexFieldMetadatas'],
|
||||
});
|
||||
const [indexes, applications, objectMetadatas] = await Promise.all([
|
||||
this.indexMetadataRepository.find({
|
||||
where: {
|
||||
workspaceId,
|
||||
},
|
||||
withDeleted: true,
|
||||
relationLoadStrategy: 'join',
|
||||
select: {
|
||||
// Note: We need all IndexFieldMetadataEntity in order to build a FlatIndex
|
||||
indexFieldMetadatas: true,
|
||||
},
|
||||
relations: ['indexFieldMetadatas'],
|
||||
}),
|
||||
this.applicationRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
}),
|
||||
this.objectMetadataRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
const applicationIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(applications);
|
||||
const objectMetadataIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(objectMetadatas);
|
||||
|
||||
const flatIndexMaps = createEmptyFlatEntityMaps();
|
||||
|
||||
for (const indexEntity of indexes) {
|
||||
const flatIndex = fromIndexMetadataEntityToFlatIndexMetadata(indexEntity);
|
||||
const flatIndex = fromIndexMetadataEntityToFlatIndexMetadata({
|
||||
entity: indexEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
objectMetadataIdToUniversalIdentifierMap,
|
||||
});
|
||||
|
||||
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
|
||||
flatEntity: flatIndex,
|
||||
|
||||
+40
-5
@@ -1,12 +1,42 @@
|
||||
import { removePropertiesFromRecord } from 'twenty-shared/utils';
|
||||
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 FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
|
||||
import { type IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
|
||||
import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type';
|
||||
|
||||
export const fromIndexMetadataEntityToFlatIndexMetadata = ({
|
||||
entity: indexMetadataEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
objectMetadataIdToUniversalIdentifierMap,
|
||||
}: FromEntityToFlatEntityArgs<'index'>): FlatIndexMetadata => {
|
||||
const applicationUniversalIdentifier =
|
||||
applicationIdToUniversalIdentifierMap.get(
|
||||
indexMetadataEntity.applicationId,
|
||||
);
|
||||
|
||||
if (!isDefined(applicationUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Application with id ${indexMetadataEntity.applicationId} not found for index ${indexMetadataEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const objectMetadataUniversalIdentifier =
|
||||
objectMetadataIdToUniversalIdentifierMap.get(
|
||||
indexMetadataEntity.objectMetadataId,
|
||||
);
|
||||
|
||||
if (!isDefined(objectMetadataUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`ObjectMetadata with id ${indexMetadataEntity.objectMetadataId} not found for index ${indexMetadataEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
export const fromIndexMetadataEntityToFlatIndexMetadata = (
|
||||
indexMetadataEntity: IndexMetadataEntity,
|
||||
): FlatIndexMetadata => {
|
||||
const indexMetadataEntityWithoutRelations = removePropertiesFromRecord(
|
||||
indexMetadataEntity,
|
||||
getMetadataEntityRelationProperties('index'),
|
||||
@@ -28,5 +58,10 @@ export const fromIndexMetadataEntityToFlatIndexMetadata = (
|
||||
updatedAt: indexFieldMetadata.updatedAt.toISOString(),
|
||||
}),
|
||||
),
|
||||
__universal: {
|
||||
universalIdentifier: indexMetadataEntity.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
objectMetadataUniversalIdentifier,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
+9
-1
@@ -1,13 +1,21 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
|
||||
import { WorkspaceFlatNavigationMenuItemMapCacheService } from 'src/engine/metadata-modules/flat-navigation-menu-item/services/workspace-flat-navigation-menu-item-map-cache.service';
|
||||
import { NavigationMenuItemEntity } from 'src/engine/metadata-modules/navigation-menu-item/entities/navigation-menu-item.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([NavigationMenuItemEntity]),
|
||||
TypeOrmModule.forFeature([
|
||||
NavigationMenuItemEntity,
|
||||
ApplicationEntity,
|
||||
ObjectMetadataEntity,
|
||||
ViewEntity,
|
||||
]),
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
],
|
||||
providers: [WorkspaceFlatNavigationMenuItemMapCacheService],
|
||||
|
||||
+48
-7
@@ -5,12 +5,16 @@ 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 { type FlatNavigationMenuItemMaps } from 'src/engine/metadata-modules/flat-navigation-menu-item/types/flat-navigation-menu-item-maps.type';
|
||||
import { addFlatNavigationMenuItemToMapsAndUpdateIndex } from 'src/engine/metadata-modules/flat-navigation-menu-item/utils/add-flat-navigation-menu-item-to-maps-and-update-index.util';
|
||||
import { fromNavigationMenuItemEntityToFlatNavigationMenuItem } from 'src/engine/metadata-modules/flat-navigation-menu-item/utils/from-navigation-menu-item-entity-to-flat-navigation-menu-item.util';
|
||||
import { NavigationMenuItemEntity } from 'src/engine/metadata-modules/navigation-menu-item/entities/navigation-menu-item.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.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';
|
||||
|
||||
@Injectable()
|
||||
@WorkspaceCache('flatNavigationMenuItemMaps')
|
||||
@@ -18,6 +22,12 @@ export class WorkspaceFlatNavigationMenuItemMapCacheService extends WorkspaceCac
|
||||
constructor(
|
||||
@InjectRepository(NavigationMenuItemEntity)
|
||||
private readonly navigationMenuItemRepository: Repository<NavigationMenuItemEntity>,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
@InjectRepository(ObjectMetadataEntity)
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
@InjectRepository(ViewEntity)
|
||||
private readonly viewRepository: Repository<ViewEntity>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -25,10 +35,37 @@ export class WorkspaceFlatNavigationMenuItemMapCacheService extends WorkspaceCac
|
||||
async computeForCache(
|
||||
workspaceId: string,
|
||||
): Promise<FlatNavigationMenuItemMaps> {
|
||||
const navigationMenuItems = await this.navigationMenuItemRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
});
|
||||
const [navigationMenuItems, applications, objectMetadatas, views] =
|
||||
await Promise.all([
|
||||
this.navigationMenuItemRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.applicationRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.objectMetadataRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.viewRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
const applicationIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(applications);
|
||||
const objectMetadataIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(objectMetadatas);
|
||||
const navigationMenuItemIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(navigationMenuItems);
|
||||
const viewIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(views);
|
||||
|
||||
const flatNavigationMenuItemMaps = {
|
||||
...createEmptyFlatEntityMaps(),
|
||||
@@ -37,9 +74,13 @@ export class WorkspaceFlatNavigationMenuItemMapCacheService extends WorkspaceCac
|
||||
|
||||
for (const navigationMenuItemEntity of navigationMenuItems) {
|
||||
const flatNavigationMenuItem =
|
||||
fromNavigationMenuItemEntityToFlatNavigationMenuItem(
|
||||
navigationMenuItemEntity,
|
||||
);
|
||||
fromNavigationMenuItemEntityToFlatNavigationMenuItem({
|
||||
entity: navigationMenuItemEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
objectMetadataIdToUniversalIdentifierMap,
|
||||
navigationMenuItemIdToUniversalIdentifierMap,
|
||||
viewIdToUniversalIdentifierMap,
|
||||
});
|
||||
|
||||
addFlatNavigationMenuItemToMapsAndUpdateIndex({
|
||||
flatNavigationMenuItem,
|
||||
|
||||
+81
-5
@@ -1,9 +1,78 @@
|
||||
import { type FlatNavigationMenuItem } from 'src/engine/metadata-modules/flat-navigation-menu-item/types/flat-navigation-menu-item.type';
|
||||
import { type NavigationMenuItemEntity } from 'src/engine/metadata-modules/navigation-menu-item/entities/navigation-menu-item.entity';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
FlatEntityMapsException,
|
||||
FlatEntityMapsExceptionCode,
|
||||
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
|
||||
import { type FlatNavigationMenuItem } from 'src/engine/metadata-modules/flat-navigation-menu-item/types/flat-navigation-menu-item.type';
|
||||
import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type';
|
||||
|
||||
export const fromNavigationMenuItemEntityToFlatNavigationMenuItem = ({
|
||||
entity: navigationMenuItemEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
objectMetadataIdToUniversalIdentifierMap,
|
||||
navigationMenuItemIdToUniversalIdentifierMap,
|
||||
viewIdToUniversalIdentifierMap,
|
||||
}: FromEntityToFlatEntityArgs<'navigationMenuItem'>): FlatNavigationMenuItem => {
|
||||
const applicationUniversalIdentifier =
|
||||
applicationIdToUniversalIdentifierMap.get(
|
||||
navigationMenuItemEntity.applicationId,
|
||||
);
|
||||
|
||||
if (!isDefined(applicationUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Application with id ${navigationMenuItemEntity.applicationId} not found for navigationMenuItem ${navigationMenuItemEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
let targetObjectMetadataUniversalIdentifier: string | null = null;
|
||||
|
||||
if (isDefined(navigationMenuItemEntity.targetObjectMetadataId)) {
|
||||
targetObjectMetadataUniversalIdentifier =
|
||||
objectMetadataIdToUniversalIdentifierMap.get(
|
||||
navigationMenuItemEntity.targetObjectMetadataId,
|
||||
) ?? null;
|
||||
|
||||
if (!isDefined(targetObjectMetadataUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`ObjectMetadata with id ${navigationMenuItemEntity.targetObjectMetadataId} not found for navigationMenuItem ${navigationMenuItemEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let folderUniversalIdentifier: string | null = null;
|
||||
|
||||
if (isDefined(navigationMenuItemEntity.folderId)) {
|
||||
folderUniversalIdentifier =
|
||||
navigationMenuItemIdToUniversalIdentifierMap.get(
|
||||
navigationMenuItemEntity.folderId,
|
||||
) ?? null;
|
||||
|
||||
if (!isDefined(folderUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`NavigationMenuItem (folder) with id ${navigationMenuItemEntity.folderId} not found for navigationMenuItem ${navigationMenuItemEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let viewUniversalIdentifier: string | null = null;
|
||||
|
||||
if (isDefined(navigationMenuItemEntity.viewId)) {
|
||||
viewUniversalIdentifier =
|
||||
viewIdToUniversalIdentifierMap.get(navigationMenuItemEntity.viewId) ??
|
||||
null;
|
||||
|
||||
if (!isDefined(viewUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`View with id ${navigationMenuItemEntity.viewId} not found for navigationMenuItem ${navigationMenuItemEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const fromNavigationMenuItemEntityToFlatNavigationMenuItem = (
|
||||
navigationMenuItemEntity: NavigationMenuItemEntity,
|
||||
): FlatNavigationMenuItem => {
|
||||
return {
|
||||
id: navigationMenuItemEntity.id,
|
||||
userWorkspaceId: navigationMenuItemEntity.userWorkspaceId,
|
||||
@@ -18,5 +87,12 @@ export const fromNavigationMenuItemEntityToFlatNavigationMenuItem = (
|
||||
applicationId: navigationMenuItemEntity.applicationId,
|
||||
createdAt: navigationMenuItemEntity.createdAt.toISOString(),
|
||||
updatedAt: navigationMenuItemEntity.updatedAt.toISOString(),
|
||||
__universal: {
|
||||
universalIdentifier: navigationMenuItemEntity.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
targetObjectMetadataUniversalIdentifier,
|
||||
folderUniversalIdentifier,
|
||||
viewUniversalIdentifier,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
+45
-26
@@ -5,6 +5,7 @@ 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 { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
|
||||
import { FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
@@ -14,6 +15,7 @@ import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.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';
|
||||
|
||||
@@ -25,6 +27,8 @@ export class WorkspaceFlatObjectMetadataMapCacheService extends WorkspaceCachePr
|
||||
constructor(
|
||||
@InjectRepository(ObjectMetadataEntity)
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
@InjectRepository(FieldMetadataEntity)
|
||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||
@InjectRepository(IndexMetadataEntity)
|
||||
@@ -38,27 +42,33 @@ export class WorkspaceFlatObjectMetadataMapCacheService extends WorkspaceCachePr
|
||||
async computeForCache(
|
||||
workspaceId: string,
|
||||
): Promise<FlatEntityMaps<FlatObjectMetadata>> {
|
||||
const [objectMetadatas, fields, indexMetadatas, views] = await Promise.all([
|
||||
this.objectMetadataRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.fieldMetadataRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'objectMetadataId'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.indexMetadataRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'objectMetadataId'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.viewRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'objectMetadataId'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
]);
|
||||
const [objectMetadatas, applications, fields, indexMetadatas, views] =
|
||||
await Promise.all([
|
||||
this.objectMetadataRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.applicationRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.fieldMetadataRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier', 'objectMetadataId'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.indexMetadataRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier', 'objectMetadataId'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.viewRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier', 'objectMetadataId'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
const [fieldsByObjectId, indexesByObjectId, viewsByObjectId] = (
|
||||
[
|
||||
@@ -77,15 +87,24 @@ export class WorkspaceFlatObjectMetadataMapCacheService extends WorkspaceCachePr
|
||||
] as const
|
||||
).map(regroupEntitiesByRelatedEntityId);
|
||||
|
||||
const applicationIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(applications);
|
||||
const fieldMetadataIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(fields);
|
||||
|
||||
const flatObjectMetadataMaps = createEmptyFlatEntityMaps();
|
||||
|
||||
for (const objectMetadataEntity of objectMetadatas) {
|
||||
const flatObjectMetadata = fromObjectMetadataEntityToFlatObjectMetadata({
|
||||
...objectMetadataEntity,
|
||||
fields: fieldsByObjectId.get(objectMetadataEntity.id) || [],
|
||||
indexMetadatas: indexesByObjectId.get(objectMetadataEntity.id) || [],
|
||||
views: viewsByObjectId.get(objectMetadataEntity.id) || [],
|
||||
} as ObjectMetadataEntity);
|
||||
entity: {
|
||||
...objectMetadataEntity,
|
||||
fields: fieldsByObjectId.get(objectMetadataEntity.id) || [],
|
||||
indexMetadatas: indexesByObjectId.get(objectMetadataEntity.id) || [],
|
||||
views: viewsByObjectId.get(objectMetadataEntity.id) || [],
|
||||
},
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
});
|
||||
|
||||
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
|
||||
flatEntity: flatObjectMetadata,
|
||||
|
||||
+8
-1
@@ -1,6 +1,13 @@
|
||||
import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type';
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
|
||||
export type FlatObjectMetadata = FlatEntityFrom<
|
||||
type BaseFlatObjectMetadata = FlatEntityFrom<
|
||||
Omit<ObjectMetadataEntity, 'targetRelationFields' | 'dataSourceId'>
|
||||
>;
|
||||
export type FlatObjectMetadata = Omit<BaseFlatObjectMetadata, '__universal'> & {
|
||||
__universal?: BaseFlatObjectMetadata['__universal'] & {
|
||||
// TODO remove once https://github.com/twentyhq/core-team-issues/issues/2172 has been resolved
|
||||
labelIdentifierFieldMetadataUniversalIdentifier: string | null;
|
||||
imageIdentifierFieldMetadataUniversalIdentifier: string | null;
|
||||
};
|
||||
};
|
||||
|
||||
+82
-10
@@ -1,27 +1,99 @@
|
||||
import { removePropertiesFromRecord } from 'twenty-shared/utils';
|
||||
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 FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type';
|
||||
|
||||
export const fromObjectMetadataEntityToFlatObjectMetadata = (
|
||||
objectMetadataEntity: ObjectMetadataEntity,
|
||||
): FlatObjectMetadata => {
|
||||
type FromObjectMetadataEntityToFlatObjectMetadataArgs =
|
||||
FromEntityToFlatEntityArgs<'objectMetadata'> & {
|
||||
fieldMetadataIdToUniversalIdentifierMap: Map<string, string>;
|
||||
};
|
||||
|
||||
export const fromObjectMetadataEntityToFlatObjectMetadata = ({
|
||||
entity: objectMetadataEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
}: FromObjectMetadataEntityToFlatObjectMetadataArgs): FlatObjectMetadata => {
|
||||
const objectMetadataEntityWithoutRelations = removePropertiesFromRecord(
|
||||
objectMetadataEntity,
|
||||
getMetadataEntityRelationProperties('objectMetadata'),
|
||||
);
|
||||
|
||||
const applicationUniversalIdentifier =
|
||||
applicationIdToUniversalIdentifierMap.get(
|
||||
objectMetadataEntity.applicationId,
|
||||
);
|
||||
|
||||
if (!isDefined(applicationUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Application with id ${objectMetadataEntity.applicationId} not found when building flat object metadata for object ${objectMetadataEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
let labelIdentifierFieldMetadataUniversalIdentifier: string | null = null;
|
||||
|
||||
if (isDefined(objectMetadataEntity.labelIdentifierFieldMetadataId)) {
|
||||
labelIdentifierFieldMetadataUniversalIdentifier =
|
||||
fieldMetadataIdToUniversalIdentifierMap.get(
|
||||
objectMetadataEntity.labelIdentifierFieldMetadataId,
|
||||
) ?? null;
|
||||
|
||||
if (!isDefined(labelIdentifierFieldMetadataUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Label identifier field metadata with id ${objectMetadataEntity.labelIdentifierFieldMetadataId} not found when building flat object metadata for object ${objectMetadataEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let imageIdentifierFieldMetadataUniversalIdentifier: string | null = null;
|
||||
|
||||
if (isDefined(objectMetadataEntity.imageIdentifierFieldMetadataId)) {
|
||||
imageIdentifierFieldMetadataUniversalIdentifier =
|
||||
fieldMetadataIdToUniversalIdentifierMap.get(
|
||||
objectMetadataEntity.imageIdentifierFieldMetadataId,
|
||||
) ?? null;
|
||||
|
||||
if (!isDefined(imageIdentifierFieldMetadataUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Image identifier field metadata with id ${objectMetadataEntity.imageIdentifierFieldMetadataId} not found when building flat object metadata for object ${objectMetadataEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...objectMetadataEntityWithoutRelations,
|
||||
universalIdentifier:
|
||||
objectMetadataEntityWithoutRelations.universalIdentifier,
|
||||
createdAt: objectMetadataEntity.createdAt.toISOString(),
|
||||
updatedAt: objectMetadataEntity.updatedAt.toISOString(),
|
||||
viewIds: objectMetadataEntity.views.map((viewEntity) => viewEntity.id),
|
||||
indexMetadataIds: objectMetadataEntity.indexMetadatas.map(
|
||||
(indexEntity) => indexEntity.id,
|
||||
),
|
||||
fieldIds: objectMetadataEntity.fields.map((fieldEntity) => fieldEntity.id),
|
||||
viewIds: objectMetadataEntity.views.map(({ id }) => id),
|
||||
indexMetadataIds: objectMetadataEntity.indexMetadatas.map(({ id }) => id),
|
||||
fieldIds: objectMetadataEntity.fields.map(({ id }) => id),
|
||||
__universal: {
|
||||
universalIdentifier:
|
||||
objectMetadataEntityWithoutRelations.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
labelIdentifierFieldMetadataUniversalIdentifier,
|
||||
imageIdentifierFieldMetadataUniversalIdentifier,
|
||||
fieldUniversalIdentifiers: objectMetadataEntity.fields.map(
|
||||
({ universalIdentifier }) => universalIdentifier,
|
||||
),
|
||||
indexMetadataUniversalIdentifiers:
|
||||
objectMetadataEntity.indexMetadatas.map(
|
||||
({ universalIdentifier }) => universalIdentifier,
|
||||
),
|
||||
viewUniversalIdentifiers: objectMetadataEntity.views.map(
|
||||
({ universalIdentifier }) => universalIdentifier,
|
||||
),
|
||||
standardOverrides: objectMetadataEntityWithoutRelations.standardOverrides,
|
||||
duplicateCriteria: objectMetadataEntityWithoutRelations.duplicateCriteria,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
+8
-1
@@ -1,14 +1,21 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
|
||||
import { WorkspaceFlatPageLayoutTabMapCacheService } from 'src/engine/metadata-modules/flat-page-layout-tab/services/workspace-flat-page-layout-tab-map-cache.service';
|
||||
import { PageLayoutTabEntity } from 'src/engine/metadata-modules/page-layout-tab/entities/page-layout-tab.entity';
|
||||
import { PageLayoutWidgetEntity } from 'src/engine/metadata-modules/page-layout-widget/entities/page-layout-widget.entity';
|
||||
import { PageLayoutEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([PageLayoutTabEntity, PageLayoutWidgetEntity]),
|
||||
TypeOrmModule.forFeature([
|
||||
PageLayoutTabEntity,
|
||||
PageLayoutWidgetEntity,
|
||||
ApplicationEntity,
|
||||
PageLayoutEntity,
|
||||
]),
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
],
|
||||
providers: [WorkspaceFlatPageLayoutTabMapCacheService],
|
||||
|
||||
+43
-15
@@ -5,12 +5,15 @@ 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 { type FlatPageLayoutTabMaps } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab-maps.type';
|
||||
import { transformPageLayoutTabEntityToFlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/utils/transform-page-layout-tab-entity-to-flat-page-layout-tab.util';
|
||||
import { PageLayoutTabEntity } from 'src/engine/metadata-modules/page-layout-tab/entities/page-layout-tab.entity';
|
||||
import { PageLayoutWidgetEntity } from 'src/engine/metadata-modules/page-layout-widget/entities/page-layout-widget.entity';
|
||||
import { PageLayoutEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout.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';
|
||||
|
||||
@@ -22,21 +25,37 @@ export class WorkspaceFlatPageLayoutTabMapCacheService extends WorkspaceCachePro
|
||||
private readonly pageLayoutTabRepository: Repository<PageLayoutTabEntity>,
|
||||
@InjectRepository(PageLayoutWidgetEntity)
|
||||
private readonly pageLayoutWidgetRepository: Repository<PageLayoutWidgetEntity>,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
@InjectRepository(PageLayoutEntity)
|
||||
private readonly pageLayoutRepository: Repository<PageLayoutEntity>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async computeForCache(workspaceId: string): Promise<FlatPageLayoutTabMaps> {
|
||||
const [pageLayoutTabs, pageLayoutWidgets] = await Promise.all([
|
||||
await this.pageLayoutTabRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
}),
|
||||
await this.pageLayoutWidgetRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
}),
|
||||
]);
|
||||
const [pageLayoutTabs, pageLayoutWidgets, applications, pageLayouts] =
|
||||
await Promise.all([
|
||||
this.pageLayoutTabRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.pageLayoutWidgetRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier', 'pageLayoutTabId'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.applicationRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.pageLayoutRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
const [pageLayoutWidgetsByPageLayoutTabId] = (
|
||||
[
|
||||
@@ -47,16 +66,25 @@ export class WorkspaceFlatPageLayoutTabMapCacheService extends WorkspaceCachePro
|
||||
] as const
|
||||
).map(regroupEntitiesByRelatedEntityId);
|
||||
|
||||
const applicationIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(applications);
|
||||
const pageLayoutIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(pageLayouts);
|
||||
|
||||
const flatPageLayoutTabMaps = createEmptyFlatEntityMaps();
|
||||
|
||||
for (const pageLayoutTabEntity of pageLayoutTabs) {
|
||||
const flatPageLayoutTab = transformPageLayoutTabEntityToFlatPageLayoutTab(
|
||||
{
|
||||
...pageLayoutTabEntity,
|
||||
widgets:
|
||||
pageLayoutWidgetsByPageLayoutTabId.get(pageLayoutTabEntity.id) ||
|
||||
[],
|
||||
} as PageLayoutTabEntity,
|
||||
entity: {
|
||||
...pageLayoutTabEntity,
|
||||
widgets:
|
||||
pageLayoutWidgetsByPageLayoutTabId.get(pageLayoutTabEntity.id) ||
|
||||
[],
|
||||
},
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
pageLayoutIdToUniversalIdentifierMap,
|
||||
},
|
||||
);
|
||||
|
||||
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
|
||||
|
||||
+43
-5
@@ -1,9 +1,39 @@
|
||||
import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type';
|
||||
import { type PageLayoutTabEntity } from 'src/engine/metadata-modules/page-layout-tab/entities/page-layout-tab.entity';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
FlatEntityMapsException,
|
||||
FlatEntityMapsExceptionCode,
|
||||
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
|
||||
import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type';
|
||||
import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type';
|
||||
|
||||
export const transformPageLayoutTabEntityToFlatPageLayoutTab = ({
|
||||
entity: pageLayoutTabEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
pageLayoutIdToUniversalIdentifierMap,
|
||||
}: FromEntityToFlatEntityArgs<'pageLayoutTab'>): FlatPageLayoutTab => {
|
||||
const applicationUniversalIdentifier =
|
||||
applicationIdToUniversalIdentifierMap.get(
|
||||
pageLayoutTabEntity.applicationId,
|
||||
);
|
||||
|
||||
if (!isDefined(applicationUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Application with id ${pageLayoutTabEntity.applicationId} not found for pageLayoutTab ${pageLayoutTabEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const pageLayoutUniversalIdentifier =
|
||||
pageLayoutIdToUniversalIdentifierMap.get(pageLayoutTabEntity.pageLayoutId);
|
||||
|
||||
if (!isDefined(pageLayoutUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`PageLayout with id ${pageLayoutTabEntity.pageLayoutId} not found for pageLayoutTab ${pageLayoutTabEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
export const transformPageLayoutTabEntityToFlatPageLayoutTab = (
|
||||
pageLayoutTabEntity: PageLayoutTabEntity,
|
||||
): FlatPageLayoutTab => {
|
||||
return {
|
||||
createdAt: pageLayoutTabEntity.createdAt.toISOString(),
|
||||
deletedAt: pageLayoutTabEntity.deletedAt?.toISOString() ?? null,
|
||||
@@ -16,5 +46,13 @@ export const transformPageLayoutTabEntityToFlatPageLayoutTab = (
|
||||
universalIdentifier: pageLayoutTabEntity.universalIdentifier,
|
||||
applicationId: pageLayoutTabEntity.applicationId,
|
||||
widgetIds: pageLayoutTabEntity.widgets.map((widget) => widget.id),
|
||||
__universal: {
|
||||
universalIdentifier: pageLayoutTabEntity.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
pageLayoutUniversalIdentifier,
|
||||
widgetUniversalIdentifiers: pageLayoutTabEntity.widgets.map(
|
||||
(widget) => widget.universalIdentifier,
|
||||
),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
+13
-1
@@ -1,12 +1,24 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { FlatPageLayoutWidgetTypeValidatorService } from 'src/engine/metadata-modules/flat-page-layout-widget/services/flat-page-layout-widget-type-validator.service';
|
||||
import { WorkspaceFlatPageLayoutWidgetMapCacheService } from 'src/engine/metadata-modules/flat-page-layout-widget/services/workspace-flat-page-layout-widget-map-cache.service';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { PageLayoutTabEntity } from 'src/engine/metadata-modules/page-layout-tab/entities/page-layout-tab.entity';
|
||||
import { PageLayoutWidgetEntity } from 'src/engine/metadata-modules/page-layout-widget/entities/page-layout-widget.entity';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([PageLayoutWidgetEntity])],
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([
|
||||
PageLayoutWidgetEntity,
|
||||
ApplicationEntity,
|
||||
PageLayoutTabEntity,
|
||||
ObjectMetadataEntity,
|
||||
FieldMetadataEntity,
|
||||
]),
|
||||
],
|
||||
providers: [
|
||||
WorkspaceFlatPageLayoutWidgetMapCacheService,
|
||||
FlatPageLayoutWidgetTypeValidatorService,
|
||||
|
||||
+60
-9
@@ -5,11 +5,16 @@ 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 { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
|
||||
import { FlatPageLayoutWidgetMaps } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-maps.type';
|
||||
import { fromPageLayoutWidgetEntityToFlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-entity-to-flat-page-layout-widget.util';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { PageLayoutTabEntity } from 'src/engine/metadata-modules/page-layout-tab/entities/page-layout-tab.entity';
|
||||
import { PageLayoutWidgetEntity } from 'src/engine/metadata-modules/page-layout-widget/entities/page-layout-widget.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 { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
|
||||
|
||||
@Injectable()
|
||||
@@ -18,6 +23,14 @@ export class WorkspaceFlatPageLayoutWidgetMapCacheService extends WorkspaceCache
|
||||
constructor(
|
||||
@InjectRepository(PageLayoutWidgetEntity)
|
||||
private readonly pageLayoutWidgetRepository: Repository<PageLayoutWidgetEntity>,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
@InjectRepository(PageLayoutTabEntity)
|
||||
private readonly pageLayoutTabRepository: Repository<PageLayoutTabEntity>,
|
||||
@InjectRepository(ObjectMetadataEntity)
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
@InjectRepository(FieldMetadataEntity)
|
||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -25,21 +38,59 @@ export class WorkspaceFlatPageLayoutWidgetMapCacheService extends WorkspaceCache
|
||||
async computeForCache(
|
||||
workspaceId: string,
|
||||
): Promise<FlatPageLayoutWidgetMaps> {
|
||||
const existingPageLayoutWidgets =
|
||||
await this.pageLayoutWidgetRepository.find({
|
||||
where: {
|
||||
workspaceId,
|
||||
},
|
||||
const [
|
||||
existingPageLayoutWidgets,
|
||||
applications,
|
||||
pageLayoutTabs,
|
||||
objectMetadatas,
|
||||
fieldMetadatas,
|
||||
] = await Promise.all([
|
||||
this.pageLayoutWidgetRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
});
|
||||
}),
|
||||
this.applicationRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.pageLayoutTabRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.objectMetadataRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.fieldMetadataRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
const applicationIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(applications);
|
||||
const pageLayoutTabIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(pageLayoutTabs);
|
||||
const objectMetadataIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(objectMetadatas);
|
||||
const fieldMetadataIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(fieldMetadatas);
|
||||
|
||||
const flatPageLayoutWidgetMaps = createEmptyFlatEntityMaps();
|
||||
|
||||
for (const pageLayoutWidgetEntity of existingPageLayoutWidgets) {
|
||||
const flatPageLayoutWidget =
|
||||
fromPageLayoutWidgetEntityToFlatPageLayoutWidget(
|
||||
pageLayoutWidgetEntity,
|
||||
);
|
||||
fromPageLayoutWidgetEntityToFlatPageLayoutWidget({
|
||||
entity: pageLayoutWidgetEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
pageLayoutTabIdToUniversalIdentifierMap,
|
||||
objectMetadataIdToUniversalIdentifierMap,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
});
|
||||
|
||||
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
|
||||
flatEntity: flatPageLayoutWidget,
|
||||
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
|
||||
import { type PageLayoutWidgetEntity } from 'src/engine/metadata-modules/page-layout-widget/entities/page-layout-widget.entity';
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
|
||||
type PageLayoutWidgetConfiguration = PageLayoutWidgetEntity['configuration'];
|
||||
|
||||
type UniversalPageLayoutWidgetConfiguration = NonNullable<
|
||||
FlatPageLayoutWidget['__universal']
|
||||
>['configuration'];
|
||||
|
||||
// Field metadata IDs in widget configurations don't need to reference existing entities
|
||||
const getFieldMetadataUniversalIdentifier = ({
|
||||
fieldMetadataId,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
}: {
|
||||
fieldMetadataId: string;
|
||||
fieldMetadataIdToUniversalIdentifierMap: Map<string, string>;
|
||||
}): string | null => {
|
||||
return fieldMetadataIdToUniversalIdentifierMap.get(fieldMetadataId) ?? null;
|
||||
};
|
||||
|
||||
export const fromPageLayoutWidgetConfigurationToUniversalConfiguration = ({
|
||||
configuration,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
}: {
|
||||
configuration: PageLayoutWidgetConfiguration;
|
||||
fieldMetadataIdToUniversalIdentifierMap: Map<string, string>;
|
||||
}): UniversalPageLayoutWidgetConfiguration => {
|
||||
switch (configuration.configurationType) {
|
||||
case WidgetConfigurationType.AGGREGATE_CHART: {
|
||||
const { aggregateFieldMetadataId, ratioAggregateConfig, ...rest } =
|
||||
configuration;
|
||||
|
||||
return {
|
||||
...rest,
|
||||
aggregateFieldMetadataUniversalIdentifier:
|
||||
getFieldMetadataUniversalIdentifier({
|
||||
fieldMetadataId: aggregateFieldMetadataId,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
}),
|
||||
...(isDefined(ratioAggregateConfig) && {
|
||||
ratioAggregateConfig: {
|
||||
optionValue: ratioAggregateConfig.optionValue,
|
||||
fieldMetadataUniversalIdentifier:
|
||||
getFieldMetadataUniversalIdentifier({
|
||||
fieldMetadataId: ratioAggregateConfig.fieldMetadataId,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
case WidgetConfigurationType.GAUGE_CHART: {
|
||||
const { aggregateFieldMetadataId, ...rest } = configuration;
|
||||
|
||||
return {
|
||||
...rest,
|
||||
aggregateFieldMetadataUniversalIdentifier:
|
||||
getFieldMetadataUniversalIdentifier({
|
||||
fieldMetadataId: aggregateFieldMetadataId,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
case WidgetConfigurationType.PIE_CHART: {
|
||||
const { aggregateFieldMetadataId, groupByFieldMetadataId, ...rest } =
|
||||
configuration;
|
||||
|
||||
return {
|
||||
...rest,
|
||||
aggregateFieldMetadataUniversalIdentifier:
|
||||
getFieldMetadataUniversalIdentifier({
|
||||
fieldMetadataId: aggregateFieldMetadataId,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
}),
|
||||
groupByFieldMetadataUniversalIdentifier:
|
||||
getFieldMetadataUniversalIdentifier({
|
||||
fieldMetadataId: groupByFieldMetadataId,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
case WidgetConfigurationType.BAR_CHART: {
|
||||
const {
|
||||
aggregateFieldMetadataId,
|
||||
primaryAxisGroupByFieldMetadataId,
|
||||
secondaryAxisGroupByFieldMetadataId,
|
||||
...rest
|
||||
} = configuration;
|
||||
|
||||
return {
|
||||
...rest,
|
||||
aggregateFieldMetadataUniversalIdentifier:
|
||||
getFieldMetadataUniversalIdentifier({
|
||||
fieldMetadataId: aggregateFieldMetadataId,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
}),
|
||||
primaryAxisGroupByFieldMetadataUniversalIdentifier:
|
||||
getFieldMetadataUniversalIdentifier({
|
||||
fieldMetadataId: primaryAxisGroupByFieldMetadataId,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
}),
|
||||
...(isDefined(secondaryAxisGroupByFieldMetadataId) && {
|
||||
secondaryAxisGroupByFieldMetadataUniversalIdentifier:
|
||||
getFieldMetadataUniversalIdentifier({
|
||||
fieldMetadataId: secondaryAxisGroupByFieldMetadataId,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
}),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
case WidgetConfigurationType.LINE_CHART: {
|
||||
const {
|
||||
aggregateFieldMetadataId,
|
||||
primaryAxisGroupByFieldMetadataId,
|
||||
secondaryAxisGroupByFieldMetadataId,
|
||||
...rest
|
||||
} = configuration;
|
||||
|
||||
return {
|
||||
...rest,
|
||||
aggregateFieldMetadataUniversalIdentifier:
|
||||
getFieldMetadataUniversalIdentifier({
|
||||
fieldMetadataId: aggregateFieldMetadataId,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
}),
|
||||
primaryAxisGroupByFieldMetadataUniversalIdentifier:
|
||||
getFieldMetadataUniversalIdentifier({
|
||||
fieldMetadataId: primaryAxisGroupByFieldMetadataId,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
}),
|
||||
...(isDefined(secondaryAxisGroupByFieldMetadataId) && {
|
||||
secondaryAxisGroupByFieldMetadataUniversalIdentifier:
|
||||
getFieldMetadataUniversalIdentifier({
|
||||
fieldMetadataId: secondaryAxisGroupByFieldMetadataId,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
}),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
case WidgetConfigurationType.VIEW:
|
||||
case WidgetConfigurationType.FIELD:
|
||||
case WidgetConfigurationType.FIELDS:
|
||||
case WidgetConfigurationType.TIMELINE:
|
||||
case WidgetConfigurationType.TASKS:
|
||||
case WidgetConfigurationType.NOTES:
|
||||
case WidgetConfigurationType.FILES:
|
||||
case WidgetConfigurationType.EMAILS:
|
||||
case WidgetConfigurationType.CALENDAR:
|
||||
case WidgetConfigurationType.FIELD_RICH_TEXT:
|
||||
case WidgetConfigurationType.WORKFLOW:
|
||||
case WidgetConfigurationType.WORKFLOW_VERSION:
|
||||
case WidgetConfigurationType.WORKFLOW_RUN:
|
||||
case WidgetConfigurationType.FRONT_COMPONENT:
|
||||
case WidgetConfigurationType.IFRAME:
|
||||
case WidgetConfigurationType.STANDALONE_RICH_TEXT:
|
||||
return configuration;
|
||||
}
|
||||
};
|
||||
+73
-5
@@ -1,17 +1,77 @@
|
||||
import { removePropertiesFromRecord } from 'twenty-shared/utils';
|
||||
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 FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
|
||||
import { type PageLayoutWidgetEntity } from 'src/engine/metadata-modules/page-layout-widget/entities/page-layout-widget.entity';
|
||||
import { fromPageLayoutWidgetConfigurationToUniversalConfiguration } from 'src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-configuration-to-universal-configuration.util';
|
||||
import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type';
|
||||
|
||||
export const fromPageLayoutWidgetEntityToFlatPageLayoutWidget = (
|
||||
pageLayoutWidgetEntity: PageLayoutWidgetEntity,
|
||||
): FlatPageLayoutWidget => {
|
||||
type FromPageLayoutWidgetEntityToFlatPageLayoutWidgetArgs =
|
||||
FromEntityToFlatEntityArgs<'pageLayoutWidget'> & {
|
||||
fieldMetadataIdToUniversalIdentifierMap: Map<string, string>;
|
||||
};
|
||||
|
||||
export const fromPageLayoutWidgetEntityToFlatPageLayoutWidget = ({
|
||||
entity: pageLayoutWidgetEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
pageLayoutTabIdToUniversalIdentifierMap,
|
||||
objectMetadataIdToUniversalIdentifierMap,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
}: FromPageLayoutWidgetEntityToFlatPageLayoutWidgetArgs): FlatPageLayoutWidget => {
|
||||
const pageLayoutWidgetEntityWithoutRelations = removePropertiesFromRecord(
|
||||
pageLayoutWidgetEntity,
|
||||
getMetadataEntityRelationProperties('pageLayoutWidget'),
|
||||
);
|
||||
|
||||
const applicationUniversalIdentifier =
|
||||
applicationIdToUniversalIdentifierMap.get(
|
||||
pageLayoutWidgetEntity.applicationId,
|
||||
);
|
||||
|
||||
if (!isDefined(applicationUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Application with id ${pageLayoutWidgetEntity.applicationId} not found for pageLayoutWidget ${pageLayoutWidgetEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const pageLayoutTabUniversalIdentifier =
|
||||
pageLayoutTabIdToUniversalIdentifierMap.get(
|
||||
pageLayoutWidgetEntity.pageLayoutTabId,
|
||||
);
|
||||
|
||||
if (!isDefined(pageLayoutTabUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`PageLayoutTab with id ${pageLayoutWidgetEntity.pageLayoutTabId} not found for pageLayoutWidget ${pageLayoutWidgetEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
let objectMetadataUniversalIdentifier: string | null = null;
|
||||
|
||||
if (isDefined(pageLayoutWidgetEntity.objectMetadataId)) {
|
||||
objectMetadataUniversalIdentifier =
|
||||
objectMetadataIdToUniversalIdentifierMap.get(
|
||||
pageLayoutWidgetEntity.objectMetadataId,
|
||||
) ?? null;
|
||||
|
||||
if (!isDefined(objectMetadataUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`ObjectMetadata with id ${pageLayoutWidgetEntity.objectMetadataId} not found for pageLayoutWidget ${pageLayoutWidgetEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const configurationWithUniversalIdentifiers =
|
||||
fromPageLayoutWidgetConfigurationToUniversalConfiguration({
|
||||
configuration: pageLayoutWidgetEntityWithoutRelations.configuration,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
});
|
||||
|
||||
return {
|
||||
...pageLayoutWidgetEntityWithoutRelations,
|
||||
createdAt: pageLayoutWidgetEntity.createdAt.toISOString(),
|
||||
@@ -20,5 +80,13 @@ export const fromPageLayoutWidgetEntityToFlatPageLayoutWidget = (
|
||||
universalIdentifier:
|
||||
pageLayoutWidgetEntityWithoutRelations.universalIdentifier,
|
||||
applicationId: pageLayoutWidgetEntityWithoutRelations.applicationId,
|
||||
__universal: {
|
||||
universalIdentifier: pageLayoutWidgetEntity.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
pageLayoutTabUniversalIdentifier,
|
||||
objectMetadataUniversalIdentifier,
|
||||
gridPosition: pageLayoutWidgetEntityWithoutRelations.gridPosition,
|
||||
configuration: configurationWithUniversalIdentifiers,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
+10
-1
@@ -1,12 +1,21 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { WorkspaceFlatPageLayoutMapCacheService } from 'src/engine/metadata-modules/flat-page-layout/services/workspace-flat-page-layout-map-cache.service';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { PageLayoutTabEntity } from 'src/engine/metadata-modules/page-layout-tab/entities/page-layout-tab.entity';
|
||||
import { PageLayoutEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout.entity';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([PageLayoutEntity, PageLayoutTabEntity])],
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([
|
||||
PageLayoutEntity,
|
||||
PageLayoutTabEntity,
|
||||
ApplicationEntity,
|
||||
ObjectMetadataEntity,
|
||||
]),
|
||||
],
|
||||
providers: [WorkspaceFlatPageLayoutMapCacheService],
|
||||
exports: [WorkspaceFlatPageLayoutMapCacheService],
|
||||
})
|
||||
|
||||
+41
-13
@@ -5,12 +5,15 @@ 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 { type FlatPageLayoutMaps } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout-maps.type';
|
||||
import { transformPageLayoutEntityToFlatPageLayout } from 'src/engine/metadata-modules/flat-page-layout/utils/transform-page-layout-entity-to-flat-page-layout.util';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { PageLayoutTabEntity } from 'src/engine/metadata-modules/page-layout-tab/entities/page-layout-tab.entity';
|
||||
import { PageLayoutEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout.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';
|
||||
|
||||
@@ -22,21 +25,37 @@ export class WorkspaceFlatPageLayoutMapCacheService extends WorkspaceCacheProvid
|
||||
private readonly pageLayoutRepository: Repository<PageLayoutEntity>,
|
||||
@InjectRepository(PageLayoutTabEntity)
|
||||
private readonly pageLayoutTabRepository: Repository<PageLayoutTabEntity>,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
@InjectRepository(ObjectMetadataEntity)
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async computeForCache(workspaceId: string): Promise<FlatPageLayoutMaps> {
|
||||
const [pageLayouts, pageLayoutTabs] = await Promise.all([
|
||||
await this.pageLayoutRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
}),
|
||||
await this.pageLayoutTabRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
}),
|
||||
]);
|
||||
const [pageLayouts, pageLayoutTabs, applications, objectMetadatas] =
|
||||
await Promise.all([
|
||||
this.pageLayoutRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.pageLayoutTabRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier', 'pageLayoutId'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.applicationRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.objectMetadataRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
const [pageLayoutTabsByPageLayoutId] = (
|
||||
[
|
||||
@@ -47,13 +66,22 @@ export class WorkspaceFlatPageLayoutMapCacheService extends WorkspaceCacheProvid
|
||||
] as const
|
||||
).map(regroupEntitiesByRelatedEntityId);
|
||||
|
||||
const applicationIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(applications);
|
||||
const objectMetadataIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(objectMetadatas);
|
||||
|
||||
const flatPageLayoutMaps = createEmptyFlatEntityMaps();
|
||||
|
||||
for (const pageLayoutEntity of pageLayouts) {
|
||||
const flatPageLayout = transformPageLayoutEntityToFlatPageLayout({
|
||||
...pageLayoutEntity,
|
||||
tabs: pageLayoutTabsByPageLayoutId.get(pageLayoutEntity.id) || [],
|
||||
} as PageLayoutEntity);
|
||||
entity: {
|
||||
...pageLayoutEntity,
|
||||
tabs: pageLayoutTabsByPageLayoutId.get(pageLayoutEntity.id) || [],
|
||||
},
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
objectMetadataIdToUniversalIdentifierMap,
|
||||
});
|
||||
|
||||
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
|
||||
flatEntity: flatPageLayout,
|
||||
|
||||
+47
-5
@@ -1,9 +1,43 @@
|
||||
import { type FlatPageLayout } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout.type';
|
||||
import { type PageLayoutEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout.entity';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
FlatEntityMapsException,
|
||||
FlatEntityMapsExceptionCode,
|
||||
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
|
||||
import { type FlatPageLayout } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout.type';
|
||||
import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type';
|
||||
|
||||
export const transformPageLayoutEntityToFlatPageLayout = ({
|
||||
entity: pageLayoutEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
objectMetadataIdToUniversalIdentifierMap,
|
||||
}: FromEntityToFlatEntityArgs<'pageLayout'>): FlatPageLayout => {
|
||||
const applicationUniversalIdentifier =
|
||||
applicationIdToUniversalIdentifierMap.get(pageLayoutEntity.applicationId);
|
||||
|
||||
if (!isDefined(applicationUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Application with id ${pageLayoutEntity.applicationId} not found for pageLayout ${pageLayoutEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
let objectMetadataUniversalIdentifier: string | null = null;
|
||||
|
||||
if (isDefined(pageLayoutEntity.objectMetadataId)) {
|
||||
objectMetadataUniversalIdentifier =
|
||||
objectMetadataIdToUniversalIdentifierMap.get(
|
||||
pageLayoutEntity.objectMetadataId,
|
||||
) ?? null;
|
||||
|
||||
if (!isDefined(objectMetadataUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`ObjectMetadata with id ${pageLayoutEntity.objectMetadataId} not found for pageLayout ${pageLayoutEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const transformPageLayoutEntityToFlatPageLayout = (
|
||||
pageLayoutEntity: PageLayoutEntity,
|
||||
): FlatPageLayout => {
|
||||
return {
|
||||
createdAt: pageLayoutEntity.createdAt.toISOString(),
|
||||
deletedAt: pageLayoutEntity.deletedAt?.toISOString() ?? null,
|
||||
@@ -16,5 +50,13 @@ export const transformPageLayoutEntityToFlatPageLayout = (
|
||||
universalIdentifier: pageLayoutEntity.universalIdentifier,
|
||||
applicationId: pageLayoutEntity.applicationId,
|
||||
tabIds: pageLayoutEntity.tabs.map((tab) => tab.id),
|
||||
__universal: {
|
||||
universalIdentifier: pageLayoutEntity.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
objectMetadataUniversalIdentifier,
|
||||
tabUniversalIdentifiers: pageLayoutEntity.tabs.map(
|
||||
(tab) => tab.universalIdentifier,
|
||||
),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
+34
-9
@@ -5,11 +5,14 @@ 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 { type FlatRoleTargetMaps } from 'src/engine/metadata-modules/flat-role-target/types/flat-role-target-maps.type';
|
||||
import { fromRoleTargetsEntityToFlatRoleTarget } from 'src/engine/metadata-modules/flat-role-target/utils/from-role-target-entity-to-flat-role-target.util';
|
||||
import { fromRoleTargetEntityToFlatRoleTarget } from 'src/engine/metadata-modules/flat-role-target/utils/from-role-target-entity-to-flat-role-target.util';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.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 { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
|
||||
|
||||
@Injectable()
|
||||
@@ -18,23 +21,45 @@ export class WorkspaceFlatRoleTargetMapCacheService extends WorkspaceCacheProvid
|
||||
constructor(
|
||||
@InjectRepository(RoleTargetEntity)
|
||||
private readonly roleTargetRepository: Repository<RoleTargetEntity>,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
@InjectRepository(RoleEntity)
|
||||
private readonly roleRepository: Repository<RoleEntity>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async computeForCache(workspaceId: string): Promise<FlatRoleTargetMaps> {
|
||||
const roleTargets = await this.roleTargetRepository.find({
|
||||
where: {
|
||||
workspaceId,
|
||||
},
|
||||
withDeleted: true,
|
||||
});
|
||||
const [roleTargets, applications, roles] = await Promise.all([
|
||||
this.roleTargetRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.applicationRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.roleRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
const applicationIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(applications);
|
||||
const roleIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(roles);
|
||||
|
||||
const flatRoleTargetMaps = createEmptyFlatEntityMaps();
|
||||
|
||||
for (const roleTargetEntity of roleTargets) {
|
||||
const flatRoleTarget =
|
||||
fromRoleTargetsEntityToFlatRoleTarget(roleTargetEntity);
|
||||
const flatRoleTarget = fromRoleTargetEntityToFlatRoleTarget({
|
||||
entity: roleTargetEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
roleIdToUniversalIdentifierMap,
|
||||
});
|
||||
|
||||
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
|
||||
flatEntity: flatRoleTarget,
|
||||
|
||||
+49
-15
@@ -1,19 +1,53 @@
|
||||
import { type FlatRoleTarget } from 'src/engine/metadata-modules/flat-role-target/types/flat-role-target.type';
|
||||
import { type RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
FlatEntityMapsException,
|
||||
FlatEntityMapsExceptionCode,
|
||||
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
|
||||
import { type FlatRoleTarget } from 'src/engine/metadata-modules/flat-role-target/types/flat-role-target.type';
|
||||
import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type';
|
||||
|
||||
export const fromRoleTargetEntityToFlatRoleTarget = ({
|
||||
entity: roleTargetEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
roleIdToUniversalIdentifierMap,
|
||||
}: FromEntityToFlatEntityArgs<'roleTarget'>): FlatRoleTarget => {
|
||||
const applicationUniversalIdentifier =
|
||||
applicationIdToUniversalIdentifierMap.get(roleTargetEntity.applicationId);
|
||||
|
||||
if (!isDefined(applicationUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Application with id ${roleTargetEntity.applicationId} not found when building flat role target for role target ${roleTargetEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const roleUniversalIdentifier = roleIdToUniversalIdentifierMap.get(
|
||||
roleTargetEntity.roleId,
|
||||
);
|
||||
|
||||
if (!isDefined(roleUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Role with id ${roleTargetEntity.roleId} not found when building flat role target for role target ${roleTargetEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
export const fromRoleTargetsEntityToFlatRoleTarget = (
|
||||
roleTarget: RoleTargetEntity,
|
||||
): FlatRoleTarget => {
|
||||
return {
|
||||
id: roleTarget.id,
|
||||
workspaceId: roleTarget.workspaceId,
|
||||
roleId: roleTarget.roleId,
|
||||
userWorkspaceId: roleTarget.userWorkspaceId,
|
||||
agentId: roleTarget.agentId,
|
||||
apiKeyId: roleTarget.apiKeyId,
|
||||
applicationId: roleTarget.applicationId,
|
||||
universalIdentifier: roleTarget.universalIdentifier,
|
||||
createdAt: roleTarget.createdAt.toISOString(),
|
||||
updatedAt: roleTarget.updatedAt.toISOString(),
|
||||
id: roleTargetEntity.id,
|
||||
workspaceId: roleTargetEntity.workspaceId,
|
||||
roleId: roleTargetEntity.roleId,
|
||||
userWorkspaceId: roleTargetEntity.userWorkspaceId,
|
||||
agentId: roleTargetEntity.agentId,
|
||||
apiKeyId: roleTargetEntity.apiKeyId,
|
||||
applicationId: roleTargetEntity.applicationId,
|
||||
universalIdentifier: roleTargetEntity.universalIdentifier,
|
||||
createdAt: roleTargetEntity.createdAt.toISOString(),
|
||||
updatedAt: roleTargetEntity.updatedAt.toISOString(),
|
||||
__universal: {
|
||||
universalIdentifier: roleTargetEntity.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
roleUniversalIdentifier,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
+78
-30
@@ -1,36 +1,84 @@
|
||||
import { type FlatRole } from 'src/engine/metadata-modules/flat-role/types/flat-role.type';
|
||||
import { type RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
FlatEntityMapsException,
|
||||
FlatEntityMapsExceptionCode,
|
||||
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
|
||||
import { type MetadataEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-entity.type';
|
||||
import { type FlatRole } from 'src/engine/metadata-modules/flat-role/types/flat-role.type';
|
||||
import { type EntityManyToOneIdByUniversalIdentifierMaps } from 'src/engine/workspace-cache/types/entity-many-to-one-id-by-universal-identifier-maps.type';
|
||||
import { type EntityWithRegroupedOneToManyRelations } from 'src/engine/workspace-cache/types/entity-with-regrouped-one-to-many-relations.type';
|
||||
import { type RegroupedEntity } from 'src/engine/workspace-cache/utils/regroup-entities-by-related-entity-id';
|
||||
|
||||
type FromRoleEntityToFlatRoleArgs = {
|
||||
entity: Omit<
|
||||
EntityWithRegroupedOneToManyRelations<MetadataEntity<'role'>>,
|
||||
'objectPermissions' | 'permissionFlags' | 'fieldPermissions'
|
||||
> & {
|
||||
objectPermissions: RegroupedEntity[];
|
||||
permissionFlags: RegroupedEntity[];
|
||||
fieldPermissions: RegroupedEntity[];
|
||||
};
|
||||
} & EntityManyToOneIdByUniversalIdentifierMaps<'role'>;
|
||||
|
||||
export const fromRoleEntityToFlatRole = ({
|
||||
entity: roleEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
}: FromRoleEntityToFlatRoleArgs): FlatRole => {
|
||||
const applicationUniversalIdentifier =
|
||||
applicationIdToUniversalIdentifierMap.get(roleEntity.applicationId);
|
||||
|
||||
if (!isDefined(applicationUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Application with id ${roleEntity.applicationId} not found for role ${roleEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
export const fromRoleEntityToFlatRole = (role: RoleEntity): FlatRole => {
|
||||
return {
|
||||
id: role.id,
|
||||
standardId: role.standardId,
|
||||
label: role.label,
|
||||
description: role.description,
|
||||
icon: role.icon,
|
||||
isEditable: role.isEditable,
|
||||
canUpdateAllSettings: role.canUpdateAllSettings,
|
||||
canAccessAllTools: role.canAccessAllTools,
|
||||
canReadAllObjectRecords: role.canReadAllObjectRecords,
|
||||
canUpdateAllObjectRecords: role.canUpdateAllObjectRecords,
|
||||
canSoftDeleteAllObjectRecords: role.canSoftDeleteAllObjectRecords,
|
||||
canDestroyAllObjectRecords: role.canDestroyAllObjectRecords,
|
||||
canBeAssignedToUsers: role.canBeAssignedToUsers,
|
||||
canBeAssignedToAgents: role.canBeAssignedToAgents,
|
||||
canBeAssignedToApiKeys: role.canBeAssignedToApiKeys,
|
||||
workspaceId: role.workspaceId,
|
||||
createdAt: role.createdAt.toISOString(),
|
||||
updatedAt: role.updatedAt.toISOString(),
|
||||
universalIdentifier: role.universalIdentifier,
|
||||
applicationId: role.applicationId,
|
||||
roleTargetIds: role.roleTargets.map((rt) => rt.id),
|
||||
objectPermissionIds: role.objectPermissions.map((op) => op.id),
|
||||
permissionFlagIds: role.permissionFlags.map((pf) => pf.id),
|
||||
fieldPermissionIds: role.fieldPermissions.map((fp) => fp.id),
|
||||
rowLevelPermissionPredicateIds: role.rowLevelPermissionPredicates.map(
|
||||
(rp) => rp.id,
|
||||
id: roleEntity.id,
|
||||
standardId: roleEntity.standardId,
|
||||
label: roleEntity.label,
|
||||
description: roleEntity.description,
|
||||
icon: roleEntity.icon,
|
||||
isEditable: roleEntity.isEditable,
|
||||
canUpdateAllSettings: roleEntity.canUpdateAllSettings,
|
||||
canAccessAllTools: roleEntity.canAccessAllTools,
|
||||
canReadAllObjectRecords: roleEntity.canReadAllObjectRecords,
|
||||
canUpdateAllObjectRecords: roleEntity.canUpdateAllObjectRecords,
|
||||
canSoftDeleteAllObjectRecords: roleEntity.canSoftDeleteAllObjectRecords,
|
||||
canDestroyAllObjectRecords: roleEntity.canDestroyAllObjectRecords,
|
||||
canBeAssignedToUsers: roleEntity.canBeAssignedToUsers,
|
||||
canBeAssignedToAgents: roleEntity.canBeAssignedToAgents,
|
||||
canBeAssignedToApiKeys: roleEntity.canBeAssignedToApiKeys,
|
||||
workspaceId: roleEntity.workspaceId,
|
||||
createdAt: roleEntity.createdAt.toISOString(),
|
||||
updatedAt: roleEntity.updatedAt.toISOString(),
|
||||
universalIdentifier: roleEntity.universalIdentifier,
|
||||
applicationId: roleEntity.applicationId,
|
||||
roleTargetIds: roleEntity.roleTargets.map(({ id }) => id),
|
||||
objectPermissionIds: roleEntity.objectPermissions.map(({ id }) => id),
|
||||
permissionFlagIds: roleEntity.permissionFlags.map(({ id }) => id),
|
||||
fieldPermissionIds: roleEntity.fieldPermissions.map(({ id }) => id),
|
||||
rowLevelPermissionPredicateIds: roleEntity.rowLevelPermissionPredicates.map(
|
||||
({ id }) => id,
|
||||
),
|
||||
rowLevelPermissionPredicateGroupIds:
|
||||
role.rowLevelPermissionPredicateGroups.map((rp) => rp.id),
|
||||
roleEntity.rowLevelPermissionPredicateGroups.map(({ id }) => id),
|
||||
__universal: {
|
||||
universalIdentifier: roleEntity.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
roleTargetUniversalIdentifiers: roleEntity.roleTargets.map(
|
||||
({ universalIdentifier }) => universalIdentifier,
|
||||
),
|
||||
rowLevelPermissionPredicateUniversalIdentifiers:
|
||||
roleEntity.rowLevelPermissionPredicates.map(
|
||||
({ universalIdentifier }) => universalIdentifier,
|
||||
),
|
||||
rowLevelPermissionPredicateGroupUniversalIdentifiers:
|
||||
roleEntity.rowLevelPermissionPredicateGroups.map(
|
||||
({ universalIdentifier }) => universalIdentifier,
|
||||
),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
+91
-7
@@ -7,11 +7,17 @@ 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 { fromRowLevelPermissionPredicateGroupEntityToFlatRowLevelPermissionPredicateGroup } from 'src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-row-level-permission-predicate-group-entity-to-flat-row-level-permission-predicate-group.util';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
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 { type FlatRowLevelPermissionPredicateGroupMaps } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-group-maps.type';
|
||||
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()
|
||||
@@ -20,6 +26,14 @@ export class WorkspaceFlatRowLevelPermissionPredicateGroupMapCacheService extend
|
||||
constructor(
|
||||
@InjectRepository(RowLevelPermissionPredicateGroupEntity)
|
||||
private readonly rowLevelPermissionPredicateGroupRepository: Repository<RowLevelPermissionPredicateGroupEntity>,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
@InjectRepository(ObjectMetadataEntity)
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
@InjectRepository(RoleEntity)
|
||||
private readonly roleRepository: Repository<RoleEntity>,
|
||||
@InjectRepository(RowLevelPermissionPredicateEntity)
|
||||
private readonly rowLevelPermissionPredicateRepository: Repository<RowLevelPermissionPredicateEntity>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -27,13 +41,67 @@ export class WorkspaceFlatRowLevelPermissionPredicateGroupMapCacheService extend
|
||||
async computeForCache(
|
||||
workspaceId: string,
|
||||
): Promise<FlatRowLevelPermissionPredicateGroupMaps> {
|
||||
const rowLevelPermissionPredicateGroups =
|
||||
await this.rowLevelPermissionPredicateGroupRepository.find({
|
||||
where: {
|
||||
workspaceId,
|
||||
},
|
||||
const [
|
||||
rowLevelPermissionPredicateGroups,
|
||||
applications,
|
||||
objectMetadatas,
|
||||
roles,
|
||||
rowLevelPermissionPredicates,
|
||||
] = await Promise.all([
|
||||
this.rowLevelPermissionPredicateGroupRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
});
|
||||
}),
|
||||
this.applicationRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.objectMetadataRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.roleRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.rowLevelPermissionPredicateRepository.find({
|
||||
where: { workspaceId },
|
||||
select: [
|
||||
'id',
|
||||
'universalIdentifier',
|
||||
'rowLevelPermissionPredicateGroupId',
|
||||
],
|
||||
withDeleted: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
const [
|
||||
childRowLevelPermissionPredicateGroupsByParentId,
|
||||
rowLevelPermissionPredicatesByGroupId,
|
||||
] = (
|
||||
[
|
||||
{
|
||||
entities: rowLevelPermissionPredicateGroups,
|
||||
foreignKey: 'parentRowLevelPermissionPredicateGroupId',
|
||||
},
|
||||
{
|
||||
entities: rowLevelPermissionPredicates,
|
||||
foreignKey: 'rowLevelPermissionPredicateGroupId',
|
||||
},
|
||||
] as const
|
||||
).map(regroupEntitiesByRelatedEntityId);
|
||||
|
||||
const applicationIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(applications);
|
||||
const objectMetadataIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(objectMetadatas);
|
||||
const roleIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(roles);
|
||||
const rowLevelPermissionPredicateGroupIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(rowLevelPermissionPredicateGroups);
|
||||
|
||||
const flatRowLevelPermissionPredicateGroupMaps =
|
||||
createEmptyFlatEntityMaps();
|
||||
@@ -41,7 +109,23 @@ export class WorkspaceFlatRowLevelPermissionPredicateGroupMapCacheService extend
|
||||
for (const rowLevelPermissionPredicateGroupEntity of rowLevelPermissionPredicateGroups) {
|
||||
const flatRowLevelPermissionPredicateGroup =
|
||||
fromRowLevelPermissionPredicateGroupEntityToFlatRowLevelPermissionPredicateGroup(
|
||||
rowLevelPermissionPredicateGroupEntity,
|
||||
{
|
||||
entity: {
|
||||
...rowLevelPermissionPredicateGroupEntity,
|
||||
childRowLevelPermissionPredicateGroups:
|
||||
childRowLevelPermissionPredicateGroupsByParentId.get(
|
||||
rowLevelPermissionPredicateGroupEntity.id,
|
||||
) || [],
|
||||
rowLevelPermissionPredicates:
|
||||
rowLevelPermissionPredicatesByGroupId.get(
|
||||
rowLevelPermissionPredicateGroupEntity.id,
|
||||
) || [],
|
||||
},
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
objectMetadataIdToUniversalIdentifierMap,
|
||||
roleIdToUniversalIdentifierMap,
|
||||
rowLevelPermissionPredicateGroupIdToUniversalIdentifierMap,
|
||||
},
|
||||
);
|
||||
|
||||
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
|
||||
|
||||
+72
-9
@@ -7,11 +7,17 @@ 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 { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
|
||||
import { fromRowLevelPermissionPredicateEntityToFlatRowLevelPermissionPredicate } from 'src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-row-level-permission-predicate-entity-to-flat-row-level-permission-predicate.util';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
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 { type FlatRowLevelPermissionPredicateMaps } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-maps.type';
|
||||
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 { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
|
||||
|
||||
@Injectable()
|
||||
@@ -20,6 +26,16 @@ export class WorkspaceFlatRowLevelPermissionPredicateMapCacheService extends Wor
|
||||
constructor(
|
||||
@InjectRepository(RowLevelPermissionPredicateEntity)
|
||||
private readonly rowLevelPermissionPredicateRepository: Repository<RowLevelPermissionPredicateEntity>,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
@InjectRepository(FieldMetadataEntity)
|
||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||
@InjectRepository(ObjectMetadataEntity)
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
@InjectRepository(RoleEntity)
|
||||
private readonly roleRepository: Repository<RoleEntity>,
|
||||
@InjectRepository(RowLevelPermissionPredicateGroupEntity)
|
||||
private readonly rowLevelPermissionPredicateGroupRepository: Repository<RowLevelPermissionPredicateGroupEntity>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -27,21 +43,68 @@ export class WorkspaceFlatRowLevelPermissionPredicateMapCacheService extends Wor
|
||||
async computeForCache(
|
||||
workspaceId: string,
|
||||
): Promise<FlatRowLevelPermissionPredicateMaps> {
|
||||
const rowLevelPermissionPredicates =
|
||||
await this.rowLevelPermissionPredicateRepository.find({
|
||||
where: {
|
||||
workspaceId,
|
||||
},
|
||||
const [
|
||||
rowLevelPermissionPredicates,
|
||||
applications,
|
||||
fieldMetadatas,
|
||||
objectMetadatas,
|
||||
roles,
|
||||
rowLevelPermissionPredicateGroups,
|
||||
] = await Promise.all([
|
||||
this.rowLevelPermissionPredicateRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
});
|
||||
}),
|
||||
this.applicationRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.fieldMetadataRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.objectMetadataRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.roleRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.rowLevelPermissionPredicateGroupRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
const applicationIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(applications);
|
||||
const fieldMetadataIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(fieldMetadatas);
|
||||
const objectMetadataIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(objectMetadatas);
|
||||
const roleIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(roles);
|
||||
const rowLevelPermissionPredicateGroupIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(rowLevelPermissionPredicateGroups);
|
||||
|
||||
const flatRowLevelPermissionPredicateMaps = createEmptyFlatEntityMaps();
|
||||
|
||||
for (const rowLevelPermissionPredicateEntity of rowLevelPermissionPredicates) {
|
||||
const flatRowLevelPermissionPredicate =
|
||||
fromRowLevelPermissionPredicateEntityToFlatRowLevelPermissionPredicate(
|
||||
rowLevelPermissionPredicateEntity,
|
||||
);
|
||||
fromRowLevelPermissionPredicateEntityToFlatRowLevelPermissionPredicate({
|
||||
entity: rowLevelPermissionPredicateEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
objectMetadataIdToUniversalIdentifierMap,
|
||||
roleIdToUniversalIdentifierMap,
|
||||
rowLevelPermissionPredicateGroupIdToUniversalIdentifierMap,
|
||||
});
|
||||
|
||||
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
|
||||
flatEntity: flatRowLevelPermissionPredicate,
|
||||
|
||||
+113
-5
@@ -1,20 +1,117 @@
|
||||
/* @license Enterprise */
|
||||
|
||||
import { removePropertiesFromRecord } from 'twenty-shared/utils';
|
||||
import { isDefined, removePropertiesFromRecord } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
FlatEntityMapsException,
|
||||
FlatEntityMapsExceptionCode,
|
||||
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
|
||||
import { ROW_LEVEL_PERMISSION_PREDICATE_ENTITY_RELATION_PROPERTIES } from 'src/engine/metadata-modules/flat-row-level-permission-predicate/constants/row-level-permission-predicate-entity-relation-properties.constant';
|
||||
import { type RowLevelPermissionPredicateEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate.entity';
|
||||
import { type FlatRowLevelPermissionPredicate } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate.type';
|
||||
import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type';
|
||||
|
||||
export const fromRowLevelPermissionPredicateEntityToFlatRowLevelPermissionPredicate =
|
||||
(
|
||||
rowLevelPermissionPredicateEntity: RowLevelPermissionPredicateEntity,
|
||||
): FlatRowLevelPermissionPredicate => {
|
||||
({
|
||||
entity: rowLevelPermissionPredicateEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
objectMetadataIdToUniversalIdentifierMap,
|
||||
roleIdToUniversalIdentifierMap,
|
||||
rowLevelPermissionPredicateGroupIdToUniversalIdentifierMap,
|
||||
}: FromEntityToFlatEntityArgs<'rowLevelPermissionPredicate'>): FlatRowLevelPermissionPredicate => {
|
||||
const rowLevelPermissionPredicateEntityWithoutRelations =
|
||||
removePropertiesFromRecord(rowLevelPermissionPredicateEntity, [
|
||||
...ROW_LEVEL_PERMISSION_PREDICATE_ENTITY_RELATION_PROPERTIES,
|
||||
] as (typeof ROW_LEVEL_PERMISSION_PREDICATE_ENTITY_RELATION_PROPERTIES)[number][]);
|
||||
|
||||
const applicationUniversalIdentifier =
|
||||
applicationIdToUniversalIdentifierMap.get(
|
||||
rowLevelPermissionPredicateEntity.applicationId,
|
||||
);
|
||||
|
||||
if (!isDefined(applicationUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Application with id ${rowLevelPermissionPredicateEntity.applicationId} not found for rowLevelPermissionPredicate ${rowLevelPermissionPredicateEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const fieldMetadataUniversalIdentifier =
|
||||
fieldMetadataIdToUniversalIdentifierMap.get(
|
||||
rowLevelPermissionPredicateEntity.fieldMetadataId,
|
||||
);
|
||||
|
||||
if (!isDefined(fieldMetadataUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`FieldMetadata with id ${rowLevelPermissionPredicateEntity.fieldMetadataId} not found for rowLevelPermissionPredicate ${rowLevelPermissionPredicateEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const objectMetadataUniversalIdentifier =
|
||||
objectMetadataIdToUniversalIdentifierMap.get(
|
||||
rowLevelPermissionPredicateEntity.objectMetadataId,
|
||||
);
|
||||
|
||||
if (!isDefined(objectMetadataUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`ObjectMetadata with id ${rowLevelPermissionPredicateEntity.objectMetadataId} not found for rowLevelPermissionPredicate ${rowLevelPermissionPredicateEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const roleUniversalIdentifier = roleIdToUniversalIdentifierMap.get(
|
||||
rowLevelPermissionPredicateEntity.roleId,
|
||||
);
|
||||
|
||||
if (!isDefined(roleUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Role with id ${rowLevelPermissionPredicateEntity.roleId} not found for rowLevelPermissionPredicate ${rowLevelPermissionPredicateEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
let workspaceMemberFieldMetadataUniversalIdentifier: string | null = null;
|
||||
|
||||
if (
|
||||
isDefined(
|
||||
rowLevelPermissionPredicateEntity.workspaceMemberFieldMetadataId,
|
||||
)
|
||||
) {
|
||||
workspaceMemberFieldMetadataUniversalIdentifier =
|
||||
fieldMetadataIdToUniversalIdentifierMap.get(
|
||||
rowLevelPermissionPredicateEntity.workspaceMemberFieldMetadataId,
|
||||
) ?? null;
|
||||
|
||||
if (!isDefined(workspaceMemberFieldMetadataUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`FieldMetadata with id ${rowLevelPermissionPredicateEntity.workspaceMemberFieldMetadataId} not found for rowLevelPermissionPredicate ${rowLevelPermissionPredicateEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let rowLevelPermissionPredicateGroupUniversalIdentifier: string | null =
|
||||
null;
|
||||
|
||||
if (
|
||||
isDefined(
|
||||
rowLevelPermissionPredicateEntity.rowLevelPermissionPredicateGroupId,
|
||||
)
|
||||
) {
|
||||
rowLevelPermissionPredicateGroupUniversalIdentifier =
|
||||
rowLevelPermissionPredicateGroupIdToUniversalIdentifierMap.get(
|
||||
rowLevelPermissionPredicateEntity.rowLevelPermissionPredicateGroupId,
|
||||
) ?? null;
|
||||
|
||||
if (!isDefined(rowLevelPermissionPredicateGroupUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`RowLevelPermissionPredicateGroup with id ${rowLevelPermissionPredicateEntity.rowLevelPermissionPredicateGroupId} not found for rowLevelPermissionPredicate ${rowLevelPermissionPredicateEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...rowLevelPermissionPredicateEntityWithoutRelations,
|
||||
createdAt: rowLevelPermissionPredicateEntity.createdAt.toISOString(),
|
||||
@@ -23,5 +120,16 @@ export const fromRowLevelPermissionPredicateEntityToFlatRowLevelPermissionPredic
|
||||
rowLevelPermissionPredicateEntity.deletedAt?.toISOString() ?? null,
|
||||
universalIdentifier:
|
||||
rowLevelPermissionPredicateEntityWithoutRelations.universalIdentifier,
|
||||
__universal: {
|
||||
universalIdentifier:
|
||||
rowLevelPermissionPredicateEntity.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
fieldMetadataUniversalIdentifier,
|
||||
objectMetadataUniversalIdentifier,
|
||||
roleUniversalIdentifier,
|
||||
workspaceMemberFieldMetadataUniversalIdentifier,
|
||||
rowLevelPermissionPredicateGroupUniversalIdentifier,
|
||||
value: rowLevelPermissionPredicateEntityWithoutRelations.value,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
+96
-5
@@ -1,20 +1,95 @@
|
||||
/* @license Enterprise */
|
||||
|
||||
import { removePropertiesFromRecord } from 'twenty-shared/utils';
|
||||
import { isDefined, removePropertiesFromRecord } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
FlatEntityMapsException,
|
||||
FlatEntityMapsExceptionCode,
|
||||
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
|
||||
import { ROW_LEVEL_PERMISSION_PREDICATE_GROUP_ENTITY_RELATION_PROPERTIES } from 'src/engine/metadata-modules/flat-row-level-permission-predicate/constants/row-level-permission-predicate-group-entity-relation-properties.constant';
|
||||
import { type RowLevelPermissionPredicateGroupEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate-group.entity';
|
||||
import { type FlatRowLevelPermissionPredicateGroup } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-group.type';
|
||||
import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type';
|
||||
|
||||
type FromRowLevelPermissionPredicateGroupEntityToFlatRowLevelPermissionPredicateGroupArgs =
|
||||
FromEntityToFlatEntityArgs<'rowLevelPermissionPredicateGroup'> & {
|
||||
rowLevelPermissionPredicateGroupIdToUniversalIdentifierMap: Map<
|
||||
string,
|
||||
string
|
||||
>;
|
||||
};
|
||||
|
||||
export const fromRowLevelPermissionPredicateGroupEntityToFlatRowLevelPermissionPredicateGroup =
|
||||
(
|
||||
rowLevelPermissionPredicateGroupEntity: RowLevelPermissionPredicateGroupEntity,
|
||||
): FlatRowLevelPermissionPredicateGroup => {
|
||||
({
|
||||
entity: rowLevelPermissionPredicateGroupEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
objectMetadataIdToUniversalIdentifierMap,
|
||||
roleIdToUniversalIdentifierMap,
|
||||
rowLevelPermissionPredicateGroupIdToUniversalIdentifierMap,
|
||||
}: FromRowLevelPermissionPredicateGroupEntityToFlatRowLevelPermissionPredicateGroupArgs): FlatRowLevelPermissionPredicateGroup => {
|
||||
const rowLevelPermissionPredicateGroupEntityWithoutRelations =
|
||||
removePropertiesFromRecord(rowLevelPermissionPredicateGroupEntity, [
|
||||
...ROW_LEVEL_PERMISSION_PREDICATE_GROUP_ENTITY_RELATION_PROPERTIES,
|
||||
] as (typeof ROW_LEVEL_PERMISSION_PREDICATE_GROUP_ENTITY_RELATION_PROPERTIES)[number][]);
|
||||
|
||||
const applicationUniversalIdentifier =
|
||||
applicationIdToUniversalIdentifierMap.get(
|
||||
rowLevelPermissionPredicateGroupEntity.applicationId,
|
||||
);
|
||||
|
||||
if (!isDefined(applicationUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Application with id ${rowLevelPermissionPredicateGroupEntity.applicationId} not found for rowLevelPermissionPredicateGroup ${rowLevelPermissionPredicateGroupEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const objectMetadataUniversalIdentifier =
|
||||
objectMetadataIdToUniversalIdentifierMap.get(
|
||||
rowLevelPermissionPredicateGroupEntity.objectMetadataId,
|
||||
);
|
||||
|
||||
if (!isDefined(objectMetadataUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`ObjectMetadata with id ${rowLevelPermissionPredicateGroupEntity.objectMetadataId} not found for rowLevelPermissionPredicateGroup ${rowLevelPermissionPredicateGroupEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const roleUniversalIdentifier = roleIdToUniversalIdentifierMap.get(
|
||||
rowLevelPermissionPredicateGroupEntity.roleId,
|
||||
);
|
||||
|
||||
if (!isDefined(roleUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Role with id ${rowLevelPermissionPredicateGroupEntity.roleId} not found for rowLevelPermissionPredicateGroup ${rowLevelPermissionPredicateGroupEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
let parentRowLevelPermissionPredicateGroupUniversalIdentifier:
|
||||
| string
|
||||
| null = null;
|
||||
|
||||
if (
|
||||
isDefined(
|
||||
rowLevelPermissionPredicateGroupEntity.parentRowLevelPermissionPredicateGroupId,
|
||||
)
|
||||
) {
|
||||
parentRowLevelPermissionPredicateGroupUniversalIdentifier =
|
||||
rowLevelPermissionPredicateGroupIdToUniversalIdentifierMap.get(
|
||||
rowLevelPermissionPredicateGroupEntity.parentRowLevelPermissionPredicateGroupId,
|
||||
) ?? null;
|
||||
|
||||
if (
|
||||
!isDefined(parentRowLevelPermissionPredicateGroupUniversalIdentifier)
|
||||
) {
|
||||
throw new FlatEntityMapsException(
|
||||
`RowLevelPermissionPredicateGroup with id ${rowLevelPermissionPredicateGroupEntity.parentRowLevelPermissionPredicateGroupId} not found for rowLevelPermissionPredicateGroup ${rowLevelPermissionPredicateGroupEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...rowLevelPermissionPredicateGroupEntityWithoutRelations,
|
||||
createdAt: rowLevelPermissionPredicateGroupEntity.createdAt.toISOString(),
|
||||
@@ -31,5 +106,21 @@ export const fromRowLevelPermissionPredicateGroupEntityToFlatRowLevelPermissionP
|
||||
rowLevelPermissionPredicateGroupEntity.rowLevelPermissionPredicates ??
|
||||
[]
|
||||
).map(({ id }) => id),
|
||||
__universal: {
|
||||
universalIdentifier:
|
||||
rowLevelPermissionPredicateGroupEntity.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
objectMetadataUniversalIdentifier,
|
||||
roleUniversalIdentifier,
|
||||
parentRowLevelPermissionPredicateGroupUniversalIdentifier,
|
||||
childRowLevelPermissionPredicateGroupUniversalIdentifiers: (
|
||||
rowLevelPermissionPredicateGroupEntity.childRowLevelPermissionPredicateGroups ??
|
||||
[]
|
||||
).map(({ universalIdentifier }) => universalIdentifier),
|
||||
rowLevelPermissionPredicateUniversalIdentifiers: (
|
||||
rowLevelPermissionPredicateGroupEntity.rowLevelPermissionPredicates ??
|
||||
[]
|
||||
).map(({ universalIdentifier }) => universalIdentifier),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
|
||||
import { WorkspaceFlatSkillMapCacheService } from 'src/engine/metadata-modules/flat-skill/services/workspace-flat-skill-map-cache.service';
|
||||
import { SkillEntity } from 'src/engine/metadata-modules/skill/entities/skill.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([SkillEntity]),
|
||||
TypeOrmModule.forFeature([ApplicationEntity, SkillEntity]),
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
],
|
||||
providers: [WorkspaceFlatSkillMapCacheService],
|
||||
|
||||
+22
-5
@@ -5,11 +5,13 @@ 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 { type FlatSkillMaps } from 'src/engine/metadata-modules/flat-skill/types/flat-skill-maps.type';
|
||||
import { fromSkillEntityToFlatSkill } from 'src/engine/metadata-modules/flat-skill/utils/from-skill-entity-to-flat-skill.util';
|
||||
import { SkillEntity } from 'src/engine/metadata-modules/skill/entities/skill.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 { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
|
||||
|
||||
@Injectable()
|
||||
@@ -18,20 +20,35 @@ export class WorkspaceFlatSkillMapCacheService extends WorkspaceCacheProvider<Fl
|
||||
constructor(
|
||||
@InjectRepository(SkillEntity)
|
||||
private readonly skillRepository: Repository<SkillEntity>,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async computeForCache(workspaceId: string): Promise<FlatSkillMaps> {
|
||||
const skills = await this.skillRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
});
|
||||
const [skills, applications] = await Promise.all([
|
||||
this.skillRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.applicationRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
const applicationIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(applications);
|
||||
|
||||
const flatSkillMaps = createEmptyFlatEntityMaps();
|
||||
|
||||
for (const skillEntity of skills) {
|
||||
const flatSkill = fromSkillEntityToFlatSkill(skillEntity);
|
||||
const flatSkill = fromSkillEntityToFlatSkill({
|
||||
entity: skillEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
});
|
||||
|
||||
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
|
||||
flatEntity: flatSkill,
|
||||
|
||||
+26
-5
@@ -1,9 +1,26 @@
|
||||
import { type FlatSkill } from 'src/engine/metadata-modules/flat-skill/types/flat-skill.type';
|
||||
import { type SkillEntity } from 'src/engine/metadata-modules/skill/entities/skill.entity';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
FlatEntityMapsException,
|
||||
FlatEntityMapsExceptionCode,
|
||||
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
|
||||
import { type FlatSkill } from 'src/engine/metadata-modules/flat-skill/types/flat-skill.type';
|
||||
import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type';
|
||||
|
||||
export const fromSkillEntityToFlatSkill = ({
|
||||
entity: skillEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
}: FromEntityToFlatEntityArgs<'skill'>): FlatSkill => {
|
||||
const applicationUniversalIdentifier =
|
||||
applicationIdToUniversalIdentifierMap.get(skillEntity.applicationId);
|
||||
|
||||
if (!isDefined(applicationUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Application with id ${skillEntity.applicationId} not found for skill ${skillEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
export const fromSkillEntityToFlatSkill = (
|
||||
skillEntity: SkillEntity,
|
||||
): FlatSkill => {
|
||||
return {
|
||||
createdAt: skillEntity.createdAt.toISOString(),
|
||||
updatedAt: skillEntity.updatedAt.toISOString(),
|
||||
@@ -19,5 +36,9 @@ export const fromSkillEntityToFlatSkill = (
|
||||
isActive: skillEntity.isActive,
|
||||
universalIdentifier: skillEntity.universalIdentifier,
|
||||
applicationId: skillEntity.applicationId,
|
||||
__universal: {
|
||||
universalIdentifier: skillEntity.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
+47
-8
@@ -5,11 +5,15 @@ 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 { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
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 { 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 { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
|
||||
|
||||
@Injectable()
|
||||
@@ -18,22 +22,57 @@ export class WorkspaceFlatViewFieldMapCacheService extends WorkspaceCacheProvide
|
||||
constructor(
|
||||
@InjectRepository(ViewFieldEntity)
|
||||
private readonly viewFieldRepository: Repository<ViewFieldEntity>,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
@InjectRepository(FieldMetadataEntity)
|
||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||
@InjectRepository(ViewEntity)
|
||||
private readonly viewRepository: Repository<ViewEntity>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async computeForCache(workspaceId: string): Promise<FlatViewFieldMaps> {
|
||||
const existingViewFields = await this.viewFieldRepository.find({
|
||||
where: {
|
||||
workspaceId,
|
||||
},
|
||||
withDeleted: true,
|
||||
});
|
||||
const [viewFields, applications, fieldMetadatas, views] = await Promise.all(
|
||||
[
|
||||
this.viewFieldRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.applicationRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.fieldMetadataRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.viewRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
],
|
||||
);
|
||||
|
||||
const applicationIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(applications);
|
||||
const fieldMetadataIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(fieldMetadatas);
|
||||
const viewIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(views);
|
||||
|
||||
const flatViewFieldMaps = createEmptyFlatEntityMaps();
|
||||
|
||||
for (const viewFieldEntity of existingViewFields) {
|
||||
const flatViewField = fromViewFieldEntityToFlatViewField(viewFieldEntity);
|
||||
for (const viewFieldEntity of viewFields) {
|
||||
const flatViewField = fromViewFieldEntityToFlatViewField({
|
||||
entity: viewFieldEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
viewIdToUniversalIdentifierMap,
|
||||
});
|
||||
|
||||
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
|
||||
flatEntity: flatViewField,
|
||||
|
||||
+51
-5
@@ -1,22 +1,68 @@
|
||||
import { removePropertiesFromRecord } from 'twenty-shared/utils';
|
||||
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 FlatViewField } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field.type';
|
||||
import { type ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
|
||||
import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type';
|
||||
|
||||
export const fromViewFieldEntityToFlatViewField = (
|
||||
viewFieldEntity: ViewFieldEntity,
|
||||
): FlatViewField => {
|
||||
export const fromViewFieldEntityToFlatViewField = ({
|
||||
entity: viewFieldEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
viewIdToUniversalIdentifierMap,
|
||||
}: FromEntityToFlatEntityArgs<'viewField'>): FlatViewField => {
|
||||
const viewFieldEntityWithoutRelations = removePropertiesFromRecord(
|
||||
viewFieldEntity,
|
||||
getMetadataEntityRelationProperties('viewField'),
|
||||
);
|
||||
|
||||
const applicationUniversalIdentifier =
|
||||
applicationIdToUniversalIdentifierMap.get(viewFieldEntity.applicationId);
|
||||
|
||||
if (!isDefined(applicationUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Application with id ${viewFieldEntity.applicationId} not found for viewField ${viewFieldEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const fieldMetadataUniversalIdentifier =
|
||||
fieldMetadataIdToUniversalIdentifierMap.get(
|
||||
viewFieldEntity.fieldMetadataId,
|
||||
);
|
||||
|
||||
if (!isDefined(fieldMetadataUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`FieldMetadata with id ${viewFieldEntity.fieldMetadataId} not found for viewField ${viewFieldEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const viewUniversalIdentifier = viewIdToUniversalIdentifierMap.get(
|
||||
viewFieldEntity.viewId,
|
||||
);
|
||||
|
||||
if (!isDefined(viewUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`View with id ${viewFieldEntity.viewId} not found for viewField ${viewFieldEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
...viewFieldEntityWithoutRelations,
|
||||
createdAt: viewFieldEntity.createdAt.toISOString(),
|
||||
updatedAt: viewFieldEntity.updatedAt.toISOString(),
|
||||
deletedAt: viewFieldEntity.deletedAt?.toISOString() ?? null,
|
||||
universalIdentifier: viewFieldEntityWithoutRelations.universalIdentifier,
|
||||
__universal: {
|
||||
universalIdentifier: viewFieldEntity.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
fieldMetadataUniversalIdentifier,
|
||||
viewUniversalIdentifier,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
+49
-17
@@ -5,12 +5,15 @@ import { type 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 { type FlatViewFilterGroupMaps } from 'src/engine/metadata-modules/flat-view-filter-group/types/flat-view-filter-group-maps.type';
|
||||
import { fromViewFilterGroupEntityToFlatViewFilterGroup } from 'src/engine/metadata-modules/flat-view-filter-group/utils/from-view-filter-group-entity-to-flat-view-filter-group.util';
|
||||
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';
|
||||
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';
|
||||
|
||||
@@ -20,24 +23,39 @@ export class WorkspaceFlatViewFilterGroupMapCacheService extends WorkspaceCacheP
|
||||
constructor(
|
||||
@InjectRepository(ViewFilterGroupEntity)
|
||||
private readonly viewFilterGroupRepository: Repository<ViewFilterGroupEntity>,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
@InjectRepository(ViewFilterEntity)
|
||||
private readonly viewFilterRepository: Repository<ViewFilterEntity>,
|
||||
@InjectRepository(ViewEntity)
|
||||
private readonly viewRepository: Repository<ViewEntity>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async computeForCache(workspaceId: string): Promise<FlatViewFilterGroupMaps> {
|
||||
const [viewFilterGroups, viewFilters] = await Promise.all([
|
||||
this.viewFilterGroupRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.viewFilterRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'viewFilterGroupId'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
]);
|
||||
const [viewFilterGroups, applications, viewFilters, views] =
|
||||
await Promise.all([
|
||||
this.viewFilterGroupRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.applicationRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.viewFilterRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier', 'viewFilterGroupId'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.viewRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
const [viewFiltersByViewFilterGroupId, childViewFilterGroupsByParentId] = (
|
||||
[
|
||||
@@ -52,17 +70,31 @@ export class WorkspaceFlatViewFilterGroupMapCacheService extends WorkspaceCacheP
|
||||
] as const
|
||||
).map(regroupEntitiesByRelatedEntityId);
|
||||
|
||||
const applicationIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(applications);
|
||||
const viewFilterGroupIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(viewFilterGroups);
|
||||
const viewIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(views);
|
||||
|
||||
const flatViewFilterGroupMaps = createEmptyFlatEntityMaps();
|
||||
|
||||
for (const viewFilterGroupEntity of viewFilterGroups) {
|
||||
const flatViewFilterGroup =
|
||||
fromViewFilterGroupEntityToFlatViewFilterGroup({
|
||||
...viewFilterGroupEntity,
|
||||
viewFilters:
|
||||
viewFiltersByViewFilterGroupId.get(viewFilterGroupEntity.id) || [],
|
||||
childViewFilterGroups:
|
||||
childViewFilterGroupsByParentId.get(viewFilterGroupEntity.id) || [],
|
||||
} as ViewFilterGroupEntity);
|
||||
entity: {
|
||||
...viewFilterGroupEntity,
|
||||
viewFilters:
|
||||
viewFiltersByViewFilterGroupId.get(viewFilterGroupEntity.id) ||
|
||||
[],
|
||||
childViewFilterGroups:
|
||||
childViewFilterGroupsByParentId.get(viewFilterGroupEntity.id) ||
|
||||
[],
|
||||
},
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
viewFilterGroupIdToUniversalIdentifierMap,
|
||||
viewIdToUniversalIdentifierMap,
|
||||
});
|
||||
|
||||
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
|
||||
flatEntity: flatViewFilterGroup,
|
||||
|
||||
+72
-11
@@ -1,17 +1,68 @@
|
||||
import { removePropertiesFromRecord } from 'twenty-shared/utils';
|
||||
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 FlatViewFilterGroup } from 'src/engine/metadata-modules/flat-view-filter-group/types/flat-view-filter-group.type';
|
||||
import { type ViewFilterGroupEntity } from 'src/engine/metadata-modules/view-filter-group/entities/view-filter-group.entity';
|
||||
import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type';
|
||||
|
||||
export const fromViewFilterGroupEntityToFlatViewFilterGroup = (
|
||||
viewFilterGroupEntity: ViewFilterGroupEntity,
|
||||
): FlatViewFilterGroup => {
|
||||
type FromViewFilterGroupEntityToFlatViewFilterGroupArgs =
|
||||
FromEntityToFlatEntityArgs<'viewFilterGroup'> & {
|
||||
viewFilterGroupIdToUniversalIdentifierMap: Map<string, string>;
|
||||
};
|
||||
|
||||
export const fromViewFilterGroupEntityToFlatViewFilterGroup = ({
|
||||
entity: viewFilterGroupEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
viewFilterGroupIdToUniversalIdentifierMap,
|
||||
viewIdToUniversalIdentifierMap,
|
||||
}: FromViewFilterGroupEntityToFlatViewFilterGroupArgs): FlatViewFilterGroup => {
|
||||
const viewFilterGroupEntityWithoutRelations = removePropertiesFromRecord(
|
||||
viewFilterGroupEntity,
|
||||
getMetadataEntityRelationProperties('viewFilterGroup'),
|
||||
);
|
||||
|
||||
const applicationUniversalIdentifier =
|
||||
applicationIdToUniversalIdentifierMap.get(
|
||||
viewFilterGroupEntity.applicationId,
|
||||
);
|
||||
|
||||
if (!isDefined(applicationUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Application with id ${viewFilterGroupEntity.applicationId} not found for viewFilterGroup ${viewFilterGroupEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
let parentViewFilterGroupUniversalIdentifier: string | null = null;
|
||||
|
||||
if (isDefined(viewFilterGroupEntity.parentViewFilterGroupId)) {
|
||||
parentViewFilterGroupUniversalIdentifier =
|
||||
viewFilterGroupIdToUniversalIdentifierMap.get(
|
||||
viewFilterGroupEntity.parentViewFilterGroupId,
|
||||
) ?? null;
|
||||
|
||||
if (!isDefined(parentViewFilterGroupUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`ViewFilterGroup with id ${viewFilterGroupEntity.parentViewFilterGroupId} not found for viewFilterGroup ${viewFilterGroupEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const viewUniversalIdentifier = viewIdToUniversalIdentifierMap.get(
|
||||
viewFilterGroupEntity.viewId,
|
||||
);
|
||||
|
||||
if (!isDefined(viewUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`View with id ${viewFilterGroupEntity.viewId} not found for viewFilterGroup ${viewFilterGroupEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
...viewFilterGroupEntityWithoutRelations,
|
||||
createdAt: viewFilterGroupEntity.createdAt.toISOString(),
|
||||
@@ -19,12 +70,22 @@ export const fromViewFilterGroupEntityToFlatViewFilterGroup = (
|
||||
deletedAt: viewFilterGroupEntity.deletedAt?.toISOString() ?? null,
|
||||
universalIdentifier:
|
||||
viewFilterGroupEntityWithoutRelations.universalIdentifier,
|
||||
viewFilterIds:
|
||||
viewFilterGroupEntity.viewFilters?.map((viewFilter) => viewFilter.id) ??
|
||||
[],
|
||||
viewFilterIds: viewFilterGroupEntity.viewFilters?.map(({ id }) => id) ?? [],
|
||||
childViewFilterGroupIds:
|
||||
viewFilterGroupEntity.childViewFilterGroups?.map(
|
||||
(childGroup) => childGroup.id,
|
||||
) ?? [],
|
||||
viewFilterGroupEntity.childViewFilterGroups?.map(({ id }) => id) ?? [],
|
||||
__universal: {
|
||||
universalIdentifier: viewFilterGroupEntity.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
parentViewFilterGroupUniversalIdentifier,
|
||||
viewUniversalIdentifier,
|
||||
viewFilterUniversalIdentifiers:
|
||||
viewFilterGroupEntity.viewFilters?.map(
|
||||
({ universalIdentifier }) => universalIdentifier,
|
||||
) ?? [],
|
||||
childViewFilterGroupUniversalIdentifiers:
|
||||
viewFilterGroupEntity.childViewFilterGroups?.map(
|
||||
({ universalIdentifier }) => universalIdentifier,
|
||||
) ?? [],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
+56
-8
@@ -5,11 +5,16 @@ 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 { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
|
||||
import { type FlatViewFilterMaps } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter-maps.type';
|
||||
import { fromViewFilterEntityToFlatViewFilter } from 'src/engine/metadata-modules/flat-view-filter/utils/from-view-filter-entity-to-flat-view-filter.util';
|
||||
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';
|
||||
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 { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
|
||||
|
||||
@Injectable()
|
||||
@@ -18,23 +23,66 @@ export class WorkspaceFlatViewFilterMapCacheService extends WorkspaceCacheProvid
|
||||
constructor(
|
||||
@InjectRepository(ViewFilterEntity)
|
||||
private readonly viewFilterRepository: Repository<ViewFilterEntity>,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
@InjectRepository(FieldMetadataEntity)
|
||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||
@InjectRepository(ViewFilterGroupEntity)
|
||||
private readonly viewFilterGroupRepository: Repository<ViewFilterGroupEntity>,
|
||||
@InjectRepository(ViewEntity)
|
||||
private readonly viewRepository: Repository<ViewEntity>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async computeForCache(workspaceId: string): Promise<FlatViewFilterMaps> {
|
||||
const viewFilters = await this.viewFilterRepository.find({
|
||||
where: {
|
||||
workspaceId,
|
||||
},
|
||||
withDeleted: true,
|
||||
});
|
||||
const [viewFilters, applications, fieldMetadatas, viewFilterGroups, views] =
|
||||
await Promise.all([
|
||||
this.viewFilterRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.applicationRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.fieldMetadataRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.viewFilterGroupRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.viewRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
const applicationIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(applications);
|
||||
const fieldMetadataIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(fieldMetadatas);
|
||||
const viewFilterGroupIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(viewFilterGroups);
|
||||
const viewIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(views);
|
||||
|
||||
const flatViewFilterMaps = createEmptyFlatEntityMaps();
|
||||
|
||||
for (const viewFilterEntity of viewFilters) {
|
||||
const flatViewFilter =
|
||||
fromViewFilterEntityToFlatViewFilter(viewFilterEntity);
|
||||
const flatViewFilter = fromViewFilterEntityToFlatViewFilter({
|
||||
entity: viewFilterEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
viewFilterGroupIdToUniversalIdentifierMap,
|
||||
viewIdToUniversalIdentifierMap,
|
||||
});
|
||||
|
||||
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
|
||||
flatEntity: flatViewFilter,
|
||||
|
||||
+70
-5
@@ -1,22 +1,87 @@
|
||||
import { removePropertiesFromRecord } from 'twenty-shared/utils';
|
||||
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 FlatViewFilter } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter.type';
|
||||
import { type ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity';
|
||||
import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type';
|
||||
|
||||
export const fromViewFilterEntityToFlatViewFilter = (
|
||||
viewFilterEntity: ViewFilterEntity,
|
||||
): FlatViewFilter => {
|
||||
export const fromViewFilterEntityToFlatViewFilter = ({
|
||||
entity: viewFilterEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
viewFilterGroupIdToUniversalIdentifierMap,
|
||||
viewIdToUniversalIdentifierMap,
|
||||
}: FromEntityToFlatEntityArgs<'viewFilter'>): FlatViewFilter => {
|
||||
const viewFilterEntityWithoutRelations = removePropertiesFromRecord(
|
||||
viewFilterEntity,
|
||||
getMetadataEntityRelationProperties('viewFilter'),
|
||||
);
|
||||
|
||||
const applicationUniversalIdentifier =
|
||||
applicationIdToUniversalIdentifierMap.get(viewFilterEntity.applicationId);
|
||||
|
||||
if (!isDefined(applicationUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Application with id ${viewFilterEntity.applicationId} not found for viewFilter ${viewFilterEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const fieldMetadataUniversalIdentifier =
|
||||
fieldMetadataIdToUniversalIdentifierMap.get(
|
||||
viewFilterEntity.fieldMetadataId,
|
||||
);
|
||||
|
||||
if (!isDefined(fieldMetadataUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`FieldMetadata with id ${viewFilterEntity.fieldMetadataId} not found for viewFilter ${viewFilterEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
let viewFilterGroupUniversalIdentifier: string | null = null;
|
||||
|
||||
if (isDefined(viewFilterEntity.viewFilterGroupId)) {
|
||||
viewFilterGroupUniversalIdentifier =
|
||||
viewFilterGroupIdToUniversalIdentifierMap.get(
|
||||
viewFilterEntity.viewFilterGroupId,
|
||||
) ?? null;
|
||||
|
||||
if (!isDefined(viewFilterGroupUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`ViewFilterGroup with id ${viewFilterEntity.viewFilterGroupId} not found for viewFilter ${viewFilterEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const viewUniversalIdentifier = viewIdToUniversalIdentifierMap.get(
|
||||
viewFilterEntity.viewId,
|
||||
);
|
||||
|
||||
if (!isDefined(viewUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`View with id ${viewFilterEntity.viewId} not found for viewFilter ${viewFilterEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
...viewFilterEntityWithoutRelations,
|
||||
createdAt: viewFilterEntity.createdAt.toISOString(),
|
||||
updatedAt: viewFilterEntity.updatedAt.toISOString(),
|
||||
deletedAt: viewFilterEntity.deletedAt?.toISOString() ?? null,
|
||||
universalIdentifier: viewFilterEntityWithoutRelations.universalIdentifier,
|
||||
__universal: {
|
||||
universalIdentifier: viewFilterEntity.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
fieldMetadataUniversalIdentifier,
|
||||
viewFilterGroupUniversalIdentifier,
|
||||
viewUniversalIdentifier,
|
||||
value: viewFilterEntityWithoutRelations.value,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
+34
-8
@@ -5,11 +5,14 @@ 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 { FlatViewGroupMaps } from 'src/engine/metadata-modules/flat-view-group/types/flat-view-group-maps.type';
|
||||
import { fromViewGroupEntityToFlatViewGroup } from 'src/engine/metadata-modules/flat-view-group/utils/from-view-group-entity-to-flat-view-group.util';
|
||||
import { ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.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 { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
|
||||
|
||||
@Injectable()
|
||||
@@ -18,22 +21,45 @@ export class WorkspaceFlatViewGroupMapCacheService extends WorkspaceCacheProvide
|
||||
constructor(
|
||||
@InjectRepository(ViewGroupEntity)
|
||||
private readonly viewGroupRepository: Repository<ViewGroupEntity>,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
@InjectRepository(ViewEntity)
|
||||
private readonly viewRepository: Repository<ViewEntity>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async computeForCache(workspaceId: string): Promise<FlatViewGroupMaps> {
|
||||
const existingViewGroups = await this.viewGroupRepository.find({
|
||||
where: {
|
||||
workspaceId,
|
||||
},
|
||||
withDeleted: true,
|
||||
});
|
||||
const [viewGroups, applications, views] = await Promise.all([
|
||||
this.viewGroupRepository.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,
|
||||
}),
|
||||
]);
|
||||
|
||||
const applicationIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(applications);
|
||||
const viewIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(views);
|
||||
|
||||
const flatViewGroupMaps = createEmptyFlatEntityMaps();
|
||||
|
||||
for (const viewGroupEntity of existingViewGroups) {
|
||||
const flatViewGroup = fromViewGroupEntityToFlatViewGroup(viewGroupEntity);
|
||||
for (const viewGroupEntity of viewGroups) {
|
||||
const flatViewGroup = fromViewGroupEntityToFlatViewGroup({
|
||||
entity: viewGroupEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
viewIdToUniversalIdentifierMap,
|
||||
});
|
||||
|
||||
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
|
||||
flatEntity: flatViewGroup,
|
||||
|
||||
+37
-5
@@ -1,22 +1,54 @@
|
||||
import { removePropertiesFromRecord } from 'twenty-shared/utils';
|
||||
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 FlatViewGroup } from 'src/engine/metadata-modules/flat-view-group/types/flat-view-group.type';
|
||||
import { type ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity';
|
||||
import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type';
|
||||
|
||||
export const fromViewGroupEntityToFlatViewGroup = (
|
||||
viewGroupEntity: ViewGroupEntity,
|
||||
): FlatViewGroup => {
|
||||
export const fromViewGroupEntityToFlatViewGroup = ({
|
||||
entity: viewGroupEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
viewIdToUniversalIdentifierMap,
|
||||
}: FromEntityToFlatEntityArgs<'viewGroup'>): FlatViewGroup => {
|
||||
const viewGroupEntityWithoutRelations = removePropertiesFromRecord(
|
||||
viewGroupEntity,
|
||||
getMetadataEntityRelationProperties('viewGroup'),
|
||||
);
|
||||
|
||||
const applicationUniversalIdentifier =
|
||||
applicationIdToUniversalIdentifierMap.get(viewGroupEntity.applicationId);
|
||||
|
||||
if (!isDefined(applicationUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Application with id ${viewGroupEntity.applicationId} not found for viewGroup ${viewGroupEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const viewUniversalIdentifier = viewIdToUniversalIdentifierMap.get(
|
||||
viewGroupEntity.viewId,
|
||||
);
|
||||
|
||||
if (!isDefined(viewUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`View with id ${viewGroupEntity.viewId} not found for viewGroup ${viewGroupEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
...viewGroupEntityWithoutRelations,
|
||||
createdAt: viewGroupEntity.createdAt.toISOString(),
|
||||
updatedAt: viewGroupEntity.updatedAt.toISOString(),
|
||||
deletedAt: viewGroupEntity.deletedAt?.toISOString() ?? null,
|
||||
universalIdentifier: viewGroupEntityWithoutRelations.universalIdentifier,
|
||||
__universal: {
|
||||
universalIdentifier: viewGroupEntity.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
viewUniversalIdentifier,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { WorkspaceFlatViewMapCacheService } from 'src/engine/metadata-modules/flat-view/services/workspace-flat-view-map-cache.service';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.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';
|
||||
@@ -11,6 +14,9 @@ import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entit
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([
|
||||
ApplicationEntity,
|
||||
FieldMetadataEntity,
|
||||
ObjectMetadataEntity,
|
||||
ViewEntity,
|
||||
ViewFieldEntity,
|
||||
ViewFilterEntity,
|
||||
|
||||
+78
-33
@@ -5,15 +5,19 @@ import { type 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 { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
|
||||
import { type FlatViewMaps } from 'src/engine/metadata-modules/flat-view/types/flat-view-maps.type';
|
||||
import { fromViewEntityToFlatView } from 'src/engine/metadata-modules/flat-view/utils/from-view-entity-to-flat-view.util';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.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';
|
||||
import { ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.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';
|
||||
|
||||
@@ -23,6 +27,12 @@ export class WorkspaceFlatViewMapCacheService extends WorkspaceCacheProvider<Fla
|
||||
constructor(
|
||||
@InjectRepository(ViewEntity)
|
||||
private readonly viewRepository: Repository<ViewEntity>,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
@InjectRepository(ObjectMetadataEntity)
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
@InjectRepository(FieldMetadataEntity)
|
||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||
@InjectRepository(ViewFieldEntity)
|
||||
private readonly viewFieldRepository: Repository<ViewFieldEntity>,
|
||||
@InjectRepository(ViewFilterEntity)
|
||||
@@ -36,33 +46,56 @@ export class WorkspaceFlatViewMapCacheService extends WorkspaceCacheProvider<Fla
|
||||
}
|
||||
|
||||
async computeForCache(workspaceId: string): Promise<FlatViewMaps> {
|
||||
const [views, viewFields, viewFilters, viewGroups, viewFilterGroups] =
|
||||
await Promise.all([
|
||||
this.viewRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.viewFieldRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'viewId'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.viewFilterRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'viewId'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.viewGroupRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'viewId'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.viewFilterGroupRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'viewId'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
]);
|
||||
const [
|
||||
views,
|
||||
applications,
|
||||
objectMetadatas,
|
||||
fieldMetadatas,
|
||||
viewFields,
|
||||
viewFilters,
|
||||
viewGroups,
|
||||
viewFilterGroups,
|
||||
] = await Promise.all([
|
||||
this.viewRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.applicationRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.objectMetadataRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.fieldMetadataRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.viewFieldRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier', 'viewId'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.viewFilterRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier', 'viewId'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.viewGroupRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier', 'viewId'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.viewFilterGroupRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier', 'viewId'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
const [
|
||||
viewFieldsByViewId,
|
||||
@@ -90,16 +123,28 @@ export class WorkspaceFlatViewMapCacheService extends WorkspaceCacheProvider<Fla
|
||||
] as const
|
||||
).map(regroupEntitiesByRelatedEntityId);
|
||||
|
||||
const applicationIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(applications);
|
||||
const objectMetadataIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(objectMetadatas);
|
||||
const fieldMetadataIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(fieldMetadatas);
|
||||
|
||||
const flatViewMaps = createEmptyFlatEntityMaps();
|
||||
|
||||
for (const viewEntity of views) {
|
||||
const flatView = fromViewEntityToFlatView({
|
||||
...viewEntity,
|
||||
viewFields: viewFieldsByViewId.get(viewEntity.id) || [],
|
||||
viewFilters: viewFiltersByViewId.get(viewEntity.id) || [],
|
||||
viewGroups: viewGroupsByViewId.get(viewEntity.id) || [],
|
||||
viewFilterGroups: viewFilterGroupsByViewId.get(viewEntity.id) || [],
|
||||
} as ViewEntity);
|
||||
entity: {
|
||||
...viewEntity,
|
||||
viewFields: viewFieldsByViewId.get(viewEntity.id) || [],
|
||||
viewFilters: viewFiltersByViewId.get(viewEntity.id) || [],
|
||||
viewGroups: viewGroupsByViewId.get(viewEntity.id) || [],
|
||||
viewFilterGroups: viewFilterGroupsByViewId.get(viewEntity.id) || [],
|
||||
},
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
objectMetadataIdToUniversalIdentifierMap,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
});
|
||||
|
||||
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
|
||||
flatEntity: flatView,
|
||||
|
||||
+106
-10
@@ -1,27 +1,123 @@
|
||||
import { removePropertiesFromRecord } from 'twenty-shared/utils';
|
||||
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 FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
|
||||
import { type ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
|
||||
import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type';
|
||||
|
||||
export const fromViewEntityToFlatView = (viewEntity: ViewEntity): FlatView => {
|
||||
export const fromViewEntityToFlatView = ({
|
||||
entity: viewEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
objectMetadataIdToUniversalIdentifierMap,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
}: FromEntityToFlatEntityArgs<'view'>): FlatView => {
|
||||
const viewEntityWithoutRelations = removePropertiesFromRecord(
|
||||
viewEntity,
|
||||
getMetadataEntityRelationProperties('view'),
|
||||
);
|
||||
|
||||
const applicationUniversalIdentifier =
|
||||
applicationIdToUniversalIdentifierMap.get(viewEntity.applicationId);
|
||||
|
||||
if (!isDefined(applicationUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Application with id ${viewEntity.applicationId} not found for view ${viewEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const objectMetadataUniversalIdentifier =
|
||||
objectMetadataIdToUniversalIdentifierMap.get(viewEntity.objectMetadataId);
|
||||
|
||||
if (!isDefined(objectMetadataUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`ObjectMetadata with id ${viewEntity.objectMetadataId} not found for view ${viewEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
let kanbanAggregateOperationFieldMetadataUniversalIdentifier: string | null =
|
||||
null;
|
||||
|
||||
if (isDefined(viewEntity.kanbanAggregateOperationFieldMetadataId)) {
|
||||
kanbanAggregateOperationFieldMetadataUniversalIdentifier =
|
||||
fieldMetadataIdToUniversalIdentifierMap.get(
|
||||
viewEntity.kanbanAggregateOperationFieldMetadataId,
|
||||
) ?? null;
|
||||
|
||||
if (!isDefined(kanbanAggregateOperationFieldMetadataUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`FieldMetadata with id ${viewEntity.kanbanAggregateOperationFieldMetadataId} not found for view ${viewEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let calendarFieldMetadataUniversalIdentifier: string | null = null;
|
||||
|
||||
if (isDefined(viewEntity.calendarFieldMetadataId)) {
|
||||
calendarFieldMetadataUniversalIdentifier =
|
||||
fieldMetadataIdToUniversalIdentifierMap.get(
|
||||
viewEntity.calendarFieldMetadataId,
|
||||
) ?? null;
|
||||
|
||||
if (!isDefined(calendarFieldMetadataUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`FieldMetadata with id ${viewEntity.calendarFieldMetadataId} not found for view ${viewEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mainGroupByFieldMetadataUniversalIdentifier: string | null = null;
|
||||
|
||||
if (isDefined(viewEntity.mainGroupByFieldMetadataId)) {
|
||||
mainGroupByFieldMetadataUniversalIdentifier =
|
||||
fieldMetadataIdToUniversalIdentifierMap.get(
|
||||
viewEntity.mainGroupByFieldMetadataId,
|
||||
) ?? null;
|
||||
|
||||
if (!isDefined(mainGroupByFieldMetadataUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`FieldMetadata with id ${viewEntity.mainGroupByFieldMetadataId} not found for view ${viewEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...viewEntityWithoutRelations,
|
||||
createdAt: viewEntity.createdAt.toISOString(),
|
||||
updatedAt: viewEntity.updatedAt.toISOString(),
|
||||
deletedAt: viewEntity.deletedAt?.toISOString() ?? null,
|
||||
universalIdentifier: viewEntityWithoutRelations.universalIdentifier,
|
||||
viewFieldIds: viewEntity.viewFields.map((viewField) => viewField.id),
|
||||
viewFilterIds: viewEntity.viewFilters.map((viewFilter) => viewFilter.id),
|
||||
viewGroupIds: viewEntity.viewGroups.map((viewGroup) => viewGroup.id),
|
||||
viewFilterGroupIds:
|
||||
viewEntity.viewFilterGroups?.map(
|
||||
(viewFilterGroup) => viewFilterGroup.id,
|
||||
) ?? [],
|
||||
viewFieldIds: viewEntity.viewFields.map(({ id }) => id),
|
||||
viewFilterIds: viewEntity.viewFilters.map(({ id }) => id),
|
||||
viewGroupIds: viewEntity.viewGroups.map(({ id }) => id),
|
||||
viewFilterGroupIds: viewEntity.viewFilterGroups?.map(({ id }) => id) ?? [],
|
||||
__universal: {
|
||||
universalIdentifier: viewEntity.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
objectMetadataUniversalIdentifier,
|
||||
kanbanAggregateOperationFieldMetadataUniversalIdentifier,
|
||||
calendarFieldMetadataUniversalIdentifier,
|
||||
mainGroupByFieldMetadataUniversalIdentifier,
|
||||
viewFieldUniversalIdentifiers: viewEntity.viewFields.map(
|
||||
({ universalIdentifier }) => universalIdentifier,
|
||||
),
|
||||
viewFilterUniversalIdentifiers: viewEntity.viewFilters.map(
|
||||
({ universalIdentifier }) => universalIdentifier,
|
||||
),
|
||||
viewGroupUniversalIdentifiers: viewEntity.viewGroups.map(
|
||||
({ universalIdentifier }) => universalIdentifier,
|
||||
),
|
||||
viewFilterGroupUniversalIdentifiers:
|
||||
viewEntity.viewFilterGroups?.map(
|
||||
({ universalIdentifier }) => universalIdentifier,
|
||||
) ?? [],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
+2
-1
@@ -1,13 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
|
||||
import { WorkspaceFlatWebhookMapCacheService } from 'src/engine/metadata-modules/flat-webhook/services/workspace-flat-webhook-map-cache.service';
|
||||
import { WebhookEntity } from 'src/engine/metadata-modules/webhook/entities/webhook.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([WebhookEntity]),
|
||||
TypeOrmModule.forFeature([ApplicationEntity, WebhookEntity]),
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
],
|
||||
providers: [WorkspaceFlatWebhookMapCacheService],
|
||||
|
||||
+20
-4
@@ -5,11 +5,13 @@ import { IsNull, 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 { type FlatWebhookMaps } from 'src/engine/metadata-modules/flat-webhook/types/flat-webhook-maps.type';
|
||||
import { fromWebhookEntityToFlatWebhook } from 'src/engine/metadata-modules/flat-webhook/utils/from-webhook-entity-to-flat-webhook.util';
|
||||
import { WebhookEntity } from 'src/engine/metadata-modules/webhook/entities/webhook.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 { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
|
||||
|
||||
@Injectable()
|
||||
@@ -18,19 +20,33 @@ export class WorkspaceFlatWebhookMapCacheService extends WorkspaceCacheProvider<
|
||||
constructor(
|
||||
@InjectRepository(WebhookEntity)
|
||||
private readonly webhookRepository: Repository<WebhookEntity>,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async computeForCache(workspaceId: string): Promise<FlatWebhookMaps> {
|
||||
const webhooks = await this.webhookRepository.find({
|
||||
where: { workspaceId, deletedAt: IsNull() },
|
||||
});
|
||||
const [webhooks, applications] = await Promise.all([
|
||||
this.webhookRepository.find({
|
||||
where: { workspaceId, deletedAt: IsNull() },
|
||||
}),
|
||||
this.applicationRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
}),
|
||||
]);
|
||||
|
||||
const applicationIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(applications);
|
||||
|
||||
const flatWebhookMaps = createEmptyFlatEntityMaps();
|
||||
|
||||
for (const webhookEntity of webhooks) {
|
||||
const flatWebhook = fromWebhookEntityToFlatWebhook(webhookEntity);
|
||||
const flatWebhook = fromWebhookEntityToFlatWebhook({
|
||||
entity: webhookEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
});
|
||||
|
||||
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
|
||||
flatEntity: flatWebhook,
|
||||
|
||||
+26
-5
@@ -1,9 +1,26 @@
|
||||
import { type FlatWebhook } from 'src/engine/metadata-modules/flat-webhook/types/flat-webhook.type';
|
||||
import { type WebhookEntity } from 'src/engine/metadata-modules/webhook/entities/webhook.entity';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
FlatEntityMapsException,
|
||||
FlatEntityMapsExceptionCode,
|
||||
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
|
||||
import { type FlatWebhook } from 'src/engine/metadata-modules/flat-webhook/types/flat-webhook.type';
|
||||
import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type';
|
||||
|
||||
export const fromWebhookEntityToFlatWebhook = ({
|
||||
entity: webhookEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
}: FromEntityToFlatEntityArgs<'webhook'>): FlatWebhook => {
|
||||
const applicationUniversalIdentifier =
|
||||
applicationIdToUniversalIdentifierMap.get(webhookEntity.applicationId);
|
||||
|
||||
if (!isDefined(applicationUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Application with id ${webhookEntity.applicationId} not found for webhook ${webhookEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
export const fromWebhookEntityToFlatWebhook = (
|
||||
webhookEntity: WebhookEntity,
|
||||
): FlatWebhook => {
|
||||
return {
|
||||
id: webhookEntity.id,
|
||||
targetUrl: webhookEntity.targetUrl,
|
||||
@@ -16,5 +33,9 @@ export const fromWebhookEntityToFlatWebhook = (
|
||||
createdAt: webhookEntity.createdAt.toISOString(),
|
||||
updatedAt: webhookEntity.updatedAt.toISOString(),
|
||||
deletedAt: webhookEntity.deletedAt?.toISOString() ?? null,
|
||||
__universal: {
|
||||
universalIdentifier: webhookEntity.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
+4
-3
@@ -3,6 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
||||
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||
import { AuditModule } from 'src/engine/core-modules/audit/audit.module';
|
||||
import { TokenModule } from 'src/engine/core-modules/auth/token/token.module';
|
||||
@@ -13,7 +14,7 @@ import { FileModule } from 'src/engine/core-modules/file/file.module';
|
||||
import { SecretEncryptionModule } from 'src/engine/core-modules/secret-encryption/secret-encryption.module';
|
||||
import { ThrottlerModule } from 'src/engine/core-modules/throttler/throttler.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 { FunctionBuildModule } from 'src/engine/metadata-modules/function-build/function-build.module';
|
||||
import { LogicFunctionLayerModule } from 'src/engine/metadata-modules/logic-function-layer/logic-function-layer.module';
|
||||
import { LogicFunctionTriggerJob } from 'src/engine/metadata-modules/logic-function/jobs/logic-function-trigger.job';
|
||||
import { LogicFunctionEntity } from 'src/engine/metadata-modules/logic-function/logic-function.entity';
|
||||
@@ -21,16 +22,16 @@ import { LogicFunctionResolver } from 'src/engine/metadata-modules/logic-functio
|
||||
import { LogicFunctionService } from 'src/engine/metadata-modules/logic-function/logic-function.service';
|
||||
import { LogicFunctionV2Service } from 'src/engine/metadata-modules/logic-function/services/logic-function-v2.service';
|
||||
import { WorkspaceFlatLogicFunctionMapCacheService } from 'src/engine/metadata-modules/logic-function/services/workspace-flat-logic-function-map-cache.service';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
import { SubscriptionsModule } from 'src/engine/subscriptions/subscriptions.module';
|
||||
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
|
||||
import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module';
|
||||
import { FunctionBuildModule } from 'src/engine/metadata-modules/function-build/function-build.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
FileUploadModule,
|
||||
NestjsQueryTypeOrmModule.forFeature([LogicFunctionEntity]),
|
||||
TypeOrmModule.forFeature([FeatureFlagEntity]),
|
||||
TypeOrmModule.forFeature([ApplicationEntity, FeatureFlagEntity]),
|
||||
FileModule,
|
||||
ThrottlerModule,
|
||||
ApplicationModule,
|
||||
|
||||
+21
-6
@@ -5,12 +5,14 @@ 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 { FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { LogicFunctionEntity } from 'src/engine/metadata-modules/logic-function/logic-function.entity';
|
||||
import { FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type';
|
||||
import { fromLogicFunctionEntityToFlatLogicFunction } from 'src/engine/metadata-modules/logic-function/utils/from-logic-function-entity-to-flat-logic-function.util';
|
||||
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 { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
|
||||
|
||||
@Injectable()
|
||||
@@ -21,6 +23,8 @@ export class WorkspaceFlatLogicFunctionMapCacheService extends WorkspaceCachePro
|
||||
constructor(
|
||||
@InjectRepository(LogicFunctionEntity)
|
||||
private readonly logicFunctionRepository: Repository<LogicFunctionEntity>,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -28,16 +32,27 @@ export class WorkspaceFlatLogicFunctionMapCacheService extends WorkspaceCachePro
|
||||
async computeForCache(
|
||||
workspaceId: string,
|
||||
): Promise<FlatEntityMaps<FlatLogicFunction>> {
|
||||
const logicFunctions = await this.logicFunctionRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
});
|
||||
const [logicFunctions, applications] = await Promise.all([
|
||||
this.logicFunctionRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.applicationRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
}),
|
||||
]);
|
||||
|
||||
const applicationIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(applications);
|
||||
|
||||
const flatLogicFunctionMaps = createEmptyFlatEntityMaps();
|
||||
|
||||
for (const logicFunctionEntity of logicFunctions) {
|
||||
const flatLogicFunction =
|
||||
fromLogicFunctionEntityToFlatLogicFunction(logicFunctionEntity);
|
||||
const flatLogicFunction = fromLogicFunctionEntityToFlatLogicFunction({
|
||||
entity: logicFunctionEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
});
|
||||
|
||||
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
|
||||
flatEntity: flatLogicFunction,
|
||||
|
||||
+34
-7
@@ -1,14 +1,32 @@
|
||||
import { removePropertiesFromRecord } from 'twenty-shared/utils';
|
||||
import { isDefined, removePropertiesFromRecord } from 'twenty-shared/utils';
|
||||
|
||||
import { type LogicFunctionEntity } from 'src/engine/metadata-modules/logic-function/logic-function.entity';
|
||||
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 FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type';
|
||||
import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type';
|
||||
|
||||
export const fromLogicFunctionEntityToFlatLogicFunction = ({
|
||||
entity: logicFunctionEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
}: FromEntityToFlatEntityArgs<'logicFunction'>): FlatLogicFunction => {
|
||||
const applicationUniversalIdentifier =
|
||||
applicationIdToUniversalIdentifierMap.get(
|
||||
logicFunctionEntity.applicationId,
|
||||
);
|
||||
|
||||
if (!isDefined(applicationUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Application with id ${logicFunctionEntity.applicationId} not found for logicFunction ${logicFunctionEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
export const fromLogicFunctionEntityToFlatLogicFunction = (
|
||||
logicFunctionEntity: LogicFunctionEntity,
|
||||
): FlatLogicFunction => {
|
||||
const logicFunctionWithoutRelations = removePropertiesFromRecord(
|
||||
logicFunctionEntity,
|
||||
['logicFunctionLayer', 'application'],
|
||||
getMetadataEntityRelationProperties('logicFunction'),
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -16,6 +34,15 @@ export const fromLogicFunctionEntityToFlatLogicFunction = (
|
||||
createdAt: logicFunctionEntity.createdAt.toISOString(),
|
||||
updatedAt: logicFunctionEntity.updatedAt.toISOString(),
|
||||
deletedAt: logicFunctionEntity.deletedAt?.toISOString() ?? null,
|
||||
universalIdentifier: logicFunctionEntity.universalIdentifier,
|
||||
__universal: {
|
||||
universalIdentifier: logicFunctionEntity.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
publishedVersions: logicFunctionEntity.publishedVersions,
|
||||
toolInputSchema: logicFunctionEntity.toolInputSchema,
|
||||
cronTriggerSettings: logicFunctionEntity.cronTriggerSettings,
|
||||
databaseEventTriggerSettings:
|
||||
logicFunctionEntity.databaseEventTriggerSettings,
|
||||
httpRouteTriggerSettings: logicFunctionEntity.httpRouteTriggerSettings,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
+2
-1
@@ -11,7 +11,6 @@ import {
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
import { type JsonbProperty } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/jsonb-property.type';
|
||||
import { type WorkspaceEntityDuplicateCriteria } from 'src/engine/api/graphql/workspace-query-builder/types/workspace-entity-duplicate-criteria.type';
|
||||
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
@@ -21,6 +20,7 @@ import { FieldPermissionEntity } from 'src/engine/metadata-modules/object-permis
|
||||
import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permission/object-permission.entity';
|
||||
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
|
||||
import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
|
||||
import { type JsonbProperty } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/jsonb-property.type';
|
||||
|
||||
@Entity('objectMetadata')
|
||||
@Unique('IDX_OBJECT_METADATA_NAME_SINGULAR_WORKSPACE_ID_UNIQUE', [
|
||||
@@ -100,6 +100,7 @@ export class ObjectMetadataEntity
|
||||
shortcut: string | null;
|
||||
|
||||
// TODO: This should not be nullable - legacy field introduced when label identifier was nullable
|
||||
// TODO: This should be a joinColumn and we should have a FK on this too
|
||||
@Column({ nullable: true, type: 'uuid' })
|
||||
labelIdentifierFieldMetadataId: string | null;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { ApiKeyModule } from 'src/engine/core-modules/api-key/api-key.module';
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||
import { FileModule } from 'src/engine/core-modules/file/file.module';
|
||||
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
@@ -34,6 +35,7 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([
|
||||
ApplicationEntity,
|
||||
RoleEntity,
|
||||
RoleTargetEntity,
|
||||
ObjectPermissionEntity,
|
||||
|
||||
+29
-13
@@ -5,6 +5,7 @@ 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 { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatRole } from 'src/engine/metadata-modules/flat-role/types/flat-role.type';
|
||||
@@ -17,6 +18,7 @@ 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 { 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';
|
||||
|
||||
@@ -28,6 +30,8 @@ export class WorkspaceFlatRoleMapCacheService extends WorkspaceCacheProvider<
|
||||
constructor(
|
||||
@InjectRepository(RoleEntity)
|
||||
private readonly roleRepository: Repository<RoleEntity>,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
@InjectRepository(RoleTargetEntity)
|
||||
private readonly roleTargetRepository: Repository<RoleTargetEntity>,
|
||||
@InjectRepository(ObjectPermissionEntity)
|
||||
@@ -49,6 +53,7 @@ export class WorkspaceFlatRoleMapCacheService extends WorkspaceCacheProvider<
|
||||
): Promise<FlatEntityMaps<FlatRole>> {
|
||||
const [
|
||||
roles,
|
||||
applications,
|
||||
roleTargets,
|
||||
objectPermissions,
|
||||
permissionFlags,
|
||||
@@ -60,9 +65,14 @@ export class WorkspaceFlatRoleMapCacheService extends WorkspaceCacheProvider<
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.applicationRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.roleTargetRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'roleId'],
|
||||
select: ['id', 'universalIdentifier', 'roleId'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.objectPermissionRepository.find({
|
||||
@@ -82,12 +92,12 @@ export class WorkspaceFlatRoleMapCacheService extends WorkspaceCacheProvider<
|
||||
}),
|
||||
this.rowLevelPermissionPredicateRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'roleId'],
|
||||
select: ['id', 'universalIdentifier', 'roleId'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.rowLevelPermissionPredicateGroupRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'roleId'],
|
||||
select: ['id', 'universalIdentifier', 'roleId'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
]);
|
||||
@@ -128,20 +138,26 @@ export class WorkspaceFlatRoleMapCacheService extends WorkspaceCacheProvider<
|
||||
] as const
|
||||
).map(regroupEntitiesByRelatedEntityId);
|
||||
|
||||
const applicationIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(applications);
|
||||
|
||||
const flatRoleMaps = createEmptyFlatEntityMaps();
|
||||
|
||||
for (const roleEntity of roles) {
|
||||
const flatRole = fromRoleEntityToFlatRole({
|
||||
...roleEntity,
|
||||
roleTargets: roleTargetsByRoleId.get(roleEntity.id) || [],
|
||||
objectPermissions: objectPermissionsByRoleId.get(roleEntity.id) || [],
|
||||
permissionFlags: permissionFlagsByRoleId.get(roleEntity.id) || [],
|
||||
fieldPermissions: fieldPermissionsByRoleId.get(roleEntity.id) || [],
|
||||
rowLevelPermissionPredicates:
|
||||
rowLevelPermissionPredicatesByRoleId.get(roleEntity.id) || [],
|
||||
rowLevelPermissionPredicateGroups:
|
||||
rowLevelPermissionPredicateGroupsByRoleId.get(roleEntity.id) || [],
|
||||
} as RoleEntity);
|
||||
entity: {
|
||||
...roleEntity,
|
||||
roleTargets: roleTargetsByRoleId.get(roleEntity.id) || [],
|
||||
objectPermissions: objectPermissionsByRoleId.get(roleEntity.id) || [],
|
||||
permissionFlags: permissionFlagsByRoleId.get(roleEntity.id) || [],
|
||||
fieldPermissions: fieldPermissionsByRoleId.get(roleEntity.id) || [],
|
||||
rowLevelPermissionPredicates:
|
||||
rowLevelPermissionPredicatesByRoleId.get(roleEntity.id) || [],
|
||||
rowLevelPermissionPredicateGroups:
|
||||
rowLevelPermissionPredicateGroupsByRoleId.get(roleEntity.id) || [],
|
||||
},
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
});
|
||||
|
||||
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
|
||||
flatEntity: flatRole,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||
import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
|
||||
@@ -17,7 +18,7 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([WebhookEntity]),
|
||||
TypeOrmModule.forFeature([ApplicationEntity, WebhookEntity]),
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
WorkspaceMigrationModule,
|
||||
WorkspaceCacheStorageModule,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { IsNull, Repository } from 'typeorm';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
@@ -16,6 +17,7 @@ import { type CreateWebhookInput } from 'src/engine/metadata-modules/webhook/dto
|
||||
import { type UpdateWebhookInput } from 'src/engine/metadata-modules/webhook/dtos/update-webhook.input';
|
||||
import { type WebhookDTO } from 'src/engine/metadata-modules/webhook/dtos/webhook.dto';
|
||||
import { WebhookEntity } from 'src/engine/metadata-modules/webhook/entities/webhook.entity';
|
||||
import { createIdToUniversalIdentifierMap } from 'src/engine/workspace-cache/utils/create-id-to-universal-identifier-map.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';
|
||||
|
||||
@@ -24,6 +26,8 @@ export class WebhookService {
|
||||
constructor(
|
||||
@InjectRepository(WebhookEntity)
|
||||
private readonly webhookRepository: Repository<WebhookEntity>,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
|
||||
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
@@ -40,26 +44,54 @@ export class WebhookService {
|
||||
}
|
||||
|
||||
async findAll(workspaceId: string): Promise<WebhookDTO[]> {
|
||||
const webhooks = await this.webhookRepository.find({
|
||||
where: { workspaceId, deletedAt: IsNull() },
|
||||
order: { createdAt: 'ASC' },
|
||||
});
|
||||
const [webhooks, applications] = await Promise.all([
|
||||
this.webhookRepository.find({
|
||||
where: { workspaceId, deletedAt: IsNull() },
|
||||
order: { createdAt: 'ASC' },
|
||||
}),
|
||||
this.applicationRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
}),
|
||||
]);
|
||||
|
||||
const applicationIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(applications);
|
||||
|
||||
return webhooks
|
||||
.map(fromWebhookEntityToFlatWebhook)
|
||||
.map((webhookEntity) =>
|
||||
fromWebhookEntityToFlatWebhook({
|
||||
entity: webhookEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
}),
|
||||
)
|
||||
.map(fromFlatWebhookToWebhookDto);
|
||||
}
|
||||
|
||||
async findById(id: string, workspaceId: string): Promise<WebhookDTO | null> {
|
||||
const webhook = await this.webhookRepository.findOne({
|
||||
where: { id, workspaceId, deletedAt: IsNull() },
|
||||
});
|
||||
const [webhook, applications] = await Promise.all([
|
||||
this.webhookRepository.findOne({
|
||||
where: { id, workspaceId, deletedAt: IsNull() },
|
||||
}),
|
||||
this.applicationRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
}),
|
||||
]);
|
||||
|
||||
if (!isDefined(webhook)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fromFlatWebhookToWebhookDto(fromWebhookEntityToFlatWebhook(webhook));
|
||||
const applicationIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(applications);
|
||||
|
||||
return fromFlatWebhookToWebhookDto(
|
||||
fromWebhookEntityToFlatWebhook({
|
||||
entity: webhook,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async create(
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import { type AllMetadataName } from 'twenty-shared/metadata';
|
||||
|
||||
import { type MetadataManyToOneRelatedMetadataNames } from 'src/engine/metadata-modules/flat-entity/types/metadata-many-to-one-related-metadata-names.type';
|
||||
|
||||
export type EntityManyToOneIdByUniversalIdentifierMaps<
|
||||
T extends AllMetadataName,
|
||||
> = {
|
||||
[P in MetadataManyToOneRelatedMetadataNames<T> as `${P}IdToUniversalIdentifierMap`]: Map<
|
||||
string,
|
||||
string
|
||||
>;
|
||||
} & {
|
||||
applicationIdToUniversalIdentifierMap: Map<string, string>;
|
||||
};
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { type ExtractEntityOneToManyEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-one-to-many-entity-relation-properties.type';
|
||||
import { type RegroupedEntity } from 'src/engine/workspace-cache/utils/regroup-entities-by-related-entity-id';
|
||||
import { type SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
|
||||
|
||||
export type EntityWithRegroupedOneToManyRelations<
|
||||
TEntity extends SyncableEntity,
|
||||
> = Omit<
|
||||
TEntity,
|
||||
ExtractEntityOneToManyEntityRelationProperties<TEntity, SyncableEntity>
|
||||
> & {
|
||||
[P in ExtractEntityOneToManyEntityRelationProperties<
|
||||
TEntity,
|
||||
SyncableEntity
|
||||
>]: RegroupedEntity[];
|
||||
};
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { type AllMetadataName } from 'twenty-shared/metadata';
|
||||
|
||||
import { type MetadataEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-entity.type';
|
||||
import { type EntityManyToOneIdByUniversalIdentifierMaps } from 'src/engine/workspace-cache/types/entity-many-to-one-id-by-universal-identifier-maps.type';
|
||||
import { type EntityWithRegroupedOneToManyRelations } from 'src/engine/workspace-cache/types/entity-with-regrouped-one-to-many-relations.type';
|
||||
|
||||
export type FromEntityToFlatEntityArgs<T extends AllMetadataName> = {
|
||||
entity: EntityWithRegroupedOneToManyRelations<MetadataEntity<T>>;
|
||||
} & EntityManyToOneIdByUniversalIdentifierMaps<T>;
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
export const createIdToUniversalIdentifierMap = (
|
||||
entities: { id: string; universalIdentifier: string }[],
|
||||
): Map<string, string> => {
|
||||
const map = new Map<string, string>();
|
||||
|
||||
for (const entity of entities) {
|
||||
map.set(entity.id, entity.universalIdentifier);
|
||||
}
|
||||
|
||||
return map;
|
||||
};
|
||||
+7
-4
@@ -4,6 +4,8 @@ import { type AllMetadataName } from 'twenty-shared/metadata';
|
||||
import { type MetadataEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-entity.type';
|
||||
import { type MetadataManyToOneJoinColumn } from 'src/engine/metadata-modules/flat-entity/types/metadata-many-to-one-join-column.type';
|
||||
|
||||
export type RegroupedEntity = { id: string; universalIdentifier: string };
|
||||
|
||||
export type RegroupEntitiesByRelatedEntityIdArgs<T extends AllMetadataName> =
|
||||
MetadataManyToOneJoinColumn<T> extends never
|
||||
? never
|
||||
@@ -15,7 +17,7 @@ export const regroupEntitiesByRelatedEntityId = <T extends AllMetadataName>({
|
||||
entities,
|
||||
foreignKey,
|
||||
}: RegroupEntitiesByRelatedEntityIdArgs<T>) => {
|
||||
const entitiesByRelatedEntityId = new Map<string, { id: string }[]>();
|
||||
const entitiesByRelatedEntityId = new Map<string, RegroupedEntity[]>();
|
||||
|
||||
for (const entity of entities) {
|
||||
const currentRelatedEntityId = entity[
|
||||
@@ -30,9 +32,10 @@ export const regroupEntitiesByRelatedEntityId = <T extends AllMetadataName>({
|
||||
entitiesByRelatedEntityId.set(currentRelatedEntityId, []);
|
||||
}
|
||||
|
||||
entitiesByRelatedEntityId
|
||||
.get(currentRelatedEntityId)!
|
||||
.push({ id: entity.id });
|
||||
entitiesByRelatedEntityId.get(currentRelatedEntityId)!.push({
|
||||
id: entity.id,
|
||||
universalIdentifier: entity.universalIdentifier,
|
||||
});
|
||||
}
|
||||
|
||||
return entitiesByRelatedEntityId;
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
import { type AllMetadataName } from 'twenty-shared/metadata';
|
||||
|
||||
import { type MetadataEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-entity.type';
|
||||
import { type ExtractJsonbProperties } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/extract-jsonb-properties.type';
|
||||
|
||||
export const ALL_JSONB_PROPERTIES_BY_METADATA_NAME = {
|
||||
fieldMetadata: {
|
||||
defaultValue: 'defaultValue',
|
||||
standardOverrides: 'standardOverrides',
|
||||
options: 'options',
|
||||
settings: 'settings',
|
||||
},
|
||||
objectMetadata: {
|
||||
standardOverrides: 'standardOverrides',
|
||||
duplicateCriteria: 'duplicateCriteria',
|
||||
},
|
||||
view: {},
|
||||
viewField: {},
|
||||
viewGroup: {},
|
||||
viewFilter: { value: 'value' },
|
||||
viewFilterGroup: {},
|
||||
index: {},
|
||||
role: {},
|
||||
roleTarget: {},
|
||||
rowLevelPermissionPredicate: { value: 'value' },
|
||||
rowLevelPermissionPredicateGroup: {},
|
||||
logicFunction: {
|
||||
cronTriggerSettings: 'cronTriggerSettings',
|
||||
databaseEventTriggerSettings: 'databaseEventTriggerSettings',
|
||||
httpRouteTriggerSettings: 'httpRouteTriggerSettings',
|
||||
publishedVersions: 'publishedVersions',
|
||||
toolInputSchema: 'toolInputSchema',
|
||||
},
|
||||
webhook: {},
|
||||
agent: {
|
||||
responseFormat: 'responseFormat',
|
||||
modelConfiguration: 'modelConfiguration',
|
||||
},
|
||||
skill: {},
|
||||
pageLayout: {},
|
||||
pageLayoutTab: {},
|
||||
pageLayoutWidget: {
|
||||
gridPosition: 'gridPosition',
|
||||
configuration: 'configuration',
|
||||
},
|
||||
commandMenuItem: {},
|
||||
navigationMenuItem: {},
|
||||
frontComponent: {},
|
||||
} as const satisfies {
|
||||
[P in AllMetadataName]: {
|
||||
[K in ExtractJsonbProperties<MetadataEntity<P>>]: K;
|
||||
};
|
||||
};
|
||||
|
||||
export type AllJsonbPropertiesForMetadataName<T extends AllMetadataName> =
|
||||
keyof (typeof ALL_JSONB_PROPERTIES_BY_METADATA_NAME)[T];
|
||||
+100
-17
@@ -18,24 +18,24 @@ type BrandedObjectWithRelation = JsonbProperty<ObjectWithRelation>;
|
||||
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars
|
||||
type ObjectAssertions = [
|
||||
// Object with SerializedRelation: Id suffix renamed to UniversalIdentifier
|
||||
// Object with SerializedRelation: Id suffix renamed to UniversalIdentifier, value becomes nullable
|
||||
Expect<
|
||||
Equal<
|
||||
FormatRecordSerializedRelationProperties<ObjectWithRelation>,
|
||||
{
|
||||
name: string;
|
||||
targetFieldMetadataUniversalIdentifier: SerializedRelation;
|
||||
targetFieldMetadataUniversalIdentifier: SerializedRelation | null;
|
||||
}
|
||||
>
|
||||
>,
|
||||
|
||||
// Branded object with SerializedRelation: renames property, preserves brand key
|
||||
// Branded object with SerializedRelation: renames property, preserves brand key, value becomes nullable
|
||||
Expect<
|
||||
Equal<
|
||||
FormatRecordSerializedRelationProperties<BrandedObjectWithRelation>,
|
||||
{
|
||||
name: string;
|
||||
targetFieldMetadataUniversalIdentifier: SerializedRelation;
|
||||
targetFieldMetadataUniversalIdentifier: SerializedRelation | null;
|
||||
__JsonbPropertyBrand__?: undefined;
|
||||
}
|
||||
>
|
||||
@@ -56,6 +56,13 @@ type PrimitiveAssertions = [
|
||||
Expect<Equal<FormatRecordSerializedRelationProperties<string>, string>>,
|
||||
Expect<Equal<FormatRecordSerializedRelationProperties<number>, number>>,
|
||||
Expect<Equal<FormatRecordSerializedRelationProperties<null>, null>>,
|
||||
// SerializedRelation becomes nullable
|
||||
Expect<
|
||||
Equal<
|
||||
FormatRecordSerializedRelationProperties<SerializedRelation>,
|
||||
SerializedRelation | null
|
||||
>
|
||||
>,
|
||||
];
|
||||
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars
|
||||
@@ -66,7 +73,7 @@ type ArrayAssertions = [
|
||||
FormatRecordSerializedRelationProperties<ObjectWithRelation[]>,
|
||||
{
|
||||
name: string;
|
||||
targetFieldMetadataUniversalIdentifier: SerializedRelation;
|
||||
targetFieldMetadataUniversalIdentifier: SerializedRelation | null;
|
||||
}[]
|
||||
>
|
||||
>,
|
||||
@@ -88,7 +95,7 @@ type ArrayAssertions = [
|
||||
FormatRecordSerializedRelationProperties<ObjectWithRelation[][]>,
|
||||
{
|
||||
name: string;
|
||||
targetFieldMetadataUniversalIdentifier: SerializedRelation;
|
||||
targetFieldMetadataUniversalIdentifier: SerializedRelation | null;
|
||||
}[][]
|
||||
>
|
||||
>,
|
||||
@@ -102,7 +109,7 @@ type UnionAssertions = [
|
||||
FormatRecordSerializedRelationProperties<ObjectWithRelation | null>,
|
||||
{
|
||||
name: string;
|
||||
targetFieldMetadataUniversalIdentifier: SerializedRelation;
|
||||
targetFieldMetadataUniversalIdentifier: SerializedRelation | null;
|
||||
} | null
|
||||
>
|
||||
>,
|
||||
@@ -113,7 +120,7 @@ type UnionAssertions = [
|
||||
FormatRecordSerializedRelationProperties<(ObjectWithRelation | null)[]>,
|
||||
({
|
||||
name: string;
|
||||
targetFieldMetadataUniversalIdentifier: SerializedRelation;
|
||||
targetFieldMetadataUniversalIdentifier: SerializedRelation | null;
|
||||
} | null)[]
|
||||
>
|
||||
>,
|
||||
@@ -128,14 +135,14 @@ type MultipleRelationsObject = {
|
||||
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars
|
||||
type MultipleRelationsAssertions = [
|
||||
// Multiple SerializedRelation properties: all get renamed
|
||||
// Multiple SerializedRelation properties: all get renamed and become nullable
|
||||
Expect<
|
||||
Equal<
|
||||
FormatRecordSerializedRelationProperties<MultipleRelationsObject>,
|
||||
{
|
||||
name: string;
|
||||
sourceFieldUniversalIdentifier: SerializedRelation;
|
||||
targetFieldUniversalIdentifier: SerializedRelation;
|
||||
sourceFieldUniversalIdentifier: SerializedRelation | null;
|
||||
targetFieldUniversalIdentifier: SerializedRelation | null;
|
||||
regularId: string;
|
||||
}
|
||||
>
|
||||
@@ -190,7 +197,7 @@ type NestedObjectAssertions = [
|
||||
{
|
||||
name: string;
|
||||
nested: {
|
||||
fieldMetadataUniversalIdentifier: SerializedRelation;
|
||||
fieldMetadataUniversalIdentifier: SerializedRelation | null;
|
||||
};
|
||||
}
|
||||
>
|
||||
@@ -204,7 +211,7 @@ type NestedObjectAssertions = [
|
||||
name: string;
|
||||
level1: {
|
||||
level2: {
|
||||
targetUniversalIdentifier: SerializedRelation;
|
||||
targetUniversalIdentifier: SerializedRelation | null;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -217,9 +224,9 @@ type NestedObjectAssertions = [
|
||||
FormatRecordSerializedRelationProperties<MixedNestedObject>,
|
||||
{
|
||||
name: string;
|
||||
directRelationUniversalIdentifier: SerializedRelation;
|
||||
directRelationUniversalIdentifier: SerializedRelation | null;
|
||||
nested: {
|
||||
nestedRelationUniversalIdentifier: SerializedRelation;
|
||||
nestedRelationUniversalIdentifier: SerializedRelation | null;
|
||||
plainField: string;
|
||||
};
|
||||
}
|
||||
@@ -233,7 +240,7 @@ type NestedObjectAssertions = [
|
||||
{
|
||||
name: string;
|
||||
nested: {
|
||||
relationUniversalIdentifier: SerializedRelation;
|
||||
relationUniversalIdentifier: SerializedRelation | null;
|
||||
} | null;
|
||||
}
|
||||
>
|
||||
@@ -246,9 +253,85 @@ type NestedObjectAssertions = [
|
||||
{
|
||||
name: string;
|
||||
items: {
|
||||
itemRelationUniversalIdentifier: SerializedRelation;
|
||||
itemRelationUniversalIdentifier: SerializedRelation | null;
|
||||
}[];
|
||||
}
|
||||
>
|
||||
>,
|
||||
];
|
||||
|
||||
// JSON Schema-like structures with Record<string, X> properties
|
||||
// These should NOT have their 'properties' key renamed
|
||||
type JsonSchemaLikeObject = {
|
||||
type: 'object';
|
||||
properties: Record<string, { type: string; description?: string }>;
|
||||
required?: string[];
|
||||
};
|
||||
|
||||
type JsonSchemaUnion =
|
||||
| { type: 'text' }
|
||||
| {
|
||||
type: 'json';
|
||||
schema: JsonSchemaLikeObject;
|
||||
};
|
||||
|
||||
type BrandedJsonSchemaUnion = JsonbProperty<JsonSchemaUnion>;
|
||||
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars
|
||||
type RecordPropertyAssertions = [
|
||||
// Object with 'properties' key containing Record<string, X>:
|
||||
// The 'properties' key should NOT be renamed to 'propertiesUniversalIdentifier'
|
||||
Expect<
|
||||
Equal<
|
||||
FormatRecordSerializedRelationProperties<JsonSchemaLikeObject>,
|
||||
{
|
||||
type: 'object';
|
||||
properties: Record<string, { type: string; description?: string }>;
|
||||
required?: string[];
|
||||
}
|
||||
>
|
||||
>,
|
||||
|
||||
// Union type with JSON schema: properties inside schema should stay as 'properties'
|
||||
Expect<
|
||||
Equal<
|
||||
FormatRecordSerializedRelationProperties<JsonSchemaUnion>,
|
||||
| { type: 'text' }
|
||||
| {
|
||||
type: 'json';
|
||||
schema: {
|
||||
type: 'object';
|
||||
properties: Record<string, { type: string; description?: string }>;
|
||||
required?: string[];
|
||||
};
|
||||
}
|
||||
>
|
||||
>,
|
||||
|
||||
// Branded JSON schema union: should preserve structure, only add brand
|
||||
Expect<
|
||||
Equal<
|
||||
FormatRecordSerializedRelationProperties<BrandedJsonSchemaUnion>,
|
||||
| { type: 'text'; __JsonbPropertyBrand__?: undefined }
|
||||
| {
|
||||
type: 'json';
|
||||
schema: {
|
||||
type: 'object';
|
||||
properties: Record<string, { type: string; description?: string }>;
|
||||
required?: string[];
|
||||
};
|
||||
__JsonbPropertyBrand__?: undefined;
|
||||
}
|
||||
>
|
||||
>,
|
||||
|
||||
// Plain Record<string, X> should pass through unchanged
|
||||
Expect<
|
||||
Equal<
|
||||
FormatRecordSerializedRelationProperties<
|
||||
Record<string, { value: number }>
|
||||
>,
|
||||
Record<string, { value: number }>
|
||||
>
|
||||
>,
|
||||
];
|
||||
|
||||
+8
-2
@@ -98,7 +98,10 @@ type NarrowedExpectedResult = {
|
||||
relationType: RelationType;
|
||||
onDelete?: RelationOnDeleteAction | undefined;
|
||||
joinColumnName?: string | null | undefined;
|
||||
junctionTargetFieldUniversalIdentifier?: SerializedRelation | undefined;
|
||||
junctionTargetFieldUniversalIdentifier?:
|
||||
| SerializedRelation
|
||||
| null
|
||||
| undefined;
|
||||
__JsonbPropertyBrand__?: undefined;
|
||||
};
|
||||
|
||||
@@ -111,7 +114,10 @@ type SettingsExpectedResult =
|
||||
relationType: RelationType;
|
||||
onDelete?: RelationOnDeleteAction | undefined;
|
||||
joinColumnName?: string | null | undefined;
|
||||
junctionTargetFieldUniversalIdentifier?: SerializedRelation | undefined;
|
||||
junctionTargetFieldUniversalIdentifier?:
|
||||
| SerializedRelation
|
||||
| null
|
||||
| undefined;
|
||||
__JsonbPropertyBrand__?: undefined;
|
||||
}
|
||||
| {
|
||||
|
||||
+28
-5
@@ -1,17 +1,40 @@
|
||||
import { type ExtractSerializedRelationProperties } from 'twenty-shared/types';
|
||||
import {
|
||||
type ExtractSerializedRelationProperties,
|
||||
type IsSerializedRelation,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import { type RemoveSuffix } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/remove-suffix.type';
|
||||
|
||||
// Determines if a property should be transformed to a universal identifier
|
||||
// A property is transformed only if:
|
||||
// 1. It matches ExtractSerializedRelationProperties (has the serialized relation brand)
|
||||
// 2. Its value does NOT have a string index signature (not a Record<string, X>)
|
||||
type ShouldTransformToUniversalIdentifier<T, P extends keyof T> = [P] extends [
|
||||
ExtractSerializedRelationProperties<T>,
|
||||
]
|
||||
? string extends keyof NonNullable<T[P]>
|
||||
? false
|
||||
: true
|
||||
: false;
|
||||
|
||||
export type FormatRecordSerializedRelationProperties<T> = T extends unknown
|
||||
? T extends (infer U)[]
|
||||
? FormatRecordSerializedRelationProperties<U>[]
|
||||
: T extends string
|
||||
? T
|
||||
? // By definition we assume that any SerializedRelation are not enforced at pg scope through an FK
|
||||
// Which mean that SerializedRelation might resolve to non-existent entities ( null )
|
||||
IsSerializedRelation<T> extends true
|
||||
? T | null
|
||||
: T
|
||||
: T extends object
|
||||
? {
|
||||
[P in keyof T as P extends ExtractSerializedRelationProperties<T> &
|
||||
string
|
||||
? `${RemoveSuffix<P, 'Id'>}UniversalIdentifier`
|
||||
[P in keyof T as ShouldTransformToUniversalIdentifier<
|
||||
T,
|
||||
P
|
||||
> extends true
|
||||
? P extends string
|
||||
? `${RemoveSuffix<P, 'Id'>}UniversalIdentifier`
|
||||
: P
|
||||
: P]: FormatRecordSerializedRelationProperties<T[P]>;
|
||||
}
|
||||
: T
|
||||
|
||||
+29
-21
@@ -1,16 +1,38 @@
|
||||
import { type AllMetadataName } from 'twenty-shared/metadata';
|
||||
|
||||
import { type AddSuffixToEntityManyToOneProperties } from 'src/engine/metadata-modules/flat-entity/types/add-suffix-to-entity-many-to-one-properties.type';
|
||||
import { type AddSuffixToEntityOneToManyProperties } from 'src/engine/metadata-modules/flat-entity/types/add-suffix-to-entity-one-to-many-properties.type';
|
||||
import { type CastRecordTypeOrmDatePropertiesToString } from 'src/engine/metadata-modules/flat-entity/types/cast-record-typeorm-date-properties-to-string.type';
|
||||
import { type ExtractEntityOneToManyEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-one-to-many-entity-relation-properties.type';
|
||||
import { type ExtractEntityRelatedEntityProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-related-entity-properties.type';
|
||||
import { type FromMetadataEntityToMetadataName } from 'src/engine/metadata-modules/flat-entity/types/from-metadata-entity-to-metadata-name.type';
|
||||
import { type MetadataManyToOneJoinColumn } from 'src/engine/metadata-modules/flat-entity/types/metadata-many-to-one-join-column.type';
|
||||
import { type SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
|
||||
import { type ExtractJsonbProperties } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/extract-jsonb-properties.type';
|
||||
import { type AllJsonbPropertiesForMetadataName } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-jsonb-properties-by-metadata-name.constant';
|
||||
import { type FormatRecordSerializedRelationProperties } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/format-record-serialized-relation-properties.type';
|
||||
import { type RemoveSuffix } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/remove-suffix.type';
|
||||
|
||||
// TODO Handle universal settings
|
||||
export type UniversalSyncableFlatEntity = Omit<
|
||||
SyncableEntity,
|
||||
'application' | 'applicationId' | 'workspace' | 'workspaceId'
|
||||
> & {
|
||||
applicationUniversalIdentifier: string;
|
||||
};
|
||||
|
||||
export type UniversalFlatEntityExtraProperties<
|
||||
TEntity extends SyncableEntity,
|
||||
TMetadataName extends
|
||||
AllMetadataName = FromMetadataEntityToMetadataName<TEntity>,
|
||||
> = AddSuffixToEntityOneToManyProperties<TEntity, 'universalIdentifiers'> &
|
||||
AddSuffixToEntityManyToOneProperties<
|
||||
TEntity,
|
||||
TMetadataName,
|
||||
'universalIdentifier'
|
||||
> & {
|
||||
applicationUniversalIdentifier: string;
|
||||
} & {
|
||||
[P in AllJsonbPropertiesForMetadataName<TMetadataName> &
|
||||
keyof TEntity]: FormatRecordSerializedRelationProperties<TEntity[P]>;
|
||||
};
|
||||
|
||||
export type UniversalFlatEntityFrom<
|
||||
TEntity extends SyncableEntity,
|
||||
TMetadataName extends
|
||||
@@ -24,21 +46,7 @@ export type UniversalFlatEntityFrom<
|
||||
| ExtractEntityRelatedEntityProperties<TEntity>
|
||||
| Extract<MetadataManyToOneJoinColumn<TMetadataName>, keyof TEntity>
|
||||
| keyof CastRecordTypeOrmDatePropertiesToString<TEntity>
|
||||
| ExtractJsonbProperties<TEntity>
|
||||
| AllJsonbPropertiesForMetadataName<TMetadataName>
|
||||
> &
|
||||
CastRecordTypeOrmDatePropertiesToString<TEntity> & {
|
||||
[P in ExtractEntityOneToManyEntityRelationProperties<
|
||||
TEntity,
|
||||
SyncableEntity
|
||||
> &
|
||||
string as `${RemoveSuffix<P, 's'>}UniversalIdentifiers`]: string[];
|
||||
} & {
|
||||
[P in Extract<MetadataManyToOneJoinColumn<TMetadataName>, keyof TEntity> &
|
||||
string as `${RemoveSuffix<P, 'Id'>}UniversalIdentifier`]: TEntity[P];
|
||||
} & {
|
||||
applicationUniversalIdentifier: string;
|
||||
} & {
|
||||
[P in ExtractJsonbProperties<TEntity>]: FormatRecordSerializedRelationProperties<
|
||||
TEntity[P]
|
||||
>;
|
||||
};
|
||||
CastRecordTypeOrmDatePropertiesToString<TEntity> &
|
||||
UniversalFlatEntityExtraProperties<TEntity, TMetadataName>;
|
||||
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import { type RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
import { type UniversalFlatEntityFrom } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-from.type';
|
||||
|
||||
export type UniversalFlatRole = UniversalFlatEntityFrom<RoleEntity, 'role'>;
|
||||
+5
-1
@@ -1,10 +1,11 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect a created entity 1`] = `
|
||||
{
|
||||
"createdFlatEntityMaps": {
|
||||
"byId": {
|
||||
"field-id-1": {
|
||||
"__universal": {},
|
||||
"applicationId": "application-id-1",
|
||||
"calendarViewIds": [],
|
||||
"createdAt": "2024-01-01T00:00:00.000Z",
|
||||
@@ -69,6 +70,7 @@ exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect a dele
|
||||
"deletedFlatEntityMaps": {
|
||||
"byId": {
|
||||
"field-id-1": {
|
||||
"__universal": {},
|
||||
"applicationId": "application-id-1",
|
||||
"calendarViewIds": [],
|
||||
"createdAt": "2024-01-01T00:00:00.000Z",
|
||||
@@ -151,6 +153,7 @@ exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect create
|
||||
"createdFlatEntityMaps": {
|
||||
"byId": {
|
||||
"field-id-3": {
|
||||
"__universal": {},
|
||||
"applicationId": "application-id-1",
|
||||
"calendarViewIds": [],
|
||||
"createdAt": "2024-01-01T00:00:00.000Z",
|
||||
@@ -197,6 +200,7 @@ exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect create
|
||||
"deletedFlatEntityMaps": {
|
||||
"byId": {
|
||||
"field-id-2": {
|
||||
"__universal": {},
|
||||
"applicationId": "application-id-1",
|
||||
"calendarViewIds": [],
|
||||
"createdAt": "2024-01-01T00:00:00.000Z",
|
||||
|
||||
+1
@@ -18,5 +18,6 @@ export type UpdateFieldAction =
|
||||
|
||||
export type DeleteFieldAction =
|
||||
BaseDeleteWorkspaceMigrationAction<'fieldMetadata'> & {
|
||||
// Remove this
|
||||
objectMetadataId: string;
|
||||
};
|
||||
|
||||
+13
-15
@@ -1,7 +1,5 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { assertIsDefinedOrThrow } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
|
||||
import { type WorkspacePreQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
|
||||
import { type DeleteOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
@@ -11,8 +9,8 @@ import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner
|
||||
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
|
||||
import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception';
|
||||
import { fromObjectMetadataEntityToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-entity-to-flat-object-metadata.util';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
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 { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
@@ -24,8 +22,7 @@ export class ConnectedAccountDeleteOnePreQueryHook
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||
@InjectRepository(ObjectMetadataEntity)
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
) {}
|
||||
|
||||
async execute(
|
||||
@@ -55,17 +52,18 @@ export class ConnectedAccountDeleteOnePreQueryHook
|
||||
authContext as WorkspaceAuthContext,
|
||||
);
|
||||
|
||||
const objectMetadataEntity =
|
||||
await this.objectMetadataRepository.findOneOrFail({
|
||||
where: {
|
||||
nameSingular: 'messageChannel',
|
||||
const { flatObjectMetadataMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId: workspace.id,
|
||||
flatMapsKeys: ['flatObjectMetadataMaps'],
|
||||
},
|
||||
relations: ['fields', 'indexMetadatas', 'views'],
|
||||
});
|
||||
);
|
||||
|
||||
const flatObjectMetadata =
|
||||
fromObjectMetadataEntityToFlatObjectMetadata(objectMetadataEntity);
|
||||
const flatObjectMetadata = findFlatEntityByUniversalIdentifierOrThrow({
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
universalIdentifier: STANDARD_OBJECT_IDS.messageChannel,
|
||||
});
|
||||
|
||||
// TODO: handle cascade events for delete
|
||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
||||
|
||||
+5
-1
@@ -2,11 +2,15 @@ import { Module } from '@nestjs/common';
|
||||
|
||||
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
||||
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { ConnectedAccountDeleteOnePreQueryHook } from 'src/modules/connected-account/query-hooks/connected-account-delete-one.pre-query.hook';
|
||||
|
||||
@Module({
|
||||
imports: [NestjsQueryTypeOrmModule.forFeature([ObjectMetadataEntity])],
|
||||
imports: [
|
||||
NestjsQueryTypeOrmModule.forFeature([ObjectMetadataEntity]),
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
],
|
||||
providers: [ConnectedAccountDeleteOnePreQueryHook],
|
||||
})
|
||||
export class ConnectedAccountQueryHookModule {}
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import { type SERIALIZED_RELATION_BRAND } from './SerializedRelation.type';
|
||||
|
||||
type HasSerializedRelationPropertyBrand<T> =
|
||||
typeof SERIALIZED_RELATION_BRAND extends keyof T ? true : false;
|
||||
import { IsSerializedRelation } from "@/types/IsSerializedRelation.type";
|
||||
|
||||
export type ExtractSerializedRelationProperties<T> = T extends unknown
|
||||
? T extends object
|
||||
? {
|
||||
[P in keyof T]-?: [NonNullable<T[P]>] extends [never]
|
||||
? never
|
||||
: HasSerializedRelationPropertyBrand<NonNullable<T[P]>> extends true
|
||||
: IsSerializedRelation<NonNullable<T[P]>> extends true
|
||||
? P
|
||||
: never;
|
||||
}[keyof T]
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { SERIALIZED_RELATION_BRAND } from '@/types/SerializedRelation.type';
|
||||
|
||||
export type IsSerializedRelation<T> =
|
||||
typeof SERIALIZED_RELATION_BRAND extends keyof T ? true : false;
|
||||
+16
@@ -66,4 +66,20 @@ type Assertions = [
|
||||
'rel'
|
||||
>
|
||||
>,
|
||||
|
||||
// Types with string index signatures should return never
|
||||
// (string index signatures shouldn't be mistaken for the brand)
|
||||
Expect<
|
||||
Equal<ExtractSerializedRelationProperties<Record<string, string>>, never>
|
||||
>,
|
||||
Expect<
|
||||
Equal<
|
||||
ExtractSerializedRelationProperties<
|
||||
Record<string, { type: string; description?: string }>
|
||||
>,
|
||||
never
|
||||
>
|
||||
>,
|
||||
];
|
||||
|
||||
|
||||
|
||||
@@ -107,6 +107,7 @@ export type { FromTo } from './FromToType';
|
||||
export { HTTPMethod } from './HttpMethod';
|
||||
export type { IsEmptyRecord } from './IsEmptyRecord.type';
|
||||
export type { IsExactly } from './IsExactly';
|
||||
export type { IsSerializedRelation } from './IsSerializedRelation.type';
|
||||
export type { LogicFunctionEvent } from './LogicFunctionEvent';
|
||||
export { MessageParticipantRole } from './MessageParticipantRole';
|
||||
export type { ModifiedProperties } from './ModifiedProperties';
|
||||
|
||||
Reference in New Issue
Block a user