[GQL_VIEW_FILTER_API_BREAKING_CHANGE][WHEN_RELEASED_REQUIRES_CACHE_FLUSH] ViewFilter migration to workspace migration v2 (#15010)
# Introduction Migrating `viewFilter` to v2 in order to migrate later the field update side effect on view to v2 too ## What's done - Created flat-view-filter - flat view filter runner - flat view filter builder - create view filter service v2 and input transpilers - refactor the existing view filter resolver to fix standard ( BREAKING_CHANGE on graphql api update especially ) REST stays the same - refactored the front to consume the mutations autogenerated ## New generic tools ### Compare two flat entity Introducing a new util to compare two flat entity, it's strictly typed and will be added to the generic builder in a following PR This will ease flat entity addition as won't required to create a specific abstraction for comparison Generic builder will expect specific constant: properties to compare and properties to stringify ### Transform flat entity for comparison Forked and refactor the initial existing method for flat entity business scope and type safety ## Coverage Migrated existing integration tests to fit new contract API This PR does not add strong coverage on validation exceptions Deadlines are too short close https://github.com/twentyhq/core-team-issues/issues/1666
This commit is contained in:
+1
@@ -24,6 +24,7 @@ export const throwOnFieldInputTranspilationsError: ThrowOnFieldInputTranspilatio
|
||||
throw new WorkspaceMigrationBuilderExceptionV2(
|
||||
{
|
||||
report: {
|
||||
viewFilter: [],
|
||||
fieldMetadata: [],
|
||||
index: [],
|
||||
view: [],
|
||||
|
||||
+5
-42
@@ -1,11 +1,8 @@
|
||||
import diff from 'microdiff';
|
||||
import { type FromTo } from 'twenty-shared/types';
|
||||
|
||||
import { compareTwoFlatEntity } from 'src/engine/core-modules/common/utils/compare-two-flat-entity.util';
|
||||
import { FLAT_VIEW_FIELD_PROPERTIES_TO_COMPARE } from 'src/engine/metadata-modules/flat-view-field/constants/flat-view-field-properties-to-compare.constant';
|
||||
import { type FlatViewFieldPropertiesToCompare } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field-properties-to-compare.type';
|
||||
import { type FlatViewField } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field.type';
|
||||
import { type UpdateViewFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-view-field-action-v2.type';
|
||||
import { transformMetadataForComparison } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util';
|
||||
|
||||
type GetWorkspaceMigrationUpdateViewFieldActionArgs = FromTo<
|
||||
FlatViewField,
|
||||
@@ -16,44 +13,10 @@ export const compareTwoFlatViewField = ({
|
||||
fromFlatViewField,
|
||||
toFlatViewField,
|
||||
}: GetWorkspaceMigrationUpdateViewFieldActionArgs) => {
|
||||
const transformMetadataForComparisonParameters = {
|
||||
shouldIgnoreProperty: (property: string) =>
|
||||
!FLAT_VIEW_FIELD_PROPERTIES_TO_COMPARE.includes(
|
||||
property as FlatViewFieldPropertiesToCompare,
|
||||
),
|
||||
return compareTwoFlatEntity({
|
||||
fromFlatEntity: fromFlatViewField,
|
||||
toFlatEntity: toFlatViewField,
|
||||
propertiesToStringify: [],
|
||||
};
|
||||
const fromCompare = transformMetadataForComparison(
|
||||
fromFlatViewField,
|
||||
transformMetadataForComparisonParameters,
|
||||
);
|
||||
const toCompare = transformMetadataForComparison(
|
||||
toFlatViewField,
|
||||
transformMetadataForComparisonParameters,
|
||||
);
|
||||
|
||||
const flatViewFieldDifferences = diff(fromCompare, toCompare);
|
||||
|
||||
return flatViewFieldDifferences.flatMap<
|
||||
UpdateViewFieldAction['updates'][number]
|
||||
>((difference) => {
|
||||
switch (difference.type) {
|
||||
case 'CHANGE': {
|
||||
const { oldValue, path, value } = difference;
|
||||
const property = path[0] as FlatViewFieldPropertiesToCompare;
|
||||
|
||||
return {
|
||||
from: oldValue,
|
||||
to: value,
|
||||
property,
|
||||
};
|
||||
}
|
||||
case 'CREATE':
|
||||
case 'REMOVE':
|
||||
default: {
|
||||
// Should never occurs, we should only provide null never undefined and so on
|
||||
return [];
|
||||
}
|
||||
}
|
||||
propertiesToCompare: FLAT_VIEW_FIELD_PROPERTIES_TO_COMPARE,
|
||||
});
|
||||
};
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { type FlatViewFilter } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter.type';
|
||||
|
||||
export const FLAT_VIEW_FILTER_EDITABLE_PROPERTIES = [
|
||||
'fieldMetadataId',
|
||||
'operand',
|
||||
'value',
|
||||
'viewFilterGroupId',
|
||||
'positionInViewFilterGroup',
|
||||
'subFieldName',
|
||||
] as const satisfies (keyof FlatViewFilter)[];
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import { type FlatViewFilter } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter.type';
|
||||
|
||||
export const FLAT_VIEW_FILTER_JSONB_PROPERTIES = [
|
||||
'value',
|
||||
] as const satisfies (keyof FlatViewFilter)[];
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import { FLAT_VIEW_FILTER_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-view-filter/constants/flat-view-filter-editable-properties.constant';
|
||||
import { type FlatViewFilter } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter.type';
|
||||
|
||||
export const FLAT_VIEW_FILTER_PROPERTIES_TO_COMPARE = [
|
||||
'viewId',
|
||||
'deletedAt',
|
||||
...FLAT_VIEW_FILTER_EDITABLE_PROPERTIES,
|
||||
] as const satisfies (keyof FlatViewFilter)[];
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import { type ViewFilterEntityRelationProperties } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter.type';
|
||||
|
||||
export const VIEW_FILTER_ENTITY_RELATION_PROPERTIES = [
|
||||
'fieldMetadata',
|
||||
'view',
|
||||
'viewFilterGroup',
|
||||
'workspace',
|
||||
] as const satisfies ViewFilterEntityRelationProperties[];
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator';
|
||||
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
|
||||
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
|
||||
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/core-modules/common/constant/empty-flat-entity-maps.constant';
|
||||
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
|
||||
import { type FlatViewFilterMaps } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter-maps.type';
|
||||
import { fromViewFilterEntityToFlatViewFilter } from 'src/engine/metadata-modules/flat-view-filter/utils/from-view-filter-entity-to-flat-view-filter.util';
|
||||
import { ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity';
|
||||
import { WorkspaceFlatMapCache } from 'src/engine/workspace-flat-map-cache/decorators/workspace-flat-map-cache.decorator';
|
||||
import { WorkspaceFlatMapCacheService } from 'src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache.service';
|
||||
|
||||
@Injectable()
|
||||
@WorkspaceFlatMapCache('flatViewFilterMaps')
|
||||
export class WorkspaceFlatViewFilterMapCacheService extends WorkspaceFlatMapCacheService<FlatViewFilterMaps> {
|
||||
constructor(
|
||||
@InjectCacheStorage(CacheStorageNamespace.EngineWorkspace)
|
||||
cacheStorageService: CacheStorageService,
|
||||
@InjectRepository(ViewFilterEntity)
|
||||
private readonly viewFilterRepository: Repository<ViewFilterEntity>,
|
||||
) {
|
||||
super(cacheStorageService);
|
||||
}
|
||||
|
||||
protected async computeFlatMap({
|
||||
workspaceId,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
}): Promise<FlatViewFilterMaps> {
|
||||
const viewFilters = await this.viewFilterRepository.find({
|
||||
where: {
|
||||
workspaceId,
|
||||
},
|
||||
withDeleted: true,
|
||||
});
|
||||
|
||||
return viewFilters.reduce((flatViewFilterMaps, viewFilterEntity) => {
|
||||
const flatViewFilter =
|
||||
fromViewFilterEntityToFlatViewFilter(viewFilterEntity);
|
||||
|
||||
return addFlatEntityToFlatEntityMapsOrThrow({
|
||||
flatEntity: flatViewFilter,
|
||||
flatEntityMaps: flatViewFilterMaps,
|
||||
});
|
||||
}, EMPTY_FLAT_ENTITY_MAPS);
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
|
||||
import { type FlatViewFilter } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter.type';
|
||||
|
||||
export type FlatViewFilterMaps = FlatEntityMaps<FlatViewFilter>;
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import { type FLAT_VIEW_FILTER_PROPERTIES_TO_COMPARE } from 'src/engine/metadata-modules/flat-view-filter/constants/flat-view-filter-properties-to-compare.constant';
|
||||
|
||||
export type FlatViewFilterPropertiesToCompare =
|
||||
(typeof FLAT_VIEW_FILTER_PROPERTIES_TO_COMPARE)[number];
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { type Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { type ViewFilterGroupEntity } from 'src/engine/metadata-modules/view-filter-group/entities/view-filter-group.entity';
|
||||
import { type ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity';
|
||||
import { type ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
|
||||
import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type';
|
||||
|
||||
export type ViewFilterEntityRelationProperties =
|
||||
ExtractRecordTypeOrmRelationProperties<
|
||||
ViewFilterEntity,
|
||||
FieldMetadataEntity | ViewEntity | ViewFilterGroupEntity | Workspace
|
||||
>;
|
||||
|
||||
export type FlatViewFilter = Omit<
|
||||
ViewFilterEntity,
|
||||
ViewFilterEntityRelationProperties
|
||||
> & {
|
||||
universalIdentifier: string;
|
||||
};
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { type FromTo } from 'twenty-shared/types';
|
||||
|
||||
import { compareTwoFlatEntity } from 'src/engine/core-modules/common/utils/compare-two-flat-entity.util';
|
||||
import { FLAT_VIEW_FILTER_JSONB_PROPERTIES } from 'src/engine/metadata-modules/flat-view-filter/constants/flat-view-filter-jsonb-properties.constant';
|
||||
import { FLAT_VIEW_FILTER_PROPERTIES_TO_COMPARE } from 'src/engine/metadata-modules/flat-view-filter/constants/flat-view-filter-properties-to-compare.constant';
|
||||
import { type FlatViewFilter } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter.type';
|
||||
|
||||
type GetWorkspaceMigrationUpdateViewFilterActionArgs = FromTo<
|
||||
FlatViewFilter,
|
||||
'FlatViewFilter'
|
||||
>;
|
||||
|
||||
export const compareTwoFlatViewFilter = ({
|
||||
fromFlatViewFilter,
|
||||
toFlatViewFilter,
|
||||
}: GetWorkspaceMigrationUpdateViewFilterActionArgs) => {
|
||||
return compareTwoFlatEntity({
|
||||
fromFlatEntity: fromFlatViewFilter,
|
||||
toFlatEntity: toFlatViewFilter,
|
||||
propertiesToStringify: FLAT_VIEW_FILTER_JSONB_PROPERTIES,
|
||||
propertiesToCompare: FLAT_VIEW_FILTER_PROPERTIES_TO_COMPARE,
|
||||
});
|
||||
};
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
import { ViewFilterOperand } from 'twenty-shared/types';
|
||||
import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'twenty-shared/utils';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { type FlatViewFilter } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter.type';
|
||||
import { type CreateViewFilterInput } from 'src/engine/metadata-modules/view-filter/dtos/inputs/create-view-filter.input';
|
||||
|
||||
export const fromCreateViewFilterInputToFlatViewFilterToCreate = ({
|
||||
createViewFilterInput: rawCreateViewFilterInput,
|
||||
workspaceId,
|
||||
}: {
|
||||
createViewFilterInput: CreateViewFilterInput;
|
||||
workspaceId: string;
|
||||
}): FlatViewFilter => {
|
||||
const { fieldMetadataId, viewId, value, ...createViewFilterInput } =
|
||||
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
|
||||
rawCreateViewFilterInput,
|
||||
[
|
||||
'fieldMetadataId',
|
||||
'id',
|
||||
'viewId',
|
||||
'viewFilterGroupId',
|
||||
'operand',
|
||||
'subFieldName',
|
||||
],
|
||||
);
|
||||
|
||||
const createdAt = new Date();
|
||||
const viewFilterId = createViewFilterInput.id ?? v4();
|
||||
|
||||
return {
|
||||
id: viewFilterId,
|
||||
fieldMetadataId,
|
||||
viewId,
|
||||
workspaceId,
|
||||
createdAt: createdAt,
|
||||
updatedAt: createdAt,
|
||||
deletedAt: null,
|
||||
universalIdentifier: viewFilterId,
|
||||
operand: createViewFilterInput.operand ?? ViewFilterOperand.CONTAINS,
|
||||
value: value,
|
||||
viewFilterGroupId: createViewFilterInput.viewFilterGroupId ?? null,
|
||||
positionInViewFilterGroup:
|
||||
createViewFilterInput.positionInViewFilterGroup ?? null,
|
||||
subFieldName: createViewFilterInput.subFieldName ?? null,
|
||||
};
|
||||
};
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
extractAndSanitizeObjectStringFields,
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatViewFilterMaps } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter-maps.type';
|
||||
import { type FlatViewFilter } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter.type';
|
||||
import { type DeleteViewFilterInput } from 'src/engine/metadata-modules/view-filter/dtos/inputs/delete-view-filter.input';
|
||||
import {
|
||||
ViewFilterException,
|
||||
ViewFilterExceptionCode,
|
||||
} from 'src/engine/metadata-modules/view-filter/exceptions/view-filter.exception';
|
||||
|
||||
export const fromDeleteViewFilterInputToFlatViewFilterOrThrow = ({
|
||||
deleteViewFilterInput: rawDeleteViewFilterInput,
|
||||
flatViewFilterMaps,
|
||||
}: {
|
||||
deleteViewFilterInput: DeleteViewFilterInput;
|
||||
flatViewFilterMaps: FlatViewFilterMaps;
|
||||
}): FlatViewFilter => {
|
||||
const { id: viewFilterId } = extractAndSanitizeObjectStringFields(
|
||||
rawDeleteViewFilterInput,
|
||||
['id'],
|
||||
);
|
||||
|
||||
const existingFlatViewFilterToDelete = flatViewFilterMaps.byId[viewFilterId];
|
||||
|
||||
if (!isDefined(existingFlatViewFilterToDelete)) {
|
||||
throw new ViewFilterException(
|
||||
t`View filter to delete not found`,
|
||||
ViewFilterExceptionCode.VIEW_FILTER_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
...existingFlatViewFilterToDelete,
|
||||
deletedAt: new Date(),
|
||||
};
|
||||
};
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
extractAndSanitizeObjectStringFields,
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatViewFilterMaps } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter-maps.type';
|
||||
import { type FlatViewFilter } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter.type';
|
||||
import { type DestroyViewFilterInput } from 'src/engine/metadata-modules/view-filter/dtos/inputs/destroy-view-filter.input';
|
||||
import {
|
||||
ViewFilterException,
|
||||
ViewFilterExceptionCode,
|
||||
} from 'src/engine/metadata-modules/view-filter/exceptions/view-filter.exception';
|
||||
|
||||
export const fromDestroyViewFilterInputToFlatViewFilterOrThrow = ({
|
||||
destroyViewFilterInput,
|
||||
flatViewFilterMaps,
|
||||
}: {
|
||||
destroyViewFilterInput: DestroyViewFilterInput;
|
||||
flatViewFilterMaps: FlatViewFilterMaps;
|
||||
}): FlatViewFilter => {
|
||||
const { id: viewFilterId } = extractAndSanitizeObjectStringFields(
|
||||
destroyViewFilterInput,
|
||||
['id'],
|
||||
);
|
||||
|
||||
const existingFlatViewFilterToDestroy = flatViewFilterMaps.byId[viewFilterId];
|
||||
|
||||
if (!isDefined(existingFlatViewFilterToDestroy)) {
|
||||
throw new ViewFilterException(
|
||||
t`View filter to destroy not found`,
|
||||
ViewFilterExceptionCode.VIEW_FILTER_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return existingFlatViewFilterToDestroy;
|
||||
};
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
extractAndSanitizeObjectStringFields,
|
||||
isDefined,
|
||||
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
import { FLAT_VIEW_FILTER_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-view-filter/constants/flat-view-filter-editable-properties.constant';
|
||||
import { type FlatViewFilterMaps } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter-maps.type';
|
||||
import { type FlatViewFilter } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter.type';
|
||||
import { type UpdateViewFilterInput } from 'src/engine/metadata-modules/view-filter/dtos/inputs/update-view-filter.input';
|
||||
import {
|
||||
ViewFilterException,
|
||||
ViewFilterExceptionCode,
|
||||
} from 'src/engine/metadata-modules/view-filter/exceptions/view-filter.exception';
|
||||
import { mergeUpdateInExistingRecord } from 'src/utils/merge-update-in-existing-record.util';
|
||||
|
||||
export const fromUpdateViewFilterInputToFlatViewFilterToUpdateOrThrow = ({
|
||||
updateViewFilterInput: rawUpdateViewFilterInput,
|
||||
flatViewFilterMaps,
|
||||
}: {
|
||||
updateViewFilterInput: UpdateViewFilterInput;
|
||||
flatViewFilterMaps: FlatViewFilterMaps;
|
||||
}): FlatViewFilter => {
|
||||
const { id: viewFilterToUpdateId } =
|
||||
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
|
||||
rawUpdateViewFilterInput,
|
||||
['id'],
|
||||
);
|
||||
|
||||
const existingFlatViewFilterToUpdate =
|
||||
flatViewFilterMaps.byId[viewFilterToUpdateId];
|
||||
|
||||
if (!isDefined(existingFlatViewFilterToUpdate)) {
|
||||
throw new ViewFilterException(
|
||||
t`View filter to update not found`,
|
||||
ViewFilterExceptionCode.VIEW_FILTER_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const updatedEditableFieldProperties = extractAndSanitizeObjectStringFields(
|
||||
rawUpdateViewFilterInput.update,
|
||||
FLAT_VIEW_FILTER_EDITABLE_PROPERTIES,
|
||||
);
|
||||
|
||||
return mergeUpdateInExistingRecord({
|
||||
existing: existingFlatViewFilterToUpdate,
|
||||
properties: FLAT_VIEW_FILTER_EDITABLE_PROPERTIES,
|
||||
update: updatedEditableFieldProperties,
|
||||
});
|
||||
};
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { removePropertiesFromRecord } from 'twenty-shared/utils';
|
||||
|
||||
import { VIEW_FILTER_ENTITY_RELATION_PROPERTIES } from 'src/engine/metadata-modules/flat-view-filter/constants/view-filter-entity-relation-properties.constant';
|
||||
import { type FlatViewFilter } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter.type';
|
||||
import { type ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity';
|
||||
|
||||
export const fromViewFilterEntityToFlatViewFilter = (
|
||||
viewFilterEntity: ViewFilterEntity,
|
||||
): FlatViewFilter => {
|
||||
const viewFilterEntityWithoutRelations = removePropertiesFromRecord(
|
||||
viewFilterEntity,
|
||||
VIEW_FILTER_ENTITY_RELATION_PROPERTIES,
|
||||
);
|
||||
|
||||
return {
|
||||
...viewFilterEntityWithoutRelations,
|
||||
universalIdentifier:
|
||||
viewFilterEntityWithoutRelations.universalIdentifier ??
|
||||
viewFilterEntityWithoutRelations.id,
|
||||
};
|
||||
};
|
||||
+4
-1
@@ -39,8 +39,11 @@ export class WorkspaceFlatViewMapCacheService extends WorkspaceFlatMapCacheServi
|
||||
viewFields: {
|
||||
id: true,
|
||||
},
|
||||
viewFilters: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
relations: ['viewFields'],
|
||||
relations: ['viewFields', 'viewFilters'],
|
||||
withDeleted: true,
|
||||
});
|
||||
|
||||
|
||||
@@ -23,4 +23,5 @@ export type ViewEntityRelationProperties =
|
||||
export type FlatView = Omit<ViewEntity, ViewEntityRelationProperties> & {
|
||||
universalIdentifier: string;
|
||||
viewFieldIds: string[];
|
||||
viewFilterIds: string[];
|
||||
};
|
||||
|
||||
+1
@@ -44,5 +44,6 @@ export const fromCreateViewInputToFlatViewToCreate = ({
|
||||
type: createViewInput.type ?? ViewType.TABLE,
|
||||
universalIdentifier: v4(),
|
||||
viewFieldIds: [],
|
||||
viewFilterIds: [],
|
||||
};
|
||||
};
|
||||
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
|
||||
import { ViewOpenRecordIn } from 'src/engine/metadata-modules/view/enums/view-open-record-in';
|
||||
import { ViewType } from 'src/engine/metadata-modules/view/enums/view-type.enum';
|
||||
|
||||
export const fromPartialFlatViewToFlatViewWithDefault = (
|
||||
partialFlatView: Partial<FlatView>,
|
||||
): FlatView => {
|
||||
const createdAt = new Date();
|
||||
const viewId = partialFlatView.id ?? v4();
|
||||
|
||||
return {
|
||||
...partialFlatView,
|
||||
id: viewId,
|
||||
name: partialFlatView.name ?? '',
|
||||
objectMetadataId: partialFlatView.objectMetadataId ?? '',
|
||||
type: partialFlatView.type ?? ViewType.TABLE,
|
||||
key: partialFlatView.key ?? null,
|
||||
icon: partialFlatView.icon ?? '',
|
||||
position: partialFlatView.position ?? 0,
|
||||
isCompact: partialFlatView.isCompact ?? false,
|
||||
isCustom: partialFlatView.isCustom ?? false,
|
||||
openRecordIn: partialFlatView.openRecordIn ?? ViewOpenRecordIn.SIDE_PANEL,
|
||||
kanbanAggregateOperation: partialFlatView.kanbanAggregateOperation ?? null,
|
||||
kanbanAggregateOperationFieldMetadataId:
|
||||
partialFlatView.kanbanAggregateOperationFieldMetadataId ?? null,
|
||||
workspaceId: partialFlatView.workspaceId ?? '',
|
||||
createdAt: createdAt,
|
||||
updatedAt: createdAt,
|
||||
deletedAt: null,
|
||||
anyFieldFilterValue: partialFlatView.anyFieldFilterValue ?? null,
|
||||
universalIdentifier: partialFlatView.universalIdentifier ?? viewId,
|
||||
viewFieldIds: partialFlatView.viewFieldIds ?? [],
|
||||
calendarLayout: partialFlatView.calendarLayout ?? null,
|
||||
calendarFieldMetadataId: partialFlatView.calendarFieldMetadataId ?? null,
|
||||
};
|
||||
};
|
||||
+2
-1
@@ -15,6 +15,7 @@ export const fromViewEntityToFlatView = (viewEntity: ViewEntity): FlatView => {
|
||||
universalIdentifier:
|
||||
viewEntityWithoutRelations.universalIdentifier ??
|
||||
viewEntityWithoutRelations.id,
|
||||
viewFieldIds: viewEntity.viewFields.map((viewField) => viewField.id) ?? [],
|
||||
viewFieldIds: viewEntity.viewFields.map((viewField) => viewField.id),
|
||||
viewFilterIds: viewEntity.viewFilters.map((viewFilter) => viewFilter.id),
|
||||
};
|
||||
};
|
||||
|
||||
+7
-2
@@ -89,10 +89,15 @@ export class ViewFilterController {
|
||||
@Body() input: UpdateViewFilterInput,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<ViewFilterDTO> {
|
||||
const updatedViewFilter = await this.viewFilterService.update(
|
||||
const updateInput: UpdateViewFilterInput = {
|
||||
id,
|
||||
update: input.update ?? input,
|
||||
};
|
||||
|
||||
const updatedViewFilter = await this.viewFilterService.update(
|
||||
updateInput.id,
|
||||
workspace.id,
|
||||
input,
|
||||
updateInput.update,
|
||||
);
|
||||
|
||||
return updatedViewFilter;
|
||||
|
||||
+21
@@ -2,33 +2,54 @@ import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import GraphQLJSON from 'graphql-type-json';
|
||||
import { ViewFilterOperand } from 'twenty-shared/types';
|
||||
import {
|
||||
IsDefined,
|
||||
IsEnum,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
} from 'class-validator';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { ViewFilterValue } from 'src/engine/metadata-modules/view-filter/types/view-filter-value.type';
|
||||
|
||||
@InputType()
|
||||
export class CreateViewFilterInput {
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
id?: string;
|
||||
|
||||
@IsUUID()
|
||||
@Field(() => UUIDScalarType, { nullable: false })
|
||||
fieldMetadataId: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(ViewFilterOperand)
|
||||
@Field({ nullable: true, defaultValue: ViewFilterOperand.CONTAINS })
|
||||
operand?: ViewFilterOperand;
|
||||
|
||||
@IsDefined()
|
||||
@Field(() => GraphQLJSON, { nullable: false })
|
||||
value: ViewFilterValue;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
viewFilterGroupId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Field({ nullable: true })
|
||||
positionInViewFilterGroup?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@Field({ nullable: true })
|
||||
subFieldName?: string;
|
||||
|
||||
@IsUUID()
|
||||
@Field(() => UUIDScalarType, { nullable: false })
|
||||
viewId: string;
|
||||
}
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IDField } from '@ptc-org/nestjs-query-graphql';
|
||||
import { IsUUID } from 'class-validator';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@InputType()
|
||||
export class DeleteViewFilterInput {
|
||||
@IDField(() => UUIDScalarType, {
|
||||
description: 'The id of the view filter to delete.',
|
||||
})
|
||||
@IsUUID()
|
||||
id: string;
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IDField } from '@ptc-org/nestjs-query-graphql';
|
||||
import { IsUUID } from 'class-validator';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@InputType()
|
||||
export class DestroyViewFilterInput {
|
||||
@IDField(() => UUIDScalarType, {
|
||||
description: 'The id of the view filter to destroy.',
|
||||
})
|
||||
@IsUUID()
|
||||
id: string;
|
||||
}
|
||||
+63
-3
@@ -1,6 +1,66 @@
|
||||
import { InputType, PartialType } from '@nestjs/graphql';
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { CreateViewFilterInput } from './create-view-filter.input';
|
||||
import {
|
||||
IsEnum,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
import GraphQLJSON from 'graphql-type-json';
|
||||
import { ViewFilterOperand } from 'twenty-shared/types';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { ViewFilterValue } from 'src/engine/metadata-modules/view-filter/types/view-filter-value.type';
|
||||
|
||||
@InputType()
|
||||
export class UpdateViewFilterInput extends PartialType(CreateViewFilterInput) {}
|
||||
class UpdateViewFilterInputUpdates {
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
fieldMetadataId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(ViewFilterOperand)
|
||||
@Field({ nullable: true })
|
||||
operand?: ViewFilterOperand;
|
||||
|
||||
@IsOptional()
|
||||
@Field(() => GraphQLJSON, { nullable: true })
|
||||
value?: ViewFilterValue;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
viewFilterGroupId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Field({ nullable: true })
|
||||
positionInViewFilterGroup?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@Field({ nullable: true })
|
||||
subFieldName?: string;
|
||||
}
|
||||
|
||||
@InputType()
|
||||
export class UpdateViewFilterInput {
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
@Field(() => UUIDScalarType, {
|
||||
description: 'The id of the view filter to update',
|
||||
})
|
||||
id: string;
|
||||
|
||||
@Type(() => UpdateViewFilterInputUpdates)
|
||||
@ValidateNested()
|
||||
@Field(() => UpdateViewFilterInputUpdates, {
|
||||
description: 'The view filter to update',
|
||||
})
|
||||
update: UpdateViewFilterInputUpdates;
|
||||
}
|
||||
|
||||
+7
-4
@@ -26,7 +26,10 @@ import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entit
|
||||
where: '"deletedAt" IS NULL',
|
||||
})
|
||||
@Index('IDX_VIEW_FILTER_FIELD_METADATA_ID', ['fieldMetadataId'])
|
||||
export class ViewFilterEntity extends SyncableEntity {
|
||||
export class ViewFilterEntity
|
||||
extends SyncableEntity
|
||||
implements Required<ViewFilterEntity>
|
||||
{
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@@ -51,13 +54,13 @@ export class ViewFilterEntity extends SyncableEntity {
|
||||
value: ViewFilterValue;
|
||||
|
||||
@Column({ nullable: true, type: 'uuid' })
|
||||
viewFilterGroupId?: string | null;
|
||||
viewFilterGroupId: string | null;
|
||||
|
||||
@Column({ nullable: true, type: 'double precision' })
|
||||
positionInViewFilterGroup: number | null;
|
||||
|
||||
@Column({ nullable: true, type: 'text', default: null })
|
||||
subFieldName?: string | null;
|
||||
subFieldName: string | null;
|
||||
|
||||
@Column({ nullable: false, type: 'uuid' })
|
||||
viewId: string;
|
||||
@@ -72,7 +75,7 @@ export class ViewFilterEntity extends SyncableEntity {
|
||||
updatedAt: Date;
|
||||
|
||||
@DeleteDateColumn({ type: 'timestamptz' })
|
||||
deletedAt?: Date | null;
|
||||
deletedAt: Date | null;
|
||||
|
||||
@ManyToOne(() => Workspace, {
|
||||
onDelete: 'CASCADE',
|
||||
|
||||
+90
-26
@@ -1,14 +1,17 @@
|
||||
import { UseFilters, UseGuards } from '@nestjs/common';
|
||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { CreateViewFilterInput } from 'src/engine/metadata-modules/view-filter/dtos/inputs/create-view-filter.input';
|
||||
import { DeleteViewFilterInput } from 'src/engine/metadata-modules/view-filter/dtos/inputs/delete-view-filter.input';
|
||||
import { DestroyViewFilterInput } from 'src/engine/metadata-modules/view-filter/dtos/inputs/destroy-view-filter.input';
|
||||
import { UpdateViewFilterInput } from 'src/engine/metadata-modules/view-filter/dtos/inputs/update-view-filter.input';
|
||||
import { ViewFilterDTO } from 'src/engine/metadata-modules/view-filter/dtos/view-filter.dto';
|
||||
import { ViewFilterV2Service } from 'src/engine/metadata-modules/view-filter/services/view-filter-v2.service';
|
||||
import { ViewFilterService } from 'src/engine/metadata-modules/view-filter/services/view-filter.service';
|
||||
import { ViewGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/view/utils/view-graphql-api-exception.filter';
|
||||
|
||||
@@ -16,7 +19,11 @@ import { ViewGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/view/
|
||||
@UseFilters(ViewGraphqlApiExceptionFilter)
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
export class ViewFilterResolver {
|
||||
constructor(private readonly viewFilterService: ViewFilterService) {}
|
||||
constructor(
|
||||
private readonly viewFilterService: ViewFilterService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
private readonly viewFilterV2Service: ViewFilterV2Service,
|
||||
) {}
|
||||
|
||||
@Query(() => [ViewFilterDTO])
|
||||
async getCoreViewFilters(
|
||||
@@ -41,47 +48,104 @@ export class ViewFilterResolver {
|
||||
|
||||
@Mutation(() => ViewFilterDTO)
|
||||
async createCoreViewFilter(
|
||||
@Args('input') input: CreateViewFilterInput,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@Args('input') createViewFilterInput: CreateViewFilterInput,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
): Promise<ViewFilterDTO> {
|
||||
const isWorkspaceMigrationV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (isWorkspaceMigrationV2Enabled) {
|
||||
return await this.viewFilterV2Service.createOne({
|
||||
createViewFilterInput,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
return this.viewFilterService.create({
|
||||
...input,
|
||||
workspaceId: workspace.id,
|
||||
...createViewFilterInput,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => ViewFilterDTO)
|
||||
async updateCoreViewFilter(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@Args('input') input: UpdateViewFilterInput,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@Args('input') updateViewFilterInput: UpdateViewFilterInput,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
): Promise<ViewFilterDTO> {
|
||||
return this.viewFilterService.update(id, workspace.id, input);
|
||||
const isWorkspaceMigrationV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (isWorkspaceMigrationV2Enabled) {
|
||||
return await this.viewFilterV2Service.updateOne({
|
||||
updateViewFilterInput,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
return this.viewFilterService.update(
|
||||
updateViewFilterInput.id,
|
||||
workspaceId,
|
||||
updateViewFilterInput.update,
|
||||
);
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
@Mutation(() => ViewFilterDTO)
|
||||
async deleteCoreViewFilter(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<boolean> {
|
||||
@Args('input') deleteViewFilterInput: DeleteViewFilterInput,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
): Promise<ViewFilterDTO> {
|
||||
const isWorkspaceMigrationV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (isWorkspaceMigrationV2Enabled) {
|
||||
return await this.viewFilterV2Service.deleteOne({
|
||||
deleteViewFilterInput,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
const deletedViewFilter = await this.viewFilterService.delete(
|
||||
id,
|
||||
workspace.id,
|
||||
deleteViewFilterInput.id,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
return isDefined(deletedViewFilter);
|
||||
return deletedViewFilter;
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
@Mutation(() => ViewFilterDTO)
|
||||
async destroyCoreViewFilter(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<boolean> {
|
||||
const deletedViewFilter = await this.viewFilterService.destroy(
|
||||
id,
|
||||
workspace.id,
|
||||
@Args('input') destroyViewFilterInput: DestroyViewFilterInput,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
): Promise<ViewFilterDTO> {
|
||||
const isWorkspaceMigrationV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (isWorkspaceMigrationV2Enabled) {
|
||||
const destroyedViewFilter = await this.viewFilterV2Service.destroyOne({
|
||||
destroyViewFilterInput,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return destroyedViewFilter;
|
||||
}
|
||||
|
||||
const destroyedViewFilter = await this.viewFilterService.destroy(
|
||||
destroyViewFilterInput.id,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
return isDefined(deletedViewFilter);
|
||||
return destroyedViewFilter;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -333,7 +333,7 @@ describe('ViewFilterService', () => {
|
||||
|
||||
expect(viewFilterService.findById).toHaveBeenCalledWith(id, workspaceId);
|
||||
expect(viewFilterRepository.delete).toHaveBeenCalledWith(id);
|
||||
expect(result).toEqual(true);
|
||||
expect(result).toEqual(mockViewFilter);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+326
@@ -0,0 +1,326 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/core-modules/common/constant/empty-flat-entity-maps.constant';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
|
||||
import { deleteFlatEntityFromFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util';
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { getSubFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/get-sub-flat-entity-maps-or-throw.util';
|
||||
import { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/replace-flat-entity-in-flat-entity-maps-or-throw.util';
|
||||
import { fromCreateViewFilterInputToFlatViewFilterToCreate } from 'src/engine/metadata-modules/flat-view-filter/utils/from-create-view-filter-input-to-flat-view-filter-to-create.util';
|
||||
import { fromDeleteViewFilterInputToFlatViewFilterOrThrow } from 'src/engine/metadata-modules/flat-view-filter/utils/from-delete-view-filter-input-to-flat-view-filter-or-throw.util';
|
||||
import { fromDestroyViewFilterInputToFlatViewFilterOrThrow } from 'src/engine/metadata-modules/flat-view-filter/utils/from-destroy-view-filter-input-to-flat-view-filter-or-throw.util';
|
||||
import { fromUpdateViewFilterInputToFlatViewFilterToUpdateOrThrow } from 'src/engine/metadata-modules/flat-view-filter/utils/from-update-view-filter-input-to-flat-view-filter-to-update-or-throw.util';
|
||||
import { CreateViewFilterInput } from 'src/engine/metadata-modules/view-filter/dtos/inputs/create-view-filter.input';
|
||||
import { DeleteViewFilterInput } from 'src/engine/metadata-modules/view-filter/dtos/inputs/delete-view-filter.input';
|
||||
import { DestroyViewFilterInput } from 'src/engine/metadata-modules/view-filter/dtos/inputs/destroy-view-filter.input';
|
||||
import { UpdateViewFilterInput } from 'src/engine/metadata-modules/view-filter/dtos/inputs/update-view-filter.input';
|
||||
import { ViewFilterDTO } from 'src/engine/metadata-modules/view-filter/dtos/view-filter.dto';
|
||||
import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service';
|
||||
|
||||
@Injectable()
|
||||
export class ViewFilterV2Service {
|
||||
constructor(
|
||||
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
|
||||
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
) {}
|
||||
|
||||
async createOne({
|
||||
createViewFilterInput,
|
||||
workspaceId,
|
||||
}: {
|
||||
createViewFilterInput: CreateViewFilterInput;
|
||||
workspaceId: string;
|
||||
}): Promise<ViewFilterDTO> {
|
||||
const {
|
||||
flatViewFilterMaps: existingFlatViewFilterMaps,
|
||||
flatViewMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
} = await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: [
|
||||
'flatViewFilterMaps',
|
||||
'flatViewMaps',
|
||||
'flatFieldMetadataMaps',
|
||||
'flatObjectMetadataMaps',
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
const flatViewFilterToCreate =
|
||||
fromCreateViewFilterInputToFlatViewFilterToCreate({
|
||||
createViewFilterInput,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const toFlatViewFilterMaps = addFlatEntityToFlatEntityMapsOrThrow({
|
||||
flatEntity: flatViewFilterToCreate,
|
||||
flatEntityMaps: existingFlatViewFilterMaps,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
fromToAllFlatEntityMaps: {
|
||||
flatViewFilterMaps: {
|
||||
from: existingFlatViewFilterMaps,
|
||||
to: toFlatViewFilterMaps,
|
||||
},
|
||||
},
|
||||
dependencyAllFlatEntityMaps: {
|
||||
flatFieldMetadataMaps,
|
||||
flatViewMaps,
|
||||
flatObjectMetadataMaps,
|
||||
},
|
||||
buildOptions: {
|
||||
isSystemBuild: false,
|
||||
inferDeletionFromMissingEntities: false,
|
||||
},
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
if (isDefined(validateAndBuildResult)) {
|
||||
throw new WorkspaceMigrationBuilderExceptionV2(
|
||||
validateAndBuildResult,
|
||||
'Multiple validation errors occurred while creating view filter',
|
||||
);
|
||||
}
|
||||
|
||||
const { flatViewFilterMaps: recomputedExistingFlatViewFilterMaps } =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatViewFilterMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
return findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: flatViewFilterToCreate.id,
|
||||
flatEntityMaps: recomputedExistingFlatViewFilterMaps,
|
||||
});
|
||||
}
|
||||
|
||||
async updateOne({
|
||||
updateViewFilterInput,
|
||||
workspaceId,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
updateViewFilterInput: UpdateViewFilterInput;
|
||||
}): Promise<ViewFilterDTO> {
|
||||
const {
|
||||
flatViewFilterMaps: existingFlatViewFilterMaps,
|
||||
flatViewMaps,
|
||||
flatFieldMetadataMaps,
|
||||
} = await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: [
|
||||
'flatViewFilterMaps',
|
||||
'flatViewMaps',
|
||||
'flatFieldMetadataMaps',
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
const optimisticallyUpdatedFlatViewFilter =
|
||||
fromUpdateViewFilterInputToFlatViewFilterToUpdateOrThrow({
|
||||
flatViewFilterMaps: existingFlatViewFilterMaps,
|
||||
updateViewFilterInput,
|
||||
});
|
||||
|
||||
const toFlatViewFilterMaps = addFlatEntityToFlatEntityMapsOrThrow({
|
||||
flatEntity: optimisticallyUpdatedFlatViewFilter,
|
||||
flatEntityMaps: EMPTY_FLAT_ENTITY_MAPS,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
fromToAllFlatEntityMaps: {
|
||||
flatViewFilterMaps: {
|
||||
from: existingFlatViewFilterMaps,
|
||||
to: toFlatViewFilterMaps,
|
||||
},
|
||||
},
|
||||
dependencyAllFlatEntityMaps: {
|
||||
flatViewMaps,
|
||||
flatFieldMetadataMaps,
|
||||
},
|
||||
buildOptions: {
|
||||
isSystemBuild: false,
|
||||
inferDeletionFromMissingEntities: false,
|
||||
},
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
if (isDefined(validateAndBuildResult)) {
|
||||
throw new WorkspaceMigrationBuilderExceptionV2(
|
||||
validateAndBuildResult,
|
||||
'Multiple validation errors occurred while updating view filter',
|
||||
);
|
||||
}
|
||||
|
||||
const { flatViewFilterMaps: recomputedExistingFlatViewFilterMaps } =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatViewFilterMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
return findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: optimisticallyUpdatedFlatViewFilter.id,
|
||||
flatEntityMaps: recomputedExistingFlatViewFilterMaps,
|
||||
});
|
||||
}
|
||||
|
||||
async deleteOne({
|
||||
deleteViewFilterInput,
|
||||
workspaceId,
|
||||
}: {
|
||||
deleteViewFilterInput: DeleteViewFilterInput;
|
||||
workspaceId: string;
|
||||
}): Promise<ViewFilterDTO> {
|
||||
const {
|
||||
flatViewFilterMaps: existingFlatViewFilterMaps,
|
||||
flatViewMaps,
|
||||
flatFieldMetadataMaps,
|
||||
} = await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: [
|
||||
'flatViewFilterMaps',
|
||||
'flatViewMaps',
|
||||
'flatFieldMetadataMaps',
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
const optimisticallyUpdatedFlatViewFilterWithDeletedAt =
|
||||
fromDeleteViewFilterInputToFlatViewFilterOrThrow({
|
||||
flatViewFilterMaps: existingFlatViewFilterMaps,
|
||||
deleteViewFilterInput,
|
||||
});
|
||||
|
||||
const toFlatViewFilterMaps = replaceFlatEntityInFlatEntityMapsOrThrow({
|
||||
flatEntity: optimisticallyUpdatedFlatViewFilterWithDeletedAt,
|
||||
flatEntityMaps: existingFlatViewFilterMaps,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
fromToAllFlatEntityMaps: {
|
||||
flatViewFilterMaps: {
|
||||
from: existingFlatViewFilterMaps,
|
||||
to: toFlatViewFilterMaps,
|
||||
},
|
||||
},
|
||||
dependencyAllFlatEntityMaps: {
|
||||
flatFieldMetadataMaps,
|
||||
flatViewMaps,
|
||||
},
|
||||
buildOptions: {
|
||||
isSystemBuild: false,
|
||||
inferDeletionFromMissingEntities: false,
|
||||
},
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
if (isDefined(validateAndBuildResult)) {
|
||||
throw new WorkspaceMigrationBuilderExceptionV2(
|
||||
validateAndBuildResult,
|
||||
'Multiple validation errors occurred while deleting view filter',
|
||||
);
|
||||
}
|
||||
|
||||
const { flatViewFilterMaps: recomputedExistingFlatViewFilterMaps } =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatViewFilterMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
return findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: optimisticallyUpdatedFlatViewFilterWithDeletedAt.id,
|
||||
flatEntityMaps: recomputedExistingFlatViewFilterMaps,
|
||||
});
|
||||
}
|
||||
|
||||
async destroyOne({
|
||||
destroyViewFilterInput,
|
||||
workspaceId,
|
||||
}: {
|
||||
destroyViewFilterInput: DestroyViewFilterInput;
|
||||
workspaceId: string;
|
||||
}): Promise<ViewFilterDTO> {
|
||||
const {
|
||||
flatViewFilterMaps: existingFlatViewFilterMaps,
|
||||
flatViewMaps: existingFlatViewMaps,
|
||||
flatFieldMetadataMaps: existingFlatFieldMetadataMaps,
|
||||
} = await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: [
|
||||
'flatViewFilterMaps',
|
||||
'flatViewMaps',
|
||||
'flatFieldMetadataMaps',
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
const existingViewFilterToDelete =
|
||||
fromDestroyViewFilterInputToFlatViewFilterOrThrow({
|
||||
destroyViewFilterInput,
|
||||
flatViewFilterMaps: existingFlatViewFilterMaps,
|
||||
});
|
||||
|
||||
const fromFlatViewFilterMaps = getSubFlatEntityMapsOrThrow({
|
||||
flatEntityIds: [existingViewFilterToDelete.id],
|
||||
flatEntityMaps: existingFlatViewFilterMaps,
|
||||
});
|
||||
const toFlatViewFilterMaps = deleteFlatEntityFromFlatEntityMapsOrThrow({
|
||||
flatEntityMaps: fromFlatViewFilterMaps,
|
||||
entityToDeleteId: existingViewFilterToDelete.id,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
fromToAllFlatEntityMaps: {
|
||||
flatViewFilterMaps: {
|
||||
from: fromFlatViewFilterMaps,
|
||||
to: toFlatViewFilterMaps,
|
||||
},
|
||||
},
|
||||
dependencyAllFlatEntityMaps: {
|
||||
flatViewMaps: existingFlatViewMaps,
|
||||
flatFieldMetadataMaps: existingFlatFieldMetadataMaps,
|
||||
},
|
||||
buildOptions: {
|
||||
isSystemBuild: false,
|
||||
inferDeletionFromMissingEntities: true,
|
||||
},
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
if (isDefined(validateAndBuildResult)) {
|
||||
throw new WorkspaceMigrationBuilderExceptionV2(
|
||||
validateAndBuildResult,
|
||||
'Multiple validation errors occurred while destroying view filter',
|
||||
);
|
||||
}
|
||||
|
||||
return existingViewFilterToDelete;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -168,7 +168,7 @@ export class ViewFilterService {
|
||||
return viewFilter;
|
||||
}
|
||||
|
||||
async destroy(id: string, workspaceId: string): Promise<boolean> {
|
||||
async destroy(id: string, workspaceId: string): Promise<ViewFilterEntity> {
|
||||
const viewFilter = await this.findById(id, workspaceId);
|
||||
|
||||
if (!isDefined(viewFilter)) {
|
||||
@@ -185,7 +185,7 @@ export class ViewFilterService {
|
||||
|
||||
await this.flushGraphQLCache(workspaceId);
|
||||
|
||||
return true;
|
||||
return viewFilter;
|
||||
}
|
||||
|
||||
private async flushGraphQLCache(workspaceId: string): Promise<void> {
|
||||
|
||||
+11
-3
@@ -1,19 +1,27 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { ViewFilterController } from 'src/engine/metadata-modules/view-filter/controllers/view-filter.controller';
|
||||
import { ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity';
|
||||
import { ViewFilterResolver } from 'src/engine/metadata-modules/view-filter/resolvers/view-filter.resolver';
|
||||
import { ViewFilterV2Service } from 'src/engine/metadata-modules/view-filter/services/view-filter-v2.service';
|
||||
import { ViewFilterService } from 'src/engine/metadata-modules/view-filter/services/view-filter.service';
|
||||
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
|
||||
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
||||
import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([ViewFilterEntity]),
|
||||
TypeOrmModule.forFeature([ViewFilterEntity, ViewEntity]),
|
||||
WorkspaceCacheStorageModule,
|
||||
FeatureFlagModule,
|
||||
WorkspaceMigrationV2Module,
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
],
|
||||
controllers: [ViewFilterController],
|
||||
providers: [ViewFilterService, ViewFilterResolver],
|
||||
exports: [ViewFilterService],
|
||||
providers: [ViewFilterService, ViewFilterResolver, ViewFilterV2Service],
|
||||
exports: [ViewFilterService, ViewFilterV2Service],
|
||||
})
|
||||
export class ViewFilterModule {}
|
||||
|
||||
Reference in New Issue
Block a user