[WEB-5379] fix: optimize filter handling in work item components (#8079)

This commit is contained in:
Prateek Shourya
2025-11-07 20:28:10 +05:30
committed by GitHub
parent 1e5f583a24
commit dfc49eea27
4 changed files with 112 additions and 63 deletions

View File

@@ -62,27 +62,28 @@ const WorkItemFilterRoot = observer((props: TWorkItemFilterProps) => {
[isTemporary, entityId]
);
// memoize initial values to prevent re-computations when reference changes
const initialUserFilters = useMemo(
() => initialWorkItemFilters.richFilters,
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
); // Empty dependency array to capture only the initial value
const initialUserFilters = useMemo(() => initialWorkItemFilters.richFilters, [initialWorkItemFilters]);
const workItemFiltersConfig = useWorkItemFiltersConfig({
allowedFilters: filtersToShowByLayout ? filtersToShowByLayout : [],
...entityConfigProps,
});
// get or create filter instance
const workItemLayoutFilter = getOrCreateFilter({
entityType,
entityId: workItemEntityID,
initialExpression: initialUserFilters,
onExpressionChange: updateFilters,
expressionOptions: {
saveViewOptions,
updateViewOptions,
},
showOnMount,
});
const workItemLayoutFilter = useMemo(
() =>
getOrCreateFilter({
entityType,
entityId: workItemEntityID,
initialExpression: initialUserFilters,
onExpressionChange: updateFilters,
expressionOptions: {
saveViewOptions,
updateViewOptions,
},
showOnMount,
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[entityType, workItemEntityID, saveViewOptions, updateViewOptions, updateFilters]
);
// delete filter instance when component unmounts
useEffect(
@@ -92,8 +93,14 @@ const WorkItemFilterRoot = observer((props: TWorkItemFilterProps) => {
[deleteFilter, entityType, workItemEntityID]
);
workItemLayoutFilter.configManager.setAreConfigsReady(workItemFiltersConfig.areAllConfigsInitialized);
workItemLayoutFilter.configManager.registerAll(workItemFiltersConfig.configs);
useEffect(() => {
workItemLayoutFilter.configManager.setAreConfigsReady(workItemFiltersConfig.areAllConfigsInitialized);
workItemLayoutFilter.configManager.registerAll(workItemFiltersConfig.configs);
}, [
workItemFiltersConfig.areAllConfigsInitialized,
workItemFiltersConfig.configs,
workItemLayoutFilter.configManager,
]);
return <>{typeof children === "function" ? children({ filter: workItemLayoutFilter }) : children}</>;
});

View File

@@ -88,6 +88,17 @@ export const ProjectLevelWorkItemFiltersHOC = observer((props: TProjectLevelWork
isCurrentUserOwner,
]
);
const createViewLabel = useMemo(() => props.saveViewOptions?.label, [props.saveViewOptions?.label]);
const updateViewLabel = useMemo(() => props.updateViewOptions?.label, [props.updateViewOptions?.label]);
const hasAdditionalChanges = useMemo(
() =>
!isEqual(initialWorkItemFilters?.displayFilters, viewDetails?.display_filters) ||
!isEqual(
removeNillKeys(initialWorkItemFilters?.displayProperties),
removeNillKeys(viewDetails?.display_properties)
),
[initialWorkItemFilters, viewDetails]
);
const getDefaultViewDetailPayload: () => Partial<IProjectView> = useCallback(
() => ({
@@ -108,6 +119,17 @@ export const ProjectLevelWorkItemFiltersHOC = observer((props: TProjectLevelWork
[initialWorkItemFilters]
);
const handleViewSave = useCallback(
(expression: TWorkItemFilterExpression) => {
setCreateViewPayload({
...getDefaultViewDetailPayload(),
...getViewFilterPayload(expression),
});
setIsCreateViewModalOpen(true);
},
[getDefaultViewDetailPayload, getViewFilterPayload]
);
const handleViewUpdate = useCallback(
(filterExpression: TWorkItemFilterExpression) => {
if (!viewDetails) {
@@ -153,6 +175,25 @@ export const ProjectLevelWorkItemFiltersHOC = observer((props: TProjectLevelWork
[viewDetails, updateView, workspaceSlug, projectId, getViewFilterPayload]
);
const saveViewOptions = useMemo(
() => ({
label: createViewLabel,
isDisabled: !canCreateView,
onViewSave: handleViewSave,
}),
[createViewLabel, canCreateView, handleViewSave]
);
const updateViewOptions = useMemo(
() => ({
label: updateViewLabel,
isDisabled: !canUpdateView,
hasAdditionalChanges,
onViewUpdate: handleViewUpdate,
}),
[updateViewLabel, canUpdateView, hasAdditionalChanges, handleViewUpdate]
);
return (
<>
<CreateUpdateProjectViewModal
@@ -177,28 +218,8 @@ export const ProjectLevelWorkItemFiltersHOC = observer((props: TProjectLevelWork
memberIds={getProjectMemberIds(projectId, false) ?? undefined}
moduleIds={getProjectModuleIds(projectId) ?? undefined}
stateIds={getProjectStateIds(projectId)}
saveViewOptions={{
label: props.saveViewOptions?.label,
isDisabled: !canCreateView,
onViewSave: (expression) => {
setCreateViewPayload({
...getDefaultViewDetailPayload(),
...getViewFilterPayload(expression),
});
setIsCreateViewModalOpen(true);
},
}}
updateViewOptions={{
label: props.updateViewOptions?.label,
isDisabled: !canUpdateView,
hasAdditionalChanges:
!isEqual(initialWorkItemFilters?.displayFilters, viewDetails?.display_filters) ||
!isEqual(
removeNillKeys(initialWorkItemFilters?.displayProperties),
removeNillKeys(viewDetails?.display_properties)
),
onViewUpdate: handleViewUpdate,
}}
saveViewOptions={saveViewOptions}
updateViewOptions={updateViewOptions}
>
{children}
</WorkItemFiltersHOC>

View File

@@ -70,6 +70,17 @@ export const WorkspaceLevelWorkItemFiltersHOC = observer((props: TWorkspaceLevel
isCurrentUserOwner,
]
);
const createViewLabel = useMemo(() => props.saveViewOptions?.label, [props.saveViewOptions?.label]);
const updateViewLabel = useMemo(() => props.updateViewOptions?.label, [props.updateViewOptions?.label]);
const hasAdditionalChanges = useMemo(
() =>
!isEqual(initialWorkItemFilters?.displayFilters, viewDetails?.display_filters) ||
!isEqual(
removeNillKeys(initialWorkItemFilters?.displayProperties),
removeNillKeys(viewDetails?.display_properties)
),
[initialWorkItemFilters, viewDetails]
);
const getDefaultViewDetailPayload: () => Partial<IWorkspaceView> = useCallback(
() => ({
@@ -89,6 +100,17 @@ export const WorkspaceLevelWorkItemFiltersHOC = observer((props: TWorkspaceLevel
[initialWorkItemFilters]
);
const handleViewSave = useCallback(
(expression: TWorkItemFilterExpression) => {
setCreateViewPayload({
...getDefaultViewDetailPayload(),
...getViewFilterPayload(expression),
});
setIsCreateViewModalOpen(true);
},
[getDefaultViewDetailPayload, getViewFilterPayload]
);
const handleViewUpdate = useCallback(
(filterExpression: TWorkItemFilterExpression) => {
if (!viewDetails) {
@@ -140,6 +162,25 @@ export const WorkspaceLevelWorkItemFiltersHOC = observer((props: TWorkspaceLevel
[viewDetails, updateGlobalView, workspaceSlug, getViewFilterPayload]
);
const saveViewOptions = useMemo(
() => ({
label: createViewLabel,
isDisabled: !canCreateView,
onViewSave: handleViewSave,
}),
[createViewLabel, canCreateView, handleViewSave]
);
const updateViewOptions = useMemo(
() => ({
label: updateViewLabel,
isDisabled: !canUpdateView,
hasAdditionalChanges,
onViewUpdate: handleViewUpdate,
}),
[updateViewLabel, canUpdateView, hasAdditionalChanges, handleViewUpdate]
);
return (
<>
<CreateUpdateWorkspaceViewModal
@@ -155,28 +196,8 @@ export const WorkspaceLevelWorkItemFiltersHOC = observer((props: TWorkspaceLevel
memberIds={getWorkspaceMemberIds(workspaceSlug)}
labelIds={getWorkspaceLabelIds(workspaceSlug)}
projectIds={joinedProjectIds}
saveViewOptions={{
label: props.saveViewOptions?.label,
isDisabled: !canCreateView,
onViewSave: (expression) => {
setCreateViewPayload({
...getDefaultViewDetailPayload(),
...getViewFilterPayload(expression),
});
setIsCreateViewModalOpen(true);
},
}}
updateViewOptions={{
label: props.updateViewOptions?.label,
isDisabled: !canUpdateView,
hasAdditionalChanges:
!isEqual(initialWorkItemFilters?.displayFilters, viewDetails?.display_filters) ||
!isEqual(
removeNillKeys(initialWorkItemFilters?.displayProperties),
removeNillKeys(viewDetails?.display_properties)
),
onViewUpdate: handleViewUpdate,
}}
saveViewOptions={saveViewOptions}
updateViewOptions={updateViewOptions}
>
{children}
</WorkItemFiltersHOC>

View File

@@ -160,7 +160,7 @@ export class FilterInstance<P extends TFilterProperty, E extends TExternalFilter
id: observable,
initialFilterExpression: observable,
expression: observable,
expressionOptions: observable,
expressionOptions: observable.struct,
adapter: observable,
configManager: observable,
// computed