Various fixes on core views (#14093)

- Fix fragments
- Add id to create inputs
- Refresh core views
- Fix kanban loading
- Fix core views creation from current view

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Raphaël Bosi
2025-08-29 11:20:56 +02:00
committed by GitHub
parent 0e6ce123c9
commit 0bdd1c4803
56 changed files with 504 additions and 341 deletions
@@ -1,7 +1,7 @@
import { Test, type TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { getDataSourceToken, getRepositoryToken } from '@nestjs/typeorm';
import { type Repository } from 'typeorm';
import { type DataSource, type Repository } from 'typeorm';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
@@ -33,13 +33,22 @@ describe('WorkspaceManagerService', () => {
let workspaceDataSourceService: WorkspaceDataSourceService;
let roleTargetsRepository: Repository<RoleTargetsEntity>;
let roleRepository: Repository<RoleEntity>;
let mockDataSource: jest.Mocked<DataSource>;
beforeEach(async () => {
mockDataSource = {
transaction: jest.fn(),
} as any;
const module: TestingModule = await Test.createTestingModule({
providers: [
WorkspaceManagerService,
WorkspaceMigrationService,
DataSourceService,
{
provide: getDataSourceToken(),
useValue: mockDataSource,
},
{
provide: getRepositoryToken(Workspace),
useValue: {},
@@ -6,7 +6,6 @@ import { DataSource } from 'typeorm';
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
import { shouldSeedWorkspaceFavorite } from 'src/engine/utils/should-seed-workspace-favorite';
import {
CALENDAR_CHANNEL_DATA_SEED_COLUMNS,
CALENDAR_CHANNEL_DATA_SEEDS,
@@ -88,9 +87,7 @@ import {
WORKSPACE_MEMBER_DATA_SEEDS,
} from 'src/engine/workspace-manager/dev-seeder/data/constants/workspace-member-data-seeds.constant';
import { TimelineActivitySeederService } from 'src/engine/workspace-manager/dev-seeder/data/services/timeline-activity-seeder.service';
import { prefillViews } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-views';
import { prefillWorkflows } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-workflows';
import { prefillWorkspaceFavorites } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-workspace-favorites';
const RECORD_SEEDS_CONFIGS = [
{
@@ -242,28 +239,7 @@ export class DevSeederDataService {
workspaceId,
});
const viewDefinitionsWithId = await prefillViews(
entityManager,
schemaName,
objectMetadataItems.filter((item) => !item.isCustom),
);
await prefillWorkflows(entityManager, schemaName, objectMetadataItems);
await prefillWorkspaceFavorites(
viewDefinitionsWithId
.filter(
(view) =>
view.key === 'INDEX' &&
shouldSeedWorkspaceFavorite(
view.objectMetadataId,
objectMetadataItems,
),
)
.map((view) => view.id),
entityManager,
schemaName,
);
},
);
}
@@ -104,7 +104,7 @@ export class DevSeederMetadataService {
});
}
await this.seedCoreViews(workspaceId);
await this.seedCoreViews({ workspaceId, dataSourceMetadata });
}
private async seedCustomObject({
@@ -152,14 +152,21 @@ export class DevSeederMetadataService {
);
}
private async seedCoreViews(workspaceId: string): Promise<void> {
private async seedCoreViews({
workspaceId,
dataSourceMetadata,
}: {
workspaceId: string;
dataSourceMetadata: DataSourceEntity;
}): Promise<void> {
const createdObjectMetadata =
await this.objectMetadataService.findManyWithinWorkspace(workspaceId);
await prefillCoreViews(
this.coreDataSource,
await prefillCoreViews({
coreDataSource: this.coreDataSource,
workspaceId,
createdObjectMetadata,
);
objectMetadataItems: createdObjectMetadata,
schemaName: dataSourceMetadata.schema,
});
}
}
@@ -1,5 +1,6 @@
import { type EntityManager } from 'typeorm';
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
export const AIRBNB_ID = 'c776ee49-f608-4a77-8cc8-6fe96ae1e43f';
export const ANTHROPIC_ID = 'f45ee421-8a3e-4aa5-a1cf-7207cc6754e1';
@@ -8,13 +9,11 @@ export const FIGMA_ID = '9d5bcf43-7d38-4e88-82cb-d6d4ce638bf0';
export const NOTION_ID = '06290608-8bf0-4806-99ae-a715a6a93fad';
export const prefillCompanies = async (
entityManager: WorkspaceEntityManager,
entityManager: EntityManager,
schemaName: string,
) => {
await entityManager
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.createQueryBuilder()
.insert()
.into(`${schemaName}.company`, [
'id',
@@ -12,12 +12,15 @@ 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';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { shouldSeedWorkspaceFavorite } from 'src/engine/utils/should-seed-workspace-favorite';
import { prefillWorkspaceFavorites } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-workspace-favorites';
import { type ViewDefinition } from 'src/engine/workspace-manager/standard-objects-prefill-data/types/view-definition.interface';
import { companiesAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/companies-all.view';
import { dashboardsAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/dashboards-all.view';
import { notesAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/notes-all.view';
import { opportunitiesAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/opportunities-all.view';
import { opportunitiesTableByStageView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/opportunity-table-by-stage.view';
import { opportunitiesByStageView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/opportunity-by-stage.view';
import { peopleAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/people-all.view';
import { tasksAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-all.view';
import { tasksAssignedToMeView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-assigned-to-me';
@@ -28,17 +31,26 @@ import { workflowsAllView } from 'src/engine/workspace-manager/standard-objects-
import { ViewOpenRecordInType } from 'src/modules/view/standard-objects/view.workspace-entity';
import { convertViewFilterOperandToCoreOperand } from 'src/modules/view/utils/convert-view-filter-operand-to-core-operand.util';
export const prefillCoreViews = async (
coreDataSource: DataSource,
workspaceId: string,
objectMetadataItems: ObjectMetadataEntity[],
featureFlags?: Record<string, boolean>,
): Promise<ViewEntity[]> => {
type PrefillCoreViewsArgs = {
coreDataSource: DataSource;
workspaceId: string;
objectMetadataItems: ObjectMetadataEntity[];
schemaName: string;
featureFlags?: Record<string, boolean>;
};
export const prefillCoreViews = async ({
coreDataSource,
workspaceId,
objectMetadataItems,
schemaName,
featureFlags,
}: PrefillCoreViewsArgs): Promise<ViewEntity[]> => {
const views = [
companiesAllView(objectMetadataItems, true),
peopleAllView(objectMetadataItems, true),
opportunitiesAllView(objectMetadataItems, true),
opportunitiesTableByStageView(objectMetadataItems, true),
opportunitiesByStageView(objectMetadataItems, true),
notesAllView(objectMetadataItems, true),
tasksAllView(objectMetadataItems, true),
tasksAssignedToMeView(objectMetadataItems, true),
@@ -61,6 +73,21 @@ export const prefillCoreViews = async (
const createdViews = await createCoreViews(queryRunner, workspaceId, views);
await prefillWorkspaceFavorites(
createdViews
.filter(
(view) =>
view.key === 'INDEX' &&
shouldSeedWorkspaceFavorite(
view.objectMetadataId,
objectMetadataItems,
),
)
.map((view) => view.id),
queryRunner.manager as WorkspaceEntityManager,
schemaName,
);
await queryRunner.commitTransaction();
return createdViews;
@@ -1,5 +1,6 @@
import { type EntityManager } from 'typeorm';
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import {
AIRBNB_ID,
ANTHROPIC_ID,
@@ -9,13 +10,11 @@ import {
} from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-companies';
export const prefillPeople = async (
entityManager: WorkspaceEntityManager,
entityManager: EntityManager,
schemaName: string,
) => {
await entityManager
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.createQueryBuilder()
.insert()
.into(`${schemaName}.person`, [
'nameFirstName',
@@ -1,8 +1,8 @@
import { type EntityManager } from 'typeorm';
import { v4 } from 'uuid';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { type ViewDefinition } from 'src/engine/workspace-manager/standard-objects-prefill-data/types/view-definition.interface';
import { companiesAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/companies-all.view';
import { customAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/custom-all.view';
@@ -19,7 +19,7 @@ import { workflowVersionsAllView } from 'src/engine/workspace-manager/standard-o
import { workflowsAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/workflows-all.view';
export const prefillViews = async (
entityManager: WorkspaceEntityManager,
entityManager: EntityManager,
schemaName: string,
objectMetadataItems: ObjectMetadataEntity[],
featureFlags?: Record<string, boolean>,
@@ -55,7 +55,7 @@ export const prefillViews = async (
};
const createWorkspaceViews = async (
entityManager: WorkspaceEntityManager,
entityManager: EntityManager,
schemaName: string,
viewDefinitions: ViewDefinition[],
) => {
@@ -65,9 +65,7 @@ const createWorkspaceViews = async (
}));
await entityManager
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.createQueryBuilder()
.insert()
.into(`${schemaName}.view`, [
'id',
@@ -117,9 +115,7 @@ const createWorkspaceViews = async (
for (const viewDefinition of viewDefinitionsWithId) {
if (viewDefinition.fields && viewDefinition.fields.length > 0) {
await entityManager
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.createQueryBuilder()
.insert()
.into(`${schemaName}.viewField`, [
'fieldMetadataId',
@@ -144,9 +140,7 @@ const createWorkspaceViews = async (
if (viewDefinition.filters && viewDefinition.filters.length > 0) {
await entityManager
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.createQueryBuilder()
.insert()
.into(`${schemaName}.viewFilter`, [
'fieldMetadataId',
@@ -174,9 +168,7 @@ const createWorkspaceViews = async (
viewDefinition.groups.length > 0
) {
await entityManager
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.createQueryBuilder()
.insert()
.into(`${schemaName}.viewGroup`, [
'fieldMetadataId',
@@ -1,16 +1,16 @@
import { isDefined } from 'twenty-shared/utils';
import { type EntityManager } from 'typeorm';
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { generateObjectMetadataMaps } from 'src/engine/metadata-modules/utils/generate-object-metadata-maps.util';
import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { generateObjectRecordFields } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-object-record-fields';
const QUICK_LEAD_WORKFLOW_ID = '8b213cac-a68b-4ffe-817a-3ec994e9932d';
const QUICK_LEAD_WORKFLOW_VERSION_ID = 'ac67974f-c524-4288-9d88-af8515400b68';
export const prefillWorkflows = async (
entityManager: WorkspaceEntityManager,
entityManager: EntityManager,
schemaName: string,
objectMetadataItems: ObjectMetadataEntity[],
) => {
@@ -37,9 +37,7 @@ export const prefillWorkflows = async (
}
await entityManager
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.createQueryBuilder()
.insert()
.into(`${schemaName}.workflow`, [
'id',
@@ -70,9 +68,7 @@ export const prefillWorkflows = async (
.execute();
await entityManager
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.createQueryBuilder()
.insert()
.into(`${schemaName}.workflowVersion`, [
'id',
@@ -1,18 +1,15 @@
import { type EntityManager } from 'typeorm';
import { v4 } from 'uuid';
import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
const tableName = 'favorite';
export const prefillWorkspaceFavorites = async (
viewIds: string[],
entityManager: WorkspaceEntityManager,
entityManager: EntityManager,
schemaName: string,
) => {
await entityManager
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.createQueryBuilder()
.insert()
.into(`${schemaName}.${tableName}`, ['id', 'viewId', 'position'])
.values(
@@ -1,6 +1,6 @@
import { type DataSource, type EntityManager } from 'typeorm';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { type WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { shouldSeedWorkspaceFavorite } from 'src/engine/utils/should-seed-workspace-favorite';
import { prefillCompanies } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-companies';
import { prefillPeople } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-people';
@@ -9,40 +9,38 @@ import { prefillWorkflows } from 'src/engine/workspace-manager/standard-objects-
import { prefillWorkspaceFavorites } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-workspace-favorites';
export const standardObjectsPrefillData = async (
workspaceDataSource: WorkspaceDataSource,
dataSource: DataSource,
schemaName: string,
objectMetadataItems: ObjectMetadataEntity[],
featureFlags?: Record<string, boolean>,
) => {
workspaceDataSource.transaction(
async (entityManager: WorkspaceEntityManager) => {
await prefillCompanies(entityManager, schemaName);
dataSource.transaction(async (entityManager: EntityManager) => {
await prefillCompanies(entityManager, schemaName);
await prefillPeople(entityManager, schemaName);
await prefillPeople(entityManager, schemaName);
await prefillWorkflows(entityManager, schemaName, objectMetadataItems);
await prefillWorkflows(entityManager, schemaName, objectMetadataItems);
const viewDefinitionsWithId = await prefillViews(
entityManager,
schemaName,
objectMetadataItems,
featureFlags,
);
const viewDefinitionsWithId = await prefillViews(
entityManager,
schemaName,
objectMetadataItems,
featureFlags,
);
await prefillWorkspaceFavorites(
viewDefinitionsWithId
.filter(
(view) =>
view.key === 'INDEX' &&
shouldSeedWorkspaceFavorite(
view.objectMetadataId,
objectMetadataItems,
),
)
.map((view) => view.id),
entityManager,
schemaName,
);
},
);
await prefillWorkspaceFavorites(
viewDefinitionsWithId
.filter(
(view) =>
view.key === 'INDEX' &&
shouldSeedWorkspaceFavorite(
view.objectMetadataId,
objectMetadataItems,
),
)
.map((view) => view.id),
entityManager,
schemaName,
);
});
};
@@ -1,7 +1,7 @@
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { DataSource, Repository } from 'typeorm';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
@@ -29,6 +29,8 @@ export class WorkspaceManagerService {
private readonly logger = new Logger(WorkspaceManagerService.name);
constructor(
@InjectDataSource()
private readonly coreDataSource: DataSource,
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
private readonly workspaceMigrationService: WorkspaceMigrationService,
private readonly objectMetadataService: ObjectMetadataService,
@@ -125,16 +127,11 @@ export class WorkspaceManagerService {
workspaceId: string,
featureFlags: Record<string, boolean>,
) {
const workspaceDataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
workspaceId,
});
const createdObjectMetadata =
await this.objectMetadataService.findManyWithinWorkspace(workspaceId);
await standardObjectsPrefillData(
workspaceDataSource,
this.coreDataSource,
dataSourceMetadata.schema,
createdObjectMetadata,
featureFlags,
@@ -143,12 +140,13 @@ export class WorkspaceManagerService {
if (featureFlags[FeatureFlagKey.IS_CORE_VIEW_SYNCING_ENABLED]) {
this.logger.log(`Prefilling core views for workspace ${workspaceId}`);
await prefillCoreViews(
workspaceDataSource,
await prefillCoreViews({
coreDataSource: this.coreDataSource,
workspaceId,
createdObjectMetadata,
objectMetadataItems: createdObjectMetadata,
schemaName: dataSourceMetadata.schema,
featureFlags,
);
});
}
}