Fix view rest api in v2 (#15398)
# Introduction Initially wanted to reactive the test introduced in https://github.com/twentyhq/twenty/pull/15393, that was failing because of direct data source access removing all views ( even seeded one ) which was making the test fail While doing so discovered a lot of issue with the rest API: - Rest api wasn't consuming the v2 at all - Rest api wasn't prepared to handle v2 exceptions - Rest api did not handled unknown exceptions ( timeout ) Refactored the cleanup of each test to follow black box pattern and avoid test leakage
This commit is contained in:
+82
-5
@@ -13,11 +13,14 @@ import {
|
||||
|
||||
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 { WorkspaceEntity } 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 { CreateViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/create-view-field.input';
|
||||
import { UpdateViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/update-view-field.input';
|
||||
import { ViewFieldDTO } from 'src/engine/metadata-modules/view-field/dtos/view-field.dto';
|
||||
import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
|
||||
import {
|
||||
generateViewFieldExceptionMessage,
|
||||
@@ -27,13 +30,18 @@ import {
|
||||
ViewFieldExceptionMessageKey,
|
||||
} from 'src/engine/metadata-modules/view-field/exceptions/view-field.exception';
|
||||
import { ViewFieldRestApiExceptionFilter } from 'src/engine/metadata-modules/view-field/filters/view-field-rest-api-exception.filter';
|
||||
import { ViewFieldV2Service } from 'src/engine/metadata-modules/view-field/services/view-field-v2.service';
|
||||
import { ViewFieldService } from 'src/engine/metadata-modules/view-field/services/view-field.service';
|
||||
|
||||
@Controller('rest/metadata/viewFields')
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@UseFilters(ViewFieldRestApiExceptionFilter)
|
||||
export class ViewFieldController {
|
||||
constructor(private readonly viewFieldService: ViewFieldService) {}
|
||||
constructor(
|
||||
private readonly viewFieldService: ViewFieldService,
|
||||
private readonly viewFieldV2Service: ViewFieldV2Service,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
async findMany(
|
||||
@@ -77,25 +85,79 @@ export class ViewFieldController {
|
||||
@Param('id') id: string,
|
||||
@Body() input: UpdateViewFieldInput['update'],
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<ViewFieldEntity> {
|
||||
): Promise<ViewFieldDTO> {
|
||||
const isWorkspaceMigrationV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
if (isWorkspaceMigrationV2Enabled) {
|
||||
return await this.viewFieldV2Service.updateOne({
|
||||
updateViewFieldInput: { id, update: input },
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
|
||||
const updatedViewField = await this.viewFieldService.update(
|
||||
id,
|
||||
workspace.id,
|
||||
input,
|
||||
);
|
||||
|
||||
return updatedViewField;
|
||||
// Convert ViewFieldEntity to ViewFieldDTO for consistency
|
||||
return {
|
||||
id: updatedViewField.id,
|
||||
fieldMetadataId: updatedViewField.fieldMetadataId,
|
||||
isVisible: updatedViewField.isVisible,
|
||||
size: updatedViewField.size,
|
||||
position: updatedViewField.position,
|
||||
aggregateOperation: updatedViewField.aggregateOperation,
|
||||
viewId: updatedViewField.viewId,
|
||||
workspaceId: updatedViewField.workspaceId,
|
||||
createdAt: updatedViewField.createdAt,
|
||||
updatedAt: updatedViewField.updatedAt,
|
||||
deletedAt: updatedViewField.deletedAt,
|
||||
};
|
||||
}
|
||||
|
||||
@Post()
|
||||
async create(
|
||||
@Body() input: CreateViewFieldInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<ViewFieldEntity> {
|
||||
return this.viewFieldService.create({
|
||||
): Promise<ViewFieldDTO> {
|
||||
const isWorkspaceMigrationV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
if (isWorkspaceMigrationV2Enabled) {
|
||||
return await this.viewFieldV2Service.createOne({
|
||||
createViewFieldInput: input,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
|
||||
const createdViewField = await this.viewFieldService.create({
|
||||
...input,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
// Convert ViewFieldEntity to ViewFieldDTO for consistency
|
||||
return {
|
||||
id: createdViewField.id,
|
||||
fieldMetadataId: createdViewField.fieldMetadataId,
|
||||
isVisible: createdViewField.isVisible,
|
||||
size: createdViewField.size,
|
||||
position: createdViewField.position,
|
||||
aggregateOperation: createdViewField.aggregateOperation,
|
||||
viewId: createdViewField.viewId,
|
||||
workspaceId: createdViewField.workspaceId,
|
||||
createdAt: createdViewField.createdAt,
|
||||
updatedAt: createdViewField.updatedAt,
|
||||
deletedAt: createdViewField.deletedAt,
|
||||
};
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@@ -103,6 +165,21 @@ export class ViewFieldController {
|
||||
@Param('id') id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<{ success: boolean }> {
|
||||
const isWorkspaceMigrationV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
if (isWorkspaceMigrationV2Enabled) {
|
||||
const deletedViewField = await this.viewFieldV2Service.deleteOne({
|
||||
deleteViewFieldInput: { id },
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
return { success: isDefined(deletedViewField) };
|
||||
}
|
||||
|
||||
const deletedViewField = await this.viewFieldService.delete(
|
||||
id,
|
||||
workspace.id,
|
||||
|
||||
+66
-22
@@ -2,47 +2,91 @@ import {
|
||||
type ArgumentsHost,
|
||||
Catch,
|
||||
type ExceptionFilter,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { type Response } from 'express';
|
||||
import { SOURCE_LOCALE } from 'twenty-shared/translations';
|
||||
|
||||
import { HttpExceptionHandlerService } from 'src/engine/core-modules/exception-handler/http-exception-handler.service';
|
||||
import { I18nService } from 'src/engine/core-modules/i18n/i18n.service';
|
||||
import {
|
||||
ViewFieldException,
|
||||
ViewFieldExceptionCode,
|
||||
} from 'src/engine/metadata-modules/view-field/exceptions/view-field.exception';
|
||||
import { type CustomException } from 'src/utils/custom-exception';
|
||||
import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2';
|
||||
import { fromWorkspaceMigrationBuilderExceptionToMetadataValidationResponseError } from 'src/engine/workspace-manager/workspace-migration-v2/interceptors/utils/from-workspace-migration-builder-exception-to-metadata-validation-response-error.util';
|
||||
import {
|
||||
type CustomException,
|
||||
UnknownException,
|
||||
} from 'src/utils/custom-exception';
|
||||
|
||||
@Catch(ViewFieldException)
|
||||
@Injectable()
|
||||
@Catch(ViewFieldException, WorkspaceMigrationBuilderExceptionV2)
|
||||
export class ViewFieldRestApiExceptionFilter implements ExceptionFilter {
|
||||
constructor(
|
||||
private readonly httpExceptionHandlerService: HttpExceptionHandlerService,
|
||||
private readonly i18nService: I18nService,
|
||||
) {}
|
||||
|
||||
catch(exception: ViewFieldException, host: ArgumentsHost) {
|
||||
catch(
|
||||
exception: ViewFieldException | WorkspaceMigrationBuilderExceptionV2,
|
||||
host: ArgumentsHost,
|
||||
) {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
|
||||
switch (exception.code) {
|
||||
case ViewFieldExceptionCode.VIEW_FIELD_NOT_FOUND:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
404,
|
||||
);
|
||||
case ViewFieldExceptionCode.INVALID_VIEW_FIELD_DATA:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
400,
|
||||
);
|
||||
default:
|
||||
// TODO: change to 500 when we have input validation
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
400,
|
||||
if (exception instanceof WorkspaceMigrationBuilderExceptionV2) {
|
||||
const i18n = this.i18nService.getI18nInstance(SOURCE_LOCALE);
|
||||
const { errors, summary } =
|
||||
fromWorkspaceMigrationBuilderExceptionToMetadataValidationResponseError(
|
||||
exception,
|
||||
i18n,
|
||||
);
|
||||
|
||||
return response.status(400).json({
|
||||
statusCode: 400,
|
||||
error: 'METADATA_VALIDATION_ERROR',
|
||||
message: exception.message || 'Validation failed',
|
||||
errors,
|
||||
summary,
|
||||
});
|
||||
}
|
||||
|
||||
if (exception instanceof ViewFieldException) {
|
||||
switch (exception.code) {
|
||||
case ViewFieldExceptionCode.VIEW_FIELD_NOT_FOUND:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
404,
|
||||
);
|
||||
case ViewFieldExceptionCode.INVALID_VIEW_FIELD_DATA:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
400,
|
||||
);
|
||||
default:
|
||||
// TODO: change to 500 when we have input validation
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
400,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback for any other exception type
|
||||
const unknownException = new UnknownException(
|
||||
'Internal server error',
|
||||
'INTERNAL_ERROR',
|
||||
);
|
||||
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
unknownException as CustomException,
|
||||
response,
|
||||
500,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+66
-22
@@ -2,47 +2,91 @@ import {
|
||||
type ArgumentsHost,
|
||||
Catch,
|
||||
type ExceptionFilter,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { type Response } from 'express';
|
||||
import { SOURCE_LOCALE } from 'twenty-shared/translations';
|
||||
|
||||
import { HttpExceptionHandlerService } from 'src/engine/core-modules/exception-handler/http-exception-handler.service';
|
||||
import { I18nService } from 'src/engine/core-modules/i18n/i18n.service';
|
||||
import {
|
||||
ViewFilterGroupException,
|
||||
ViewFilterGroupExceptionCode,
|
||||
} from 'src/engine/metadata-modules/view-filter-group/exceptions/view-filter-group.exception';
|
||||
import { type CustomException } from 'src/utils/custom-exception';
|
||||
import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2';
|
||||
import { fromWorkspaceMigrationBuilderExceptionToMetadataValidationResponseError } from 'src/engine/workspace-manager/workspace-migration-v2/interceptors/utils/from-workspace-migration-builder-exception-to-metadata-validation-response-error.util';
|
||||
import {
|
||||
type CustomException,
|
||||
UnknownException,
|
||||
} from 'src/utils/custom-exception';
|
||||
|
||||
@Catch(ViewFilterGroupException)
|
||||
@Injectable()
|
||||
@Catch(ViewFilterGroupException, WorkspaceMigrationBuilderExceptionV2)
|
||||
export class ViewFilterGroupRestApiExceptionFilter implements ExceptionFilter {
|
||||
constructor(
|
||||
private readonly httpExceptionHandlerService: HttpExceptionHandlerService,
|
||||
private readonly i18nService: I18nService,
|
||||
) {}
|
||||
|
||||
catch(exception: ViewFilterGroupException, host: ArgumentsHost) {
|
||||
catch(
|
||||
exception: ViewFilterGroupException | WorkspaceMigrationBuilderExceptionV2,
|
||||
host: ArgumentsHost,
|
||||
) {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
|
||||
switch (exception.code) {
|
||||
case ViewFilterGroupExceptionCode.VIEW_FILTER_GROUP_NOT_FOUND:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
404,
|
||||
);
|
||||
case ViewFilterGroupExceptionCode.INVALID_VIEW_FILTER_GROUP_DATA:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
400,
|
||||
);
|
||||
default:
|
||||
// TODO: change to 500 when we have input validation
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
400,
|
||||
if (exception instanceof WorkspaceMigrationBuilderExceptionV2) {
|
||||
const i18n = this.i18nService.getI18nInstance(SOURCE_LOCALE);
|
||||
const { errors, summary } =
|
||||
fromWorkspaceMigrationBuilderExceptionToMetadataValidationResponseError(
|
||||
exception,
|
||||
i18n,
|
||||
);
|
||||
|
||||
return response.status(400).json({
|
||||
statusCode: 400,
|
||||
error: 'METADATA_VALIDATION_ERROR',
|
||||
message: exception.message || 'Validation failed',
|
||||
errors,
|
||||
summary,
|
||||
});
|
||||
}
|
||||
|
||||
if (exception instanceof ViewFilterGroupException) {
|
||||
switch (exception.code) {
|
||||
case ViewFilterGroupExceptionCode.VIEW_FILTER_GROUP_NOT_FOUND:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
404,
|
||||
);
|
||||
case ViewFilterGroupExceptionCode.INVALID_VIEW_FILTER_GROUP_DATA:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
400,
|
||||
);
|
||||
default:
|
||||
// TODO: change to 500 when we have input validation
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
400,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback for any other exception type
|
||||
const unknownException = new UnknownException(
|
||||
'Internal server error',
|
||||
'INTERNAL_ERROR',
|
||||
);
|
||||
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
unknownException as CustomException,
|
||||
response,
|
||||
500,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+49
-1
@@ -13,6 +13,8 @@ import {
|
||||
|
||||
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 { WorkspaceEntity } 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';
|
||||
@@ -27,13 +29,18 @@ import {
|
||||
ViewFilterExceptionMessageKey,
|
||||
} from 'src/engine/metadata-modules/view-filter/exceptions/view-filter.exception';
|
||||
import { ViewFilterRestApiExceptionFilter } from 'src/engine/metadata-modules/view-filter/filters/view-filter-rest-api-exception.filter';
|
||||
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';
|
||||
|
||||
@Controller('rest/metadata/viewFilters')
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@UseFilters(ViewFilterRestApiExceptionFilter)
|
||||
export class ViewFilterController {
|
||||
constructor(private readonly viewFilterService: ViewFilterService) {}
|
||||
constructor(
|
||||
private readonly viewFilterService: ViewFilterService,
|
||||
private readonly viewFilterV2Service: ViewFilterV2Service,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
async findMany(
|
||||
@@ -77,6 +84,19 @@ export class ViewFilterController {
|
||||
@Body() input: CreateViewFilterInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<ViewFilterDTO> {
|
||||
const isWorkspaceMigrationV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
if (isWorkspaceMigrationV2Enabled) {
|
||||
return await this.viewFilterV2Service.createOne({
|
||||
createViewFilterInput: input,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
|
||||
return this.viewFilterService.create({
|
||||
...input,
|
||||
workspaceId: workspace.id,
|
||||
@@ -94,6 +114,19 @@ export class ViewFilterController {
|
||||
update: input.update ?? input,
|
||||
};
|
||||
|
||||
const isWorkspaceMigrationV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
if (isWorkspaceMigrationV2Enabled) {
|
||||
return await this.viewFilterV2Service.updateOne({
|
||||
updateViewFilterInput: updateInput,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
|
||||
const updatedViewFilter = await this.viewFilterService.update(
|
||||
updateInput.id,
|
||||
workspace.id,
|
||||
@@ -108,6 +141,21 @@ export class ViewFilterController {
|
||||
@Param('id') id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<{ success: boolean }> {
|
||||
const isWorkspaceMigrationV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
if (isWorkspaceMigrationV2Enabled) {
|
||||
const deletedViewFilter = await this.viewFilterV2Service.deleteOne({
|
||||
deleteViewFilterInput: { id },
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
return { success: isDefined(deletedViewFilter) };
|
||||
}
|
||||
|
||||
const deletedViewFilter = await this.viewFilterService.delete(
|
||||
id,
|
||||
workspace.id,
|
||||
|
||||
+66
-22
@@ -2,47 +2,91 @@ import {
|
||||
type ArgumentsHost,
|
||||
Catch,
|
||||
type ExceptionFilter,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { type Response } from 'express';
|
||||
import { SOURCE_LOCALE } from 'twenty-shared/translations';
|
||||
|
||||
import { HttpExceptionHandlerService } from 'src/engine/core-modules/exception-handler/http-exception-handler.service';
|
||||
import { I18nService } from 'src/engine/core-modules/i18n/i18n.service';
|
||||
import {
|
||||
ViewFilterException,
|
||||
ViewFilterExceptionCode,
|
||||
} from 'src/engine/metadata-modules/view-filter/exceptions/view-filter.exception';
|
||||
import { type CustomException } from 'src/utils/custom-exception';
|
||||
import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2';
|
||||
import { fromWorkspaceMigrationBuilderExceptionToMetadataValidationResponseError } from 'src/engine/workspace-manager/workspace-migration-v2/interceptors/utils/from-workspace-migration-builder-exception-to-metadata-validation-response-error.util';
|
||||
import {
|
||||
type CustomException,
|
||||
UnknownException,
|
||||
} from 'src/utils/custom-exception';
|
||||
|
||||
@Catch(ViewFilterException)
|
||||
@Injectable()
|
||||
@Catch(ViewFilterException, WorkspaceMigrationBuilderExceptionV2)
|
||||
export class ViewFilterRestApiExceptionFilter implements ExceptionFilter {
|
||||
constructor(
|
||||
private readonly httpExceptionHandlerService: HttpExceptionHandlerService,
|
||||
private readonly i18nService: I18nService,
|
||||
) {}
|
||||
|
||||
catch(exception: ViewFilterException, host: ArgumentsHost) {
|
||||
catch(
|
||||
exception: ViewFilterException | WorkspaceMigrationBuilderExceptionV2,
|
||||
host: ArgumentsHost,
|
||||
) {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
|
||||
switch (exception.code) {
|
||||
case ViewFilterExceptionCode.VIEW_FILTER_NOT_FOUND:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
404,
|
||||
);
|
||||
case ViewFilterExceptionCode.INVALID_VIEW_FILTER_DATA:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
400,
|
||||
);
|
||||
default:
|
||||
// TODO: change to 500 when we have input validation
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
400,
|
||||
if (exception instanceof WorkspaceMigrationBuilderExceptionV2) {
|
||||
const i18n = this.i18nService.getI18nInstance(SOURCE_LOCALE);
|
||||
const { errors, summary } =
|
||||
fromWorkspaceMigrationBuilderExceptionToMetadataValidationResponseError(
|
||||
exception,
|
||||
i18n,
|
||||
);
|
||||
|
||||
return response.status(400).json({
|
||||
statusCode: 400,
|
||||
error: 'METADATA_VALIDATION_ERROR',
|
||||
message: exception.message || 'Validation failed',
|
||||
errors,
|
||||
summary,
|
||||
});
|
||||
}
|
||||
|
||||
if (exception instanceof ViewFilterException) {
|
||||
switch (exception.code) {
|
||||
case ViewFilterExceptionCode.VIEW_FILTER_NOT_FOUND:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
404,
|
||||
);
|
||||
case ViewFilterExceptionCode.INVALID_VIEW_FILTER_DATA:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
400,
|
||||
);
|
||||
default:
|
||||
// TODO: change to 500 when we have input validation
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
400,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback for any other exception type
|
||||
const unknownException = new UnknownException(
|
||||
'Internal server error',
|
||||
'INTERNAL_ERROR',
|
||||
);
|
||||
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
unknownException as CustomException,
|
||||
response,
|
||||
500,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+54
-1
@@ -13,6 +13,8 @@ import {
|
||||
|
||||
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 { WorkspaceEntity } 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';
|
||||
@@ -27,13 +29,18 @@ import {
|
||||
ViewGroupExceptionMessageKey,
|
||||
} from 'src/engine/metadata-modules/view-group/exceptions/view-group.exception';
|
||||
import { ViewGroupRestApiExceptionFilter } from 'src/engine/metadata-modules/view-group/filters/view-group-rest-api-exception.filter';
|
||||
import { ViewGroupV2Service } from 'src/engine/metadata-modules/view-group/services/view-group-v2.service';
|
||||
import { ViewGroupService } from 'src/engine/metadata-modules/view-group/services/view-group.service';
|
||||
|
||||
@Controller('rest/metadata/viewGroups')
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@UseFilters(ViewGroupRestApiExceptionFilter)
|
||||
export class ViewGroupController {
|
||||
constructor(private readonly viewGroupService: ViewGroupService) {}
|
||||
constructor(
|
||||
private readonly viewGroupService: ViewGroupService,
|
||||
private readonly viewGroupV2Service: ViewGroupV2Service,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
async findMany(
|
||||
@@ -77,6 +84,19 @@ export class ViewGroupController {
|
||||
@Body() input: CreateViewGroupInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<ViewGroupDTO> {
|
||||
const isWorkspaceMigrationV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
if (isWorkspaceMigrationV2Enabled) {
|
||||
return await this.viewGroupV2Service.createOne({
|
||||
createViewGroupInput: input,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
|
||||
return this.viewGroupService.create({
|
||||
...input,
|
||||
workspaceId: workspace.id,
|
||||
@@ -89,6 +109,24 @@ export class ViewGroupController {
|
||||
@Body() input: UpdateViewGroupInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<ViewGroupDTO> {
|
||||
const isWorkspaceMigrationV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
if (isWorkspaceMigrationV2Enabled) {
|
||||
const updateInput = {
|
||||
id,
|
||||
update: input.update ?? input,
|
||||
};
|
||||
|
||||
return await this.viewGroupV2Service.updateOne({
|
||||
updateViewGroupInput: updateInput,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
|
||||
const updatedViewGroup = await this.viewGroupService.update(
|
||||
id,
|
||||
workspace.id,
|
||||
@@ -103,6 +141,21 @@ export class ViewGroupController {
|
||||
@Param('id') id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<{ success: boolean }> {
|
||||
const isWorkspaceMigrationV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
if (isWorkspaceMigrationV2Enabled) {
|
||||
const deletedViewGroup = await this.viewGroupV2Service.deleteOne({
|
||||
deleteViewGroupInput: { id },
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
return { success: isDefined(deletedViewGroup) };
|
||||
}
|
||||
|
||||
const deletedViewGroup = await this.viewGroupService.delete(
|
||||
id,
|
||||
workspace.id,
|
||||
|
||||
+5
-3
@@ -1,6 +1,5 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { type MessageDescriptor } from '@lingui/core';
|
||||
import { assertUnreachable } from 'twenty-shared/utils';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
|
||||
import { CustomException } from 'src/utils/custom-exception';
|
||||
|
||||
@@ -44,7 +43,7 @@ export const generateViewGroupExceptionMessage = (
|
||||
case ViewGroupExceptionMessageKey.FIELD_METADATA_ID_REQUIRED:
|
||||
return 'FieldMetadataId is required';
|
||||
default:
|
||||
assertUnreachable(key);
|
||||
return 'unknown';
|
||||
}
|
||||
};
|
||||
|
||||
@@ -58,5 +57,8 @@ export const generateViewGroupUserFriendlyExceptionMessage = (
|
||||
return msg`ViewId is required to create a view group.`;
|
||||
case ViewGroupExceptionMessageKey.FIELD_METADATA_ID_REQUIRED:
|
||||
return msg`FieldMetadataId is required to create a view group.`;
|
||||
default: {
|
||||
return msg`unknown`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
+65
-22
@@ -2,47 +2,90 @@ import {
|
||||
type ArgumentsHost,
|
||||
Catch,
|
||||
type ExceptionFilter,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { type Response } from 'express';
|
||||
import { SOURCE_LOCALE } from 'twenty-shared/translations';
|
||||
|
||||
import { HttpExceptionHandlerService } from 'src/engine/core-modules/exception-handler/http-exception-handler.service';
|
||||
import { I18nService } from 'src/engine/core-modules/i18n/i18n.service';
|
||||
import {
|
||||
ViewGroupException,
|
||||
ViewGroupExceptionCode,
|
||||
} from 'src/engine/metadata-modules/view-group/exceptions/view-group.exception';
|
||||
import { type CustomException } from 'src/utils/custom-exception';
|
||||
import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2';
|
||||
import { fromWorkspaceMigrationBuilderExceptionToMetadataValidationResponseError } from 'src/engine/workspace-manager/workspace-migration-v2/interceptors/utils/from-workspace-migration-builder-exception-to-metadata-validation-response-error.util';
|
||||
import {
|
||||
type CustomException,
|
||||
UnknownException,
|
||||
} from 'src/utils/custom-exception';
|
||||
|
||||
@Catch(ViewGroupException)
|
||||
@Injectable()
|
||||
@Catch(ViewGroupException, WorkspaceMigrationBuilderExceptionV2)
|
||||
export class ViewGroupRestApiExceptionFilter implements ExceptionFilter {
|
||||
constructor(
|
||||
private readonly httpExceptionHandlerService: HttpExceptionHandlerService,
|
||||
private readonly i18nService: I18nService,
|
||||
) {}
|
||||
|
||||
catch(exception: ViewGroupException, host: ArgumentsHost) {
|
||||
catch(
|
||||
exception: ViewGroupException | WorkspaceMigrationBuilderExceptionV2,
|
||||
host: ArgumentsHost,
|
||||
) {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
|
||||
switch (exception.code) {
|
||||
case ViewGroupExceptionCode.VIEW_GROUP_NOT_FOUND:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
404,
|
||||
);
|
||||
case ViewGroupExceptionCode.INVALID_VIEW_GROUP_DATA:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
400,
|
||||
);
|
||||
default:
|
||||
// TODO: change to 500 when we have input validation
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
400,
|
||||
if (exception instanceof WorkspaceMigrationBuilderExceptionV2) {
|
||||
const i18n = this.i18nService.getI18nInstance(SOURCE_LOCALE);
|
||||
const { errors, summary } =
|
||||
fromWorkspaceMigrationBuilderExceptionToMetadataValidationResponseError(
|
||||
exception,
|
||||
i18n,
|
||||
);
|
||||
|
||||
return response.status(400).json({
|
||||
statusCode: 400,
|
||||
error: 'METADATA_VALIDATION_ERROR',
|
||||
message: exception.message || 'Validation failed',
|
||||
errors,
|
||||
summary,
|
||||
});
|
||||
}
|
||||
|
||||
if (exception instanceof ViewGroupException) {
|
||||
switch (exception.code) {
|
||||
case ViewGroupExceptionCode.VIEW_GROUP_NOT_FOUND:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
404,
|
||||
);
|
||||
case ViewGroupExceptionCode.INVALID_VIEW_GROUP_DATA:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
400,
|
||||
);
|
||||
default:
|
||||
// TODO: change to 500 when we have input validation
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
400,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const unknownException = new UnknownException(
|
||||
'Internal server error',
|
||||
'INTERNAL_ERROR',
|
||||
);
|
||||
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
unknownException as CustomException,
|
||||
response,
|
||||
500,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+66
-22
@@ -2,47 +2,91 @@ import {
|
||||
type ArgumentsHost,
|
||||
Catch,
|
||||
type ExceptionFilter,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { type Response } from 'express';
|
||||
import { SOURCE_LOCALE } from 'twenty-shared/translations';
|
||||
|
||||
import { HttpExceptionHandlerService } from 'src/engine/core-modules/exception-handler/http-exception-handler.service';
|
||||
import { I18nService } from 'src/engine/core-modules/i18n/i18n.service';
|
||||
import {
|
||||
ViewSortException,
|
||||
ViewSortExceptionCode,
|
||||
} from 'src/engine/metadata-modules/view-sort/exceptions/view-sort.exception';
|
||||
import { type CustomException } from 'src/utils/custom-exception';
|
||||
import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2';
|
||||
import { fromWorkspaceMigrationBuilderExceptionToMetadataValidationResponseError } from 'src/engine/workspace-manager/workspace-migration-v2/interceptors/utils/from-workspace-migration-builder-exception-to-metadata-validation-response-error.util';
|
||||
import {
|
||||
type CustomException,
|
||||
UnknownException,
|
||||
} from 'src/utils/custom-exception';
|
||||
|
||||
@Catch(ViewSortException)
|
||||
@Injectable()
|
||||
@Catch(ViewSortException, WorkspaceMigrationBuilderExceptionV2)
|
||||
export class ViewSortRestApiExceptionFilter implements ExceptionFilter {
|
||||
constructor(
|
||||
private readonly httpExceptionHandlerService: HttpExceptionHandlerService,
|
||||
private readonly i18nService: I18nService,
|
||||
) {}
|
||||
|
||||
catch(exception: ViewSortException, host: ArgumentsHost) {
|
||||
catch(
|
||||
exception: ViewSortException | WorkspaceMigrationBuilderExceptionV2,
|
||||
host: ArgumentsHost,
|
||||
) {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
|
||||
switch (exception.code) {
|
||||
case ViewSortExceptionCode.VIEW_SORT_NOT_FOUND:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
404,
|
||||
);
|
||||
case ViewSortExceptionCode.INVALID_VIEW_SORT_DATA:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
400,
|
||||
);
|
||||
default:
|
||||
// TODO: change to 500 when we have input validation
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
400,
|
||||
if (exception instanceof WorkspaceMigrationBuilderExceptionV2) {
|
||||
const i18n = this.i18nService.getI18nInstance(SOURCE_LOCALE);
|
||||
const { errors, summary } =
|
||||
fromWorkspaceMigrationBuilderExceptionToMetadataValidationResponseError(
|
||||
exception,
|
||||
i18n,
|
||||
);
|
||||
|
||||
return response.status(400).json({
|
||||
statusCode: 400,
|
||||
error: 'METADATA_VALIDATION_ERROR',
|
||||
message: exception.message || 'Validation failed',
|
||||
errors,
|
||||
summary,
|
||||
});
|
||||
}
|
||||
|
||||
if (exception instanceof ViewSortException) {
|
||||
switch (exception.code) {
|
||||
case ViewSortExceptionCode.VIEW_SORT_NOT_FOUND:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
404,
|
||||
);
|
||||
case ViewSortExceptionCode.INVALID_VIEW_SORT_DATA:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
400,
|
||||
);
|
||||
default:
|
||||
// TODO: change to 500 when we have input validation
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
400,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback for any other exception type
|
||||
const unknownException = new UnknownException(
|
||||
'Internal server error',
|
||||
'INTERNAL_ERROR',
|
||||
);
|
||||
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
unknownException as CustomException,
|
||||
response,
|
||||
500,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+59
-6
@@ -14,6 +14,8 @@ import {
|
||||
import { type APP_LOCALES } from 'twenty-shared/translations';
|
||||
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 { I18nService } from 'src/engine/core-modules/i18n/i18n.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
@@ -31,6 +33,7 @@ import {
|
||||
ViewExceptionMessageKey,
|
||||
} from 'src/engine/metadata-modules/view/exceptions/view.exception';
|
||||
import { ViewRestApiExceptionFilter } from 'src/engine/metadata-modules/view/filters/view-rest-api-exception.filter';
|
||||
import { ViewV2Service } from 'src/engine/metadata-modules/view/services/view-v2.service';
|
||||
import { ViewService } from 'src/engine/metadata-modules/view/services/view.service';
|
||||
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
|
||||
|
||||
@@ -40,6 +43,8 @@ import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/works
|
||||
export class ViewController {
|
||||
constructor(
|
||||
private readonly viewService: ViewService,
|
||||
private readonly viewV2Service: ViewV2Service,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
|
||||
private readonly i18nService: I18nService,
|
||||
) {}
|
||||
@@ -98,10 +103,25 @@ export class ViewController {
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@RequestLocale() locale?: keyof typeof APP_LOCALES,
|
||||
): Promise<ViewDTO> {
|
||||
const view = await this.viewService.create({
|
||||
...input,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
const isWorkspaceMigrationV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
let view: ViewDTO;
|
||||
|
||||
if (isWorkspaceMigrationV2Enabled) {
|
||||
view = await this.viewV2Service.createOne({
|
||||
createViewInput: input,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
} else {
|
||||
view = await this.viewService.create({
|
||||
...input,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
|
||||
const processedViews = await this.processViewsWithTemplates(
|
||||
[view],
|
||||
@@ -119,7 +139,25 @@ export class ViewController {
|
||||
@RequestLocale() locale: keyof typeof APP_LOCALES | undefined,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<ViewDTO> {
|
||||
const updatedView = await this.viewService.update(id, workspace.id, input);
|
||||
const isWorkspaceMigrationV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
let updatedView: ViewDTO;
|
||||
|
||||
if (isWorkspaceMigrationV2Enabled) {
|
||||
updatedView = await this.viewV2Service.updateOne({
|
||||
updateViewInput: {
|
||||
...input,
|
||||
id,
|
||||
},
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
} else {
|
||||
updatedView = await this.viewService.update(id, workspace.id, input);
|
||||
}
|
||||
|
||||
const processedViews = await this.processViewsWithTemplates(
|
||||
[updatedView],
|
||||
@@ -135,7 +173,22 @@ export class ViewController {
|
||||
@Param('id') id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<{ success: boolean }> {
|
||||
const deletedView = await this.viewService.delete(id, workspace.id);
|
||||
const isWorkspaceMigrationV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
let deletedView: ViewDTO | null;
|
||||
|
||||
if (isWorkspaceMigrationV2Enabled) {
|
||||
deletedView = await this.viewV2Service.deleteOne({
|
||||
deleteViewInput: { id },
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
} else {
|
||||
deletedView = await this.viewService.delete(id, workspace.id);
|
||||
}
|
||||
|
||||
return { success: isDefined(deletedView) };
|
||||
}
|
||||
|
||||
+66
-22
@@ -2,47 +2,91 @@ import {
|
||||
type ArgumentsHost,
|
||||
Catch,
|
||||
type ExceptionFilter,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { type Response } from 'express';
|
||||
import { SOURCE_LOCALE } from 'twenty-shared/translations';
|
||||
|
||||
import { HttpExceptionHandlerService } from 'src/engine/core-modules/exception-handler/http-exception-handler.service';
|
||||
import { I18nService } from 'src/engine/core-modules/i18n/i18n.service';
|
||||
import {
|
||||
ViewException,
|
||||
ViewExceptionCode,
|
||||
} from 'src/engine/metadata-modules/view/exceptions/view.exception';
|
||||
import { type CustomException } from 'src/utils/custom-exception';
|
||||
import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2';
|
||||
import { fromWorkspaceMigrationBuilderExceptionToMetadataValidationResponseError } from 'src/engine/workspace-manager/workspace-migration-v2/interceptors/utils/from-workspace-migration-builder-exception-to-metadata-validation-response-error.util';
|
||||
import {
|
||||
type CustomException,
|
||||
UnknownException,
|
||||
} from 'src/utils/custom-exception';
|
||||
|
||||
@Catch(ViewException)
|
||||
@Injectable()
|
||||
@Catch(ViewException, WorkspaceMigrationBuilderExceptionV2)
|
||||
export class ViewRestApiExceptionFilter implements ExceptionFilter {
|
||||
constructor(
|
||||
private readonly httpExceptionHandlerService: HttpExceptionHandlerService,
|
||||
private readonly i18nService: I18nService,
|
||||
) {}
|
||||
|
||||
catch(exception: ViewException, host: ArgumentsHost) {
|
||||
catch(
|
||||
exception: ViewException | WorkspaceMigrationBuilderExceptionV2,
|
||||
host: ArgumentsHost,
|
||||
) {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
|
||||
switch (exception.code) {
|
||||
case ViewExceptionCode.VIEW_NOT_FOUND:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
404,
|
||||
);
|
||||
case ViewExceptionCode.INVALID_VIEW_DATA:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
400,
|
||||
);
|
||||
default:
|
||||
// TODO: change to 500 when we have input validation
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
400,
|
||||
if (exception instanceof WorkspaceMigrationBuilderExceptionV2) {
|
||||
const i18n = this.i18nService.getI18nInstance(SOURCE_LOCALE);
|
||||
const { errors, summary } =
|
||||
fromWorkspaceMigrationBuilderExceptionToMetadataValidationResponseError(
|
||||
exception,
|
||||
i18n,
|
||||
);
|
||||
|
||||
return response.status(400).json({
|
||||
statusCode: 400,
|
||||
error: 'METADATA_VALIDATION_ERROR',
|
||||
message: exception.message || 'Validation failed',
|
||||
errors,
|
||||
summary,
|
||||
});
|
||||
}
|
||||
|
||||
if (exception instanceof ViewException) {
|
||||
switch (exception.code) {
|
||||
case ViewExceptionCode.VIEW_NOT_FOUND:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
404,
|
||||
);
|
||||
case ViewExceptionCode.INVALID_VIEW_DATA:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
400,
|
||||
);
|
||||
default:
|
||||
// TODO: change to 500 when we have input validation
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
400,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback for any other exception type
|
||||
const unknownException = new UnknownException(
|
||||
'Internal server error',
|
||||
'INTERNAL_ERROR',
|
||||
);
|
||||
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
unknownException as CustomException,
|
||||
response,
|
||||
500,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
export const TEST_VIEW_1_ID = '20202020-05e2-439a-a768-d10e2945759e';
|
||||
export const TEST_VIEW_2_ID = '20202020-d172-433b-ba1b-b03ad2560f09';
|
||||
export const TEST_VIEW_3_ID = '20202020-2bbd-4e25-b141-24f11da50e0a';
|
||||
|
||||
export const TEST_OBJECT_METADATA_1_ID = '20202020-1436-4a52-b386-74a0142fb1d1';
|
||||
export const TEST_OBJECT_METADATA_2_ID = '20202020-887f-4bc0-9011-2a81a3e852c3';
|
||||
|
||||
export const TEST_FIELD_METADATA_1_ID = '20202020-ac79-494f-a0a5-e456c72dcc6f';
|
||||
export const TEST_FIELD_METADATA_2_ID = '20202020-be78-4d14-a413-ea4d8562e6f0';
|
||||
|
||||
export const TEST_NOT_EXISTING_VIEW_ID = '20202020-ce26-4249-93a0-b71d405d3775';
|
||||
export const TEST_NOT_EXISTING_VIEW_FIELD_ID =
|
||||
'20202020-6be8-40ec-82fa-5c9ae1930915';
|
||||
export const TEST_NOT_EXISTING_VIEW_FILTER_GROUP_ID =
|
||||
'20202020-e214-44fa-a39a-d81447b2c44f';
|
||||
export const TEST_NOT_EXISTING_VIEW_FILTER_ID =
|
||||
'20202020-034a-433d-b6e6-5ee7aaf5aaa6';
|
||||
export const TEST_NOT_EXISTING_VIEW_SORT_ID =
|
||||
'20202020-e0dd-49d6-94f9-a23416fc5a50';
|
||||
export const TEST_NOT_EXISTING_VIEW_GROUP_ID =
|
||||
'20202020-d1df-43be-9cf7-187dbffa08d7';
|
||||
+1
-9
@@ -2,10 +2,7 @@ import { createOneObjectMetadata } from 'test/integration/metadata/suites/object
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import { createOneCoreView } from 'test/integration/metadata/suites/view/utils/create-one-core-view.util';
|
||||
import {
|
||||
assertViewStructure,
|
||||
cleanupViewRecords,
|
||||
} from 'test/integration/utils/view-test.util';
|
||||
import { assertViewStructure } from 'test/integration/utils/view-test.util';
|
||||
|
||||
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';
|
||||
@@ -46,11 +43,6 @@ describe('Create core view', () => {
|
||||
expectToFail: false,
|
||||
input: { idToDelete: testObjectMetadataId },
|
||||
});
|
||||
await cleanupViewRecords();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await cleanupViewRecords();
|
||||
});
|
||||
|
||||
it('should create a new view with all properties', async () => {
|
||||
|
||||
+2
-7
@@ -1,4 +1,3 @@
|
||||
import { TEST_NOT_EXISTING_VIEW_ID } from 'test/integration/constants/test-view-ids.constants';
|
||||
import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
@@ -6,7 +5,8 @@ import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object
|
||||
import { createOneCoreView } from 'test/integration/metadata/suites/view/utils/create-one-core-view.util';
|
||||
import { deleteOneCoreView } from 'test/integration/metadata/suites/view/utils/delete-one-core-view.util';
|
||||
import { findOneCoreView } from 'test/integration/metadata/suites/view/utils/find-one-core-view.util';
|
||||
import { cleanupViewRecords } from 'test/integration/utils/view-test.util';
|
||||
|
||||
const TEST_NOT_EXISTING_VIEW_ID = '20202020-0000-4000-8000-000000000000';
|
||||
|
||||
describe('Delete core view', () => {
|
||||
let testObjectMetadataId: string;
|
||||
@@ -44,11 +44,6 @@ describe('Delete core view', () => {
|
||||
expectToFail: false,
|
||||
input: { idToDelete: testObjectMetadataId },
|
||||
});
|
||||
await cleanupViewRecords();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await cleanupViewRecords();
|
||||
});
|
||||
|
||||
it('should delete an existing view', async () => {
|
||||
|
||||
+2
-7
@@ -1,4 +1,3 @@
|
||||
import { TEST_NOT_EXISTING_VIEW_ID } from 'test/integration/constants/test-view-ids.constants';
|
||||
import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
@@ -7,7 +6,8 @@ import { createOneCoreView } from 'test/integration/metadata/suites/view/utils/c
|
||||
import { deleteOneCoreView } from 'test/integration/metadata/suites/view/utils/delete-one-core-view.util';
|
||||
import { destroyOneCoreView } from 'test/integration/metadata/suites/view/utils/destroy-one-core-view.util';
|
||||
import { findOneCoreView } from 'test/integration/metadata/suites/view/utils/find-one-core-view.util';
|
||||
import { cleanupViewRecords } from 'test/integration/utils/view-test.util';
|
||||
|
||||
const TEST_NOT_EXISTING_VIEW_ID = '20202020-0000-4000-8000-000000000000';
|
||||
|
||||
describe('Destroy core view', () => {
|
||||
let testObjectMetadataId: string;
|
||||
@@ -45,11 +45,6 @@ describe('Destroy core view', () => {
|
||||
expectToFail: false,
|
||||
input: { idToDelete: testObjectMetadataId },
|
||||
});
|
||||
await cleanupViewRecords();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await cleanupViewRecords();
|
||||
});
|
||||
|
||||
it.only('should destroy an existing view', async () => {
|
||||
|
||||
+1
-2
@@ -13,8 +13,7 @@ import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util
|
||||
|
||||
import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum';
|
||||
|
||||
// TODO prastoin deprecate cleanupViewRecords that breaks this test suite
|
||||
describe.skip('successful find view with all sub-relations (e2e)', () => {
|
||||
describe('successful find view with all sub-relations (e2e)', () => {
|
||||
let companyObjectMetadataId: string;
|
||||
|
||||
const COMPREHENSIVE_VIEW_GQL_FIELDS = `
|
||||
|
||||
+2
-7
@@ -1,14 +1,14 @@
|
||||
import { TEST_NOT_EXISTING_VIEW_ID } from 'test/integration/constants/test-view-ids.constants';
|
||||
import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import { createOneCoreView } from 'test/integration/metadata/suites/view/utils/create-one-core-view.util';
|
||||
import { updateOneCoreView } from 'test/integration/metadata/suites/view/utils/update-one-core-view.util';
|
||||
import { cleanupViewRecords } from 'test/integration/utils/view-test.util';
|
||||
|
||||
import { ViewType } from 'src/engine/metadata-modules/view/enums/view-type.enum';
|
||||
|
||||
const TEST_NOT_EXISTING_VIEW_ID = '20202020-0000-4000-8000-000000000000';
|
||||
|
||||
describe('Update core view', () => {
|
||||
let testObjectMetadataId: string;
|
||||
|
||||
@@ -45,11 +45,6 @@ describe('Update core view', () => {
|
||||
expectToFail: false,
|
||||
input: { idToDelete: testObjectMetadataId },
|
||||
});
|
||||
await cleanupViewRecords();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await cleanupViewRecords();
|
||||
});
|
||||
|
||||
it('should update an existing view', async () => {
|
||||
|
||||
+2
-1
@@ -1,4 +1,3 @@
|
||||
import { TEST_NOT_EXISTING_VIEW_FIELD_ID } from 'test/integration/constants/test-view-ids.constants';
|
||||
import {
|
||||
cleanupViewFieldTestV2,
|
||||
setupViewFieldTestV2,
|
||||
@@ -13,6 +12,8 @@ import {
|
||||
|
||||
import { type DeleteViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/delete-view-field.input';
|
||||
|
||||
const TEST_NOT_EXISTING_VIEW_FIELD_ID = '20202020-0000-4000-8000-000000000001';
|
||||
|
||||
describe('View Field Resolver - Failing Delete Operation - v2', () => {
|
||||
let testSetup: ViewFieldTestSetup;
|
||||
|
||||
|
||||
+2
-1
@@ -1,4 +1,3 @@
|
||||
import { TEST_NOT_EXISTING_VIEW_FIELD_ID } from 'test/integration/constants/test-view-ids.constants';
|
||||
import {
|
||||
cleanupViewFieldTestV2,
|
||||
setupViewFieldTestV2,
|
||||
@@ -13,6 +12,8 @@ import {
|
||||
|
||||
import { type DestroyViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/destroy-view-field.input';
|
||||
|
||||
const TEST_NOT_EXISTING_VIEW_FIELD_ID = '20202020-0000-4000-8000-000000000001';
|
||||
|
||||
describe('View Field Resolver - Failing Destroy Operation - v2', () => {
|
||||
let testSetup: ViewFieldTestSetup;
|
||||
|
||||
|
||||
+2
-1
@@ -1,4 +1,3 @@
|
||||
import { TEST_NOT_EXISTING_VIEW_FIELD_ID } from 'test/integration/constants/test-view-ids.constants';
|
||||
import {
|
||||
cleanupViewFieldTestV2,
|
||||
setupViewFieldTestV2,
|
||||
@@ -13,6 +12,8 @@ import {
|
||||
|
||||
import { type UpdateViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/update-view-field.input';
|
||||
|
||||
const TEST_NOT_EXISTING_VIEW_FIELD_ID = '20202020-0000-4000-8000-000000000001';
|
||||
|
||||
describe('View Field Resolver - Failing Update Operation - v2', () => {
|
||||
let testSetup: ViewFieldTestSetup;
|
||||
|
||||
|
||||
+11
-8
@@ -1,4 +1,3 @@
|
||||
import { TEST_NOT_EXISTING_VIEW_FIELD_ID } from 'test/integration/constants/test-view-ids.constants';
|
||||
import { findViewFieldsOperationFactory } from 'test/integration/graphql/utils/find-view-fields-operation-factory.util';
|
||||
import {
|
||||
assertGraphQLErrorResponse,
|
||||
@@ -19,10 +18,8 @@ import { createOneCoreViewField } from 'test/integration/metadata/suites/view-fi
|
||||
import { deleteOneCoreViewField } from 'test/integration/metadata/suites/view-field/utils/delete-one-core-view-field.util';
|
||||
import { destroyOneCoreViewField } from 'test/integration/metadata/suites/view-field/utils/destroy-one-core-view-field.util';
|
||||
import { updateOneCoreViewField } from 'test/integration/metadata/suites/view-field/utils/update-one-core-view-field.util';
|
||||
import {
|
||||
assertViewFieldStructure,
|
||||
cleanupViewRecords,
|
||||
} from 'test/integration/utils/view-test.util';
|
||||
import { destroyOneCoreView } from 'test/integration/metadata/suites/view/utils/destroy-one-core-view.util';
|
||||
import { assertViewFieldStructure } from 'test/integration/utils/view-test.util';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
@@ -33,6 +30,8 @@ import {
|
||||
ViewFieldExceptionMessageKey,
|
||||
} from 'src/engine/metadata-modules/view-field/exceptions/view-field.exception';
|
||||
|
||||
const TEST_NOT_EXISTING_VIEW_FIELD_ID = '20202020-0000-4000-8000-000000000001';
|
||||
|
||||
describe('View Field Resolver', () => {
|
||||
let testViewId: string;
|
||||
let testObjectMetadataId: string;
|
||||
@@ -104,7 +103,6 @@ describe('View Field Resolver', () => {
|
||||
expectToFail: false,
|
||||
input: { idToDelete: testObjectMetadataId },
|
||||
});
|
||||
await cleanupViewRecords();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
@@ -116,8 +114,6 @@ describe('View Field Resolver', () => {
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await cleanupViewRecords();
|
||||
|
||||
const view = await createTestViewWithGraphQL({
|
||||
name: 'Test View for Fields',
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
@@ -126,6 +122,13 @@ describe('View Field Resolver', () => {
|
||||
testViewId = view.id;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await destroyOneCoreView({
|
||||
viewId: testViewId,
|
||||
expectToFail: false,
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCoreViewFields', () => {
|
||||
it('should return empty array when no view fields exist', async () => {
|
||||
const operation = findViewFieldsOperationFactory({ viewId: testViewId });
|
||||
|
||||
+93
-23
@@ -1,4 +1,3 @@
|
||||
import { TEST_NOT_EXISTING_VIEW_FILTER_GROUP_ID } from 'test/integration/constants/test-view-ids.constants';
|
||||
import { createViewFilterGroupOperationFactory } from 'test/integration/graphql/utils/create-view-filter-group-operation-factory.util';
|
||||
import { deleteViewFilterGroupOperationFactory } from 'test/integration/graphql/utils/delete-view-filter-group-operation-factory.util';
|
||||
import { destroyViewFilterGroupOperationFactory } from 'test/integration/graphql/utils/destroy-view-filter-group-operation-factory.util';
|
||||
@@ -18,23 +17,28 @@ import { createTestViewWithGraphQL } from 'test/integration/graphql/utils/view-g
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import {
|
||||
assertViewFilterGroupStructure,
|
||||
cleanupViewRecords,
|
||||
} from 'test/integration/utils/view-test.util';
|
||||
import { destroyOneCoreView } from 'test/integration/metadata/suites/view/utils/destroy-one-core-view.util';
|
||||
import { assertViewFilterGroupStructure } from 'test/integration/utils/view-test.util';
|
||||
import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util.test';
|
||||
|
||||
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||
import { type ViewFilterGroupDTO } from 'src/engine/metadata-modules/view-filter-group/dtos/view-filter-group.dto';
|
||||
import { ViewFilterGroupLogicalOperator } from 'src/engine/metadata-modules/view-filter-group/enums/view-filter-group-logical-operator';
|
||||
import {
|
||||
generateViewFilterGroupExceptionMessage,
|
||||
ViewFilterGroupExceptionMessageKey,
|
||||
} from 'src/engine/metadata-modules/view-filter-group/exceptions/view-filter-group.exception';
|
||||
|
||||
const TEST_NOT_EXISTING_VIEW_FILTER_GROUP_ID =
|
||||
'20202020-0000-4000-8000-000000000002';
|
||||
|
||||
describe('View Filter Group Resolver', () => {
|
||||
let testViewId: string;
|
||||
|
||||
let testObjectMetadataId: string;
|
||||
|
||||
let createdViewFilterGroup: string[] = [];
|
||||
|
||||
beforeAll(async () => {
|
||||
const {
|
||||
data: {
|
||||
@@ -68,12 +72,9 @@ describe('View Filter Group Resolver', () => {
|
||||
expectToFail: false,
|
||||
input: { idToDelete: testObjectMetadataId },
|
||||
});
|
||||
await cleanupViewRecords();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await cleanupViewRecords();
|
||||
|
||||
const view = await createTestViewWithGraphQL({
|
||||
name: 'Test View for Filter Groups',
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
@@ -82,6 +83,23 @@ describe('View Filter Group Resolver', () => {
|
||||
testViewId = view.id;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// Clean up all created view filter groups
|
||||
for (const filterGroupId of createdViewFilterGroup) {
|
||||
const destroyOperation = destroyViewFilterGroupOperationFactory({
|
||||
viewFilterGroupId: filterGroupId,
|
||||
});
|
||||
|
||||
await makeGraphqlAPIRequest(destroyOperation);
|
||||
}
|
||||
createdViewFilterGroup = [];
|
||||
|
||||
await destroyOneCoreView({
|
||||
viewId: testViewId,
|
||||
expectToFail: false,
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCoreViewFilterGroups', () => {
|
||||
it('should return empty array when no view filter groups exist', async () => {
|
||||
const operation = findViewFilterGroupsOperationFactory({
|
||||
@@ -101,14 +119,23 @@ describe('View Filter Group Resolver', () => {
|
||||
data: filterGroupData,
|
||||
});
|
||||
|
||||
await makeGraphqlAPIRequest(createOperation);
|
||||
const createResponse = await makeGraphqlAPIRequest(createOperation);
|
||||
const createdFilterGroupId =
|
||||
createResponse.body.data.createCoreViewFilterGroup.id;
|
||||
|
||||
createdViewFilterGroup.push(createdFilterGroupId);
|
||||
|
||||
const getOperation = findViewFilterGroupsOperationFactory();
|
||||
const response = await makeGraphqlAPIRequest(getOperation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(response);
|
||||
expect(response.body.data.getCoreViewFilterGroups).toHaveLength(1);
|
||||
expect(response.body.data.getCoreViewFilterGroups[0]).toMatchObject({
|
||||
|
||||
const foundFilterGroup = response.body.data.getCoreViewFilterGroups.find(
|
||||
(el: ViewFilterGroupDTO) => el.id === createdFilterGroupId,
|
||||
);
|
||||
|
||||
jestExpectToBeDefined(foundFilterGroup);
|
||||
expect(foundFilterGroup).toMatchObject({
|
||||
logicalOperator: ViewFilterGroupLogicalOperator.AND,
|
||||
viewId: testViewId,
|
||||
});
|
||||
@@ -122,7 +149,11 @@ describe('View Filter Group Resolver', () => {
|
||||
data: filterGroupData,
|
||||
});
|
||||
|
||||
await makeGraphqlAPIRequest(createOperation);
|
||||
const createResponse = await makeGraphqlAPIRequest(createOperation);
|
||||
const createdFilterGroupId =
|
||||
createResponse.body.data.createCoreViewFilterGroup.id;
|
||||
|
||||
createdViewFilterGroup.push(createdFilterGroupId);
|
||||
|
||||
const getOperation = findViewFilterGroupsOperationFactory({
|
||||
viewId: testViewId,
|
||||
@@ -130,14 +161,16 @@ describe('View Filter Group Resolver', () => {
|
||||
const response = await makeGraphqlAPIRequest(getOperation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(response);
|
||||
expect(response.body.data.getCoreViewFilterGroups).toHaveLength(1);
|
||||
assertViewFilterGroupStructure(
|
||||
response.body.data.getCoreViewFilterGroups[0],
|
||||
{
|
||||
logicalOperator: ViewFilterGroupLogicalOperator.OR,
|
||||
viewId: testViewId,
|
||||
},
|
||||
|
||||
const foundFilterGroup = response.body.data.getCoreViewFilterGroups.find(
|
||||
(group: ViewFilterGroupDTO) => group.id === createdFilterGroupId,
|
||||
);
|
||||
|
||||
jestExpectToBeDefined(foundFilterGroup);
|
||||
assertViewFilterGroupStructure(foundFilterGroup, {
|
||||
logicalOperator: ViewFilterGroupLogicalOperator.OR,
|
||||
viewId: testViewId,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return nested filter groups with parent relationships', async () => {
|
||||
@@ -150,6 +183,8 @@ describe('View Filter Group Resolver', () => {
|
||||
const parentResponse = await makeGraphqlAPIRequest(parentOperation);
|
||||
const parentId = parentResponse.body.data.createCoreViewFilterGroup.id;
|
||||
|
||||
createdViewFilterGroup.push(parentId);
|
||||
|
||||
const childData = createViewFilterGroupData(testViewId, {
|
||||
parentViewFilterGroupId: parentId,
|
||||
logicalOperator: ViewFilterGroupLogicalOperator.OR,
|
||||
@@ -158,7 +193,10 @@ describe('View Filter Group Resolver', () => {
|
||||
data: childData,
|
||||
});
|
||||
|
||||
await makeGraphqlAPIRequest(childOperation);
|
||||
const childResponse = await makeGraphqlAPIRequest(childOperation);
|
||||
const childId = childResponse.body.data.createCoreViewFilterGroup.id;
|
||||
|
||||
createdViewFilterGroup.push(childId);
|
||||
|
||||
const getOperation = findViewFilterGroupsOperationFactory({
|
||||
viewId: testViewId,
|
||||
@@ -166,15 +204,17 @@ describe('View Filter Group Resolver', () => {
|
||||
const response = await makeGraphqlAPIRequest(getOperation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(response);
|
||||
expect(response.body.data.getCoreViewFilterGroups).toHaveLength(2);
|
||||
|
||||
const parentGroup = response.body.data.getCoreViewFilterGroups.find(
|
||||
(group: any) => group.parentViewFilterGroupId === null,
|
||||
(group: ViewFilterGroupDTO) => group.id === parentId,
|
||||
);
|
||||
const childGroup = response.body.data.getCoreViewFilterGroups.find(
|
||||
(group: any) => group.parentViewFilterGroupId !== null,
|
||||
(group: ViewFilterGroupDTO) => group.id === childId,
|
||||
);
|
||||
|
||||
jestExpectToBeDefined(parentGroup);
|
||||
jestExpectToBeDefined(childGroup);
|
||||
|
||||
assertViewFilterGroupStructure(parentGroup, {
|
||||
logicalOperator: ViewFilterGroupLogicalOperator.AND,
|
||||
parentViewFilterGroupId: null,
|
||||
@@ -209,6 +249,8 @@ describe('View Filter Group Resolver', () => {
|
||||
const filterGroupId =
|
||||
createResponse.body.data.createCoreViewFilterGroup.id;
|
||||
|
||||
createdViewFilterGroup.push(filterGroupId);
|
||||
|
||||
const getOperation = findViewFilterGroupOperationFactory({
|
||||
viewFilterGroupId: filterGroupId,
|
||||
});
|
||||
@@ -237,6 +279,10 @@ describe('View Filter Group Resolver', () => {
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(response);
|
||||
|
||||
createdViewFilterGroup.push(
|
||||
response.body.data.createCoreViewFilterGroup.id,
|
||||
);
|
||||
assertViewFilterGroupStructure(
|
||||
response.body.data.createCoreViewFilterGroup,
|
||||
{
|
||||
@@ -256,6 +302,10 @@ describe('View Filter Group Resolver', () => {
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(response);
|
||||
|
||||
createdViewFilterGroup.push(
|
||||
response.body.data.createCoreViewFilterGroup.id,
|
||||
);
|
||||
assertViewFilterGroupStructure(
|
||||
response.body.data.createCoreViewFilterGroup,
|
||||
{
|
||||
@@ -274,6 +324,10 @@ describe('View Filter Group Resolver', () => {
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(response);
|
||||
|
||||
createdViewFilterGroup.push(
|
||||
response.body.data.createCoreViewFilterGroup.id,
|
||||
);
|
||||
assertViewFilterGroupStructure(
|
||||
response.body.data.createCoreViewFilterGroup,
|
||||
{
|
||||
@@ -292,6 +346,8 @@ describe('View Filter Group Resolver', () => {
|
||||
const parentResponse = await makeGraphqlAPIRequest(parentOperation);
|
||||
const parentId = parentResponse.body.data.createCoreViewFilterGroup.id;
|
||||
|
||||
createdViewFilterGroup.push(parentId);
|
||||
|
||||
const childData = createViewFilterGroupData(testViewId, {
|
||||
parentViewFilterGroupId: parentId,
|
||||
logicalOperator: ViewFilterGroupLogicalOperator.OR,
|
||||
@@ -302,6 +358,10 @@ describe('View Filter Group Resolver', () => {
|
||||
const childResponse = await makeGraphqlAPIRequest(childOperation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(childResponse);
|
||||
|
||||
createdViewFilterGroup.push(
|
||||
childResponse.body.data.createCoreViewFilterGroup.id,
|
||||
);
|
||||
assertViewFilterGroupStructure(
|
||||
childResponse.body.data.createCoreViewFilterGroup,
|
||||
{
|
||||
@@ -324,6 +384,8 @@ describe('View Filter Group Resolver', () => {
|
||||
const filterGroupId =
|
||||
createResponse.body.data.createCoreViewFilterGroup.id;
|
||||
|
||||
createdViewFilterGroup.push(filterGroupId);
|
||||
|
||||
const updateInput = updateViewFilterGroupData({
|
||||
logicalOperator: ViewFilterGroupLogicalOperator.OR,
|
||||
});
|
||||
@@ -350,6 +412,8 @@ describe('View Filter Group Resolver', () => {
|
||||
const parentResponse = await makeGraphqlAPIRequest(parentOperation);
|
||||
const parentId = parentResponse.body.data.createCoreViewFilterGroup.id;
|
||||
|
||||
createdViewFilterGroup.push(parentId);
|
||||
|
||||
const childData = createViewFilterGroupData(testViewId, {
|
||||
logicalOperator: ViewFilterGroupLogicalOperator.OR,
|
||||
});
|
||||
@@ -359,6 +423,8 @@ describe('View Filter Group Resolver', () => {
|
||||
const childResponse = await makeGraphqlAPIRequest(childOperation);
|
||||
const childId = childResponse.body.data.createCoreViewFilterGroup.id;
|
||||
|
||||
createdViewFilterGroup.push(childId);
|
||||
|
||||
const updateInput = updateViewFilterGroupData({
|
||||
parentViewFilterGroupId: parentId,
|
||||
});
|
||||
@@ -408,6 +474,8 @@ describe('View Filter Group Resolver', () => {
|
||||
const filterGroupId =
|
||||
createResponse.body.data.createCoreViewFilterGroup.id;
|
||||
|
||||
createdViewFilterGroup.push(filterGroupId);
|
||||
|
||||
const deleteOperation = deleteViewFilterGroupOperationFactory({
|
||||
viewFilterGroupId: filterGroupId,
|
||||
});
|
||||
@@ -453,6 +521,8 @@ describe('View Filter Group Resolver', () => {
|
||||
const filterGroupId =
|
||||
createResponse.body.data.createCoreViewFilterGroup.id;
|
||||
|
||||
createdViewFilterGroup.push(filterGroupId);
|
||||
|
||||
const destroyOperation = destroyViewFilterGroupOperationFactory({
|
||||
viewFilterGroupId: filterGroupId,
|
||||
});
|
||||
|
||||
+8
-4
@@ -9,7 +9,7 @@ import { deleteOneCoreViewFilter } from 'test/integration/metadata/suites/view-f
|
||||
import { destroyOneCoreViewFilter } from 'test/integration/metadata/suites/view-filter/utils/destroy-one-core-view-filter.util';
|
||||
import { findCoreViewFilters } from 'test/integration/metadata/suites/view-filter/utils/find-core-view-filters.util';
|
||||
import { updateOneCoreViewFilter } from 'test/integration/metadata/suites/view-filter/utils/update-one-core-view-filter.util';
|
||||
import { cleanupViewRecords } from 'test/integration/utils/view-test.util';
|
||||
import { destroyOneCoreView } from 'test/integration/metadata/suites/view/utils/destroy-one-core-view.util';
|
||||
import { FieldMetadataType, ViewFilterOperand } from 'twenty-shared/types';
|
||||
|
||||
const TEST_NOT_EXISTING_VIEW_FILTER_ID = '20202020-52c5-4152-8c09-76a845fb8ece';
|
||||
@@ -69,12 +69,9 @@ describe('View Filter Resolver', () => {
|
||||
expectToFail: false,
|
||||
input: { idToDelete: testObjectMetadataId },
|
||||
});
|
||||
await cleanupViewRecords();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await cleanupViewRecords();
|
||||
|
||||
const view = await createTestViewWithGraphQL({
|
||||
name: 'Test View for Filters',
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
@@ -83,6 +80,13 @@ describe('View Filter Resolver', () => {
|
||||
testViewId = view.id;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await destroyOneCoreView({
|
||||
viewId: testViewId,
|
||||
expectToFail: false,
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCoreViewFilters', () => {
|
||||
it('should return empty array when no view filters exist', async () => {
|
||||
const { data, errors } = await findCoreViewFilters({
|
||||
|
||||
+11
-8
@@ -1,4 +1,3 @@
|
||||
import { TEST_NOT_EXISTING_VIEW_GROUP_ID } from 'test/integration/constants/test-view-ids.constants';
|
||||
import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util';
|
||||
import { createTestViewWithGraphQL } from 'test/integration/graphql/utils/view-graphql.util';
|
||||
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||
@@ -10,12 +9,12 @@ import { deleteOneCoreViewGroup } from 'test/integration/metadata/suites/view-gr
|
||||
import { destroyOneCoreViewGroup } from 'test/integration/metadata/suites/view-group/utils/destroy-one-core-view-group.util';
|
||||
import { findCoreViewGroups } from 'test/integration/metadata/suites/view-group/utils/find-core-view-groups.util';
|
||||
import { updateOneCoreViewGroup } from 'test/integration/metadata/suites/view-group/utils/update-one-core-view-group.util';
|
||||
import {
|
||||
assertViewGroupStructure,
|
||||
cleanupViewRecords,
|
||||
} from 'test/integration/utils/view-test.util';
|
||||
import { destroyOneCoreView } from 'test/integration/metadata/suites/view/utils/destroy-one-core-view.util';
|
||||
import { assertViewGroupStructure } from 'test/integration/utils/view-test.util';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
const TEST_NOT_EXISTING_VIEW_GROUP_ID = '20202020-0000-4000-8000-000000000003';
|
||||
|
||||
describe('View Group Resolver', () => {
|
||||
let testViewId: string;
|
||||
let testObjectMetadataId: string;
|
||||
@@ -71,12 +70,9 @@ describe('View Group Resolver', () => {
|
||||
expectToFail: false,
|
||||
input: { idToDelete: testObjectMetadataId },
|
||||
});
|
||||
await cleanupViewRecords();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await cleanupViewRecords();
|
||||
|
||||
const view = await createTestViewWithGraphQL({
|
||||
name: 'Test View for Groups',
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
@@ -85,6 +81,13 @@ describe('View Group Resolver', () => {
|
||||
testViewId = view.id;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await destroyOneCoreView({
|
||||
viewId: testViewId,
|
||||
expectToFail: false,
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCoreViewGroups', () => {
|
||||
it('should return empty array when no view groups exist', async () => {
|
||||
const { data } = await findCoreViewGroups({
|
||||
|
||||
+11
-8
@@ -1,4 +1,3 @@
|
||||
import { TEST_NOT_EXISTING_VIEW_SORT_ID } from 'test/integration/constants/test-view-ids.constants';
|
||||
import { createViewSortOperationFactory } from 'test/integration/graphql/utils/create-view-sort-operation-factory.util';
|
||||
import { deleteViewSortOperationFactory } from 'test/integration/graphql/utils/delete-view-sort-operation-factory.util';
|
||||
import { destroyViewSortOperationFactory } from 'test/integration/graphql/utils/destroy-view-sort-operation-factory.util';
|
||||
@@ -18,10 +17,8 @@ import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-m
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import {
|
||||
assertViewSortStructure,
|
||||
cleanupViewRecords,
|
||||
} from 'test/integration/utils/view-test.util';
|
||||
import { destroyOneCoreView } from 'test/integration/metadata/suites/view/utils/destroy-one-core-view.util';
|
||||
import { assertViewSortStructure } from 'test/integration/utils/view-test.util';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||
@@ -31,6 +28,8 @@ import {
|
||||
ViewSortExceptionMessageKey,
|
||||
} from 'src/engine/metadata-modules/view-sort/exceptions/view-sort.exception';
|
||||
|
||||
const TEST_NOT_EXISTING_VIEW_SORT_ID = '20202020-0000-4000-8000-000000000004';
|
||||
|
||||
describe('View Sort Resolver', () => {
|
||||
let testViewId: string;
|
||||
let testObjectMetadataId: string;
|
||||
@@ -86,12 +85,9 @@ describe('View Sort Resolver', () => {
|
||||
expectToFail: false,
|
||||
input: { idToDelete: testObjectMetadataId },
|
||||
});
|
||||
await cleanupViewRecords();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await cleanupViewRecords();
|
||||
|
||||
const view = await createTestViewWithGraphQL({
|
||||
name: 'Test View for Sorts',
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
@@ -100,6 +96,13 @@ describe('View Sort Resolver', () => {
|
||||
testViewId = view.id;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await destroyOneCoreView({
|
||||
viewId: testViewId,
|
||||
expectToFail: false,
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCoreViewSorts', () => {
|
||||
it('should return empty array when no view sorts exist', async () => {
|
||||
const operation = findViewSortsOperationFactory({ viewId: testViewId });
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`View Group REST API POST /metadata/viewGroups should fail to create view group with missing required fields 1`] = `
|
||||
{
|
||||
"error": "METADATA_VALIDATION_ERROR",
|
||||
"errors": {
|
||||
"cronTrigger": [],
|
||||
"databaseEventTrigger": [],
|
||||
"fieldMetadata": [],
|
||||
"index": [],
|
||||
"objectMetadata": [],
|
||||
"routeTrigger": [],
|
||||
"serverlessFunction": [],
|
||||
"view": [],
|
||||
"viewField": [],
|
||||
"viewFilter": [],
|
||||
"viewGroup": [
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"code": "INVALID_VIEW_DATA",
|
||||
"message": "Field metadata not found",
|
||||
"userFriendlyMessage": "Field metadata not found",
|
||||
},
|
||||
],
|
||||
"flatEntityMinimalInformation": {
|
||||
"id": Any<String>,
|
||||
"viewId": Any<String>,
|
||||
},
|
||||
"status": "fail",
|
||||
"type": "create_view_group",
|
||||
},
|
||||
],
|
||||
},
|
||||
"message": "Multiple validation errors occurred while creating view group",
|
||||
"statusCode": 400,
|
||||
"summary": {
|
||||
"invalidCronTrigger": 0,
|
||||
"invalidDatabaseEventTrigger": 0,
|
||||
"invalidFieldMetadata": 0,
|
||||
"invalidIndex": 0,
|
||||
"invalidObjectMetadata": 0,
|
||||
"invalidRouteTrigger": 0,
|
||||
"invalidServerlessFunction": 0,
|
||||
"invalidView": 0,
|
||||
"invalidViewField": 0,
|
||||
"invalidViewFilter": 0,
|
||||
"invalidViewGroup": 0,
|
||||
"totalErrors": 0,
|
||||
},
|
||||
}
|
||||
`;
|
||||
+66
-52
@@ -1,7 +1,3 @@
|
||||
import {
|
||||
TEST_NOT_EXISTING_VIEW_FIELD_ID,
|
||||
TEST_VIEW_1_ID,
|
||||
} from 'test/integration/constants/test-view-ids.constants';
|
||||
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
@@ -15,14 +11,13 @@ import {
|
||||
import {
|
||||
createTestViewFieldWithRestApi,
|
||||
createTestViewWithRestApi,
|
||||
deleteTestViewFieldWithRestApi,
|
||||
} from 'test/integration/rest/utils/view-rest-api.util';
|
||||
import {
|
||||
assertViewFieldStructure,
|
||||
cleanupViewRecords,
|
||||
} from 'test/integration/utils/view-test.util';
|
||||
import { assertViewFieldStructure } from 'test/integration/utils/view-test.util';
|
||||
import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util.test';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { destroyOneCoreViewField } from 'test/integration/metadata/suites/view-field/utils/destroy-one-core-view-field.util';
|
||||
|
||||
import { type ViewFieldDTO } from 'src/engine/metadata-modules/view-field/dtos/view-field.dto';
|
||||
import {
|
||||
generateViewFieldExceptionMessage,
|
||||
ViewFieldExceptionMessageKey,
|
||||
@@ -31,6 +26,8 @@ import {
|
||||
describe('View Field REST API', () => {
|
||||
let testObjectMetadataId: string;
|
||||
let testFieldMetadataId: string;
|
||||
let testViewId: string;
|
||||
let testViewFieldId: string | undefined;
|
||||
|
||||
beforeAll(async () => {
|
||||
const {
|
||||
@@ -40,11 +37,11 @@ describe('View Field REST API', () => {
|
||||
} = await createOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
nameSingular: 'myTestObject',
|
||||
namePlural: 'myTestObjects',
|
||||
labelSingular: 'My Test Object',
|
||||
labelPlural: 'My Test Objects',
|
||||
icon: 'Icon123',
|
||||
nameSingular: 'testViewFieldObject',
|
||||
namePlural: 'testViewFieldObjects',
|
||||
labelSingular: 'Test View Field Object',
|
||||
labelPlural: 'Test View Field Objects',
|
||||
icon: 'IconField',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -74,6 +71,13 @@ describe('View Field REST API', () => {
|
||||
});
|
||||
|
||||
testFieldMetadataId = fieldMetadataId;
|
||||
|
||||
const testView = await createTestViewWithRestApi({
|
||||
name: 'Test View for Field Integration',
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
});
|
||||
|
||||
testViewId = testView.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
@@ -92,24 +96,23 @@ describe('View Field REST API', () => {
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await cleanupViewRecords();
|
||||
afterEach(async () => {
|
||||
if (!testViewFieldId) return;
|
||||
|
||||
await createTestViewWithRestApi({
|
||||
name: 'Test View for Fields',
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
await destroyOneCoreViewField({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
id: testViewFieldId,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanupViewRecords();
|
||||
testViewFieldId = undefined;
|
||||
});
|
||||
|
||||
describe('GET /metadata/viewFields', () => {
|
||||
it('should return empty array when no view fields exist', async () => {
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/viewFields?viewId=${TEST_VIEW_1_ID}`,
|
||||
path: `/metadata/viewFields?viewId=${testViewId}`,
|
||||
bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
@@ -130,86 +133,95 @@ describe('View Field REST API', () => {
|
||||
|
||||
it('should return view fields for a specific view after creating one', async () => {
|
||||
const viewField = await createTestViewFieldWithRestApi({
|
||||
viewId: testViewId,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
});
|
||||
|
||||
testViewFieldId = viewField.id;
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/viewFields?viewId=${TEST_VIEW_1_ID}`,
|
||||
path: `/metadata/viewFields?viewId=${testViewId}`,
|
||||
bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
assertRestApiSuccessfulResponse(response);
|
||||
expect(Array.isArray(response.body)).toBe(true);
|
||||
expect(response.body).toHaveLength(1);
|
||||
|
||||
const returnedViewField = response.body[0];
|
||||
const returnedViewField = response.body.find(
|
||||
(el: ViewFieldDTO) => el.id === viewField.id,
|
||||
);
|
||||
|
||||
jestExpectToBeDefined(returnedViewField);
|
||||
|
||||
assertViewFieldStructure(returnedViewField, {
|
||||
id: viewField.id,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
});
|
||||
|
||||
await deleteTestViewFieldWithRestApi(viewField.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /metadata/viewFields', () => {
|
||||
it('should create a new view field', async () => {
|
||||
const viewField = await createTestViewFieldWithRestApi({
|
||||
viewId: testViewId,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
position: 1,
|
||||
isVisible: true,
|
||||
size: 200,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
});
|
||||
|
||||
testViewFieldId = viewField.id;
|
||||
|
||||
assertViewFieldStructure(viewField, {
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
position: 1,
|
||||
isVisible: true,
|
||||
size: 200,
|
||||
});
|
||||
|
||||
await deleteTestViewFieldWithRestApi(viewField.id);
|
||||
});
|
||||
|
||||
it('should create a hidden view field', async () => {
|
||||
const hiddenField = await createTestViewFieldWithRestApi({
|
||||
viewId: testViewId,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
position: 2,
|
||||
isVisible: false,
|
||||
size: 100,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
});
|
||||
|
||||
testViewFieldId = hiddenField.id;
|
||||
|
||||
assertViewFieldStructure(hiddenField, {
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
position: 2,
|
||||
isVisible: false,
|
||||
size: 100,
|
||||
});
|
||||
|
||||
await deleteTestViewFieldWithRestApi(hiddenField.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /metadata/viewFields/:id', () => {
|
||||
it('should return a view field by id', async () => {
|
||||
const viewField = await createTestViewFieldWithRestApi({
|
||||
viewId: testViewId,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
});
|
||||
|
||||
testViewFieldId = viewField.id;
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/viewFields/${viewField.id}`,
|
||||
@@ -220,16 +232,14 @@ describe('View Field REST API', () => {
|
||||
assertViewFieldStructure(response.body, {
|
||||
id: viewField.id,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
});
|
||||
|
||||
await deleteTestViewFieldWithRestApi(viewField.id);
|
||||
});
|
||||
|
||||
it('should return empty object for non-existent view field', async () => {
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/viewFields/${TEST_NOT_EXISTING_VIEW_FIELD_ID}`,
|
||||
path: `/metadata/viewFields/20202020-f891-4d2a-8b23-c1e4d7f6a9b2`,
|
||||
bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
@@ -240,12 +250,15 @@ describe('View Field REST API', () => {
|
||||
describe('PATCH /metadata/viewFields/:id', () => {
|
||||
it('should update an existing view field', async () => {
|
||||
const viewField = await createTestViewFieldWithRestApi({
|
||||
viewId: testViewId,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
});
|
||||
|
||||
testViewFieldId = viewField.id;
|
||||
|
||||
const updateData = {
|
||||
position: 5,
|
||||
isVisible: false,
|
||||
@@ -266,10 +279,8 @@ describe('View Field REST API', () => {
|
||||
isVisible: false,
|
||||
size: 300,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
});
|
||||
|
||||
await deleteTestViewFieldWithRestApi(viewField.id);
|
||||
});
|
||||
|
||||
it('should return 404 error when updating non-existent view field', async () => {
|
||||
@@ -281,7 +292,7 @@ describe('View Field REST API', () => {
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'patch',
|
||||
path: `/metadata/viewFields/${TEST_NOT_EXISTING_VIEW_FIELD_ID}`,
|
||||
path: `/metadata/viewFields/20202020-f891-4d2a-8b23-c1e4d7f6a9b2`,
|
||||
body: updateData,
|
||||
bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN,
|
||||
});
|
||||
@@ -291,7 +302,7 @@ describe('View Field REST API', () => {
|
||||
404,
|
||||
generateViewFieldExceptionMessage(
|
||||
ViewFieldExceptionMessageKey.VIEW_FIELD_NOT_FOUND,
|
||||
TEST_NOT_EXISTING_VIEW_FIELD_ID,
|
||||
'20202020-f891-4d2a-8b23-c1e4d7f6a9b2',
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -300,12 +311,15 @@ describe('View Field REST API', () => {
|
||||
describe('DELETE /metadata/viewFields/:id', () => {
|
||||
it('should delete an existing view field', async () => {
|
||||
const viewField = await createTestViewFieldWithRestApi({
|
||||
viewId: testViewId,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
});
|
||||
|
||||
testViewFieldId = viewField.id;
|
||||
|
||||
const deleteResponse = await makeRestAPIRequest({
|
||||
method: 'delete',
|
||||
path: `/metadata/viewFields/${viewField.id}`,
|
||||
@@ -327,7 +341,7 @@ describe('View Field REST API', () => {
|
||||
it('should return 404 error when deleting non-existent view field', async () => {
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'delete',
|
||||
path: `/metadata/viewFields/${TEST_NOT_EXISTING_VIEW_FIELD_ID}`,
|
||||
path: `/metadata/viewFields/20202020-f891-4d2a-8b23-c1e4d7f6a9b2`,
|
||||
bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
|
||||
+78
-51
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
TEST_NOT_EXISTING_VIEW_FILTER_GROUP_ID,
|
||||
TEST_VIEW_1_ID,
|
||||
} from 'test/integration/constants/test-view-ids.constants';
|
||||
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
@@ -16,12 +13,11 @@ import {
|
||||
createTestViewWithRestApi,
|
||||
deleteTestViewFilterGroupWithRestApi,
|
||||
} from 'test/integration/rest/utils/view-rest-api.util';
|
||||
import { generateRecordName } from 'test/integration/utils/generate-record-name';
|
||||
import {
|
||||
assertViewFilterGroupStructure,
|
||||
cleanupViewRecords,
|
||||
} from 'test/integration/utils/view-test.util';
|
||||
import { assertViewFilterGroupStructure } from 'test/integration/utils/view-test.util';
|
||||
import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util.test';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type ViewFilterGroupDTO } from 'src/engine/metadata-modules/view-filter-group/dtos/view-filter-group.dto';
|
||||
import { ViewFilterGroupLogicalOperator } from 'src/engine/metadata-modules/view-filter-group/enums/view-filter-group-logical-operator';
|
||||
import {
|
||||
generateViewFilterGroupExceptionMessage,
|
||||
@@ -30,6 +26,8 @@ import {
|
||||
|
||||
describe('View Filter Group REST API', () => {
|
||||
let testObjectMetadataId: string;
|
||||
let testViewId: string;
|
||||
let testViewFilterGroupId: string | undefined;
|
||||
|
||||
beforeAll(async () => {
|
||||
const {
|
||||
@@ -38,15 +36,40 @@ describe('View Filter Group REST API', () => {
|
||||
},
|
||||
} = await createOneObjectMetadata({
|
||||
input: {
|
||||
nameSingular: 'myTestObject',
|
||||
namePlural: 'myTestObjects',
|
||||
labelSingular: 'My Test Object',
|
||||
labelPlural: 'My Test Objects',
|
||||
icon: 'Icon123',
|
||||
nameSingular: 'testViewFilterGroupObject',
|
||||
namePlural: 'testViewFilterGroupObjects',
|
||||
labelSingular: 'Test View Filter Group Object',
|
||||
labelPlural: 'Test View Filter Group Objects',
|
||||
icon: 'IconFilterGroup',
|
||||
},
|
||||
});
|
||||
|
||||
testObjectMetadataId = objectMetadataId;
|
||||
|
||||
const createFieldInput = {
|
||||
name: 'testField',
|
||||
label: 'Test Field',
|
||||
type: FieldMetadataType.TEXT,
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
isLabelSyncedWithName: true,
|
||||
};
|
||||
|
||||
await createOneFieldMetadata({
|
||||
input: createFieldInput,
|
||||
gqlFields: `
|
||||
id
|
||||
name
|
||||
label
|
||||
isLabelSyncedWithName
|
||||
`,
|
||||
});
|
||||
|
||||
const testView = await createTestViewWithRestApi({
|
||||
name: 'Test View for Filter Group Integration',
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
});
|
||||
|
||||
testViewId = testView.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
@@ -64,24 +87,18 @@ describe('View Filter Group REST API', () => {
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await cleanupViewRecords();
|
||||
afterEach(async () => {
|
||||
if (!testViewFilterGroupId) return;
|
||||
|
||||
await createTestViewWithRestApi({
|
||||
name: generateRecordName('Test View for Filter Groups'),
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanupViewRecords();
|
||||
await deleteTestViewFilterGroupWithRestApi(testViewFilterGroupId);
|
||||
testViewFilterGroupId = undefined;
|
||||
});
|
||||
|
||||
describe('GET /metadata/viewFilterGroups', () => {
|
||||
it('should return empty array when no view filter groups exist', async () => {
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/viewFilterGroups?viewId=${TEST_VIEW_1_ID}`,
|
||||
path: `/metadata/viewFilterGroups?viewId=${testViewId}`,
|
||||
bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
@@ -102,33 +119,39 @@ describe('View Filter Group REST API', () => {
|
||||
|
||||
it('should return view filter groups for a specific view after creating one', async () => {
|
||||
const viewFilterGroup = await createTestViewFilterGroupWithRestApi({
|
||||
viewId: testViewId,
|
||||
logicalOperator: ViewFilterGroupLogicalOperator.AND,
|
||||
});
|
||||
|
||||
testViewFilterGroupId = viewFilterGroup.id;
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/viewFilterGroups?viewId=${TEST_VIEW_1_ID}`,
|
||||
path: `/metadata/viewFilterGroups?viewId=${testViewId}`,
|
||||
bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
assertRestApiSuccessfulResponse(response);
|
||||
expect(Array.isArray(response.body)).toBe(true);
|
||||
expect(response.body).toHaveLength(1);
|
||||
|
||||
const returnedViewFilterGroup = response.body[0];
|
||||
const returnedViewFilterGroup = response.body.find(
|
||||
(el: ViewFilterGroupDTO) => el.id === viewFilterGroup.id,
|
||||
);
|
||||
|
||||
jestExpectToBeDefined(returnedViewFilterGroup);
|
||||
|
||||
assertViewFilterGroupStructure(returnedViewFilterGroup, {
|
||||
id: viewFilterGroup.id,
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
logicalOperator: ViewFilterGroupLogicalOperator.AND,
|
||||
});
|
||||
|
||||
await deleteTestViewFilterGroupWithRestApi(viewFilterGroup.id);
|
||||
testViewFilterGroupId = viewFilterGroup.id;
|
||||
});
|
||||
|
||||
it('should return nested filter groups with parent relationships', async () => {
|
||||
const parentData = {
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
logicalOperator: 'AND',
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
};
|
||||
@@ -143,7 +166,7 @@ describe('View Filter Group REST API', () => {
|
||||
const parentId = parentResponse.body.id;
|
||||
|
||||
const childData = {
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
parentViewFilterGroupId: parentId,
|
||||
logicalOperator: 'OR',
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
@@ -160,7 +183,7 @@ describe('View Filter Group REST API', () => {
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/viewFilterGroups?viewId=${TEST_VIEW_1_ID}`,
|
||||
path: `/metadata/viewFilterGroups?viewId=${testViewId}`,
|
||||
bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
@@ -204,11 +227,14 @@ describe('View Filter Group REST API', () => {
|
||||
describe('POST /metadata/viewFilterGroups', () => {
|
||||
it('should create a new filter group with AND operator', async () => {
|
||||
const viewFilterGroup = await createTestViewFilterGroupWithRestApi({
|
||||
viewId: testViewId,
|
||||
logicalOperator: ViewFilterGroupLogicalOperator.AND,
|
||||
});
|
||||
|
||||
testViewFilterGroupId = viewFilterGroup.id;
|
||||
|
||||
assertViewFilterGroupStructure(viewFilterGroup, {
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
logicalOperator: ViewFilterGroupLogicalOperator.AND,
|
||||
});
|
||||
expect(viewFilterGroup.parentViewFilterGroupId).toBeNull();
|
||||
@@ -216,21 +242,22 @@ describe('View Filter Group REST API', () => {
|
||||
|
||||
it('should create a filter group with OR operator', async () => {
|
||||
const orGroup = await createTestViewFilterGroupWithRestApi({
|
||||
viewId: testViewId,
|
||||
logicalOperator: ViewFilterGroupLogicalOperator.OR,
|
||||
});
|
||||
|
||||
testViewFilterGroupId = orGroup.id;
|
||||
|
||||
assertViewFilterGroupStructure(orGroup, {
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
logicalOperator: ViewFilterGroupLogicalOperator.OR,
|
||||
});
|
||||
expect(orGroup.parentViewFilterGroupId).toBeNull();
|
||||
|
||||
await deleteTestViewFilterGroupWithRestApi(orGroup.id);
|
||||
});
|
||||
|
||||
it('should create a filter group with NOT operator', async () => {
|
||||
const viewFilterGroupData = {
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
logicalOperator: 'NOT',
|
||||
};
|
||||
|
||||
@@ -254,7 +281,7 @@ describe('View Filter Group REST API', () => {
|
||||
|
||||
it('should create a nested filter group with parent relationship', async () => {
|
||||
const parentData = {
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
logicalOperator: 'AND',
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
};
|
||||
@@ -269,7 +296,7 @@ describe('View Filter Group REST API', () => {
|
||||
const parentId = parentResponse.body.id;
|
||||
|
||||
const childData = {
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
parentViewFilterGroupId: parentId,
|
||||
logicalOperator: 'OR',
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
@@ -289,7 +316,7 @@ describe('View Filter Group REST API', () => {
|
||||
describe('GET /metadata/viewFilterGroups/:id', () => {
|
||||
it('should return a view filter group by id', async () => {
|
||||
const viewFilterGroupData = {
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
logicalOperator: 'NOT',
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
};
|
||||
@@ -312,14 +339,14 @@ describe('View Filter Group REST API', () => {
|
||||
assertRestApiSuccessfulResponse(response);
|
||||
expect(response.body).toBeDefined();
|
||||
expect(response.body.id).toBe(viewFilterGroupId);
|
||||
expect(response.body.viewId).toBe(TEST_VIEW_1_ID);
|
||||
expect(response.body.viewId).toBe(testViewId);
|
||||
expect(response.body.logicalOperator).toBe('NOT');
|
||||
});
|
||||
|
||||
it('should return empty object for non-existent view filter group', async () => {
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/viewFilterGroups/${TEST_NOT_EXISTING_VIEW_FILTER_GROUP_ID}`,
|
||||
path: `/metadata/viewFilterGroups/20202020-e214-44fa-a39a-d81447b2c44f`,
|
||||
bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
@@ -330,7 +357,7 @@ describe('View Filter Group REST API', () => {
|
||||
describe('PATCH /metadata/viewFilterGroups/:id', () => {
|
||||
it('should update an existing filter group', async () => {
|
||||
const viewFilterGroupData = {
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
logicalOperator: 'AND',
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
};
|
||||
@@ -360,12 +387,12 @@ describe('View Filter Group REST API', () => {
|
||||
expect(response.body).toBeDefined();
|
||||
expect(response.body.id).toBe(viewFilterGroupId);
|
||||
expect(response.body.logicalOperator).toBe('OR');
|
||||
expect(response.body.viewId).toBe(TEST_VIEW_1_ID);
|
||||
expect(response.body.viewId).toBe(testViewId);
|
||||
});
|
||||
|
||||
it('should update parent relationship of filter group', async () => {
|
||||
const parentData = {
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
logicalOperator: 'AND',
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
};
|
||||
@@ -380,7 +407,7 @@ describe('View Filter Group REST API', () => {
|
||||
const parentId = parentResponse.body.id;
|
||||
|
||||
const childData = {
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
logicalOperator: 'OR',
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
};
|
||||
@@ -421,7 +448,7 @@ describe('View Filter Group REST API', () => {
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'patch',
|
||||
path: `/metadata/viewFilterGroups/${TEST_NOT_EXISTING_VIEW_FILTER_GROUP_ID}`,
|
||||
path: `/metadata/viewFilterGroups/20202020-e214-44fa-a39a-d81447b2c44f`,
|
||||
body: updateData,
|
||||
bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN,
|
||||
});
|
||||
@@ -431,7 +458,7 @@ describe('View Filter Group REST API', () => {
|
||||
404,
|
||||
generateViewFilterGroupExceptionMessage(
|
||||
ViewFilterGroupExceptionMessageKey.VIEW_FILTER_GROUP_NOT_FOUND,
|
||||
TEST_NOT_EXISTING_VIEW_FILTER_GROUP_ID,
|
||||
'20202020-e214-44fa-a39a-d81447b2c44f',
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -440,7 +467,7 @@ describe('View Filter Group REST API', () => {
|
||||
describe('DELETE /metadata/viewFilterGroups/:id', () => {
|
||||
it('should delete an existing filter group', async () => {
|
||||
const viewFilterGroupData = {
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
logicalOperator: 'AND',
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
};
|
||||
@@ -476,7 +503,7 @@ describe('View Filter Group REST API', () => {
|
||||
it('should return 404 error when deleting non-existent filter group', async () => {
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'delete',
|
||||
path: `/metadata/viewFilterGroups/${TEST_NOT_EXISTING_VIEW_FILTER_GROUP_ID}`,
|
||||
path: `/metadata/viewFilterGroups/20202020-e214-44fa-a39a-d81447b2c44f`,
|
||||
bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
|
||||
+67
-45
@@ -1,11 +1,8 @@
|
||||
import {
|
||||
TEST_NOT_EXISTING_VIEW_FILTER_ID,
|
||||
TEST_VIEW_1_ID,
|
||||
} from 'test/integration/constants/test-view-ids.constants';
|
||||
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import { destroyOneCoreViewFilter } from 'test/integration/metadata/suites/view-filter/utils/destroy-one-core-view-filter.util';
|
||||
import { makeRestAPIRequest } from 'test/integration/rest/utils/make-rest-api-request.util';
|
||||
import {
|
||||
assertRestApiErrorNotFoundResponse,
|
||||
@@ -14,17 +11,18 @@ import {
|
||||
import {
|
||||
createTestViewFilterWithRestApi,
|
||||
createTestViewWithRestApi,
|
||||
deleteTestViewFilterWithRestApi,
|
||||
} from 'test/integration/rest/utils/view-rest-api.util';
|
||||
import {
|
||||
assertViewFilterStructure,
|
||||
cleanupViewRecords,
|
||||
} from 'test/integration/utils/view-test.util';
|
||||
import { assertViewFilterStructure } from 'test/integration/utils/view-test.util';
|
||||
import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util.test';
|
||||
import { FieldMetadataType, ViewFilterOperand } from 'twenty-shared/types';
|
||||
|
||||
import { type ViewFilterDTO } from 'src/engine/metadata-modules/view-filter/dtos/view-filter.dto';
|
||||
|
||||
describe('View Filter REST API', () => {
|
||||
let testObjectMetadataId: string;
|
||||
let testFieldMetadataId: string;
|
||||
let testViewId: string;
|
||||
let testViewFilterId: string | undefined;
|
||||
|
||||
beforeAll(async () => {
|
||||
const {
|
||||
@@ -33,11 +31,11 @@ describe('View Filter REST API', () => {
|
||||
},
|
||||
} = await createOneObjectMetadata({
|
||||
input: {
|
||||
nameSingular: 'myTestObject',
|
||||
namePlural: 'myTestObjects',
|
||||
labelSingular: 'My Test Object',
|
||||
labelPlural: 'My Test Objects',
|
||||
icon: 'Icon123',
|
||||
nameSingular: 'testViewFilterObject',
|
||||
namePlural: 'testViewFilterObjects',
|
||||
labelSingular: 'Test View Filter Object',
|
||||
labelPlural: 'Test View Filter Objects',
|
||||
icon: 'IconFilter',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -66,6 +64,13 @@ describe('View Filter REST API', () => {
|
||||
});
|
||||
|
||||
testFieldMetadataId = fieldMetadataId;
|
||||
|
||||
const testView = await createTestViewWithRestApi({
|
||||
name: 'Test View for Filter Integration',
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
});
|
||||
|
||||
testViewId = testView.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
@@ -83,24 +88,23 @@ describe('View Filter REST API', () => {
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await cleanupViewRecords();
|
||||
afterEach(async () => {
|
||||
if (!testViewFilterId) return;
|
||||
|
||||
await createTestViewWithRestApi({
|
||||
name: 'Test View for Filters',
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
await destroyOneCoreViewFilter({
|
||||
input: {
|
||||
id: testViewFilterId,
|
||||
},
|
||||
expectToFail: false,
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanupViewRecords();
|
||||
testViewFilterId = undefined;
|
||||
});
|
||||
|
||||
describe('GET /metadata/viewFilters', () => {
|
||||
it('should return empty array when no view filters exist', async () => {
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/viewFilters?viewId=${TEST_VIEW_1_ID}`,
|
||||
path: `/metadata/viewFilters?viewId=${testViewId}`,
|
||||
bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
@@ -121,96 +125,108 @@ describe('View Filter REST API', () => {
|
||||
|
||||
it('should return view filters for a specific view after creating one', async () => {
|
||||
const viewFilter = await createTestViewFilterWithRestApi({
|
||||
viewId: testViewId,
|
||||
operand: ViewFilterOperand.CONTAINS,
|
||||
value: 'test',
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
});
|
||||
|
||||
testViewFilterId = viewFilter.id;
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/viewFilters?viewId=${TEST_VIEW_1_ID}`,
|
||||
path: `/metadata/viewFilters?viewId=${testViewId}`,
|
||||
bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
assertRestApiSuccessfulResponse(response);
|
||||
expect(Array.isArray(response.body)).toBe(true);
|
||||
expect(response.body).toHaveLength(1);
|
||||
|
||||
const returnedViewFilter = response.body[0];
|
||||
const returnedViewFilter = response.body.find(
|
||||
(el: ViewFilterDTO) => el.id === viewFilter.id,
|
||||
);
|
||||
|
||||
jestExpectToBeDefined(returnedViewFilter);
|
||||
|
||||
assertViewFilterStructure(returnedViewFilter, {
|
||||
id: viewFilter.id,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
operand: ViewFilterOperand.CONTAINS,
|
||||
value: 'test',
|
||||
});
|
||||
|
||||
await deleteTestViewFilterWithRestApi(viewFilter.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /metadata/viewFilters', () => {
|
||||
it('should create a new view filter with string value', async () => {
|
||||
const viewFilter = await createTestViewFilterWithRestApi({
|
||||
viewId: testViewId,
|
||||
operand: ViewFilterOperand.IS,
|
||||
value: 'test value',
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
});
|
||||
|
||||
testViewFilterId = viewFilter.id;
|
||||
|
||||
assertViewFilterStructure(viewFilter, {
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
operand: ViewFilterOperand.IS,
|
||||
value: 'test value',
|
||||
});
|
||||
|
||||
await deleteTestViewFilterWithRestApi(viewFilter.id);
|
||||
testViewFilterId = viewFilter.id;
|
||||
});
|
||||
|
||||
it('should create a view filter with numeric value', async () => {
|
||||
const numericFilter = await createTestViewFilterWithRestApi({
|
||||
viewId: testViewId,
|
||||
operand: ViewFilterOperand.GREATER_THAN_OR_EQUAL,
|
||||
value: '100',
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
});
|
||||
|
||||
testViewFilterId = numericFilter.id;
|
||||
|
||||
assertViewFilterStructure(numericFilter, {
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
operand: ViewFilterOperand.GREATER_THAN_OR_EQUAL,
|
||||
value: '100',
|
||||
});
|
||||
|
||||
await deleteTestViewFilterWithRestApi(numericFilter.id);
|
||||
});
|
||||
|
||||
it('should create a view filter with boolean value', async () => {
|
||||
const booleanFilter = await createTestViewFilterWithRestApi({
|
||||
viewId: testViewId,
|
||||
operand: ViewFilterOperand.IS,
|
||||
value: 'true',
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
});
|
||||
|
||||
testViewFilterId = booleanFilter.id;
|
||||
|
||||
assertViewFilterStructure(booleanFilter, {
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
operand: ViewFilterOperand.IS,
|
||||
value: 'true',
|
||||
});
|
||||
|
||||
await deleteTestViewFilterWithRestApi(booleanFilter.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /metadata/viewFilters/:id', () => {
|
||||
it('should return a view filter by id', async () => {
|
||||
const viewFilter = await createTestViewFilterWithRestApi({
|
||||
viewId: testViewId,
|
||||
operand: ViewFilterOperand.IS,
|
||||
value: 'test',
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
});
|
||||
|
||||
testViewFilterId = viewFilter.id;
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/viewFilters/${viewFilter.id}`,
|
||||
@@ -221,18 +237,18 @@ describe('View Filter REST API', () => {
|
||||
assertViewFilterStructure(response.body, {
|
||||
id: viewFilter.id,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
operand: ViewFilterOperand.IS,
|
||||
value: 'test',
|
||||
});
|
||||
|
||||
await deleteTestViewFilterWithRestApi(viewFilter.id);
|
||||
testViewFilterId = viewFilter.id;
|
||||
});
|
||||
|
||||
it('should return empty object for non-existent view filter', async () => {
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/viewFilters/${TEST_NOT_EXISTING_VIEW_FILTER_ID}`,
|
||||
path: `/metadata/viewFilters/20202020-5262-419d-ab77-575bfaf3db28`,
|
||||
bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
@@ -243,11 +259,14 @@ describe('View Filter REST API', () => {
|
||||
describe('PATCH /metadata/viewFilters/:id', () => {
|
||||
it('should update an existing view filter', async () => {
|
||||
const viewFilter = await createTestViewFilterWithRestApi({
|
||||
viewId: testViewId,
|
||||
operand: ViewFilterOperand.IS,
|
||||
value: 'original',
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
});
|
||||
|
||||
testViewFilterId = viewFilter.id;
|
||||
|
||||
const updateData = {
|
||||
operand: ViewFilterOperand.IS_NOT,
|
||||
value: 'updated',
|
||||
@@ -266,10 +285,10 @@ describe('View Filter REST API', () => {
|
||||
operand: ViewFilterOperand.IS_NOT,
|
||||
value: 'updated',
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
});
|
||||
|
||||
await deleteTestViewFilterWithRestApi(viewFilter.id);
|
||||
testViewFilterId = viewFilter.id;
|
||||
});
|
||||
|
||||
it('should return 404 error when updating non-existent view filter', async () => {
|
||||
@@ -280,7 +299,7 @@ describe('View Filter REST API', () => {
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'patch',
|
||||
path: `/metadata/viewFilters/${TEST_NOT_EXISTING_VIEW_FILTER_ID}`,
|
||||
path: `/metadata/viewFilters/20202020-d8db-4dfb-b654-01b872851b37`,
|
||||
body: updateData,
|
||||
bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN,
|
||||
});
|
||||
@@ -292,11 +311,14 @@ describe('View Filter REST API', () => {
|
||||
describe('DELETE /metadata/viewFilters/:id', () => {
|
||||
it('should delete an existing view filter', async () => {
|
||||
const viewFilter = await createTestViewFilterWithRestApi({
|
||||
viewId: testViewId,
|
||||
operand: ViewFilterOperand.IS,
|
||||
value: 'to delete',
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
});
|
||||
|
||||
testViewFilterId = viewFilter.id;
|
||||
|
||||
const deleteResponse = await makeRestAPIRequest({
|
||||
method: 'delete',
|
||||
path: `/metadata/viewFilters/${viewFilter.id}`,
|
||||
@@ -318,7 +340,7 @@ describe('View Filter REST API', () => {
|
||||
it('should return 404 error when deleting non-existent view filter', async () => {
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'delete',
|
||||
path: `/metadata/viewFilters/${TEST_NOT_EXISTING_VIEW_FILTER_ID}`,
|
||||
path: `/metadata/viewFilters/20202020-b8a3-4885-ae28-b89c2a4942d8`,
|
||||
bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
|
||||
+85
-69
@@ -1,38 +1,30 @@
|
||||
import {
|
||||
TEST_NOT_EXISTING_VIEW_GROUP_ID,
|
||||
TEST_VIEW_1_ID,
|
||||
} from 'test/integration/constants/test-view-ids.constants';
|
||||
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import { destroyOneCoreViewGroup } from 'test/integration/metadata/suites/view-group/utils/destroy-one-core-view-group.util';
|
||||
import { makeRestAPIRequest } from 'test/integration/rest/utils/make-rest-api-request.util';
|
||||
import {
|
||||
assertRestApiErrorNotFoundResponse,
|
||||
assertRestApiErrorResponse,
|
||||
assertRestApiSuccessfulResponse,
|
||||
} from 'test/integration/rest/utils/rest-test-assertions.util';
|
||||
import {
|
||||
createTestViewGroupWithRestApi,
|
||||
createTestViewWithRestApi,
|
||||
deleteTestViewGroupWithRestApi,
|
||||
} from 'test/integration/rest/utils/view-rest-api.util';
|
||||
import { generateRecordName } from 'test/integration/utils/generate-record-name';
|
||||
import {
|
||||
assertViewGroupStructure,
|
||||
cleanupViewRecords,
|
||||
} from 'test/integration/utils/view-test.util';
|
||||
import { assertViewGroupStructure } from 'test/integration/utils/view-test.util';
|
||||
import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any';
|
||||
import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util.test';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type ViewGroupDTO } from 'src/engine/metadata-modules/view-group/dtos/view-group.dto';
|
||||
import { type ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity';
|
||||
import {
|
||||
generateViewGroupExceptionMessage,
|
||||
ViewGroupExceptionMessageKey,
|
||||
} from 'src/engine/metadata-modules/view-group/exceptions/view-group.exception';
|
||||
|
||||
describe('View Group REST API', () => {
|
||||
let testObjectMetadataId: string;
|
||||
let testFieldMetadataId: string;
|
||||
let testViewId: string;
|
||||
let testViewGroupId: string | undefined;
|
||||
|
||||
beforeAll(async () => {
|
||||
const {
|
||||
@@ -42,11 +34,11 @@ describe('View Group REST API', () => {
|
||||
} = await createOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
nameSingular: 'myTestObject',
|
||||
namePlural: 'myTestObjects',
|
||||
labelSingular: 'My Test Object',
|
||||
labelPlural: 'My Test Objects',
|
||||
icon: 'Icon123',
|
||||
nameSingular: 'testViewGroupObject',
|
||||
namePlural: 'testViewGroupObjects',
|
||||
labelSingular: 'Test View Group Object',
|
||||
labelPlural: 'Test View Group Objects',
|
||||
icon: 'IconGroup',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -76,6 +68,13 @@ describe('View Group REST API', () => {
|
||||
});
|
||||
|
||||
testFieldMetadataId = fieldMetadataId;
|
||||
|
||||
const testView = await createTestViewWithRestApi({
|
||||
name: 'Test View for Group Integration',
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
});
|
||||
|
||||
testViewId = testView.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
@@ -94,24 +93,23 @@ describe('View Group REST API', () => {
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await cleanupViewRecords();
|
||||
afterEach(async () => {
|
||||
if (!testViewGroupId) return;
|
||||
|
||||
await createTestViewWithRestApi({
|
||||
name: generateRecordName('Test View for Groups'),
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
await destroyOneCoreViewGroup({
|
||||
input: {
|
||||
id: testViewGroupId,
|
||||
},
|
||||
expectToFail: false,
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanupViewRecords();
|
||||
testViewGroupId = undefined;
|
||||
});
|
||||
|
||||
describe('GET /metadata/viewGroups', () => {
|
||||
it('should return empty array when no view groups exist', async () => {
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/viewGroups?viewId=${TEST_VIEW_1_ID}`,
|
||||
path: `/metadata/viewGroups?viewId=${testViewId}`,
|
||||
bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
@@ -132,51 +130,59 @@ describe('View Group REST API', () => {
|
||||
|
||||
it('should return view groups for a specific view after creating one', async () => {
|
||||
const viewGroup = await createTestViewGroupWithRestApi({
|
||||
viewId: testViewId,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
fieldValue: 'test-field-value',
|
||||
isVisible: true,
|
||||
position: 0,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
});
|
||||
|
||||
testViewGroupId = viewGroup.id;
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/viewGroups?viewId=${TEST_VIEW_1_ID}`,
|
||||
path: `/metadata/viewGroups?viewId=${testViewId}`,
|
||||
bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
assertRestApiSuccessfulResponse(response);
|
||||
expect(Array.isArray(response.body)).toBe(true);
|
||||
expect(response.body).toHaveLength(1);
|
||||
|
||||
const returnedViewGroup = response.body[0];
|
||||
const returnedViewGroup = response.body.find(
|
||||
(el: ViewGroupDTO) => el.id === viewGroup.id,
|
||||
);
|
||||
|
||||
jestExpectToBeDefined(returnedViewGroup);
|
||||
|
||||
assertViewGroupStructure(returnedViewGroup, {
|
||||
id: viewGroup.id,
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
fieldValue: 'test-field-value',
|
||||
isVisible: true,
|
||||
position: 0,
|
||||
});
|
||||
|
||||
await deleteTestViewGroupWithRestApi(viewGroup.id);
|
||||
testViewGroupId = viewGroup.id;
|
||||
});
|
||||
|
||||
it('should return multiple view groups for a view', async () => {
|
||||
const viewGroup1 = await createTestViewGroupWithRestApi({
|
||||
viewId: testViewId,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
fieldValue: 'group-1',
|
||||
position: 0,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
});
|
||||
|
||||
const viewGroup2 = await createTestViewGroupWithRestApi({
|
||||
viewId: testViewId,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
fieldValue: 'group-2',
|
||||
position: 1,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
});
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/viewGroups?viewId=${TEST_VIEW_1_ID}`,
|
||||
path: `/metadata/viewGroups?viewId=${testViewId}`,
|
||||
bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
@@ -201,19 +207,21 @@ describe('View Group REST API', () => {
|
||||
position: 1,
|
||||
});
|
||||
|
||||
await deleteTestViewGroupWithRestApi(viewGroup1.id);
|
||||
await deleteTestViewGroupWithRestApi(viewGroup2.id);
|
||||
testViewGroupId = viewGroup2.id;
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /metadata/viewGroups/:id', () => {
|
||||
it('should return a specific view group by id', async () => {
|
||||
const viewGroup = await createTestViewGroupWithRestApi({
|
||||
viewId: testViewId,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
fieldValue: 'specific-group',
|
||||
isVisible: false,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
});
|
||||
|
||||
testViewGroupId = viewGroup.id;
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/viewGroups/${viewGroup.id}`,
|
||||
@@ -223,19 +231,19 @@ describe('View Group REST API', () => {
|
||||
assertRestApiSuccessfulResponse(response);
|
||||
assertViewGroupStructure(response.body, {
|
||||
id: viewGroup.id,
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
fieldValue: 'specific-group',
|
||||
isVisible: false,
|
||||
});
|
||||
|
||||
await deleteTestViewGroupWithRestApi(viewGroup.id);
|
||||
testViewGroupId = viewGroup.id;
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /metadata/viewGroups', () => {
|
||||
describe.only('POST /metadata/viewGroups', () => {
|
||||
it('should create a new view group', async () => {
|
||||
const viewGroupData = {
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
fieldValue: 'new-group-value',
|
||||
isVisible: true,
|
||||
@@ -251,18 +259,18 @@ describe('View Group REST API', () => {
|
||||
|
||||
assertRestApiSuccessfulResponse(response, 201);
|
||||
assertViewGroupStructure(response.body, {
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
fieldValue: 'new-group-value',
|
||||
isVisible: true,
|
||||
position: 5,
|
||||
});
|
||||
|
||||
await deleteTestViewGroupWithRestApi(response.body.id);
|
||||
testViewGroupId = response.body.id;
|
||||
});
|
||||
|
||||
it('should create view group with minimal required fields', async () => {
|
||||
const viewGroupData = {
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
fieldValue: 'minimal-group',
|
||||
};
|
||||
@@ -276,18 +284,18 @@ describe('View Group REST API', () => {
|
||||
|
||||
assertRestApiSuccessfulResponse(response, 201);
|
||||
assertViewGroupStructure(response.body, {
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
fieldValue: 'minimal-group',
|
||||
isVisible: true,
|
||||
position: 0,
|
||||
});
|
||||
|
||||
await deleteTestViewGroupWithRestApi(response.body.id);
|
||||
testViewGroupId = response.body.id;
|
||||
});
|
||||
|
||||
it('should fail to create view group with missing required fields', async () => {
|
||||
const invalidData = {
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
};
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
@@ -297,12 +305,12 @@ describe('View Group REST API', () => {
|
||||
bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
assertRestApiErrorResponse(
|
||||
response,
|
||||
400,
|
||||
generateViewGroupExceptionMessage(
|
||||
ViewGroupExceptionMessageKey.INVALID_VIEW_GROUP_DATA,
|
||||
),
|
||||
expect(response.status).toBe(400);
|
||||
|
||||
const errorResponse = JSON.parse(response.text);
|
||||
|
||||
expect(errorResponse).toMatchSnapshot(
|
||||
extractRecordIdsAndDatesAsExpectAny(errorResponse),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -310,12 +318,15 @@ describe('View Group REST API', () => {
|
||||
describe('PATCH /metadata/viewGroups/:id', () => {
|
||||
it('should update an existing view group', async () => {
|
||||
const viewGroup = await createTestViewGroupWithRestApi({
|
||||
viewId: testViewId,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
fieldValue: 'original-value',
|
||||
isVisible: true,
|
||||
position: 1,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
});
|
||||
|
||||
testViewGroupId = viewGroup.id;
|
||||
|
||||
const updateData = {
|
||||
fieldValue: 'updated-value',
|
||||
isVisible: false,
|
||||
@@ -332,23 +343,24 @@ describe('View Group REST API', () => {
|
||||
assertRestApiSuccessfulResponse(response);
|
||||
assertViewGroupStructure(response.body, {
|
||||
id: viewGroup.id,
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
fieldValue: 'updated-value',
|
||||
isVisible: false,
|
||||
position: 2,
|
||||
});
|
||||
|
||||
await deleteTestViewGroupWithRestApi(viewGroup.id);
|
||||
});
|
||||
|
||||
it('should update only specific fields', async () => {
|
||||
const viewGroup = await createTestViewGroupWithRestApi({
|
||||
viewId: testViewId,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
fieldValue: 'original-value',
|
||||
isVisible: true,
|
||||
position: 1,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
});
|
||||
|
||||
testViewGroupId = viewGroup.id;
|
||||
|
||||
const updateData = {
|
||||
fieldValue: 'partially-updated',
|
||||
};
|
||||
@@ -367,8 +379,6 @@ describe('View Group REST API', () => {
|
||||
isVisible: true,
|
||||
position: 1,
|
||||
});
|
||||
|
||||
await deleteTestViewGroupWithRestApi(viewGroup.id);
|
||||
});
|
||||
|
||||
it('should return 404 for non-existent view group', async () => {
|
||||
@@ -378,7 +388,7 @@ describe('View Group REST API', () => {
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'patch',
|
||||
path: `/metadata/viewGroups/${TEST_NOT_EXISTING_VIEW_GROUP_ID}`,
|
||||
path: `/metadata/viewGroups/20202020-9c8b-4a7e-9f2d-1a2b3c4d5e6f`,
|
||||
body: updateData,
|
||||
bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN,
|
||||
});
|
||||
@@ -390,10 +400,13 @@ describe('View Group REST API', () => {
|
||||
describe('DELETE /metadata/viewGroups/:id', () => {
|
||||
it('should delete an existing view group', async () => {
|
||||
const viewGroup = await createTestViewGroupWithRestApi({
|
||||
fieldValue: 'to-be-deleted',
|
||||
viewId: testViewId,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
fieldValue: 'to-be-deleted',
|
||||
});
|
||||
|
||||
testViewGroupId = viewGroup.id;
|
||||
|
||||
const deleteResponse = await makeRestAPIRequest({
|
||||
method: 'delete',
|
||||
path: `/metadata/viewGroups/${viewGroup.id}`,
|
||||
@@ -407,7 +420,7 @@ describe('View Group REST API', () => {
|
||||
it('should return 404 for non-existent view group', async () => {
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'delete',
|
||||
path: `/metadata/viewGroups/${TEST_NOT_EXISTING_VIEW_GROUP_ID}`,
|
||||
path: `/metadata/viewGroups/20202020-9c8b-4a7e-9f2d-1a2b3c4d5e6f`,
|
||||
bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
@@ -416,10 +429,13 @@ describe('View Group REST API', () => {
|
||||
|
||||
it('should return success even when group is already deleted', async () => {
|
||||
const viewGroup = await createTestViewGroupWithRestApi({
|
||||
fieldValue: 'double-delete-test',
|
||||
viewId: testViewId,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
fieldValue: 'double-delete-test',
|
||||
});
|
||||
|
||||
testViewGroupId = viewGroup.id;
|
||||
|
||||
const deleteResponse = await makeRestAPIRequest({
|
||||
method: 'delete',
|
||||
path: `/metadata/viewGroups/${viewGroup.id}`,
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import {
|
||||
TEST_NOT_EXISTING_VIEW_SORT_ID,
|
||||
TEST_VIEW_1_ID,
|
||||
} from 'test/integration/constants/test-view-ids.constants';
|
||||
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
@@ -15,14 +11,13 @@ import {
|
||||
import {
|
||||
createTestViewSortWithRestApi,
|
||||
createTestViewWithRestApi,
|
||||
deleteTestViewSortWithRestApi,
|
||||
} from 'test/integration/rest/utils/view-rest-api.util';
|
||||
import { generateRecordName } from 'test/integration/utils/generate-record-name';
|
||||
import {
|
||||
assertViewSortStructure,
|
||||
cleanupViewRecords,
|
||||
} from 'test/integration/utils/view-test.util';
|
||||
import { assertViewSortStructure } from 'test/integration/utils/view-test.util';
|
||||
import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util.test';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type ViewSortDTO } from 'src/engine/metadata-modules/view-sort/dtos/view-sort.dto';
|
||||
import { ViewSortDirection } from 'src/engine/metadata-modules/view-sort/enums/view-sort-direction';
|
||||
import {
|
||||
generateViewSortExceptionMessage,
|
||||
@@ -32,6 +27,8 @@ import {
|
||||
describe('View Sort REST API', () => {
|
||||
let testObjectMetadataId: string;
|
||||
let testFieldMetadataId: string;
|
||||
let testViewId: string;
|
||||
let testViewSortId: string | undefined;
|
||||
|
||||
beforeAll(async () => {
|
||||
const {
|
||||
@@ -40,11 +37,11 @@ describe('View Sort REST API', () => {
|
||||
},
|
||||
} = await createOneObjectMetadata({
|
||||
input: {
|
||||
nameSingular: 'myTestObject',
|
||||
namePlural: 'myTestObjects',
|
||||
labelSingular: 'My Test Object',
|
||||
labelPlural: 'My Test Objects',
|
||||
icon: 'Icon123',
|
||||
nameSingular: 'testViewSortObject',
|
||||
namePlural: 'testViewSortObjects',
|
||||
labelSingular: 'Test View Sort Object',
|
||||
labelPlural: 'Test View Sort Objects',
|
||||
icon: 'IconSort',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -73,6 +70,13 @@ describe('View Sort REST API', () => {
|
||||
});
|
||||
|
||||
testFieldMetadataId = fieldMetadataId;
|
||||
|
||||
const testView = await createTestViewWithRestApi({
|
||||
name: 'Test View for Sort Integration',
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
});
|
||||
|
||||
testViewId = testView.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
@@ -90,24 +94,18 @@ describe('View Sort REST API', () => {
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await cleanupViewRecords();
|
||||
afterEach(async () => {
|
||||
if (!testViewSortId) return;
|
||||
|
||||
await createTestViewWithRestApi({
|
||||
name: generateRecordName('Test View for Sorts'),
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanupViewRecords();
|
||||
await deleteTestViewSortWithRestApi(testViewSortId);
|
||||
testViewSortId = undefined;
|
||||
});
|
||||
|
||||
describe('GET /metadata/viewSorts', () => {
|
||||
it('should return empty array when no view sorts exist', async () => {
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/viewSorts?viewId=${TEST_VIEW_1_ID}`,
|
||||
path: `/metadata/viewSorts?viewId=${testViewId}`,
|
||||
bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
@@ -128,26 +126,32 @@ describe('View Sort REST API', () => {
|
||||
|
||||
it('should return view sorts for a specific view after creating one', async () => {
|
||||
const viewSort = await createTestViewSortWithRestApi({
|
||||
direction: ViewSortDirection.ASC,
|
||||
viewId: testViewId,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
direction: ViewSortDirection.ASC,
|
||||
});
|
||||
|
||||
testViewSortId = viewSort.id;
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/viewSorts?viewId=${TEST_VIEW_1_ID}`,
|
||||
path: `/metadata/viewSorts?viewId=${testViewId}`,
|
||||
bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
assertRestApiSuccessfulResponse(response);
|
||||
expect(Array.isArray(response.body)).toBe(true);
|
||||
expect(response.body).toHaveLength(1);
|
||||
|
||||
const returnedViewSort = response.body[0];
|
||||
const returnedViewSort = response.body.find(
|
||||
(el: ViewSortDTO) => el.id === viewSort.id,
|
||||
);
|
||||
|
||||
jestExpectToBeDefined(returnedViewSort);
|
||||
|
||||
assertViewSortStructure(returnedViewSort, {
|
||||
id: viewSort.id,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
direction: ViewSortDirection.ASC,
|
||||
});
|
||||
});
|
||||
@@ -156,26 +160,32 @@ describe('View Sort REST API', () => {
|
||||
describe('POST /metadata/viewSorts', () => {
|
||||
it('should create a new view sort with ASC direction', async () => {
|
||||
const viewSort = await createTestViewSortWithRestApi({
|
||||
direction: ViewSortDirection.ASC,
|
||||
viewId: testViewId,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
direction: ViewSortDirection.ASC,
|
||||
});
|
||||
|
||||
testViewSortId = viewSort.id;
|
||||
|
||||
assertViewSortStructure(viewSort, {
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
direction: ViewSortDirection.ASC,
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a view sort with DESC direction', async () => {
|
||||
const descSort = await createTestViewSortWithRestApi({
|
||||
direction: ViewSortDirection.DESC,
|
||||
viewId: testViewId,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
direction: ViewSortDirection.DESC,
|
||||
});
|
||||
|
||||
testViewSortId = descSort.id;
|
||||
|
||||
assertViewSortStructure(descSort, {
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
direction: ViewSortDirection.DESC,
|
||||
});
|
||||
});
|
||||
@@ -184,10 +194,13 @@ describe('View Sort REST API', () => {
|
||||
describe('GET /metadata/viewSorts/:id', () => {
|
||||
it('should return a view sort by id', async () => {
|
||||
const viewSort = await createTestViewSortWithRestApi({
|
||||
direction: ViewSortDirection.ASC,
|
||||
viewId: testViewId,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
direction: ViewSortDirection.ASC,
|
||||
});
|
||||
|
||||
testViewSortId = viewSort.id;
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/viewSorts/${viewSort.id}`,
|
||||
@@ -198,7 +211,7 @@ describe('View Sort REST API', () => {
|
||||
assertViewSortStructure(response.body, {
|
||||
id: viewSort.id,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
direction: ViewSortDirection.ASC,
|
||||
});
|
||||
});
|
||||
@@ -206,7 +219,7 @@ describe('View Sort REST API', () => {
|
||||
it('should return empty object for non-existent view sort', async () => {
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/viewSorts/${TEST_NOT_EXISTING_VIEW_SORT_ID}`,
|
||||
path: `/metadata/viewSorts/20202020-a1b2-4c3d-8e9f-123456789abc`,
|
||||
bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
@@ -217,10 +230,13 @@ describe('View Sort REST API', () => {
|
||||
describe('PATCH /metadata/viewSorts/:id', () => {
|
||||
it('should update an existing view sort', async () => {
|
||||
const viewSort = await createTestViewSortWithRestApi({
|
||||
direction: ViewSortDirection.ASC,
|
||||
viewId: testViewId,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
direction: ViewSortDirection.ASC,
|
||||
});
|
||||
|
||||
testViewSortId = viewSort.id;
|
||||
|
||||
const updateData = {
|
||||
direction: ViewSortDirection.DESC,
|
||||
};
|
||||
@@ -237,7 +253,7 @@ describe('View Sort REST API', () => {
|
||||
id: viewSort.id,
|
||||
direction: ViewSortDirection.DESC,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
viewId: testViewId,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -248,7 +264,7 @@ describe('View Sort REST API', () => {
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'patch',
|
||||
path: `/metadata/viewSorts/${TEST_NOT_EXISTING_VIEW_SORT_ID}`,
|
||||
path: `/metadata/viewSorts/20202020-a1b2-4c3d-8e9f-123456789abc`,
|
||||
body: updateData,
|
||||
bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN,
|
||||
});
|
||||
@@ -260,10 +276,13 @@ describe('View Sort REST API', () => {
|
||||
describe('DELETE /metadata/viewSorts/:id', () => {
|
||||
it('should delete an existing view sort', async () => {
|
||||
const viewSort = await createTestViewSortWithRestApi({
|
||||
direction: ViewSortDirection.ASC,
|
||||
viewId: testViewId,
|
||||
fieldMetadataId: testFieldMetadataId,
|
||||
direction: ViewSortDirection.ASC,
|
||||
});
|
||||
|
||||
testViewSortId = viewSort.id;
|
||||
|
||||
const deleteResponse = await makeRestAPIRequest({
|
||||
method: 'delete',
|
||||
path: `/metadata/viewSorts/${viewSort.id}`,
|
||||
@@ -285,7 +304,7 @@ describe('View Sort REST API', () => {
|
||||
it('should return 404 error when deleting non-existent view sort', async () => {
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'delete',
|
||||
path: `/metadata/viewSorts/${TEST_NOT_EXISTING_VIEW_SORT_ID}`,
|
||||
path: `/metadata/viewSorts/20202020-a1b2-4c3d-8e9f-123456789abc`,
|
||||
bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
@@ -294,7 +313,7 @@ describe('View Sort REST API', () => {
|
||||
404,
|
||||
generateViewSortExceptionMessage(
|
||||
ViewSortExceptionMessageKey.VIEW_SORT_NOT_FOUND,
|
||||
TEST_NOT_EXISTING_VIEW_SORT_ID,
|
||||
'20202020-a1b2-4c3d-8e9f-123456789abc',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
import { TEST_NOT_EXISTING_VIEW_ID } from 'test/integration/constants/test-view-ids.constants';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import { destroyOneCoreView } from 'test/integration/metadata/suites/view/utils/destroy-one-core-view.util';
|
||||
import { makeRestAPIRequest } from 'test/integration/rest/utils/make-rest-api-request.util';
|
||||
import {
|
||||
assertRestApiErrorNotFoundResponse,
|
||||
assertRestApiSuccessfulResponse,
|
||||
} from 'test/integration/rest/utils/rest-test-assertions.util';
|
||||
import {
|
||||
createTestViewWithRestApi,
|
||||
deleteTestViewWithRestApi,
|
||||
} from 'test/integration/rest/utils/view-rest-api.util';
|
||||
import { createTestViewWithRestApi } from 'test/integration/rest/utils/view-rest-api.util';
|
||||
import { generateRecordName } from 'test/integration/utils/generate-record-name';
|
||||
import {
|
||||
assertViewStructure,
|
||||
cleanupViewRecords,
|
||||
} from 'test/integration/utils/view-test.util';
|
||||
import { assertViewStructure } from 'test/integration/utils/view-test.util';
|
||||
|
||||
import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum';
|
||||
import { ViewOpenRecordIn } from 'src/engine/metadata-modules/view/enums/view-open-record-in';
|
||||
@@ -23,6 +17,7 @@ import { ViewType } from 'src/engine/metadata-modules/view/enums/view-type.enum'
|
||||
|
||||
describe('View REST API', () => {
|
||||
let testObjectMetadataId: string;
|
||||
let testViewId: string | undefined;
|
||||
|
||||
beforeAll(async () => {
|
||||
const {
|
||||
@@ -31,11 +26,11 @@ describe('View REST API', () => {
|
||||
},
|
||||
} = await createOneObjectMetadata({
|
||||
input: {
|
||||
nameSingular: 'myTestObject',
|
||||
namePlural: 'myTestObjects',
|
||||
labelSingular: 'My Test Object',
|
||||
labelPlural: 'My Test Objects',
|
||||
icon: 'Icon123',
|
||||
nameSingular: 'testViewObject',
|
||||
namePlural: 'testViewObjects',
|
||||
labelSingular: 'Test View Object',
|
||||
labelPlural: 'Test View Objects',
|
||||
icon: 'IconView',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -57,12 +52,14 @@ describe('View REST API', () => {
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await cleanupViewRecords();
|
||||
});
|
||||
afterEach(async () => {
|
||||
if (!testViewId) return;
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanupViewRecords();
|
||||
await destroyOneCoreView({
|
||||
viewId: testViewId,
|
||||
expectToFail: false,
|
||||
});
|
||||
testViewId = undefined;
|
||||
});
|
||||
|
||||
describe('GET /metadata/views', () => {
|
||||
@@ -107,6 +104,8 @@ describe('View REST API', () => {
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
});
|
||||
|
||||
testViewId = view.id;
|
||||
|
||||
assertViewStructure(view, {
|
||||
name: viewName,
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
@@ -132,6 +131,8 @@ describe('View REST API', () => {
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
});
|
||||
|
||||
testViewId = kanbanView.id;
|
||||
|
||||
assertViewStructure(kanbanView, {
|
||||
name: viewName,
|
||||
type: ViewType.KANBAN,
|
||||
@@ -139,8 +140,6 @@ describe('View REST API', () => {
|
||||
openRecordIn: ViewOpenRecordIn.SIDE_PANEL,
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
});
|
||||
|
||||
await deleteTestViewWithRestApi(kanbanView.id);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -158,6 +157,8 @@ describe('View REST API', () => {
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
});
|
||||
|
||||
testViewId = view.id;
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/views/${view.id}`,
|
||||
@@ -175,7 +176,7 @@ describe('View REST API', () => {
|
||||
it('should return empty object for non-existent view', async () => {
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/views/${TEST_NOT_EXISTING_VIEW_ID}`,
|
||||
path: `/metadata/views/20202020-b7a4-4f8e-9c1d-2e3f4a5b6c7d`,
|
||||
bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
@@ -197,6 +198,8 @@ describe('View REST API', () => {
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
});
|
||||
|
||||
testViewId = view.id;
|
||||
|
||||
const updatedName = generateRecordName('Updated View');
|
||||
const updateData = {
|
||||
name: updatedName,
|
||||
@@ -231,7 +234,7 @@ describe('View REST API', () => {
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'patch',
|
||||
path: `/metadata/views/${TEST_NOT_EXISTING_VIEW_ID}`,
|
||||
path: `/metadata/views/20202020-b7a4-4f8e-9c1d-2e3f4a5b6c7d`,
|
||||
body: updateData,
|
||||
bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN,
|
||||
});
|
||||
@@ -254,6 +257,8 @@ describe('View REST API', () => {
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
});
|
||||
|
||||
testViewId = view.id;
|
||||
|
||||
const deleteResponse = await makeRestAPIRequest({
|
||||
method: 'delete',
|
||||
path: `/metadata/views/${view.id}`,
|
||||
@@ -275,7 +280,7 @@ describe('View REST API', () => {
|
||||
it('should return 404 error when deleting non-existent view', async () => {
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'delete',
|
||||
path: `/metadata/views/${TEST_NOT_EXISTING_VIEW_ID}`,
|
||||
path: `/metadata/views/20202020-b7a4-4f8e-9c1d-2e3f4a5b6c7d`,
|
||||
bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
import {
|
||||
TEST_FIELD_METADATA_1_ID,
|
||||
TEST_OBJECT_METADATA_1_ID,
|
||||
TEST_VIEW_1_ID,
|
||||
} from 'test/integration/constants/test-view-ids.constants';
|
||||
import { makeRestAPIRequest } from 'test/integration/rest/utils/make-rest-api-request.util';
|
||||
import { generateRecordName } from 'test/integration/utils/generate-record-name';
|
||||
|
||||
@@ -48,19 +43,21 @@ export const findViewFilterWithRestApi = async (
|
||||
};
|
||||
|
||||
export const createTestViewWithRestApi = async (
|
||||
overrides: Partial<ViewEntity> = {},
|
||||
params: {
|
||||
objectMetadataId: string;
|
||||
} & Partial<Omit<ViewEntity, 'objectMetadataId'>>,
|
||||
): Promise<ViewEntity> => {
|
||||
const { objectMetadataId, name, ...restParams } = params;
|
||||
const viewData = {
|
||||
id: TEST_VIEW_1_ID,
|
||||
name: generateRecordName('Test View'),
|
||||
objectMetadataId: TEST_OBJECT_METADATA_1_ID,
|
||||
name: name || generateRecordName('Test View'),
|
||||
objectMetadataId,
|
||||
icon: 'IconTable',
|
||||
type: ViewType.TABLE,
|
||||
key: 'INDEX',
|
||||
position: 0,
|
||||
isCompact: false,
|
||||
openRecordIn: ViewOpenRecordIn.SIDE_PANEL,
|
||||
...overrides,
|
||||
...restParams,
|
||||
};
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
@@ -80,15 +77,19 @@ export const createTestViewWithRestApi = async (
|
||||
};
|
||||
|
||||
export const createTestViewFieldWithRestApi = async (
|
||||
overrides: Partial<ViewFieldEntity> = {},
|
||||
params: {
|
||||
viewId: string;
|
||||
fieldMetadataId: string;
|
||||
} & Partial<Omit<ViewFieldEntity, 'viewId' | 'fieldMetadataId'>>,
|
||||
): Promise<ViewFieldEntity> => {
|
||||
const { viewId, fieldMetadataId, ...restParams } = params;
|
||||
const viewFieldData = {
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
fieldMetadataId: TEST_FIELD_METADATA_1_ID,
|
||||
viewId,
|
||||
fieldMetadataId,
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
...overrides,
|
||||
...restParams,
|
||||
};
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
@@ -108,14 +109,18 @@ export const createTestViewFieldWithRestApi = async (
|
||||
};
|
||||
|
||||
export const createTestViewFilterWithRestApi = async (
|
||||
overrides: Partial<ViewFilterEntity> = {},
|
||||
params: {
|
||||
viewId: string;
|
||||
fieldMetadataId: string;
|
||||
} & Partial<Omit<ViewFilterEntity, 'viewId' | 'fieldMetadataId'>>,
|
||||
): Promise<ViewFilterEntity> => {
|
||||
const { viewId, fieldMetadataId, operand, value, ...restParams } = params;
|
||||
const viewFilterData = {
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
fieldMetadataId: TEST_FIELD_METADATA_1_ID,
|
||||
operand: 'Is',
|
||||
value: 'test-value',
|
||||
...overrides,
|
||||
viewId,
|
||||
fieldMetadataId,
|
||||
operand: operand || 'Is',
|
||||
value: value || 'test-value',
|
||||
...restParams,
|
||||
};
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
@@ -135,13 +140,17 @@ export const createTestViewFilterWithRestApi = async (
|
||||
};
|
||||
|
||||
export const createTestViewSortWithRestApi = async (
|
||||
overrides: Partial<ViewSortEntity> = {},
|
||||
params: {
|
||||
viewId: string;
|
||||
fieldMetadataId: string;
|
||||
} & Partial<Omit<ViewSortEntity, 'viewId' | 'fieldMetadataId'>>,
|
||||
): Promise<ViewSortEntity> => {
|
||||
const { viewId, fieldMetadataId, direction, ...restParams } = params;
|
||||
const viewSortData = {
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
fieldMetadataId: TEST_FIELD_METADATA_1_ID,
|
||||
direction: 'ASC',
|
||||
...overrides,
|
||||
viewId,
|
||||
fieldMetadataId,
|
||||
direction: direction || 'ASC',
|
||||
...restParams,
|
||||
};
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
@@ -161,15 +170,19 @@ export const createTestViewSortWithRestApi = async (
|
||||
};
|
||||
|
||||
export const createTestViewGroupWithRestApi = async (
|
||||
overrides: Partial<ViewGroupEntity> = {},
|
||||
params: {
|
||||
viewId: string;
|
||||
fieldMetadataId: string;
|
||||
} & Partial<Omit<ViewGroupEntity, 'viewId' | 'fieldMetadataId'>>,
|
||||
): Promise<ViewGroupEntity> => {
|
||||
const { viewId, fieldMetadataId, fieldValue, ...restParams } = params;
|
||||
const viewGroupData = {
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
fieldMetadataId: TEST_FIELD_METADATA_1_ID,
|
||||
viewId,
|
||||
fieldMetadataId,
|
||||
isVisible: true,
|
||||
fieldValue: 'test-group-value',
|
||||
fieldValue: fieldValue || 'test-group-value',
|
||||
position: 0,
|
||||
...overrides,
|
||||
...restParams,
|
||||
};
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
@@ -189,12 +202,15 @@ export const createTestViewGroupWithRestApi = async (
|
||||
};
|
||||
|
||||
export const createTestViewFilterGroupWithRestApi = async (
|
||||
overrides: Partial<ViewFilterGroupEntity> = {},
|
||||
params: {
|
||||
viewId: string;
|
||||
} & Partial<Omit<ViewFilterGroupEntity, 'viewId'>>,
|
||||
): Promise<ViewFilterGroupEntity> => {
|
||||
const { viewId, logicalOperator, ...restParams } = params;
|
||||
const viewFilterGroupData = {
|
||||
viewId: TEST_VIEW_1_ID,
|
||||
logicalOperator: 'AND',
|
||||
...overrides,
|
||||
viewId,
|
||||
logicalOperator: logicalOperator || 'AND',
|
||||
...restParams,
|
||||
};
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
|
||||
@@ -6,10 +6,6 @@ import { type ViewGroupEntity } from 'src/engine/metadata-modules/view-group/ent
|
||||
import { type ViewSortEntity } from 'src/engine/metadata-modules/view-sort/entities/view-sort.entity';
|
||||
import { type ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
|
||||
|
||||
export const cleanupViewRecords = async (): Promise<void> => {
|
||||
await global.testDataSource.query(`DELETE from "core"."view"`);
|
||||
};
|
||||
|
||||
export const assertViewStructure = (
|
||||
view: ViewEntity,
|
||||
expectedFields?: Partial<ViewEntity>,
|
||||
|
||||
Reference in New Issue
Block a user