part 2 of filter/sort drill down from charts (#16013)

in this PR, we will handle simple filter translations (ie, fields that
use the contains operand)
This commit is contained in:
nitin
2025-11-25 18:56:55 +05:30
committed by GitHub
parent 2028a8f9be
commit 3be3c4e965
35 changed files with 1097 additions and 245 deletions
@@ -0,0 +1,69 @@
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/utils/isCompositeFieldType';
import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { type UrlSingleFilter } from '@/views/types/UrlSingleFilter';
import { isNonEmptyString } from '@sniptt/guards';
import { isDefined, isExpectedSubFieldName } from 'twenty-shared/utils';
import { v4 as uuidv4 } from 'uuid';
export const convertUrlSingleFilterToRecordFilter = ({
urlSingleFilter,
objectMetadataItem,
recordFilterGroupId,
positionInGroup,
}: {
urlSingleFilter: UrlSingleFilter;
objectMetadataItem: ObjectMetadataItem;
recordFilterGroupId?: string;
positionInGroup: number;
}): RecordFilter | null => {
const fieldMetadataItem = objectMetadataItem.fields.find(
(field) => field.name === urlSingleFilter.field,
);
if (!isDefined(fieldMetadataItem)) {
return null;
}
if (isNonEmptyString(urlSingleFilter.subField)) {
if (!isCompositeFieldType(fieldMetadataItem.type)) {
return null;
}
if (
!isExpectedSubFieldName(
fieldMetadataItem.type as Parameters<typeof isExpectedSubFieldName>[0],
urlSingleFilter.subField as Parameters<
typeof isExpectedSubFieldName
>[1],
urlSingleFilter.subField,
)
) {
return null;
}
}
const displayValue = urlSingleFilter.value;
const recordFilter: RecordFilter = {
id: uuidv4(),
fieldMetadataId: fieldMetadataItem.id,
value: urlSingleFilter.value,
displayValue,
type: fieldMetadataItem.type as RecordFilter['type'],
operand: urlSingleFilter.op,
label: fieldMetadataItem.label,
positionInRecordFilterGroup: positionInGroup,
};
if (isDefined(recordFilterGroupId)) {
recordFilter.recordFilterGroupId = recordFilterGroupId;
}
if (isNonEmptyString(urlSingleFilter.subField)) {
recordFilter.subFieldName =
urlSingleFilter.subField as RecordFilter['subFieldName'];
}
return recordFilter;
};
@@ -0,0 +1,85 @@
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { type RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup';
import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { type UrlFilterDeserializationResult } from '@/views/types/UrlFilterDeserializationResult';
import { type UrlRecursiveFilterGroup } from '@/views/types/UrlRecursiveFilterGroup';
import { convertUrlSingleFilterToRecordFilter } from '@/views/utils/convertUrlSingleFilterToRecordFilter';
import { isDefined } from 'twenty-shared/utils';
import { v4 as uuidv4 } from 'uuid';
export const deserializeUrlRecursiveFilterGroup = ({
urlRecursiveFilterGroup,
objectMetadataItem,
parentGroupId,
positionInParent,
}: {
urlRecursiveFilterGroup: UrlRecursiveFilterGroup;
objectMetadataItem: ObjectMetadataItem;
parentGroupId?: string;
positionInParent: number;
}): UrlFilterDeserializationResult => {
const groupId = uuidv4();
const recordFilters: RecordFilter[] = [];
const recordFilterGroups: RecordFilterGroup[] = [];
const groupFilters: RecordFilter[] = [];
let positionCounter = 0;
if (isDefined(urlRecursiveFilterGroup.filters)) {
for (const urlSingleFilter of urlRecursiveFilterGroup.filters) {
const recordFilter = convertUrlSingleFilterToRecordFilter({
urlSingleFilter,
objectMetadataItem,
recordFilterGroupId: groupId,
positionInGroup: positionCounter++,
});
if (isDefined(recordFilter)) {
groupFilters.push(recordFilter);
recordFilters.push(recordFilter);
}
}
}
const childGroupResults: RecordFilterGroup[] = [];
if (isDefined(urlRecursiveFilterGroup.groups)) {
for (const childUrlGroup of urlRecursiveFilterGroup.groups) {
const childResult = deserializeUrlRecursiveFilterGroup({
urlRecursiveFilterGroup: childUrlGroup,
objectMetadataItem,
parentGroupId: groupId,
positionInParent: positionCounter++,
});
if (
childResult.recordFilters.length > 0 ||
childResult.recordFilterGroups.length > 0
) {
recordFilters.push(...childResult.recordFilters);
childGroupResults.push(...childResult.recordFilterGroups);
recordFilterGroups.push(...childResult.recordFilterGroups);
}
}
}
const hasContent = groupFilters.length > 0 || childGroupResults.length > 0;
if (hasContent) {
const currentGroup: RecordFilterGroup = {
id: groupId,
logicalOperator: urlRecursiveFilterGroup.operator,
positionInRecordFilterGroup: positionInParent,
};
if (isDefined(parentGroupId)) {
currentGroup.parentRecordFilterGroupId = parentGroupId;
}
recordFilterGroups.push(currentGroup);
}
return {
recordFilters,
recordFilterGroups,
};
};
@@ -0,0 +1,16 @@
export const splitFieldNameIntoBaseAndSubField = (
fieldName: string,
): {
baseFieldName: string;
subFieldName?: string;
} => {
const fieldParts = fieldName.split('.');
const baseFieldName = fieldParts[0];
const subFieldName =
fieldParts.length > 1 ? fieldParts.slice(1).join('.') : undefined;
return {
baseFieldName,
subFieldName,
};
};