Refactored and improved seeds (#8695)

- Added a new Seeder service to help with custom object seeds
- Added RichTextFieldInput to edit a rich text field directly on the
table, but deactivated it for now.
This commit is contained in:
Lucas Bordeau
2024-12-24 14:44:52 +01:00
committed by GitHub
parent 4f329d6005
commit e9717603f2
52 changed files with 5807 additions and 86 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,7 @@
import { CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
import { CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input';
export type ObjectMetadataSeed = Omit<
CreateObjectInput,
'workspaceId' | 'dataSourceId'
> & { fields: Omit<CreateFieldInput, 'objectMetadataId' | 'workspaceId'>[] };
@@ -0,0 +1,41 @@
import { ObjectMetadataSeed } from 'src/engine/seeder/interfaces/object-metadata-seed';
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
export const PETS_METADATA_SEEDS: ObjectMetadataSeed = {
labelPlural: 'Pets',
labelSingular: 'Pet',
namePlural: 'pets',
nameSingular: 'pet',
icon: 'IconCat',
fields: [
{
type: FieldMetadataType.SELECT,
label: 'Species',
name: 'species',
options: [
{ label: 'Dog', value: 'dog', position: 0, color: 'blue' },
{ label: 'Cat', value: 'cat', position: 1, color: 'red' },
{ label: 'Bird', value: 'bird', position: 2, color: 'green' },
{ label: 'Fish', value: 'fish', position: 3, color: 'yellow' },
{ label: 'Rabbit', value: 'rabbit', position: 4, color: 'purple' },
{ label: 'Hamster', value: 'hamster', position: 5, color: 'orange' },
],
},
{
type: FieldMetadataType.TEXT,
label: 'Comments',
name: 'comments',
},
{
type: FieldMetadataType.NUMBER,
label: 'Age',
name: 'age',
},
{
type: FieldMetadataType.ADDRESS,
label: 'Location',
name: 'location',
},
],
};
@@ -0,0 +1,72 @@
import {
FieldMetadataNumberSettings,
FieldMetadataTextSettings,
NumberDataType,
} from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
import { ObjectMetadataSeed } from 'src/engine/seeder/interfaces/object-metadata-seed';
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
export const SURVEY_RESULTS_METADATA_SEEDS: ObjectMetadataSeed = {
labelPlural: 'Survey results',
labelSingular: 'Survey result',
namePlural: 'surveyResults',
nameSingular: 'surveyResult',
icon: 'IconRulerMeasure',
fields: [
{
type: FieldMetadataType.NUMBER,
label: 'Score (Float 3 decimals)',
name: 'score',
settings: {
dataType: NumberDataType.FLOAT,
decimals: 3,
type: 'number',
} as FieldMetadataNumberSettings,
},
{
type: FieldMetadataType.NUMBER,
label: 'Percentage of completion (Float 3 decimals + percentage)',
name: 'percentageOfCompletion',
settings: {
dataType: NumberDataType.FLOAT,
decimals: 6,
type: 'percentage',
} as FieldMetadataNumberSettings,
},
{
type: FieldMetadataType.NUMBER,
label: 'Participants (Int)',
name: 'participants',
settings: {
dataType: NumberDataType.INT,
type: 'number',
} as FieldMetadataNumberSettings,
},
{
type: FieldMetadataType.NUMBER,
label: 'Average estimated number of atoms in the universe (BigInt)',
name: 'averageEstimatedNumberOfAtomsInTheUniverse',
settings: {
dataType: NumberDataType.BIGINT,
type: 'number',
} as FieldMetadataNumberSettings,
},
{
type: FieldMetadataType.TEXT,
label: 'Comments (Max 5 rows)',
name: 'comments',
settings: {
displayedMaxRows: 5,
} as FieldMetadataTextSettings,
},
{
type: FieldMetadataType.TEXT,
label: 'Short notes (Max 1 row)',
name: 'shortNotes',
settings: {
displayedMaxRows: 1,
} as FieldMetadataTextSettings,
},
],
};
@@ -0,0 +1,17 @@
import { Module } from '@nestjs/common';
import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module';
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
import { SeederService } from 'src/engine/seeder/seeder.service';
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
@Module({
imports: [
ObjectMetadataModule,
FieldMetadataModule,
WorkspaceDataSourceModule,
],
exports: [SeederService],
providers: [SeederService],
})
export class SeederModule {}
@@ -0,0 +1,161 @@
import { Injectable } from '@nestjs/common';
import { ObjectMetadataSeed } from 'src/engine/seeder/interfaces/object-metadata-seed';
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/field-metadata.service';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { capitalize } from 'src/utils/capitalize';
import { isDefined } from 'src/utils/is-defined';
@Injectable()
export class SeederService {
constructor(
private readonly objectMetadataService: ObjectMetadataService,
private readonly fieldMetadataService: FieldMetadataService,
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
) {}
public async seedCustomObjects(
dataSourceId: string,
workspaceId: string,
metadataSeeds: ObjectMetadataSeed,
dataSeeds: Record<string, any>[],
): Promise<void> {
const createdObjectMetadata = await this.objectMetadataService.createOne({
...metadataSeeds,
dataSourceId,
workspaceId,
});
if (!createdObjectMetadata) {
throw new Error("Object metadata couldn't be created");
}
for (const customField of metadataSeeds.fields) {
await this.fieldMetadataService.createOne({
...customField,
objectMetadataId: createdObjectMetadata.id,
workspaceId,
});
}
const objectMetadataAfterFieldCreation =
await this.objectMetadataService.findOneWithinWorkspace(workspaceId, {
where: { nameSingular: metadataSeeds.nameSingular },
});
if (!objectMetadataAfterFieldCreation) {
throw new Error(
"Object metadata couldn't be found after field creation.",
);
}
const schemaName =
this.workspaceDataSourceService.getSchemaName(workspaceId);
const workspaceDataSource =
await this.workspaceDataSourceService.connectToWorkspaceDataSource(
workspaceId,
);
const entityManager = workspaceDataSource.createEntityManager();
const filteredFields = metadataSeeds.fields.filter((field) =>
objectMetadataAfterFieldCreation.fields.some(
(f) => f.name === field.name || f.name === `name`,
),
);
if (filteredFields.length === 0) {
throw new Error('No fields found for seeding, check metadata');
}
filteredFields.unshift({
name: 'name',
type: FieldMetadataType.TEXT,
label: 'Name',
});
const fieldMetadataMap = filteredFields
.map((field) => {
if (isCompositeFieldMetadataType(field.type)) {
const compositeFieldTypeDefinition = compositeTypeDefinitions.get(
field.type,
);
if (!isDefined(compositeFieldTypeDefinition)) {
throw new Error(
`Composite field type definition not found for ${field.type}`,
);
}
const fieldNames = compositeFieldTypeDefinition.properties?.map(
(property) => property.name,
);
return (
fieldNames?.map(
(subFieldName: string) =>
`${field.name}${capitalize(subFieldName)}`,
) ?? []
);
} else {
return field.name;
}
})
.flat()
.filter(isDefined);
const flattenedSeeds = dataSeeds.map((seed) => {
const flattenedSeed = {};
for (const field of filteredFields) {
if (isCompositeFieldMetadataType(field.type)) {
const compositeFieldTypeDefinition = compositeTypeDefinitions.get(
field.type,
);
if (!isDefined(compositeFieldTypeDefinition)) {
throw new Error(
`Composite field type definition not found for ${field.type}`,
);
}
const fieldNames = compositeFieldTypeDefinition.properties
?.map((property) => property.name)
.filter(isDefined);
for (const subFieldName of fieldNames) {
flattenedSeed[`${field.name}${capitalize(subFieldName)}`] =
seed?.[field.name]?.[subFieldName];
}
} else {
flattenedSeed[field.name] = seed[field.name];
}
}
return flattenedSeed;
});
await entityManager
.createQueryBuilder()
.insert()
.into(`${schemaName}._${objectMetadataAfterFieldCreation.nameSingular}`, [
...fieldMetadataMap,
'position',
])
.orIgnore()
.values(
flattenedSeeds.map((flattenedSeed, index) => ({
...flattenedSeed,
position: index,
})),
)
.returning('*')
.execute();
}
}