Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c2fed8232 | |||
| 14048f7253 |
File diff suppressed because one or more lines are too long
@@ -0,0 +1,11 @@
|
||||
import { VIEW_GROUP_FRAGMENT } from '@/views/graphql/fragments/viewGroupFragment';
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const UPDATE_MANY_VIEW_GROUPS = gql`
|
||||
${VIEW_GROUP_FRAGMENT}
|
||||
mutation UpdateManyViewGroups($inputs: [UpdateViewGroupInput!]!) {
|
||||
updateManyViewGroups(inputs: $inputs) {
|
||||
...ViewGroupFragment
|
||||
}
|
||||
}
|
||||
`;
|
||||
+19
-18
@@ -8,43 +8,44 @@ import { t } from '@lingui/core/macro';
|
||||
import { CrudOperationType } from 'twenty-shared/types';
|
||||
import { useMutation } from '@apollo/client/react';
|
||||
import {
|
||||
type UpdateViewGroupMutationVariables,
|
||||
UpdateViewGroupDocument,
|
||||
type UpdateManyViewGroupsMutationVariables,
|
||||
UpdateManyViewGroupsDocument,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const usePerformViewGroupAPIPersist = () => {
|
||||
const [updateViewGroupMutation] = useMutation(UpdateViewGroupDocument);
|
||||
const [updateManyViewGroupsMutation] = useMutation(
|
||||
UpdateManyViewGroupsDocument,
|
||||
);
|
||||
|
||||
const { handleMetadataError } = useMetadataErrorHandler();
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
|
||||
const performViewGroupAPIUpdate = useCallback(
|
||||
async (
|
||||
updateViewGroupInputs: UpdateViewGroupMutationVariables[],
|
||||
updateViewGroupInputs: UpdateManyViewGroupsMutationVariables,
|
||||
): Promise<
|
||||
MetadataRequestResult<
|
||||
Awaited<ReturnType<typeof updateViewGroupMutation>>[]
|
||||
>
|
||||
MetadataRequestResult<Awaited<
|
||||
ReturnType<typeof updateManyViewGroupsMutation>
|
||||
> | null>
|
||||
> => {
|
||||
if (updateViewGroupInputs.length === 0) {
|
||||
if (
|
||||
!Array.isArray(updateViewGroupInputs.inputs) ||
|
||||
updateViewGroupInputs.inputs.length === 0
|
||||
) {
|
||||
return {
|
||||
status: 'successful',
|
||||
response: [],
|
||||
response: null,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const results = await Promise.all(
|
||||
updateViewGroupInputs.map((variables) =>
|
||||
updateViewGroupMutation({
|
||||
variables,
|
||||
}),
|
||||
),
|
||||
);
|
||||
const result = await updateManyViewGroupsMutation({
|
||||
variables: updateViewGroupInputs,
|
||||
});
|
||||
|
||||
return {
|
||||
status: 'successful',
|
||||
response: results,
|
||||
response: result,
|
||||
};
|
||||
} catch (error) {
|
||||
if (CombinedGraphQLErrors.is(error)) {
|
||||
@@ -62,7 +63,7 @@ export const usePerformViewGroupAPIPersist = () => {
|
||||
};
|
||||
}
|
||||
},
|
||||
[updateViewGroupMutation, handleMetadataError, enqueueErrorSnackBar],
|
||||
[updateManyViewGroupsMutation, handleMetadataError, enqueueErrorSnackBar],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -67,9 +67,9 @@ export const useSaveCurrentViewGroups = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
await performViewGroupAPIUpdate([
|
||||
{
|
||||
input: {
|
||||
await performViewGroupAPIUpdate({
|
||||
inputs: [
|
||||
{
|
||||
id: existingField.id,
|
||||
update: {
|
||||
isVisible: viewGroupToSave.isVisible,
|
||||
@@ -77,8 +77,8 @@ export const useSaveCurrentViewGroups = () => {
|
||||
fieldValue: viewGroupToSave.fieldValue,
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
],
|
||||
});
|
||||
},
|
||||
[
|
||||
store,
|
||||
@@ -109,7 +109,7 @@ export const useSaveCurrentViewGroups = () => {
|
||||
|
||||
const currentViewGroups = view.viewGroups;
|
||||
|
||||
const viewGroupsToUpdate = viewGroupsToSave
|
||||
const viewGroupInputsToUpdate = viewGroupsToSave
|
||||
.map((viewGroupToSave) => {
|
||||
const existingField = currentViewGroups.find(
|
||||
(currentViewGroup) =>
|
||||
@@ -136,13 +136,11 @@ export const useSaveCurrentViewGroups = () => {
|
||||
}
|
||||
|
||||
return {
|
||||
input: {
|
||||
id: existingField.id,
|
||||
update: {
|
||||
isVisible: viewGroupToSave.isVisible,
|
||||
position: viewGroupToSave.position,
|
||||
fieldValue: viewGroupToSave.fieldValue,
|
||||
},
|
||||
id: existingField.id,
|
||||
update: {
|
||||
isVisible: viewGroupToSave.isVisible,
|
||||
position: viewGroupToSave.position,
|
||||
fieldValue: viewGroupToSave.fieldValue,
|
||||
},
|
||||
};
|
||||
})
|
||||
@@ -152,7 +150,7 @@ export const useSaveCurrentViewGroups = () => {
|
||||
throw new Error('mainGroupByFieldMetadataId is required');
|
||||
}
|
||||
|
||||
await performViewGroupAPIUpdate(viewGroupsToUpdate);
|
||||
await performViewGroupAPIUpdate({ inputs: viewGroupInputsToUpdate });
|
||||
},
|
||||
[
|
||||
store,
|
||||
|
||||
+13
@@ -84,6 +84,19 @@ export class ViewGroupResolver {
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => [ViewGroupDTO])
|
||||
@UseGuards(UpdateViewGroupPermissionGuard)
|
||||
async updateManyViewGroups(
|
||||
@Args('inputs', { type: () => [UpdateViewGroupInput] })
|
||||
updateViewGroupInputs: UpdateViewGroupInput[],
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
): Promise<ViewGroupDTO[]> {
|
||||
return await this.viewGroupService.updateMany({
|
||||
updateViewGroupInputs,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => ViewGroupDTO)
|
||||
@UseGuards(DeleteViewGroupPermissionGuard)
|
||||
async deleteViewGroup(
|
||||
|
||||
+43
-13
@@ -7,6 +7,7 @@ import { IsNull, Repository } from 'typeorm';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-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';
|
||||
@@ -152,6 +153,32 @@ export class ViewGroupService {
|
||||
workspaceId: string;
|
||||
updateViewGroupInput: UpdateViewGroupInput;
|
||||
}): Promise<ViewGroupDTO> {
|
||||
const [updatedViewGroup] = await this.updateMany({
|
||||
updateViewGroupInputs: [updateViewGroupInput],
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
if (!isDefined(updatedViewGroup)) {
|
||||
throw new ViewGroupException(
|
||||
'Failed to update view group',
|
||||
ViewGroupExceptionCode.INVALID_VIEW_GROUP_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
return updatedViewGroup;
|
||||
}
|
||||
|
||||
async updateMany({
|
||||
updateViewGroupInputs,
|
||||
workspaceId,
|
||||
}: {
|
||||
updateViewGroupInputs: UpdateViewGroupInput[];
|
||||
workspaceId: string;
|
||||
}): Promise<ViewGroupDTO[]> {
|
||||
if (updateViewGroupInputs.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const { workspaceCustomFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{
|
||||
@@ -167,11 +194,13 @@ export class ViewGroupService {
|
||||
},
|
||||
);
|
||||
|
||||
const optimisticallyUpdatedFlatViewGroup =
|
||||
fromUpdateViewGroupInputToFlatViewGroupToUpdateOrThrow({
|
||||
flatViewGroupMaps: existingFlatViewGroupMaps,
|
||||
updateViewGroupInput,
|
||||
});
|
||||
const flatViewGroupsToUpdate = updateViewGroupInputs.map(
|
||||
(updateViewGroupInput) =>
|
||||
fromUpdateViewGroupInputToFlatViewGroupToUpdateOrThrow({
|
||||
flatViewGroupMaps: existingFlatViewGroupMaps,
|
||||
updateViewGroupInput,
|
||||
}),
|
||||
);
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
@@ -180,7 +209,7 @@ export class ViewGroupService {
|
||||
viewGroup: {
|
||||
flatEntityToCreate: [],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [optimisticallyUpdatedFlatViewGroup],
|
||||
flatEntityToUpdate: flatViewGroupsToUpdate,
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
@@ -193,7 +222,7 @@ export class ViewGroupService {
|
||||
if (validateAndBuildResult.status === 'fail') {
|
||||
throw new WorkspaceMigrationBuilderException(
|
||||
validateAndBuildResult,
|
||||
'Multiple validation errors occurred while updating view group',
|
||||
'Multiple validation errors occurred while updating view groups',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -205,12 +234,13 @@ export class ViewGroupService {
|
||||
},
|
||||
);
|
||||
|
||||
return fromFlatViewGroupToViewGroupDto(
|
||||
findFlatEntityByUniversalIdentifierOrThrow({
|
||||
universalIdentifier:
|
||||
optimisticallyUpdatedFlatViewGroup.universalIdentifier,
|
||||
flatEntityMaps: recomputedExistingFlatViewGroupMaps,
|
||||
}),
|
||||
return updateViewGroupInputs.map(({ id }) =>
|
||||
fromFlatViewGroupToViewGroupDto(
|
||||
findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: recomputedExistingFlatViewGroupMaps,
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+235
@@ -0,0 +1,235 @@
|
||||
import { createOneSelectFieldMetadataForIntegrationTests } from 'test/integration/metadata/suites/field-metadata/utils/create-one-select-field-metadata-for-integration-tests.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 { createManyViewGroups } from 'test/integration/metadata/suites/view-group/utils/create-many-view-groups.util';
|
||||
import { deleteOneViewGroup } from 'test/integration/metadata/suites/view-group/utils/delete-one-view-group.util';
|
||||
import { destroyOneViewGroup } from 'test/integration/metadata/suites/view-group/utils/destroy-one-view-group.util';
|
||||
import { updateManyViewGroups } from 'test/integration/metadata/suites/view-group/utils/update-many-view-groups.util';
|
||||
import { createOneView } from 'test/integration/metadata/suites/view/utils/create-one-view.util';
|
||||
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 Update Many Operations - v2', () => {
|
||||
let testSetup: {
|
||||
testViewId: string;
|
||||
testObjectMetadataId: string;
|
||||
};
|
||||
let createdViewGroupIds: string[] = [];
|
||||
|
||||
beforeAll(async () => {
|
||||
const {
|
||||
data: {
|
||||
createOneObject: { id: objectMetadataId },
|
||||
},
|
||||
} = await createOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
nameSingular: 'myUpdateManyGroupTestObjectV2',
|
||||
namePlural: 'myUpdateManyGroupTestObjectsV2',
|
||||
labelSingular: 'My Update Many Group Test Object v2',
|
||||
labelPlural: 'My Update Many Group Test Objects v2',
|
||||
icon: 'Icon123',
|
||||
},
|
||||
});
|
||||
|
||||
const { selectFieldMetadataId } =
|
||||
await createOneSelectFieldMetadataForIntegrationTests({
|
||||
input: {
|
||||
objectMetadataId,
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
data: {
|
||||
createView: { id: testViewId },
|
||||
},
|
||||
} = await createOneView({
|
||||
input: {
|
||||
icon: 'icon123',
|
||||
objectMetadataId,
|
||||
name: 'TestViewForUpdateManyGroups',
|
||||
mainGroupByFieldMetadataId: selectFieldMetadataId,
|
||||
},
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
testSetup = {
|
||||
testViewId,
|
||||
testObjectMetadataId: objectMetadataId,
|
||||
};
|
||||
});
|
||||
|
||||
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: { deleteViewGroup },
|
||||
} = await deleteOneViewGroup({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
id: viewGroupId,
|
||||
},
|
||||
});
|
||||
|
||||
expect(deleteViewGroup.deletedAt).not.toBeNull();
|
||||
await destroyOneViewGroup({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
id: viewGroupId,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
createdViewGroupIds = [];
|
||||
});
|
||||
|
||||
it('should batch-update positions of multiple view groups at once', async () => {
|
||||
const createInputs: CreateViewGroupInput[] = [
|
||||
{
|
||||
viewId: testSetup.testViewId,
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
fieldValue: 'Group A',
|
||||
},
|
||||
{
|
||||
viewId: testSetup.testViewId,
|
||||
position: 1,
|
||||
isVisible: true,
|
||||
fieldValue: 'Group B',
|
||||
},
|
||||
{
|
||||
viewId: testSetup.testViewId,
|
||||
position: 2,
|
||||
isVisible: true,
|
||||
fieldValue: 'Group C',
|
||||
},
|
||||
];
|
||||
|
||||
const {
|
||||
data: { createManyViewGroups: createdGroups },
|
||||
} = await createManyViewGroups({
|
||||
inputs: createInputs,
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
createdViewGroupIds = createdGroups.map(
|
||||
(viewGroup: { id: string }) => viewGroup.id,
|
||||
);
|
||||
|
||||
const {
|
||||
data: { updateManyViewGroups: updatedGroups },
|
||||
errors,
|
||||
} = await updateManyViewGroups({
|
||||
inputs: [
|
||||
{ id: createdGroups[0].id, update: { position: 2 } },
|
||||
{ id: createdGroups[1].id, update: { position: 0 } },
|
||||
{ id: createdGroups[2].id, update: { position: 1 } },
|
||||
],
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
expect(errors).toBeUndefined();
|
||||
expect(updatedGroups).toBeDefined();
|
||||
expect(updatedGroups).toHaveLength(3);
|
||||
|
||||
expect(updatedGroups[0]).toMatchObject({
|
||||
id: createdGroups[0].id,
|
||||
position: 2,
|
||||
});
|
||||
expect(updatedGroups[1]).toMatchObject({
|
||||
id: createdGroups[1].id,
|
||||
position: 0,
|
||||
});
|
||||
expect(updatedGroups[2]).toMatchObject({
|
||||
id: createdGroups[2].id,
|
||||
position: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('should batch-update visibility of multiple view groups at once', async () => {
|
||||
const createInputs: CreateViewGroupInput[] = [
|
||||
{
|
||||
viewId: testSetup.testViewId,
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
fieldValue: 'Visible Group',
|
||||
},
|
||||
{
|
||||
viewId: testSetup.testViewId,
|
||||
position: 1,
|
||||
isVisible: true,
|
||||
fieldValue: 'To Be Hidden Group',
|
||||
},
|
||||
];
|
||||
|
||||
const {
|
||||
data: { createManyViewGroups: createdGroups },
|
||||
} = await createManyViewGroups({
|
||||
inputs: createInputs,
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
createdViewGroupIds = createdGroups.map(
|
||||
(viewGroup: { id: string }) => viewGroup.id,
|
||||
);
|
||||
|
||||
const {
|
||||
data: { updateManyViewGroups: updatedGroups },
|
||||
errors,
|
||||
} = await updateManyViewGroups({
|
||||
inputs: [
|
||||
{ id: createdGroups[0].id, update: { isVisible: false } },
|
||||
{
|
||||
id: createdGroups[1].id,
|
||||
update: { isVisible: false, position: 5 },
|
||||
},
|
||||
],
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
expect(errors).toBeUndefined();
|
||||
expect(updatedGroups).toBeDefined();
|
||||
expect(updatedGroups).toHaveLength(2);
|
||||
|
||||
expect(updatedGroups[0]).toMatchObject({
|
||||
id: createdGroups[0].id,
|
||||
isVisible: false,
|
||||
});
|
||||
expect(updatedGroups[1]).toMatchObject({
|
||||
id: createdGroups[1].id,
|
||||
isVisible: false,
|
||||
position: 5,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return empty array for empty inputs', async () => {
|
||||
const {
|
||||
data: { updateManyViewGroups: updatedGroups },
|
||||
errors,
|
||||
} = await updateManyViewGroups({
|
||||
inputs: [],
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
expect(errors).toBeUndefined();
|
||||
expect(updatedGroups).toBeDefined();
|
||||
expect(updatedGroups).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 UpdateViewGroupInput } from 'src/engine/metadata-modules/view-group/dtos/inputs/update-view-group.input';
|
||||
|
||||
export const updateManyViewGroupsQueryFactory = ({
|
||||
gqlFields = VIEW_GROUP_GQL_FIELDS,
|
||||
inputs,
|
||||
}: {
|
||||
gqlFields?: string;
|
||||
inputs: UpdateViewGroupInput[];
|
||||
}) => ({
|
||||
query: gql`
|
||||
mutation UpdateManyViewGroups($inputs: [UpdateViewGroupInput!]!) {
|
||||
updateManyViewGroups(inputs: $inputs) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
inputs,
|
||||
},
|
||||
});
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
||||
import { updateManyViewGroupsQueryFactory } from 'test/integration/metadata/suites/view-group/utils/update-many-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 UpdateViewGroupInput } from 'src/engine/metadata-modules/view-group/dtos/inputs/update-view-group.input';
|
||||
import { type ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity';
|
||||
|
||||
export const updateManyViewGroups = async ({
|
||||
inputs,
|
||||
gqlFields,
|
||||
expectToFail,
|
||||
}: {
|
||||
inputs: UpdateViewGroupInput[];
|
||||
gqlFields?: string;
|
||||
expectToFail?: boolean;
|
||||
}): CommonResponseBody<{
|
||||
updateManyViewGroups: ViewGroupEntity[];
|
||||
}> => {
|
||||
const graphqlOperation = updateManyViewGroupsQueryFactory({
|
||||
inputs,
|
||||
gqlFields,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
||||
|
||||
if (expectToFail === true) {
|
||||
warnIfNoErrorButExpectedToFail({
|
||||
response,
|
||||
errorMessage:
|
||||
'View Groups batch update should have failed but did not',
|
||||
});
|
||||
}
|
||||
|
||||
if (expectToFail === false) {
|
||||
warnIfErrorButNotExpectedToFail({
|
||||
response,
|
||||
errorMessage:
|
||||
'View Groups batch update has failed but should not',
|
||||
});
|
||||
}
|
||||
|
||||
return { data: response.body.data, errors: response.body.errors };
|
||||
};
|
||||
Reference in New Issue
Block a user