Fix meeting bot CalendarEvent field visibility and editability (#21883)

- Add the meeting bot preference field to the CalendarEvent record page
fields view.
- Use a Standard-app ownership gate for record field read-only logic.
- Allow app-owned and workspace-custom fields on system objects to
follow isUIEditable and permissions.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21883?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
nitin
2026-06-22 14:54:32 +05:30
committed by GitHub
parent 584c567a7f
commit 6237598a30
17 changed files with 233 additions and 70 deletions
@@ -1,4 +1,4 @@
import { useGetIsMetadataItemCustom } from '@/object-metadata/hooks/useGetIsMetadataItemCustom';
import { useGetIsMetadataItemFromStandardApplication } from '@/object-metadata/hooks/useGetIsMetadataItemFromStandardApplication';
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
import { formatFieldMetadataItemAsFieldDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition';
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
@@ -26,8 +26,8 @@ export const useIsRecordFieldReadOnly = ({
);
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const getIsMetadataItemCustom = useGetIsMetadataItemCustom();
const getIsMetadataItemFromStandardApplication =
useGetIsMetadataItemFromStandardApplication();
const objectPermissions = getObjectPermissionsForObject(
objectPermissionsByObjectMetadataId,
@@ -51,7 +51,8 @@ export const useIsRecordFieldReadOnly = ({
return isRecordFieldReadOnly({
isRecordReadOnly,
isSystemObject: objectMetadataItem.isSystem,
isFieldCustom: getIsMetadataItemCustom(fieldMetadataItem),
isFieldFromStandardApplication:
getIsMetadataItemFromStandardApplication(fieldMetadataItem),
objectPermissions,
fieldMetadataItem,
fieldDefinition,
@@ -10,7 +10,6 @@ describe('isRecordFieldReadOnly', () => {
const mockParams = {
isRecordReadOnly: false,
isFieldCustom: false,
objectPermissions: mockObjectPermissions,
fieldMetadataId: 'field-123',
fieldMetadataType: FieldMetadataType.TEXT,
@@ -88,7 +87,35 @@ describe('isRecordFieldReadOnly', () => {
expect(result).toBe(false);
});
it('should return true when field is non-custom on a system object', () => {
it('should return true when field is from the standard application on a system object', () => {
const result = isRecordFieldReadOnly({
...mockParams,
isSystemObject: true,
isFieldFromStandardApplication: true,
fieldMetadataItem: {
id: 'field-123',
isUIEditable: true,
},
});
expect(result).toBe(true);
});
it('should return false when field is not from the standard application on a system object', () => {
const result = isRecordFieldReadOnly({
...mockParams,
isSystemObject: true,
isFieldFromStandardApplication: false,
fieldMetadataItem: {
id: 'field-123',
isUIEditable: true,
},
});
expect(result).toBe(false);
});
it('should return true when field application is not resolved on a system object', () => {
const result = isRecordFieldReadOnly({
...mockParams,
isSystemObject: true,
@@ -101,20 +128,6 @@ describe('isRecordFieldReadOnly', () => {
expect(result).toBe(true);
});
it('should return false when field is custom on a system object', () => {
const result = isRecordFieldReadOnly({
...mockParams,
isSystemObject: true,
isFieldCustom: true,
fieldMetadataItem: {
id: 'field-123',
isUIEditable: true,
},
});
expect(result).toBe(false);
});
it('should return false when isSystemObject is not provided', () => {
const result = isRecordFieldReadOnly({
...mockParams,
@@ -15,7 +15,7 @@ type ObjectPermissionsByObjectMetadataId = Record<
type IsRecordFieldReadOnlyParams = {
isRecordReadOnly: boolean;
isSystemObject?: boolean;
isFieldCustom: boolean;
isFieldFromStandardApplication?: boolean;
fieldMetadataItem: Pick<FieldMetadataItem, 'id' | 'isUIEditable'>;
objectPermissions: ObjectPermission;
fieldDefinition?: FieldDefinition<FieldMetadata>;
@@ -26,7 +26,7 @@ export const isRecordFieldReadOnly = ({
objectPermissions,
isRecordReadOnly,
isSystemObject,
isFieldCustom,
isFieldFromStandardApplication,
fieldMetadataItem,
fieldDefinition,
objectPermissionsByObjectMetadataId,
@@ -44,9 +44,14 @@ export const isRecordFieldReadOnly = ({
objectPermissionsByObjectMetadataId,
});
// Keep system-object standard fields read-only. If the application origin
// cannot be resolved yet, fail closed until metadata finishes loading.
const isReadOnlyStandardFieldOnSystemObject =
isSystemObject === true && isFieldFromStandardApplication !== false;
return (
isRecordReadOnly ||
(isSystemObject === true && !isFieldCustom) ||
isReadOnlyStandardFieldOnSystemObject ||
!(fieldMetadataItem.isUIEditable ?? true) ||
fieldReadOnlyByPermissions ||
oneToManyTargetReadOnly
@@ -1,4 +1,4 @@
import { useGetIsMetadataItemCustom } from '@/object-metadata/hooks/useGetIsMetadataItemCustom';
import { useGetIsMetadataItemFromStandardApplication } from '@/object-metadata/hooks/useGetIsMetadataItemFromStandardApplication';
import { isRecordFieldReadOnly } from '@/object-record/read-only/utils/isRecordFieldReadOnly';
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
import { StopPropagationContainer } from '@/object-record/record-board/record-board-card/components/StopPropagationContainer';
@@ -55,8 +55,8 @@ export const RecordBoardCardBody = () => {
const setRecordBoardCardHoverPosition = useSetAtomComponentState(
recordBoardCardHoverPositionComponentState,
);
const getIsMetadataItemCustom = useGetIsMetadataItemCustom();
const getIsMetadataItemFromStandardApplication =
useGetIsMetadataItemFromStandardApplication();
const handleMouseEnter = (index: number) => {
setRecordBoardCardHoverPosition(index);
@@ -78,10 +78,11 @@ export const RecordBoardCardBody = () => {
isRecordFieldReadOnly: isRecordFieldReadOnly({
isRecordReadOnly,
isSystemObject: objectMetadataItem.isSystem,
isFieldCustom: getIsMetadataItemCustom({
applicationId:
correspondingFieldDefinition.metadata.applicationId,
}),
isFieldFromStandardApplication:
getIsMetadataItemFromStandardApplication({
applicationId:
correspondingFieldDefinition.metadata.applicationId,
}),
objectPermissions,
fieldMetadataItem: {
id: recordField.fieldMetadataItemId,
@@ -1,4 +1,4 @@
import { useGetIsMetadataItemCustom } from '@/object-metadata/hooks/useGetIsMetadataItemCustom';
import { useGetIsMetadataItemFromStandardApplication } from '@/object-metadata/hooks/useGetIsMetadataItemFromStandardApplication';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { isRecordFieldReadOnly } from '@/object-record/read-only/utils/isRecordFieldReadOnly';
import { StopPropagationContainer } from '@/object-record/record-board/record-board-card/components/StopPropagationContainer';
@@ -64,8 +64,8 @@ export const RecordCalendarCardBody = ({
const setRecordCalendarCardHoverPosition = useSetAtomComponentState(
recordCalendarCardHoverPositionComponentState,
);
const getIsMetadataItemCustom = useGetIsMetadataItemCustom();
const getIsMetadataItemFromStandardApplication =
useGetIsMetadataItemFromStandardApplication();
const handleMouseEnter = (index: number) => {
setRecordCalendarCardHoverPosition(index);
@@ -87,10 +87,11 @@ export const RecordCalendarCardBody = ({
isRecordFieldReadOnly: isRecordFieldReadOnly({
isRecordReadOnly,
isSystemObject: objectMetadataItem.isSystem,
isFieldCustom: getIsMetadataItemCustom({
applicationId:
correspondingFieldDefinition.metadata.applicationId,
}),
isFieldFromStandardApplication:
getIsMetadataItemFromStandardApplication({
applicationId:
correspondingFieldDefinition.metadata.applicationId,
}),
objectPermissions,
fieldMetadataItem: {
id: recordField.fieldMetadataItemId,
@@ -1,5 +1,5 @@
import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell';
import { useGetIsMetadataItemCustom } from '@/object-metadata/hooks/useGetIsMetadataItemCustom';
import { useGetIsMetadataItemFromStandardApplication } from '@/object-metadata/hooks/useGetIsMetadataItemFromStandardApplication';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
@@ -58,8 +58,8 @@ export const RecordFieldList = ({
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const { objectMetadataItems } = useObjectMetadataItems();
const getIsMetadataItemCustom = useGetIsMetadataItemCustom();
const getIsMetadataItemFromStandardApplication =
useGetIsMetadataItemFromStandardApplication();
const { useUpdateOneObjectRecordMutation } = useRecordShowContainerActions({
objectNameSingular,
@@ -131,7 +131,10 @@ export const RecordFieldList = ({
objectPermissionsByObjectMetadataId,
objectMetadataId: objectMetadataItem.id,
}),
isFieldCustom: getIsMetadataItemCustom(fieldMetadataItem),
isFieldFromStandardApplication:
getIsMetadataItemFromStandardApplication(
fieldMetadataItem,
),
fieldMetadataItem: {
id: fieldMetadataItem.id,
isUIEditable: fieldMetadataItem.isUIEditable ?? true,
@@ -187,7 +190,8 @@ export const RecordFieldList = ({
objectPermissionsByObjectMetadataId,
objectMetadataId: objectMetadataItem.id,
}),
isFieldCustom: getIsMetadataItemCustom(fieldMetadataItem),
isFieldFromStandardApplication:
getIsMetadataItemFromStandardApplication(fieldMetadataItem),
fieldMetadataItem: {
id: fieldMetadataItem.id,
isUIEditable: fieldMetadataItem.isUIEditable ?? true,
@@ -267,7 +271,8 @@ export const RecordFieldList = ({
objectPermissionsByObjectMetadataId,
objectMetadataId: objectMetadataItem.id,
}),
isFieldCustom: getIsMetadataItemCustom(fieldMetadataItem),
isFieldFromStandardApplication:
getIsMetadataItemFromStandardApplication(fieldMetadataItem),
fieldMetadataItem: {
id: fieldMetadataItem.id,
isUIEditable: fieldMetadataItem.isUIEditable ?? true,
@@ -1,4 +1,4 @@
import { useGetIsMetadataItemCustom } from '@/object-metadata/hooks/useGetIsMetadataItemCustom';
import { useGetIsMetadataItemFromStandardApplication } from '@/object-metadata/hooks/useGetIsMetadataItemFromStandardApplication';
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
import { isRecordFieldReadOnly } from '@/object-record/read-only/utils/isRecordFieldReadOnly';
@@ -44,8 +44,8 @@ export const RecordTableCellFieldContextGeneric = ({
fieldDefinitionByFieldMetadataItemId[recordField.fieldMetadataItemId];
const updateRecord = useContext(RecordTableUpdateContext);
const getIsMetadataItemCustom = useGetIsMetadataItemCustom();
const getIsMetadataItemFromStandardApplication =
useGetIsMetadataItemFromStandardApplication();
let hasObjectReadPermissions = objectPermissions.canReadObjectRecords;
@@ -113,9 +113,10 @@ export const RecordTableCellFieldContextGeneric = ({
isRecordFieldReadOnly({
isRecordReadOnly: isRecordReadOnly ?? false,
isSystemObject: objectMetadataItem.isSystem,
isFieldCustom: getIsMetadataItemCustom({
applicationId: fieldDefinition.metadata.applicationId,
}),
isFieldFromStandardApplication:
getIsMetadataItemFromStandardApplication({
applicationId: fieldDefinition.metadata.applicationId,
}),
objectPermissions,
fieldMetadataItem: {
id: fieldDefinition.fieldMetadataId,
@@ -1,4 +1,4 @@
import { useGetIsMetadataItemCustom } from '@/object-metadata/hooks/useGetIsMetadataItemCustom';
import { useGetIsMetadataItemFromStandardApplication } from '@/object-metadata/hooks/useGetIsMetadataItemFromStandardApplication';
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
import { isRecordFieldReadOnly } from '@/object-record/read-only/utils/isRecordFieldReadOnly';
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
@@ -46,8 +46,8 @@ export const RecordTableCellFieldContextLabelIdentifier = ({
const hasObjectReadPermissions = objectPermissions.canReadObjectRecords;
const updateRecord = useContext(RecordTableUpdateContext);
const getIsMetadataItemCustom = useGetIsMetadataItemCustom();
const getIsMetadataItemFromStandardApplication =
useGetIsMetadataItemFromStandardApplication();
const fieldDefinition =
fieldDefinitionByFieldMetadataItemId[recordField.fieldMetadataItemId];
@@ -70,9 +70,10 @@ export const RecordTableCellFieldContextLabelIdentifier = ({
isRecordFieldReadOnly({
isRecordReadOnly: isRecordReadOnly ?? false,
isSystemObject: objectMetadataItem.isSystem,
isFieldCustom: getIsMetadataItemCustom({
applicationId: fieldDefinition.metadata.applicationId,
}),
isFieldFromStandardApplication:
getIsMetadataItemFromStandardApplication({
applicationId: fieldDefinition.metadata.applicationId,
}),
objectPermissions,
fieldMetadataItem: {
id: recordField.fieldMetadataItemId,