Support workspace member Me filter for relation fields in dashboards (#23282)
<img width="3024" height="1484" alt="CleanShot 2026-07-27 at 15 04 22@2x" src="https://github.com/user-attachments/assets/a57797b7-dbe9-4748-aeff-18667f1f69bb" /> Fixes #20225 Workspace member "Me" filters worked for standard actor fields (Created by / Updated by) in dashboard widgets but not for relation fields pointing to a workspace member (e.g. "Account owner"). In the advanced-filter UI, picking such a relation forced a relation traversal and never produced a filter you could set to "Me". For a many-to-one relation targeting workspaceMember, the relation-target sub-menu now offers a "filter by record" entry that creates a direct relation filter with the same "Me" multi-select picker used by view filters. Traversal (e.g. "Account owner -> Name") is preserved. No backend change is needed: the stored value matches view filters and is already resolved server-side. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23282?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
+52
-2
@@ -1,7 +1,9 @@
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { isManyToOneRelationField } from '@/object-metadata/utils/isManyToOneRelationField';
|
||||
import { useAdvancedFilterFieldSelectDropdown } from '@/object-record/advanced-filter/hooks/useAdvancedFilterFieldSelectDropdown';
|
||||
import { useApplyAdvancedFilterRelationTargetField } from '@/object-record/advanced-filter/hooks/useApplyAdvancedFilterRelationTargetField';
|
||||
import { useApplyAdvancedFilterSourceField } from '@/object-record/advanced-filter/hooks/useApplyAdvancedFilterSourceField';
|
||||
import { usePushFocusForLeafFieldValuePicker } from '@/object-record/advanced-filter/hooks/usePushFocusForLeafFieldValuePicker';
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { objectFilterDropdownIsSelectingRelationTargetFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingRelationTargetFieldComponentState';
|
||||
@@ -10,6 +12,7 @@ import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
@@ -17,10 +20,13 @@ import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states
|
||||
import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
|
||||
import { CoreObjectNameSingular } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconChevronLeft, useIcons } from 'twenty-ui/icon';
|
||||
import { IconChevronLeft, IconUserCircle, useIcons } from 'twenty-ui/icon';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
|
||||
const RELATION_RECORD_SELECTABLE_ITEM_ID = 'relation-record-select';
|
||||
|
||||
type AdvancedFilterRelationTargetFieldSelectMenuProps = {
|
||||
recordFilterId: string;
|
||||
};
|
||||
@@ -45,6 +51,9 @@ export const AdvancedFilterRelationTargetFieldSelectMenu = ({
|
||||
const { applyAdvancedFilterRelationTargetField } =
|
||||
useApplyAdvancedFilterRelationTargetField();
|
||||
|
||||
const { applyAdvancedFilterSourceField } =
|
||||
useApplyAdvancedFilterSourceField();
|
||||
|
||||
const { pushFocusForLeafFieldValuePicker } =
|
||||
usePushFocusForLeafFieldValuePicker();
|
||||
|
||||
@@ -56,6 +65,11 @@ export const AdvancedFilterRelationTargetFieldSelectMenu = ({
|
||||
advancedFilterFieldSelectDropdownId,
|
||||
);
|
||||
|
||||
const { objectMetadataItem: workspaceMemberObjectMetadataItem } =
|
||||
useObjectMetadataItem({
|
||||
objectNameSingular: CoreObjectNameSingular.WorkspaceMember,
|
||||
});
|
||||
|
||||
const targetObjectMetadataId =
|
||||
isDefined(sourceFieldMetadataItem) &&
|
||||
isManyToOneRelationField(sourceFieldMetadataItem)
|
||||
@@ -72,6 +86,10 @@ export const AdvancedFilterRelationTargetFieldSelectMenu = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
const isWorkspaceMemberTarget =
|
||||
sourceFieldMetadataItem.relation.targetObjectMetadata.nameSingular ===
|
||||
CoreObjectNameSingular.WorkspaceMember;
|
||||
|
||||
const handleSubMenuBack = () => {
|
||||
setObjectFilterDropdownIsSelectingRelationTargetField(false);
|
||||
};
|
||||
@@ -91,7 +109,20 @@ export const AdvancedFilterRelationTargetFieldSelectMenu = ({
|
||||
closeAdvancedFilterFieldSelectDropdown();
|
||||
};
|
||||
|
||||
const selectableItemIdArray = relationTargetFields.map((field) => field.id);
|
||||
const handleSelectRelationRecord = () => {
|
||||
applyAdvancedFilterSourceField({
|
||||
sourceFieldMetadataItem,
|
||||
recordFilterId,
|
||||
});
|
||||
|
||||
setObjectFilterDropdownIsSelectingRelationTargetField(false);
|
||||
closeAdvancedFilterFieldSelectDropdown();
|
||||
};
|
||||
|
||||
const selectableItemIdArray = [
|
||||
...(isWorkspaceMemberTarget ? [RELATION_RECORD_SELECTABLE_ITEM_ID] : []),
|
||||
...relationTargetFields.map((field) => field.id),
|
||||
];
|
||||
|
||||
return (
|
||||
<DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}>
|
||||
@@ -111,6 +142,25 @@ export const AdvancedFilterRelationTargetFieldSelectMenu = ({
|
||||
selectableItemIdArray={selectableItemIdArray}
|
||||
selectableListInstanceId={advancedFilterFieldSelectDropdownId}
|
||||
>
|
||||
{isWorkspaceMemberTarget && (
|
||||
<>
|
||||
<SelectableListItem
|
||||
itemId={RELATION_RECORD_SELECTABLE_ITEM_ID}
|
||||
onEnter={handleSelectRelationRecord}
|
||||
>
|
||||
<MenuItem
|
||||
focused={
|
||||
selectedItemId === RELATION_RECORD_SELECTABLE_ITEM_ID
|
||||
}
|
||||
testId="select-filter-relation-record"
|
||||
onClick={handleSelectRelationRecord}
|
||||
text={workspaceMemberObjectMetadataItem.labelSingular}
|
||||
LeftIcon={IconUserCircle}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
<DropdownMenuSeparator />
|
||||
</>
|
||||
)}
|
||||
{relationTargetFields.map((targetField, index) => (
|
||||
<SelectableListItem
|
||||
itemId={targetField.id}
|
||||
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
import { fireEvent, render, waitFor } from '@testing-library/react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { isManyToOneRelationField } from '@/object-metadata/utils/isManyToOneRelationField';
|
||||
import { AdvancedFilterRelationTargetFieldSelectMenu } from '@/object-record/advanced-filter/components/AdvancedFilterRelationTargetFieldSelectMenu';
|
||||
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
|
||||
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
|
||||
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
|
||||
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
|
||||
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
||||
|
||||
const INSTANCE_ID = 'advanced-filter-relation-target-test';
|
||||
const FILTER_ID = 'relation-target-filter';
|
||||
|
||||
const opportunity = getMockObjectMetadataItemOrThrow('opportunity');
|
||||
|
||||
const workspaceMemberRelationField = opportunity.fields.find(
|
||||
(field) =>
|
||||
isManyToOneRelationField(field) &&
|
||||
field.relation.targetObjectMetadata.nameSingular === 'workspaceMember',
|
||||
);
|
||||
|
||||
const nonWorkspaceMemberRelationField = opportunity.fields.find(
|
||||
(field) =>
|
||||
isManyToOneRelationField(field) &&
|
||||
field.relation.targetObjectMetadata.nameSingular !== 'workspaceMember',
|
||||
);
|
||||
|
||||
if (!workspaceMemberRelationField || !nonWorkspaceMemberRelationField) {
|
||||
throw new Error('Missing expected relation fields in opportunity mock');
|
||||
}
|
||||
|
||||
const BaseWrapper = getJestMetadataAndApolloMocksWrapper({ apolloMocks: [] });
|
||||
|
||||
const CurrentRecordFiltersObserver = () => {
|
||||
const currentRecordFilters = useAtomComponentStateValue(
|
||||
currentRecordFiltersComponentState,
|
||||
);
|
||||
|
||||
return (
|
||||
<div data-testid="current-record-filters">
|
||||
{JSON.stringify(currentRecordFilters)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Seed = ({
|
||||
sourceFieldMetadataId,
|
||||
children,
|
||||
}: {
|
||||
sourceFieldMetadataId: string;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const setFieldMetadataItemIdUsedInDropdown = useSetAtomComponentState(
|
||||
fieldMetadataItemIdUsedInDropdownComponentState,
|
||||
);
|
||||
|
||||
const [isSeeded, setIsSeeded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setFieldMetadataItemIdUsedInDropdown(sourceFieldMetadataId);
|
||||
setIsSeeded(true);
|
||||
}, [sourceFieldMetadataId, setFieldMetadataItemIdUsedInDropdown]);
|
||||
|
||||
return isSeeded ? <>{children}</> : null;
|
||||
};
|
||||
|
||||
const renderSubMenu = (sourceFieldMetadataId: string) => {
|
||||
return render(
|
||||
<BaseWrapper>
|
||||
<RecordFiltersComponentInstanceContext.Provider
|
||||
value={{ instanceId: INSTANCE_ID }}
|
||||
>
|
||||
<ObjectFilterDropdownComponentInstanceContext.Provider
|
||||
value={{ instanceId: INSTANCE_ID }}
|
||||
>
|
||||
<Seed sourceFieldMetadataId={sourceFieldMetadataId}>
|
||||
<AdvancedFilterRelationTargetFieldSelectMenu
|
||||
recordFilterId={FILTER_ID}
|
||||
/>
|
||||
</Seed>
|
||||
<CurrentRecordFiltersObserver />
|
||||
</ObjectFilterDropdownComponentInstanceContext.Provider>
|
||||
</RecordFiltersComponentInstanceContext.Provider>
|
||||
</BaseWrapper>,
|
||||
);
|
||||
};
|
||||
|
||||
describe('AdvancedFilterRelationTargetFieldSelectMenu', () => {
|
||||
it('shows a "filter by record" entry when the relation target is workspaceMember', async () => {
|
||||
const { getByTestId } = renderSubMenu(workspaceMemberRelationField.id);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByTestId('select-filter-relation-record')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('does not show the "filter by record" entry for a non-workspaceMember relation', async () => {
|
||||
const { findByText, queryByTestId } = renderSubMenu(
|
||||
nonWorkspaceMemberRelationField.id,
|
||||
);
|
||||
|
||||
await findByText(nonWorkspaceMemberRelationField.label);
|
||||
|
||||
expect(
|
||||
queryByTestId('select-filter-relation-record'),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('creates a direct RELATION filter (relationTargetFieldMetadataId null) when the record entry is selected', async () => {
|
||||
const { getByTestId } = renderSubMenu(workspaceMemberRelationField.id);
|
||||
|
||||
const recordEntry = await waitFor(() =>
|
||||
getByTestId('select-filter-relation-record'),
|
||||
);
|
||||
|
||||
fireEvent.click(recordEntry);
|
||||
|
||||
await waitFor(() => {
|
||||
const currentRecordFilters = JSON.parse(
|
||||
getByTestId('current-record-filters').textContent || '[]',
|
||||
);
|
||||
|
||||
expect(currentRecordFilters).toHaveLength(1);
|
||||
expect(currentRecordFilters[0]).toMatchObject({
|
||||
fieldMetadataId: workspaceMemberRelationField.id,
|
||||
type: 'RELATION',
|
||||
relationTargetFieldMetadataId: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
-5
@@ -19,11 +19,6 @@ type ApplyAdvancedFilterSourceFieldParams = {
|
||||
recordFilterId: string;
|
||||
};
|
||||
|
||||
// Creates a leaf advanced filter from a source field selection. Composite
|
||||
// and many-to-one relation sources are handled by their own specialized
|
||||
// hooks (useApplyAdvancedFilterCompositeSubField,
|
||||
// useApplyAdvancedFilterRelationTargetField) — callers branch to those
|
||||
// before reaching this hook.
|
||||
export const useApplyAdvancedFilterSourceField = () => {
|
||||
const setSelectedOperandInDropdown = useSetAtomComponentState(
|
||||
selectedOperandInDropdownComponentState,
|
||||
|
||||
+22
-1
@@ -13,11 +13,13 @@ import { FormBooleanFieldInput } from '@/object-record/record-field/ui/form-type
|
||||
import { FormMultiSelectFieldInput } from '@/object-record/record-field/ui/form-types/components/FormMultiSelectFieldInput';
|
||||
import { FormRelativeDatePicker } from '@/object-record/record-field/ui/form-types/components/FormRelativeDatePicker';
|
||||
import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/components/FormTextFieldInput';
|
||||
import { FormWorkspaceMemberFilterValueInput } from '@/object-record/record-field/ui/form-types/components/FormWorkspaceMemberFilterValueInput';
|
||||
import {
|
||||
type FieldMetadata,
|
||||
type FieldMultiSelectMetadata,
|
||||
type FieldSelectMetadata,
|
||||
} from '@/object-record/record-field/ui/types/FieldMetadata';
|
||||
import { isFieldRelationManyToOne } from '@/object-record/record-field/ui/types/guards/isFieldRelationManyToOne';
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand';
|
||||
import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
|
||||
@@ -27,7 +29,7 @@ import { stringifyRelativeDateFilter } from '@/views/view-filter-value/utils/str
|
||||
import { WORKFLOW_TIMEZONE } from '@/workflow/constants/WorkflowTimeZone';
|
||||
import { isObject, isString } from '@sniptt/guards';
|
||||
import { useContext } from 'react';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { CoreObjectNameSingular, FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined, type RelativeDateFilter } from 'twenty-shared/utils';
|
||||
import { parseBooleanFromStringValue } from 'twenty-shared/workflow';
|
||||
import { type JsonValue } from 'type-fest';
|
||||
@@ -215,6 +217,25 @@ export const AdvancedFilterSidePanelValueFormInput = ({
|
||||
metadata: fieldDefinition?.metadata as FieldMetadata,
|
||||
};
|
||||
|
||||
const isDirectWorkspaceMemberRelationFilter =
|
||||
!isDefined(recordFilter.relationTargetFieldMetadataId) &&
|
||||
isFieldRelationManyToOne(field) &&
|
||||
field.metadata.relationObjectMetadataNameSingular ===
|
||||
CoreObjectNameSingular.WorkspaceMember;
|
||||
|
||||
if (isDirectWorkspaceMemberRelationFilter) {
|
||||
return (
|
||||
<FormWorkspaceMemberFilterValueInput
|
||||
label=""
|
||||
defaultValue={recordFilter.value}
|
||||
onChange={handleChange}
|
||||
onClear={handleClear}
|
||||
readonly={readonly}
|
||||
VariablePicker={VariablePicker}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const shouldUseUTCTimeZone = isWorkflowFindRecords === true;
|
||||
const timeZone = shouldUseUTCTimeZone ? WORKFLOW_TIMEZONE : undefined;
|
||||
|
||||
|
||||
+136
-30
@@ -1,6 +1,7 @@
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { isManyToOneRelationField } from '@/object-metadata/utils/isManyToOneRelationField';
|
||||
import { AdvancedFilterSidePanelValueFormInput } from '@/object-record/advanced-filter/side-panel/components/AdvancedFilterSidePanelValueFormInput';
|
||||
import { AdvancedFilterContext } from '@/object-record/advanced-filter/states/context/AdvancedFilterContext';
|
||||
import { getAdvancedFilterObjectFilterDropdownComponentInstanceId } from '@/object-record/advanced-filter/utils/getAdvancedFilterObjectFilterDropdownComponentInstanceId';
|
||||
@@ -15,7 +16,7 @@ import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectM
|
||||
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
||||
|
||||
jest.mock('@/object-record/record-field/ui/components/FormFieldInput', () => ({
|
||||
FormFieldInput: () => null,
|
||||
FormFieldInput: () => <div data-testid="form-field-input" />,
|
||||
}));
|
||||
|
||||
jest.mock(
|
||||
@@ -29,6 +30,15 @@ jest.mock(
|
||||
}),
|
||||
);
|
||||
|
||||
jest.mock(
|
||||
'@/object-record/record-field/ui/form-types/components/FormWorkspaceMemberFilterValueInput',
|
||||
() => ({
|
||||
FormWorkspaceMemberFilterValueInput: () => (
|
||||
<div data-testid="workspace-member-filter-input" />
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
const INSTANCE_ID = 'advanced-filter-value-test';
|
||||
const FILTER_ID = 'relation-select-filter';
|
||||
|
||||
@@ -43,8 +53,25 @@ const selectField = opportunity.fields.find(
|
||||
field.type === FieldMetadataType.SELECT && (field.options?.length ?? 0) > 0,
|
||||
);
|
||||
|
||||
if (!relationField || !selectField) {
|
||||
throw new Error('Missing relation or select field in opportunity mock');
|
||||
const workspaceMemberRelationField = opportunity.fields.find(
|
||||
(field) =>
|
||||
isManyToOneRelationField(field) &&
|
||||
field.relation.targetObjectMetadata.nameSingular === 'workspaceMember',
|
||||
);
|
||||
|
||||
const nonWorkspaceMemberRelationField = opportunity.fields.find(
|
||||
(field) =>
|
||||
isManyToOneRelationField(field) &&
|
||||
field.relation.targetObjectMetadata.nameSingular !== 'workspaceMember',
|
||||
);
|
||||
|
||||
if (
|
||||
!relationField ||
|
||||
!selectField ||
|
||||
!workspaceMemberRelationField ||
|
||||
!nonWorkspaceMemberRelationField
|
||||
) {
|
||||
throw new Error('Missing expected fields in opportunity mock');
|
||||
}
|
||||
|
||||
const relationToSelectFilter: RecordFilter = {
|
||||
@@ -58,9 +85,39 @@ const relationToSelectFilter: RecordFilter = {
|
||||
label: `${relationField.label} → ${selectField.label}`,
|
||||
};
|
||||
|
||||
const workspaceMemberRelationFilter: RecordFilter = {
|
||||
id: FILTER_ID,
|
||||
fieldMetadataId: workspaceMemberRelationField.id,
|
||||
relationTargetFieldMetadataId: null,
|
||||
type: FieldMetadataType.RELATION,
|
||||
operand: ViewFilterOperand.IS,
|
||||
value: '',
|
||||
displayValue: '',
|
||||
label: workspaceMemberRelationField.label,
|
||||
};
|
||||
|
||||
const nonWorkspaceMemberRelationFilter: RecordFilter = {
|
||||
id: FILTER_ID,
|
||||
fieldMetadataId: nonWorkspaceMemberRelationField.id,
|
||||
relationTargetFieldMetadataId: null,
|
||||
type: FieldMetadataType.RELATION,
|
||||
operand: ViewFilterOperand.IS,
|
||||
value: '',
|
||||
displayValue: '',
|
||||
label: nonWorkspaceMemberRelationField.label,
|
||||
};
|
||||
|
||||
const BaseWrapper = getJestMetadataAndApolloMocksWrapper({ apolloMocks: [] });
|
||||
|
||||
const Seed = ({ children }: { children: React.ReactNode }) => {
|
||||
const Seed = ({
|
||||
recordFilter,
|
||||
dropdownFieldMetadataId,
|
||||
children,
|
||||
}: {
|
||||
recordFilter: RecordFilter;
|
||||
dropdownFieldMetadataId: string;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const setCurrentRecordFilters = useSetAtomComponentState(
|
||||
currentRecordFiltersComponentState,
|
||||
);
|
||||
@@ -72,42 +129,66 @@ const Seed = ({ children }: { children: React.ReactNode }) => {
|
||||
const [isSeeded, setIsSeeded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentRecordFilters([relationToSelectFilter]);
|
||||
setFieldMetadataItemIdUsedInDropdown(relationField.id);
|
||||
setCurrentRecordFilters([recordFilter]);
|
||||
setFieldMetadataItemIdUsedInDropdown(dropdownFieldMetadataId);
|
||||
setIsSeeded(true);
|
||||
}, [setCurrentRecordFilters, setFieldMetadataItemIdUsedInDropdown]);
|
||||
}, [
|
||||
recordFilter,
|
||||
dropdownFieldMetadataId,
|
||||
setCurrentRecordFilters,
|
||||
setFieldMetadataItemIdUsedInDropdown,
|
||||
]);
|
||||
|
||||
return isSeeded ? <>{children}</> : null;
|
||||
};
|
||||
|
||||
const Wrapper = () => (
|
||||
<BaseWrapper>
|
||||
<AdvancedFilterContext.Provider
|
||||
value={{ objectMetadataItem: opportunity, isWorkflowFindRecords: false }}
|
||||
>
|
||||
<RecordFiltersComponentInstanceContext.Provider
|
||||
value={{ instanceId: INSTANCE_ID }}
|
||||
const renderValueInput = ({
|
||||
recordFilter,
|
||||
dropdownFieldMetadataId,
|
||||
}: {
|
||||
recordFilter: RecordFilter;
|
||||
dropdownFieldMetadataId: string;
|
||||
}) => {
|
||||
return render(
|
||||
<BaseWrapper>
|
||||
<AdvancedFilterContext.Provider
|
||||
value={{
|
||||
objectMetadataItem: opportunity,
|
||||
isWorkflowFindRecords: false,
|
||||
}}
|
||||
>
|
||||
<ObjectFilterDropdownComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId:
|
||||
getAdvancedFilterObjectFilterDropdownComponentInstanceId(
|
||||
FILTER_ID,
|
||||
),
|
||||
}}
|
||||
<RecordFiltersComponentInstanceContext.Provider
|
||||
value={{ instanceId: INSTANCE_ID }}
|
||||
>
|
||||
<Seed>
|
||||
<AdvancedFilterSidePanelValueFormInput recordFilterId={FILTER_ID} />
|
||||
</Seed>
|
||||
</ObjectFilterDropdownComponentInstanceContext.Provider>
|
||||
</RecordFiltersComponentInstanceContext.Provider>
|
||||
</AdvancedFilterContext.Provider>
|
||||
</BaseWrapper>
|
||||
);
|
||||
<ObjectFilterDropdownComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId:
|
||||
getAdvancedFilterObjectFilterDropdownComponentInstanceId(
|
||||
FILTER_ID,
|
||||
),
|
||||
}}
|
||||
>
|
||||
<Seed
|
||||
recordFilter={recordFilter}
|
||||
dropdownFieldMetadataId={dropdownFieldMetadataId}
|
||||
>
|
||||
<AdvancedFilterSidePanelValueFormInput
|
||||
recordFilterId={FILTER_ID}
|
||||
/>
|
||||
</Seed>
|
||||
</ObjectFilterDropdownComponentInstanceContext.Provider>
|
||||
</RecordFiltersComponentInstanceContext.Provider>
|
||||
</AdvancedFilterContext.Provider>
|
||||
</BaseWrapper>,
|
||||
);
|
||||
};
|
||||
|
||||
describe('AdvancedFilterSidePanelValueFormInput', () => {
|
||||
it('resolves select options from the relation target field for a relation-traversal filter', async () => {
|
||||
const { getByTestId } = render(<Wrapper />);
|
||||
const { getByTestId } = renderValueInput({
|
||||
recordFilter: relationToSelectFilter,
|
||||
dropdownFieldMetadataId: relationField.id,
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByTestId('multiselect-options-count').textContent).toBe(
|
||||
@@ -115,4 +196,29 @@ describe('AdvancedFilterSidePanelValueFormInput', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the workspace member "Me" picker for a direct relation-to-workspaceMember filter', async () => {
|
||||
const { getByTestId } = renderValueInput({
|
||||
recordFilter: workspaceMemberRelationFilter,
|
||||
dropdownFieldMetadataId: workspaceMemberRelationField.id,
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByTestId('workspace-member-filter-input')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('falls through to the generic field input for a direct relation to a non-workspaceMember object', async () => {
|
||||
const { getByTestId, queryByTestId } = renderValueInput({
|
||||
recordFilter: nonWorkspaceMemberRelationFilter,
|
||||
dropdownFieldMetadataId: nonWorkspaceMemberRelationField.id,
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByTestId('form-field-input')).toBeInTheDocument();
|
||||
});
|
||||
expect(
|
||||
queryByTestId('workspace-member-filter-input'),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user