Fix seeds for local workspace and newly created workspaces (#2333)

* Update metadata/data seeds

* fix

* fix

* move seeding into a transaction

* add no-non-null-assertion

---------

Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
This commit is contained in:
Weiko
2023-11-03 14:33:45 +01:00
committed by GitHub
parent 56a5f99108
commit b56f6f3947
33 changed files with 1392 additions and 630 deletions

View File

@ -1,47 +1,69 @@
import { Command, CommandRunner, Option } from 'nest-commander';
import { InjectDataSource } from '@nestjs/typeorm';
import { Command, CommandRunner } from 'nest-commander';
import { DataSource } from 'typeorm';
import { DataSourceMetadataService } from 'src/metadata/data-source-metadata/data-source-metadata.service';
import { TenantInitialisationService } from 'src/metadata/tenant-initialisation/tenant-initialisation.service';
import { DataSourceService } from 'src/metadata/data-source/data-source.service';
import { TenantMigrationService } from 'src/metadata/tenant-migration/tenant-migration.service';
import { MigrationRunnerService } from 'src/metadata/migration-runner/migration-runner.service';
import { seedCompanies } from 'src/database/typeorm-seeds/tenant/companies';
import { seedViewFields } from 'src/database/typeorm-seeds/tenant/view-fields';
import { seedViews } from 'src/database/typeorm-seeds/tenant/views';
import { seedFieldMetadata } from 'src/database/typeorm-seeds/metadata/field-metadata';
import { seedObjectMetadata } from 'src/database/typeorm-seeds/metadata/object-metadata';
// TODO: implement dry-run
interface DataSeedTenantOptions {
workspaceId: string;
}
@Command({
name: 'tenant:data-seed',
description: 'Seed tenant with initial data',
name: 'tenant:seed',
description:
'Seed tenant with initial data. This command is intended for development only.',
})
export class DataSeedTenantCommand extends CommandRunner {
workspaceId = 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419';
constructor(
@InjectDataSource('metadata')
private readonly metadataDataSource: DataSource,
private readonly dataSourceMetadataService: DataSourceMetadataService,
private readonly tenantInitialisationService: TenantInitialisationService,
private readonly dataSourceService: DataSourceService,
private readonly tenantMigrationService: TenantMigrationService,
private readonly migrationRunnerService: MigrationRunnerService,
) {
super();
}
async run(
_passedParam: string[],
options: DataSeedTenantOptions,
): Promise<void> {
async run(): Promise<void> {
const dataSourceMetadata =
await this.dataSourceMetadataService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
options.workspaceId,
this.workspaceId,
);
// TODO: run in a dedicated job + run queries in a transaction.
await this.tenantInitialisationService.prefillWorkspaceWithStandardObjects(
dataSourceMetadata,
options.workspaceId,
const workspaceDataSource =
await this.dataSourceService.connectToWorkspaceDataSource(
this.workspaceId,
);
if (!workspaceDataSource) {
throw new Error('Could not connect to workspace data source');
}
await seedObjectMetadata(this.metadataDataSource, 'metadata');
await seedFieldMetadata(this.metadataDataSource, 'metadata');
await this.tenantMigrationService.insertStandardMigrations(
this.workspaceId,
);
await this.migrationRunnerService.executeMigrationFromPendingMigrations(
this.workspaceId,
);
await seedCompanies(workspaceDataSource, dataSourceMetadata.schema);
await seedViewFields(workspaceDataSource, dataSourceMetadata.schema);
await seedViews(workspaceDataSource, dataSourceMetadata.schema);
await this.dataSourceService.disconnectFromWorkspaceDataSource(
this.workspaceId,
);
}
// TODO: workspaceId should be optional and we should run migrations for all workspaces
@Option({
flags: '-w, --workspace-id [workspace_id]',
description: 'workspace id',
required: true,
})
parseWorkspaceId(value: string): string {
return value;
}
}

View File

@ -6,6 +6,7 @@ import { ObjectMetadataModule } from 'src/metadata/object-metadata/object-metada
import { FieldMetadataModule } from 'src/metadata/field-metadata/field-metadata.module';
import { TenantInitialisationModule } from 'src/metadata/tenant-initialisation/tenant-initialisation.module';
import { DataSourceMetadataModule } from 'src/metadata/data-source-metadata/data-source-metadata.module';
import { DataSourceModule } from 'src/metadata/data-source/data-source.module';
import { SyncTenantMetadataCommand } from './sync-tenant-metadata.command';
import { RunTenantMigrationsCommand } from './run-tenant-migrations.command';
@ -19,6 +20,7 @@ import { DataSeedTenantCommand } from './data-seed-tenant.command';
FieldMetadataModule,
DataSourceMetadataModule,
TenantInitialisationModule,
DataSourceModule,
],
providers: [
RunTenantMigrationsCommand,

View File

@ -1,7 +1,6 @@
import { Command, CommandRunner, Option } from 'nest-commander';
import { ObjectMetadataService } from 'src/metadata/object-metadata/services/object-metadata.service';
import { TenantInitialisationService } from 'src/metadata/tenant-initialisation/tenant-initialisation.service';
import { DataSourceMetadataService } from 'src/metadata/data-source-metadata/data-source-metadata.service';
// TODO: implement dry-run
@ -17,7 +16,6 @@ export class SyncTenantMetadataCommand extends CommandRunner {
constructor(
private readonly objectMetadataService: ObjectMetadataService,
private readonly dataSourceMetadataService: DataSourceMetadataService,
private readonly tenantInitialisationService: TenantInitialisationService,
) {
super();
}
@ -37,8 +35,7 @@ export class SyncTenantMetadataCommand extends CommandRunner {
workspaceId: { eq: options.workspaceId },
});
// TODO: this should not be the responsibility of tenantInitialisationService.
await this.tenantInitialisationService.createObjectsAndFieldsMetadata(
await this.objectMetadataService.createStandardObjectsAndFieldsMetadata(
dataSourceMetadata.id,
options.workspaceId,
);

View File

@ -88,9 +88,6 @@ export class DataSourceService implements OnModuleInit, OnModuleDestroy {
await workspaceDataSource.initialize();
// Set search path to workspace schema for raw queries
await workspaceDataSource?.query(`SET search_path TO ${schema};`);
this.dataSources.set(workspaceId, workspaceDataSource);
return workspaceDataSource;

View File

@ -22,7 +22,7 @@ const configService = new ConfigService();
export const typeORMMetadataModuleOptions: TypeOrmModuleOptions = {
url: configService.get<string>('PG_DATABASE_URL'),
type: 'postgres',
logging: false,
logging: ['query', 'error'],
schema: 'metadata',
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: false,

View File

@ -13,6 +13,7 @@ import { TenantMigrationService } from 'src/metadata/tenant-migration/tenant-mig
import { TenantMigrationTableAction } from 'src/metadata/tenant-migration/tenant-migration.entity';
import { MigrationRunnerService } from 'src/metadata/migration-runner/migration-runner.service';
import { ObjectMetadata } from 'src/metadata/object-metadata/object-metadata.entity';
import { standardObjectsMetadata } from 'src/metadata/standard-objects/standard-object-metadata';
@Injectable()
export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadata> {
@ -84,4 +85,32 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadata> {
where: { id: objectMetadataId, workspaceId },
});
}
/**
*
* Create all standard objects and fields metadata for a given workspace
*
* @param dataSourceMetadataId
* @param workspaceId
*/
public async createStandardObjectsAndFieldsMetadata(
dataSourceMetadataId: string,
workspaceId: string,
) {
await this.objectMetadataRepository.save(
Object.values(standardObjectsMetadata).map((objectMetadata) => ({
...objectMetadata,
dataSourceId: dataSourceMetadataId,
workspaceId,
isCustom: false,
isActive: true,
fields: objectMetadata.fields.map((field) => ({
...field,
workspaceId,
isCustom: false,
isActive: true,
})),
})),
);
}
}

View File

@ -0,0 +1,57 @@
const companiesMetadata = {
nameSingular: 'companyV2',
namePlural: 'companiesV2',
labelSingular: 'Company',
labelPlural: 'Companies',
targetTableName: 'company',
description: 'A company',
icon: 'IconBuildingSkyscraper',
fields: [
{
type: 'text',
name: 'name',
label: 'Name',
targetColumnMap: {
value: 'name',
},
description: 'Name of the company',
icon: 'IconBuildingSkyscraper',
isNullable: false,
},
{
type: 'text',
name: 'domainName',
label: 'Domain Name',
targetColumnMap: {
value: 'domainName',
},
description: 'Domain name of the company',
icon: 'IconLink',
isNullable: true,
},
{
type: 'text',
name: 'address',
label: 'Address',
targetColumnMap: {
value: 'address',
},
description: 'Address of the company',
icon: 'IconMap',
isNullable: true,
},
{
type: 'number',
name: 'employees',
label: 'Employees',
targetColumnMap: {
value: 'employees',
},
description: 'Number of employees',
icon: 'IconUsers',
isNullable: true,
},
],
};
export default companiesMetadata;

View File

@ -0,0 +1,13 @@
import companiesMetadata from './companies/companies.metadata';
import viewFieldsMetadata from './view-fields/view-fields.metadata';
import viewFiltersMetadata from './view-filters/view-filters.metadata';
import viewSortsMetadata from './view-sorts/view-sorts.metadata';
import viewsMetadata from './views/views.metadata';
export const standardObjectsMetadata = {
companyV2: companiesMetadata,
viewV2: viewsMetadata,
viewFieldV2: viewFieldsMetadata,
viewFilterV2: viewFiltersMetadata,
viewSortV2: viewSortsMetadata,
};

View File

@ -0,0 +1,68 @@
const viewFieldsMetadata = {
nameSingular: 'viewFieldV2',
namePlural: 'viewFieldsV2',
labelSingular: 'View Field',
labelPlural: 'View Fields',
targetTableName: 'viewField',
description: '(System) View Fields',
icon: 'IconColumns3',
fields: [
{
type: 'text',
name: 'fieldId',
label: 'Field Id',
targetColumnMap: {
value: 'fieldId',
},
description: 'View Field target field',
icon: null,
isNullable: false,
},
{
type: 'text',
name: 'viewId',
label: 'View Id',
targetColumnMap: {
value: 'viewId',
},
description: 'View Field related view',
icon: null,
isNullable: false,
},
{
type: 'boolean',
name: 'isVisible',
label: 'Visible',
targetColumnMap: {
value: 'isVisible',
},
description: 'View Field visibility',
icon: null,
isNullable: false,
},
{
type: 'number',
name: 'size',
label: 'Size',
targetColumnMap: {
value: 'size',
},
description: 'View Field size',
icon: null,
isNullable: false,
},
{
type: 'number',
name: 'position',
label: 'Position',
targetColumnMap: {
value: 'position',
},
description: 'View Field position',
icon: null,
isNullable: false,
},
],
};
export default viewFieldsMetadata;

View File

@ -0,0 +1,68 @@
const viewFiltersMetadata = {
nameSingular: 'viewFilterV2',
namePlural: 'viewFiltersV2',
labelSingular: 'View Filter',
labelPlural: 'View Filters',
targetTableName: 'viewFilter',
description: '(System) View Filters',
icon: 'IconFilterBolt',
fields: [
{
type: 'text',
name: 'fieldId',
label: 'Field Id',
targetColumnMap: {
value: 'fieldId',
},
description: 'View Filter target field',
icon: null,
isNullable: true,
},
{
type: 'text',
name: 'viewId',
label: 'View Id',
targetColumnMap: {
value: 'viewId',
},
description: 'View Filter related view',
icon: null,
isNullable: false,
},
{
type: 'text',
name: 'operand',
label: 'Operand',
targetColumnMap: {
value: 'operand',
},
description: 'View Filter operand',
icon: null,
isNullable: false,
},
{
type: 'text',
name: 'value',
label: 'Value',
targetColumnMap: {
value: 'value',
},
description: 'View Filter value',
icon: null,
isNullable: false,
},
{
type: 'text',
name: 'displayValue',
label: 'Display Value',
targetColumnMap: {
value: 'displayValue',
},
description: 'View Filter Display Value',
icon: null,
isNullable: false,
},
],
};
export default viewFiltersMetadata;

View File

@ -0,0 +1,46 @@
const viewSortsMetadata = {
nameSingular: 'viewSortV2',
namePlural: 'viewSortsV2',
labelSingular: 'View Sort',
labelPlural: 'View Sorts',
targetTableName: 'viewSort',
description: '(System) View Sorts',
icon: 'IconArrowsSort',
fields: [
{
type: 'text',
name: 'fieldId',
label: 'Field Id',
targetColumnMap: {
value: 'fieldId',
},
description: 'View Sort target field',
icon: null,
isNullable: false,
},
{
type: 'text',
name: 'viewId',
label: 'View Id',
targetColumnMap: {
value: 'viewId',
},
description: 'View Sort related view',
icon: null,
isNullable: false,
},
{
type: 'text',
name: 'direction',
label: 'Direction',
targetColumnMap: {
value: 'direction',
},
description: 'View Sort direction',
icon: null,
isNullable: false,
},
],
};
export default viewSortsMetadata;

View File

@ -0,0 +1,46 @@
const viewsMetadata = {
nameSingular: 'viewV2',
namePlural: 'viewsV2',
labelSingular: 'View',
labelPlural: 'Views',
targetTableName: 'view',
description: '(System) Views',
icon: 'IconLayoutCollage',
fields: [
{
type: 'text',
name: 'name',
label: 'Name',
targetColumnMap: {
value: 'name',
},
description: 'View name',
icon: null,
isNullable: false,
},
{
type: 'text',
name: 'objectId',
label: 'Object Id',
targetColumnMap: {
value: 'objectId',
},
description: 'View target object',
icon: null,
isNullable: false,
},
{
type: 'text',
name: 'type',
label: 'Type',
targetColumnMap: {
value: 'type',
},
description: 'View type',
icon: null,
isNullable: false,
},
],
};
export default viewsMetadata;

View File

@ -0,0 +1,269 @@
import { DataSource, EntityManager } from 'typeorm';
export const standardObjectsPrefillData = async (
workspaceDataSource: DataSource,
schemaName: string,
) => {
workspaceDataSource.transaction(async (entityManager: EntityManager) => {
const createdCompanies = await entityManager
.createQueryBuilder()
.insert()
.into(`${schemaName}.company`, [
'name',
'domainName',
'address',
'employees',
])
.orIgnore()
.values([
{
name: 'Airbnb',
domainName: 'airbnb.com',
address: 'San Francisco',
employees: 5000,
},
{
name: 'Qonto',
domainName: 'qonto.com',
address: 'San Francisco',
employees: 800,
},
{
name: 'Stripe',
domainName: 'stripe.com',
address: 'San Francisco',
employees: 8000,
},
{
name: 'Figma',
domainName: 'figma.com',
address: 'San Francisco',
employees: 800,
},
{
name: 'Notion',
domainName: 'notion.com',
address: 'San Francisco',
employees: 400,
},
])
.returning('*')
.execute();
const companyIdMap = createdCompanies.raw.reduce((acc, view) => {
acc[view.name] = view.id;
return acc;
}, {});
const createdViews = await entityManager
.createQueryBuilder()
.insert()
.into(`${schemaName}.view`, ['name', 'objectId', 'type'])
.orIgnore()
.values([
{
name: 'All companies',
objectId: 'company',
type: 'table',
},
{
name: 'All people',
objectId: 'person',
type: 'table',
},
{
name: 'All opportunities',
objectId: 'company',
type: 'kanban',
},
{
name: 'All Companies (V2)',
objectId: companyIdMap['Airbnb'],
type: 'table',
},
])
.returning('*')
.execute();
const viewIdMap = createdViews.raw.reduce((acc, view) => {
acc[view.name] = view.id;
return acc;
}, {});
await entityManager
.createQueryBuilder()
.insert()
.into(`${schemaName}.viewField`, [
'fieldId',
'viewId',
'position',
'isVisible',
'size',
])
.orIgnore()
.values([
{
fieldId: 'name',
viewId: viewIdMap['All Companies (V2)'],
position: 0,
isVisible: true,
size: 180,
},
{
fieldId: 'name',
viewId: viewIdMap['All companies'],
position: 0,
isVisible: true,
size: 180,
},
{
fieldId: 'domainName',
viewId: viewIdMap['All companies'],
position: 1,
isVisible: true,
size: 100,
},
{
fieldId: 'accountOwner',
viewId: viewIdMap['All companies'],
position: 2,
isVisible: true,
size: 150,
},
{
fieldId: 'createdAt',
viewId: viewIdMap['All companies'],
position: 3,
isVisible: true,
size: 150,
},
{
fieldId: 'employees',
viewId: viewIdMap['All companies'],
position: 4,
isVisible: true,
size: 150,
},
{
fieldId: 'linkedin',
viewId: viewIdMap['All companies'],
position: 5,
isVisible: true,
size: 170,
},
{
fieldId: 'address',
viewId: viewIdMap['All companies'],
position: 6,
isVisible: true,
size: 170,
},
{
fieldId: 'displayName',
viewId: viewIdMap['All people'],
position: 0,
isVisible: true,
size: 210,
},
{
fieldId: 'email',
viewId: viewIdMap['All people'],
position: 1,
isVisible: true,
size: 150,
},
{
fieldId: 'company',
viewId: viewIdMap['All people'],
position: 2,
isVisible: true,
size: 150,
},
{
fieldId: 'phone',
viewId: viewIdMap['All people'],
position: 3,
isVisible: true,
size: 150,
},
{
fieldId: 'createdAt',
viewId: viewIdMap['All people'],
position: 4,
isVisible: true,
size: 150,
},
{
fieldId: 'city',
viewId: viewIdMap['All people'],
position: 5,
isVisible: true,
size: 150,
},
{
fieldId: 'jobTitle',
viewId: viewIdMap['All people'],
position: 6,
isVisible: true,
size: 150,
},
{
fieldId: 'linkedin',
viewId: viewIdMap['All people'],
position: 7,
isVisible: true,
size: 150,
},
{
fieldId: 'x',
viewId: viewIdMap['All people'],
position: 8,
isVisible: true,
size: 150,
},
{
fieldId: 'amount',
viewId: viewIdMap['All opportunities'],
position: 0,
isVisible: true,
size: 180,
},
{
fieldId: 'probability',
viewId: viewIdMap['All opportunities'],
position: 1,
isVisible: true,
size: 150,
},
{
fieldId: 'closeDate',
viewId: viewIdMap['All opportunities'],
position: 2,
isVisible: true,
size: 100,
},
{
fieldId: 'company',
viewId: viewIdMap['All opportunities'],
position: 3,
isVisible: true,
size: 150,
},
{
fieldId: 'createdAt',
viewId: viewIdMap['All opportunities'],
position: 4,
isVisible: true,
size: 150,
},
{
fieldId: 'pointOfContact',
viewId: viewIdMap['All opportunities'],
position: 5,
isVisible: true,
size: 150,
},
])
.execute();
});
};

View File

@ -1,56 +0,0 @@
{
"id": "1a8487a0-480c-434e-b4c7-e22408b97047",
"nameSingular": "companyV2",
"namePlural": "companiesV2",
"labelSingular": "Company",
"labelPlural": "Companies",
"targetTableName": "company",
"description": "A company",
"icon": "IconBuildingSkyscraper",
"fields": [
{
"type": "text",
"name": "name",
"label": "Name",
"targetColumnMap": {
"value": "name"
},
"description": "Name of the company",
"icon": "IconBuildingSkyscraper",
"isNullable": false
},
{
"type": "text",
"name": "domainName",
"label": "Domain Name",
"targetColumnMap": {
"value": "domainName"
},
"description": "Domain name of the company",
"icon": "IconLink",
"isNullable": true
},
{
"type": "text",
"name": "address",
"label": "Address",
"targetColumnMap": {
"value": "address"
},
"description": "Address of the company",
"icon": "IconMap",
"isNullable": true
},
{
"type": "number",
"name": "employees",
"label": "Employees",
"targetColumnMap": {
"value": "employees"
},
"description": "Number of employees",
"icon": "IconUsers",
"isNullable": true
}
]
}

View File

@ -1,32 +0,0 @@
[
{
"name": "Airbnb",
"domainName": "airbnb.com",
"address": "San Francisco",
"employees": 5000
},
{
"name": "Qonto",
"domainName": "qonto.com",
"address": "San Francisco",
"employees": 800
},
{
"name": "Stripe",
"domainName": "stripe.com",
"address": "San Francisco",
"employees": 8000
},
{
"name": "Figma",
"domainName": "figma.com",
"address": "San Francisco",
"employees": 800
},
{
"name": "Notion",
"domainName": "notion.com",
"address": "San Francisco",
"employees": 400
}
]

View File

@ -1,13 +0,0 @@
import companyObject from './companies/companies.metadata.json';
import viewObject from './views/views.metadata.json';
import viewFieldObject from './view-fields/view-fields.metadata.json';
import viewFilterObject from './view-filters/view-filters.metadata.json';
import viewSortObject from './view-sorts/view-sorts.metadata.json';
export const standardObjectsMetadata = {
companyV2: companyObject,
viewV2: viewObject,
viewFieldV2: viewFieldObject,
viewFilterV2: viewFilterObject,
viewSortV2: viewSortObject,
};

View File

@ -1,9 +0,0 @@
import companySeeds from './companies/companies.seeds.json';
import viewSeeds from './views/views.seeds.json';
import viewFieldSeeds from './view-fields/view-fields.seeds.json';
export const standardObjectsSeeds = {
companyV2: companySeeds,
viewV2: viewSeeds,
viewFieldV2: viewFieldSeeds,
};

View File

@ -1,66 +0,0 @@
{
"nameSingular": "viewFieldV2",
"namePlural": "viewFieldsV2",
"labelSingular": "View Field",
"labelPlural": "View Fields",
"targetTableName": "viewField",
"description": "(System) View Fields",
"icon": "IconColumns3",
"fields": [
{
"type": "text",
"name": "fieldId",
"label": "Field Id",
"targetColumnMap": {
"value": "fieldId"
},
"description": "View Field target field",
"icon": null,
"isNullable": false
},
{
"type": "text",
"name": "viewId",
"label": "View Id",
"targetColumnMap": {
"value": "viewId"
},
"description": "View Field related view",
"icon": null,
"isNullable": false
},
{
"type": "boolean",
"name": "isVisible",
"label": "Visible",
"targetColumnMap": {
"value": "isVisible"
},
"description": "View Field visibility",
"icon": null,
"isNullable": false
},
{
"type": "number",
"name": "size",
"label": "Size",
"targetColumnMap": {
"value": "size"
},
"description": "View Field size",
"icon": null,
"isNullable": false
},
{
"type": "number",
"name": "position",
"label": "Position",
"targetColumnMap": {
"value": "position"
},
"description": "View Field position",
"icon": null,
"isNullable": false
}
]
}

View File

@ -1,166 +0,0 @@
[
{
"fieldId": "name",
"viewId": "10bec73c-0aea-4cc4-a3b2-8c2186f29b43",
"position": 0,
"isVisible": true,
"size": 180
},
{
"fieldId": "name",
"viewId": "37a8a866-eb17-4e76-9382-03143a2f6a80",
"position": 0,
"isVisible": true,
"size": 180
},
{
"fieldId": "domainName",
"viewId": "37a8a866-eb17-4e76-9382-03143a2f6a80",
"position": 1,
"isVisible": true,
"size": 100
},
{
"fieldId": "accountOwner",
"viewId": "37a8a866-eb17-4e76-9382-03143a2f6a80",
"position": 2,
"isVisible": true,
"size": 150
},
{
"fieldId": "createdAt",
"viewId": "37a8a866-eb17-4e76-9382-03143a2f6a80",
"position": 3,
"isVisible": true,
"size": 150
},
{
"fieldId": "employees",
"viewId": "37a8a866-eb17-4e76-9382-03143a2f6a80",
"position": 4,
"isVisible": true,
"size": 150
},
{
"fieldId": "linkedin",
"viewId": "37a8a866-eb17-4e76-9382-03143a2f6a80",
"position": 5,
"isVisible": true,
"size": 170
},
{
"fieldId": "address",
"viewId": "37a8a866-eb17-4e76-9382-03143a2f6a80",
"position": 6,
"isVisible": true,
"size": 170
},
{
"fieldId": "displayName",
"viewId": "6095799e-b48f-4e00-b071-10818083593a",
"position": 0,
"isVisible": true,
"size": 210
},
{
"fieldId": "email",
"viewId": "6095799e-b48f-4e00-b071-10818083593a",
"position": 1,
"isVisible": true,
"size": 150
},
{
"fieldId": "company",
"viewId": "6095799e-b48f-4e00-b071-10818083593a",
"position": 2,
"isVisible": true,
"size": 150
},
{
"fieldId": "phone",
"viewId": "6095799e-b48f-4e00-b071-10818083593a",
"position": 3,
"isVisible": true,
"size": 150
},
{
"fieldId": "createdAt",
"viewId": "6095799e-b48f-4e00-b071-10818083593a",
"position": 4,
"isVisible": true,
"size": 150
},
{
"fieldId": "city",
"viewId": "6095799e-b48f-4e00-b071-10818083593a",
"position": 5,
"isVisible": true,
"size": 150
},
{
"fieldId": "jobTitle",
"viewId": "6095799e-b48f-4e00-b071-10818083593a",
"position": 6,
"isVisible": true,
"size": 150
},
{
"fieldId": "linkedin",
"viewId": "6095799e-b48f-4e00-b071-10818083593a",
"position": 7,
"isVisible": true,
"size": 150
},
{
"fieldId": "x",
"viewId": "6095799e-b48f-4e00-b071-10818083593a",
"position": 8,
"isVisible": true,
"size": 150
},
{
"fieldId": "amount",
"viewId": "e26f66b7-f890-4a5c-b4d2-ec09987b5308",
"position": 0,
"isVisible": true,
"size": 180
},
{
"fieldId": "probability",
"viewId": "e26f66b7-f890-4a5c-b4d2-ec09987b5308",
"position": 1,
"isVisible": true,
"size": 150
},
{
"fieldId": "closeDate",
"viewId": "e26f66b7-f890-4a5c-b4d2-ec09987b5308",
"position": 2,
"isVisible": true,
"size": 100
},
{
"fieldId": "company",
"viewId": "e26f66b7-f890-4a5c-b4d2-ec09987b5308",
"position": 3,
"isVisible": true,
"size": 150
},
{
"fieldId": "createdAt",
"viewId": "e26f66b7-f890-4a5c-b4d2-ec09987b5308",
"position": 4,
"isVisible": true,
"size": 150
},
{
"fieldId": "pointOfContact",
"viewId": "e26f66b7-f890-4a5c-b4d2-ec09987b5308",
"position": 5,
"isVisible": true,
"size": 150
}
]

View File

@ -1,66 +0,0 @@
{
"nameSingular": "viewFilterV2",
"namePlural": "viewFiltersV2",
"labelSingular": "View Filter",
"labelPlural": "View Filters",
"targetTableName": "viewFilter",
"description": "(System) View Filters",
"icon": "IconFilterBolt",
"fields": [
{
"type": "text",
"name": "fieldId",
"label": "Field Id",
"targetColumnMap": {
"value": "fieldId"
},
"description": "View Filter target field",
"icon": null,
"isNullable": false
},
{
"type": "text",
"name": "viewId",
"label": "View Id",
"targetColumnMap": {
"value": "viewId"
},
"description": "View Filter related view",
"icon": null,
"isNullable": false
},
{
"type": "text",
"name": "operand",
"label": "Operand",
"targetColumnMap": {
"value": "operand"
},
"description": "View Filter operand",
"icon": null,
"isNullable": false
},
{
"type": "text",
"name": "value",
"label": "Value",
"targetColumnMap": {
"value": "value"
},
"description": "View Filter value",
"icon": null,
"isNullable": false
},
{
"type": "text",
"name": "displayValue",
"label": "Display Value",
"targetColumnMap": {
"value": "displayValue"
},
"description": "View Filter Display Value",
"icon": null,
"isNullable": false
}
]
}

View File

@ -1,44 +0,0 @@
{
"nameSingular": "viewSortV2",
"namePlural": "viewSortsV2",
"labelSingular": "View Sort",
"labelPlural": "View Sorts",
"targetTableName": "viewSort",
"description": "(System) View Sorts",
"icon": "IconArrowsSort",
"fields": [
{
"type": "text",
"name": "fieldId",
"label": "Field Id",
"targetColumnMap": {
"value": "fieldId"
},
"description": "View Sort target field",
"icon": null,
"isNullable": false
},
{
"type": "text",
"name": "viewId",
"label": "View Id",
"targetColumnMap": {
"value": "viewId"
},
"description": "View Sort related view",
"icon": null,
"isNullable": false
},
{
"type": "text",
"name": "direction",
"label": "Direction",
"targetColumnMap": {
"value": "direction"
},
"description": "View Sort direction",
"icon": null,
"isNullable": false
}
]
}

View File

@ -1,44 +0,0 @@
{
"nameSingular": "viewV2",
"namePlural": "viewsV2",
"labelSingular": "View",
"labelPlural": "Views",
"targetTableName": "view",
"description": "(System) Views",
"icon": "IconLayoutCollage",
"fields": [
{
"type": "text",
"name": "name",
"label": "Name",
"targetColumnMap": {
"value": "name"
},
"description": "View name",
"icon": null,
"isNullable": false
},
{
"type": "text",
"name": "objectId",
"label": "Object Id",
"targetColumnMap": {
"value": "objectId"
},
"description": "View target object",
"icon": null,
"isNullable": false
},
{
"type": "text",
"name": "type",
"label": "Type",
"targetColumnMap": {
"value": "type"
},
"description": "View type",
"icon": null,
"isNullable": false
}
]
}

View File

@ -1,26 +0,0 @@
[
{
"id": "37a8a866-eb17-4e76-9382-03143a2f6a80",
"name": "All companies",
"objectId": "company",
"type": "table"
},
{
"id": "6095799e-b48f-4e00-b071-10818083593a",
"name": "All people",
"objectId": "person",
"type": "table"
},
{
"id": "e26f66b7-f890-4a5c-b4d2-ec09987b5308",
"name": "All opportunities",
"objectId": "company",
"type": "kanban"
},
{
"id": "10bec73c-0aea-4cc4-a3b2-8c2186f29b43",
"name": "All Companies (V2)",
"objectId": "1a8487a0-480c-434e-b4c7-e22408b97047",
"type": "table"
}
]

View File

@ -3,9 +3,8 @@ import { Module } from '@nestjs/common';
import { DataSourceModule } from 'src/metadata/data-source/data-source.module';
import { MigrationRunnerModule } from 'src/metadata/migration-runner/migration-runner.module';
import { TenantMigrationModule } from 'src/metadata/tenant-migration/tenant-migration.module';
import { FieldMetadataModule } from 'src/metadata/field-metadata/field-metadata.module';
import { ObjectMetadataModule } from 'src/metadata/object-metadata/object-metadata.module';
import { DataSourceMetadataModule } from 'src/metadata/data-source-metadata/data-source-metadata.module';
import { ObjectMetadataModule } from 'src/metadata/object-metadata/object-metadata.module';
import { TenantInitialisationService } from './tenant-initialisation.service';
@ -15,7 +14,6 @@ import { TenantInitialisationService } from './tenant-initialisation.service';
TenantMigrationModule,
MigrationRunnerModule,
ObjectMetadataModule,
FieldMetadataModule,
DataSourceMetadataModule,
],
exports: [TenantInitialisationService],

View File

@ -3,15 +3,11 @@ import { Injectable } from '@nestjs/common';
import { TenantMigrationService } from 'src/metadata/tenant-migration/tenant-migration.service';
import { MigrationRunnerService } from 'src/metadata/migration-runner/migration-runner.service';
import { DataSourceService } from 'src/metadata/data-source/data-source.service';
import { FieldMetadataService } from 'src/metadata/field-metadata/services/field-metadata.service';
import { ObjectMetadataService } from 'src/metadata/object-metadata/services/object-metadata.service';
import { DataSourceMetadataService } from 'src/metadata/data-source-metadata/data-source-metadata.service';
import { FieldMetadata } from 'src/metadata/field-metadata/field-metadata.entity';
import { ObjectMetadata } from 'src/metadata/object-metadata/object-metadata.entity';
import { ObjectMetadataService } from 'src/metadata/object-metadata/services/object-metadata.service';
import { DataSourceMetadata } from 'src/metadata/data-source-metadata/data-source-metadata.entity';
import { standardObjectsMetadata } from './standard-objects/standard-object-metadata';
import { standardObjectsSeeds } from './standard-objects/standard-object-seeds';
import { standardObjectsPrefillData } from './standard-objects-prefill-data/standard-objects-prefill-data';
@Injectable()
export class TenantInitialisationService {
@ -20,7 +16,6 @@ export class TenantInitialisationService {
private readonly tenantMigrationService: TenantMigrationService,
private readonly migrationRunnerService: MigrationRunnerService,
private readonly objectMetadataService: ObjectMetadataService,
private readonly fieldMetadataService: FieldMetadataService,
private readonly dataSourceMetadataService: DataSourceMetadataService,
) {}
@ -49,7 +44,7 @@ export class TenantInitialisationService {
workspaceId,
);
await this.createObjectsAndFieldsMetadata(
await this.objectMetadataService.createStandardObjectsAndFieldsMetadata(
dataSourceMetadata.id,
workspaceId,
);
@ -62,69 +57,22 @@ export class TenantInitialisationService {
/**
*
* Create all standard objects and fields metadata for a given workspace
* We are prefilling a few standard objects with data to make it easier for the user to get started.
*
* @param dataSourceMetadataId
* @param dataSourceMetadata
* @param workspaceId
*/
public async createObjectsAndFieldsMetadata(
dataSourceMetadataId: string,
workspaceId: string,
) {
const createdObjectMetadata = await this.objectMetadataService.createMany(
Object.values(standardObjectsMetadata).map((objectMetadata) => ({
...objectMetadata,
dataSourceId: dataSourceMetadataId,
fields: [],
workspaceId,
isCustom: false,
isActive: true,
})),
);
await this.fieldMetadataService.createMany(
createdObjectMetadata.flatMap((objectMetadata: ObjectMetadata) =>
standardObjectsMetadata[objectMetadata.nameSingular].fields.map(
(field: FieldMetadata) => ({
...field,
objectId: objectMetadata.id,
dataSourceId: dataSourceMetadataId,
workspaceId,
isCustom: false,
isActive: true,
}),
),
),
);
}
public async prefillWorkspaceWithStandardObjects(
private async prefillWorkspaceWithStandardObjects(
dataSourceMetadata: DataSourceMetadata,
workspaceId: string,
) {
const objects =
await this.objectMetadataService.getObjectMetadataFromDataSourceId(
dataSourceMetadata.id,
);
const workspaceDataSource =
await this.dataSourceService.connectToWorkspaceDataSource(workspaceId);
for (const object of objects) {
const seedData = standardObjectsSeeds[object.nameSingular];
if (!seedData) {
continue;
}
const columns = Object.keys(seedData[0]);
await workspaceDataSource
?.createQueryBuilder()
.insert()
.into(`${dataSourceMetadata.schema}.${object.targetTableName}`, columns)
.values(seedData)
.execute();
if (!workspaceDataSource) {
throw new Error('Could not connect to workspace data source');
}
standardObjectsPrefillData(workspaceDataSource, dataSourceMetadata.schema);
}
}