Refactored table filters to consume new currentRecordFilters component state (#9652)

This PR implements a first real use case, now currentRecordFilters
component state acts as the global record filter reference.

It is set by the view initially and can be reset to view filters state
at any point.

This new state is also modified by two new upsertRecordFilter /
removeRecordFilter hooks that will be drop-in replacement of the actual
upsertCombinedViewFilter and removeCombinediewFilter hooks.

This PR implements the logic to manipulate record filters but only reads
it to make the table find many request, all other features are still
relying on the old view filter implementation.

Advanced filters are ignored because they are hidden and because this
effort is made precisely to allow the completion of the advanced filters
feature.
This commit is contained in:
Lucas Bordeau
2025-01-23 11:09:44 +01:00
committed by GitHub
parent 3ab193f298
commit bddca09451
42 changed files with 1303 additions and 302 deletions
@@ -10,6 +10,7 @@ import { getActionBarIdFromActionMenuId } from '@/action-menu/utils/getActionBar
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState'; import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState'; import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState';
import { expect, jest } from '@storybook/jest'; import { expect, jest } from '@storybook/jest';
import { Meta, StoryObj } from '@storybook/react'; import { Meta, StoryObj } from '@storybook/react';
@@ -25,58 +26,63 @@ const meta: Meta<typeof RecordIndexActionMenuBar> = {
decorators: [ decorators: [
RouterDecorator, RouterDecorator,
(Story) => ( (Story) => (
<ContextStoreComponentInstanceContext.Provider <RecordFiltersComponentInstanceContext.Provider
value={{ instanceId: 'story-action-menu' }} value={{ instanceId: 'story-action-menu' }}
> >
<RecoilRoot <ContextStoreComponentInstanceContext.Provider
initializeState={({ set }) => { value={{ instanceId: 'story-action-menu' }}
set(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId: 'story-action-menu',
}),
{
mode: 'selection',
selectedRecordIds: ['1', '2', '3'],
},
);
set(
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
instanceId: 'story-action-menu',
}),
3,
);
const map = new Map<string, ActionMenuEntry>();
map.set('delete', {
isPinned: true,
scope: ActionMenuEntryScope.RecordSelection,
type: ActionMenuEntryType.Standard,
key: 'delete',
label: 'Delete',
position: 0,
Icon: IconTrash,
onClick: deleteMock,
});
set(
actionMenuEntriesComponentState.atomFamily({
instanceId: 'story-action-menu',
}),
map,
);
set(
isBottomBarOpenedComponentState.atomFamily({
instanceId: getActionBarIdFromActionMenuId('story-action-menu'),
}),
true,
);
}}
> >
<ActionMenuComponentInstanceContext.Provider <RecoilRoot
value={{ instanceId: 'story-action-menu' }} initializeState={({ set }) => {
set(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId: 'story-action-menu',
}),
{
mode: 'selection',
selectedRecordIds: ['1', '2', '3'],
},
);
set(
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
instanceId: 'story-action-menu',
}),
3,
);
const map = new Map<string, ActionMenuEntry>();
map.set('delete', {
isPinned: true,
scope: ActionMenuEntryScope.RecordSelection,
type: ActionMenuEntryType.Standard,
key: 'delete',
label: 'Delete',
position: 0,
Icon: IconTrash,
onClick: deleteMock,
});
set(
actionMenuEntriesComponentState.atomFamily({
instanceId: 'story-action-menu',
}),
map,
);
set(
isBottomBarOpenedComponentState.atomFamily({
instanceId:
getActionBarIdFromActionMenuId('story-action-menu'),
}),
true,
);
}}
> >
<Story /> <ActionMenuComponentInstanceContext.Provider
</ActionMenuComponentInstanceContext.Provider> value={{ instanceId: 'story-action-menu' }}
</RecoilRoot> >
</ContextStoreComponentInstanceContext.Provider> <Story />
</ActionMenuComponentInstanceContext.Provider>
</RecoilRoot>
</ContextStoreComponentInstanceContext.Provider>
</RecordFiltersComponentInstanceContext.Provider>
), ),
], ],
args: { args: {
@@ -9,6 +9,7 @@ import { useCommandMenuHotKeys } from '@/command-menu/hooks/useCommandMenuHotKey
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState'; import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { CommandMenuAnimationVariant } from '@/command-menu/types/CommandMenuAnimationVariant'; import { CommandMenuAnimationVariant } from '@/command-menu/types/CommandMenuAnimationVariant';
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope'; import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { workflowReactFlowRefState } from '@/workflow/workflow-diagram/states/workflowReactFlowRefState'; import { workflowReactFlowRefState } from '@/workflow/workflow-diagram/states/workflowReactFlowRefState';
@@ -74,37 +75,41 @@ export const CommandMenuContainer = ({
const theme = useTheme(); const theme = useTheme();
return ( return (
<ContextStoreComponentInstanceContext.Provider <RecordFiltersComponentInstanceContext.Provider
value={{ instanceId: 'command-menu' }} value={{ instanceId: 'command-menu' }}
> >
<ActionMenuComponentInstanceContext.Provider <ContextStoreComponentInstanceContext.Provider
value={{ instanceId: 'command-menu' }} value={{ instanceId: 'command-menu' }}
> >
<ActionMenuContext.Provider <ActionMenuComponentInstanceContext.Provider
value={{ value={{ instanceId: 'command-menu' }}
isInRightDrawer: false,
onActionExecutedCallback: toggleCommandMenu,
}}
> >
<RecordActionMenuEntriesSetter /> <ActionMenuContext.Provider
{isWorkflowEnabled && <RecordAgnosticActionsSetterEffect />} value={{
<ActionMenuConfirmationModals /> isInRightDrawer: false,
{isCommandMenuOpened && ( onActionExecutedCallback: toggleCommandMenu,
<StyledCommandMenu }}
data-testid="command-menu" >
ref={commandMenuRef} <RecordActionMenuEntriesSetter />
className="command-menu" {isWorkflowEnabled && <RecordAgnosticActionsSetterEffect />}
animate={targetVariantForAnimation} <ActionMenuConfirmationModals />
initial="closed" {isCommandMenuOpened && (
exit="closed" <StyledCommandMenu
variants={COMMAND_MENU_ANIMATION_VARIANTS} data-testid="command-menu"
transition={{ duration: theme.animation.duration.normal }} ref={commandMenuRef}
> className="command-menu"
{children} animate={targetVariantForAnimation}
</StyledCommandMenu> initial="closed"
)} exit="closed"
</ActionMenuContext.Provider> variants={COMMAND_MENU_ANIMATION_VARIANTS}
</ActionMenuComponentInstanceContext.Provider> transition={{ duration: theme.animation.duration.normal }}
</ContextStoreComponentInstanceContext.Provider> >
{children}
</StyledCommandMenu>
)}
</ActionMenuContext.Provider>
</ActionMenuComponentInstanceContext.Provider>
</ContextStoreComponentInstanceContext.Provider>
</RecordFiltersComponentInstanceContext.Provider>
); );
}; };
@@ -19,6 +19,7 @@ import { ActionMenuComponentInstanceContext } from '@/action-menu/states/context
import { CommandMenuRouter } from '@/command-menu/components/CommandMenuRouter'; import { CommandMenuRouter } from '@/command-menu/components/CommandMenuRouter';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState'; import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator'; import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { JestContextStoreSetter } from '~/testing/jest/JestContextStoreSetter'; import { JestContextStoreSetter } from '~/testing/jest/JestContextStoreSetter';
import { CommandMenu } from '../CommandMenu'; import { CommandMenu } from '../CommandMenu';
@@ -29,17 +30,21 @@ const openTimeout = 50;
const ContextStoreDecorator: Decorator = (Story) => { const ContextStoreDecorator: Decorator = (Story) => {
return ( return (
<ContextStoreComponentInstanceContext.Provider <RecordFiltersComponentInstanceContext.Provider
value={{ instanceId: 'command-menu' }} value={{ instanceId: 'command-menu' }}
> >
<ActionMenuComponentInstanceContext.Provider <ContextStoreComponentInstanceContext.Provider
value={{ instanceId: 'command-menu' }} value={{ instanceId: 'command-menu' }}
> >
<JestContextStoreSetter contextStoreCurrentObjectMetadataNameSingular="company"> <ActionMenuComponentInstanceContext.Provider
<Story /> value={{ instanceId: 'command-menu' }}
</JestContextStoreSetter> >
</ActionMenuComponentInstanceContext.Provider> <JestContextStoreSetter contextStoreCurrentObjectMetadataNameSingular="company">
</ContextStoreComponentInstanceContext.Provider> <Story />
</JestContextStoreSetter>
</ActionMenuComponentInstanceContext.Provider>
</ContextStoreComponentInstanceContext.Provider>
</RecordFiltersComponentInstanceContext.Provider>
); );
}; };
@@ -4,7 +4,7 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
import { useReadFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useReadFindManyRecordsQueryInCache'; import { useReadFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useReadFindManyRecordsQueryInCache';
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord'; import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { PREFETCH_CONFIG } from '@/prefetch/constants/PrefetchConfig'; import { PREFETCH_CONFIG } from '@/prefetch/constants/PrefetchConfig';
import { usePrefetchRunQuery } from '@/prefetch/hooks/internal/usePrefetchRunQuery'; import { useUpsertRecordsInCacheForPrefetchKey } from '@/prefetch/hooks/internal/useUpsertRecordsInCacheForPrefetchKey';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
export const useDeleteFavoriteFolder = () => { export const useDeleteFavoriteFolder = () => {
@@ -12,9 +12,10 @@ export const useDeleteFavoriteFolder = () => {
objectNameSingular: CoreObjectNameSingular.FavoriteFolder, objectNameSingular: CoreObjectNameSingular.FavoriteFolder,
}); });
const { upsertRecordsInCache } = usePrefetchRunQuery<Favorite>({ const { upsertRecordsInCache } =
prefetchKey: PrefetchKey.AllFavorites, useUpsertRecordsInCacheForPrefetchKey<Favorite>({
}); prefetchKey: PrefetchKey.AllFavorites,
});
const { objectMetadataItem } = useObjectMetadataItem({ const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular: objectNameSingular:
@@ -1,6 +1,6 @@
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { Favorite } from '@/favorites/types/Favorite'; import { Favorite } from '@/favorites/types/Favorite';
import { usePrefetchRunQuery } from '@/prefetch/hooks/internal/usePrefetchRunQuery'; import { useUpsertRecordsInCacheForPrefetchKey } from '@/prefetch/hooks/internal/useUpsertRecordsInCacheForPrefetchKey';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
@@ -33,7 +33,7 @@ export const usePrefetchedFavoritesData = (): PrefetchedFavoritesData => {
); );
const { upsertRecordsInCache: upsertFavorites } = const { upsertRecordsInCache: upsertFavorites } =
usePrefetchRunQuery<Favorite>({ useUpsertRecordsInCacheForPrefetchKey<Favorite>({
prefetchKey: PrefetchKey.AllFavorites, prefetchKey: PrefetchKey.AllFavorites,
}); });
@@ -1,6 +1,6 @@
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { FavoriteFolder } from '@/favorites/types/FavoriteFolder'; import { FavoriteFolder } from '@/favorites/types/FavoriteFolder';
import { usePrefetchRunQuery } from '@/prefetch/hooks/internal/usePrefetchRunQuery'; import { useUpsertRecordsInCacheForPrefetchKey } from '@/prefetch/hooks/internal/useUpsertRecordsInCacheForPrefetchKey';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
@@ -26,7 +26,7 @@ export const usePrefetchedFavoritesFoldersData =
); );
const { upsertRecordsInCache: upsertFavoriteFolders } = const { upsertRecordsInCache: upsertFavoriteFolders } =
usePrefetchRunQuery<FavoriteFolder>({ useUpsertRecordsInCacheForPrefetchKey<FavoriteFolder>({
prefetchKey: PrefetchKey.AllFavoritesFolders, prefetchKey: PrefetchKey.AllFavoritesFolders,
}); });
@@ -9,6 +9,7 @@ import { selectedFilterComponentState } from '@/object-record/object-filter-drop
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
import { getActorSourceMultiSelectOptions } from '@/object-record/object-filter-dropdown/utils/getActorSourceMultiSelectOptions'; import { getActorSourceMultiSelectOptions } from '@/object-record/object-filter-dropdown/utils/getActorSourceMultiSelectOptions';
import { useApplyRecordFilter } from '@/object-record/record-filter/hooks/useApplyRecordFilter'; import { useApplyRecordFilter } from '@/object-record/record-filter/hooks/useApplyRecordFilter';
import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter';
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 { SelectableItem } from '@/object-record/select/types/SelectableItem'; import { SelectableItem } from '@/object-record/select/types/SelectableItem';
@@ -61,6 +62,7 @@ export const ObjectFilterDropdownSourceSelect = ({
const { currentViewWithCombinedFiltersAndSorts } = const { currentViewWithCombinedFiltersAndSorts } =
useGetCurrentView(viewComponentId); useGetCurrentView(viewComponentId);
// TODO: this should be removed as it is not consistent across re-renders
const [fieldId] = useState(v4()); const [fieldId] = useState(v4());
const sourceTypes = getActorSourceMultiSelectOptions( const sourceTypes = getActorSourceMultiSelectOptions(
@@ -73,6 +75,8 @@ export const ObjectFilterDropdownSourceSelect = ({
const { emptyRecordFilter } = useEmptyRecordFilter(); const { emptyRecordFilter } = useEmptyRecordFilter();
const { removeRecordFilter } = useRemoveRecordFilter();
const handleMultipleItemSelectChange = ( const handleMultipleItemSelectChange = (
itemToSelect: SelectableItem, itemToSelect: SelectableItem,
newSelectedValue: boolean, newSelectedValue: boolean,
@@ -83,8 +87,13 @@ export const ObjectFilterDropdownSourceSelect = ({
(id) => id !== itemToSelect.id, (id) => id !== itemToSelect.id,
); );
if (!filterDefinitionUsedInDropdown) {
throw new Error('Filter definition used in dropdown should be defined');
}
if (newSelectedItemIds.length === 0) { if (newSelectedItemIds.length === 0) {
emptyRecordFilter(); emptyRecordFilter();
removeRecordFilter(filterDefinitionUsedInDropdown.fieldMetadataId);
deleteCombinedViewFilter(fieldId); deleteCombinedViewFilter(fieldId);
return; return;
} }
@@ -5,6 +5,7 @@ import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlur
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { MultipleFiltersDropdownButton } from '@/object-record/object-filter-dropdown/components/MultipleFiltersDropdownButton'; import { MultipleFiltersDropdownButton } from '@/object-record/object-filter-dropdown/components/MultipleFiltersDropdownButton';
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext'; import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext'; import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState'; import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
@@ -107,17 +108,21 @@ const meta: Meta<typeof MultipleFiltersDropdownButton> = {
recordIndexId: instanceId, recordIndexId: instanceId,
}} }}
> >
<ObjectFilterDropdownComponentInstanceContext.Provider <RecordFiltersComponentInstanceContext.Provider
value={{ instanceId }} value={{ instanceId }}
> >
<RecordTableComponentInstanceContext.Provider <ObjectFilterDropdownComponentInstanceContext.Provider
value={{ instanceId: instanceId, onColumnsChange: () => {} }} value={{ instanceId }}
> >
<ViewComponentInstanceContext.Provider value={{ instanceId }}> <RecordTableComponentInstanceContext.Provider
<Story /> value={{ instanceId: instanceId, onColumnsChange: () => {} }}
</ViewComponentInstanceContext.Provider> >
</RecordTableComponentInstanceContext.Provider> <ViewComponentInstanceContext.Provider value={{ instanceId }}>
</ObjectFilterDropdownComponentInstanceContext.Provider> <Story />
</ViewComponentInstanceContext.Provider>
</RecordTableComponentInstanceContext.Provider>
</ObjectFilterDropdownComponentInstanceContext.Provider>
</RecordFiltersComponentInstanceContext.Provider>
</RecordIndexContextProvider> </RecordIndexContextProvider>
); );
}, },
@@ -1,5 +1,6 @@
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext'; import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
import { RecordFilterDefinition } from '@/object-record/record-filter/types/RecordFilterDefinition'; import { RecordFilterDefinition } from '@/object-record/record-filter/types/RecordFilterDefinition';
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
export const availableFilterDefinitionsComponentState = createComponentStateV2< export const availableFilterDefinitionsComponentState = createComponentStateV2<
@@ -7,6 +7,7 @@ import { ObjectOptionsDropdownContent } from '@/object-record/object-options-dro
import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropdown/constants/ObjectOptionsDropdownId'; import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropdown/constants/ObjectOptionsDropdownId';
import { ObjectOptionsDropdownContext } from '@/object-record/object-options-dropdown/states/contexts/ObjectOptionsDropdownContext'; import { ObjectOptionsDropdownContext } from '@/object-record/object-options-dropdown/states/contexts/ObjectOptionsDropdownContext';
import { ObjectOptionsContentId } from '@/object-record/object-options-dropdown/types/ObjectOptionsContentId'; import { ObjectOptionsContentId } from '@/object-record/object-options-dropdown/types/ObjectOptionsContentId';
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext'; import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
@@ -37,22 +38,26 @@ const meta: Meta<typeof ObjectOptionsDropdownContent> = {
}, [setObjectMetadataItems]); }, [setObjectMetadataItems]);
return ( return (
<RecordTableComponentInstanceContext.Provider <RecordFiltersComponentInstanceContext.Provider
value={{ instanceId, onColumnsChange: () => {} }} value={{ instanceId: 'object-options-dropdown' }}
> >
<ViewComponentInstanceContext.Provider value={{ instanceId }}> <RecordTableComponentInstanceContext.Provider
<ContextStoreComponentInstanceContext.Provider value={{ instanceId, onColumnsChange: () => {} }}
value={{ instanceId }} >
> <ViewComponentInstanceContext.Provider value={{ instanceId }}>
<MemoryRouter <ContextStoreComponentInstanceContext.Provider
initialEntries={['/one', '/two', { pathname: '/three' }]} value={{ instanceId }}
initialIndex={1}
> >
<Story /> <MemoryRouter
</MemoryRouter> initialEntries={['/one', '/two', { pathname: '/three' }]}
</ContextStoreComponentInstanceContext.Provider> initialIndex={1}
</ViewComponentInstanceContext.Provider> >
</RecordTableComponentInstanceContext.Provider> <Story />
</MemoryRouter>
</ContextStoreComponentInstanceContext.Provider>
</ViewComponentInstanceContext.Provider>
</RecordTableComponentInstanceContext.Provider>
</RecordFiltersComponentInstanceContext.Provider>
); );
}, },
ObjectMetadataItemsDecorator, ObjectMetadataItemsDecorator,
@@ -0,0 +1,117 @@
import { renderHook } from '@testing-library/react';
import { act } from 'react-dom/test-utils';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
import { useRemoveRecordFilter } from '../useRemoveRecordFilter';
import { useUpsertRecordFilter } from '../useUpsertRecordFilter';
const Wrapper = getJestMetadataAndApolloMocksWrapper({
apolloMocks: [],
});
describe('useRemoveRecordFilter', () => {
it('should remove an existing filter', () => {
const { result } = renderHook(
() => {
const currentRecordFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);
const { upsertRecordFilter } = useUpsertRecordFilter();
const { removeRecordFilter } = useRemoveRecordFilter();
return {
upsertRecordFilter,
removeRecordFilter,
currentRecordFilters,
};
},
{
wrapper: Wrapper,
},
);
const filter: RecordFilter = {
id: 'filter-1',
fieldMetadataId: 'field-1',
value: 'test-value',
operand: ViewFilterOperand.Contains,
displayValue: 'test-value',
definition: {
type: 'TEXT',
fieldMetadataId: 'field-1',
label: 'Test Field',
iconName: 'IconText',
},
};
// First add a filter
act(() => {
result.current.upsertRecordFilter(filter);
});
expect(result.current.currentRecordFilters).toHaveLength(1);
expect(result.current.currentRecordFilters[0]).toEqual(filter);
// Then remove it
act(() => {
result.current.removeRecordFilter(filter.fieldMetadataId);
});
expect(result.current.currentRecordFilters).toHaveLength(0);
});
it('should not modify filters when trying to remove a non-existent filter', () => {
const { result } = renderHook(
() => {
const currentRecordFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);
const { upsertRecordFilter } = useUpsertRecordFilter();
const { removeRecordFilter } = useRemoveRecordFilter();
return {
upsertRecordFilter,
removeRecordFilter,
currentRecordFilters,
};
},
{
wrapper: Wrapper,
},
);
const filter: RecordFilter = {
id: 'filter-1',
fieldMetadataId: 'field-1',
value: 'test-value',
operand: ViewFilterOperand.Contains,
displayValue: 'test-value',
definition: {
type: 'TEXT',
fieldMetadataId: 'field-1',
label: 'Test Field',
iconName: 'IconText',
},
};
// Add a filter
act(() => {
result.current.upsertRecordFilter(filter);
});
expect(result.current.currentRecordFilters).toHaveLength(1);
// Try to remove a non-existent filter
act(() => {
result.current.removeRecordFilter('non-existent-field');
});
// Filter list should remain unchanged
expect(result.current.currentRecordFilters).toHaveLength(1);
expect(result.current.currentRecordFilters[0]).toEqual(filter);
});
});
@@ -0,0 +1,112 @@
import { renderHook } from '@testing-library/react';
import { act } from 'react-dom/test-utils';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
import { useUpsertRecordFilter } from '../useUpsertRecordFilter';
const Wrapper = getJestMetadataAndApolloMocksWrapper({
apolloMocks: [],
});
describe('useUpsertRecordFilter', () => {
it('should add a new filter when fieldMetadataId does not exist', () => {
const { result } = renderHook(
() => {
const currentRecordFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);
const { upsertRecordFilter } = useUpsertRecordFilter();
return { upsertRecordFilter, currentRecordFilters };
},
{
wrapper: Wrapper,
},
);
const newFilter: RecordFilter = {
id: 'filter-1',
fieldMetadataId: 'field-1',
value: 'test-value',
operand: ViewFilterOperand.Contains,
displayValue: 'test-value',
definition: {
type: 'TEXT',
fieldMetadataId: 'field-1',
label: 'Test Field',
iconName: 'IconText',
},
};
act(() => {
result.current.upsertRecordFilter(newFilter);
});
expect(result.current.currentRecordFilters).toHaveLength(1);
expect(result.current.currentRecordFilters[0]).toEqual(newFilter);
});
it('should update an existing filter when fieldMetadataId exists', () => {
const { result } = renderHook(
() => {
const currentRecordFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);
const { upsertRecordFilter } = useUpsertRecordFilter();
return { upsertRecordFilter, currentRecordFilters };
},
{
wrapper: Wrapper,
},
);
const initialFilter: RecordFilter = {
id: 'filter-1',
fieldMetadataId: 'field-1',
value: 'initial-value',
operand: ViewFilterOperand.Contains,
displayValue: 'initial-value',
definition: {
type: 'TEXT',
fieldMetadataId: 'field-1',
label: 'Test Field',
iconName: 'IconText',
},
};
const updatedFilter: RecordFilter = {
id: 'filter-1',
fieldMetadataId: 'field-1',
value: 'updated-value',
operand: ViewFilterOperand.Contains,
displayValue: 'updated-value',
definition: {
type: 'TEXT',
fieldMetadataId: 'field-1',
label: 'Test Field',
iconName: 'IconText',
},
};
act(() => {
result.current.upsertRecordFilter(initialFilter);
});
expect(result.current.currentRecordFilters).toHaveLength(1);
expect(result.current.currentRecordFilters[0]).toEqual(initialFilter);
act(() => {
result.current.upsertRecordFilter(updatedFilter);
});
expect(result.current.currentRecordFilters).toHaveLength(1);
expect(result.current.currentRecordFilters[0]).toEqual(updatedFilter);
});
});
@@ -1,7 +1,6 @@
import { onFilterSelectComponentState } from '@/object-record/object-filter-dropdown/states/onFilterSelectComponentState';
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState'; import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter';
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { useUpsertCombinedViewFilters } from '@/views/hooks/useUpsertCombinedViewFilters'; import { useUpsertCombinedViewFilters } from '@/views/hooks/useUpsertCombinedViewFilters';
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
@@ -14,32 +13,19 @@ export const useApplyRecordFilter = (componentInstanceId?: string) => {
componentInstanceId, componentInstanceId,
); );
const onFilterSelectCallbackState = useRecoilComponentCallbackStateV2( const { upsertRecordFilter } = useUpsertRecordFilter();
onFilterSelectComponentState,
componentInstanceId,
);
const applyRecordFilter = useRecoilCallback( const applyRecordFilter = useRecoilCallback(
({ set, snapshot }) => ({ set }) =>
(filter: RecordFilter | null) => { (filter: RecordFilter | null) => {
set(selectedFilterCallbackState, filter); set(selectedFilterCallbackState, filter);
const onFilterSelect = getSnapshotValue(
snapshot,
onFilterSelectCallbackState,
);
if (isDefined(filter)) { if (isDefined(filter)) {
upsertCombinedViewFilter(filter); upsertCombinedViewFilter(filter);
upsertRecordFilter(filter);
} }
onFilterSelect?.(filter);
}, },
[ [selectedFilterCallbackState, upsertCombinedViewFilter, upsertRecordFilter],
selectedFilterCallbackState,
onFilterSelectCallbackState,
upsertCombinedViewFilter,
],
); );
return { return {
@@ -0,0 +1,46 @@
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { useRecoilCallback } from 'recoil';
export const useRemoveRecordFilter = () => {
const currentRecordFiltersCallbackState = useRecoilComponentCallbackStateV2(
currentRecordFiltersComponentState,
);
const removeRecordFilter = useRecoilCallback(
({ set, snapshot }) =>
(fieldMetadataId: string) => {
const currentRecordFilters = getSnapshotValue(
snapshot,
currentRecordFiltersCallbackState,
);
const foundRecordFilterInCurrentRecordFilters =
currentRecordFilters.some(
(existingFilter) =>
existingFilter.fieldMetadataId === fieldMetadataId,
);
if (foundRecordFilterInCurrentRecordFilters) {
set(currentRecordFiltersCallbackState, (currentRecordFilters) => {
const newCurrentRecordFilters = [...currentRecordFilters];
const indexOfFilterToRemove = newCurrentRecordFilters.findIndex(
(existingFilter) =>
existingFilter.fieldMetadataId === fieldMetadataId,
);
newCurrentRecordFilters.splice(indexOfFilterToRemove, 1);
return newCurrentRecordFilters;
});
}
},
[currentRecordFiltersCallbackState],
);
return {
removeRecordFilter,
};
};
@@ -0,0 +1,54 @@
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { useRecoilCallback } from 'recoil';
export const useUpsertRecordFilter = () => {
const currentRecordFiltersCallbackState = useRecoilComponentCallbackStateV2(
currentRecordFiltersComponentState,
);
const upsertRecordFilter = useRecoilCallback(
({ set, snapshot }) =>
(filter: RecordFilter) => {
const currentRecordFilters = getSnapshotValue(
snapshot,
currentRecordFiltersCallbackState,
);
const foundRecordFilterInCurrentRecordFilters =
currentRecordFilters.some(
(existingFilter) =>
existingFilter.fieldMetadataId === filter.fieldMetadataId,
);
if (!foundRecordFilterInCurrentRecordFilters) {
set(currentRecordFiltersCallbackState, [
...currentRecordFilters,
filter,
]);
} else {
set(currentRecordFiltersCallbackState, (currentRecordFilters) => {
const newCurrentRecordFilters = [...currentRecordFilters];
const indexOfFilterToUpdate = newCurrentRecordFilters.findIndex(
(existingFilter) =>
existingFilter.fieldMetadataId === filter.fieldMetadataId,
);
newCurrentRecordFilters[indexOfFilterToUpdate] = {
...filter,
};
return newCurrentRecordFilters;
});
}
},
[currentRecordFiltersCallbackState],
);
return {
upsertRecordFilter,
};
};
@@ -0,0 +1,4 @@
import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext';
export const RecordFiltersComponentInstanceContext =
createComponentInstanceContext();
@@ -0,0 +1,11 @@
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
import { RecordFilter } from '../../record-filter/types/RecordFilter';
export const currentRecordFiltersComponentState = createComponentStateV2<
RecordFilter[]
>({
key: 'currentRecordFiltersComponentState',
defaultValue: [],
componentInstanceContext: RecordFiltersComponentInstanceContext,
});
@@ -0,0 +1,128 @@
import { isMatchingArrayFilter } from '../isMatchingArrayFilter';
describe('isMatchingArrayFilter', () => {
describe('is filter', () => {
it('should return true when checking for NULL and value is null', () => {
expect(
isMatchingArrayFilter({
arrayFilter: { is: 'NULL' },
value: null,
}),
).toBe(true);
});
it('should return false when checking for NULL and value is not null', () => {
expect(
isMatchingArrayFilter({
arrayFilter: { is: 'NULL' },
value: ['test'],
}),
).toBe(false);
});
it('should return true when checking for NOT_NULL and value is not null', () => {
expect(
isMatchingArrayFilter({
arrayFilter: { is: 'NOT_NULL' },
value: ['test'],
}),
).toBe(true);
});
it('should return false when checking for NOT_NULL and value is null', () => {
expect(
isMatchingArrayFilter({
arrayFilter: { is: 'NOT_NULL' },
value: null,
}),
).toBe(false);
});
});
describe('isEmptyArray filter', () => {
it('should return true when array is empty and checking for empty array', () => {
expect(
isMatchingArrayFilter({
arrayFilter: { isEmptyArray: true },
value: [],
}),
).toBe(true);
});
it('should return false when array is not empty and checking for empty array', () => {
expect(
isMatchingArrayFilter({
arrayFilter: { isEmptyArray: true },
value: ['test'],
}),
).toBe(false);
});
it('should return false when value is null and checking for empty array', () => {
expect(
isMatchingArrayFilter({
arrayFilter: { isEmptyArray: true },
value: null,
}),
).toBe(false);
});
});
describe('containsIlike filter', () => {
it('should return true when array contains item matching case-insensitive search', () => {
expect(
isMatchingArrayFilter({
arrayFilter: { containsIlike: 'TEST' },
value: ['test item'],
}),
).toBe(true);
});
it('should return false when array does not contain item matching search', () => {
expect(
isMatchingArrayFilter({
arrayFilter: { containsIlike: 'missing' },
value: ['test item'],
}),
).toBe(false);
});
it('should return false when value is null and using containsIlike', () => {
expect(
isMatchingArrayFilter({
arrayFilter: { containsIlike: 'test' },
value: null,
}),
).toBe(false);
});
it('should match partial strings case-insensitively', () => {
expect(
isMatchingArrayFilter({
arrayFilter: { containsIlike: 'TE' },
value: ['Test Item', 'Another Item'],
}),
).toBe(true);
});
});
describe('error handling', () => {
it('should throw error for invalid filter', () => {
expect(() =>
isMatchingArrayFilter({
arrayFilter: {},
value: [],
}),
).toThrow('Unexpected value for array filter');
});
it('should throw error for unknown filter type', () => {
expect(() =>
isMatchingArrayFilter({
arrayFilter: { unknownFilter: 'test' } as any,
value: [],
}),
).toThrow('Unexpected value for array filter');
});
});
});
@@ -1,10 +1,10 @@
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 { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
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';
import { tableFiltersComponentState } from '@/object-record/record-table/states/tableFiltersComponentState';
import { tableSortsComponentState } from '@/object-record/record-table/states/tableSortsComponentState'; import { tableSortsComponentState } from '@/object-record/record-table/states/tableSortsComponentState';
import { tableViewFilterGroupsComponentState } from '@/object-record/record-table/states/tableViewFilterGroupsComponentState'; import { tableViewFilterGroupsComponentState } from '@/object-record/record-table/states/tableViewFilterGroupsComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
@@ -27,20 +27,21 @@ export const useFindManyRecordIndexTableParams = (
tableViewFilterGroupsComponentState, tableViewFilterGroupsComponentState,
recordTableId, recordTableId,
); );
const tableFilters = useRecoilComponentValueV2(
tableFiltersComponentState,
recordTableId,
);
const tableSorts = useRecoilComponentValueV2( const tableSorts = useRecoilComponentValueV2(
tableSortsComponentState, tableSortsComponentState,
recordTableId, recordTableId,
); );
const currentRecordFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);
const { filterValueDependencies } = useFilterValueDependencies(); const { filterValueDependencies } = useFilterValueDependencies();
const stateFilter = computeViewRecordGqlOperationFilter( const stateFilter = computeViewRecordGqlOperationFilter(
filterValueDependencies, filterValueDependencies,
tableFilters, currentRecordFilters,
objectMetadataItem?.fields ?? [], objectMetadataItem?.fields ?? [],
tableViewFilterGroups, tableViewFilterGroups,
); );
@@ -5,6 +5,7 @@ import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/u
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useSelectFilterDefinitionUsedInDropdown } from '@/object-record/object-filter-dropdown/hooks/useSelectFilterDefinitionUsedInDropdown'; import { useSelectFilterDefinitionUsedInDropdown } from '@/object-record/object-filter-dropdown/hooks/useSelectFilterDefinitionUsedInDropdown';
import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter';
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { getRecordFilterOperandsForRecordFilterDefinition } from '@/object-record/record-filter/utils/getRecordFilterOperandsForRecordFilterDefinition'; import { getRecordFilterOperandsForRecordFilterDefinition } from '@/object-record/record-filter/utils/getRecordFilterOperandsForRecordFilterDefinition';
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState'; import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
@@ -33,6 +34,7 @@ export const useHandleToggleColumnFilter = ({
useColumnDefinitionsFromFieldMetadata(objectMetadataItem); useColumnDefinitionsFromFieldMetadata(objectMetadataItem);
const { upsertCombinedViewFilter } = useUpsertCombinedViewFilters(viewBarId); const { upsertCombinedViewFilter } = useUpsertCombinedViewFilters(viewBarId);
const { upsertRecordFilter } = useUpsertRecordFilter();
const openDropdown = useRecoilCallback(({ set }) => { const openDropdown = useRecoilCallback(({ set }) => {
return (dropdownId: string) => { return (dropdownId: string) => {
@@ -93,6 +95,8 @@ export const useHandleToggleColumnFilter = ({
value: '', value: '',
}; };
upsertRecordFilter(newFilter);
await upsertCombinedViewFilter(newFilter); await upsertCombinedViewFilter(newFilter);
selectFilterDefinitionUsedInDropdown({ filterDefinition }); selectFilterDefinitionUsedInDropdown({ filterDefinition });
@@ -107,6 +111,7 @@ export const useHandleToggleColumnFilter = ({
selectFilterDefinitionUsedInDropdown, selectFilterDefinitionUsedInDropdown,
currentViewWithCombinedFiltersAndSorts, currentViewWithCombinedFiltersAndSorts,
availableFilterDefinitions, availableFilterDefinitions,
upsertRecordFilter,
], ],
); );
@@ -4,6 +4,7 @@ import { v4 } from 'uuid';
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata'; import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter';
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { isSoftDeleteFilterActiveComponentState } from '@/object-record/record-table/states/isSoftDeleteFilterActiveComponentState'; import { isSoftDeleteFilterActiveComponentState } from '@/object-record/record-table/states/isSoftDeleteFilterActiveComponentState';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
@@ -36,6 +37,8 @@ export const useHandleToggleTrashColumnFilter = ({
viewBarId, viewBarId,
); );
const { upsertRecordFilter } = useUpsertRecordFilter();
const handleToggleTrashColumnFilter = useCallback(() => { const handleToggleTrashColumnFilter = useCallback(() => {
const trashFieldMetadata = objectMetadataItem.fields.find( const trashFieldMetadata = objectMetadataItem.fields.find(
(field: { name: string }) => field.name === 'deletedAt', (field: { name: string }) => field.name === 'deletedAt',
@@ -69,8 +72,14 @@ export const useHandleToggleTrashColumnFilter = ({
value: '', value: '',
}; };
upsertRecordFilter(newFilter);
upsertCombinedViewFilter(newFilter); upsertCombinedViewFilter(newFilter);
}, [columnDefinitions, objectMetadataItem, upsertCombinedViewFilter]); }, [
columnDefinitions,
objectMetadataItem,
upsertCombinedViewFilter,
upsertRecordFilter,
]);
const toggleSoftDeleteFilterState = useRecoilCallback( const toggleSoftDeleteFilterState = useRecoilCallback(
({ set }) => ({ set }) =>
@@ -2,6 +2,7 @@ import { useRecoilValue } from 'recoil';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
import { isNewViewableRecordLoadingState } from '@/object-record/record-right-drawer/states/isNewViewableRecordLoading'; import { isNewViewableRecordLoadingState } from '@/object-record/record-right-drawer/states/isNewViewableRecordLoading';
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState'; import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState'; import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState';
@@ -42,29 +43,33 @@ export const RightDrawerRecord = () => {
); );
return ( return (
<ContextStoreComponentInstanceContext.Provider <RecordFiltersComponentInstanceContext.Provider
value={{ value={{ instanceId: `record-show-${objectRecordId}` }}
instanceId: `record-show-${objectRecordId}`,
}}
> >
<ActionMenuComponentInstanceContext.Provider <ContextStoreComponentInstanceContext.Provider
value={{ instanceId: `record-show-${objectRecordId}` }} value={{
instanceId: `record-show-${objectRecordId}`,
}}
> >
<StyledRightDrawerRecord isMobile={isMobile}> <ActionMenuComponentInstanceContext.Provider
<RecordFieldValueSelectorContextProvider> value={{ instanceId: `record-show-${objectRecordId}` }}
{!isNewViewableRecordLoading && ( >
<RecordValueSetterEffect recordId={objectRecordId} /> <StyledRightDrawerRecord isMobile={isMobile}>
)} <RecordFieldValueSelectorContextProvider>
<RecordShowContainer {!isNewViewableRecordLoading && (
objectNameSingular={objectNameSingular} <RecordValueSetterEffect recordId={objectRecordId} />
objectRecordId={objectRecordId} )}
loading={false} <RecordShowContainer
isInRightDrawer={true} objectNameSingular={objectNameSingular}
isNewRightDrawerItemLoading={isNewViewableRecordLoading} objectRecordId={objectRecordId}
/> loading={false}
</RecordFieldValueSelectorContextProvider> isInRightDrawer={true}
</StyledRightDrawerRecord> isNewRightDrawerItemLoading={isNewViewableRecordLoading}
</ActionMenuComponentInstanceContext.Provider> />
</ContextStoreComponentInstanceContext.Provider> </RecordFieldValueSelectorContextProvider>
</StyledRightDrawerRecord>
</ActionMenuComponentInstanceContext.Provider>
</ContextStoreComponentInstanceContext.Provider>
</RecordFiltersComponentInstanceContext.Provider>
); );
}; };
@@ -1,6 +1,7 @@
import { IconFilterOff } from 'twenty-ui'; import { IconFilterOff } from 'twenty-ui';
import { useObjectLabel } from '@/object-metadata/hooks/useObjectLabel'; import { useObjectLabel } from '@/object-metadata/hooks/useObjectLabel';
import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter';
import { useHandleToggleTrashColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleTrashColumnFilter'; import { useHandleToggleTrashColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleTrashColumnFilter';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { RecordTableEmptyStateDisplay } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay'; import { RecordTableEmptyStateDisplay } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay';
@@ -25,14 +26,22 @@ export const RecordTableEmptyStateSoftDelete = () => {
viewBarId: recordTableId, viewBarId: recordTableId,
}); });
const { removeRecordFilter } = useRemoveRecordFilter();
const handleButtonClick = async () => { const handleButtonClick = async () => {
deleteCombinedViewFilter( const deletedFilter = tableFilters.find(
tableFilters.find( (filter) =>
(filter) => filter.definition.label === 'Deleted' &&
filter.definition.label === 'Deleted' && filter.operand === 'isNotEmpty',
filter.operand === 'isNotEmpty',
)?.id ?? '',
); );
if (!deletedFilter) {
throw new Error('Deleted filter not found');
}
removeRecordFilter(deletedFilter.fieldMetadataId);
deleteCombinedViewFilter(deletedFilter.id);
toggleSoftDeleteFilterState(false); toggleSoftDeleteFilterState(false);
}; };
@@ -7,7 +7,7 @@ import { FavoriteFolder } from '@/favorites/types/FavoriteFolder';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { useCombinedFindManyRecords } from '@/object-record/multiple-objects/hooks/useCombinedFindManyRecords'; import { useCombinedFindManyRecords } from '@/object-record/multiple-objects/hooks/useCombinedFindManyRecords';
import { PREFETCH_CONFIG } from '@/prefetch/constants/PrefetchConfig'; import { PREFETCH_CONFIG } from '@/prefetch/constants/PrefetchConfig';
import { usePrefetchRunQuery } from '@/prefetch/hooks/internal/usePrefetchRunQuery'; import { useUpsertRecordsInCacheForPrefetchKey } from '@/prefetch/hooks/internal/useUpsertRecordsInCacheForPrefetchKey';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { View } from '@/views/types/View'; import { View } from '@/views/types/View';
import { useIsWorkspaceActivationStatusSuspended } from '@/workspace/hooks/useIsWorkspaceActivationStatusSuspended'; import { useIsWorkspaceActivationStatusSuspended } from '@/workspace/hooks/useIsWorkspaceActivationStatusSuspended';
@@ -19,16 +19,16 @@ export const PrefetchRunQueriesEffect = () => {
const isWorkspaceSuspended = useIsWorkspaceActivationStatusSuspended(); const isWorkspaceSuspended = useIsWorkspaceActivationStatusSuspended();
const { upsertRecordsInCache: upsertViewsInCache } = const { upsertRecordsInCache: upsertViewsInCache } =
usePrefetchRunQuery<View>({ useUpsertRecordsInCacheForPrefetchKey<View>({
prefetchKey: PrefetchKey.AllViews, prefetchKey: PrefetchKey.AllViews,
}); });
const { upsertRecordsInCache: upsertFavoritesInCache } = const { upsertRecordsInCache: upsertFavoritesInCache } =
usePrefetchRunQuery<Favorite>({ useUpsertRecordsInCacheForPrefetchKey<Favorite>({
prefetchKey: PrefetchKey.AllFavorites, prefetchKey: PrefetchKey.AllFavorites,
}); });
const { upsertRecordsInCache: upsertFavoritesFoldersInCache } = const { upsertRecordsInCache: upsertFavoritesFoldersInCache } =
usePrefetchRunQuery<FavoriteFolder>({ useUpsertRecordsInCacheForPrefetchKey<FavoriteFolder>({
prefetchKey: PrefetchKey.AllFavoritesFolders, prefetchKey: PrefetchKey.AllFavoritesFolders,
}); });
const { objectMetadataItems } = useObjectMetadataItems(); const { objectMetadataItems } = useObjectMetadataItems();
@@ -11,7 +11,7 @@ export type UsePrefetchRunQuery = {
prefetchKey: PrefetchKey; prefetchKey: PrefetchKey;
}; };
export const usePrefetchRunQuery = <T extends ObjectRecord>({ export const useUpsertRecordsInCacheForPrefetchKey = <T extends ObjectRecord>({
prefetchKey, prefetchKey,
}: UsePrefetchRunQuery) => { }: UsePrefetchRunQuery) => {
const setPrefetchDataIsLoaded = useSetRecoilState( const setPrefetchDataIsLoaded = useSetRecoilState(
@@ -45,7 +45,6 @@ export const usePrefetchRunQuery = <T extends ObjectRecord>({
return { return {
objectMetadataItem, objectMetadataItem,
setPrefetchDataIsLoaded,
upsertRecordsInCache, upsertRecordsInCache,
}; };
}; };
@@ -3,6 +3,7 @@ import styled from '@emotion/styled';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext'; import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
import { RecordTableWithWrappers } from '@/object-record/record-table/components/RecordTableWithWrappers'; import { RecordTableWithWrappers } from '@/object-record/record-table/components/RecordTableWithWrappers';
import { SignInBackgroundMockContainerEffect } from '@/sign-in-background-mock/components/SignInBackgroundMockContainerEffect'; import { SignInBackgroundMockContainerEffect } from '@/sign-in-background-mock/components/SignInBackgroundMockContainerEffect';
@@ -41,30 +42,36 @@ export const SignInBackgroundMockContainer = () => {
<ViewComponentInstanceContext.Provider <ViewComponentInstanceContext.Provider
value={{ instanceId: recordIndexId }} value={{ instanceId: recordIndexId }}
> >
<ContextStoreComponentInstanceContext.Provider <RecordFiltersComponentInstanceContext.Provider
value={{ instanceId: recordIndexId }} value={{ instanceId: recordIndexId }}
> >
<ActionMenuComponentInstanceContext.Provider <ContextStoreComponentInstanceContext.Provider
value={{ instanceId: recordIndexId }} value={{
instanceId: recordIndexId,
}}
> >
<ViewBar <ActionMenuComponentInstanceContext.Provider
viewBarId={viewBarId} value={{ instanceId: recordIndexId }}
onCurrentViewChange={() => {}} >
optionsDropdownButton={<></>} <ViewBar
/> viewBarId={viewBarId}
<SignInBackgroundMockContainerEffect onCurrentViewChange={() => {}}
objectNamePlural={objectNamePlural} optionsDropdownButton={<></>}
recordTableId={recordIndexId} />
viewId={viewBarId} <SignInBackgroundMockContainerEffect
/> objectNamePlural={objectNamePlural}
<RecordTableWithWrappers recordTableId={recordIndexId}
objectNameSingular={objectNameSingular} viewId={viewBarId}
recordTableId={recordIndexId} />
viewBarId={viewBarId} <RecordTableWithWrappers
updateRecordMutation={() => {}} objectNameSingular={objectNameSingular}
/> recordTableId={recordIndexId}
</ActionMenuComponentInstanceContext.Provider> viewBarId={viewBarId}
</ContextStoreComponentInstanceContext.Provider> updateRecordMutation={() => {}}
/>
</ActionMenuComponentInstanceContext.Provider>
</ContextStoreComponentInstanceContext.Provider>
</RecordFiltersComponentInstanceContext.Provider>
</ViewComponentInstanceContext.Provider> </ViewComponentInstanceContext.Provider>
</RecordIndexContextProvider> </RecordIndexContextProvider>
</StyledContainer> </StyledContainer>
@@ -11,6 +11,7 @@ import { ObjectFilterOperandSelectAndInput } from '@/object-record/object-filter
import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState'; import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState';
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState'; import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter';
import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand'; import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { useDeleteCombinedViewFilters } from '@/views/hooks/useDeleteCombinedViewFilters'; import { useDeleteCombinedViewFilters } from '@/views/hooks/useDeleteCombinedViewFilters';
@@ -73,14 +74,17 @@ export const EditableFilterDropdownButton = ({
viewFilterDropdownId, viewFilterDropdownId,
]); ]);
const { removeRecordFilter } = useRemoveRecordFilter();
const handleRemove = () => { const handleRemove = () => {
closeDropdown(); closeDropdown();
deleteCombinedViewFilter(viewFilter.id); deleteCombinedViewFilter(viewFilter.id);
removeRecordFilter(viewFilter.fieldMetadataId);
}; };
const handleDropdownClickOutside = useCallback(() => { const handleDropdownClickOutside = useCallback(() => {
const { id: fieldId, value, operand } = viewFilter; const { id: fieldId, value, operand, fieldMetadataId } = viewFilter;
if ( if (
!value && !value &&
![ ![
@@ -91,9 +95,10 @@ export const EditableFilterDropdownButton = ({
RecordFilterOperand.IsToday, RecordFilterOperand.IsToday,
].includes(operand) ].includes(operand)
) { ) {
removeRecordFilter(fieldMetadataId);
deleteCombinedViewFilter(fieldId); deleteCombinedViewFilter(fieldId);
} }
}, [viewFilter, deleteCombinedViewFilter]); }, [viewFilter, deleteCombinedViewFilter, removeRecordFilter]);
return ( return (
<Dropdown <Dropdown
@@ -3,6 +3,7 @@ import { useEffect } from 'react';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentFamilyStateV2'; import { useSetRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentFamilyStateV2';
import { useViewFromQueryParams } from '@/views/hooks/internal/useViewFromQueryParams'; import { useViewFromQueryParams } from '@/views/hooks/internal/useViewFromQueryParams';
import { useApplyViewFiltersToCurrentRecordFilters } from '@/views/hooks/useApplyViewFiltersToCurrentRecordFilters';
import { useResetUnsavedViewStates } from '@/views/hooks/useResetUnsavedViewStates'; import { useResetUnsavedViewStates } from '@/views/hooks/useResetUnsavedViewStates';
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState'; import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
import { unsavedToUpsertViewFiltersComponentFamilyState } from '@/views/states/unsavedToUpsertViewFiltersComponentFamilyState'; import { unsavedToUpsertViewFiltersComponentFamilyState } from '@/views/states/unsavedToUpsertViewFiltersComponentFamilyState';
@@ -20,6 +21,9 @@ export const QueryParamsFiltersEffect = () => {
const { resetUnsavedViewStates } = useResetUnsavedViewStates(); const { resetUnsavedViewStates } = useResetUnsavedViewStates();
const { applyViewFiltersToCurrentRecordFilters } =
useApplyViewFiltersToCurrentRecordFilters();
useEffect(() => { useEffect(() => {
if (!hasFiltersQueryParams) { if (!hasFiltersQueryParams) {
return; return;
@@ -27,10 +31,12 @@ export const QueryParamsFiltersEffect = () => {
getFiltersFromQueryParams().then((filtersFromParams) => { getFiltersFromQueryParams().then((filtersFromParams) => {
if (Array.isArray(filtersFromParams)) { if (Array.isArray(filtersFromParams)) {
applyViewFiltersToCurrentRecordFilters(filtersFromParams);
setUnsavedViewFilter(filtersFromParams); setUnsavedViewFilter(filtersFromParams);
} }
}); });
}, [ }, [
applyViewFiltersToCurrentRecordFilters,
getFiltersFromQueryParams, getFiltersFromQueryParams,
hasFiltersQueryParams, hasFiltersQueryParams,
resetUnsavedViewStates, resetUnsavedViewStates,
@@ -1,6 +1,7 @@
import { useIcons } from 'twenty-ui'; import { useIcons } from 'twenty-ui';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural'; import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter';
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { useHandleToggleTrashColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleTrashColumnFilter'; import { useHandleToggleTrashColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleTrashColumnFilter';
import { SortOrFilterChip } from '@/views/components/SortOrFilterChip'; import { SortOrFilterChip } from '@/views/components/SortOrFilterChip';
@@ -29,10 +30,14 @@ export const VariantFilterChip = ({
viewBarId, viewBarId,
}); });
const { removeRecordFilter } = useRemoveRecordFilter();
const { getIcon } = useIcons(); const { getIcon } = useIcons();
const handleRemoveClick = () => { const handleRemoveClick = () => {
deleteCombinedViewFilter(viewFilter.id); deleteCombinedViewFilter(viewFilter.id);
removeRecordFilter(viewFilter.fieldMetadataId);
if ( if (
viewFilter.definition.label === 'Deleted' && viewFilter.definition.label === 'Deleted' &&
viewFilter.operand === 'isNotEmpty' viewFilter.operand === 'isNotEmpty'
@@ -21,6 +21,7 @@ import { ViewsHotkeyScope } from '../types/ViewsHotkeyScope';
import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope'; import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope';
import { VIEW_SORT_DROPDOWN_ID } from '@/object-record/object-sort-dropdown/constants/ViewSortDropdownId'; import { VIEW_SORT_DROPDOWN_ID } from '@/object-record/object-sort-dropdown/constants/ViewSortDropdownId';
import { ObjectSortDropdownComponentInstanceContext } from '@/object-record/object-sort-dropdown/states/context/ObjectSortDropdownComponentInstanceContext'; import { ObjectSortDropdownComponentInstanceContext } from '@/object-record/object-sort-dropdown/states/context/ObjectSortDropdownComponentInstanceContext';
import { ViewBarRecordFilterEffect } from '@/views/components/ViewBarRecordFilterEffect';
import { ViewEventContext } from '@/views/events/contexts/ViewEventContext'; import { ViewEventContext } from '@/views/events/contexts/ViewEventContext';
import { UpdateViewButtonGroup } from './UpdateViewButtonGroup'; import { UpdateViewButtonGroup } from './UpdateViewButtonGroup';
import { ViewBarDetails } from './ViewBarDetails'; import { ViewBarDetails } from './ViewBarDetails';
@@ -53,6 +54,7 @@ export const ViewBar = ({
value={{ instanceId: VIEW_SORT_DROPDOWN_ID }} value={{ instanceId: VIEW_SORT_DROPDOWN_ID }}
> >
<ViewEventContext.Provider value={{ onCurrentViewChange }}> <ViewEventContext.Provider value={{ onCurrentViewChange }}>
<ViewBarRecordFilterEffect />
<ViewBarEffect viewBarId={viewBarId} /> <ViewBarEffect viewBarId={viewBarId} />
<ViewBarFilterEffect filterDropdownId={filterDropdownId} /> <ViewBarFilterEffect filterDropdownId={filterDropdownId} />
<ViewBarSortEffect /> <ViewBarSortEffect />
@@ -14,6 +14,8 @@ import { EditableFilterDropdownButton } from '@/views/components/EditableFilterD
import { EditableSortChip } from '@/views/components/EditableSortChip'; import { EditableSortChip } from '@/views/components/EditableSortChip';
import { ViewBarFilterEffect } from '@/views/components/ViewBarFilterEffect'; import { ViewBarFilterEffect } from '@/views/components/ViewBarFilterEffect';
import { useViewFromQueryParams } from '@/views/hooks/internal/useViewFromQueryParams'; import { useViewFromQueryParams } from '@/views/hooks/internal/useViewFromQueryParams';
import { useApplyCurrentViewFiltersToCurrentRecordFilters } from '@/views/hooks/useApplyCurrentViewFiltersToCurrentRecordFilters';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { useResetUnsavedViewStates } from '@/views/hooks/useResetUnsavedViewStates'; import { useResetUnsavedViewStates } from '@/views/hooks/useResetUnsavedViewStates';
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
@@ -167,9 +169,13 @@ export const ViewBarDetails = ({
}; };
}, [currentViewWithCombinedFiltersAndSorts]); }, [currentViewWithCombinedFiltersAndSorts]);
const { applyCurrentViewFiltersToCurrentRecordFilters } =
useApplyCurrentViewFiltersToCurrentRecordFilters();
const handleCancelClick = () => { const handleCancelClick = () => {
if (isDefined(viewId)) { if (isDefined(viewId)) {
resetUnsavedViewStates(viewId); resetUnsavedViewStates(viewId);
applyCurrentViewFiltersToCurrentRecordFilters();
toggleSoftDeleteFilterState(false); toggleSoftDeleteFilterState(false);
} }
}; };
@@ -1,16 +1,13 @@
import { isNonEmptyString } from '@sniptt/guards'; import { isNonEmptyString } from '@sniptt/guards';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState'; import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState';
import { objectFilterDropdownSelectedOptionValuesComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedOptionValuesComponentState'; import { objectFilterDropdownSelectedOptionValuesComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedOptionValuesComponentState';
import { objectFilterDropdownSelectedRecordIdsComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedRecordIdsComponentState'; import { objectFilterDropdownSelectedRecordIdsComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedRecordIdsComponentState';
import { onFilterSelectComponentState } from '@/object-record/object-filter-dropdown/states/onFilterSelectComponentState';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { useUpsertCombinedViewFilters } from '@/views/hooks/useUpsertCombinedViewFilters';
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
import { jsonRelationFilterValueSchema } from '@/views/view-filter-value/validation-schemas/jsonRelationFilterValueSchema'; import { jsonRelationFilterValueSchema } from '@/views/view-filter-value/validation-schemas/jsonRelationFilterValueSchema';
import { simpleRelationFilterValueSchema } from '@/views/view-filter-value/validation-schemas/simpleRelationFilterValueSchema'; import { simpleRelationFilterValueSchema } from '@/views/view-filter-value/validation-schemas/simpleRelationFilterValueSchema';
@@ -23,19 +20,12 @@ type ViewBarFilterEffectProps = {
export const ViewBarFilterEffect = ({ export const ViewBarFilterEffect = ({
filterDropdownId, filterDropdownId,
}: ViewBarFilterEffectProps) => { }: ViewBarFilterEffectProps) => {
const { upsertCombinedViewFilter } = useUpsertCombinedViewFilters();
const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView(); const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView();
const availableFilterDefinitions = useRecoilComponentValueV2( const availableFilterDefinitions = useRecoilComponentValueV2(
availableFilterDefinitionsComponentState, availableFilterDefinitionsComponentState,
); );
const setOnFilterSelect = useSetRecoilComponentStateV2(
onFilterSelectComponentState,
filterDropdownId,
);
const filterDefinitionUsedInDropdown = useRecoilComponentValueV2( const filterDefinitionUsedInDropdown = useRecoilComponentValueV2(
filterDefinitionUsedInDropdownComponentState, filterDefinitionUsedInDropdownComponentState,
filterDropdownId, filterDropdownId,
@@ -62,17 +52,7 @@ export const ViewBarFilterEffect = ({
if (isDefined(availableFilterDefinitions)) { if (isDefined(availableFilterDefinitions)) {
setAvailableFilterDefinitions(availableFilterDefinitions); setAvailableFilterDefinitions(availableFilterDefinitions);
} }
setOnFilterSelect(() => (filter: RecordFilter | null) => { }, [availableFilterDefinitions, setAvailableFilterDefinitions]);
if (isDefined(filter)) {
upsertCombinedViewFilter(filter);
}
});
}, [
availableFilterDefinitions,
setAvailableFilterDefinitions,
setOnFilterSelect,
upsertCombinedViewFilter,
]);
useEffect(() => { useEffect(() => {
if (filterDefinitionUsedInDropdown?.type === 'RELATION') { if (filterDefinitionUsedInDropdown?.type === 'RELATION') {
@@ -0,0 +1,50 @@
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
import { View } from '@/views/types/View';
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
import { useEffect } from 'react';
import { isDefined } from 'twenty-ui';
export const ViewBarRecordFilterEffect = () => {
const { records: views, isDataPrefetched } = usePrefetchedData<View>(
PrefetchKey.AllViews,
);
const currentViewId = useRecoilComponentValueV2(currentViewIdComponentState);
const setCurrentRecordFilters = useSetRecoilComponentStateV2(
currentRecordFiltersComponentState,
);
const availableFilterDefinitions = useRecoilComponentValueV2(
availableFilterDefinitionsComponentState,
);
useEffect(() => {
if (isDataPrefetched) {
const currentView = views.find((view) => view.id === currentViewId);
if (isDefined(currentView)) {
setCurrentRecordFilters(
mapViewFiltersToFilters(
currentView.viewFilters,
availableFilterDefinitions,
),
);
}
}
}, [
isDataPrefetched,
views,
availableFilterDefinitions,
currentViewId,
setCurrentRecordFilters,
]);
return null;
};
@@ -0,0 +1,198 @@
import { act, renderHook } from '@testing-library/react';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { RecordFilterDefinition } from '@/object-record/record-filter/types/RecordFilterDefinition';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
import { ViewFilter } from '@/views/types/ViewFilter';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
import { useApplyCurrentViewFiltersToCurrentRecordFilters } from '../useApplyCurrentViewFiltersToCurrentRecordFilters';
jest.mock('@/prefetch/hooks/usePrefetchedData');
describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => {
const mockFilterDefinition: RecordFilterDefinition = {
fieldMetadataId: 'field-1',
label: 'Test Field',
type: 'TEXT',
iconName: 'IconText',
};
const mockViewFilter: ViewFilter = {
__typename: 'ViewFilter',
id: 'filter-1',
fieldMetadataId: 'field-1',
operand: ViewFilterOperand.Contains,
value: 'test',
displayValue: 'test',
viewFilterGroupId: 'group-1',
positionInViewFilterGroup: 0,
definition: mockFilterDefinition,
};
const mockView = {
id: 'view-1',
name: 'Test View',
objectMetadataId: 'object-1',
viewFilters: [mockViewFilter],
};
beforeEach(() => {
(usePrefetchedData as jest.Mock).mockReturnValue({
records: [mockView],
});
});
it('should apply filters from current view', () => {
const { result } = renderHook(
() => {
const { applyCurrentViewFiltersToCurrentRecordFilters } =
useApplyCurrentViewFiltersToCurrentRecordFilters();
const currentFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);
return {
applyCurrentViewFiltersToCurrentRecordFilters,
currentFilters,
};
},
{
wrapper: getJestMetadataAndApolloMocksWrapper({
onInitializeRecoilSnapshot: (snapshot) => {
snapshot.set(
currentViewIdComponentState.atomFamily({
instanceId: 'instanceId',
}),
mockView.id,
);
snapshot.set(
availableFilterDefinitionsComponentState.atomFamily({
instanceId: 'instanceId',
}),
[mockFilterDefinition],
);
},
}),
},
);
act(() => {
result.current.applyCurrentViewFiltersToCurrentRecordFilters();
});
expect(result.current.currentFilters).toEqual([
{
id: mockViewFilter.id,
fieldMetadataId: mockViewFilter.fieldMetadataId,
value: mockViewFilter.value,
displayValue: mockViewFilter.displayValue,
operand: mockViewFilter.operand,
viewFilterGroupId: mockViewFilter.viewFilterGroupId,
positionInViewFilterGroup: mockViewFilter.positionInViewFilterGroup,
definition: mockFilterDefinition,
},
]);
});
it('should not apply filters when current view is not found', () => {
(usePrefetchedData as jest.Mock).mockReturnValue({
records: [],
});
const { result } = renderHook(
() => {
const { applyCurrentViewFiltersToCurrentRecordFilters } =
useApplyCurrentViewFiltersToCurrentRecordFilters();
const currentFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);
return {
applyCurrentViewFiltersToCurrentRecordFilters,
currentFilters,
};
},
{
wrapper: getJestMetadataAndApolloMocksWrapper({
onInitializeRecoilSnapshot: (snapshot) => {
snapshot.set(
currentViewIdComponentState.atomFamily({
instanceId: 'instanceId',
}),
mockView.id,
);
snapshot.set(
availableFilterDefinitionsComponentState.atomFamily({
instanceId: 'instanceId',
}),
[mockFilterDefinition],
);
},
}),
},
);
act(() => {
result.current.applyCurrentViewFiltersToCurrentRecordFilters();
});
expect(result.current.currentFilters).toEqual([]);
});
it('should handle view with empty filters', () => {
const viewWithNoFilters = {
...mockView,
viewFilters: [],
};
(usePrefetchedData as jest.Mock).mockReturnValue({
records: [viewWithNoFilters],
});
const { result } = renderHook(
() => {
const { applyCurrentViewFiltersToCurrentRecordFilters } =
useApplyCurrentViewFiltersToCurrentRecordFilters();
const currentFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);
return {
applyCurrentViewFiltersToCurrentRecordFilters,
currentFilters,
};
},
{
wrapper: getJestMetadataAndApolloMocksWrapper({
onInitializeRecoilSnapshot: (snapshot) => {
snapshot.set(
currentViewIdComponentState.atomFamily({
instanceId: 'instanceId',
}),
mockView.id,
);
snapshot.set(
availableFilterDefinitionsComponentState.atomFamily({
instanceId: 'instanceId',
}),
[mockFilterDefinition],
);
},
}),
},
);
act(() => {
result.current.applyCurrentViewFiltersToCurrentRecordFilters();
});
expect(result.current.currentFilters).toEqual([]);
});
});
@@ -0,0 +1,108 @@
import { act, renderHook } from '@testing-library/react';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { RecordFilterDefinition } from '@/object-record/record-filter/types/RecordFilterDefinition';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
import { ViewFilter } from '@/views/types/ViewFilter';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
import { useApplyViewFiltersToCurrentRecordFilters } from '../useApplyViewFiltersToCurrentRecordFilters';
describe('useApplyViewFiltersToCurrentRecordFilters', () => {
const mockAvailableFilterDefinition: RecordFilterDefinition = {
fieldMetadataId: 'field-1',
label: 'Test Field',
type: 'TEXT',
iconName: 'IconText',
};
const mockViewFilter: ViewFilter = {
__typename: 'ViewFilter',
id: 'filter-1',
fieldMetadataId: 'field-1',
operand: ViewFilterOperand.Contains,
value: 'test',
displayValue: 'test',
viewFilterGroupId: 'group-1',
positionInViewFilterGroup: 0,
definition: mockAvailableFilterDefinition,
};
it('should apply view filters to current record filters', () => {
const { result } = renderHook(
() => {
const { applyViewFiltersToCurrentRecordFilters } =
useApplyViewFiltersToCurrentRecordFilters();
const currentFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);
return { applyViewFiltersToCurrentRecordFilters, currentFilters };
},
{
wrapper: getJestMetadataAndApolloMocksWrapper({
onInitializeRecoilSnapshot: (snapshot) => {
snapshot.set(
availableFilterDefinitionsComponentState.atomFamily({
instanceId: 'instanceId',
}),
[mockAvailableFilterDefinition],
);
},
}),
},
);
act(() => {
result.current.applyViewFiltersToCurrentRecordFilters([mockViewFilter]);
});
expect(result.current.currentFilters).toEqual([
{
id: mockViewFilter.id,
fieldMetadataId: mockViewFilter.fieldMetadataId,
value: mockViewFilter.value,
displayValue: mockViewFilter.displayValue,
operand: mockViewFilter.operand,
viewFilterGroupId: mockViewFilter.viewFilterGroupId,
positionInViewFilterGroup: mockViewFilter.positionInViewFilterGroup,
definition: mockAvailableFilterDefinition,
},
]);
});
it('should handle empty view filters array', () => {
const { result } = renderHook(
() => {
const { applyViewFiltersToCurrentRecordFilters } =
useApplyViewFiltersToCurrentRecordFilters();
const currentFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);
return { applyViewFiltersToCurrentRecordFilters, currentFilters };
},
{
wrapper: getJestMetadataAndApolloMocksWrapper({
onInitializeRecoilSnapshot: (snapshot) => {
snapshot.set(
availableFilterDefinitionsComponentState.atomFamily({
instanceId: 'instanceId',
}),
[mockAvailableFilterDefinition],
);
},
}),
},
);
act(() => {
result.current.applyViewFiltersToCurrentRecordFilters([]);
});
expect(result.current.currentFilters).toEqual([]);
});
});
@@ -0,0 +1,42 @@
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
import { View } from '@/views/types/View';
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
import { isDefined } from 'twenty-ui';
export const useApplyCurrentViewFiltersToCurrentRecordFilters = () => {
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews);
const currentViewId = useRecoilComponentValueV2(currentViewIdComponentState);
const setCurrentRecordFilters = useSetRecoilComponentStateV2(
currentRecordFiltersComponentState,
);
const availableFilterDefinitions = useRecoilComponentValueV2(
availableFilterDefinitionsComponentState,
);
const applyCurrentViewFiltersToCurrentRecordFilters = () => {
const currentView = views.find((view) => view.id === currentViewId);
if (isDefined(currentView)) {
setCurrentRecordFilters(
mapViewFiltersToFilters(
currentView.viewFilters,
availableFilterDefinitions,
),
);
}
};
return {
applyCurrentViewFiltersToCurrentRecordFilters,
};
};
@@ -0,0 +1,31 @@
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
import { ViewFilter } from '@/views/types/ViewFilter';
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
export const useApplyViewFiltersToCurrentRecordFilters = () => {
const setCurrentRecordFilters = useSetRecoilComponentStateV2(
currentRecordFiltersComponentState,
);
const availableFilterDefinitions = useRecoilComponentValueV2(
availableFilterDefinitionsComponentState,
);
const applyViewFiltersToCurrentRecordFilters = (
viewFilters: ViewFilter[],
) => {
const recordFiltersToApply = mapViewFiltersToFilters(
viewFilters,
availableFilterDefinitions,
);
setCurrentRecordFilters(recordFiltersToApply);
};
return {
applyViewFiltersToCurrentRecordFilters,
};
};
@@ -9,6 +9,7 @@ import { ContextStoreComponentInstanceContext } from '@/context-store/states/con
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 { lastShowPageRecordIdState } from '@/object-record/record-field/states/lastShowPageRecordId'; import { lastShowPageRecordIdState } from '@/object-record/record-field/states/lastShowPageRecordId';
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
import { RecordIndexContainer } from '@/object-record/record-index/components/RecordIndexContainer'; import { RecordIndexContainer } from '@/object-record/record-index/components/RecordIndexContainer';
import { RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect } from '@/object-record/record-index/components/RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect'; import { RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect } from '@/object-record/record-index/components/RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect';
import { RecordIndexContainerContextStoreObjectMetadataEffect } from '@/object-record/record-index/components/RecordIndexContainerContextStoreObjectMetadataEffect'; import { RecordIndexContainerContextStoreObjectMetadataEffect } from '@/object-record/record-index/components/RecordIndexContainerContextStoreObjectMetadataEffect';
@@ -81,28 +82,32 @@ export const RecordIndexPage = () => {
<ViewComponentInstanceContext.Provider <ViewComponentInstanceContext.Provider
value={{ instanceId: recordIndexId }} value={{ instanceId: recordIndexId }}
> >
<ContextStoreComponentInstanceContext.Provider <RecordFiltersComponentInstanceContext.Provider
value={{ value={{ instanceId: recordIndexId }}
instanceId: getActionMenuIdFromRecordIndexId(recordIndexId),
}}
> >
<ActionMenuComponentInstanceContext.Provider <ContextStoreComponentInstanceContext.Provider
value={{ value={{
instanceId: getActionMenuIdFromRecordIndexId(recordIndexId), instanceId: getActionMenuIdFromRecordIndexId(recordIndexId),
}} }}
> >
<PageTitle title={`${capitalize(objectNamePlural)}`} /> <ActionMenuComponentInstanceContext.Provider
<RecordIndexPageHeader /> value={{
<PageBody> instanceId: getActionMenuIdFromRecordIndexId(recordIndexId),
<StyledIndexContainer> }}
<RecordIndexContainerContextStoreObjectMetadataEffect /> >
<RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect /> <PageTitle title={`${capitalize(objectNamePlural)}`} />
<MainContextStoreComponentInstanceIdSetterEffect /> <RecordIndexPageHeader />
<RecordIndexContainer /> <PageBody>
</StyledIndexContainer> <StyledIndexContainer>
</PageBody> <RecordIndexContainerContextStoreObjectMetadataEffect />
</ActionMenuComponentInstanceContext.Provider> <RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect />
</ContextStoreComponentInstanceContext.Provider> <MainContextStoreComponentInstanceIdSetterEffect />
<RecordIndexContainer />
</StyledIndexContainer>
</PageBody>
</ActionMenuComponentInstanceContext.Provider>
</ContextStoreComponentInstanceContext.Provider>
</RecordFiltersComponentInstanceContext.Provider>
</ViewComponentInstanceContext.Provider> </ViewComponentInstanceContext.Provider>
</RecordIndexContextProvider> </RecordIndexContextProvider>
</PageContainer> </PageContainer>
@@ -5,6 +5,7 @@ import { ActionMenuComponentInstanceContext } from '@/action-menu/states/context
import { TimelineActivityContext } from '@/activities/timeline-activities/contexts/TimelineActivityContext'; import { TimelineActivityContext } from '@/activities/timeline-activities/contexts/TimelineActivityContext';
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
import { RecordShowContainer } from '@/object-record/record-show/components/RecordShowContainer'; import { RecordShowContainer } from '@/object-record/record-show/components/RecordShowContainer';
import { useRecordShowPage } from '@/object-record/record-show/hooks/useRecordShowPage'; import { useRecordShowPage } from '@/object-record/record-show/hooks/useRecordShowPage';
import { RecordValueSetterEffect } from '@/object-record/record-store/components/RecordValueSetterEffect'; import { RecordValueSetterEffect } from '@/object-record/record-store/components/RecordValueSetterEffect';
@@ -46,62 +47,68 @@ export const RecordShowPage = () => {
return ( return (
<RecordFieldValueSelectorContextProvider> <RecordFieldValueSelectorContextProvider>
<ContextStoreComponentInstanceContext.Provider <RecordFiltersComponentInstanceContext.Provider
value={{ instanceId: `record-show-${objectRecordId}` }} value={{ instanceId: `record-show-${objectRecordId}` }}
> >
<ActionMenuComponentInstanceContext.Provider <ContextStoreComponentInstanceContext.Provider
value={{ instanceId: `record-show-${objectRecordId}` }} value={{ instanceId: `record-show-${objectRecordId}` }}
> >
<RecordValueSetterEffect recordId={objectRecordId} /> <ActionMenuComponentInstanceContext.Provider
<PageContainer> value={{ instanceId: `record-show-${objectRecordId}` }}
<PageTitle title={pageTitle} /> >
<RecordShowPageHeader <RecordValueSetterEffect recordId={objectRecordId} />
objectNameSingular={objectNameSingular} <PageContainer>
objectRecordId={objectRecordId} <PageTitle title={pageTitle} />
headerIcon={headerIcon} <RecordShowPageHeader
> objectNameSingular={objectNameSingular}
<> objectRecordId={objectRecordId}
{!isCommandMenuV2Enabled && headerIcon={headerIcon}
objectNameSingular === CoreObjectNameSingular.Workflow && ( >
<RecordShowPageWorkflowHeader workflowId={objectRecordId} /> <>
)} {!isCommandMenuV2Enabled &&
{!isCommandMenuV2Enabled && objectNameSingular === CoreObjectNameSingular.Workflow && (
objectNameSingular === <RecordShowPageWorkflowHeader
CoreObjectNameSingular.WorkflowVersion && ( workflowId={objectRecordId}
<RecordShowPageWorkflowVersionHeader />
workflowVersionId={objectRecordId} )}
{!isCommandMenuV2Enabled &&
objectNameSingular ===
CoreObjectNameSingular.WorkflowVersion && (
<RecordShowPageWorkflowVersionHeader
workflowVersionId={objectRecordId}
/>
)}
{(isCommandMenuV2Enabled ||
(objectNameSingular !== CoreObjectNameSingular.Workflow &&
objectNameSingular !==
CoreObjectNameSingular.WorkflowVersion)) && (
<RecordShowActionMenu
{...{
isFavorite,
record,
handleFavoriteButtonClick,
objectMetadataItem,
objectNameSingular,
}}
/> />
)} )}
{(isCommandMenuV2Enabled || </>
(objectNameSingular !== CoreObjectNameSingular.Workflow && </RecordShowPageHeader>
objectNameSingular !== <PageBody>
CoreObjectNameSingular.WorkflowVersion)) && ( <TimelineActivityContext.Provider
<RecordShowActionMenu value={{ labelIdentifierValue: pageName }}
{...{ >
isFavorite, <RecordShowContainer
record, objectNameSingular={objectNameSingular}
handleFavoriteButtonClick, objectRecordId={objectRecordId}
objectMetadataItem, loading={loading}
objectNameSingular,
}}
/> />
)} </TimelineActivityContext.Provider>
</> </PageBody>
</RecordShowPageHeader> </PageContainer>
<PageBody> </ActionMenuComponentInstanceContext.Provider>
<TimelineActivityContext.Provider </ContextStoreComponentInstanceContext.Provider>
value={{ labelIdentifierValue: pageName }} </RecordFiltersComponentInstanceContext.Provider>
>
<RecordShowContainer
objectNameSingular={objectNameSingular}
objectRecordId={objectRecordId}
loading={loading}
/>
</TimelineActivityContext.Provider>
</PageBody>
</PageContainer>
</ActionMenuComponentInstanceContext.Provider>
</ContextStoreComponentInstanceContext.Provider>
</RecordFieldValueSelectorContextProvider> </RecordFieldValueSelectorContextProvider>
); );
}; };
@@ -22,6 +22,7 @@ import { mockedApolloClient } from '~/testing/mockedApolloClient';
import { RecoilDebugObserverEffect } from '@/debug/components/RecoilDebugObserver'; import { RecoilDebugObserverEffect } from '@/debug/components/RecoilDebugObserver';
import { ObjectMetadataItemsProvider } from '@/object-metadata/components/ObjectMetadataItemsProvider'; import { ObjectMetadataItemsProvider } from '@/object-metadata/components/ObjectMetadataItemsProvider';
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
import { PrefetchDataProvider } from '@/prefetch/components/PrefetchDataProvider'; import { PrefetchDataProvider } from '@/prefetch/components/PrefetchDataProvider';
import { WorkspaceProviderEffect } from '@/workspace/components/WorkspaceProviderEffect'; import { WorkspaceProviderEffect } from '@/workspace/components/WorkspaceProviderEffect';
import { i18n } from '@lingui/core'; import { i18n } from '@lingui/core';
@@ -88,7 +89,13 @@ const Providers = () => {
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager"> <SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
<IconsProvider> <IconsProvider>
<PrefetchDataProvider> <PrefetchDataProvider>
<Outlet /> <RecordFiltersComponentInstanceContext.Provider
value={{
instanceId: 'storybook-test-record-filters',
}}
>
<Outlet />
</RecordFiltersComponentInstanceContext.Provider>
</PrefetchDataProvider> </PrefetchDataProvider>
</IconsProvider> </IconsProvider>
</SnackBarProviderScope> </SnackBarProviderScope>
@@ -1,6 +1,7 @@
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
import { ContextStoreTargetedRecordsRule } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; import { ContextStoreTargetedRecordsRule } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
import { MockedResponse } from '@apollo/client/testing'; import { MockedResponse } from '@apollo/client/testing';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { MutableSnapshot } from 'recoil'; import { MutableSnapshot } from 'recoil';
@@ -33,25 +34,33 @@ export const getJestMetadataAndApolloMocksAndActionMenuWrapper = ({
return ({ children }: { children: ReactNode }) => ( return ({ children }: { children: ReactNode }) => (
<Wrapper> <Wrapper>
<ContextStoreComponentInstanceContext.Provider <RecordFiltersComponentInstanceContext.Provider
value={{ instanceId: componentInstanceId }} value={{
instanceId: componentInstanceId,
}}
> >
<ActionMenuComponentInstanceContext.Provider <ContextStoreComponentInstanceContext.Provider
value={{ instanceId: componentInstanceId }} value={{ instanceId: componentInstanceId }}
> >
<JestContextStoreSetter <ActionMenuComponentInstanceContext.Provider
contextStoreTargetedRecordsRule={contextStoreTargetedRecordsRule} value={{
contextStoreNumberOfSelectedRecords={ instanceId: componentInstanceId,
contextStoreNumberOfSelectedRecords }}
}
contextStoreCurrentObjectMetadataNameSingular={
contextStoreCurrentObjectMetadataNameSingular
}
> >
{children} <JestContextStoreSetter
</JestContextStoreSetter> contextStoreTargetedRecordsRule={contextStoreTargetedRecordsRule}
</ActionMenuComponentInstanceContext.Provider> contextStoreNumberOfSelectedRecords={
</ContextStoreComponentInstanceContext.Provider> contextStoreNumberOfSelectedRecords
}
contextStoreCurrentObjectMetadataNameSingular={
contextStoreCurrentObjectMetadataNameSingular
}
>
{children}
</JestContextStoreSetter>
</ActionMenuComponentInstanceContext.Provider>
</ContextStoreComponentInstanceContext.Provider>
</RecordFiltersComponentInstanceContext.Provider>
</Wrapper> </Wrapper>
); );
}; };
@@ -2,14 +2,16 @@ import { MockedProvider, MockedResponse } from '@apollo/client/testing';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { MutableSnapshot, RecoilRoot } from 'recoil'; import { MutableSnapshot, RecoilRoot } from 'recoil';
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter'; import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter';
export const getJestMetadataAndApolloMocksWrapper = ({ export const getJestMetadataAndApolloMocksWrapper = ({
apolloMocks, apolloMocks,
onInitializeRecoilSnapshot, onInitializeRecoilSnapshot,
}: { }: {
apolloMocks: apolloMocks?:
| readonly MockedResponse<Record<string, any>, Record<string, any>>[] | readonly MockedResponse<Record<string, any>, Record<string, any>>[]
| undefined; | undefined;
onInitializeRecoilSnapshot?: (snapshot: MutableSnapshot) => void; onInitializeRecoilSnapshot?: (snapshot: MutableSnapshot) => void;
@@ -18,9 +20,17 @@ export const getJestMetadataAndApolloMocksWrapper = ({
<RecoilRoot initializeState={onInitializeRecoilSnapshot}> <RecoilRoot initializeState={onInitializeRecoilSnapshot}>
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager"> <SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
<MockedProvider mocks={apolloMocks} addTypename={false}> <MockedProvider mocks={apolloMocks} addTypename={false}>
<JestObjectMetadataItemSetter> <RecordFiltersComponentInstanceContext.Provider
{children} value={{ instanceId: 'instanceId' }}
</JestObjectMetadataItemSetter> >
<ViewComponentInstanceContext.Provider
value={{ instanceId: 'instanceId' }}
>
<JestObjectMetadataItemSetter>
{children}
</JestObjectMetadataItemSetter>
</ViewComponentInstanceContext.Provider>
</RecordFiltersComponentInstanceContext.Provider>
</MockedProvider> </MockedProvider>
</SnackBarProviderScope> </SnackBarProviderScope>
</RecoilRoot> </RecoilRoot>