add UpsertViewWidget resolver (#20053)
This commit is contained in:
+40
@@ -0,0 +1,40 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IsBoolean, IsNumber, IsOptional, IsUUID } from 'class-validator';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { AtLeastOneOf } from 'src/engine/metadata-modules/view-field-group/dtos/validators/at-least-one-of.validator';
|
||||
|
||||
@InputType()
|
||||
@AtLeastOneOf(['viewFieldId', 'fieldMetadataId'])
|
||||
export class UpsertViewWidgetViewFieldInput {
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
@Field(() => UUIDScalarType, {
|
||||
nullable: true,
|
||||
description: 'The id of an existing view field to update.',
|
||||
})
|
||||
viewFieldId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
@Field(() => UUIDScalarType, {
|
||||
nullable: true,
|
||||
description:
|
||||
'The field metadata id. Used to create a new view field when viewFieldId is not provided.',
|
||||
})
|
||||
fieldMetadataId?: string;
|
||||
|
||||
@IsBoolean()
|
||||
@Field()
|
||||
isVisible: boolean;
|
||||
|
||||
@IsNumber()
|
||||
@Field()
|
||||
position: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Field({ nullable: true })
|
||||
size?: number;
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IsNumber, IsOptional, IsUUID } from 'class-validator';
|
||||
import { ViewFilterGroupLogicalOperator } from 'twenty-shared/types';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@InputType()
|
||||
export class UpsertViewWidgetViewFilterGroupInput {
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
id?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
parentViewFilterGroupId?: string;
|
||||
|
||||
@Field(() => ViewFilterGroupLogicalOperator, {
|
||||
nullable: true,
|
||||
defaultValue: ViewFilterGroupLogicalOperator.AND,
|
||||
})
|
||||
logicalOperator?: ViewFilterGroupLogicalOperator;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Field({ nullable: true })
|
||||
positionInViewFilterGroup?: number;
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
IsDefined,
|
||||
IsEnum,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
} from 'class-validator';
|
||||
import GraphQLJSON from 'graphql-type-json';
|
||||
import { ViewFilterOperand } from 'twenty-shared/types';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { type ViewFilterValue } from 'src/engine/metadata-modules/view-filter/types/view-filter-value.type';
|
||||
|
||||
@InputType()
|
||||
export class UpsertViewWidgetViewFilterInput {
|
||||
@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;
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IsEnum, IsOptional, IsUUID } from 'class-validator';
|
||||
import { ViewSortDirection } from 'twenty-shared/types';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@InputType()
|
||||
export class UpsertViewWidgetViewSortInput {
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
id?: string;
|
||||
|
||||
@IsUUID()
|
||||
@Field(() => UUIDScalarType, { nullable: false })
|
||||
fieldMetadataId: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(ViewSortDirection)
|
||||
@Field(() => ViewSortDirection, {
|
||||
nullable: true,
|
||||
defaultValue: ViewSortDirection.ASC,
|
||||
})
|
||||
direction?: ViewSortDirection;
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { Type } from 'class-transformer';
|
||||
import {
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsUUID,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { UpsertViewWidgetViewFieldInput } from 'src/engine/metadata-modules/view/dtos/inputs/upsert-view-widget-view-field.input';
|
||||
import { UpsertViewWidgetViewFilterGroupInput } from 'src/engine/metadata-modules/view/dtos/inputs/upsert-view-widget-view-filter-group.input';
|
||||
import { UpsertViewWidgetViewFilterInput } from 'src/engine/metadata-modules/view/dtos/inputs/upsert-view-widget-view-filter.input';
|
||||
import { UpsertViewWidgetViewSortInput } from 'src/engine/metadata-modules/view/dtos/inputs/upsert-view-widget-view-sort.input';
|
||||
|
||||
@InputType()
|
||||
export class UpsertViewWidgetInput {
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
@Field(() => UUIDScalarType, {
|
||||
description: 'The id of the view widget (page layout widget).',
|
||||
})
|
||||
widgetId: string;
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => UpsertViewWidgetViewFieldInput)
|
||||
@Field(() => [UpsertViewWidgetViewFieldInput], {
|
||||
nullable: true,
|
||||
description: 'The view fields to upsert.',
|
||||
})
|
||||
viewFields?: UpsertViewWidgetViewFieldInput[];
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => UpsertViewWidgetViewFilterInput)
|
||||
@Field(() => [UpsertViewWidgetViewFilterInput], {
|
||||
nullable: true,
|
||||
description: 'The view filters to upsert.',
|
||||
})
|
||||
viewFilters?: UpsertViewWidgetViewFilterInput[];
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => UpsertViewWidgetViewFilterGroupInput)
|
||||
@Field(() => [UpsertViewWidgetViewFilterGroupInput], {
|
||||
nullable: true,
|
||||
description: 'The view filter groups to upsert.',
|
||||
})
|
||||
viewFilterGroups?: UpsertViewWidgetViewFilterGroupInput[];
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => UpsertViewWidgetViewSortInput)
|
||||
@Field(() => [UpsertViewWidgetViewSortInput], {
|
||||
nullable: true,
|
||||
description: 'The view sorts to upsert.',
|
||||
})
|
||||
viewSorts?: UpsertViewWidgetViewSortInput[];
|
||||
}
|
||||
@@ -21,6 +21,7 @@ export enum ViewExceptionCode {
|
||||
INVALID_VIEW_DATA = 'INVALID_VIEW_DATA',
|
||||
VIEW_CREATE_PERMISSION_DENIED = 'VIEW_CREATE_PERMISSION_DENIED',
|
||||
VIEW_MODIFY_PERMISSION_DENIED = 'VIEW_MODIFY_PERMISSION_DENIED',
|
||||
VIEW_WIDGET_NOT_FOUND = 'VIEW_WIDGET_NOT_FOUND',
|
||||
}
|
||||
|
||||
export enum ViewExceptionMessageKey {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { UseFilters, UseGuards } from '@nestjs/common';
|
||||
import { UseFilters, UseGuards, UsePipes } from '@nestjs/common';
|
||||
import {
|
||||
Args,
|
||||
Context,
|
||||
@@ -8,10 +8,12 @@ import {
|
||||
ResolveField,
|
||||
} from '@nestjs/graphql';
|
||||
|
||||
import { PermissionFlagType } from 'twenty-shared/constants';
|
||||
import { ViewType, ViewVisibility } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
|
||||
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
|
||||
import { I18nService } from 'src/engine/core-modules/i18n/i18n.service';
|
||||
import { type I18nContext } from 'src/engine/core-modules/i18n/types/i18n-context.type';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
@@ -20,6 +22,7 @@ import { AuthUserWorkspaceId } from 'src/engine/decorators/auth/auth-user-worksp
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { CustomPermissionGuard } from 'src/engine/guards/custom-permission.guard';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { resolveObjectMetadataStandardOverride } from 'src/engine/metadata-modules/object-metadata/utils/resolve-object-metadata-standard-override.util';
|
||||
import { ViewFieldGroupDTO } from 'src/engine/metadata-modules/view-field-group/dtos/view-field-group.dto';
|
||||
@@ -34,7 +37,10 @@ import { UpdateViewPermissionGuard } from 'src/engine/metadata-modules/view-perm
|
||||
import { ViewSortDTO } from 'src/engine/metadata-modules/view-sort/dtos/view-sort.dto';
|
||||
import { CreateViewInput } from 'src/engine/metadata-modules/view/dtos/inputs/create-view.input';
|
||||
import { UpdateViewInput } from 'src/engine/metadata-modules/view/dtos/inputs/update-view.input';
|
||||
import { UpsertViewWidgetInput } from 'src/engine/metadata-modules/view/dtos/inputs/upsert-view-widget.input';
|
||||
import { ViewDTO } from 'src/engine/metadata-modules/view/dtos/view.dto';
|
||||
import { type ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
|
||||
import { ViewWidgetUpsertService } from 'src/engine/metadata-modules/view/services/view-widget-upsert.service';
|
||||
import { ViewService } from 'src/engine/metadata-modules/view/services/view.service';
|
||||
import { ViewGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/view/utils/view-graphql-api-exception.filter';
|
||||
|
||||
@@ -44,6 +50,7 @@ import { ViewGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/view/
|
||||
export class ViewResolver {
|
||||
constructor(
|
||||
private readonly viewService: ViewService,
|
||||
private readonly viewWidgetUpsertService: ViewWidgetUpsertService,
|
||||
private readonly i18nService: I18nService,
|
||||
) {}
|
||||
|
||||
@@ -197,6 +204,19 @@ export class ViewResolver {
|
||||
return isDefined(deletedView);
|
||||
}
|
||||
|
||||
@Mutation(() => ViewDTO)
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
@UsePipes(ResolverValidationPipe)
|
||||
async upsertViewWidget(
|
||||
@Args('input') input: UpsertViewWidgetInput,
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
): Promise<ViewEntity> {
|
||||
return await this.viewWidgetUpsertService.upsertViewWidget({
|
||||
input,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
@ResolveField(() => [ViewFieldDTO])
|
||||
async viewFields(
|
||||
@Parent() view: ViewDTO,
|
||||
|
||||
+859
@@ -0,0 +1,859 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
ViewFilterGroupLogicalOperator,
|
||||
ViewFilterOperand,
|
||||
ViewSortDirection,
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IsNull, Repository } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util';
|
||||
import { splitEntitiesByRemovalStrategy } from 'src/engine/metadata-modules/flat-entity/utils/split-entities-by-removal-strategy.util';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { isFlatPageLayoutWidgetConfigurationOfType } from 'src/engine/metadata-modules/flat-page-layout-widget/utils/is-flat-page-layout-widget-configuration-of-type.util';
|
||||
import { DEFAULT_VIEW_FIELD_SIZE } from 'src/engine/metadata-modules/flat-view-field/constants/default-view-field-size.constant';
|
||||
import { type FlatViewField } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field.type';
|
||||
import { fromViewFieldOverridesToUniversalOverrides } from 'src/engine/metadata-modules/flat-view-field/utils/from-view-field-overrides-to-universal-overrides.util';
|
||||
import { type FlatViewFilterGroupMaps } from 'src/engine/metadata-modules/flat-view-filter-group/types/flat-view-filter-group-maps.type';
|
||||
import { type FlatViewFilterGroup } from 'src/engine/metadata-modules/flat-view-filter-group/types/flat-view-filter-group.type';
|
||||
import { type FlatViewFilter } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter.type';
|
||||
import { type FlatViewSort } from 'src/engine/metadata-modules/flat-view-sort/types/flat-view-sort.type';
|
||||
import { type FlatViewMaps } from 'src/engine/metadata-modules/flat-view/types/flat-view-maps.type';
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
import { isCallerOverridingEntity } from 'src/engine/metadata-modules/utils/is-caller-overriding-entity.util';
|
||||
import { sanitizeOverridableEntityInput } from 'src/engine/metadata-modules/utils/sanitize-overridable-entity-input.util';
|
||||
import { type UpsertViewWidgetViewFieldInput } from 'src/engine/metadata-modules/view/dtos/inputs/upsert-view-widget-view-field.input';
|
||||
import { type UpsertViewWidgetViewFilterGroupInput } from 'src/engine/metadata-modules/view/dtos/inputs/upsert-view-widget-view-filter-group.input';
|
||||
import { type UpsertViewWidgetViewFilterInput } from 'src/engine/metadata-modules/view/dtos/inputs/upsert-view-widget-view-filter.input';
|
||||
import { type UpsertViewWidgetViewSortInput } from 'src/engine/metadata-modules/view/dtos/inputs/upsert-view-widget-view-sort.input';
|
||||
import { type UpsertViewWidgetInput } from 'src/engine/metadata-modules/view/dtos/inputs/upsert-view-widget.input';
|
||||
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
|
||||
import {
|
||||
ViewException,
|
||||
ViewExceptionCode,
|
||||
} from 'src/engine/metadata-modules/view/exceptions/view.exception';
|
||||
import { WorkspaceMigrationBuilderException } from 'src/engine/workspace-manager/workspace-migration/exceptions/workspace-migration-builder-exception';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
|
||||
|
||||
const EMPTY_FIELD_OPS = {
|
||||
fieldsToCreate: [] as FlatViewField[],
|
||||
fieldsToUpdate: [] as FlatViewField[],
|
||||
};
|
||||
|
||||
const EMPTY_FILTER_GROUP_OPS = {
|
||||
filterGroupsToCreate: [] as FlatViewFilterGroup[],
|
||||
filterGroupsToUpdate: [] as FlatViewFilterGroup[],
|
||||
filterGroupsToRemove: [] as FlatViewFilterGroup[],
|
||||
};
|
||||
|
||||
const EMPTY_FILTER_OPS = {
|
||||
filtersToCreate: [] as FlatViewFilter[],
|
||||
filtersToUpdate: [] as FlatViewFilter[],
|
||||
filtersToRemove: [] as FlatViewFilter[],
|
||||
};
|
||||
|
||||
const EMPTY_SORT_OPS = {
|
||||
sortsToCreate: [] as FlatViewSort[],
|
||||
sortsToUpdate: [] as FlatViewSort[],
|
||||
sortsToRemove: [] as FlatViewSort[],
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class ViewWidgetUpsertService {
|
||||
constructor(
|
||||
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
|
||||
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
@InjectRepository(ViewEntity)
|
||||
private readonly viewRepository: Repository<ViewEntity>,
|
||||
) {}
|
||||
|
||||
async upsertViewWidget({
|
||||
input,
|
||||
workspaceId,
|
||||
}: {
|
||||
input: UpsertViewWidgetInput;
|
||||
workspaceId: string;
|
||||
}): Promise<ViewEntity> {
|
||||
const { widgetId } = input;
|
||||
|
||||
const { workspaceCustomFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{ workspaceId },
|
||||
);
|
||||
|
||||
const {
|
||||
flatPageLayoutWidgetMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatViewFieldMaps,
|
||||
flatViewFilterMaps,
|
||||
flatViewFilterGroupMaps,
|
||||
flatViewSortMaps,
|
||||
flatViewMaps,
|
||||
} =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: [
|
||||
'flatPageLayoutWidgetMaps',
|
||||
'flatFieldMetadataMaps',
|
||||
'flatViewFieldMaps',
|
||||
'flatViewFilterMaps',
|
||||
'flatViewFilterGroupMaps',
|
||||
'flatViewSortMaps',
|
||||
'flatViewMaps',
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
const widget = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: widgetId,
|
||||
flatEntityMaps: flatPageLayoutWidgetMaps,
|
||||
});
|
||||
|
||||
if (
|
||||
!isDefined(widget) ||
|
||||
!isFlatPageLayoutWidgetConfigurationOfType(
|
||||
widget,
|
||||
WidgetConfigurationType.RECORD_TABLE,
|
||||
)
|
||||
) {
|
||||
throw new ViewException(
|
||||
t`Record table widget not found`,
|
||||
ViewExceptionCode.VIEW_WIDGET_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const viewId = widget.configuration.viewId;
|
||||
|
||||
if (!isDefined(viewId)) {
|
||||
throw new ViewException(
|
||||
t`Record table widget has no associated view`,
|
||||
ViewExceptionCode.VIEW_WIDGET_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const flatView = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: viewId,
|
||||
flatEntityMaps: flatViewMaps,
|
||||
});
|
||||
|
||||
if (!isDefined(flatView)) {
|
||||
throw new ViewException(
|
||||
t`View not found for widget`,
|
||||
ViewExceptionCode.VIEW_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const upsertContext = {
|
||||
viewId,
|
||||
workspaceId,
|
||||
applicationId: workspaceCustomFlatApplication.id,
|
||||
applicationUniversalIdentifier:
|
||||
workspaceCustomFlatApplication.universalIdentifier,
|
||||
now: new Date().toISOString(),
|
||||
};
|
||||
|
||||
if (
|
||||
!isDefined(input.viewFields) &&
|
||||
!isDefined(input.viewFilterGroups) &&
|
||||
!isDefined(input.viewFilters) &&
|
||||
!isDefined(input.viewSorts)
|
||||
) {
|
||||
const view = await this.viewRepository.findOne({
|
||||
where: {
|
||||
id: upsertContext.viewId,
|
||||
workspaceId: upsertContext.workspaceId,
|
||||
deletedAt: IsNull(),
|
||||
},
|
||||
});
|
||||
|
||||
if (!isDefined(view)) {
|
||||
throw new ViewException(
|
||||
t`View not found`,
|
||||
ViewExceptionCode.VIEW_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
const existingViewFields = Object.values(
|
||||
flatViewFieldMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((field) => field.isActive && field.viewId === viewId);
|
||||
|
||||
const existingViewFilters = Object.values(
|
||||
flatViewFilterMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((filter) => filter.viewId === viewId);
|
||||
|
||||
const existingViewFilterGroups = Object.values(
|
||||
flatViewFilterGroupMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((group) => group.viewId === viewId);
|
||||
|
||||
const existingViewSorts = Object.values(
|
||||
flatViewSortMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((sort) => sort.viewId === viewId);
|
||||
|
||||
const viewFieldOperations = isDefined(input.viewFields)
|
||||
? this.computeViewFieldOperations({
|
||||
inputFields: input.viewFields,
|
||||
existingViewFields,
|
||||
...upsertContext,
|
||||
flatFieldMetadataMaps,
|
||||
flatViewMaps,
|
||||
})
|
||||
: EMPTY_FIELD_OPS;
|
||||
|
||||
const viewFilterGroupOperations = isDefined(input.viewFilterGroups)
|
||||
? this.computeViewFilterGroupOperations({
|
||||
inputFilterGroups: input.viewFilterGroups,
|
||||
existingViewFilterGroups,
|
||||
...upsertContext,
|
||||
flatViewMaps,
|
||||
flatViewFilterGroupMaps,
|
||||
})
|
||||
: EMPTY_FILTER_GROUP_OPS;
|
||||
|
||||
const optimisticFilterGroupMaps =
|
||||
viewFilterGroupOperations.filterGroupsToCreate.reduce(
|
||||
(maps, group) =>
|
||||
addFlatEntityToFlatEntityMapsOrThrow({
|
||||
flatEntity: group,
|
||||
flatEntityMaps: maps,
|
||||
}),
|
||||
flatViewFilterGroupMaps,
|
||||
);
|
||||
|
||||
const viewFilterOperations = isDefined(input.viewFilters)
|
||||
? this.computeViewFilterOperations({
|
||||
inputFilters: input.viewFilters,
|
||||
existingViewFilters,
|
||||
...upsertContext,
|
||||
flatFieldMetadataMaps,
|
||||
flatViewMaps,
|
||||
flatViewFilterGroupMaps: optimisticFilterGroupMaps,
|
||||
})
|
||||
: EMPTY_FILTER_OPS;
|
||||
|
||||
const viewSortOperations = isDefined(input.viewSorts)
|
||||
? this.computeViewSortOperations({
|
||||
inputSorts: input.viewSorts,
|
||||
existingViewSorts,
|
||||
...upsertContext,
|
||||
flatFieldMetadataMaps,
|
||||
flatViewMaps,
|
||||
})
|
||||
: EMPTY_SORT_OPS;
|
||||
|
||||
const {
|
||||
toHardDelete: filterGroupsToDelete,
|
||||
toDeactivate: filterGroupsToDeactivate,
|
||||
} = splitEntitiesByRemovalStrategy({
|
||||
entitiesToRemove: viewFilterGroupOperations.filterGroupsToRemove,
|
||||
workspaceCustomApplicationUniversalIdentifier:
|
||||
upsertContext.applicationUniversalIdentifier,
|
||||
now: upsertContext.now,
|
||||
});
|
||||
|
||||
const { toHardDelete: filtersToDelete, toDeactivate: filtersToDeactivate } =
|
||||
splitEntitiesByRemovalStrategy({
|
||||
entitiesToRemove: viewFilterOperations.filtersToRemove,
|
||||
workspaceCustomApplicationUniversalIdentifier:
|
||||
upsertContext.applicationUniversalIdentifier,
|
||||
now: upsertContext.now,
|
||||
});
|
||||
|
||||
const { toHardDelete: sortsToDelete, toDeactivate: sortsToDeactivate } =
|
||||
splitEntitiesByRemovalStrategy({
|
||||
entitiesToRemove: viewSortOperations.sortsToRemove,
|
||||
workspaceCustomApplicationUniversalIdentifier:
|
||||
upsertContext.applicationUniversalIdentifier,
|
||||
now: upsertContext.now,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
viewField: {
|
||||
flatEntityToCreate: viewFieldOperations.fieldsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: viewFieldOperations.fieldsToUpdate,
|
||||
},
|
||||
viewFilterGroup: {
|
||||
flatEntityToCreate:
|
||||
viewFilterGroupOperations.filterGroupsToCreate,
|
||||
flatEntityToDelete: filterGroupsToDelete,
|
||||
flatEntityToUpdate: [
|
||||
...viewFilterGroupOperations.filterGroupsToUpdate,
|
||||
...filterGroupsToDeactivate,
|
||||
],
|
||||
},
|
||||
viewFilter: {
|
||||
flatEntityToCreate: viewFilterOperations.filtersToCreate,
|
||||
flatEntityToDelete: filtersToDelete,
|
||||
flatEntityToUpdate: [
|
||||
...viewFilterOperations.filtersToUpdate,
|
||||
...filtersToDeactivate,
|
||||
],
|
||||
},
|
||||
viewSort: {
|
||||
flatEntityToCreate: viewSortOperations.sortsToCreate,
|
||||
flatEntityToDelete: sortsToDelete,
|
||||
flatEntityToUpdate: [
|
||||
...viewSortOperations.sortsToUpdate,
|
||||
...sortsToDeactivate,
|
||||
],
|
||||
},
|
||||
},
|
||||
workspaceId: upsertContext.workspaceId,
|
||||
isSystemBuild: false,
|
||||
applicationUniversalIdentifier:
|
||||
upsertContext.applicationUniversalIdentifier,
|
||||
},
|
||||
);
|
||||
|
||||
if (validateAndBuildResult.status === 'fail') {
|
||||
throw new WorkspaceMigrationBuilderException(
|
||||
validateAndBuildResult,
|
||||
'Multiple validation errors occurred while upserting view widget',
|
||||
);
|
||||
}
|
||||
|
||||
const view = await this.viewRepository.findOne({
|
||||
where: {
|
||||
id: upsertContext.viewId,
|
||||
workspaceId: upsertContext.workspaceId,
|
||||
deletedAt: IsNull(),
|
||||
},
|
||||
});
|
||||
|
||||
if (!isDefined(view)) {
|
||||
throw new ViewException(
|
||||
t`View not found after upsert`,
|
||||
ViewExceptionCode.VIEW_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private computeViewFieldOperations({
|
||||
inputFields,
|
||||
existingViewFields,
|
||||
viewId,
|
||||
workspaceId,
|
||||
applicationId,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
flatFieldMetadataMaps,
|
||||
flatViewMaps,
|
||||
}: {
|
||||
inputFields: UpsertViewWidgetViewFieldInput[];
|
||||
existingViewFields: FlatViewField[];
|
||||
viewId: string;
|
||||
workspaceId: string;
|
||||
applicationId: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
now: string;
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
flatViewMaps: FlatViewMaps;
|
||||
}): {
|
||||
fieldsToCreate: FlatViewField[];
|
||||
fieldsToUpdate: FlatViewField[];
|
||||
} {
|
||||
const fieldsToCreate: FlatViewField[] = [];
|
||||
const fieldsToUpdate: FlatViewField[] = [];
|
||||
|
||||
for (const inputField of inputFields) {
|
||||
const existingField = isDefined(inputField.viewFieldId)
|
||||
? existingViewFields.find((f) => f.id === inputField.viewFieldId)
|
||||
: isDefined(inputField.fieldMetadataId)
|
||||
? existingViewFields.find(
|
||||
(f) => f.fieldMetadataId === inputField.fieldMetadataId,
|
||||
)
|
||||
: undefined;
|
||||
|
||||
if (isDefined(existingField)) {
|
||||
const resolvedIsVisible = isDefined(existingField.overrides?.isVisible)
|
||||
? existingField.overrides.isVisible
|
||||
: existingField.isVisible;
|
||||
const resolvedPosition = isDefined(existingField.overrides?.position)
|
||||
? existingField.overrides.position
|
||||
: existingField.position;
|
||||
const resolvedSize = isDefined(existingField.overrides?.size)
|
||||
? existingField.overrides.size
|
||||
: existingField.size;
|
||||
|
||||
const hasChanged =
|
||||
resolvedIsVisible !== inputField.isVisible ||
|
||||
resolvedPosition !== inputField.position ||
|
||||
(isDefined(inputField.size) && resolvedSize !== inputField.size);
|
||||
|
||||
if (!hasChanged) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const shouldOverride = isCallerOverridingEntity({
|
||||
callerApplicationUniversalIdentifier: applicationUniversalIdentifier,
|
||||
entityApplicationUniversalIdentifier:
|
||||
existingField.applicationUniversalIdentifier,
|
||||
workspaceCustomApplicationUniversalIdentifier:
|
||||
applicationUniversalIdentifier,
|
||||
});
|
||||
|
||||
const { overrides, updatedEditableProperties: sanitizedFieldProps } =
|
||||
sanitizeOverridableEntityInput({
|
||||
metadataName: 'viewField',
|
||||
existingFlatEntity: existingField,
|
||||
updatedEditableProperties: {
|
||||
isVisible: inputField.isVisible,
|
||||
position: inputField.position,
|
||||
...(isDefined(inputField.size) ? { size: inputField.size } : {}),
|
||||
},
|
||||
shouldOverride,
|
||||
});
|
||||
|
||||
const updatedField: FlatViewField = {
|
||||
...existingField,
|
||||
...sanitizedFieldProps,
|
||||
overrides,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
if (isDefined(overrides)) {
|
||||
updatedField.universalOverrides =
|
||||
fromViewFieldOverridesToUniversalOverrides({
|
||||
overrides,
|
||||
viewFieldGroupUniversalIdentifierById: {},
|
||||
});
|
||||
} else {
|
||||
updatedField.universalOverrides = null;
|
||||
}
|
||||
|
||||
fieldsToUpdate.push(updatedField);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isDefined(inputField.fieldMetadataId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const fieldMetadata = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: inputField.fieldMetadataId,
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
if (!isDefined(fieldMetadata)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const { fieldMetadataUniversalIdentifier, viewUniversalIdentifier } =
|
||||
resolveEntityRelationUniversalIdentifiers({
|
||||
metadataName: 'viewField',
|
||||
foreignKeyValues: {
|
||||
fieldMetadataId: inputField.fieldMetadataId,
|
||||
viewId,
|
||||
},
|
||||
flatEntityMaps: {
|
||||
flatFieldMetadataMaps,
|
||||
flatViewMaps,
|
||||
},
|
||||
});
|
||||
|
||||
fieldsToCreate.push({
|
||||
id: v4(),
|
||||
workspaceId,
|
||||
applicationId,
|
||||
universalIdentifier: v4(),
|
||||
applicationUniversalIdentifier,
|
||||
fieldMetadataId: inputField.fieldMetadataId,
|
||||
fieldMetadataUniversalIdentifier,
|
||||
viewId,
|
||||
viewUniversalIdentifier,
|
||||
viewFieldGroupId: null,
|
||||
viewFieldGroupUniversalIdentifier: null,
|
||||
isVisible: inputField.isVisible,
|
||||
size: inputField.size ?? DEFAULT_VIEW_FIELD_SIZE,
|
||||
position: inputField.position,
|
||||
aggregateOperation: null,
|
||||
overrides: null,
|
||||
universalOverrides: null,
|
||||
isActive: true,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
});
|
||||
}
|
||||
|
||||
return { fieldsToCreate, fieldsToUpdate };
|
||||
}
|
||||
|
||||
private computeViewFilterGroupOperations({
|
||||
inputFilterGroups,
|
||||
existingViewFilterGroups,
|
||||
viewId,
|
||||
workspaceId,
|
||||
applicationId,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
flatViewMaps,
|
||||
flatViewFilterGroupMaps,
|
||||
}: {
|
||||
inputFilterGroups: UpsertViewWidgetViewFilterGroupInput[];
|
||||
existingViewFilterGroups: FlatViewFilterGroup[];
|
||||
viewId: string;
|
||||
workspaceId: string;
|
||||
applicationId: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
now: string;
|
||||
flatViewMaps: FlatViewMaps;
|
||||
flatViewFilterGroupMaps: FlatViewFilterGroupMaps;
|
||||
}): {
|
||||
filterGroupsToCreate: FlatViewFilterGroup[];
|
||||
filterGroupsToUpdate: FlatViewFilterGroup[];
|
||||
filterGroupsToRemove: FlatViewFilterGroup[];
|
||||
} {
|
||||
const filterGroupsToCreate: FlatViewFilterGroup[] = [];
|
||||
const filterGroupsToUpdate: FlatViewFilterGroup[] = [];
|
||||
const inputFilterGroupIds = new Set(
|
||||
inputFilterGroups.map((g) => g.id).filter(isDefined),
|
||||
);
|
||||
|
||||
for (const inputGroup of inputFilterGroups) {
|
||||
const existingGroup = isDefined(inputGroup.id)
|
||||
? existingViewFilterGroups.find((g) => g.id === inputGroup.id)
|
||||
: undefined;
|
||||
|
||||
if (!isDefined(existingGroup)) {
|
||||
const filterGroupId = inputGroup.id ?? v4();
|
||||
|
||||
const {
|
||||
viewUniversalIdentifier,
|
||||
parentViewFilterGroupUniversalIdentifier,
|
||||
} = resolveEntityRelationUniversalIdentifiers({
|
||||
metadataName: 'viewFilterGroup',
|
||||
foreignKeyValues: {
|
||||
viewId,
|
||||
parentViewFilterGroupId: inputGroup.parentViewFilterGroupId,
|
||||
},
|
||||
flatEntityMaps: { flatViewMaps, flatViewFilterGroupMaps },
|
||||
});
|
||||
|
||||
filterGroupsToCreate.push({
|
||||
id: filterGroupId,
|
||||
workspaceId,
|
||||
applicationId,
|
||||
universalIdentifier: filterGroupId,
|
||||
applicationUniversalIdentifier,
|
||||
viewId,
|
||||
viewUniversalIdentifier,
|
||||
logicalOperator:
|
||||
inputGroup.logicalOperator ?? ViewFilterGroupLogicalOperator.AND,
|
||||
parentViewFilterGroupId: inputGroup.parentViewFilterGroupId ?? null,
|
||||
parentViewFilterGroupUniversalIdentifier,
|
||||
positionInViewFilterGroup:
|
||||
inputGroup.positionInViewFilterGroup ?? null,
|
||||
viewFilterIds: [],
|
||||
viewFilterUniversalIdentifiers: [],
|
||||
childViewFilterGroupIds: [],
|
||||
childViewFilterGroupUniversalIdentifiers: [],
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
});
|
||||
} else {
|
||||
const hasChanged =
|
||||
existingGroup.logicalOperator !== inputGroup.logicalOperator ||
|
||||
existingGroup.positionInViewFilterGroup !==
|
||||
inputGroup.positionInViewFilterGroup ||
|
||||
existingGroup.parentViewFilterGroupId !==
|
||||
inputGroup.parentViewFilterGroupId;
|
||||
|
||||
if (hasChanged) {
|
||||
const resolvedParentId =
|
||||
inputGroup.parentViewFilterGroupId ??
|
||||
existingGroup.parentViewFilterGroupId;
|
||||
|
||||
const { parentViewFilterGroupUniversalIdentifier } =
|
||||
resolveEntityRelationUniversalIdentifiers({
|
||||
metadataName: 'viewFilterGroup',
|
||||
foreignKeyValues: {
|
||||
parentViewFilterGroupId: resolvedParentId,
|
||||
},
|
||||
flatEntityMaps: { flatViewFilterGroupMaps },
|
||||
});
|
||||
|
||||
filterGroupsToUpdate.push({
|
||||
...existingGroup,
|
||||
logicalOperator:
|
||||
inputGroup.logicalOperator ?? existingGroup.logicalOperator,
|
||||
positionInViewFilterGroup:
|
||||
inputGroup.positionInViewFilterGroup ??
|
||||
existingGroup.positionInViewFilterGroup,
|
||||
parentViewFilterGroupId: resolvedParentId,
|
||||
parentViewFilterGroupUniversalIdentifier,
|
||||
updatedAt: now,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const filterGroupsToRemove = existingViewFilterGroups.filter(
|
||||
(g) => !inputFilterGroupIds.has(g.id),
|
||||
);
|
||||
|
||||
return { filterGroupsToCreate, filterGroupsToUpdate, filterGroupsToRemove };
|
||||
}
|
||||
|
||||
private computeViewFilterOperations({
|
||||
inputFilters,
|
||||
existingViewFilters,
|
||||
viewId,
|
||||
workspaceId,
|
||||
applicationId,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
flatFieldMetadataMaps,
|
||||
flatViewMaps,
|
||||
flatViewFilterGroupMaps,
|
||||
}: {
|
||||
inputFilters: UpsertViewWidgetViewFilterInput[];
|
||||
existingViewFilters: FlatViewFilter[];
|
||||
viewId: string;
|
||||
workspaceId: string;
|
||||
applicationId: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
now: string;
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
flatViewMaps: FlatViewMaps;
|
||||
flatViewFilterGroupMaps: FlatViewFilterGroupMaps;
|
||||
}): {
|
||||
filtersToCreate: FlatViewFilter[];
|
||||
filtersToUpdate: FlatViewFilter[];
|
||||
filtersToRemove: FlatViewFilter[];
|
||||
} {
|
||||
const filtersToCreate: FlatViewFilter[] = [];
|
||||
const filtersToUpdate: FlatViewFilter[] = [];
|
||||
const inputFilterIds = new Set(
|
||||
inputFilters.map((f) => f.id).filter(isDefined),
|
||||
);
|
||||
|
||||
for (const inputFilter of inputFilters) {
|
||||
const existingFilter = isDefined(inputFilter.id)
|
||||
? existingViewFilters.find((f) => f.id === inputFilter.id)
|
||||
: undefined;
|
||||
|
||||
if (!isDefined(existingFilter)) {
|
||||
const filterId = inputFilter.id ?? v4();
|
||||
|
||||
const {
|
||||
fieldMetadataUniversalIdentifier,
|
||||
viewUniversalIdentifier,
|
||||
viewFilterGroupUniversalIdentifier,
|
||||
} = resolveEntityRelationUniversalIdentifiers({
|
||||
metadataName: 'viewFilter',
|
||||
foreignKeyValues: {
|
||||
fieldMetadataId: inputFilter.fieldMetadataId,
|
||||
viewId,
|
||||
viewFilterGroupId: inputFilter.viewFilterGroupId,
|
||||
},
|
||||
flatEntityMaps: {
|
||||
flatFieldMetadataMaps,
|
||||
flatViewMaps,
|
||||
flatViewFilterGroupMaps,
|
||||
},
|
||||
});
|
||||
|
||||
filtersToCreate.push({
|
||||
id: filterId,
|
||||
workspaceId,
|
||||
applicationId,
|
||||
universalIdentifier: v4(),
|
||||
applicationUniversalIdentifier,
|
||||
fieldMetadataId: inputFilter.fieldMetadataId,
|
||||
fieldMetadataUniversalIdentifier,
|
||||
viewId,
|
||||
viewUniversalIdentifier,
|
||||
operand: inputFilter.operand ?? ViewFilterOperand.CONTAINS,
|
||||
value: inputFilter.value,
|
||||
viewFilterGroupId: inputFilter.viewFilterGroupId ?? null,
|
||||
viewFilterGroupUniversalIdentifier,
|
||||
positionInViewFilterGroup:
|
||||
inputFilter.positionInViewFilterGroup ?? null,
|
||||
subFieldName: inputFilter.subFieldName ?? null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
});
|
||||
} else {
|
||||
const hasChanged =
|
||||
existingFilter.fieldMetadataId !== inputFilter.fieldMetadataId ||
|
||||
existingFilter.operand !== inputFilter.operand ||
|
||||
JSON.stringify(existingFilter.value) !==
|
||||
JSON.stringify(inputFilter.value) ||
|
||||
existingFilter.viewFilterGroupId !== inputFilter.viewFilterGroupId ||
|
||||
existingFilter.positionInViewFilterGroup !==
|
||||
inputFilter.positionInViewFilterGroup ||
|
||||
existingFilter.subFieldName !== inputFilter.subFieldName;
|
||||
|
||||
if (hasChanged) {
|
||||
const {
|
||||
fieldMetadataUniversalIdentifier,
|
||||
viewFilterGroupUniversalIdentifier,
|
||||
} = resolveEntityRelationUniversalIdentifiers({
|
||||
metadataName: 'viewFilter',
|
||||
foreignKeyValues: {
|
||||
fieldMetadataId: inputFilter.fieldMetadataId,
|
||||
viewFilterGroupId: inputFilter.viewFilterGroupId,
|
||||
},
|
||||
flatEntityMaps: {
|
||||
flatFieldMetadataMaps,
|
||||
flatViewFilterGroupMaps,
|
||||
},
|
||||
});
|
||||
|
||||
filtersToUpdate.push({
|
||||
...existingFilter,
|
||||
fieldMetadataId: inputFilter.fieldMetadataId,
|
||||
fieldMetadataUniversalIdentifier,
|
||||
operand: inputFilter.operand ?? existingFilter.operand,
|
||||
value: inputFilter.value,
|
||||
viewFilterGroupId:
|
||||
inputFilter.viewFilterGroupId ?? existingFilter.viewFilterGroupId,
|
||||
viewFilterGroupUniversalIdentifier,
|
||||
positionInViewFilterGroup:
|
||||
inputFilter.positionInViewFilterGroup ??
|
||||
existingFilter.positionInViewFilterGroup,
|
||||
subFieldName:
|
||||
inputFilter.subFieldName ?? existingFilter.subFieldName,
|
||||
updatedAt: now,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const filtersToRemove = existingViewFilters.filter(
|
||||
(f) => !inputFilterIds.has(f.id),
|
||||
);
|
||||
|
||||
return { filtersToCreate, filtersToUpdate, filtersToRemove };
|
||||
}
|
||||
|
||||
private computeViewSortOperations({
|
||||
inputSorts,
|
||||
existingViewSorts,
|
||||
viewId,
|
||||
workspaceId,
|
||||
applicationId,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
flatFieldMetadataMaps,
|
||||
flatViewMaps,
|
||||
}: {
|
||||
inputSorts: UpsertViewWidgetViewSortInput[];
|
||||
existingViewSorts: FlatViewSort[];
|
||||
viewId: string;
|
||||
workspaceId: string;
|
||||
applicationId: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
now: string;
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
flatViewMaps: FlatViewMaps;
|
||||
}): {
|
||||
sortsToCreate: FlatViewSort[];
|
||||
sortsToUpdate: FlatViewSort[];
|
||||
sortsToRemove: FlatViewSort[];
|
||||
} {
|
||||
const sortsToCreate: FlatViewSort[] = [];
|
||||
const sortsToUpdate: FlatViewSort[] = [];
|
||||
const inputSortIds = new Set(inputSorts.map((s) => s.id).filter(isDefined));
|
||||
|
||||
for (const inputSort of inputSorts) {
|
||||
const existingSort = isDefined(inputSort.id)
|
||||
? existingViewSorts.find((s) => s.id === inputSort.id)
|
||||
: undefined;
|
||||
|
||||
if (!isDefined(existingSort)) {
|
||||
const sortId = inputSort.id ?? v4();
|
||||
|
||||
const { fieldMetadataUniversalIdentifier, viewUniversalIdentifier } =
|
||||
resolveEntityRelationUniversalIdentifiers({
|
||||
metadataName: 'viewSort',
|
||||
foreignKeyValues: {
|
||||
fieldMetadataId: inputSort.fieldMetadataId,
|
||||
viewId,
|
||||
},
|
||||
flatEntityMaps: {
|
||||
flatFieldMetadataMaps,
|
||||
flatViewMaps,
|
||||
},
|
||||
});
|
||||
|
||||
sortsToCreate.push({
|
||||
id: sortId,
|
||||
workspaceId,
|
||||
applicationId,
|
||||
universalIdentifier: v4(),
|
||||
applicationUniversalIdentifier,
|
||||
fieldMetadataId: inputSort.fieldMetadataId,
|
||||
fieldMetadataUniversalIdentifier,
|
||||
viewId,
|
||||
viewUniversalIdentifier,
|
||||
direction: inputSort.direction ?? ViewSortDirection.ASC,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
});
|
||||
} else {
|
||||
const hasChanged =
|
||||
existingSort.fieldMetadataId !== inputSort.fieldMetadataId ||
|
||||
existingSort.direction !== inputSort.direction;
|
||||
|
||||
if (hasChanged) {
|
||||
const { fieldMetadataUniversalIdentifier } =
|
||||
resolveEntityRelationUniversalIdentifiers({
|
||||
metadataName: 'viewSort',
|
||||
foreignKeyValues: {
|
||||
fieldMetadataId: inputSort.fieldMetadataId,
|
||||
},
|
||||
flatEntityMaps: {
|
||||
flatFieldMetadataMaps,
|
||||
},
|
||||
});
|
||||
|
||||
sortsToUpdate.push({
|
||||
...existingSort,
|
||||
fieldMetadataId: inputSort.fieldMetadataId,
|
||||
fieldMetadataUniversalIdentifier,
|
||||
direction: inputSort.direction ?? existingSort.direction,
|
||||
updatedAt: now,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const sortsToRemove = existingViewSorts.filter(
|
||||
(s) => !inputSortIds.has(s.id),
|
||||
);
|
||||
|
||||
return { sortsToCreate, sortsToUpdate, sortsToRemove };
|
||||
}
|
||||
}
|
||||
+2
@@ -59,6 +59,8 @@ export const viewGraphqlApiExceptionHandler = (error: Error, i18n: I18n) => {
|
||||
throw new ForbiddenError(error.message, {
|
||||
userFriendlyMessage: error.userFriendlyMessage,
|
||||
});
|
||||
case ViewExceptionCode.VIEW_WIDGET_NOT_FOUND:
|
||||
throw new NotFoundError(error.message);
|
||||
default: {
|
||||
return assertUnreachable(error.code);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { ViewFieldModule } from 'src/engine/metadata-modules/view-field/view-fie
|
||||
import { ViewFilterModule } from 'src/engine/metadata-modules/view-filter/view-filter.module';
|
||||
import { ViewPermissionsModule } from 'src/engine/metadata-modules/view-permissions/view-permissions.module';
|
||||
import { ViewSortModule } from 'src/engine/metadata-modules/view-sort/view-sort.module';
|
||||
import { ViewWidgetUpsertService } from 'src/engine/metadata-modules/view/services/view-widget-upsert.service';
|
||||
import { ViewController } from 'src/engine/metadata-modules/view/controllers/view.controller';
|
||||
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
|
||||
import { ViewResolver } from 'src/engine/metadata-modules/view/resolvers/view.resolver';
|
||||
@@ -44,6 +45,7 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
|
||||
ViewResolver,
|
||||
ViewQueryParamsService,
|
||||
ViewToolsFactory,
|
||||
ViewWidgetUpsertService,
|
||||
],
|
||||
exports: [
|
||||
ViewService,
|
||||
|
||||
Reference in New Issue
Block a user