fix rls create new record with composite (#17243)
## Context buildValueFromFilter was not handling composite filters which was needed for RLS. This PR implements that and fix some issues with RLS Tested with a few composite + relation fields <img width="559" height="206" alt="Screenshot 2026-01-19 at 15 38 42" src="https://github.com/user-attachments/assets/d64afa6e-3e12-4843-a215-a693665112c5" />
This commit is contained in:
+112
-34
@@ -7,12 +7,22 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
|
||||
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
|
||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/utils/isCompositeFieldType';
|
||||
import { buildRecordInputFromFilter } from '@/object-record/record-table/utils/buildRecordInputFromFilter';
|
||||
import { buildCompositeValueFromSubField } from '@/object-record/record-table/utils/buildValueFromFilter';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { isUndefined } from '@sniptt/guards';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { RelationType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { isDefined, isPlainObject } from 'twenty-shared/utils';
|
||||
|
||||
const mergeCompositeValues = (
|
||||
existingValue: unknown,
|
||||
incomingValue: unknown,
|
||||
) =>
|
||||
isPlainObject(existingValue) && isPlainObject(incomingValue)
|
||||
? { ...existingValue, ...incomingValue }
|
||||
: incomingValue;
|
||||
|
||||
export const useBuildRecordInputFromRLSPredicates = ({
|
||||
objectMetadataItem,
|
||||
@@ -37,9 +47,60 @@ export const useBuildRecordInputFromRLSPredicates = ({
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
|
||||
const buildRecordInputFromRLSPredicates = (): Partial<ObjectRecord> => {
|
||||
const recordInput: Partial<ObjectRecord> = {};
|
||||
const getRecordInputFieldName = (fieldMetadataItem: {
|
||||
name: string;
|
||||
type: string;
|
||||
settings?: { relationType?: RelationType };
|
||||
}) =>
|
||||
fieldMetadataItem.type === 'RELATION' &&
|
||||
fieldMetadataItem.settings?.relationType === RelationType.MANY_TO_ONE
|
||||
? `${fieldMetadataItem.name}Id`
|
||||
: fieldMetadataItem.name;
|
||||
|
||||
const getWorkspaceMemberFieldValue = ({
|
||||
workspaceMemberFieldMetadataId,
|
||||
workspaceMemberSubFieldName,
|
||||
}: {
|
||||
workspaceMemberFieldMetadataId?: string | null;
|
||||
workspaceMemberSubFieldName?: string | null;
|
||||
}) => {
|
||||
const workspaceMemberFieldMetadataItem =
|
||||
workspaceMemberObjectMetadataItem?.fields.find(
|
||||
(field) => field.id === workspaceMemberFieldMetadataId,
|
||||
);
|
||||
|
||||
if (!isDefined(workspaceMemberFieldMetadataItem)) {
|
||||
throw new Error(
|
||||
`Workspace member field metadata item not found for id: ${workspaceMemberFieldMetadataId}`,
|
||||
);
|
||||
}
|
||||
|
||||
let workspaceMemberFieldValue =
|
||||
currentWorkspaceMemberRecord?.[workspaceMemberFieldMetadataItem.name];
|
||||
|
||||
if (isCompositeFieldType(workspaceMemberFieldMetadataItem.type)) {
|
||||
if (!workspaceMemberSubFieldName) {
|
||||
throw new Error(
|
||||
`Workspace member subfield name not found for field: ${workspaceMemberFieldMetadataItem.name}`,
|
||||
);
|
||||
}
|
||||
|
||||
const compositeValue = workspaceMemberFieldValue as
|
||||
| Record<string, unknown>
|
||||
| undefined;
|
||||
workspaceMemberFieldValue = compositeValue?.[workspaceMemberSubFieldName];
|
||||
}
|
||||
|
||||
if (isUndefined(workspaceMemberFieldValue)) {
|
||||
throw new Error(
|
||||
`Current workspace member field value not found for field: ${workspaceMemberFieldMetadataItem.name}`,
|
||||
);
|
||||
}
|
||||
|
||||
return workspaceMemberFieldValue;
|
||||
};
|
||||
|
||||
const buildRecordInputFromRLSPredicates = (): Partial<ObjectRecord> => {
|
||||
const rlsPredicates = objectPermissions.rowLevelPermissionPredicates.filter(
|
||||
(predicate) => predicate.objectMetadataId === objectMetadataItem.id,
|
||||
);
|
||||
@@ -57,6 +118,8 @@ export const useBuildRecordInputFromRLSPredicates = ({
|
||||
)
|
||||
.filter(isDefined);
|
||||
|
||||
const recordInputFromDynamicFilters: Partial<ObjectRecord> = {};
|
||||
|
||||
rlsPredicatesAsRecordFilters.forEach((filter) => {
|
||||
const fieldMetadataItem = fieldMetadataItemMap.get(
|
||||
filter.fieldMetadataId,
|
||||
@@ -67,35 +130,42 @@ export const useBuildRecordInputFromRLSPredicates = ({
|
||||
}
|
||||
|
||||
if (isDefined(filter.rlsDynamicValue)) {
|
||||
const workspaceMemberFieldMetadataItem =
|
||||
workspaceMemberObjectMetadataItem?.fields.find(
|
||||
(field) =>
|
||||
field.id ===
|
||||
filter.rlsDynamicValue?.workspaceMemberFieldMetadataId,
|
||||
);
|
||||
const recordInputField = getRecordInputFieldName(fieldMetadataItem);
|
||||
const currentWorkspaceMemberFieldValue = getWorkspaceMemberFieldValue({
|
||||
workspaceMemberFieldMetadataId:
|
||||
filter.rlsDynamicValue?.workspaceMemberFieldMetadataId,
|
||||
workspaceMemberSubFieldName:
|
||||
filter.rlsDynamicValue?.workspaceMemberSubFieldName,
|
||||
});
|
||||
|
||||
if (!isDefined(workspaceMemberFieldMetadataItem)) {
|
||||
throw new Error(
|
||||
`Workspace member field metadata item not found for id: ${filter.rlsDynamicValue?.workspaceMemberFieldMetadataId}`,
|
||||
);
|
||||
if (isCompositeFieldType(fieldMetadataItem.type)) {
|
||||
if (!filter.subFieldName) {
|
||||
throw new Error(
|
||||
`Subfield name not found for composite field: ${fieldMetadataItem.name}`,
|
||||
);
|
||||
}
|
||||
|
||||
const compositeValue = buildCompositeValueFromSubField({
|
||||
compositeFieldType: fieldMetadataItem.type,
|
||||
subFieldName: filter.subFieldName,
|
||||
value: currentWorkspaceMemberFieldValue,
|
||||
});
|
||||
|
||||
if (!compositeValue) {
|
||||
throw new Error(
|
||||
`Composite subfield not found for field: ${fieldMetadataItem.name}`,
|
||||
);
|
||||
}
|
||||
|
||||
recordInputFromDynamicFilters[recordInputField] =
|
||||
mergeCompositeValues(
|
||||
recordInputFromDynamicFilters[recordInputField],
|
||||
compositeValue,
|
||||
);
|
||||
} else {
|
||||
recordInputFromDynamicFilters[recordInputField] =
|
||||
currentWorkspaceMemberFieldValue;
|
||||
}
|
||||
|
||||
const recordInputField =
|
||||
fieldMetadataItem.type === 'RELATION' &&
|
||||
fieldMetadataItem.settings?.relationType === RelationType.MANY_TO_ONE
|
||||
? `${fieldMetadataItem.name}Id`
|
||||
: fieldMetadataItem.name;
|
||||
|
||||
const currentWorkspaceMemberFieldValue =
|
||||
currentWorkspaceMemberRecord?.[workspaceMemberFieldMetadataItem.name];
|
||||
|
||||
if (isUndefined(currentWorkspaceMemberFieldValue)) {
|
||||
throw new Error(
|
||||
`Current workspace member field value not found for field: ${workspaceMemberFieldMetadataItem.name}`,
|
||||
);
|
||||
}
|
||||
|
||||
recordInput[recordInputField] = currentWorkspaceMemberFieldValue;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -105,16 +175,24 @@ export const useBuildRecordInputFromRLSPredicates = ({
|
||||
(filter) => !isDefined(filter.rlsDynamicValue),
|
||||
);
|
||||
|
||||
const recordInputFromFilters = buildRecordInputFromFilter({
|
||||
const recordInputFromStaticFilters = buildRecordInputFromFilter({
|
||||
currentRecordFilters: staticFilters,
|
||||
objectMetadataItem,
|
||||
currentWorkspaceMember: currentWorkspaceMember ?? undefined,
|
||||
});
|
||||
|
||||
return {
|
||||
...recordInput,
|
||||
...recordInputFromFilters,
|
||||
const mergedRecordInput: Partial<ObjectRecord> = {
|
||||
...recordInputFromDynamicFilters,
|
||||
};
|
||||
|
||||
Object.entries(recordInputFromStaticFilters).forEach(([key, value]) => {
|
||||
mergedRecordInput[key] = mergeCompositeValues(
|
||||
mergedRecordInput[key],
|
||||
value,
|
||||
);
|
||||
});
|
||||
|
||||
return mergedRecordInput;
|
||||
};
|
||||
|
||||
return { buildRecordInputFromRLSPredicates };
|
||||
|
||||
Reference in New Issue
Block a user