From c593d5df1b47c94cf314003477699824bf3fbb0e Mon Sep 17 00:00:00 2001 From: rahulramesha <71900764+rahulramesha@users.noreply.github.com> Date: Thu, 18 Jan 2024 15:51:17 +0530 Subject: [PATCH] fix: enable global/ all issues (#3405) * fix global issues and views * remove separate layouts for specific views * add permissions to views * fix global issues filters --------- Co-authored-by: Rahul R --- web/components/headers/global-issues.tsx | 11 ++- .../filters/applied-filters/filters-list.tsx | 5 +- .../filters/applied-filters/members.tsx | 4 +- .../roots/global-view-root.tsx | 75 +++++++++++-------- .../roots/project-view-root.tsx | 28 ++++--- .../roots/all-issue-layout-root.tsx | 53 +++++++------ .../roots/project-view-layout-root.tsx | 2 +- .../spreadsheet/base-spreadsheet-root.tsx | 4 +- .../spreadsheet/spreadsheet-table.tsx | 6 +- .../spreadsheet/spreadsheet-view.tsx | 8 +- web/components/workspace/views/form.tsx | 6 +- web/components/workspace/views/header.tsx | 28 ++++--- web/components/workspace/views/modal.tsx | 6 +- .../workspace/views/view-list-item.tsx | 2 +- web/helpers/filter.helper.ts | 16 ---- .../workspace-views/all-issues.tsx | 24 ------ .../workspace-views/assigned.tsx | 24 ------ .../workspace-views/created.tsx | 24 ------ .../workspace-views/subscribed.tsx | 24 ------ .../helpers/issue-filter-helper.store.ts | 34 +++++++++ web/store/issue/project-views/filter.store.ts | 3 - web/store/issue/workspace/filter.store.ts | 55 ++++++++------ web/store/issue/workspace/issue.store.ts | 24 +++--- 23 files changed, 214 insertions(+), 252 deletions(-) delete mode 100644 web/pages/[workspaceSlug]/workspace-views/all-issues.tsx delete mode 100644 web/pages/[workspaceSlug]/workspace-views/assigned.tsx delete mode 100644 web/pages/[workspaceSlug]/workspace-views/created.tsx delete mode 100644 web/pages/[workspaceSlug]/workspace-views/subscribed.tsx diff --git a/web/components/headers/global-issues.tsx b/web/components/headers/global-issues.tsx index e7d4c12d2..aa9a32bc9 100644 --- a/web/components/headers/global-issues.tsx +++ b/web/components/headers/global-issues.tsx @@ -12,7 +12,12 @@ import { Breadcrumbs, Button, LayersIcon, PhotoFilterIcon, Tooltip } from "@plan // icons import { List, PlusIcon, Sheet } from "lucide-react"; // types -import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types"; +import { + IIssueDisplayFilterOptions, + IIssueDisplayProperties, + IIssueFilterOptions, + TStaticViewTypes, +} from "@plane/types"; // constants import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue"; import { EUserWorkspaceRoles } from "constants/workspace"; @@ -35,7 +40,7 @@ export const GlobalIssuesHeader: React.FC = observer((props) => { const { workspaceSlug, globalViewId } = router.query; // store hooks const { - issuesFilter: { issueFilters, updateFilters }, + issuesFilter: { filters, updateFilters }, } = useIssues(EIssuesStoreType.GLOBAL); const { membership: { currentWorkspaceRole }, @@ -47,6 +52,8 @@ export const GlobalIssuesHeader: React.FC = observer((props) => { workspace: { workspaceMemberIds }, } = useMember(); + const issueFilters = globalViewId ? filters[globalViewId.toString()] : undefined; + const handleFiltersUpdate = useCallback( (key: keyof IIssueFilterOptions, value: string | string[]) => { if (!workspaceSlug || !globalViewId) return; diff --git a/web/components/issues/issue-layouts/filters/applied-filters/filters-list.tsx b/web/components/issues/issue-layouts/filters/applied-filters/filters-list.tsx index 18eac8525..4ca2538e5 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/filters-list.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/filters-list.tsx @@ -25,13 +25,14 @@ type Props = { handleRemoveFilter: (key: keyof IIssueFilterOptions, value: string | null) => void; labels?: IIssueLabel[] | undefined; states?: IState[] | undefined; + alwaysAllowEditing?: boolean; }; const membersFilters = ["assignees", "mentions", "created_by", "subscriber"]; const dateFilters = ["start_date", "target_date"]; export const AppliedFiltersList: React.FC = observer((props) => { - const { appliedFilters, handleClearAllFilters, handleRemoveFilter, labels, states } = props; + const { appliedFilters, handleClearAllFilters, handleRemoveFilter, labels, states, alwaysAllowEditing } = props; // store hooks const { membership: { currentProjectRole }, @@ -41,7 +42,7 @@ export const AppliedFiltersList: React.FC = observer((props) => { if (Object.keys(appliedFilters).length === 0) return null; - const isEditingAllowed = currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER; + const isEditingAllowed = alwaysAllowEditing || (currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER); return (
diff --git a/web/components/issues/issue-layouts/filters/applied-filters/members.tsx b/web/components/issues/issue-layouts/filters/applied-filters/members.tsx index 94ea9221e..ff5034c97 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/members.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/members.tsx @@ -15,13 +15,13 @@ export const AppliedMembersFilters: React.FC = observer((props) => { const { handleRemove, values, editable } = props; const { - project: { getProjectMemberDetails }, + workspace: { getWorkspaceMemberDetails }, } = useMember(); return ( <> {values.map((memberId) => { - const memberDetails = getProjectMemberDetails(memberId)?.member; + const memberDetails = getWorkspaceMemberDetails(memberId)?.member; if (!memberDetails) return null; diff --git a/web/components/issues/issue-layouts/filters/applied-filters/roots/global-view-root.tsx b/web/components/issues/issue-layouts/filters/applied-filters/roots/global-view-root.tsx index 87bb719c4..4d8ad5196 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/roots/global-view-root.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/roots/global-view-root.tsx @@ -1,32 +1,48 @@ import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; +import isEqual from "lodash/isEqual"; // hooks -import { useIssues, useLabel } from "hooks/store"; +import { useGlobalView, useIssues, useLabel, useUser } from "hooks/store"; +//ui +import { Button } from "@plane/ui"; // components import { AppliedFiltersList } from "components/issues"; // types -import { IIssueFilterOptions } from "@plane/types"; +import { IIssueFilterOptions, TStaticViewTypes } from "@plane/types"; import { EIssueFilterType, EIssuesStoreType } from "constants/issue"; +import { DEFAULT_GLOBAL_VIEWS_LIST, EUserWorkspaceRoles } from "constants/workspace"; -export const GlobalViewsAppliedFiltersRoot = observer(() => { +type Props = { + globalViewId: string; +}; + +export const GlobalViewsAppliedFiltersRoot = observer((props: Props) => { + const { globalViewId } = props; // router const router = useRouter(); - const { workspaceSlug, globalViewId } = router.query; + const { workspaceSlug } = router.query; // store hooks const { - issuesFilter: { issueFilters, updateFilters }, + issuesFilter: { filters, updateFilters }, } = useIssues(EIssuesStoreType.GLOBAL); const { workspace: { workspaceLabels }, } = useLabel(); + const { globalViewMap, updateGlobalView } = useGlobalView(); + const { + membership: { currentWorkspaceRole }, + } = useUser(); + // derived values - const userFilters = issueFilters?.filters; + const userFilters = filters?.[globalViewId]?.filters; + const viewDetails = globalViewMap[globalViewId]; // filters whose value not null or empty array - const appliedFilters: IIssueFilterOptions = {}; + let appliedFilters: IIssueFilterOptions | undefined = undefined; Object.entries(userFilters ?? {}).forEach(([key, value]) => { if (!value) return; if (Array.isArray(value) && value.length === 0) return; + if (!appliedFilters) appliedFilters = {}; appliedFilters[key as keyof IIssueFilterOptions] = value; }); @@ -70,29 +86,24 @@ export const GlobalViewsAppliedFiltersRoot = observer(() => { ); }; - // const handleUpdateView = () => { - // if (!workspaceSlug || !globalViewId || !viewDetails) return; + const handleUpdateView = () => { + if (!workspaceSlug || !globalViewId) return; - // globalViewsStore.updateGlobalView(workspaceSlug.toString(), globalViewId.toString(), { - // query_data: { - // ...viewDetails.query_data, - // filters: { - // ...(storedFilters ?? {}), - // }, - // }, - // }); - // }; + updateGlobalView(workspaceSlug.toString(), globalViewId.toString(), { + filters: { + ...(appliedFilters ?? {}), + }, + }); + }; - // update stored filters when view details are fetched - // useEffect(() => { - // if (!globalViewId || !viewDetails) return; + const areFiltersEqual = isEqual(appliedFilters, viewDetails?.filters); - // if (!globalViewFiltersStore.storedFilters[globalViewId.toString()]) - // globalViewFiltersStore.updateStoredFilters(globalViewId.toString(), viewDetails?.query_data?.filters ?? {}); - // }, [globalViewId, globalViewFiltersStore, viewDetails]); + const isAuthorizedUser = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER; + + const isDefaultView = DEFAULT_GLOBAL_VIEWS_LIST.map((view) => view.key).includes(globalViewId as TStaticViewTypes); // return if no filters are applied - if (Object.keys(appliedFilters).length === 0) return null; + if (!appliedFilters && areFiltersEqual) return null; return (
@@ -101,13 +112,17 @@ export const GlobalViewsAppliedFiltersRoot = observer(() => { appliedFilters={appliedFilters ?? {}} handleClearAllFilters={handleClearAllFilters} handleRemoveFilter={handleRemoveFilter} + alwaysAllowEditing /> - {/* {storedFilters && viewDetails && areFiltersDifferent(storedFilters, viewDetails.query_data.filters ?? {}) && ( - - )} */} + {!isDefaultView && !areFiltersEqual && isAuthorizedUser && ( + <> +
+ + + )}
); }); diff --git a/web/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx b/web/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx index 6748676b5..c10de461a 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx @@ -1,13 +1,12 @@ import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; +import isEqual from "lodash/isEqual"; // hooks import { useIssues, useLabel, useProjectState, useProjectView } from "hooks/store"; // components import { AppliedFiltersList } from "components/issues"; // ui import { Button } from "@plane/ui"; -// helpers -import { areFiltersDifferent } from "helpers/filter.helper"; // types import { IIssueFilterOptions } from "@plane/types"; import { EIssueFilterType, EIssuesStoreType } from "constants/issue"; @@ -33,10 +32,11 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => { const viewDetails = viewId ? viewMap[viewId.toString()] : null; const userFilters = issueFilters?.filters; // filters whose value not null or empty array - const appliedFilters: IIssueFilterOptions = {}; + let appliedFilters: IIssueFilterOptions | undefined = undefined; Object.entries(userFilters ?? {}).forEach(([key, value]) => { if (!value) return; if (Array.isArray(value) && value.length === 0) return; + if (!appliedFilters) appliedFilters = {}; appliedFilters[key as keyof IIssueFilterOptions] = value; }); @@ -78,9 +78,9 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => { updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { ...newFilters }, viewId); }; + const areFiltersEqual = isEqual(appliedFilters, viewDetails?.filters); // return if no filters are applied - if (Object.keys(appliedFilters).length === 0 && !areFiltersDifferent(appliedFilters, viewDetails?.filters ?? {})) - return null; + if (!appliedFilters && areFiltersEqual) return null; const handleUpdateView = () => { if (!workspaceSlug || !projectId || !viewId || !viewDetails) return; @@ -95,19 +95,23 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => { return (
- {viewDetails?.filters && areFiltersDifferent(appliedFilters, viewDetails?.filters ?? {}) && ( -
- -
+ {!areFiltersEqual && ( + <> +
+
+ +
+ )}
); diff --git a/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx b/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx index 18d60bf68..b87bec2d0 100644 --- a/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx @@ -16,43 +16,43 @@ import { EIssueActions } from "../types"; import { EUserProjectRoles } from "constants/project"; import { EIssueFilterType, EIssuesStoreType } from "constants/issue"; -type Props = { - type?: TStaticViewTypes | null; -}; -export const AllIssueLayoutRoot: React.FC = observer((props) => { - const { type = null } = props; + +export const AllIssueLayoutRoot: React.FC = observer(() => { // router const router = useRouter(); - const { workspaceSlug, globalViewId } = router.query as { workspaceSlug: string; globalViewId: string }; + const { workspaceSlug, globalViewId } = router.query; // store const { - issuesFilter: { issueFilters, fetchFilters, updateFilters }, + issuesFilter: { filters, fetchFilters, updateFilters }, issues: { loader, groupedIssueIds, fetchIssues, updateIssue, removeIssue }, - issueMap, } = useIssues(EIssuesStoreType.GLOBAL); + const { dataViewId, issueIds } = groupedIssueIds; const { membership: { currentWorkspaceAllProjectsRole }, } = useUser(); const { fetchAllGlobalViews } = useGlobalView(); // derived values - const currentIssueView = type ?? globalViewId; useSWR(workspaceSlug ? `WORKSPACE_GLOBAL_VIEWS${workspaceSlug}` : null, async () => { if (workspaceSlug) { - await fetchAllGlobalViews(workspaceSlug); + await fetchAllGlobalViews(workspaceSlug.toString()); } }); useSWR( - workspaceSlug && currentIssueView ? `WORKSPACE_GLOBAL_VIEW_ISSUES_${workspaceSlug}_${currentIssueView}` : null, + workspaceSlug && globalViewId ? `WORKSPACE_GLOBAL_VIEW_ISSUES_${workspaceSlug}_${globalViewId}` : null, async () => { - if (workspaceSlug && currentIssueView) { - await fetchAllGlobalViews(workspaceSlug); - await fetchFilters(workspaceSlug, currentIssueView); - await fetchIssues(workspaceSlug, currentIssueView, groupedIssueIds ? "mutation" : "init-loader"); + if (workspaceSlug && globalViewId) { + await fetchAllGlobalViews(workspaceSlug.toString()); + await fetchFilters(workspaceSlug.toString(), globalViewId.toString()); + await fetchIssues( + workspaceSlug.toString(), + globalViewId.toString(), + groupedIssueIds ? "mutation" : "init-loader" + ); } } ); @@ -65,22 +65,21 @@ export const AllIssueLayoutRoot: React.FC = observer((props) => { return !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER; }; - const issueIds = (groupedIssueIds ?? []) as TUnGroupedIssues; - const issuesArray = issueIds?.filter((id: string) => id && issueMap?.[id]).map((id: string) => issueMap?.[id]); + const issueFilters = globalViewId ? filters?.[globalViewId.toString()] : undefined; const issueActions = useMemo( () => ({ [EIssueActions.UPDATE]: async (issue: TIssue) => { const projectId = issue.project_id; - if (!workspaceSlug || !projectId) return; + if (!workspaceSlug || !projectId || !globalViewId) return; - await updateIssue(workspaceSlug, projectId, issue.id, issue, currentIssueView); + await updateIssue(workspaceSlug.toString(), projectId, issue.id, issue, globalViewId.toString()); }, [EIssueActions.DELETE]: async (issue: TIssue) => { const projectId = issue.project_id; - if (!workspaceSlug || !projectId) return; + if (!workspaceSlug || !projectId || !globalViewId) return; - await removeIssue(workspaceSlug, projectId, issue.id, currentIssueView); + await removeIssue(workspaceSlug.toString(), projectId, issue.id, globalViewId.toString()); }, }), // eslint-disable-next-line react-hooks/exhaustive-deps @@ -100,22 +99,22 @@ export const AllIssueLayoutRoot: React.FC = observer((props) => { (updatedDisplayFilter: Partial) => { if (!workspaceSlug) return; - updateFilters(workspaceSlug, undefined, EIssueFilterType.DISPLAY_FILTERS, { ...updatedDisplayFilter }); + updateFilters(workspaceSlug.toString(), undefined, EIssueFilterType.DISPLAY_FILTERS, { ...updatedDisplayFilter }); }, [updateFilters, workspaceSlug] ); return (
- {globalViewId != currentIssueView && (loader === "init-loader" || !groupedIssueIds) ? ( + {!globalViewId || globalViewId !== dataViewId || loader === "init-loader" || !issueIds ? (
) : ( <> - + - {(groupedIssueIds ?? {}).length == 0 ? ( + {(issueIds ?? {}).length == 0 ? ( <>{/* */} ) : (
@@ -123,7 +122,7 @@ export const AllIssueLayoutRoot: React.FC = observer((props) => { displayProperties={issueFilters?.displayProperties ?? {}} displayFilters={issueFilters?.displayFilters ?? {}} handleDisplayFilterUpdate={handleDisplayFiltersUpdate} - issues={issuesArray} + issueIds={issueIds} quickActions={(issue) => ( = observer((props) => { )} handleIssues={handleIssues} canEditProperties={canEditProperties} - viewId={currentIssueView} + viewId={globalViewId} />
)} diff --git a/web/components/issues/issue-layouts/roots/project-view-layout-root.tsx b/web/components/issues/issue-layouts/roots/project-view-layout-root.tsx index 11564ca84..d3d4f0417 100644 --- a/web/components/issues/issue-layouts/roots/project-view-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/project-view-layout-root.tsx @@ -30,7 +30,7 @@ export const ProjectViewLayoutRoot: React.FC = observer(() => { const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT_VIEW); useSWR( - workspaceSlug && projectId && viewId ? `PROJECT_VIEW_ISSUES_${workspaceSlug}_${projectId}` : null, + workspaceSlug && projectId && viewId ? `PROJECT_VIEW_ISSUES_${workspaceSlug}_${projectId}_${viewId}` : null, async () => { if (workspaceSlug && projectId && viewId) { await issuesFilter?.fetchFilters(workspaceSlug.toString(), projectId.toString(), viewId.toString()); diff --git a/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx b/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx index 9ffc7c641..3a022d447 100644 --- a/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx @@ -58,8 +58,6 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => { const issueIds = (issueStore.groupedIssueIds ?? []) as TUnGroupedIssues; - const issues = issueIds?.filter((id) => id && issueMap?.[id]).map((id) => issueMap?.[id]); - const handleIssues = useCallback( async (issue: TIssue, action: EIssueActions) => { if (issueActions[action]) { @@ -109,7 +107,7 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => { displayProperties={issueFiltersStore.issueFilters?.displayProperties ?? {}} displayFilters={issueFiltersStore.issueFilters?.displayFilters ?? {}} handleDisplayFilterUpdate={handleDisplayFiltersUpdate} - issues={issues} + issueIds={issueIds} quickActions={renderQuickActions} handleIssues={handleIssues} canEditProperties={canEditProperties} diff --git a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-table.tsx b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-table.tsx index 6355a3a31..e287c6d84 100644 --- a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-table.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-table.tsx @@ -10,7 +10,7 @@ type Props = { displayProperties: IIssueDisplayProperties; displayFilters: IIssueDisplayFilterOptions; handleDisplayFilterUpdate: (data: Partial) => void; - issues: TIssue[]; + issueIds: string[]; isEstimateEnabled: boolean; quickActions: ( issue: TIssue, @@ -27,7 +27,7 @@ export const SpreadsheetTable = observer((props: Props) => { displayProperties, displayFilters, handleDisplayFilterUpdate, - issues, + issueIds, isEstimateEnabled, portalElement, quickActions, @@ -44,7 +44,7 @@ export const SpreadsheetTable = observer((props: Props) => { isEstimateEnabled={isEstimateEnabled} /> - {issues.map(({ id }) => ( + {issueIds.map((id) => ( ) => void; - issues: TIssue[] | undefined; + issueIds: string[] | undefined; quickActions: ( issue: TIssue, customActionButton?: React.ReactElement, @@ -39,7 +39,7 @@ export const SpreadsheetView: React.FC = observer((props) => { displayProperties, displayFilters, handleDisplayFilterUpdate, - issues, + issueIds, quickActions, handleIssues, quickAddCallback, @@ -91,7 +91,7 @@ export const SpreadsheetView: React.FC = observer((props) => { }; }, []); - if (!issues || issues.length === 0) + if (!issueIds || issueIds.length === 0) return (
@@ -106,7 +106,7 @@ export const SpreadsheetView: React.FC = observer((props) => { displayProperties={displayProperties} displayFilters={displayFilters} handleDisplayFilterUpdate={handleDisplayFilterUpdate} - issues={issues} + issueIds={issueIds} isEstimateEnabled={isEstimateEnabled} portalElement={portalRef} quickActions={quickActions} diff --git a/web/components/workspace/views/form.tsx b/web/components/workspace/views/form.tsx index 36c2f66b4..80df40bdd 100644 --- a/web/components/workspace/views/form.tsx +++ b/web/components/workspace/views/form.tsx @@ -61,12 +61,12 @@ export const WorkspaceViewForm: React.FC = observer((props) => { }); }, [data, preLoadedData, reset]); - const selectedFilters = watch("query_data")?.filters; + const selectedFilters = watch("filters"); const clearAllFilters = () => { if (!selectedFilters) return; - setValue("query_data.filters", {}); + setValue("filters", {}); }; return ( @@ -120,7 +120,7 @@ export const WorkspaceViewForm: React.FC = observer((props) => {
( { const { viewId } = props; @@ -46,6 +46,9 @@ export const GlobalViewsHeader: React.FC = observer(() => { const { workspaceSlug, globalViewId } = router.query; // store hooks const { currentWorkspaceViews } = useGlobalView(); + const { + membership: { currentWorkspaceRole }, + } = useUser(); // bring the active view to the centre of the header useEffect(() => { @@ -56,7 +59,8 @@ export const GlobalViewsHeader: React.FC = observer(() => { if (activeTabElement) activeTabElement.scrollIntoView({ behavior: "smooth", inline: "center" }); }, [globalViewId]); - const isTabSelected = (tabKey: string) => router.pathname.includes(tabKey); + const isAuthorizedUser = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER; + return ( <> setCreateViewModal(false)} /> @@ -66,7 +70,7 @@ export const GlobalViewsHeader: React.FC = observer(() => { { ))}
- + {isAuthorizedUser && ( + + )}
); diff --git a/web/components/workspace/views/modal.tsx b/web/components/workspace/views/modal.tsx index 280bf795e..b015b4cb6 100644 --- a/web/components/workspace/views/modal.tsx +++ b/web/components/workspace/views/modal.tsx @@ -36,8 +36,8 @@ export const CreateUpdateWorkspaceViewModal: React.FC = observer((props) const payloadData: Partial = { ...payload, - query: { - ...payload.query_data?.filters, + filters: { + ...payload?.filters, }, }; @@ -67,7 +67,7 @@ export const CreateUpdateWorkspaceViewModal: React.FC = observer((props) const payloadData: Partial = { ...payload, query: { - ...payload.query_data?.filters, + ...payload?.filters, }, }; diff --git a/web/components/workspace/views/view-list-item.tsx b/web/components/workspace/views/view-list-item.tsx index af8499ec4..25be61a59 100644 --- a/web/components/workspace/views/view-list-item.tsx +++ b/web/components/workspace/views/view-list-item.tsx @@ -30,7 +30,7 @@ export const GlobalViewListItem: React.FC = observer((props) => { if (!view) return null; - const totalFilters = calculateTotalFilters(view.query_data.filters ?? {}); + const totalFilters = calculateTotalFilters(view.filters ?? {}); return ( <> diff --git a/web/helpers/filter.helper.ts b/web/helpers/filter.helper.ts index ad76decab..0b30f95e1 100644 --- a/web/helpers/filter.helper.ts +++ b/web/helpers/filter.helper.ts @@ -14,19 +14,3 @@ export const calculateTotalFilters = (filters: IIssueFilterOptions): number => .reduce((curr, prev) => curr + prev, 0) : 0; -// check if there is any difference between the saved filters and the current filters -export const areFiltersDifferent = (filtersSet1: IIssueFilterOptions, filtersSet2: IIssueFilterOptions) => { - for (const [key, value] of Object.entries(filtersSet1) as [keyof IIssueFilterOptions, string[] | null][]) { - if (value) { - if (Array.isArray(value) && Array.isArray(filtersSet2[key])) { - if (value.length !== filtersSet2[key]?.length) return true; - - for (let i = 0; i < value.length; i++) { - if (!filtersSet2[key]?.includes(value[i])) return true; - } - } else if (value !== filtersSet2[key]) return true; - } - } - - return false; -}; diff --git a/web/pages/[workspaceSlug]/workspace-views/all-issues.tsx b/web/pages/[workspaceSlug]/workspace-views/all-issues.tsx deleted file mode 100644 index 3ef478760..000000000 --- a/web/pages/[workspaceSlug]/workspace-views/all-issues.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { ReactElement } from "react"; -// components -import { GlobalViewsHeader } from "components/workspace"; -import { GlobalIssuesHeader } from "components/headers"; -import { AllIssueLayoutRoot } from "components/issues/issue-layouts"; -// layouts -import { AppLayout } from "layouts/app-layout"; -// types -import { NextPageWithLayout } from "lib/types"; - -const GlobalViewAllIssuesPage: NextPageWithLayout = () => ( -
-
- - -
-
-); - -GlobalViewAllIssuesPage.getLayout = function getLayout(page: ReactElement) { - return }>{page}; -}; - -export default GlobalViewAllIssuesPage; diff --git a/web/pages/[workspaceSlug]/workspace-views/assigned.tsx b/web/pages/[workspaceSlug]/workspace-views/assigned.tsx deleted file mode 100644 index c6115bc94..000000000 --- a/web/pages/[workspaceSlug]/workspace-views/assigned.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { ReactElement } from "react"; -// components -import { GlobalViewsHeader } from "components/workspace"; -import { GlobalIssuesHeader } from "components/headers"; -import { AllIssueLayoutRoot } from "components/issues/issue-layouts"; -// layouts -import { AppLayout } from "layouts/app-layout"; -// types -import { NextPageWithLayout } from "lib/types"; - -const GlobalViewAssignedIssuesPage: NextPageWithLayout = () => ( -
-
- - -
-
-); - -GlobalViewAssignedIssuesPage.getLayout = function getLayout(page: ReactElement) { - return }>{page}; -}; - -export default GlobalViewAssignedIssuesPage; diff --git a/web/pages/[workspaceSlug]/workspace-views/created.tsx b/web/pages/[workspaceSlug]/workspace-views/created.tsx deleted file mode 100644 index 8d3d54c3b..000000000 --- a/web/pages/[workspaceSlug]/workspace-views/created.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { ReactElement } from "react"; -// components -import { GlobalViewsHeader } from "components/workspace"; -import { GlobalIssuesHeader } from "components/headers"; -import { AllIssueLayoutRoot } from "components/issues"; -// layouts -import { AppLayout } from "layouts/app-layout"; -// types -import { NextPageWithLayout } from "lib/types"; - -const GlobalViewCreatedIssuesPage: NextPageWithLayout = () => ( -
-
- - -
-
-); - -GlobalViewCreatedIssuesPage.getLayout = function getLayout(page: ReactElement) { - return }>{page}; -}; - -export default GlobalViewCreatedIssuesPage; diff --git a/web/pages/[workspaceSlug]/workspace-views/subscribed.tsx b/web/pages/[workspaceSlug]/workspace-views/subscribed.tsx deleted file mode 100644 index 5d6020434..000000000 --- a/web/pages/[workspaceSlug]/workspace-views/subscribed.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { ReactElement } from "react"; -// layouts -import { AppLayout } from "layouts/app-layout"; -// components -import { GlobalViewsHeader } from "components/workspace"; -import { GlobalIssuesHeader } from "components/headers"; -import { AllIssueLayoutRoot } from "components/issues"; -// types -import { NextPageWithLayout } from "lib/types"; - -const GlobalViewSubscribedIssuesPage: NextPageWithLayout = () => ( -
-
- - -
-
-); - -GlobalViewSubscribedIssuesPage.getLayout = function getLayout(page: ReactElement) { - return }>{page}; -}; - -export default GlobalViewSubscribedIssuesPage; diff --git a/web/store/issue/helpers/issue-filter-helper.store.ts b/web/store/issue/helpers/issue-filter-helper.store.ts index 55ffe3e81..352b641de 100644 --- a/web/store/issue/helpers/issue-filter-helper.store.ts +++ b/web/store/issue/helpers/issue-filter-helper.store.ts @@ -8,6 +8,7 @@ import { IIssueFiltersResponse, TIssueKanbanFilters, TIssueParams, + TStaticViewTypes, } from "@plane/types"; // constants import { isNil } from "constants/common"; @@ -109,6 +110,39 @@ export class IssueFilterHelperStore implements IIssueFilterHelperStore { subscriber: filters?.subscriber || null, }); + /** + * This PR is to get the filters of the fixed global views + * @param currentUserId current logged in user id + * @param type fixed view type + * @returns filterOptions based on views + */ + getComputedFiltersBasedOnViews = (currentUserId: string | undefined, type: TStaticViewTypes) => { + const noFilters = this.computedFilters({}); + + if (!currentUserId) return noFilters; + + switch (type) { + case "assigned": + return { + ...noFilters, + assignees: [currentUserId], + }; + case "created": + return { + ...noFilters, + created_by: [currentUserId], + }; + case "subscribed": + return { + ...noFilters, + subscriber: [currentUserId], + }; + case "all-issues": + default: + return noFilters; + } + }; + /** * @description This method is used to apply the display filters on the issues * @param {IIssueDisplayFilterOptions} displayFilters diff --git a/web/store/issue/project-views/filter.store.ts b/web/store/issue/project-views/filter.store.ts index d01f27434..610d92998 100644 --- a/web/store/issue/project-views/filter.store.ts +++ b/web/store/issue/project-views/filter.store.ts @@ -160,9 +160,6 @@ export class ProjectViewIssuesFilter extends IssueFilterHelperStore implements I }); this.rootIssueStore.projectViewIssues.fetchIssues(workspaceSlug, projectId, "mutation", viewId); - await this.issueFilterService.patchView(workspaceSlug, projectId, viewId, { - filters: _filters.filters, - }); break; case EIssueFilterType.DISPLAY_FILTERS: const updatedDisplayFilters = filters as IIssueDisplayFilterOptions; diff --git a/web/store/issue/workspace/filter.store.ts b/web/store/issue/workspace/filter.store.ts index 4825f593c..dcacb117e 100644 --- a/web/store/issue/workspace/filter.store.ts +++ b/web/store/issue/workspace/filter.store.ts @@ -14,6 +14,7 @@ import { TIssueKanbanFilters, IIssueFilters, TIssueParams, + TStaticViewTypes, } from "@plane/types"; // constants import { EIssueFilterType, EIssuesStoreType } from "constants/issue"; @@ -27,7 +28,7 @@ export interface IWorkspaceIssuesFilter { // computed issueFilters: IIssueFilters | undefined; appliedFilters: Partial> | undefined; - // action + // fetch action fetchFilters: (workspaceSlug: string, viewId: string) => Promise; updateFilters: ( workspaceSlug: string, @@ -36,6 +37,9 @@ export interface IWorkspaceIssuesFilter { filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters, viewId?: string | undefined ) => Promise; + //helper action + getIssueFilters: (viewId: string | undefined) => IIssueFilters | undefined; + getAppliedFilters: (viewId: string) => Partial> | undefined; } export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWorkspaceIssuesFilter { @@ -54,9 +58,12 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo // computed issueFilters: computed, appliedFilters: computed, - // actions + // fetch actions fetchFilters: action, updateFilters: action, + // helper actions + getIssueFilters: action, + getAppliedFilters: action, }); // root store this.rootIssueStore = _rootStore; @@ -64,8 +71,7 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo this.issueFilterService = new WorkspaceService(); } - get issueFilters() { - const viewId = this.rootIssueStore.globalViewId; + getIssueFilters = (viewId: string | undefined) => { if (!viewId) return undefined; const displayFilters = this.filters[viewId] || undefined; @@ -74,10 +80,12 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo const _filters: IIssueFilters = this.computedIssueFilters(displayFilters); return _filters; - } + }; - get appliedFilters() { - const userFilters = this.issueFilters; + getAppliedFilters = (viewId: string | undefined) => { + if (!viewId) return undefined; + + const userFilters = this.getIssueFilters(viewId); if (!userFilters) return undefined; const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "issues"); @@ -92,6 +100,16 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true; return filteredRouteParams; + }; + + get issueFilters() { + const viewId = this.rootIssueStore.globalViewId; + return this.getIssueFilters(viewId); + } + + get appliedFilters() { + const viewId = this.rootIssueStore.globalViewId; + return this.getAppliedFilters(viewId); } fetchFilters = async (workspaceSlug: string, viewId: TWorkspaceFilters) => { @@ -105,15 +123,17 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo }; const _filters = this.handleIssuesLocalFilters.get(EIssuesStoreType.GLOBAL, workspaceSlug, undefined, viewId); - filters = this.computedFilters(_filters?.filters); - displayFilters = this.computedDisplayFilters(_filters?.displayFilters); - displayProperties = this.computedDisplayProperties(_filters?.displayProperties); + displayFilters = this.computedDisplayFilters(_filters?.display_filters); + displayProperties = this.computedDisplayProperties(_filters?.display_properties); kanbanFilters = { - group_by: _filters?.kanbanFilters?.group_by || [], - sub_group_by: _filters?.kanbanFilters?.sub_group_by || [], + group_by: _filters?.kanban_filters?.group_by || [], + sub_group_by: _filters?.kanban_filters?.sub_group_by || [], }; - if (!["all-issues", "assigned", "created", "subscribed"].includes(viewId)) { + if (["all-issues", "assigned", "created", "subscribed"].includes(viewId)) { + const currentUserId = this.rootIssueStore.currentUserId; + filters = this.getComputedFiltersBasedOnViews(currentUserId, viewId as TStaticViewTypes); + } else { const _filters = await this.issueFilterService.getViewDetails(workspaceSlug, viewId); filters = this.computedFilters(_filters?.filters); displayFilters = this.computedDisplayFilters(_filters?.display_filters); @@ -160,16 +180,7 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo set(this.filters, [viewId, "filters", _key], updatedFilters[_key as keyof IIssueFilterOptions]); }); }); - this.rootIssueStore.workspaceIssues.fetchIssues(workspaceSlug, viewId, "mutation"); - if (["all-issues", "assigned", "created", "subscribed"].includes(viewId)) - this.handleIssuesLocalFilters.set(EIssuesStoreType.GLOBAL, type, workspaceSlug, undefined, viewId, { - filters: _filters.filters, - }); - else - await this.issueFilterService.updateView(workspaceSlug, viewId, { - filters: _filters.filters, - }); break; case EIssueFilterType.DISPLAY_FILTERS: const updatedDisplayFilters = filters as IIssueDisplayFilterOptions; diff --git a/web/store/issue/workspace/issue.store.ts b/web/store/issue/workspace/issue.store.ts index 955167d46..7e317e5b4 100644 --- a/web/store/issue/workspace/issue.store.ts +++ b/web/store/issue/workspace/issue.store.ts @@ -15,7 +15,7 @@ export interface IWorkspaceIssues { issues: { [viewId: string]: string[] }; viewFlags: ViewFlags; // computed - groupedIssueIds: TUnGroupedIssues | undefined; + groupedIssueIds: { dataViewId: string; issueIds: TUnGroupedIssues | undefined }; // actions fetchIssues: (workspaceSlug: string, viewId: string, loadType: TLoader) => Promise; createIssue: ( @@ -59,7 +59,7 @@ export class WorkspaceIssues extends IssueHelperStore implements IWorkspaceIssue makeObservable(this, { // observable loader: observable.ref, - issues: observable.ref, + issues: observable, // computed groupedIssueIds: computed, // action @@ -77,30 +77,32 @@ export class WorkspaceIssues extends IssueHelperStore implements IWorkspaceIssue get groupedIssueIds() { const viewId = this.rootIssueStore.globalViewId; - if (!viewId) return undefined; + if (!viewId) return { dataViewId: "", issueIds: undefined }; - const displayFilters = this.rootIssueStore?.workspaceIssuesFilter?.issueFilters?.displayFilters; - if (!displayFilters) return undefined; + const displayFilters = this.rootIssueStore?.workspaceIssuesFilter?.filters?.[viewId]?.displayFilters; + if (!displayFilters) return { dataViewId: viewId, issueIds: undefined }; const orderBy = displayFilters?.order_by; - const viewIssueIds = this.issues[viewId] ?? []; + const viewIssueIds = this.issues[viewId]; + + if (!viewIssueIds) return { dataViewId: viewId, issueIds: undefined }; const _issues = this.rootStore.issues.getIssuesByIds(viewIssueIds); - if (!_issues) return undefined; + if (!_issues) return { dataViewId: viewId, issueIds: [] }; - let issues: TIssue | TUnGroupedIssues | undefined = undefined; + let issueIds: TIssue | TUnGroupedIssues | undefined = undefined; - issues = this.unGroupedIssues(orderBy ?? "-created_at", _issues); + issueIds = this.unGroupedIssues(orderBy ?? "-created_at", _issues); - return issues; + return { dataViewId: viewId, issueIds }; } fetchIssues = async (workspaceSlug: string, viewId: string, loadType: TLoader = "init-loader") => { try { this.loader = loadType; - const params = this.rootIssueStore?.workspaceIssuesFilter?.appliedFilters; + const params = this.rootIssueStore?.workspaceIssuesFilter?.getAppliedFilters(viewId); const response = await this.workspaceService.getViewIssues(workspaceSlug, params); runInAction(() => {