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

View File

@ -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'>[] };

View File

@ -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',
},
],
};

View File

@ -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,
},
],
};

View File

@ -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 {}

View File

@ -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();
}
}