Fix deactivate objects impacts (#11185)

In this PR:
- Remove deactivated objects from ActivityTargetInlineCell record picker
- Prevent users to deactivate createdAt, updatedAt, deletedAt fields on
any objects

Still left:
- write unit tests on the assert utils
- write integration tests on field metadata service
- prevent users to deactivate createdAt, updatedAt, deletedAt on FE
This commit is contained in:
Charles Bochet
2025-03-26 20:45:46 +01:00
committed by GitHub
parent 90e884d33f
commit 5bd10d40cb
49 changed files with 861 additions and 531 deletions

View File

@ -14,9 +14,9 @@ import {
LISTING_NAME_PLURAL,
LISTING_NAME_SINGULAR,
} from 'test/integration/metadata/suites/object-metadata/constants/test-object-names.constant';
import { createListingCustomObject } from 'test/integration/metadata/suites/object-metadata/utils/create-test-object-metadata.util';
import { deleteOneObjectMetadataItem } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
import { findManyObjectsMetadataItems } from 'test/integration/metadata/suites/object-metadata/utils/find-many-objects-metadata-items.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 { findManyObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/find-many-object-metadata.util';
import { EachTestingContext } from 'twenty-shared/testing';
import { SearchRecordDTO } from 'src/engine/core-modules/search/dtos/search-record-dto';
@ -46,7 +46,14 @@ describe('SearchResolver', () => {
beforeAll(async () => {
try {
const objectsMetadata = await findManyObjectsMetadataItems();
const objectsMetadata = await findManyObjectMetadata({
input: {
filter: {},
paging: {
first: 1000,
},
},
});
const listingObjectMetadata = objectsMetadata.find(
(object) => object.nameSingular === LISTING_NAME_SINGULAR,
);
@ -56,7 +63,19 @@ describe('SearchResolver', () => {
objectMetadataId: listingObjectMetadata.id,
};
} else {
listingObjectMetadataId = await createListingCustomObject();
const { data } = await createOneObjectMetadata({
input: {
labelSingular: LISTING_NAME_SINGULAR,
labelPlural: LISTING_NAME_PLURAL,
nameSingular: LISTING_NAME_SINGULAR,
namePlural: LISTING_NAME_PLURAL,
icon: 'IconBuildingSkyscraper',
},
});
listingObjectMetadataId = {
objectMetadataId: data.createOneObject.id,
};
}
await performCreateManyOperation(
@ -102,9 +121,9 @@ describe('SearchResolver', () => {
console.log(error);
});
await deleteOneObjectMetadataItem(
listingObjectMetadataId.objectMetadataId,
).catch((error) => {
await deleteOneObjectMetadata({
input: { idToDelete: listingObjectMetadataId.objectMetadataId },
}).catch((error) => {
// eslint-disable-next-line no-console
console.log(error);
});

View File

@ -1,12 +1,12 @@
import { createCustomTextFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-custom-text-field-metadata.util';
import { createOneFieldMetadataFactory } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata-factory.util';
import { deleteOneFieldMetadataItemFactory } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata-factory.util';
import { updateOneFieldMetadataFactory } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata-factory.util';
import { createOneObjectMetadataFactory } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata-factory.util';
import { createListingCustomObject } from 'test/integration/metadata/suites/object-metadata/utils/create-test-object-metadata.util';
import { deleteOneObjectMetadataItemFactory } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata-factory.util';
import { deleteOneObjectMetadataItem } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
import { updateOneObjectMetadataItemFactory } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata-factory.util';
import { createOneFieldMetadataQueryFactory } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata-query-factory.util';
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
import { deleteOneFieldMetadataQueryFactory } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata-query-factory.util';
import { updateOneFieldMetadataQueryFactory } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata-query-factory.util';
import { createOneObjectMetadataQueryFactory } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata-query-factory.util';
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
import { deleteOneObjectMetadataQueryFactory } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata-query-factory.util';
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
import { updateOneObjectMetadataQueryFactory } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata-query-factory.util';
import { makeMetadataAPIRequestWithMemberRole } from 'test/integration/metadata/suites/utils/make-metadata-api-request-with-member-role.util';
import { FieldMetadataType } from 'twenty-shared/types';
@ -21,18 +21,33 @@ describe('datamodel permissions', () => {
let testFieldId = '';
beforeAll(async () => {
const { objectMetadataId: createdObjectId } =
await createListingCustomObject();
const { data } = await createOneObjectMetadata({
input: {
nameSingular: 'listing',
namePlural: 'listings',
labelSingular: 'Listing',
labelPlural: 'Listings',
icon: 'IconBuildingSkyscraper',
},
});
listingObjectId = createdObjectId;
listingObjectId = data.createOneObject.id;
const { fieldMetadataId: createdFieldMetadaId } =
await createCustomTextFieldMetadata(createdObjectId);
const { data: createdFieldData } = await createOneFieldMetadata({
input: {
name: 'house',
type: FieldMetadataType.TEXT,
label: 'House',
objectMetadataId: listingObjectId,
},
});
testFieldId = createdFieldMetadaId;
testFieldId = createdFieldData.createOneField.id;
});
afterAll(async () => {
await deleteOneObjectMetadataItem(listingObjectId);
await deleteOneObjectMetadata({
input: { idToDelete: listingObjectId },
});
});
describe('createOne', () => {
it('should throw a permission error when user does not have permission (member role)', async () => {
@ -46,8 +61,8 @@ describe('datamodel permissions', () => {
};
// Act
const graphqlOperation = createOneFieldMetadataFactory({
input: { field: createFieldInput },
const graphqlOperation = createOneFieldMetadataQueryFactory({
input: createFieldInput,
gqlFields: `
id
name
@ -77,8 +92,8 @@ describe('datamodel permissions', () => {
label: 'Updated Name',
};
const graphqlOperation = updateOneFieldMetadataFactory({
input: { id: testFieldId, update: updateFieldInput },
const graphqlOperation = updateOneFieldMetadataQueryFactory({
input: { idToUpdate: testFieldId, updatePayload: updateFieldInput },
gqlFields: `
id
name
@ -103,8 +118,8 @@ describe('datamodel permissions', () => {
describe('deleteOne', () => {
it('should throw a permission error when user does not have permission (member role)', async () => {
// Arrange
const graphqlOperation = deleteOneFieldMetadataItemFactory({
idToDelete: testFieldId,
const graphqlOperation = deleteOneFieldMetadataQueryFactory({
input: { idToDelete: testFieldId },
});
const response =
@ -127,17 +142,15 @@ describe('datamodel permissions', () => {
describe('createOne', () => {
it('should throw a permission error when user does not have permission (member role)', async () => {
// Arrange
const graphqlOperation = createOneObjectMetadataFactory({
const graphqlOperation = createOneObjectMetadataQueryFactory({
gqlFields: `
id
`,
input: {
object: {
labelPlural: 'Test Objects',
labelSingular: 'Test Object',
namePlural: 'testObjects',
nameSingular: 'testObject',
},
labelPlural: 'Test Objects',
labelSingular: 'Test Object',
namePlural: 'testObjects',
nameSingular: 'testObject',
},
});
@ -160,18 +173,27 @@ describe('datamodel permissions', () => {
let listingObjectId = '';
beforeAll(async () => {
const { objectMetadataId: createdObjectId } =
await createListingCustomObject();
const { data } = await createOneObjectMetadata({
input: {
labelPlural: 'Listings',
labelSingular: 'Listing',
namePlural: 'listings',
nameSingular: 'listing',
icon: 'IconBuildingSkyscraper',
},
});
listingObjectId = createdObjectId;
listingObjectId = data.createOneObject.id;
});
afterAll(async () => {
await deleteOneObjectMetadataItem(listingObjectId);
await deleteOneObjectMetadata({
input: { idToDelete: listingObjectId },
});
});
describe('updateOne', () => {
it('should throw a permission error when user does not have permission (member role)', async () => {
// Arrange
const graphqlOperation = updateOneObjectMetadataItemFactory({
const graphqlOperation = updateOneObjectMetadataQueryFactory({
gqlFields: `
id
`,
@ -201,8 +223,8 @@ describe('datamodel permissions', () => {
describe('deleteOne', () => {
it('should throw a permission error when user does not have permission (member role)', async () => {
// Arrange
const graphqlOperation = deleteOneObjectMetadataItemFactory({
idToDelete: listingObjectId,
const graphqlOperation = deleteOneObjectMetadataQueryFactory({
input: { idToDelete: listingObjectId },
});
const response =

View File

@ -2,8 +2,8 @@ import request from 'supertest';
import { deleteOneRoleOperationFactory } from 'test/integration/graphql/utils/delete-one-role-operation-factory.util';
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
import { updateFeatureFlagFactory } from 'test/integration/graphql/utils/update-feature-flag-factory.util';
import { createListingCustomObject } from 'test/integration/metadata/suites/object-metadata/utils/create-test-object-metadata.util';
import { deleteOneObjectMetadataItem } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-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 { SEED_APPLE_WORKSPACE_ID } from 'src/database/typeorm-seeds/core/workspaces';
import { DEV_SEED_WORKSPACE_MEMBER_IDS } from 'src/database/typeorm-seeds/workspace/workspace-members';
@ -38,19 +38,12 @@ describe('roles permissions', () => {
let guestRoleId: string;
beforeAll(async () => {
const enablePermissionsQuery = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
'IsPermissionsEnabled',
true,
);
const enablePermissionsV2Query = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
'IsPermissionsV2Enabled',
true,
);
await makeGraphqlAPIRequest(enablePermissionsQuery);
await makeGraphqlAPIRequest(enablePermissionsV2Query);
const query = {
@ -460,14 +453,23 @@ describe('roles permissions', () => {
let listingObjectId = '';
beforeAll(async () => {
const { objectMetadataId: createdObjectId } =
await createListingCustomObject();
const { data } = await createOneObjectMetadata({
input: {
nameSingular: 'house',
namePlural: 'houses',
labelSingular: 'House',
labelPlural: 'Houses',
icon: 'IconBuildingSkyscraper',
},
});
listingObjectId = createdObjectId;
listingObjectId = data.createOneObject.id;
});
afterAll(async () => {
await deleteOneObjectMetadataItem(listingObjectId);
await deleteOneObjectMetadata({
input: { idToDelete: listingObjectId },
});
});
const upsertObjectPermissionMutation = ({