[permissions] Remove non-readable field from aggregate options (kanban) (#13559)

Non-readable fields should not be an option for aggregate, as they
currently were still in kanban headers.

<img width="769" height="733" alt="Capture d’écran 2025-08-01 à 18 16
16"
src="https://github.com/user-attachments/assets/475621a6-9a62-4577-8dd4-eb4a3ace36e2"
/>
This commit is contained in:
Marie
2025-08-04 09:17:55 +02:00
committed by GitHub
parent dc3c194f3e
commit 8d367f1ec3
3 changed files with 58 additions and 36 deletions
@@ -1,4 +1,6 @@
import { useDropdownContextStateManagement } from '@/dropdown-context-state-management/hooks/useDropdownContextStateManagement';
import { getReadRestrictedFieldMetadataIdsFromObjectPermissions } from '@/object-metadata/utils/getReadRestrictedFieldMetadataIdsFromObjectPermissions';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { RecordBoardColumnHeaderAggregateDropdownContext } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContext';
import { RecordBoardColumnHeaderAggregateDropdownFieldsContent } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownFieldsContent';
import { RecordBoardColumnHeaderAggregateDropdownMenuContent } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownMenuContent';
@@ -17,13 +19,27 @@ export const AggregateDropdownContent = () => {
context: RecordBoardColumnHeaderAggregateDropdownContext,
});
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const restrictedFieldMetadataIds =
getReadRestrictedFieldMetadataIdsFromObjectPermissions({
objectPermissions: [
objectPermissionsByObjectMetadataId[objectMetadataItem.id],
],
objectMetadataId: objectMetadataItem.id,
});
const readableFields = objectMetadataItem.fields.filter(
(field) => !restrictedFieldMetadataIds.includes(field.id),
);
switch (currentContentId) {
case 'countAggregateOperationsOptions': {
const availableAggregations: AvailableFieldsForAggregateOperation =
getAvailableFieldsIdsForAggregationFromObjectFields(
objectMetadataItem.fields,
COUNT_AGGREGATE_OPERATION_OPTIONS,
);
getAvailableFieldsIdsForAggregationFromObjectFields({
fields: readableFields,
targetAggregateOperations: COUNT_AGGREGATE_OPERATION_OPTIONS,
});
return (
<RecordBoardColumnHeaderAggregateDropdownOptionsContent
availableAggregations={availableAggregations}
@@ -33,10 +49,10 @@ export const AggregateDropdownContent = () => {
}
case 'percentAggregateOperationsOptions': {
const availableAggregations: AvailableFieldsForAggregateOperation =
getAvailableFieldsIdsForAggregationFromObjectFields(
objectMetadataItem.fields,
PERCENT_AGGREGATE_OPERATION_OPTIONS,
);
getAvailableFieldsIdsForAggregationFromObjectFields({
fields: readableFields,
targetAggregateOperations: PERCENT_AGGREGATE_OPERATION_OPTIONS,
});
return (
<RecordBoardColumnHeaderAggregateDropdownOptionsContent
availableAggregations={availableAggregations}
@@ -46,10 +62,13 @@ export const AggregateDropdownContent = () => {
}
case 'datesAggregateOperationOptions': {
const datesAvailableAggregations: AvailableFieldsForAggregateOperation =
getAvailableFieldsIdsForAggregationFromObjectFields(
objectMetadataItem.fields,
[DateAggregateOperations.EARLIEST, DateAggregateOperations.LATEST],
);
getAvailableFieldsIdsForAggregationFromObjectFields({
fields: readableFields,
targetAggregateOperations: [
DateAggregateOperations.EARLIEST,
DateAggregateOperations.LATEST,
],
});
return (
<RecordBoardColumnHeaderAggregateDropdownOptionsContent
availableAggregations={datesAvailableAggregations}
@@ -59,10 +78,10 @@ export const AggregateDropdownContent = () => {
}
case 'moreAggregateOperationOptions': {
const availableAggregationsWithoutDates: AvailableFieldsForAggregateOperation =
getAvailableFieldsIdsForAggregationFromObjectFields(
objectMetadataItem.fields,
NON_STANDARD_AGGREGATE_OPERATION_OPTIONS,
);
getAvailableFieldsIdsForAggregationFromObjectFields({
fields: readableFields,
targetAggregateOperations: NON_STANDARD_AGGREGATE_OPERATION_OPTIONS,
});
return (
<RecordBoardColumnHeaderAggregateDropdownOptionsContent
availableAggregations={availableAggregationsWithoutDates}
@@ -67,10 +67,10 @@ jest.mock(
describe('getAvailableFieldsIdsForAggregationFromObjectFields', () => {
it('should handle empty fields array', () => {
const result = getAvailableFieldsIdsForAggregationFromObjectFields(
[],
COUNT_AGGREGATE_OPERATION_OPTIONS,
);
const result = getAvailableFieldsIdsForAggregationFromObjectFields({
fields: [],
targetAggregateOperations: COUNT_AGGREGATE_OPERATION_OPTIONS,
});
COUNT_AGGREGATE_OPERATION_OPTIONS.forEach((operation) => {
expect(result[operation]).toEqual([]);
@@ -79,10 +79,10 @@ describe('getAvailableFieldsIdsForAggregationFromObjectFields', () => {
describe('with count aggregate operations', () => {
it('should include all fields', () => {
const result = getAvailableFieldsIdsForAggregationFromObjectFields(
FIELDS_MOCKS as FieldMetadataItem[],
COUNT_AGGREGATE_OPERATION_OPTIONS,
);
const result = getAvailableFieldsIdsForAggregationFromObjectFields({
fields: FIELDS_MOCKS as FieldMetadataItem[],
targetAggregateOperations: COUNT_AGGREGATE_OPERATION_OPTIONS,
});
expect(result.COUNT).toEqual(
expect.arrayContaining([
@@ -107,10 +107,10 @@ describe('getAvailableFieldsIdsForAggregationFromObjectFields', () => {
describe('with percentage aggregate operations', () => {
it('should include all fields', () => {
const result = getAvailableFieldsIdsForAggregationFromObjectFields(
FIELDS_MOCKS as FieldMetadataItem[],
PERCENT_AGGREGATE_OPERATION_OPTIONS,
);
const result = getAvailableFieldsIdsForAggregationFromObjectFields({
fields: FIELDS_MOCKS as FieldMetadataItem[],
targetAggregateOperations: PERCENT_AGGREGATE_OPERATION_OPTIONS,
});
PERCENT_AGGREGATE_OPERATION_OPTIONS.forEach((operation) => {
expect(result[operation]).toEqual([
@@ -132,10 +132,10 @@ describe('getAvailableFieldsIdsForAggregationFromObjectFields', () => {
describe('with non standard aggregate operations', () => {
it('should exclude non-numeric fields', () => {
const result = getAvailableFieldsIdsForAggregationFromObjectFields(
FIELDS_MOCKS as FieldMetadataItem[],
NON_STANDARD_AGGREGATE_OPERATION_OPTIONS,
);
const result = getAvailableFieldsIdsForAggregationFromObjectFields({
fields: FIELDS_MOCKS as FieldMetadataItem[],
targetAggregateOperations: NON_STANDARD_AGGREGATE_OPERATION_OPTIONS,
});
COUNT_AGGREGATE_OPERATION_OPTIONS.forEach((operation) => {
expect(result[operation]).toBeUndefined();
@@ -5,10 +5,13 @@ import { getAvailableAggregationsFromObjectFields } from '@/object-record/utils/
import { initializeAvailableFieldsForAggregateOperationMap } from '@/object-record/utils/initializeAvailableFieldsForAggregateOperationMap';
import { isDefined } from 'twenty-shared/utils';
export const getAvailableFieldsIdsForAggregationFromObjectFields = (
fields: FieldMetadataItem[],
targetAggregateOperations: ExtendedAggregateOperations[],
): AvailableFieldsForAggregateOperation => {
export const getAvailableFieldsIdsForAggregationFromObjectFields = ({
fields,
targetAggregateOperations,
}: {
fields: FieldMetadataItem[];
targetAggregateOperations: ExtendedAggregateOperations[];
}): AvailableFieldsForAggregateOperation => {
const aggregationMap = initializeAvailableFieldsForAggregateOperationMap(
targetAggregateOperations,
);