Fix/metadata object and settings post merge (#2269)

* WIP

* WIP2

* Seed views standard objects

* Migrate views to the new data model

---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
Charles Bochet
2023-10-28 12:25:43 +02:00
committed by GitHub
parent afd4b7c634
commit b591023eb3
30 changed files with 609 additions and 208 deletions
-47
View File
@@ -12,51 +12,4 @@ export const seedMetadata = async (prisma: PrismaClient) => {
'b37b2163-7f63-47a9-b1b3-6c7290ca9fb1', 'workspace_twenty_7icsva0r6s00mpcp6cwg4w4rd', 'postgres', 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419'
) ON CONFLICT DO NOTHING`,
);
await prisma.$queryRawUnsafe(`CREATE TABLE IF NOT EXISTS
workspace_twenty_7icsva0r6s00mpcp6cwg4w4rd.company(
"id" TEXT PRIMARY KEY,
"name" TEXT NOT NULL,
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
"deletedAt" TIMESTAMP WITH TIME ZONE,
"domainName" TEXT NOT NULL,
"address" TEXT NOT NULL,
"employees" INTEGER NOT NULL
);
`);
await prisma.$queryRawUnsafe(`INSERT INTO workspace_twenty_7icsva0r6s00mpcp6cwg4w4rd.company(
"id", "name", "domainName", "address", "employees"
)
VALUES (
'89bb825c-171e-4bcc-9cf7-43448d6fb278', 'Airbnb', 'airbnb.com', 'San Francisco', 5000
), (
'04b2e9f5-0713-40a5-8216-82802401d33e', 'Qonto', 'qonto.com', 'San Francisco', 800
), (
'118995f3-5d81-46d6-bf83-f7fd33ea6102', 'Facebook', 'facebook.com', 'San Francisco', 8000
), (
'460b6fb1-ed89-413a-b31a-962986e67bb4', 'Microsoft', 'microsoft.com', 'San Francisco', 800
), (
'fe256b39-3ec3-4fe3-8997-b76aa0bfa408', 'Linkedin', 'linkedin.com', 'San Francisco', 400
) ON CONFLICT DO NOTHING`);
await prisma.$queryRawUnsafe(`INSERT INTO metadata.object_metadata(
id, name_singular, name_plural, label_singular, label_plural, description, icon, target_table_name, is_custom, is_active, workspace_id, data_source_id
)
VALUES (
'ba391617-ee08-432f-9438-2e17df5ac279', 'companyV2', 'companiesV2', 'Company', 'Companies', 'Companies', 'IconBuilding', 'company', false, true, 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419', 'b37b2163-7f63-47a9-b1b3-6c7290ca9fb1'
) ON CONFLICT DO NOTHING`);
await prisma.$queryRawUnsafe(`INSERT INTO metadata.field_metadata(
id, object_id, type, name, label, target_column_map, description, icon, enums, is_custom, is_active, is_nullable, workspace_id
)
VALUES (
'22f5906d-153f-448c-b254-28adce721dcd', 'ba391617-ee08-432f-9438-2e17df5ac279', 'text', 'name', 'Name', '{"value": "name"}', 'Name', 'IconUser', NULL, false, true, false, 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419'
), (
'19bfab29-1cbb-4ce2-9117-8540ac45a0f1', 'ba391617-ee08-432f-9438-2e17df5ac279', 'text', 'domainName', 'Domain Name', '{"value": "domainName"}', 'Domain Name', 'IconExternalLink', NULL, false, true, true, 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419'
), (
'70130f27-9497-4b44-a04c-1a0fb9a4829c', 'ba391617-ee08-432f-9438-2e17df5ac279', 'text', 'address', 'Address', '{"value": "address"}', 'Address', 'IconMap', NULL, false, true, true, 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419'
), (
'2a63c30e-8e80-475b-b5d7-9dda17adc537', 'ba391617-ee08-432f-9438-2e17df5ac279', 'number', 'employees', 'Employees', '{"value": "employees"}', 'Employees', 'IconUsers', NULL, false, true, true, 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419'
) ON CONFLICT DO NOTHING`);
};
@@ -0,0 +1,47 @@
import { Command, CommandRunner, Option } from 'nest-commander';
import { TenantInitialisationService } from '../tenant-initialisation/tenant-initialisation.service';
import { DataSourceMetadataService } from '../data-source-metadata/data-source-metadata.service';
// TODO: implement dry-run
interface DataSeedTenantOptions {
workspaceId: string;
}
@Command({
name: 'tenant:data-seed',
description: 'Seed tenant with initial data',
})
export class DataSeedTenantCommand extends CommandRunner {
constructor(
private readonly dataSourceMetadataService: DataSourceMetadataService,
private readonly tenantInitialisationService: TenantInitialisationService,
) {
super();
}
async run(
_passedParam: string[],
options: DataSeedTenantOptions,
): Promise<void> {
const dataSourceMetadata =
await this.dataSourceMetadataService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
options.workspaceId,
);
// TODO: run in a dedicated job + run queries in a transaction.
await this.tenantInitialisationService.prefillWorkspaceWithStandardObjects(
dataSourceMetadata,
options.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;
}
}
@@ -9,6 +9,7 @@ import { DataSourceMetadataModule } from 'src/metadata/data-source-metadata/data
import { SyncTenantMetadataCommand } from './sync-tenant-metadata.command';
import { RunTenantMigrationsCommand } from './run-tenant-migrations.command';
import { DataSeedTenantCommand } from './data-seed-tenant.command';
@Module({
imports: [
@@ -19,6 +20,10 @@ import { RunTenantMigrationsCommand } from './run-tenant-migrations.command';
DataSourceMetadataModule,
TenantInitialisationModule,
],
providers: [RunTenantMigrationsCommand, SyncTenantMetadataCommand],
providers: [
RunTenantMigrationsCommand,
SyncTenantMetadataCommand,
DataSeedTenantCommand,
],
})
export class MetadataCommandModule {}
@@ -1,7 +1,6 @@
import { Command, CommandRunner, Option } from 'nest-commander';
import { ObjectMetadataService } from 'src/metadata/object-metadata/services/object-metadata.service';
import { FieldMetadataService } from 'src/metadata/field-metadata/services/field-metadata.service';
import { TenantInitialisationService } from 'src/metadata/tenant-initialisation/tenant-initialisation.service';
import { DataSourceMetadataService } from 'src/metadata/data-source-metadata/data-source-metadata.service';
@@ -17,7 +16,6 @@ interface RunTenantMigrationsOptions {
export class SyncTenantMetadataCommand extends CommandRunner {
constructor(
private readonly objectMetadataService: ObjectMetadataService,
private readonly fieldMetadataService: FieldMetadataService,
private readonly dataSourceMetadataService: DataSourceMetadataService,
private readonly tenantInitialisationService: TenantInitialisationService,
) {
@@ -1,4 +1,5 @@
{
"id": "1a8487a0-480c-434e-b4c7-e22408b97047",
"nameSingular": "companyV2",
"namePlural": "companiesV2",
"labelSingular": "Company",
@@ -1,7 +1,11 @@
import companyObject from './companies/companies.metadata.json';
import personObject from './people/people.metadata.json';
import viewObject from './views/views.metadata.json';
import viewFieldObject from './view-fields/view-fields.metadata.json';
export const standardObjectsMetadata = {
companyV2: companyObject,
personV2: personObject,
viewV2: viewObject,
viewFieldV2: viewFieldObject,
};
@@ -1,5 +1,9 @@
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,
};
@@ -0,0 +1,77 @@
{
"nameSingular": "viewFieldV2",
"namePlural": "viewFieldsV2",
"labelSingular": "View Field",
"labelPlural": "View Fields",
"targetTableName": "viewField",
"description": "(System) View Fields",
"icon": "arrows-sort",
"fields": [
{
"type": "text",
"name": "objectId",
"label": "Object Id",
"targetColumnMap": {
"value": "objectId"
},
"description": "View Field target object",
"icon": null,
"isNullable": false
},
{
"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
}
]
}
@@ -0,0 +1,43 @@
[
{
"objectId": "1a8487a0-480c-434e-b4c7-e22408b97047",
"fieldId": "name",
"viewId": "10bec73c-0aea-4cc4-a3b2-8c2186f29b43",
"position": 0,
"isVisible": true,
"size": 180
},
{
"objectId": "company",
"fieldId": "name",
"viewId": "37a8a866-eb17-4e76-9382-03143a2f6a80",
"position": 0,
"isVisible": true,
"size": 180
},
{
"objectId": "company",
"fieldId": "domainName",
"viewId": "37a8a866-eb17-4e76-9382-03143a2f6a80",
"position": 1,
"isVisible": true,
"size": 100
},
{
"objectId": "company",
"fieldId": "accountOwner",
"viewId": "37a8a866-eb17-4e76-9382-03143a2f6a80",
"position": 2,
"isVisible": true,
"size": 150
},
{
"objectId": "company",
"fieldId": "createdAt",
"viewId": "37a8a866-eb17-4e76-9382-03143a2f6a80",
"position": 3,
"isVisible": true,
"size": 150
}
]
@@ -0,0 +1,44 @@
{
"nameSingular": "viewV2",
"namePlural": "viewsV2",
"labelSingular": "View",
"labelPlural": "Views",
"targetTableName": "view",
"description": "(System) Views",
"icon": "layout-collage",
"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
}
]
}
@@ -0,0 +1,26 @@
[
{
"id": "37a8a866-eb17-4e76-9382-03143a2f6a80",
"name": "All companies",
"objectId": "company",
"type": "Table"
},
{
"id": "6095799e-b48f-4e00-b071-10818083593a",
"name": "All companies",
"objectId": "person",
"type": "Table"
},
{
"id": "e26f66b7-f890-4a5c-b4d2-ec09987b5308",
"name": "All Opportunities",
"objectId": "company",
"type": "Pipeline"
},
{
"id": "10bec73c-0aea-4cc4-a3b2-8c2186f29b43",
"name": "All Companies (V2)",
"objectId": "1a8487a0-480c-434e-b4c7-e22408b97047",
"type": "Table"
}
]
@@ -98,7 +98,7 @@ export class TenantInitialisationService {
);
}
private async prefillWorkspaceWithStandardObjects(
public async prefillWorkspaceWithStandardObjects(
dataSourceMetadata: DataSourceMetadata,
workspaceId: string,
) {
@@ -117,11 +117,7 @@ export class TenantInitialisationService {
continue;
}
const fields = standardObjectsMetadata[object.nameSingular].fields;
const columns = fields.map((field: FieldMetadata) =>
Object.values(field.targetColumnMap),
);
const columns = Object.keys(seedData[0]);
await workspaceDataSource
?.createQueryBuilder()
@@ -0,0 +1,29 @@
import { TenantMigrationTableAction } from 'src/metadata/tenant-migration/tenant-migration.entity';
export const addViewTable: TenantMigrationTableAction[] = [
{
name: 'view',
action: 'create',
},
{
name: 'view',
action: 'alter',
columns: [
{
name: 'name',
type: 'varchar',
action: 'create',
},
{
name: 'objectId',
type: 'varchar',
action: 'create',
},
{
name: 'type',
type: 'varchar',
action: 'create',
},
],
},
];
@@ -0,0 +1,44 @@
import { TenantMigrationTableAction } from 'src/metadata/tenant-migration/tenant-migration.entity';
export const addViewFieldTable: TenantMigrationTableAction[] = [
{
name: 'viewField',
action: 'create',
},
{
name: 'viewField',
action: 'alter',
columns: [
{
name: 'objectId',
type: 'varchar',
action: 'create',
},
{
name: 'fieldId',
type: 'varchar',
action: 'create',
},
{
name: 'viewId',
type: 'varchar',
action: 'create',
},
{
name: 'position',
type: 'integer',
action: 'create',
},
{
name: 'isVisible',
type: 'boolean',
action: 'create',
},
{
name: 'size',
type: 'integer',
action: 'create',
},
],
},
];
@@ -1,8 +1,12 @@
import { addCompanyTable } from './migrations/1697618009-addCompanyTable';
import { addPeopleTable } from './migrations/1697618010-addPeopleTable';
import { addViewTable } from './migrations/1697618011-addViewTable';
import { addViewFieldTable } from './migrations/1697618012-addViewFieldTable';
// TODO: read the folder and return all migrations
export const standardMigrations = {
'1697618009-addCompanyTable': addCompanyTable,
'1697618010-addPeopleTable': addPeopleTable,
'1697618011-addViewTable': addViewTable,
'1697618012-addViewFieldTable': addViewFieldTable,
};