fix: when field metadata SELECT type is edited update view groups (#8344)
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -15,6 +15,7 @@ import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dto
|
||||
import { FieldMetadataValidationService } from 'src/engine/metadata-modules/field-metadata/field-metadata-validation.service';
|
||||
import { FieldMetadataResolver } from 'src/engine/metadata-modules/field-metadata/field-metadata.resolver';
|
||||
import { FieldMetadataGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/field-metadata/interceptors/field-metadata-graphql-api-exception.interceptor';
|
||||
import { FieldMetadataRelatedRecordsService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata-related-records.service';
|
||||
import { IsFieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/validators/is-field-metadata-default-value.validator';
|
||||
import { IsFieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/validators/is-field-metadata-options.validator';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
@ -48,6 +49,7 @@ import { UpdateFieldInput } from './dtos/update-field.input';
|
||||
services: [
|
||||
IsFieldMetadataDefaultValue,
|
||||
FieldMetadataService,
|
||||
FieldMetadataRelatedRecordsService,
|
||||
FieldMetadataValidationService,
|
||||
],
|
||||
resolvers: [
|
||||
|
||||
@ -21,6 +21,7 @@ import {
|
||||
FieldMetadataException,
|
||||
FieldMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||
import { FieldMetadataRelatedRecordsService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata-related-records.service';
|
||||
import { assertDoesNotNullifyDefaultValueForNonNullableField } from 'src/engine/metadata-modules/field-metadata/utils/assert-does-not-nullify-default-value-for-non-nullable-field.util';
|
||||
import {
|
||||
computeColumnName,
|
||||
@ -28,6 +29,7 @@ import {
|
||||
} from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||
import { generateNullable } from 'src/engine/metadata-modules/field-metadata/utils/generate-nullable';
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
import { isSelectFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-select-field-metadata-type.util';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||
import {
|
||||
@ -83,6 +85,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly fieldMetadataValidationService: FieldMetadataValidationService,
|
||||
private readonly fieldMetadataRelatedRecordsService: FieldMetadataRelatedRecordsService,
|
||||
) {
|
||||
super(fieldMetadataRepository);
|
||||
}
|
||||
@ -418,6 +421,16 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
updatedFieldMetadata.isActive &&
|
||||
isSelectFieldMetadataType(updatedFieldMetadata.type)
|
||||
) {
|
||||
await this.fieldMetadataRelatedRecordsService.updateRelatedViewGroups(
|
||||
existingFieldMetadata,
|
||||
updatedFieldMetadata,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
fieldMetadataInput.name ||
|
||||
updatableFieldInput.options ||
|
||||
|
||||
@ -0,0 +1,151 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { In } from 'typeorm';
|
||||
|
||||
import {
|
||||
FieldMetadataComplexOption,
|
||||
FieldMetadataDefaultOption,
|
||||
} from 'src/engine/metadata-modules/field-metadata/dtos/options.input';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { isSelectFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-select-field-metadata-type.util';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { ViewGroupWorkspaceEntity } from 'src/modules/view/standard-objects/view-group.workspace-entity';
|
||||
import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity';
|
||||
|
||||
type Differences<T> = {
|
||||
created: T[];
|
||||
updated: { old: T; new: T }[];
|
||||
deleted: T[];
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class FieldMetadataRelatedRecordsService {
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {}
|
||||
|
||||
public async updateRelatedViewGroups(
|
||||
oldFieldMetadata: FieldMetadataEntity,
|
||||
newFieldMetadata: FieldMetadataEntity,
|
||||
) {
|
||||
if (
|
||||
!isSelectFieldMetadataType(newFieldMetadata.type) ||
|
||||
!isSelectFieldMetadataType(oldFieldMetadata.type)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const views = await this.getFieldMetadataViews(newFieldMetadata);
|
||||
|
||||
const { created, updated, deleted } = this.getOptionsDifferences(
|
||||
oldFieldMetadata.options,
|
||||
newFieldMetadata.options,
|
||||
);
|
||||
|
||||
const viewGroupRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ViewGroupWorkspaceEntity>(
|
||||
newFieldMetadata.workspaceId,
|
||||
'viewGroup',
|
||||
);
|
||||
|
||||
for (const view of views) {
|
||||
const maxPosition = view.viewGroups.reduce(
|
||||
(max, viewGroup) => Math.max(max, viewGroup.position),
|
||||
0,
|
||||
);
|
||||
|
||||
const viewGroupsToCreate = created.map((option, index) =>
|
||||
viewGroupRepository.create({
|
||||
fieldMetadataId: newFieldMetadata.id,
|
||||
fieldValue: option.value,
|
||||
position: maxPosition + index,
|
||||
isVisible: true,
|
||||
viewId: view.id,
|
||||
}),
|
||||
);
|
||||
|
||||
await viewGroupRepository.insert(viewGroupsToCreate);
|
||||
|
||||
for (const { old: oldOption, new: newOption } of updated) {
|
||||
const viewGroup = view.viewGroups.find(
|
||||
(viewGroup) => viewGroup.fieldValue === oldOption.value,
|
||||
);
|
||||
|
||||
if (!viewGroup) {
|
||||
throw new Error(`View group not found for option ${oldOption.value}`);
|
||||
}
|
||||
|
||||
await viewGroupRepository.update(
|
||||
{
|
||||
id: viewGroup.id,
|
||||
},
|
||||
{
|
||||
fieldValue: newOption.value,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const valuesToDelete = deleted.map((option) => option.value);
|
||||
|
||||
await viewGroupRepository.delete({
|
||||
fieldMetadataId: newFieldMetadata.id,
|
||||
fieldValue: In(valuesToDelete),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private getOptionsDifferences(
|
||||
oldOptions: (FieldMetadataDefaultOption | FieldMetadataComplexOption)[],
|
||||
newOptions: (FieldMetadataDefaultOption | FieldMetadataComplexOption)[],
|
||||
) {
|
||||
const differences: Differences<
|
||||
FieldMetadataDefaultOption | FieldMetadataComplexOption
|
||||
> = {
|
||||
created: [],
|
||||
updated: [],
|
||||
deleted: [],
|
||||
};
|
||||
|
||||
const oldOptionsMap = new Map(
|
||||
oldOptions.map((option) => [option.id, option]),
|
||||
);
|
||||
const newOptionsMap = new Map(
|
||||
newOptions.map((option) => [option.id, option]),
|
||||
);
|
||||
|
||||
for (const newOption of newOptions) {
|
||||
const oldOption = oldOptionsMap.get(newOption.id);
|
||||
|
||||
if (!oldOption) {
|
||||
differences.created.push(newOption);
|
||||
} else if (oldOption.value !== newOption.value) {
|
||||
differences.updated.push({ old: oldOption, new: newOption });
|
||||
}
|
||||
}
|
||||
|
||||
for (const oldOption of oldOptions) {
|
||||
if (!newOptionsMap.has(oldOption.id)) {
|
||||
differences.deleted.push(oldOption);
|
||||
}
|
||||
}
|
||||
|
||||
return differences;
|
||||
}
|
||||
|
||||
private async getFieldMetadataViews(
|
||||
fieldMetadata: FieldMetadataEntity,
|
||||
): Promise<ViewWorkspaceEntity[]> {
|
||||
const viewRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ViewWorkspaceEntity>(
|
||||
fieldMetadata.workspaceId,
|
||||
'view',
|
||||
);
|
||||
|
||||
return await viewRepository.find({
|
||||
where: {
|
||||
kanbanFieldMetadataId: fieldMetadata.id,
|
||||
},
|
||||
relations: ['viewGroups'],
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
|
||||
export const isSelectFieldMetadataType = (
|
||||
type: FieldMetadataType,
|
||||
): type is FieldMetadataType.SELECT => {
|
||||
return type === FieldMetadataType.SELECT;
|
||||
};
|
||||
Reference in New Issue
Block a user