perf: use cache for view entity lookups (#23384)
## Context View child mutation guards resolve a parent view before checking access. The lookup service queried PostgreSQL for a single `viewId`, even though the same relationship already exists in the workspace flat-map cache. With 15 guards using this service, each guarded mutation could add an unnecessary database round trip. ## What changed - Replace the five workspace-scoped repositories with `WorkspaceManyOrAllFlatEntityMapsCacheService` - Load only the flat map matching the requested child kind - Resolve view fields, filters, filter groups, groups, and sorts by ID - Preserve the existing `null` behavior for missing entities Each lookup is keyed by entity ID, no workspace-wide filtering is introduced. ## Expected impact On a warm workspace cache, permission guards resolve the parent `viewId` without querying PostgreSQL. Cold caches retain the normal workspace cache recomputation behavior. ## Validation - Typecheck reports no errors in the changed file - Existing lookup semantics are preserved for all supported entity kinds and missing IDs <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23384?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
+54
-50
@@ -1,27 +1,13 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
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 { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { type ViewChildEntityKind } from 'src/engine/metadata-modules/view-permissions/types/view-permissions.types';
|
||||
import { ViewSortEntity } from 'src/engine/metadata-modules/view-sort/entities/view-sort.entity';
|
||||
import { InjectWorkspaceScopedRepository } from 'src/engine/twenty-orm/workspace-scoped-repository/inject-workspace-scoped-repository.decorator';
|
||||
import { WorkspaceScopedRepository } from 'src/engine/twenty-orm/workspace-scoped-repository/workspace-scoped-repository';
|
||||
|
||||
@Injectable()
|
||||
export class ViewEntityLookupService {
|
||||
constructor(
|
||||
@InjectWorkspaceScopedRepository(ViewFieldEntity)
|
||||
private readonly viewFieldRepository: WorkspaceScopedRepository<ViewFieldEntity>,
|
||||
@InjectWorkspaceScopedRepository(ViewFilterEntity)
|
||||
private readonly viewFilterRepository: WorkspaceScopedRepository<ViewFilterEntity>,
|
||||
@InjectWorkspaceScopedRepository(ViewFilterGroupEntity)
|
||||
private readonly viewFilterGroupRepository: WorkspaceScopedRepository<ViewFilterGroupEntity>,
|
||||
@InjectWorkspaceScopedRepository(ViewGroupEntity)
|
||||
private readonly viewGroupRepository: WorkspaceScopedRepository<ViewGroupEntity>,
|
||||
@InjectWorkspaceScopedRepository(ViewSortEntity)
|
||||
private readonly viewSortRepository: WorkspaceScopedRepository<ViewSortEntity>,
|
||||
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
) {}
|
||||
|
||||
async findViewIdByEntityIdAndKind(
|
||||
@@ -31,58 +17,76 @@ export class ViewEntityLookupService {
|
||||
): Promise<string | null> {
|
||||
switch (kind) {
|
||||
case 'viewField': {
|
||||
const row = await this.viewFieldRepository.findOne(workspaceId, {
|
||||
where: { id: entityId },
|
||||
select: ['viewId'],
|
||||
});
|
||||
const { flatViewFieldMaps } =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{ workspaceId, flatMapsKeys: ['flatViewFieldMaps'] },
|
||||
);
|
||||
|
||||
if (row) return row.viewId;
|
||||
break;
|
||||
return (
|
||||
findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: entityId,
|
||||
flatEntityMaps: flatViewFieldMaps,
|
||||
})?.viewId ?? null
|
||||
);
|
||||
}
|
||||
|
||||
case 'viewFilter': {
|
||||
const row = await this.viewFilterRepository.findOne(workspaceId, {
|
||||
where: { id: entityId },
|
||||
select: ['viewId'],
|
||||
});
|
||||
const { flatViewFilterMaps } =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{ workspaceId, flatMapsKeys: ['flatViewFilterMaps'] },
|
||||
);
|
||||
|
||||
if (row) return row.viewId;
|
||||
break;
|
||||
return (
|
||||
findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: entityId,
|
||||
flatEntityMaps: flatViewFilterMaps,
|
||||
})?.viewId ?? null
|
||||
);
|
||||
}
|
||||
|
||||
case 'viewFilterGroup': {
|
||||
const row = await this.viewFilterGroupRepository.findOne(workspaceId, {
|
||||
where: { id: entityId },
|
||||
select: ['viewId'],
|
||||
});
|
||||
const { flatViewFilterGroupMaps } =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{ workspaceId, flatMapsKeys: ['flatViewFilterGroupMaps'] },
|
||||
);
|
||||
|
||||
if (row) return row.viewId;
|
||||
break;
|
||||
return (
|
||||
findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: entityId,
|
||||
flatEntityMaps: flatViewFilterGroupMaps,
|
||||
})?.viewId ?? null
|
||||
);
|
||||
}
|
||||
|
||||
case 'viewGroup': {
|
||||
const row = await this.viewGroupRepository.findOne(workspaceId, {
|
||||
where: { id: entityId },
|
||||
select: ['viewId'],
|
||||
});
|
||||
const { flatViewGroupMaps } =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{ workspaceId, flatMapsKeys: ['flatViewGroupMaps'] },
|
||||
);
|
||||
|
||||
if (row) return row.viewId;
|
||||
break;
|
||||
return (
|
||||
findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: entityId,
|
||||
flatEntityMaps: flatViewGroupMaps,
|
||||
})?.viewId ?? null
|
||||
);
|
||||
}
|
||||
|
||||
case 'viewSort': {
|
||||
const row = await this.viewSortRepository.findOne(workspaceId, {
|
||||
where: { id: entityId },
|
||||
select: ['viewId'],
|
||||
});
|
||||
const { flatViewSortMaps } =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{ workspaceId, flatMapsKeys: ['flatViewSortMaps'] },
|
||||
);
|
||||
|
||||
if (row) return row.viewId;
|
||||
break;
|
||||
return (
|
||||
findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: entityId,
|
||||
flatEntityMaps: flatViewSortMaps,
|
||||
})?.viewId ?? null
|
||||
);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user