Fix view performances (#14209)
This commit is contained in:
@@ -1,18 +1,30 @@
|
||||
import { type TypeOrmModuleOptions } from '@nestjs/typeorm';
|
||||
|
||||
import { config } from 'dotenv';
|
||||
import { DataSource, type DataSourceOptions } from 'typeorm';
|
||||
import { DataSource, type DataSourceOptions, type LogLevel } from 'typeorm';
|
||||
config({
|
||||
path: process.env.NODE_ENV === 'test' ? '.env.test' : '.env',
|
||||
override: true,
|
||||
});
|
||||
|
||||
const getLoggingConfig = (): LogLevel[] => {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
return ['query', 'error'];
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
return [];
|
||||
}
|
||||
|
||||
return ['error'];
|
||||
};
|
||||
|
||||
const isJest = process.argv.some((arg) => arg.includes('jest'));
|
||||
|
||||
export const typeORMCoreModuleOptions: TypeOrmModuleOptions = {
|
||||
url: process.env.PG_DATABASE_URL,
|
||||
type: 'postgres',
|
||||
logging: ['error'],
|
||||
logging: getLoggingConfig(),
|
||||
schema: 'core',
|
||||
entities:
|
||||
process.env.IS_BILLING_ENABLED === 'true'
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import { type MigrationInterface, type QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddViewIndexes1756732238898 implements MigrationInterface {
|
||||
name = 'AddViewIndexes1756732238898';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_VIEW_FILTER_VIEW_ID" ON "core"."viewFilter" ("viewId") WHERE "deletedAt" IS NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_VIEW_FILTER_GROUP_VIEW_ID" ON "core"."viewFilterGroup" ("viewId") WHERE "deletedAt" IS NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_VIEW_GROUP_VIEW_ID" ON "core"."viewGroup" ("viewId") WHERE "deletedAt" IS NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_VIEW_SORT_VIEW_ID" ON "core"."viewSort" ("viewId") WHERE "deletedAt" IS NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_VIEW_FIELD_VIEW_ID" ON "core"."viewField" ("viewId") WHERE "deletedAt" IS NULL`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP INDEX "core"."IDX_VIEW_FIELD_VIEW_ID"`);
|
||||
await queryRunner.query(`DROP INDEX "core"."IDX_VIEW_SORT_VIEW_ID"`);
|
||||
await queryRunner.query(`DROP INDEX "core"."IDX_VIEW_GROUP_VIEW_ID"`);
|
||||
await queryRunner.query(
|
||||
`DROP INDEX "core"."IDX_VIEW_FILTER_GROUP_VIEW_ID"`,
|
||||
);
|
||||
await queryRunner.query(`DROP INDEX "core"."IDX_VIEW_FILTER_VIEW_ID"`);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,11 @@ import { IDField } from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { ViewFieldDTO } from 'src/engine/core-modules/view/dtos/view-field.dto';
|
||||
import { ViewFilterGroupDTO } from 'src/engine/core-modules/view/dtos/view-filter-group.dto';
|
||||
import { ViewFilterDTO } from 'src/engine/core-modules/view/dtos/view-filter.dto';
|
||||
import { ViewGroupDTO } from 'src/engine/core-modules/view/dtos/view-group.dto';
|
||||
import { ViewSortDTO } from 'src/engine/core-modules/view/dtos/view-sort.dto';
|
||||
import { ViewKey } from 'src/engine/core-modules/view/enums/view-key.enum';
|
||||
import { ViewOpenRecordIn } from 'src/engine/core-modules/view/enums/view-open-record-in';
|
||||
import { ViewType } from 'src/engine/core-modules/view/enums/view-type.enum';
|
||||
@@ -67,4 +72,19 @@ export class ViewDTO {
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
deletedAt?: Date | null;
|
||||
|
||||
@Field(() => [ViewFieldDTO])
|
||||
viewFields: ViewFieldDTO[];
|
||||
|
||||
@Field(() => [ViewFilterDTO])
|
||||
viewFilters: ViewFilterDTO[];
|
||||
|
||||
@Field(() => [ViewFilterGroupDTO])
|
||||
viewFilterGroups: ViewFilterGroupDTO[];
|
||||
|
||||
@Field(() => [ViewSortDTO])
|
||||
viewSorts: ViewSortDTO[];
|
||||
|
||||
@Field(() => [ViewGroupDTO])
|
||||
viewGroups: ViewGroupDTO[];
|
||||
}
|
||||
|
||||
@@ -20,6 +20,9 @@ import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/
|
||||
|
||||
@Entity({ name: 'viewField', schema: 'core' })
|
||||
@Index('IDX_VIEW_FIELD_WORKSPACE_ID_VIEW_ID', ['workspaceId', 'viewId'])
|
||||
@Index('IDX_VIEW_FIELD_VIEW_ID', ['viewId'], {
|
||||
where: '"deletedAt" IS NULL',
|
||||
})
|
||||
@Index(
|
||||
'IDX_VIEW_FIELD_FIELD_METADATA_ID_VIEW_ID_UNIQUE',
|
||||
['fieldMetadataId', 'viewId'],
|
||||
|
||||
+3
@@ -21,6 +21,9 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
|
||||
@Entity({ name: 'viewFilterGroup', schema: 'core' })
|
||||
@Index('IDX_VIEW_FILTER_GROUP_WORKSPACE_ID_VIEW_ID', ['workspaceId', 'viewId'])
|
||||
@Index('IDX_VIEW_FILTER_GROUP_VIEW_ID', ['viewId'], {
|
||||
where: '"deletedAt" IS NULL',
|
||||
})
|
||||
export class ViewFilterGroupEntity extends SyncableEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@@ -22,6 +22,9 @@ import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/
|
||||
|
||||
@Entity({ name: 'viewFilter', schema: 'core' })
|
||||
@Index('IDX_VIEW_FILTER_WORKSPACE_ID_VIEW_ID', ['workspaceId', 'viewId'])
|
||||
@Index('IDX_VIEW_FILTER_VIEW_ID', ['viewId'], {
|
||||
where: '"deletedAt" IS NULL',
|
||||
})
|
||||
@Index('IDX_VIEW_FILTER_FIELD_METADATA_ID', ['fieldMetadataId'])
|
||||
export class ViewFilterEntity extends SyncableEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
|
||||
@@ -19,6 +19,9 @@ import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/
|
||||
|
||||
@Entity({ name: 'viewGroup', schema: 'core' })
|
||||
@Index('IDX_VIEW_GROUP_WORKSPACE_ID_VIEW_ID', ['workspaceId', 'viewId'])
|
||||
@Index('IDX_VIEW_GROUP_VIEW_ID', ['viewId'], {
|
||||
where: '"deletedAt" IS NULL',
|
||||
})
|
||||
export class ViewGroupEntity extends SyncableEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@@ -20,6 +20,9 @@ import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/
|
||||
|
||||
@Entity({ name: 'viewSort', schema: 'core' })
|
||||
@Index('IDX_VIEW_SORT_WORKSPACE_ID_VIEW_ID', ['workspaceId', 'viewId'])
|
||||
@Index('IDX_VIEW_SORT_VIEW_ID', ['viewId'], {
|
||||
where: '"deletedAt" IS NULL',
|
||||
})
|
||||
@Index(
|
||||
'IDX_VIEW_SORT_FIELD_METADATA_ID_VIEW_ID_UNIQUE',
|
||||
['fieldMetadataId', 'viewId'],
|
||||
|
||||
@@ -13,15 +13,9 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { I18nService } from 'src/engine/core-modules/i18n/i18n.service';
|
||||
import { type I18nContext } from 'src/engine/core-modules/i18n/types/i18n-context.type';
|
||||
import { type IDataloaders } from 'src/engine/dataloaders/dataloader.interface';
|
||||
import { generateMessageId } from 'src/engine/core-modules/i18n/utils/generateMessageId';
|
||||
import { CreateViewInput } from 'src/engine/core-modules/view/dtos/inputs/create-view.input';
|
||||
import { UpdateViewInput } from 'src/engine/core-modules/view/dtos/inputs/update-view.input';
|
||||
import { ViewFieldDTO } from 'src/engine/core-modules/view/dtos/view-field.dto';
|
||||
import { ViewFilterGroupDTO } from 'src/engine/core-modules/view/dtos/view-filter-group.dto';
|
||||
import { ViewFilterDTO } from 'src/engine/core-modules/view/dtos/view-filter.dto';
|
||||
import { ViewGroupDTO } from 'src/engine/core-modules/view/dtos/view-group.dto';
|
||||
import { ViewSortDTO } from 'src/engine/core-modules/view/dtos/view-sort.dto';
|
||||
import { ViewDTO } from 'src/engine/core-modules/view/dtos/view.dto';
|
||||
import { ViewFieldService } from 'src/engine/core-modules/view/services/view-field.service';
|
||||
import { ViewFilterGroupService } from 'src/engine/core-modules/view/services/view-filter-group.service';
|
||||
@@ -31,6 +25,7 @@ import { ViewSortService } from 'src/engine/core-modules/view/services/view-sort
|
||||
import { ViewService } from 'src/engine/core-modules/view/services/view.service';
|
||||
import { ViewGraphqlApiExceptionFilter } from 'src/engine/core-modules/view/utils/view-graphql-api-exception.filter';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { type IDataloaders } from 'src/engine/dataloaders/dataloader.interface';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { resolveObjectMetadataStandardOverride } from 'src/engine/metadata-modules/object-metadata/utils/resolve-object-metadata-standard-override.util';
|
||||
@@ -168,44 +163,4 @@ export class ViewResolver {
|
||||
|
||||
return isDefined(deletedView);
|
||||
}
|
||||
|
||||
@ResolveField(() => [ViewFieldDTO])
|
||||
async viewFields(
|
||||
@Parent() view: ViewDTO,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
) {
|
||||
return this.viewFieldService.findByViewId(workspace.id, view.id);
|
||||
}
|
||||
|
||||
@ResolveField(() => [ViewFilterDTO])
|
||||
async viewFilters(
|
||||
@Parent() view: ViewDTO,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
) {
|
||||
return this.viewFilterService.findByViewId(workspace.id, view.id);
|
||||
}
|
||||
|
||||
@ResolveField(() => [ViewFilterGroupDTO])
|
||||
async viewFilterGroups(
|
||||
@Parent() view: ViewDTO,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
) {
|
||||
return this.viewFilterGroupService.findByViewId(workspace.id, view.id);
|
||||
}
|
||||
|
||||
@ResolveField(() => [ViewSortDTO])
|
||||
async viewSorts(
|
||||
@Parent() view: ViewDTO,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
) {
|
||||
return this.viewSortService.findByViewId(workspace.id, view.id);
|
||||
}
|
||||
|
||||
@ResolveField(() => [ViewGroupDTO])
|
||||
async viewGroups(
|
||||
@Parent() view: ViewDTO,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
) {
|
||||
return this.viewGroupService.findByViewId(workspace.id, view.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import { type IndexMetadataInterface } from 'src/engine/metadata-modules/index-m
|
||||
|
||||
import { I18nService } from 'src/engine/core-modules/i18n/i18n.service';
|
||||
import { type IDataloaders } from 'src/engine/dataloaders/dataloader.interface';
|
||||
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { filterMorphRelationDuplicateFieldsDTO } from 'src/engine/dataloaders/utils/filter-morph-relation-duplicate-fields.util';
|
||||
import { type FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto';
|
||||
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
@@ -19,6 +18,7 @@ import { resolveFieldMetadataStandardOverride } from 'src/engine/metadata-module
|
||||
import { type IndexFieldMetadataDTO } from 'src/engine/metadata-modules/index-metadata/dtos/index-field-metadata.dto';
|
||||
import { type IndexMetadataDTO } from 'src/engine/metadata-modules/index-metadata/dtos/index-metadata.dto';
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
|
||||
|
||||
export type RelationMetadataLoaderPayload = {
|
||||
|
||||
Reference in New Issue
Block a user