Create many view groups (#15591)
# Introduction Same as https://github.com/twentyhq/twenty/pull/15576 but for view groups creation When creating a kanban view with v2 flag activated in production result in race condition due to request being slow and //. That's why we're introducing a batch create on view group here closing https://github.com/twentyhq/core-team-issues/issues/1847 ## In v2 - batch create view group endpoint is available - frontend will target the new endpoint ## In v1 - batch create view group endpoint is not available - frontend will stick to old fake batch view group creation loop ## Gallery ### v2 <img width="1796" height="486" alt="image" src="https://github.com/user-attachments/assets/932cfe9f-85f1-41cc-a1c4-72a4b5d5a256" /> ### v1 <img width="1852" height="966" alt="image" src="https://github.com/user-attachments/assets/8aa9df11-cdea-4b12-ae60-118cdf5e257b" />
This commit is contained in:
@@ -63,7 +63,7 @@ const jestConfig = {
|
||||
global: {
|
||||
statements: 52,
|
||||
// Temporarily decreasing to 50.97 as introduced v1 code that aims to be deleted
|
||||
lines: 50.97,
|
||||
lines: 50.95,
|
||||
functions: 41,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1758,6 +1758,7 @@ export type Mutation = {
|
||||
createEmailingDomain: EmailingDomain;
|
||||
createFile: File;
|
||||
createManyCoreViewFields: Array<CoreViewField>;
|
||||
createManyCoreViewGroups: Array<CoreViewGroup>;
|
||||
createOIDCIdentityProvider: SetupSsoOutput;
|
||||
createObjectEvent: Analytics;
|
||||
createOneAgent: Agent;
|
||||
@@ -2036,6 +2037,11 @@ export type MutationCreateManyCoreViewFieldsArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateManyCoreViewGroupsArgs = {
|
||||
inputs: Array<CreateViewGroupInput>;
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateOidcIdentityProviderArgs = {
|
||||
input: SetupOidcSsoInput;
|
||||
};
|
||||
@@ -5979,6 +5985,13 @@ export type CreateManyCoreViewFieldsMutationVariables = Exact<{
|
||||
|
||||
export type CreateManyCoreViewFieldsMutation = { __typename?: 'Mutation', createManyCoreViewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }> };
|
||||
|
||||
export type CreateManyCoreViewGroupsMutationVariables = Exact<{
|
||||
inputs: Array<CreateViewGroupInput> | CreateViewGroupInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateManyCoreViewGroupsMutation = { __typename?: 'Mutation', createManyCoreViewGroups: Array<{ __typename?: 'CoreViewGroup', id: string, fieldMetadataId: string, isVisible: boolean, fieldValue: string, position: number, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }> };
|
||||
|
||||
export type DeleteCoreViewMutationVariables = Exact<{
|
||||
id: Scalars['String'];
|
||||
}>;
|
||||
@@ -12668,6 +12681,39 @@ export function useCreateManyCoreViewFieldsMutation(baseOptions?: Apollo.Mutatio
|
||||
export type CreateManyCoreViewFieldsMutationHookResult = ReturnType<typeof useCreateManyCoreViewFieldsMutation>;
|
||||
export type CreateManyCoreViewFieldsMutationResult = Apollo.MutationResult<CreateManyCoreViewFieldsMutation>;
|
||||
export type CreateManyCoreViewFieldsMutationOptions = Apollo.BaseMutationOptions<CreateManyCoreViewFieldsMutation, CreateManyCoreViewFieldsMutationVariables>;
|
||||
export const CreateManyCoreViewGroupsDocument = gql`
|
||||
mutation CreateManyCoreViewGroups($inputs: [CreateViewGroupInput!]!) {
|
||||
createManyCoreViewGroups(inputs: $inputs) {
|
||||
...ViewGroupFragment
|
||||
}
|
||||
}
|
||||
${ViewGroupFragmentFragmentDoc}`;
|
||||
export type CreateManyCoreViewGroupsMutationFn = Apollo.MutationFunction<CreateManyCoreViewGroupsMutation, CreateManyCoreViewGroupsMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useCreateManyCoreViewGroupsMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useCreateManyCoreViewGroupsMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useCreateManyCoreViewGroupsMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [createManyCoreViewGroupsMutation, { data, loading, error }] = useCreateManyCoreViewGroupsMutation({
|
||||
* variables: {
|
||||
* inputs: // value for 'inputs'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useCreateManyCoreViewGroupsMutation(baseOptions?: Apollo.MutationHookOptions<CreateManyCoreViewGroupsMutation, CreateManyCoreViewGroupsMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<CreateManyCoreViewGroupsMutation, CreateManyCoreViewGroupsMutationVariables>(CreateManyCoreViewGroupsDocument, options);
|
||||
}
|
||||
export type CreateManyCoreViewGroupsMutationHookResult = ReturnType<typeof useCreateManyCoreViewGroupsMutation>;
|
||||
export type CreateManyCoreViewGroupsMutationResult = Apollo.MutationResult<CreateManyCoreViewGroupsMutation>;
|
||||
export type CreateManyCoreViewGroupsMutationOptions = Apollo.BaseMutationOptions<CreateManyCoreViewGroupsMutation, CreateManyCoreViewGroupsMutationVariables>;
|
||||
export const DeleteCoreViewDocument = gql`
|
||||
mutation DeleteCoreView($id: String!) {
|
||||
deleteCoreView(id: $id)
|
||||
|
||||
@@ -1715,6 +1715,7 @@ export type Mutation = {
|
||||
createEmailingDomain: EmailingDomain;
|
||||
createFile: File;
|
||||
createManyCoreViewFields: Array<CoreViewField>;
|
||||
createManyCoreViewGroups: Array<CoreViewGroup>;
|
||||
createOIDCIdentityProvider: SetupSsoOutput;
|
||||
createObjectEvent: Analytics;
|
||||
createOneAgent: Agent;
|
||||
@@ -1987,6 +1988,11 @@ export type MutationCreateManyCoreViewFieldsArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateManyCoreViewGroupsArgs = {
|
||||
inputs: Array<CreateViewGroupInput>;
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateOidcIdentityProviderArgs = {
|
||||
input: SetupOidcSsoInput;
|
||||
};
|
||||
@@ -4789,6 +4795,13 @@ export type CreateManyCoreViewFieldsMutationVariables = Exact<{
|
||||
|
||||
export type CreateManyCoreViewFieldsMutation = { __typename?: 'Mutation', createManyCoreViewFields: Array<{ __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }> };
|
||||
|
||||
export type CreateManyCoreViewGroupsMutationVariables = Exact<{
|
||||
inputs: Array<CreateViewGroupInput> | CreateViewGroupInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateManyCoreViewGroupsMutation = { __typename?: 'Mutation', createManyCoreViewGroups: Array<{ __typename?: 'CoreViewGroup', id: any, fieldMetadataId: any, isVisible: boolean, fieldValue: string, position: number, viewId: any, createdAt: string, updatedAt: string, deletedAt?: string | null }> };
|
||||
|
||||
export type DeleteCoreViewMutationVariables = Exact<{
|
||||
id: Scalars['String'];
|
||||
}>;
|
||||
@@ -5588,6 +5601,39 @@ export function useCreateManyCoreViewFieldsMutation(baseOptions?: Apollo.Mutatio
|
||||
export type CreateManyCoreViewFieldsMutationHookResult = ReturnType<typeof useCreateManyCoreViewFieldsMutation>;
|
||||
export type CreateManyCoreViewFieldsMutationResult = Apollo.MutationResult<CreateManyCoreViewFieldsMutation>;
|
||||
export type CreateManyCoreViewFieldsMutationOptions = Apollo.BaseMutationOptions<CreateManyCoreViewFieldsMutation, CreateManyCoreViewFieldsMutationVariables>;
|
||||
export const CreateManyCoreViewGroupsDocument = gql`
|
||||
mutation CreateManyCoreViewGroups($inputs: [CreateViewGroupInput!]!) {
|
||||
createManyCoreViewGroups(inputs: $inputs) {
|
||||
...ViewGroupFragment
|
||||
}
|
||||
}
|
||||
${ViewGroupFragmentFragmentDoc}`;
|
||||
export type CreateManyCoreViewGroupsMutationFn = Apollo.MutationFunction<CreateManyCoreViewGroupsMutation, CreateManyCoreViewGroupsMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useCreateManyCoreViewGroupsMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useCreateManyCoreViewGroupsMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useCreateManyCoreViewGroupsMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [createManyCoreViewGroupsMutation, { data, loading, error }] = useCreateManyCoreViewGroupsMutation({
|
||||
* variables: {
|
||||
* inputs: // value for 'inputs'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useCreateManyCoreViewGroupsMutation(baseOptions?: Apollo.MutationHookOptions<CreateManyCoreViewGroupsMutation, CreateManyCoreViewGroupsMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<CreateManyCoreViewGroupsMutation, CreateManyCoreViewGroupsMutationVariables>(CreateManyCoreViewGroupsDocument, options);
|
||||
}
|
||||
export type CreateManyCoreViewGroupsMutationHookResult = ReturnType<typeof useCreateManyCoreViewGroupsMutation>;
|
||||
export type CreateManyCoreViewGroupsMutationResult = Apollo.MutationResult<CreateManyCoreViewGroupsMutation>;
|
||||
export type CreateManyCoreViewGroupsMutationOptions = Apollo.BaseMutationOptions<CreateManyCoreViewGroupsMutation, CreateManyCoreViewGroupsMutationVariables>;
|
||||
export const DeleteCoreViewDocument = gql`
|
||||
mutation DeleteCoreView($id: String!) {
|
||||
deleteCoreView(id: $id)
|
||||
|
||||
+5
-7
@@ -58,14 +58,12 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => {
|
||||
fieldMetadataId: randomFieldForKanban,
|
||||
} satisfies ViewGroup);
|
||||
|
||||
await createViewGroups(
|
||||
viewGroupsToCreate.map(({ __typename, ...viewGroup }) => ({
|
||||
input: {
|
||||
...viewGroup,
|
||||
viewId: currentViewId,
|
||||
},
|
||||
await createViewGroups({
|
||||
inputs: viewGroupsToCreate.map(({ __typename, ...viewGroup }) => ({
|
||||
...viewGroup,
|
||||
viewId: currentViewId,
|
||||
})),
|
||||
);
|
||||
});
|
||||
|
||||
return viewGroupsToCreate;
|
||||
},
|
||||
|
||||
+5
-7
@@ -105,14 +105,12 @@ export const useHandleRecordGroupField = () => {
|
||||
);
|
||||
|
||||
if (viewGroupsToCreate.length > 0) {
|
||||
await createViewGroups(
|
||||
viewGroupsToCreate.map(({ __typename, ...viewGroup }) => ({
|
||||
input: {
|
||||
...viewGroup,
|
||||
viewId: view.id,
|
||||
},
|
||||
await createViewGroups({
|
||||
inputs: viewGroupsToCreate.map(({ __typename, ...viewGroup }) => ({
|
||||
...viewGroup,
|
||||
viewId: view.id,
|
||||
})),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (viewGroupsToDelete.length > 0) {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { VIEW_GROUP_FRAGMENT } from '@/views/graphql/fragments/viewGroupFragment';
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const CREATE_MANY_CORE_VIEW_GROUPS = gql`
|
||||
${VIEW_GROUP_FRAGMENT}
|
||||
mutation CreateManyCoreViewGroups($inputs: [CreateViewGroupInput!]!) {
|
||||
createManyCoreViewGroups(inputs: $inputs) {
|
||||
...ViewGroupFragment
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -4,15 +4,19 @@ import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetad
|
||||
import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useTriggerViewGroupOptimisticEffect } from '@/views/optimistic-effects/hooks/useTriggerViewGroupOptimisticEffect';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
type CreateCoreViewGroupMutationVariables,
|
||||
type CreateManyCoreViewGroupsMutationVariables,
|
||||
type DeleteCoreViewGroupMutationVariables,
|
||||
type DestroyCoreViewGroupMutationVariables,
|
||||
FeatureFlagKey,
|
||||
type UpdateCoreViewGroupMutationVariables,
|
||||
useCreateCoreViewGroupMutation,
|
||||
useCreateManyCoreViewGroupsMutation,
|
||||
useDeleteCoreViewGroupMutation,
|
||||
useDestroyCoreViewGroupMutation,
|
||||
useUpdateCoreViewGroupMutation,
|
||||
@@ -21,7 +25,14 @@ import {
|
||||
export const usePersistViewGroupRecords = () => {
|
||||
const { triggerViewGroupOptimisticEffect } =
|
||||
useTriggerViewGroupOptimisticEffect();
|
||||
|
||||
const isWorkspaceMigrationV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
|
||||
);
|
||||
|
||||
const [createCoreViewGroupMutation] = useCreateCoreViewGroupMutation();
|
||||
const [createManyCoreViewGroupsMutation] =
|
||||
useCreateManyCoreViewGroupsMutation();
|
||||
const [updateCoreViewGroupMutation] = useUpdateCoreViewGroupMutation();
|
||||
const [deleteCoreViewGroupMutation] = useDeleteCoreViewGroupMutation();
|
||||
const [destroyCoreViewGroupMutation] = useDestroyCoreViewGroupMutation();
|
||||
@@ -29,7 +40,7 @@ export const usePersistViewGroupRecords = () => {
|
||||
const { handleMetadataError } = useMetadataErrorHandler();
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
|
||||
const createViewGroups = useCallback(
|
||||
const oldCreateViewGroups = useCallback(
|
||||
async (
|
||||
createCoreViewGroupInputs: CreateCoreViewGroupMutationVariables[],
|
||||
): Promise<
|
||||
@@ -46,7 +57,7 @@ export const usePersistViewGroupRecords = () => {
|
||||
|
||||
try {
|
||||
const results = await Promise.all(
|
||||
createCoreViewGroupInputs.map((variables) =>
|
||||
createCoreViewGroupInputs.map(async (variables) =>
|
||||
createCoreViewGroupMutation({
|
||||
variables,
|
||||
update: (_cache, { data }) => {
|
||||
@@ -90,6 +101,77 @@ export const usePersistViewGroupRecords = () => {
|
||||
],
|
||||
);
|
||||
|
||||
const createViewGroups = useCallback(
|
||||
async (
|
||||
createCoreViewGroupInputs: CreateManyCoreViewGroupsMutationVariables,
|
||||
): Promise<
|
||||
| MetadataRequestResult<Awaited<
|
||||
ReturnType<typeof createManyCoreViewGroupsMutation>
|
||||
> | null>
|
||||
| MetadataRequestResult<
|
||||
Awaited<ReturnType<typeof createCoreViewGroupMutation>>[]
|
||||
>
|
||||
> => {
|
||||
if (
|
||||
!Array.isArray(createCoreViewGroupInputs.inputs) ||
|
||||
createCoreViewGroupInputs.inputs.length === 0
|
||||
) {
|
||||
return {
|
||||
status: 'successful',
|
||||
response: null,
|
||||
};
|
||||
}
|
||||
|
||||
if (!isWorkspaceMigrationV2Enabled) {
|
||||
const oldFormatInputs: CreateCoreViewGroupMutationVariables[] =
|
||||
createCoreViewGroupInputs.inputs.map((input) => ({ input }));
|
||||
return await oldCreateViewGroups(oldFormatInputs);
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await createManyCoreViewGroupsMutation({
|
||||
variables: createCoreViewGroupInputs,
|
||||
update: (_cache, { data }) => {
|
||||
const createdViewGroups = data?.createManyCoreViewGroups;
|
||||
if (!isDefined(createdViewGroups)) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerViewGroupOptimisticEffect({
|
||||
createdViewGroups,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
status: 'successful',
|
||||
response: result,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApolloError) {
|
||||
handleMetadataError(error, {
|
||||
primaryMetadataName: 'viewGroup',
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({ message: t`An error occurred.` });
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'failed',
|
||||
error,
|
||||
};
|
||||
}
|
||||
},
|
||||
[
|
||||
isWorkspaceMigrationV2Enabled,
|
||||
oldCreateViewGroups,
|
||||
triggerViewGroupOptimisticEffect,
|
||||
createManyCoreViewGroupsMutation,
|
||||
handleMetadataError,
|
||||
enqueueErrorSnackBar,
|
||||
],
|
||||
);
|
||||
|
||||
const updateViewGroups = useCallback(
|
||||
async (
|
||||
updateCoreViewGroupInputs: UpdateCoreViewGroupMutationVariables[],
|
||||
|
||||
@@ -204,14 +204,12 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
|
||||
fieldMetadataId: kanbanFieldMetadataId,
|
||||
} satisfies ViewGroup);
|
||||
|
||||
const groupResult = await createViewGroups(
|
||||
viewGroupsToCreate.map(({ __typename, ...viewGroup }) => ({
|
||||
input: {
|
||||
...viewGroup,
|
||||
viewId: newViewId,
|
||||
},
|
||||
const groupResult = await createViewGroups({
|
||||
inputs: viewGroupsToCreate.map(({ __typename, ...viewGroup }) => ({
|
||||
...viewGroup,
|
||||
viewId: newViewId,
|
||||
})),
|
||||
);
|
||||
});
|
||||
|
||||
if (groupResult.status === 'failed') {
|
||||
set(isPersistingViewFieldsState, false);
|
||||
|
||||
@@ -146,14 +146,12 @@ export const useSaveCurrentViewGroups = () => {
|
||||
);
|
||||
|
||||
await Promise.all([
|
||||
createViewGroups(
|
||||
viewGroupsToCreate.map(({ __typename, ...viewGroup }) => ({
|
||||
input: {
|
||||
...viewGroup,
|
||||
viewId: view.id,
|
||||
},
|
||||
createViewGroups({
|
||||
inputs: viewGroupsToCreate.map(({ __typename, ...viewGroup }) => ({
|
||||
...viewGroup,
|
||||
viewId: view.id,
|
||||
})),
|
||||
),
|
||||
}),
|
||||
updateViewGroups(viewGroupsToUpdate),
|
||||
]);
|
||||
},
|
||||
|
||||
+29
@@ -11,6 +11,10 @@ import { DeleteViewGroupInput } from 'src/engine/metadata-modules/view-group/dto
|
||||
import { DestroyViewGroupInput } from 'src/engine/metadata-modules/view-group/dtos/inputs/destroy-view-group.input';
|
||||
import { UpdateViewGroupInput } from 'src/engine/metadata-modules/view-group/dtos/inputs/update-view-group.input';
|
||||
import { ViewGroupDTO } from 'src/engine/metadata-modules/view-group/dtos/view-group.dto';
|
||||
import {
|
||||
ViewGroupException,
|
||||
ViewGroupExceptionCode,
|
||||
} from 'src/engine/metadata-modules/view-group/exceptions/view-group.exception';
|
||||
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';
|
||||
import { ViewGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/view/utils/view-graphql-api-exception.filter';
|
||||
@@ -70,6 +74,31 @@ export class ViewGroupResolver {
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => [ViewGroupDTO])
|
||||
async createManyCoreViewGroups(
|
||||
@Args('inputs', { type: () => [CreateViewGroupInput] })
|
||||
createViewGroupInputs: CreateViewGroupInput[],
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
): Promise<ViewGroupDTO[]> {
|
||||
const isWorkspaceMigrationV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (!isWorkspaceMigrationV2Enabled) {
|
||||
throw new ViewGroupException(
|
||||
'Not implemented in v1, please active IS_WORKSPACE_MIGRATION_V2_ENABLED',
|
||||
ViewGroupExceptionCode.INVALID_VIEW_GROUP_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
return await this.viewGroupV2Service.createMany({
|
||||
createViewGroupInputs,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => ViewGroupDTO)
|
||||
async updateCoreViewGroup(
|
||||
@Args('input') updateViewGroupInput: UpdateViewGroupInput,
|
||||
|
||||
+42
-9
@@ -5,6 +5,7 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { computeFlatEntityMapsFromTo } from 'src/engine/metadata-modules/flat-entity/utils/compute-flat-entity-maps-from-to.util';
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { fromCreateViewGroupInputToFlatViewGroupToCreate } from 'src/engine/metadata-modules/flat-view-group/utils/from-create-view-group-input-to-flat-view-group-to-create.util';
|
||||
import { fromDeleteViewGroupInputToFlatViewGroupOrThrow } from 'src/engine/metadata-modules/flat-view-group/utils/from-delete-view-group-input-to-flat-view-group-or-throw.util';
|
||||
import { fromDestroyViewGroupInputToFlatViewGroupOrThrow } from 'src/engine/metadata-modules/flat-view-group/utils/from-destroy-view-group-input-to-flat-view-group-or-throw.util';
|
||||
@@ -14,6 +15,10 @@ import { DeleteViewGroupInput } from 'src/engine/metadata-modules/view-group/dto
|
||||
import { DestroyViewGroupInput } from 'src/engine/metadata-modules/view-group/dtos/inputs/destroy-view-group.input';
|
||||
import { UpdateViewGroupInput } from 'src/engine/metadata-modules/view-group/dtos/inputs/update-view-group.input';
|
||||
import { ViewGroupDTO } from 'src/engine/metadata-modules/view-group/dtos/view-group.dto';
|
||||
import {
|
||||
ViewGroupException,
|
||||
ViewGroupExceptionCode,
|
||||
} from 'src/engine/metadata-modules/view-group/exceptions/view-group.exception';
|
||||
import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service';
|
||||
|
||||
@@ -31,6 +36,32 @@ export class ViewGroupV2Service {
|
||||
createViewGroupInput: CreateViewGroupInput;
|
||||
workspaceId: string;
|
||||
}): Promise<ViewGroupDTO> {
|
||||
const [createdViewGroup] = await this.createMany({
|
||||
workspaceId,
|
||||
createViewGroupInputs: [createViewGroupInput],
|
||||
});
|
||||
|
||||
if (!isDefined(createdViewGroup)) {
|
||||
throw new ViewGroupException(
|
||||
'Failed to create view group',
|
||||
ViewGroupExceptionCode.INVALID_VIEW_GROUP_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
return createdViewGroup;
|
||||
}
|
||||
|
||||
async createMany({
|
||||
createViewGroupInputs,
|
||||
workspaceId,
|
||||
}: {
|
||||
createViewGroupInputs: CreateViewGroupInput[];
|
||||
workspaceId: string;
|
||||
}): Promise<ViewGroupDTO[]> {
|
||||
if (createViewGroupInputs.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const {
|
||||
flatViewGroupMaps: existingFlatViewGroupMaps,
|
||||
flatViewMaps,
|
||||
@@ -46,11 +77,13 @@ export class ViewGroupV2Service {
|
||||
},
|
||||
);
|
||||
|
||||
const flatViewGroupToCreate =
|
||||
fromCreateViewGroupInputToFlatViewGroupToCreate({
|
||||
createViewGroupInput,
|
||||
workspaceId,
|
||||
});
|
||||
const flatViewGroupsToCreate = createViewGroupInputs.map(
|
||||
(createViewGroupInput) =>
|
||||
fromCreateViewGroupInputToFlatViewGroupToCreate({
|
||||
createViewGroupInput,
|
||||
workspaceId,
|
||||
}),
|
||||
);
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
@@ -58,7 +91,7 @@ export class ViewGroupV2Service {
|
||||
fromToAllFlatEntityMaps: {
|
||||
flatViewGroupMaps: computeFlatEntityMapsFromTo({
|
||||
flatEntityMaps: existingFlatViewGroupMaps,
|
||||
flatEntityToCreate: [flatViewGroupToCreate],
|
||||
flatEntityToCreate: flatViewGroupsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
}),
|
||||
@@ -77,7 +110,7 @@ export class ViewGroupV2Service {
|
||||
if (isDefined(validateAndBuildResult)) {
|
||||
throw new WorkspaceMigrationBuilderExceptionV2(
|
||||
validateAndBuildResult,
|
||||
'Multiple validation errors occurred while creating view group',
|
||||
'Multiple validation errors occurred while creating view groups',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -89,8 +122,8 @@ export class ViewGroupV2Service {
|
||||
},
|
||||
);
|
||||
|
||||
return findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: flatViewGroupToCreate.id,
|
||||
return findManyFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityIds: flatViewGroupsToCreate.map((el) => el.id),
|
||||
flatEntityMaps: recomputedExistingFlatViewGroupMaps,
|
||||
});
|
||||
}
|
||||
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`View Group Resolver - Failing Create Many Operations - v2 should accumulate multiple validation errors when some inputs are invalid 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "METADATA_VALIDATION_FAILED",
|
||||
"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": {
|
||||
"fieldMetadataId": Any<String>,
|
||||
"id": Any<String>,
|
||||
"viewId": Any<String>,
|
||||
},
|
||||
"status": "fail",
|
||||
"type": "create_view_group",
|
||||
},
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"code": "INVALID_VIEW_DATA",
|
||||
"message": "View not found",
|
||||
"userFriendlyMessage": "View not found",
|
||||
},
|
||||
],
|
||||
"flatEntityMinimalInformation": {
|
||||
"fieldMetadataId": Any<String>,
|
||||
"id": Any<String>,
|
||||
"viewId": Any<String>,
|
||||
},
|
||||
"status": "fail",
|
||||
"type": "create_view_group",
|
||||
},
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"code": "INVALID_VIEW_DATA",
|
||||
"message": "Field metadata not found",
|
||||
"userFriendlyMessage": "Field metadata not found",
|
||||
},
|
||||
{
|
||||
"code": "INVALID_VIEW_DATA",
|
||||
"message": "View not found",
|
||||
"userFriendlyMessage": "View not found",
|
||||
},
|
||||
],
|
||||
"flatEntityMinimalInformation": {
|
||||
"fieldMetadataId": Any<String>,
|
||||
"id": Any<String>,
|
||||
"viewId": Any<String>,
|
||||
},
|
||||
"status": "fail",
|
||||
"type": "create_view_group",
|
||||
},
|
||||
],
|
||||
},
|
||||
"message": "Validation failed for 0 object(s) and 0 field(s)",
|
||||
"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,
|
||||
},
|
||||
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
|
||||
},
|
||||
"message": "Multiple validation errors occurred while creating view groups",
|
||||
"name": "GraphQLError",
|
||||
}
|
||||
`;
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
import { createManyCoreViewGroups } from 'test/integration/metadata/suites/view-group/utils/create-many-core-view-groups.util';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util';
|
||||
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 { createOneCoreView } from 'test/integration/metadata/suites/view/utils/create-one-core-view.util';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type CreateViewGroupInput } from 'src/engine/metadata-modules/view-group/dtos/inputs/create-view-group.input';
|
||||
|
||||
describe('View Group Resolver - Failing Create Many Operations - v2', () => {
|
||||
let testSetup: {
|
||||
testViewId: string;
|
||||
testObjectMetadataId: string;
|
||||
firstTestFieldMetadataId: string;
|
||||
secondTestFieldMetadataId: string;
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
const {
|
||||
data: {
|
||||
createOneObject: { id: objectMetadataId },
|
||||
},
|
||||
} = await createOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
nameSingular: 'myGroupTestObjectV2',
|
||||
namePlural: 'myGroupTestObjectsV2',
|
||||
labelSingular: 'My Group Test Object v2',
|
||||
labelPlural: 'My Group Test Objects v2',
|
||||
icon: 'Icon123',
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
data: {
|
||||
createOneField: { id: firstTestFieldMetadataId },
|
||||
},
|
||||
} = await createOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
name: 'testField',
|
||||
label: 'Test Field',
|
||||
type: FieldMetadataType.TEXT,
|
||||
objectMetadataId,
|
||||
isLabelSyncedWithName: true,
|
||||
},
|
||||
gqlFields: `
|
||||
id
|
||||
name
|
||||
label
|
||||
isLabelSyncedWithName
|
||||
`,
|
||||
});
|
||||
|
||||
const {
|
||||
data: {
|
||||
createOneField: { id: secondTestFieldMetadataId },
|
||||
},
|
||||
} = await createOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
name: 'secondTestField',
|
||||
label: 'Test Field',
|
||||
type: FieldMetadataType.TEXT,
|
||||
objectMetadataId,
|
||||
isLabelSyncedWithName: false,
|
||||
},
|
||||
gqlFields: `
|
||||
id
|
||||
name
|
||||
label
|
||||
isLabelSyncedWithName
|
||||
`,
|
||||
});
|
||||
|
||||
const {
|
||||
data: {
|
||||
createCoreView: { id: testViewId },
|
||||
},
|
||||
} = await createOneCoreView({
|
||||
input: {
|
||||
icon: 'icon123',
|
||||
objectMetadataId,
|
||||
name: 'TestViewForGroups',
|
||||
},
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
testSetup = {
|
||||
testViewId,
|
||||
testObjectMetadataId: objectMetadataId,
|
||||
firstTestFieldMetadataId,
|
||||
secondTestFieldMetadataId,
|
||||
};
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await updateOneObjectMetadata({
|
||||
input: {
|
||||
idToUpdate: testSetup.testObjectMetadataId,
|
||||
updatePayload: {
|
||||
isActive: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
await deleteOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: { idToDelete: testSetup.testObjectMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
it('should accumulate multiple validation errors when some inputs are invalid', async () => {
|
||||
const invalidViewId = uuidv4();
|
||||
const invalidFieldMetadataId = uuidv4();
|
||||
|
||||
const inputs: CreateViewGroupInput[] = [
|
||||
{
|
||||
fieldMetadataId: invalidFieldMetadataId,
|
||||
viewId: testSetup.testViewId,
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
fieldValue: 'Invalid Group A',
|
||||
},
|
||||
{
|
||||
fieldMetadataId: testSetup.firstTestFieldMetadataId,
|
||||
viewId: invalidViewId,
|
||||
position: 1,
|
||||
isVisible: true,
|
||||
fieldValue: 'Invalid Group B',
|
||||
},
|
||||
{
|
||||
fieldMetadataId: invalidFieldMetadataId,
|
||||
viewId: invalidViewId,
|
||||
position: 2,
|
||||
isVisible: true,
|
||||
fieldValue: 'Invalid Group C',
|
||||
},
|
||||
];
|
||||
|
||||
const { errors } = await createManyCoreViewGroups({
|
||||
inputs,
|
||||
expectToFail: true,
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({
|
||||
errors,
|
||||
});
|
||||
});
|
||||
});
|
||||
+265
@@ -0,0 +1,265 @@
|
||||
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 { createManyCoreViewGroups } from 'test/integration/metadata/suites/view-group/utils/create-many-core-view-groups.util';
|
||||
import { deleteOneCoreViewGroup } from 'test/integration/metadata/suites/view-group/utils/delete-one-core-view-group.util';
|
||||
import { destroyOneCoreViewGroup } from 'test/integration/metadata/suites/view-group/utils/destroy-one-core-view-group.util';
|
||||
import { createOneCoreView } from 'test/integration/metadata/suites/view/utils/create-one-core-view.util';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type CreateViewGroupInput } from 'src/engine/metadata-modules/view-group/dtos/inputs/create-view-group.input';
|
||||
|
||||
describe('View Group Resolver - Successful Create Many Operations - v2', () => {
|
||||
let testSetup: {
|
||||
testViewId: string;
|
||||
testObjectMetadataId: string;
|
||||
firstTestFieldMetadataId: string;
|
||||
secondTestFieldMetadataId: string;
|
||||
thirdTestFieldMetadataId: string;
|
||||
};
|
||||
let createdViewGroupIds: string[] = [];
|
||||
|
||||
beforeAll(async () => {
|
||||
const {
|
||||
data: {
|
||||
createOneObject: { id: objectMetadataId },
|
||||
},
|
||||
} = await createOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
nameSingular: 'myGroupTestObjectV2',
|
||||
namePlural: 'myGroupTestObjectsV2',
|
||||
labelSingular: 'My Group Test Object v2',
|
||||
labelPlural: 'My Group Test Objects v2',
|
||||
icon: 'Icon123',
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
data: {
|
||||
createOneField: { id: firstTestFieldMetadataId },
|
||||
},
|
||||
} = await createOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
name: 'testField',
|
||||
label: 'Test Field',
|
||||
type: FieldMetadataType.TEXT,
|
||||
objectMetadataId,
|
||||
isLabelSyncedWithName: true,
|
||||
},
|
||||
gqlFields: `
|
||||
id
|
||||
name
|
||||
label
|
||||
isLabelSyncedWithName
|
||||
`,
|
||||
});
|
||||
|
||||
const {
|
||||
data: {
|
||||
createOneField: { id: secondTestFieldMetadataId },
|
||||
},
|
||||
} = await createOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
name: 'secondTestField',
|
||||
label: 'Test Field',
|
||||
type: FieldMetadataType.TEXT,
|
||||
objectMetadataId,
|
||||
isLabelSyncedWithName: false,
|
||||
},
|
||||
gqlFields: `
|
||||
id
|
||||
name
|
||||
label
|
||||
isLabelSyncedWithName
|
||||
`,
|
||||
});
|
||||
|
||||
const {
|
||||
data: {
|
||||
createOneField: { id: thirdTestFieldMetadataId },
|
||||
},
|
||||
} = await createOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
name: 'thirdTestField',
|
||||
label: 'Test Field',
|
||||
type: FieldMetadataType.TEXT,
|
||||
objectMetadataId,
|
||||
isLabelSyncedWithName: false,
|
||||
},
|
||||
gqlFields: `
|
||||
id
|
||||
name
|
||||
label
|
||||
isLabelSyncedWithName
|
||||
`,
|
||||
});
|
||||
|
||||
const {
|
||||
data: {
|
||||
createCoreView: { id: testViewId },
|
||||
},
|
||||
} = await createOneCoreView({
|
||||
input: {
|
||||
icon: 'icon123',
|
||||
objectMetadataId,
|
||||
name: 'TestViewForGroups',
|
||||
},
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
testSetup = {
|
||||
testViewId,
|
||||
testObjectMetadataId: objectMetadataId,
|
||||
firstTestFieldMetadataId,
|
||||
secondTestFieldMetadataId,
|
||||
thirdTestFieldMetadataId,
|
||||
};
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await updateOneObjectMetadata({
|
||||
input: {
|
||||
idToUpdate: testSetup.testObjectMetadataId,
|
||||
updatePayload: {
|
||||
isActive: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
await deleteOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: { idToDelete: testSetup.testObjectMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
for (const viewGroupId of createdViewGroupIds) {
|
||||
if (isDefined(viewGroupId)) {
|
||||
const {
|
||||
data: { deleteCoreViewGroup },
|
||||
} = await deleteOneCoreViewGroup({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
id: viewGroupId,
|
||||
},
|
||||
});
|
||||
|
||||
expect(deleteCoreViewGroup.deletedAt).not.toBeNull();
|
||||
await destroyOneCoreViewGroup({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
id: viewGroupId,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
createdViewGroupIds = [];
|
||||
});
|
||||
|
||||
it('should successfully create multiple view groups in batch', async () => {
|
||||
const inputs: CreateViewGroupInput[] = [
|
||||
{
|
||||
fieldMetadataId: testSetup.firstTestFieldMetadataId,
|
||||
viewId: testSetup.testViewId,
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
fieldValue: 'Group A',
|
||||
},
|
||||
{
|
||||
fieldMetadataId: testSetup.secondTestFieldMetadataId,
|
||||
viewId: testSetup.testViewId,
|
||||
position: 1,
|
||||
isVisible: false,
|
||||
fieldValue: 'Group B',
|
||||
},
|
||||
{
|
||||
fieldMetadataId: testSetup.thirdTestFieldMetadataId,
|
||||
viewId: testSetup.testViewId,
|
||||
position: 2,
|
||||
isVisible: true,
|
||||
fieldValue: 'Group C',
|
||||
},
|
||||
];
|
||||
|
||||
const {
|
||||
data: { createManyCoreViewGroups: createdViewGroups },
|
||||
errors,
|
||||
} = await createManyCoreViewGroups({
|
||||
inputs,
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
expect(errors).toBeUndefined();
|
||||
expect(createdViewGroups).toBeDefined();
|
||||
expect(createdViewGroups).toHaveLength(3);
|
||||
|
||||
createdViewGroups.forEach((viewGroup, index) => {
|
||||
expect(viewGroup).toMatchObject({
|
||||
fieldMetadataId: inputs[index].fieldMetadataId,
|
||||
viewId: testSetup.testViewId,
|
||||
position: inputs[index].position,
|
||||
isVisible: inputs[index].isVisible,
|
||||
fieldValue: inputs[index].fieldValue,
|
||||
});
|
||||
|
||||
createdViewGroupIds.push(viewGroup.id);
|
||||
});
|
||||
});
|
||||
|
||||
it('should successfully create single view group using batch endpoint', async () => {
|
||||
const inputs: CreateViewGroupInput[] = [
|
||||
{
|
||||
fieldMetadataId: testSetup.firstTestFieldMetadataId,
|
||||
viewId: testSetup.testViewId,
|
||||
position: 5,
|
||||
isVisible: true,
|
||||
fieldValue: 'Single Group',
|
||||
},
|
||||
];
|
||||
|
||||
const {
|
||||
data: { createManyCoreViewGroups: createdViewGroups },
|
||||
errors,
|
||||
} = await createManyCoreViewGroups({
|
||||
inputs,
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
expect(errors).toBeUndefined();
|
||||
expect(createdViewGroups).toBeDefined();
|
||||
expect(createdViewGroups).toHaveLength(1);
|
||||
|
||||
const viewGroup = createdViewGroups[0];
|
||||
|
||||
expect(viewGroup).toMatchObject({
|
||||
fieldMetadataId: testSetup.firstTestFieldMetadataId,
|
||||
viewId: testSetup.testViewId,
|
||||
position: 5,
|
||||
isVisible: true,
|
||||
fieldValue: 'Single Group',
|
||||
});
|
||||
|
||||
createdViewGroupIds.push(viewGroup.id);
|
||||
});
|
||||
|
||||
it('should return empty array when creating zero view groups', async () => {
|
||||
const inputs: CreateViewGroupInput[] = [];
|
||||
|
||||
const {
|
||||
data: { createManyCoreViewGroups: createdViewGroups },
|
||||
errors,
|
||||
} = await createManyCoreViewGroups({
|
||||
inputs,
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
expect(errors).toBeUndefined();
|
||||
expect(createdViewGroups).toBeDefined();
|
||||
expect(createdViewGroups).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { VIEW_GROUP_GQL_FIELDS } from 'test/integration/constants/view-gql-fields.constants';
|
||||
|
||||
import { type CreateViewGroupInput } from 'src/engine/metadata-modules/view-group/dtos/inputs/create-view-group.input';
|
||||
|
||||
export const createManyCoreViewGroupsQueryFactory = ({
|
||||
gqlFields = VIEW_GROUP_GQL_FIELDS,
|
||||
inputs,
|
||||
}: {
|
||||
gqlFields?: string;
|
||||
inputs: CreateViewGroupInput[];
|
||||
}) => ({
|
||||
query: gql`
|
||||
mutation CreateManyCoreViewGroups($inputs: [CreateViewGroupInput!]!) {
|
||||
createManyCoreViewGroups(inputs: $inputs) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
inputs,
|
||||
},
|
||||
});
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
||||
import { createManyCoreViewGroupsQueryFactory } from 'test/integration/metadata/suites/view-group/utils/create-many-core-view-groups-query-factory.util';
|
||||
import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type';
|
||||
import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util';
|
||||
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
|
||||
|
||||
import { type CreateViewGroupInput } from 'src/engine/metadata-modules/view-group/dtos/inputs/create-view-group.input';
|
||||
import { type ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity';
|
||||
|
||||
export const createManyCoreViewGroups = async ({
|
||||
inputs,
|
||||
gqlFields,
|
||||
expectToFail,
|
||||
}: {
|
||||
inputs: CreateViewGroupInput[];
|
||||
gqlFields?: string;
|
||||
expectToFail?: boolean;
|
||||
}): CommonResponseBody<{
|
||||
createManyCoreViewGroups: ViewGroupEntity[];
|
||||
}> => {
|
||||
const graphqlOperation = createManyCoreViewGroupsQueryFactory({
|
||||
inputs,
|
||||
gqlFields,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
||||
|
||||
if (expectToFail === true) {
|
||||
warnIfNoErrorButExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'View Groups batch creation should have failed but did not',
|
||||
});
|
||||
}
|
||||
|
||||
if (expectToFail === false) {
|
||||
warnIfErrorButNotExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'View Groups batch creation has failed but should not',
|
||||
});
|
||||
}
|
||||
|
||||
return { data: response.body.data, errors: response.body.errors };
|
||||
};
|
||||
+1
-1
@@ -32,7 +32,7 @@ exports[`View Group REST API POST /metadata/viewGroups should fail to create vie
|
||||
},
|
||||
],
|
||||
},
|
||||
"message": "Multiple validation errors occurred while creating view group",
|
||||
"message": "Multiple validation errors occurred while creating view groups",
|
||||
"statusCode": 400,
|
||||
"summary": {
|
||||
"invalidCronTrigger": 0,
|
||||
|
||||
Reference in New Issue
Block a user