Current workspace member filter (#8016) (#9182)

New branch based on feedback in PR #8950 and issue #8016

---------

Co-authored-by: ad-elias <elias@autodiligence.com>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
eliasylonen
2024-12-23 18:55:13 +01:00
committed by GitHub
parent 49da7d2ca0
commit 86d74724fb
29 changed files with 347 additions and 155 deletions

View File

@ -15,6 +15,7 @@ import { DELETE_MAX_COUNT } from '@/object-record/constants/DeleteMaxCount';
import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords'; import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords';
import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRecords'; import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRecords';
import { FilterOperand } from '@/object-record/object-filter-dropdown/types/FilterOperand'; import { FilterOperand } from '@/object-record/object-filter-dropdown/types/FilterOperand';
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer'; import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
@ -52,10 +53,13 @@ export const useDeleteMultipleRecordsAction = ({
contextStoreFiltersComponentState, contextStoreFiltersComponentState,
); );
const { filterValueDependencies } = useFilterValueDependencies();
const graphqlFilter = computeContextStoreFilters( const graphqlFilter = computeContextStoreFilters(
contextStoreTargetedRecordsRule, contextStoreTargetedRecordsRule,
contextStoreFilters, contextStoreFilters,
objectMetadataItem, objectMetadataItem,
filterValueDependencies,
); );
const deletedAtFieldMetadata = objectMetadataItem.fields.find( const deletedAtFieldMetadata = objectMetadataItem.fields.find(

View File

@ -4,6 +4,7 @@ import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/s
import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters'; import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById'; import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
export const useFindManyRecordsSelectedInContextStore = ({ export const useFindManyRecordsSelectedInContextStore = ({
@ -30,10 +31,13 @@ export const useFindManyRecordsSelectedInContextStore = ({
instanceId, instanceId,
); );
const { filterValueDependencies } = useFilterValueDependencies();
const queryFilter = computeContextStoreFilters( const queryFilter = computeContextStoreFilters(
contextStoreTargetedRecordsRule, contextStoreTargetedRecordsRule,
contextStoreFilters, contextStoreFilters,
objectMetadataItem, objectMetadataItem,
filterValueDependencies,
); );
const { records, loading, totalCount } = useFindManyRecords({ const { records, loading, totalCount } = useFindManyRecords({

View File

@ -1,14 +1,20 @@
import { ContextStoreTargetedRecordsRule } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; import { ContextStoreTargetedRecordsRule } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters'; import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter'; import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
import { FilterValueDependencies } from '@/object-record/record-filter/types/FilterValueDependencies';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { expect } from '@storybook/test'; import { expect } from '@storybook/test';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
describe('computeContextStoreFilters', () => { describe('computeContextStoreFilters', () => {
const personObjectMetadataItem = generatedMockObjectMetadataItems.find( const personObjectMetadataItem = generatedMockObjectMetadataItems.find(
(item) => item.nameSingular === 'person', (item) => item.nameSingular === 'person',
)!; )!;
const mockFilterValueDependencies: FilterValueDependencies = {
currentWorkspaceMemberId: '32219445-f587-4c40-b2b1-6d3205ed96da',
};
it('should work for selection mode', () => { it('should work for selection mode', () => {
const contextStoreTargetedRecordsRule: ContextStoreTargetedRecordsRule = { const contextStoreTargetedRecordsRule: ContextStoreTargetedRecordsRule = {
mode: 'selection', mode: 'selection',
@ -19,6 +25,7 @@ describe('computeContextStoreFilters', () => {
contextStoreTargetedRecordsRule, contextStoreTargetedRecordsRule,
[], [],
personObjectMetadataItem, personObjectMetadataItem,
mockFilterValueDependencies,
); );
expect(filters).toEqual({ expect(filters).toEqual({
@ -61,6 +68,7 @@ describe('computeContextStoreFilters', () => {
contextStoreTargetedRecordsRule, contextStoreTargetedRecordsRule,
contextStoreFilters, contextStoreFilters,
personObjectMetadataItem, personObjectMetadataItem,
mockFilterValueDependencies,
); );
expect(filters).toEqual({ expect(filters).toEqual({

View File

@ -2,6 +2,7 @@ import { ContextStoreTargetedRecordsRule } from '@/context-store/states/contextS
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter'; import { RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter';
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter'; import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
import { FilterValueDependencies } from '@/object-record/record-filter/types/FilterValueDependencies';
import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter'; import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables'; import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables';
@ -9,12 +10,14 @@ export const computeContextStoreFilters = (
contextStoreTargetedRecordsRule: ContextStoreTargetedRecordsRule, contextStoreTargetedRecordsRule: ContextStoreTargetedRecordsRule,
contextStoreFilters: Filter[], contextStoreFilters: Filter[],
objectMetadataItem: ObjectMetadataItem, objectMetadataItem: ObjectMetadataItem,
filterValueDependencies: FilterValueDependencies,
) => { ) => {
let queryFilter: RecordGqlOperationFilter | undefined; let queryFilter: RecordGqlOperationFilter | undefined;
if (contextStoreTargetedRecordsRule.mode === 'exclusion') { if (contextStoreTargetedRecordsRule.mode === 'exclusion') {
queryFilter = makeAndFilterVariables([ queryFilter = makeAndFilterVariables([
computeViewRecordGqlOperationFilter( computeViewRecordGqlOperationFilter(
filterValueDependencies,
contextStoreFilters, contextStoreFilters,
objectMetadataItem?.fields ?? [], objectMetadataItem?.fields ?? [],
[], [],
@ -39,6 +42,7 @@ export const computeContextStoreFilters = (
}, },
} }
: computeViewRecordGqlOperationFilter( : computeViewRecordGqlOperationFilter(
filterValueDependencies,
contextStoreFilters, contextStoreFilters,
objectMetadataItem?.fields ?? [], objectMetadataItem?.fields ?? [],
[], [],

View File

@ -8,10 +8,10 @@ import { InternalDatePicker } from '@/ui/input/components/internal/date/componen
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { computeVariableDateViewFilterValue } from '@/views/view-filter-value/utils/computeVariableDateViewFilterValue'; import { computeVariableDateViewFilterValue } from '@/views/view-filter-value/utils/computeVariableDateViewFilterValue';
import { import {
resolveDateViewFilterValue,
VariableDateViewFilterValueDirection, VariableDateViewFilterValueDirection,
VariableDateViewFilterValueUnit, VariableDateViewFilterValueUnit,
} from '@/views/view-filter-value/utils/resolveDateViewFilterValue'; } from '@/views/view-filter-value/utils/resolveDateViewFilterValue';
import { resolveFilterValue } from '@/views/view-filter-value/utils/resolveFilterValue';
import { useState } from 'react'; import { useState } from 'react';
import { isDefined } from 'twenty-ui'; import { isDefined } from 'twenty-ui';
import { FieldMetadataType } from '~/generated-metadata/graphql'; import { FieldMetadataType } from '~/generated-metadata/graphql';
@ -37,7 +37,7 @@ export const ObjectFilterDropdownDateInput = () => {
| undefined; | undefined;
const initialFilterValue = selectedFilter const initialFilterValue = selectedFilter
? resolveFilterValue(selectedFilter) ? resolveDateViewFilterValue(selectedFilter)
: null; : null;
const [internalDate, setInternalDate] = useState<Date | null>( const [internalDate, setInternalDate] = useState<Date | null>(
initialFilterValue instanceof Date ? initialFilterValue : null, initialFilterValue instanceof Date ? initialFilterValue : null,
@ -98,7 +98,7 @@ export const ObjectFilterDropdownDateInput = () => {
selectedOperandInDropdown === ViewFilterOperand.IsRelative; selectedOperandInDropdown === ViewFilterOperand.IsRelative;
const resolvedValue = selectedFilter const resolvedValue = selectedFilter
? resolveFilterValue(selectedFilter) ? resolveDateViewFilterValue(selectedFilter)
: null; : null;
const relativeDate = const relativeDate =

View File

@ -0,0 +1,45 @@
import { StyledMultipleSelectDropdownAvatarChip } from '@/object-record/select/components/StyledMultipleSelectDropdownAvatarChip';
import { SelectableItem } from '@/object-record/select/types/SelectableItem';
import styled from '@emotion/styled';
import { MenuItemMultiSelectAvatar } from 'twenty-ui';
const StyledPinnedItemsContainer = styled.div`
display: flex;
flex-direction: column;
padding: ${({ theme }) => theme.spacing(1)};
`;
export const ObjectFilterDropdownRecordPinnedItems = (props: {
selectableItems: SelectableItem[];
onChange: (
selectableItem: SelectableItem,
isNewCheckedValue: boolean,
) => void;
}) => {
return (
<StyledPinnedItemsContainer>
{props.selectableItems.map((selectableItem) => {
return (
<MenuItemMultiSelectAvatar
key={selectableItem.id}
selected={selectableItem.isSelected}
onSelectChange={(newCheckedValue) => {
props.onChange(selectableItem, newCheckedValue);
}}
avatar={
<StyledMultipleSelectDropdownAvatarChip
className="avatar-icon-container"
name={selectableItem.name}
avatarUrl={selectableItem.avatarUrl}
LeftIcon={selectableItem.AvatarIcon}
avatarType={selectableItem.avatarType}
isIconInverted={selectableItem.isIconInverted}
placeholderColorSeed={selectableItem.id}
/>
}
/>
);
})}
</StyledPinnedItemsContainer>
);
};

View File

@ -2,16 +2,26 @@ import { useState } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { ObjectFilterDropdownRecordPinnedItems } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordPinnedItems';
import { CURRENT_WORKSPACE_MEMBER_SELECTABLE_ITEM_ID } from '@/object-record/object-filter-dropdown/constants/CurrentWorkspaceMemberSelectableItemId';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown'; import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope'; import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
import { MultipleSelectDropdown } from '@/object-record/select/components/MultipleSelectDropdown'; import { MultipleSelectDropdown } from '@/object-record/select/components/MultipleSelectDropdown';
import { useRecordsForSelect } from '@/object-record/select/hooks/useRecordsForSelect'; import { useRecordsForSelect } from '@/object-record/select/hooks/useRecordsForSelect';
import { SelectableItem } from '@/object-record/select/types/SelectableItem'; import { SelectableItem } from '@/object-record/select/types/SelectableItem';
import { useDeleteCombinedViewFilters } from '@/views/hooks/useDeleteCombinedViewFilters'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { RelationFilterValue } from '@/views/view-filter-value/types/RelationFilterValue';
import { relationFilterValueSchema } from '@/views/view-filter-value/validation-schemas/relationFilterValueSchema';
import { IconUserCircle } from 'twenty-ui';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
export const EMPTY_FILTER_VALUE = '[]'; export const EMPTY_FILTER_VALUE: string = JSON.stringify({
isCurrentWorkspaceMemberSelected: false,
selectedRecordIds: [],
} satisfies RelationFilterValue);
export const MAX_RECORDS_TO_DISPLAY = 3; export const MAX_RECORDS_TO_DISPLAY = 3;
type ObjectFilterDropdownRecordSelectProps = { type ObjectFilterDropdownRecordSelectProps = {
@ -26,15 +36,10 @@ export const ObjectFilterDropdownRecordSelect = ({
objectFilterDropdownSearchInputState, objectFilterDropdownSearchInputState,
selectedOperandInDropdownState, selectedOperandInDropdownState,
selectedFilterState, selectedFilterState,
setObjectFilterDropdownSelectedRecordIds,
objectFilterDropdownSelectedRecordIdsState, objectFilterDropdownSelectedRecordIdsState,
selectFilter, selectFilter,
emptyFilterButKeepDefinition,
} = useFilterDropdown(); } = useFilterDropdown();
const { deleteCombinedViewFilter } =
useDeleteCombinedViewFilters(viewComponentId);
const { currentViewWithCombinedFiltersAndSorts } = const { currentViewWithCombinedFiltersAndSorts } =
useGetCurrentView(viewComponentId); useGetCurrentView(viewComponentId);
@ -54,9 +59,26 @@ export const ObjectFilterDropdownRecordSelect = ({
const selectedFilter = useRecoilValue(selectedFilterState); const selectedFilter = useRecoilValue(selectedFilterState);
const { isCurrentWorkspaceMemberSelected } = relationFilterValueSchema
.catch({
isCurrentWorkspaceMemberSelected: false,
selectedRecordIds: [],
})
.parse(selectedFilter?.value);
const objectNameSingular = const objectNameSingular =
filterDefinitionUsedInDropdown?.relationObjectMetadataNameSingular; filterDefinitionUsedInDropdown?.relationObjectMetadataNameSingular;
if (!isDefined(objectNameSingular)) {
throw new Error('relationObjectMetadataNameSingular is not defined');
}
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular: objectNameSingular,
});
const objectLabelPlural = objectMetadataItem?.labelPlural;
if (!isDefined(objectNameSingular)) { if (!isDefined(objectNameSingular)) {
throw new Error('objectNameSingular is not defined'); throw new Error('objectNameSingular is not defined');
} }
@ -69,27 +91,53 @@ export const ObjectFilterDropdownRecordSelect = ({
limit: 10, limit: 10,
}); });
const currentWorkspaceMemberSelectableItem: SelectableItem = {
id: CURRENT_WORKSPACE_MEMBER_SELECTABLE_ITEM_ID,
name: 'Me',
isSelected: isCurrentWorkspaceMemberSelected,
AvatarIcon: IconUserCircle,
};
const pinnedSelectableItems: SelectableItem[] =
objectNameSingular === 'workspaceMember'
? [currentWorkspaceMemberSelectableItem]
: [];
const filteredPinnedSelectableItems = pinnedSelectableItems.filter((item) =>
item.name
.toLowerCase()
.includes(objectFilterDropdownSearchInput.toLowerCase()),
);
const handleMultipleRecordSelectChange = ( const handleMultipleRecordSelectChange = (
recordToSelect: SelectableItem, itemToSelect: SelectableItem,
newSelectedValue: boolean, isNewSelectedValue: boolean,
) => { ) => {
if (loading) { if (loading) {
return; return;
} }
const newSelectedRecordIds = newSelectedValue const isItemCurrentWorkspaceMember =
? [...objectFilterDropdownSelectedRecordIds, recordToSelect.id] itemToSelect.id === CURRENT_WORKSPACE_MEMBER_SELECTABLE_ITEM_ID;
: objectFilterDropdownSelectedRecordIds.filter(
(id) => id !== recordToSelect.id,
);
if (newSelectedRecordIds.length === 0) { const selectedRecordIdsWithAddedRecord = [
emptyFilterButKeepDefinition(); ...objectFilterDropdownSelectedRecordIds,
deleteCombinedViewFilter(fieldId); itemToSelect.id,
return; ];
} const selectedRecordIdsWithRemovedRecord =
objectFilterDropdownSelectedRecordIds.filter(
(id) => id !== itemToSelect.id,
);
setObjectFilterDropdownSelectedRecordIds(newSelectedRecordIds); const newSelectedRecordIds = isItemCurrentWorkspaceMember
? objectFilterDropdownSelectedRecordIds
: isNewSelectedValue
? selectedRecordIdsWithAddedRecord
: selectedRecordIdsWithRemovedRecord;
const newIsCurrentWorkspaceMemberSelected = isItemCurrentWorkspaceMember
? isNewSelectedValue
: isCurrentWorkspaceMemberSelected;
const selectedRecordNames = [ const selectedRecordNames = [
...recordsToSelect, ...recordsToSelect,
@ -103,19 +151,32 @@ export const ObjectFilterDropdownRecordSelect = ({
.filter((record) => newSelectedRecordIds.includes(record.id)) .filter((record) => newSelectedRecordIds.includes(record.id))
.map((record) => record.name); .map((record) => record.name);
const selectedPinnedItemNames = newIsCurrentWorkspaceMemberSelected
? [currentWorkspaceMemberSelectableItem.name]
: [];
const selectedItemNames = [
...selectedPinnedItemNames,
...selectedRecordNames,
];
const filterDisplayValue = const filterDisplayValue =
selectedRecordNames.length > MAX_RECORDS_TO_DISPLAY selectedItemNames.length > MAX_RECORDS_TO_DISPLAY
? `${selectedRecordNames.length} companies` ? `${selectedItemNames.length} ${objectLabelPlural.toLowerCase()}`
: selectedRecordNames.join(', '); : selectedItemNames.join(', ');
if ( if (
isDefined(filterDefinitionUsedInDropdown) && isDefined(filterDefinitionUsedInDropdown) &&
isDefined(selectedOperandInDropdown) isDefined(selectedOperandInDropdown)
) { ) {
const newFilterValue = const newFilterValue =
newSelectedRecordIds.length > 0 newSelectedRecordIds.length > 0 || newIsCurrentWorkspaceMemberSelected
? JSON.stringify(newSelectedRecordIds) ? JSON.stringify({
: EMPTY_FILTER_VALUE; isCurrentWorkspaceMemberSelected:
newIsCurrentWorkspaceMemberSelected,
selectedRecordIds: newSelectedRecordIds,
} satisfies RelationFilterValue)
: '';
const viewFilter = const viewFilter =
currentViewWithCombinedFiltersAndSorts?.viewFilters.find( currentViewWithCombinedFiltersAndSorts?.viewFilters.find(
@ -139,15 +200,26 @@ export const ObjectFilterDropdownRecordSelect = ({
}; };
return ( return (
<MultipleSelectDropdown <>
selectableListId="object-filter-record-select-id" {filteredPinnedSelectableItems.length > 0 && (
hotkeyScope={RelationPickerHotkeyScope.RelationPicker} <>
itemsToSelect={recordsToSelect} <ObjectFilterDropdownRecordPinnedItems
filteredSelectedItems={filteredSelectedRecords} selectableItems={filteredPinnedSelectableItems}
selectedItems={selectedRecords} onChange={handleMultipleRecordSelectChange}
onChange={handleMultipleRecordSelectChange} />
searchFilter={objectFilterDropdownSearchInput} <DropdownMenuSeparator />
loadingItems={loading} </>
/> )}
<MultipleSelectDropdown
selectableListId="object-filter-record-select-id"
hotkeyScope={RelationPickerHotkeyScope.RelationPicker}
itemsToSelect={recordsToSelect}
filteredSelectedItems={filteredSelectedRecords}
selectedItems={selectedRecords}
onChange={handleMultipleRecordSelectChange}
searchFilter={objectFilterDropdownSearchInput}
loadingItems={loading}
/>
</>
); );
}; };

View File

@ -0,0 +1,2 @@
export const CURRENT_WORKSPACE_MEMBER_SELECTABLE_ITEM_ID =
'CURRENT_WORKSPACE_MEMBER';

View File

@ -3,6 +3,7 @@ import { RecordBoardContext } from '@/object-record/record-board/contexts/Record
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext'; import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
import { buildRecordGqlFieldsAggregateForRecordBoard } from '@/object-record/record-board/record-board-column/utils/buildRecordGqlFieldsAggregateForRecordBoard'; import { buildRecordGqlFieldsAggregateForRecordBoard } from '@/object-record/record-board/record-board-column/utils/buildRecordGqlFieldsAggregateForRecordBoard';
import { computeAggregateValueAndLabel } from '@/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel'; import { computeAggregateValueAndLabel } from '@/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel';
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter'; import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState'; import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState';
import { recordIndexKanbanAggregateOperationState } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState'; import { recordIndexKanbanAggregateOperationState } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
@ -53,7 +54,11 @@ export const useAggregateRecordsForRecordBoardColumn = () => {
); );
const recordIndexFilters = useRecoilValue(recordIndexFiltersState); const recordIndexFilters = useRecoilValue(recordIndexFiltersState);
const { filterValueDependencies } = useFilterValueDependencies();
const requestFilters = computeViewRecordGqlOperationFilter( const requestFilters = computeViewRecordGqlOperationFilter(
filterValueDependencies,
recordIndexFilters, recordIndexFilters,
objectMetadataItem.fields, objectMetadataItem.fields,
recordIndexViewFilterGroups, recordIndexViewFilterGroups,

View File

@ -0,0 +1,16 @@
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { FilterValueDependencies } from '@/object-record/record-filter/types/FilterValueDependencies';
import { useRecoilValue } from 'recoil';
export const useFilterValueDependencies = (): {
filterValueDependencies: FilterValueDependencies;
} => {
const { id: currentWorkspaceMemberId } =
useRecoilValue(currentWorkspaceMemberState) ?? {};
return {
filterValueDependencies: {
currentWorkspaceMemberId,
},
};
};

View File

@ -0,0 +1,3 @@
export interface FilterValueDependencies {
currentWorkspaceMemberId?: string;
}

View File

@ -1,4 +1,5 @@
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter'; import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
import { FilterValueDependencies } from '@/object-record/record-filter/types/FilterValueDependencies';
import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter'; import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { getCompaniesMock } from '~/testing/mock-data/companies'; import { getCompaniesMock } from '~/testing/mock-data/companies';
@ -14,6 +15,10 @@ const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
(item) => item.nameSingular === 'person', (item) => item.nameSingular === 'person',
)!; )!;
const mockFilterValueDependencies: FilterValueDependencies = {
currentWorkspaceMemberId: '32219445-f587-4c40-b2b1-6d3205ed96da',
};
jest.useFakeTimers().setSystemTime(new Date('2020-01-01')); jest.useFakeTimers().setSystemTime(new Date('2020-01-01'));
describe('computeViewRecordGqlOperationFilter', () => { describe('computeViewRecordGqlOperationFilter', () => {
@ -38,6 +43,7 @@ describe('computeViewRecordGqlOperationFilter', () => {
}; };
const result = computeViewRecordGqlOperationFilter( const result = computeViewRecordGqlOperationFilter(
mockFilterValueDependencies,
[nameFilter], [nameFilter],
companyMockObjectMetadataItem.fields, companyMockObjectMetadataItem.fields,
[], [],
@ -90,6 +96,7 @@ describe('computeViewRecordGqlOperationFilter', () => {
}; };
const result = computeViewRecordGqlOperationFilter( const result = computeViewRecordGqlOperationFilter(
mockFilterValueDependencies,
[nameFilter, employeesFilter], [nameFilter, employeesFilter],
companyMockObjectMetadataItem.fields, companyMockObjectMetadataItem.fields,
[], [],
@ -176,6 +183,7 @@ describe('should work as expected for the different field types', () => {
}; };
const result = computeViewRecordGqlOperationFilter( const result = computeViewRecordGqlOperationFilter(
mockFilterValueDependencies,
[ [
addressFilterContains, addressFilterContains,
addressFilterDoesNotContain, addressFilterDoesNotContain,
@ -558,6 +566,7 @@ describe('should work as expected for the different field types', () => {
}; };
const result = computeViewRecordGqlOperationFilter( const result = computeViewRecordGqlOperationFilter(
mockFilterValueDependencies,
[ [
phonesFilterContains, phonesFilterContains,
phonesFilterDoesNotContain, phonesFilterDoesNotContain,
@ -759,6 +768,7 @@ describe('should work as expected for the different field types', () => {
}; };
const result = computeViewRecordGqlOperationFilter( const result = computeViewRecordGqlOperationFilter(
mockFilterValueDependencies,
[ [
emailsFilterContains, emailsFilterContains,
emailsFilterDoesNotContain, emailsFilterDoesNotContain,
@ -914,6 +924,7 @@ describe('should work as expected for the different field types', () => {
}; };
const result = computeViewRecordGqlOperationFilter( const result = computeViewRecordGqlOperationFilter(
mockFilterValueDependencies,
[ [
dateFilterIsAfter, dateFilterIsAfter,
dateFilterIsBefore, dateFilterIsBefore,
@ -1030,6 +1041,7 @@ describe('should work as expected for the different field types', () => {
}; };
const result = computeViewRecordGqlOperationFilter( const result = computeViewRecordGqlOperationFilter(
mockFilterValueDependencies,
[ [
employeesFilterIsGreaterThan, employeesFilterIsGreaterThan,
employeesFilterIsLessThan, employeesFilterIsLessThan,

View File

@ -28,14 +28,18 @@ import {
convertRatingToRatingValue, convertRatingToRatingValue,
} from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput'; } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput';
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter'; import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
import { FilterValueDependencies } from '@/object-record/record-filter/types/FilterValueDependencies';
import { getEmptyRecordGqlOperationFilter } from '@/object-record/record-filter/utils/getEmptyRecordGqlOperationFilter'; import { getEmptyRecordGqlOperationFilter } from '@/object-record/record-filter/utils/getEmptyRecordGqlOperationFilter';
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup'; import { ViewFilterGroup } from '@/views/types/ViewFilterGroup';
import { ViewFilterGroupLogicalOperator } from '@/views/types/ViewFilterGroupLogicalOperator'; import { ViewFilterGroupLogicalOperator } from '@/views/types/ViewFilterGroupLogicalOperator';
import { resolveFilterValue } from '@/views/view-filter-value/utils/resolveFilterValue'; import { resolveDateViewFilterValue } from '@/views/view-filter-value/utils/resolveDateViewFilterValue';
import { resolveSelectViewFilterValue } from '@/views/view-filter-value/utils/resolveSelectViewFilterValue';
import { relationFilterValueSchema } from '@/views/view-filter-value/validation-schemas/relationFilterValueSchema';
import { endOfDay, roundToNearestMinutes, startOfDay } from 'date-fns'; import { endOfDay, roundToNearestMinutes, startOfDay } from 'date-fns';
import { z } from 'zod'; import { z } from 'zod';
const computeFilterRecordGqlOperationFilter = ( const computeFilterRecordGqlOperationFilter = (
filterValueDependencies: FilterValueDependencies,
filter: Filter, filter: Filter,
fields: Pick<Field, 'id' | 'name'>[], fields: Pick<Field, 'id' | 'name'>[],
): RecordGqlOperationFilter | undefined => { ): RecordGqlOperationFilter | undefined => {
@ -124,7 +128,7 @@ const computeFilterRecordGqlOperationFilter = (
} }
case 'DATE': case 'DATE':
case 'DATE_TIME': { case 'DATE_TIME': {
const resolvedFilterValue = resolveFilterValue(filter); const resolvedFilterValue = resolveDateViewFilterValue(filter);
const now = roundToNearestMinutes(new Date()); const now = roundToNearestMinutes(new Date());
const date = const date =
resolvedFilterValue instanceof Date ? resolvedFilterValue : now; resolvedFilterValue instanceof Date ? resolvedFilterValue : now;
@ -157,11 +161,8 @@ const computeFilterRecordGqlOperationFilter = (
.object({ start: z.date(), end: z.date() }) .object({ start: z.date(), end: z.date() })
.safeParse(resolvedFilterValue).data; .safeParse(resolvedFilterValue).data;
const defaultDateRange = resolveFilterValue({ const defaultDateRange = resolveDateViewFilterValue({
value: 'PAST_1_DAY', value: 'PAST_1_DAY',
definition: {
type: 'DATE',
},
operand: ViewFilterOperand.IsRelative, operand: ViewFilterOperand.IsRelative,
}); });
@ -303,32 +304,41 @@ const computeFilterRecordGqlOperationFilter = (
} }
case 'RELATION': { case 'RELATION': {
if (!isEmptyOperand) { if (!isEmptyOperand) {
try { const { isCurrentWorkspaceMemberSelected, selectedRecordIds } =
JSON.parse(filter.value); relationFilterValueSchema.parse(filter.value);
} catch (e) {
throw new Error(
`Cannot parse filter value for RELATION filter : "${filter.value}"`,
);
}
const parsedRecordIds = JSON.parse(filter.value) as string[]; const recordIds = isCurrentWorkspaceMemberSelected
? [
...selectedRecordIds,
filterValueDependencies.currentWorkspaceMemberId,
]
: selectedRecordIds;
if (parsedRecordIds.length === 0) return; if (recordIds.length === 0) return;
switch (filter.operand) { switch (filter.operand) {
case ViewFilterOperand.Is: case ViewFilterOperand.Is:
return { return {
[correspondingField.name + 'Id']: { [correspondingField.name + 'Id']: {
in: parsedRecordIds, in: recordIds,
} as RelationFilter, } as RelationFilter,
}; };
case ViewFilterOperand.IsNot: { case ViewFilterOperand.IsNot: {
if (parsedRecordIds.length === 0) return; if (recordIds.length === 0) return;
return { return {
not: { or: [
[correspondingField.name + 'Id']: { {
in: parsedRecordIds, not: {
} as RelationFilter, [correspondingField.name + 'Id']: {
}, in: recordIds,
} as RelationFilter,
},
},
{
[correspondingField.name + 'Id']: {
is: 'NULL',
} as RelationFilter,
},
],
}; };
} }
default: default:
@ -611,9 +621,7 @@ const computeFilterRecordGqlOperationFilter = (
); );
} }
const options = resolveFilterValue( const options = resolveSelectViewFilterValue(filter);
filter as Filter & { definition: { type: 'MULTI_SELECT' } },
);
if (options.length === 0) return; if (options.length === 0) return;
@ -660,9 +668,7 @@ const computeFilterRecordGqlOperationFilter = (
filter.definition, filter.definition,
); );
} }
const options = resolveFilterValue( const options = resolveSelectViewFilterValue(filter);
filter as Filter & { definition: { type: 'SELECT' } },
);
if (options.length === 0) return; if (options.length === 0) return;
@ -869,6 +875,7 @@ const computeFilterRecordGqlOperationFilter = (
}; };
const computeViewFilterGroupRecordGqlOperationFilter = ( const computeViewFilterGroupRecordGqlOperationFilter = (
filterValueDependencies: FilterValueDependencies,
filters: Filter[], filters: Filter[],
fields: Pick<Field, 'id' | 'name'>[], fields: Pick<Field, 'id' | 'name'>[],
viewFilterGroups: ViewFilterGroup[], viewFilterGroups: ViewFilterGroup[],
@ -887,7 +894,13 @@ const computeViewFilterGroupRecordGqlOperationFilter = (
); );
const groupRecordGqlOperationFilters = groupFilters const groupRecordGqlOperationFilters = groupFilters
.map((filter) => computeFilterRecordGqlOperationFilter(filter, fields)) .map((filter) =>
computeFilterRecordGqlOperationFilter(
filterValueDependencies,
filter,
fields,
),
)
.filter(isDefined); .filter(isDefined);
const subGroupRecordGqlOperationFilters = viewFilterGroups const subGroupRecordGqlOperationFilters = viewFilterGroups
@ -897,6 +910,7 @@ const computeViewFilterGroupRecordGqlOperationFilter = (
) )
.map((subViewFilterGroup) => .map((subViewFilterGroup) =>
computeViewFilterGroupRecordGqlOperationFilter( computeViewFilterGroupRecordGqlOperationFilter(
filterValueDependencies,
filters, filters,
fields, fields,
viewFilterGroups, viewFilterGroups,
@ -932,6 +946,7 @@ const computeViewFilterGroupRecordGqlOperationFilter = (
}; };
export const computeViewRecordGqlOperationFilter = ( export const computeViewRecordGqlOperationFilter = (
filterValueDependencies: FilterValueDependencies,
filters: Filter[], filters: Filter[],
fields: Pick<Field, 'id' | 'name'>[], fields: Pick<Field, 'id' | 'name'>[],
viewFilterGroups: ViewFilterGroup[], viewFilterGroups: ViewFilterGroup[],
@ -939,7 +954,11 @@ export const computeViewRecordGqlOperationFilter = (
const regularRecordGqlOperationFilter: RecordGqlOperationFilter[] = filters const regularRecordGqlOperationFilter: RecordGqlOperationFilter[] = filters
.filter((filter) => !filter.viewFilterGroupId) .filter((filter) => !filter.viewFilterGroupId)
.map((regularFilter) => .map((regularFilter) =>
computeFilterRecordGqlOperationFilter(regularFilter, fields), computeFilterRecordGqlOperationFilter(
filterValueDependencies,
regularFilter,
fields,
),
) )
.filter(isDefined); .filter(isDefined);
@ -949,6 +968,7 @@ export const computeViewRecordGqlOperationFilter = (
const advancedRecordGqlOperationFilter = const advancedRecordGqlOperationFilter =
computeViewFilterGroupRecordGqlOperationFilter( computeViewFilterGroupRecordGqlOperationFilter(
filterValueDependencies,
filters, filters,
fields, fields,
viewFilterGroups, viewFilterGroups,

View File

@ -5,6 +5,7 @@ import { computeContextStoreFilters } from '@/context-store/utils/computeContext
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural'; import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useFindManyRecordIndexTableParams } from '@/object-record/record-index/hooks/useFindManyRecordIndexTableParams'; import { useFindManyRecordIndexTableParams } from '@/object-record/record-index/hooks/useFindManyRecordIndexTableParams';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
@ -40,6 +41,8 @@ export const RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect =
contextStoreFiltersComponentState, contextStoreFiltersComponentState,
); );
const { filterValueDependencies } = useFilterValueDependencies();
const { totalCount } = useFindManyRecords({ const { totalCount } = useFindManyRecords({
...findManyRecordsParams, ...findManyRecordsParams,
recordGqlFields: { recordGqlFields: {
@ -49,6 +52,7 @@ export const RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect =
contextStoreTargetedRecordsRule, contextStoreTargetedRecordsRule,
contextStoreFilters, contextStoreFilters,
objectMetadataItem, objectMetadataItem,
filterValueDependencies,
), ),
limit: 1, limit: 1,
skip: contextStoreTargetedRecordsRule.mode === 'selection', skip: contextStoreTargetedRecordsRule.mode === 'selection',

View File

@ -8,6 +8,7 @@ import { computeContextStoreFilters } from '@/context-store/utils/computeContext
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { EXPORT_TABLE_DATA_DEFAULT_PAGE_SIZE } from '@/object-record/object-options-dropdown/constants/ExportTableDataDefaultPageSize'; import { EXPORT_TABLE_DATA_DEFAULT_PAGE_SIZE } from '@/object-record/object-options-dropdown/constants/ExportTableDataDefaultPageSize';
import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard'; import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard';
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState'; import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
import { useFindManyRecordIndexTableParams } from '@/object-record/record-index/hooks/useFindManyRecordIndexTableParams'; import { useFindManyRecordIndexTableParams } from '@/object-record/record-index/hooks/useFindManyRecordIndexTableParams';
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector'; import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
@ -71,10 +72,13 @@ export const useExportFetchRecords = ({
contextStoreFiltersComponentState, contextStoreFiltersComponentState,
); );
const { filterValueDependencies } = useFilterValueDependencies();
const queryFilter = computeContextStoreFilters( const queryFilter = computeContextStoreFilters(
contextStoreTargetedRecordsRule, contextStoreTargetedRecordsRule,
contextStoreFilters, contextStoreFilters,
objectMetadataItem, objectMetadataItem,
filterValueDependencies,
); );
const findManyRecordsParams = useFindManyRecordIndexTableParams( const findManyRecordsParams = useFindManyRecordIndexTableParams(

View File

@ -1,5 +1,6 @@
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy'; import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter'; import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
import { useCurrentRecordGroupDefinition } from '@/object-record/record-group/hooks/useCurrentRecordGroupDefinition'; import { useCurrentRecordGroupDefinition } from '@/object-record/record-group/hooks/useCurrentRecordGroupDefinition';
import { useRecordGroupFilter } from '@/object-record/record-group/hooks/useRecordGroupFilter'; import { useRecordGroupFilter } from '@/object-record/record-group/hooks/useRecordGroupFilter';
@ -35,7 +36,10 @@ export const useFindManyRecordIndexTableParams = (
recordTableId, recordTableId,
); );
const { filterValueDependencies } = useFilterValueDependencies();
const stateFilter = computeViewRecordGqlOperationFilter( const stateFilter = computeViewRecordGqlOperationFilter(
filterValueDependencies,
tableFilters, tableFilters,
objectMetadataItem?.fields ?? [], objectMetadataItem?.fields ?? [],
tableViewFilterGroups, tableViewFilterGroups,

View File

@ -7,6 +7,7 @@ import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils
import { useSetRecordBoardRecordIds } from '@/object-record/record-board/hooks/useSetRecordBoardRecordIds'; import { useSetRecordBoardRecordIds } from '@/object-record/record-board/hooks/useSetRecordBoardRecordIds';
import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCompactModeActiveComponentState'; import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCompactModeActiveComponentState';
import { recordBoardFieldDefinitionsComponentState } from '@/object-record/record-board/states/recordBoardFieldDefinitionsComponentState'; import { recordBoardFieldDefinitionsComponentState } from '@/object-record/record-board/states/recordBoardFieldDefinitionsComponentState';
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter'; import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
import { useRecordBoardRecordGqlFields } from '@/object-record/record-index/hooks/useRecordBoardRecordGqlFields'; import { useRecordBoardRecordGqlFields } from '@/object-record/record-index/hooks/useRecordBoardRecordGqlFields';
import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/states/recordIndexFieldDefinitionsState'; import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/states/recordIndexFieldDefinitionsState';
@ -56,7 +57,11 @@ export const useLoadRecordIndexBoard = ({
const recordIndexFilters = useRecoilValue(recordIndexFiltersState); const recordIndexFilters = useRecoilValue(recordIndexFiltersState);
const recordIndexSorts = useRecoilValue(recordIndexSortsState); const recordIndexSorts = useRecoilValue(recordIndexSortsState);
const { filterValueDependencies } = useFilterValueDependencies();
const requestFilters = computeViewRecordGqlOperationFilter( const requestFilters = computeViewRecordGqlOperationFilter(
filterValueDependencies,
recordIndexFilters, recordIndexFilters,
objectMetadataItem?.fields ?? [], objectMetadataItem?.fields ?? [],
recordIndexViewFilterGroups, recordIndexViewFilterGroups,

View File

@ -5,6 +5,7 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy'; import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
import { useSetRecordIdsForColumn } from '@/object-record/record-board/hooks/useSetRecordIdsForColumn'; import { useSetRecordIdsForColumn } from '@/object-record/record-board/hooks/useSetRecordIdsForColumn';
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter'; import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState'; import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
import { useRecordBoardRecordGqlFields } from '@/object-record/record-index/hooks/useRecordBoardRecordGqlFields'; import { useRecordBoardRecordGqlFields } from '@/object-record/record-index/hooks/useRecordBoardRecordGqlFields';
@ -43,7 +44,10 @@ export const useLoadRecordIndexBoardColumn = ({
const recordIndexFilters = useRecoilValue(recordIndexFiltersState); const recordIndexFilters = useRecoilValue(recordIndexFiltersState);
const recordIndexSorts = useRecoilValue(recordIndexSortsState); const recordIndexSorts = useRecoilValue(recordIndexSortsState);
const { filterValueDependencies } = useFilterValueDependencies();
const requestFilters = computeViewRecordGqlOperationFilter( const requestFilters = computeViewRecordGqlOperationFilter(
filterValueDependencies,
recordIndexFilters, recordIndexFilters,
objectMetadataItem?.fields ?? [], objectMetadataItem?.fields ?? [],
recordIndexViewFilterGroups, recordIndexViewFilterGroups,

View File

@ -1,5 +1,6 @@
import { useAggregateRecords } from '@/object-record/hooks/useAggregateRecords'; import { useAggregateRecords } from '@/object-record/hooks/useAggregateRecords';
import { computeAggregateValueAndLabel } from '@/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel'; import { computeAggregateValueAndLabel } from '@/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel';
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter'; import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
import { useRecordGroupFilter } from '@/object-record/record-group/hooks/useRecordGroupFilter'; import { useRecordGroupFilter } from '@/object-record/record-group/hooks/useRecordGroupFilter';
import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState'; import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState';
@ -26,7 +27,11 @@ export const useAggregateRecordsForRecordTableColumnFooter = (
); );
const recordIndexFilters = useRecoilValue(recordIndexFiltersState); const recordIndexFilters = useRecoilValue(recordIndexFiltersState);
const { filterValueDependencies } = useFilterValueDependencies();
const requestFilters = computeViewRecordGqlOperationFilter( const requestFilters = computeViewRecordGqlOperationFilter(
filterValueDependencies,
recordIndexFilters, recordIndexFilters,
objectMetadataItem.fields, objectMetadataItem.fields,
recordIndexViewFilterGroups, recordIndexViewFilterGroups,

View File

@ -1,9 +1,9 @@
import styled from '@emotion/styled';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
import { AvatarChip, MenuItem, MenuItemMultiSelectAvatar } from 'twenty-ui'; import { MenuItem, MenuItemMultiSelectAvatar } from 'twenty-ui';
import { StyledMultipleSelectDropdownAvatarChip } from '@/object-record/select/components/StyledMultipleSelectDropdownAvatarChip';
import { SelectableItem } from '@/object-record/select/types/SelectableItem'; import { SelectableItem } from '@/object-record/select/types/SelectableItem';
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem'; import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
@ -13,16 +13,6 @@ import { useSelectableListStates } from '@/ui/layout/selectable-list/hooks/inter
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
const StyledAvatarChip = styled(AvatarChip)`
&.avatar-icon-container {
color: ${({ theme }) => theme.font.color.secondary};
gap: ${({ theme }) => theme.spacing(2)};
padding-left: 0px;
padding-right: 0px;
font-size: ${({ theme }) => theme.font.size.md};
}
`;
export const MultipleSelectDropdown = ({ export const MultipleSelectDropdown = ({
selectableListId, selectableListId,
hotkeyScope, hotkeyScope,
@ -129,7 +119,7 @@ export const MultipleSelectDropdown = ({
handleItemSelectChange(item, newCheckedValue); handleItemSelectChange(item, newCheckedValue);
}} }}
avatar={ avatar={
<StyledAvatarChip <StyledMultipleSelectDropdownAvatarChip
className="avatar-icon-container" className="avatar-icon-container"
name={item.name} name={item.name}
avatarUrl={item.avatarUrl} avatarUrl={item.avatarUrl}

View File

@ -0,0 +1,12 @@
import styled from '@emotion/styled';
import { AvatarChip } from 'twenty-ui';
export const StyledMultipleSelectDropdownAvatarChip = styled(AvatarChip)`
&.avatar-icon-container {
color: ${({ theme }) => theme.font.color.secondary};
gap: ${({ theme }) => theme.spacing(2)};
padding-left: 0px;
padding-right: 0px;
font-size: ${({ theme }) => theme.font.size.md};
}
`;

View File

@ -10,6 +10,7 @@ import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-sta
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { useUpsertCombinedViewFilters } from '@/views/hooks/useUpsertCombinedViewFilters'; import { useUpsertCombinedViewFilters } from '@/views/hooks/useUpsertCombinedViewFilters';
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
import { relationFilterValueSchema } from '@/views/view-filter-value/validation-schemas/relationFilterValueSchema';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
type ViewBarFilterEffectProps = { type ViewBarFilterEffectProps = {
@ -69,12 +70,14 @@ export const ViewBarFilterEffect = ({
filterDefinitionUsedInDropdown?.fieldMetadataId, filterDefinitionUsedInDropdown?.fieldMetadataId,
); );
const viewFilterSelectedRecords = isNonEmptyString( const { selectedRecordIds } = relationFilterValueSchema
viewFilterUsedInDropdown?.value, .catch({
) isCurrentWorkspaceMemberSelected: false,
? JSON.parse(viewFilterUsedInDropdown.value) selectedRecordIds: [],
: []; })
setObjectFilterDropdownSelectedRecordIds(viewFilterSelectedRecords); .parse(viewFilterUsedInDropdown?.value);
setObjectFilterDropdownSelectedRecordIds(selectedRecordIds);
} else if ( } else if (
isDefined(filterDefinitionUsedInDropdown) && isDefined(filterDefinitionUsedInDropdown) &&
['SELECT', 'MULTI_SELECT'].includes(filterDefinitionUsedInDropdown.type) ['SELECT', 'MULTI_SELECT'].includes(filterDefinitionUsedInDropdown.type)

View File

@ -1,5 +1,6 @@
import { useActiveFieldMetadataItems } from '@/object-metadata/hooks/useActiveFieldMetadataItems'; import { useActiveFieldMetadataItems } from '@/object-metadata/hooks/useActiveFieldMetadataItems';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
import { useViewOrDefaultViewFromPrefetchedViews } from '@/views/hooks/useViewOrDefaultViewFromPrefetchedViews'; import { useViewOrDefaultViewFromPrefetchedViews } from '@/views/hooks/useViewOrDefaultViewFromPrefetchedViews';
import { getQueryVariablesFromView } from '@/views/utils/getQueryVariablesFromView'; import { getQueryVariablesFromView } from '@/views/utils/getQueryVariablesFromView';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
@ -22,11 +23,14 @@ export const useQueryVariablesFromActiveFieldsOfViewOrDefaultView = ({
const isJsonFilterEnabled = useIsFeatureEnabled('IS_JSON_FILTER_ENABLED'); const isJsonFilterEnabled = useIsFeatureEnabled('IS_JSON_FILTER_ENABLED');
const { filterValueDependencies } = useFilterValueDependencies();
const { filter, orderBy } = getQueryVariablesFromView({ const { filter, orderBy } = getQueryVariablesFromView({
fieldMetadataItems: activeFieldMetadataItems, fieldMetadataItems: activeFieldMetadataItems,
objectMetadataItem, objectMetadataItem,
view, view,
isJsonFilterEnabled, isJsonFilterEnabled,
filterValueDependencies,
}); });
return { return {

View File

@ -3,6 +3,7 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { formatFieldMetadataItemsAsFilterDefinitions } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; import { formatFieldMetadataItemsAsFilterDefinitions } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
import { formatFieldMetadataItemsAsSortDefinitions } from '@/object-metadata/utils/formatFieldMetadataItemsAsSortDefinitions'; import { formatFieldMetadataItemsAsSortDefinitions } from '@/object-metadata/utils/formatFieldMetadataItemsAsSortDefinitions';
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy'; import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
import { FilterValueDependencies } from '@/object-record/record-filter/types/FilterValueDependencies';
import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter'; import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
import { View } from '@/views/types/View'; import { View } from '@/views/types/View';
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters'; import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
@ -14,11 +15,13 @@ export const getQueryVariablesFromView = ({
fieldMetadataItems, fieldMetadataItems,
objectMetadataItem, objectMetadataItem,
isJsonFilterEnabled, isJsonFilterEnabled,
filterValueDependencies,
}: { }: {
view: View | null | undefined; view: View | null | undefined;
fieldMetadataItems: FieldMetadataItem[]; fieldMetadataItems: FieldMetadataItem[];
objectMetadataItem: ObjectMetadataItem; objectMetadataItem: ObjectMetadataItem;
isJsonFilterEnabled: boolean; isJsonFilterEnabled: boolean;
filterValueDependencies: FilterValueDependencies;
}) => { }) => {
if (!isDefined(view)) { if (!isDefined(view)) {
return { return {
@ -39,6 +42,7 @@ export const getQueryVariablesFromView = ({
}); });
const filter = computeViewRecordGqlOperationFilter( const filter = computeViewRecordGqlOperationFilter(
filterValueDependencies,
mapViewFiltersToFilters(viewFilters, filterDefinitions), mapViewFiltersToFilters(viewFilters, filterDefinitions),
objectMetadataItem?.fields ?? [], objectMetadataItem?.fields ?? [],
viewFilterGroups ?? [], viewFilterGroups ?? [],

View File

@ -0,0 +1,4 @@
import { relationFilterValueSchema } from '@/views/view-filter-value/validation-schemas/relationFilterValueSchema';
import { z } from 'zod';
export type RelationFilterValue = z.infer<typeof relationFilterValueSchema>;

View File

@ -1,7 +0,0 @@
import { ViewFilter } from '@/views/types/ViewFilter';
export const resolveBooleanViewFilterValue = (
viewFilter: Pick<ViewFilter, 'value'>,
) => {
return viewFilter.value === 'true';
};

View File

@ -1,53 +0,0 @@
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
import { FilterableFieldType } from '@/object-record/object-filter-dropdown/types/FilterableFieldType';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { resolveNumberViewFilterValue } from '@/views/view-filter-value/utils/resolveNumberViewFilterValue';
import { resolveSelectViewFilterValue } from '@/views/view-filter-value/utils/resolveSelectViewFilterValue';
import {
resolveDateViewFilterValue,
ResolvedDateViewFilterValue,
} from './resolveDateViewFilterValue';
import { resolveBooleanViewFilterValue } from '@/views/view-filter-value/utils/resolveBooleanViewFilterValue';
type ResolvedFilterValue<
T extends FilterableFieldType,
O extends ViewFilterOperand,
> = T extends 'DATE' | 'DATE_TIME'
? ResolvedDateViewFilterValue<O>
: T extends 'NUMBER'
? ReturnType<typeof resolveNumberViewFilterValue>
: T extends 'SELECT' | 'MULTI_SELECT'
? string[]
: T extends 'BOOLEAN'
? boolean
: string;
type PartialFilter<
T extends FilterableFieldType,
O extends ViewFilterOperand,
> = Pick<Filter, 'value'> & {
definition: { type: T };
operand: O;
};
export const resolveFilterValue = <
T extends FilterableFieldType,
O extends ViewFilterOperand,
>(
filter: PartialFilter<T, O>,
) => {
switch (filter.definition.type) {
case 'DATE':
case 'DATE_TIME':
return resolveDateViewFilterValue(filter) as ResolvedFilterValue<T, O>;
case 'NUMBER':
return resolveNumberViewFilterValue(filter) as ResolvedFilterValue<T, O>;
case 'SELECT':
case 'MULTI_SELECT':
return resolveSelectViewFilterValue(filter) as ResolvedFilterValue<T, O>;
case 'BOOLEAN':
return resolveBooleanViewFilterValue(filter) as ResolvedFilterValue<T, O>;
default:
return filter.value as ResolvedFilterValue<T, O>;
}
};

View File

@ -1,7 +0,0 @@
import { ViewFilter } from '@/views/types/ViewFilter';
export const resolveNumberViewFilterValue = (
viewFilter: Pick<ViewFilter, 'value'>,
) => {
return viewFilter.value === '' ? null : +viewFilter.value;
};

View File

@ -0,0 +1,21 @@
import { z } from 'zod';
export const relationFilterValueSchema = z
.string()
.transform((value, ctx) => {
try {
return JSON.parse(value);
} catch (error) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: (error as Error).message,
});
return z.NEVER;
}
})
.pipe(
z.object({
isCurrentWorkspaceMemberSelected: z.boolean(),
selectedRecordIds: z.array(z.string()),
}),
);