Compare commits

...

2 Commits

Author SHA1 Message Date
rahulramesha
6a4f8ab833 minor changes to virtualization 2024-08-13 16:26:31 +05:30
rahulramesha
7b459da829 pagination removal and local indexDB for issues 2024-08-11 18:54:55 +05:30
54 changed files with 335224 additions and 1708 deletions

View File

@@ -21,6 +21,7 @@ from plane.app.views import (
LabelViewSet,
BulkIssueOperationsEndpoint,
BulkArchiveIssuesEndpoint,
DeletedIssuesListViewSet,
)
urlpatterns = [
@@ -310,4 +311,9 @@ urlpatterns = [
BulkIssueOperationsEndpoint.as_view(),
name="bulk-operations-issues",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/deleted-issues/",
DeletedIssuesListViewSet.as_view(),
name="deleted-issues",
),
]

View File

@@ -112,6 +112,7 @@ from .issue.base import (
IssueViewSet,
IssueUserDisplayPropertyEndpoint,
BulkDeleteIssuesEndpoint,
DeletedIssuesListViewSet,
)
from .issue.activity import (

View File

@@ -234,10 +234,17 @@ class IssueViewSet(BaseViewSet):
@method_decorator(gzip_page)
def list(self, request, slug, project_id):
extra_filters = {}
if request.GET.get("updated_at__gt", None) is not None:
extra_filters = {
"updated_at__gt": request.GET.get("updated_at__gt")
}
print (extra_filters)
filters = issue_filters(request.query_params, "GET")
order_by_param = request.GET.get("order_by", "-created_at")
issue_queryset = self.get_queryset().filter(**filters)
issue_queryset = self.get_queryset().filter(**filters, **extra_filters)
# Custom ordering for priority and state
# Issue queryset
@@ -652,3 +659,18 @@ class BulkDeleteIssuesEndpoint(BaseAPIView):
{"message": f"{total_issues} issues were deleted"},
status=status.HTTP_200_OK,
)
class DeletedIssuesListViewSet(BaseAPIView):
permission_classes = [
ProjectEntityPermission,
]
def get(self, request, slug, project_id):
deleted_issues = Issue.all_objects.filter(
workspace__slug=slug,
project_id=project_id,
deleted_at__isnull=False,
).values_list("id", flat=True)
return Response(deleted_issues, status=status.HTTP_200_OK)

View File

@@ -29,7 +29,6 @@ from plane.authentication.adapter.error import (
AuthenticationException,
AUTHENTICATION_ERROR_CODES,
)
from plane.authentication.rate_limit import AuthenticationThrottle
class MagicGenerateEndpoint(APIView):
@@ -38,10 +37,6 @@ class MagicGenerateEndpoint(APIView):
AllowAny,
]
throttle_classes = [
AuthenticationThrottle,
]
def post(self, request):
# Check if instance is configured
instance = Instance.objects.first()

View File

@@ -27,11 +27,14 @@ class ProjectStatesEndpoint(BaseAPIView):
status=status.HTTP_404_NOT_FOUND,
)
states = State.objects.filter(
~Q(name="Triage"),
workspace__slug=deploy_board.workspace.slug,
project_id=deploy_board.project_id,
).values("name", "group", "color", "id", "sequence")
states = (
State.objects.filter(
~Q(name="Triage"),
workspace__slug=deploy_board.workspace.slug,
project_id=deploy_board.project_id,
)
.values("name", "group", "color", "id")
)
return Response(
states,

View File

@@ -82,7 +82,7 @@ class CursorResult(Sequence):
return f"<{type(self).__name__}: results={len(self.results)}>"
MAX_LIMIT = 100
MAX_LIMIT = 1000
class BadPaginationError(Exception):
@@ -118,7 +118,7 @@ class OffsetPaginator:
self.max_offset = max_offset
self.on_results = on_results
def get_result(self, limit=100, cursor=None):
def get_result(self, limit=1000, cursor=None):
# offset is page #
# value is page limit
if cursor is None:
@@ -727,7 +727,7 @@ class BasePaginator:
cursor_name = "cursor"
# get the per page parameter from request
def get_per_page(self, request, default_per_page=100, max_per_page=100):
def get_per_page(self, request, default_per_page=1000, max_per_page=1000):
try:
per_page = int(request.GET.get("per_page", default_per_page))
except ValueError:
@@ -747,8 +747,8 @@ class BasePaginator:
on_results=None,
paginator=None,
paginator_cls=OffsetPaginator,
default_per_page=100,
max_per_page=100,
default_per_page=1000,
max_per_page=1000,
cursor_cls=Cursor,
extra_stats=None,
controller=None,

View File

@@ -10,17 +10,19 @@ export * from "./issue_relation";
export * from "./issue_sub_issues";
export * from "./activity/base";
export type TLoader = "init-loader" | "mutation" | "pagination" | undefined;
export type TLoader = "init-loader" | "mutation" | undefined;
export type TUngroupedIssues = string[];
export type TGroupedIssues = {
[group_id: string]: string[];
[group_id: string]: TUngroupedIssues;
};
export type TSubGroupedIssues = {
[sub_grouped_id: string]: TGroupedIssues;
};
export type TIssues = TGroupedIssues | TSubGroupedIssues;
export type TIssues = TGroupedIssues | TSubGroupedIssues | TUngroupedIssues;
export type TPaginationData = {
nextCursor: string;

View File

@@ -26,13 +26,13 @@ export const ProjectArchivesHeader: FC<TProps> = observer((props: TProps) => {
const { workspaceSlug, projectId } = useParams();
// store hooks
const {
issues: { getGroupIssueCount },
issues: { issueIds },
} = useIssues(EIssuesStoreType.ARCHIVED);
const { currentProjectDetails, loader } = useProject();
// hooks
const { isMobile } = usePlatformOS();
const issueCount = getGroupIssueCount(undefined, undefined, false);
const issueCount = issueIds?.length;
const activeTabBreadcrumbDetail =
PROJECT_ARCHIVES_BREADCRUMB_LIST[activeTab as keyof typeof PROJECT_ARCHIVES_BREADCRUMB_LIST];

View File

@@ -76,7 +76,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
// store hooks
const {
issuesFilter: { issueFilters, updateFilters },
issues: { getGroupIssueCount },
issues: { issueIds },
} = useIssues(EIssuesStoreType.CYCLE);
const { currentProjectCycleIds, getCycleById } = useCycle();
const { toggleCreateIssueModal } = useCommandPalette();
@@ -152,7 +152,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
const canUserCreateIssue =
currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole);
const issuesCount = getGroupIssueCount(undefined, undefined, false);
const issuesCount = issueIds?.length;
return (
<>

View File

@@ -50,7 +50,7 @@ export const ProjectIssuesHeader = observer(() => {
} = useMember();
const {
issuesFilter: { issueFilters, updateFilters },
issues: { getGroupIssueCount },
issues: { issueIds },
} = useIssues(EIssuesStoreType.PROJECT);
const { toggleCreateIssueModal } = useCommandPalette();
const { setTrackElement } = useEventTracker();
@@ -113,7 +113,7 @@ export const ProjectIssuesHeader = observer(() => {
const canUserCreateIssue =
currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole);
const issuesCount = getGroupIssueCount(undefined, undefined, false);
const issuesCount = issueIds?.length;
return (
<>

View File

@@ -77,7 +77,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
// store hooks
const {
issuesFilter: { issueFilters },
issues: { getGroupIssueCount },
issues: { issueIds },
} = useIssues(EIssuesStoreType.MODULE);
const { updateFilters } = useIssuesActions(EIssuesStoreType.MODULE);
const { projectModuleIds, getModuleById } = useModule();
@@ -152,7 +152,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
const canUserCreateIssue =
currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole);
const issuesCount = getGroupIssueCount(undefined, undefined, false);
const issuesCount = issueIds?.length;
return (
<>

View File

@@ -10,6 +10,7 @@ type Props = {
as?: keyof JSX.IntrinsicElements;
classNames?: string;
placeholderChildren?: ReactNode;
shouldRenderByDefault?: boolean;
};
const RenderIfVisible: React.FC<Props> = (props) => {
@@ -22,8 +23,9 @@ const RenderIfVisible: React.FC<Props> = (props) => {
children,
classNames = "",
placeholderChildren = null, //placeholder children
shouldRenderByDefault = false,
} = props;
const [shouldVisible, setShouldVisible] = useState<boolean>();
const [shouldVisible, setShouldVisible] = useState<boolean>(shouldRenderByDefault);
const placeholderHeight = useRef<string>(defaultHeight);
const intersectionRef = useRef<HTMLElement | null>(null);

View File

@@ -33,8 +33,6 @@ import { useGanttChart } from "../hooks/use-gantt-chart";
type Props = {
blockIds: string[];
getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock;
canLoadMoreBlocks?: boolean;
loadMoreBlocks?: () => void;
blockToRender: (data: any) => React.ReactNode;
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
bottomSpacing: boolean;
@@ -56,7 +54,6 @@ export const GanttChartMainContent: React.FC<Props> = observer((props) => {
const {
blockIds,
getBlockById,
loadMoreBlocks,
blockToRender,
blockUpdateHandler,
bottomSpacing,
@@ -70,7 +67,6 @@ export const GanttChartMainContent: React.FC<Props> = observer((props) => {
showAllBlocks,
sidebarToRender,
title,
canLoadMoreBlocks,
updateCurrentViewRenderPayload,
quickAdd,
} = props;
@@ -145,38 +141,36 @@ export const GanttChartMainContent: React.FC<Props> = observer((props) => {
onScroll={onScroll}
>
<GanttChartSidebar
blockIds={blockIds}
getBlockById={getBlockById}
loadMoreBlocks={loadMoreBlocks}
canLoadMoreBlocks={canLoadMoreBlocks}
ganttContainerRef={ganttContainerRef}
blockUpdateHandler={blockUpdateHandler}
enableReorder={enableReorder}
enableSelection={enableSelection}
sidebarToRender={sidebarToRender}
title={title}
quickAdd={quickAdd}
selectionHelpers={helpers}
/>
<div className="relative min-h-full h-max flex-shrink-0 flex-grow">
<ActiveChartView />
{currentViewData && (
<GanttChartBlocksList
itemsContainerWidth={itemsContainerWidth}
blockIds={blockIds}
getBlockById={getBlockById}
blockToRender={blockToRender}
blockUpdateHandler={blockUpdateHandler}
enableBlockLeftResize={enableBlockLeftResize}
enableBlockRightResize={enableBlockRightResize}
enableBlockMove={enableBlockMove}
enableAddBlock={enableAddBlock}
ganttContainerRef={ganttContainerRef}
showAllBlocks={showAllBlocks}
selectionHelpers={helpers}
/>
)}
</div>
blockIds={blockIds}
getBlockById={getBlockById}
ganttContainerRef={ganttContainerRef}
blockUpdateHandler={blockUpdateHandler}
enableReorder={enableReorder}
enableSelection={enableSelection}
sidebarToRender={sidebarToRender}
title={title}
quickAdd={quickAdd}
selectionHelpers={helpers}
/>
<div className="relative min-h-full h-max flex-shrink-0 flex-grow">
<ActiveChartView />
{currentViewData && (
<GanttChartBlocksList
itemsContainerWidth={itemsContainerWidth}
blockIds={blockIds}
getBlockById={getBlockById}
blockToRender={blockToRender}
blockUpdateHandler={blockUpdateHandler}
enableBlockLeftResize={enableBlockLeftResize}
enableBlockRightResize={enableBlockRightResize}
enableBlockMove={enableBlockMove}
enableAddBlock={enableAddBlock}
ganttContainerRef={ganttContainerRef}
showAllBlocks={showAllBlocks}
selectionHelpers={helpers}
/>
)}
</div>
</div>
<IssueBulkOperationsRoot selectionHelpers={helpers} />
</>

View File

@@ -32,8 +32,6 @@ type ChartViewRootProps = {
bottomSpacing: boolean;
showAllBlocks: boolean;
getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock;
loadMoreBlocks?: () => void;
canLoadMoreBlocks?: boolean;
quickAdd?: React.JSX.Element | undefined;
};
@@ -43,12 +41,10 @@ export const ChartViewRoot: FC<ChartViewRootProps> = observer((props) => {
title,
blockIds,
getBlockById,
loadMoreBlocks,
loaderTitle,
blockUpdateHandler,
sidebarToRender,
blockToRender,
canLoadMoreBlocks,
enableBlockLeftResize,
enableBlockRightResize,
enableBlockMove,
@@ -165,8 +161,6 @@ export const ChartViewRoot: FC<ChartViewRootProps> = observer((props) => {
<GanttChartMainContent
blockIds={blockIds}
getBlockById={getBlockById}
loadMoreBlocks={loadMoreBlocks}
canLoadMoreBlocks={canLoadMoreBlocks}
blockToRender={blockToRender}
blockUpdateHandler={blockUpdateHandler}
bottomSpacing={bottomSpacing}

View File

@@ -14,8 +14,6 @@ type GanttChartRootProps = {
sidebarToRender: (props: any) => React.ReactNode;
quickAdd?: React.JSX.Element | undefined;
getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock;
canLoadMoreBlocks?: boolean;
loadMoreBlocks?: () => void;
enableBlockLeftResize?: boolean;
enableBlockRightResize?: boolean;
enableBlockMove?: boolean;
@@ -36,8 +34,6 @@ export const GanttChartRoot: FC<GanttChartRootProps> = (props) => {
sidebarToRender,
blockToRender,
getBlockById,
loadMoreBlocks,
canLoadMoreBlocks,
enableBlockLeftResize = false,
enableBlockRightResize = false,
enableBlockMove = false,
@@ -56,8 +52,6 @@ export const GanttChartRoot: FC<GanttChartRootProps> = (props) => {
title={title}
blockIds={blockIds}
getBlockById={getBlockById}
loadMoreBlocks={loadMoreBlocks}
canLoadMoreBlocks={canLoadMoreBlocks}
loaderTitle={loaderTitle}
blockUpdateHandler={blockUpdateHandler}
sidebarToRender={sidebarToRender}

View File

@@ -18,8 +18,6 @@ import { IssuesSidebarBlock } from "./block";
type Props = {
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
getBlockById: (id: string) => IGanttBlock;
canLoadMoreBlocks?: boolean;
loadMoreBlocks?: () => void;
ganttContainerRef: RefObject<HTMLDivElement>;
blockIds: string[];
enableReorder: boolean;
@@ -35,28 +33,10 @@ export const IssueGanttSidebar: React.FC<Props> = observer((props) => {
getBlockById,
enableReorder,
enableSelection,
loadMoreBlocks,
canLoadMoreBlocks,
ganttContainerRef,
showAllBlocks = false,
selectionHelpers,
} = props;
const {
issues: { getIssueLoader },
} = useIssuesStore();
const [intersectionElement, setIntersectionElement] = useState<HTMLDivElement | null>(null);
const isPaginating = !!getIssueLoader();
useIntersectionObserver(
ganttContainerRef,
isPaginating ? null : intersectionElement,
loadMoreBlocks,
"100% 0% 100% 0%"
);
const handleOnDrop = (
draggingBlockId: string | undefined,
droppedBlockId: string | undefined,
@@ -95,11 +75,6 @@ export const IssueGanttSidebar: React.FC<Props> = observer((props) => {
</GanttDnDHOC>
);
})}
{canLoadMoreBlocks && (
<div ref={setIntersectionElement} className="p-2">
<div className="flex h-10 md:h-8 w-full items-center justify-between gap-1.5 rounded md:px-1 px-4 py-1.5 bg-custom-background-80 animate-pulse" />
</div>
)}
</>
) : (
<Loader className="space-y-3 pr-2">

View File

@@ -13,8 +13,6 @@ import { GANTT_SELECT_GROUP, HEADER_HEIGHT, SIDEBAR_WIDTH } from "../constants";
type Props = {
blockIds: string[];
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
canLoadMoreBlocks?: boolean;
loadMoreBlocks?: () => void;
ganttContainerRef: RefObject<HTMLDivElement>;
enableReorder: boolean;
enableSelection: boolean;
@@ -33,8 +31,6 @@ export const GanttChartSidebar: React.FC<Props> = observer((props) => {
enableSelection,
sidebarToRender,
getBlockById,
loadMoreBlocks,
canLoadMoreBlocks,
ganttContainerRef,
title,
quickAdd,
@@ -91,10 +87,8 @@ export const GanttChartSidebar: React.FC<Props> = observer((props) => {
getBlockById,
enableReorder,
enableSelection,
canLoadMoreBlocks,
ganttContainerRef,
loadMoreBlocks,
selectionHelpers
selectionHelpers,
})}
</div>
{quickAdd ? quickAdd : null}

View File

@@ -44,17 +44,8 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
membership: { currentProjectRole },
} = useUser();
const { issues, issuesFilter, issueMap } = useIssues(storeType);
const {
fetchIssues,
fetchNextIssues,
quickAddIssue,
updateIssue,
removeIssue,
removeIssueFromView,
archiveIssue,
restoreIssue,
updateFilters,
} = useIssuesActions(storeType);
const { quickAddIssue, updateIssue, removeIssue, removeIssueFromView, archiveIssue, restoreIssue, updateFilters } =
useIssuesActions(storeType);
const issueCalendarView = useCalendarView();
@@ -64,25 +55,6 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
const groupedIssueIds = (issues.groupedIssueIds ?? {}) as TGroupedIssues;
const layout = displayFilters?.calendar?.layout ?? "month";
const { startDate, endDate } = issueCalendarView.getStartAndEndDate(layout) ?? {};
useEffect(() => {
startDate &&
endDate &&
layout &&
fetchIssues(
"init-loader",
{
canGroup: true,
perPageCount: layout === "month" ? 4 : 30,
before: endDate,
after: startDate,
groupedBy: EIssueGroupByToServerOptions["target_date"],
},
viewId
);
}, [fetchIssues, storeType, startDate, endDate, layout, viewId]);
const handleDragAndDrop = async (
issueId: string | undefined,
@@ -107,23 +79,6 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
});
};
const loadMoreIssues = useCallback(
(dateString: string) => {
fetchNextIssues(dateString);
},
[fetchNextIssues]
);
const getPaginationData = useCallback(
(groupId: string | undefined) => issues?.getPaginationData(groupId, undefined),
[issues?.getPaginationData]
);
const getGroupIssueCount = useCallback(
(groupId: string | undefined) => issues?.getGroupIssueCount(groupId, undefined, false),
[issues?.getGroupIssueCount]
);
return (
<>
<div className="h-full w-full overflow-hidden bg-custom-background-100 pt-4">
@@ -148,9 +103,6 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
placements={placement}
/>
)}
loadMoreIssues={loadMoreIssues}
getPaginationData={getPaginationData}
getGroupIssueCount={getGroupIssueCount}
addIssuesToView={addIssuesToView}
quickAddCallback={quickAddIssue}
readOnly={!isEditingAllowed || isCompletedCycle}

View File

@@ -46,9 +46,6 @@ type Props = {
layout: "month" | "week" | undefined;
showWeekends: boolean;
issueCalendarView: ICalendarStore;
loadMoreIssues: (dateString: string) => void;
getPaginationData: (groupId: string | undefined) => TPaginationData | undefined;
getGroupIssueCount: (groupId: string | undefined) => number | undefined;
quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise<TIssue | undefined>;
quickActions: TRenderQuickActions;
handleDragAndDrop: (
@@ -73,13 +70,10 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
layout,
showWeekends,
issueCalendarView,
loadMoreIssues,
handleDragAndDrop,
quickActions,
quickAddCallback,
addIssuesToView,
getPaginationData,
getGroupIssueCount,
updateFilters,
readOnly = false,
} = props;
@@ -159,9 +153,6 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
week={week}
issues={issues}
groupedIssueIds={groupedIssueIds}
loadMoreIssues={loadMoreIssues}
getPaginationData={getPaginationData}
getGroupIssueCount={getGroupIssueCount}
enableQuickIssueCreate
disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
quickActions={quickActions}
@@ -181,9 +172,6 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
week={issueCalendarView.allDaysOfActiveWeek}
issues={issues}
groupedIssueIds={groupedIssueIds}
loadMoreIssues={loadMoreIssues}
getPaginationData={getPaginationData}
getGroupIssueCount={getGroupIssueCount}
enableQuickIssueCreate
disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
quickActions={quickActions}
@@ -204,9 +192,6 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
<CalendarIssueBlocks
date={selectedDate}
issueIdList={issueIdList}
loadMoreIssues={loadMoreIssues}
getPaginationData={getPaginationData}
getGroupIssueCount={getGroupIssueCount}
quickActions={quickActions}
enableQuickIssueCreate
disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
@@ -231,9 +216,6 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
date={selectedDate}
issueIdList={issueIdList}
quickActions={quickActions}
loadMoreIssues={loadMoreIssues}
getPaginationData={getPaginationData}
getGroupIssueCount={getGroupIssueCount}
enableQuickIssueCreate
disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
quickAddCallback={quickAddCallback}

View File

@@ -29,9 +29,6 @@ type Props = {
date: ICalendarDate;
issues: TIssueMap | undefined;
groupedIssueIds: TGroupedIssues;
loadMoreIssues: (dateString: string) => void;
getPaginationData: (groupId: string | undefined) => TPaginationData | undefined;
getGroupIssueCount: (groupId: string | undefined) => number | undefined;
enableQuickIssueCreate?: boolean;
disableIssueCreation?: boolean;
quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise<TIssue | undefined>;
@@ -53,9 +50,6 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
date,
issues,
groupedIssueIds,
loadMoreIssues,
getPaginationData,
getGroupIssueCount,
quickActions,
enableQuickIssueCreate,
disableIssueCreation,
@@ -167,9 +161,6 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
date={date.date}
issueIdList={issueIds}
quickActions={quickActions}
loadMoreIssues={loadMoreIssues}
getPaginationData={getPaginationData}
getGroupIssueCount={getGroupIssueCount}
isDragDisabled={readOnly}
addIssuesToView={addIssuesToView}
disableIssueCreation={disableIssueCreation}

View File

@@ -6,13 +6,11 @@ import { CalendarQuickAddIssueForm, CalendarIssueBlockRoot } from "@/components/
import { renderFormattedPayloadDate } from "@/helpers/date-time.helper";
import { useIssuesStore } from "@/hooks/use-issue-layout-store";
import { TRenderQuickActions } from "../list/list-view-types";
import { useState } from "react";
// types
type Props = {
date: Date;
loadMoreIssues: (dateString: string) => void;
getPaginationData: (groupId: string | undefined) => TPaginationData | undefined;
getGroupIssueCount: (groupId: string | undefined) => number | undefined;
issueIdList: string[];
quickActions: TRenderQuickActions;
isDragDisabled?: boolean;
@@ -29,7 +27,6 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
date,
issueIdList,
quickActions,
loadMoreIssues,
isDragDisabled = false,
enableQuickIssueCreate,
disableIssueCreation,
@@ -40,24 +37,17 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
} = props;
const formattedDatePayload = renderFormattedPayloadDate(date);
const {
issues: { getGroupIssueCount, getPaginationData, getIssueLoader },
} = useIssuesStore();
const [isExpanded, setIsExpanded] = useState(isMobileView);
if (!formattedDatePayload) return null;
const dayIssueCount = getGroupIssueCount(formattedDatePayload, undefined, false);
const nextPageResults = getPaginationData(formattedDatePayload, undefined)?.nextPageResults;
const isPaginating = !!getIssueLoader(formattedDatePayload);
const dayIssueCount = issueIdList?.length ?? 0;
const shouldLoadMore =
nextPageResults === undefined && dayIssueCount !== undefined
? issueIdList?.length < dayIssueCount
: !!nextPageResults;
const currentIssueIds = isExpanded ? issueIdList : issueIdList?.slice(0, 4);
return (
<>
{issueIdList?.map((issueId) => (
{currentIssueIds?.map((issueId) => (
<div key={issueId} className="relative cursor-pointer p-1 px-2">
<CalendarIssueBlockRoot
issueId={issueId}
@@ -67,12 +57,6 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
</div>
))}
{isPaginating && (
<div className="p-1 px-2">
<div className="flex h-10 md:h-8 w-full items-center justify-between gap-1.5 rounded md:px-1 px-4 py-1.5 bg-custom-background-80 animate-pulse" />
</div>
)}
{enableQuickIssueCreate && !disableIssueCreation && !readOnly && (
<div className="border-b border-custom-border-200 px-1 py-1 md:border-none md:px-2">
<CalendarQuickAddIssueForm
@@ -87,12 +71,12 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
</div>
)}
{shouldLoadMore && !isPaginating && (
{!isExpanded && dayIssueCount > 4 && (
<div className="flex items-center px-2.5 py-1">
<button
type="button"
className="w-min whitespace-nowrap rounded text-xs px-1.5 py-1 font-medium hover:bg-custom-background-80 text-custom-primary-100 hover:text-custom-primary-200"
onClick={() => loadMoreIssues(formattedDatePayload)}
onClick={() => setIsExpanded(true)}
>
Load More
</button>

View File

@@ -18,9 +18,6 @@ type Props = {
groupedIssueIds: TGroupedIssues;
week: ICalendarWeek | undefined;
quickActions: TRenderQuickActions;
loadMoreIssues: (dateString: string) => void;
getPaginationData: (groupId: string | undefined) => TPaginationData | undefined;
getGroupIssueCount: (groupId: string | undefined) => number | undefined;
enableQuickIssueCreate?: boolean;
disableIssueCreation?: boolean;
quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise<TIssue | undefined>;
@@ -42,9 +39,6 @@ export const CalendarWeekDays: React.FC<Props> = observer((props) => {
groupedIssueIds,
handleDragAndDrop,
week,
loadMoreIssues,
getPaginationData,
getGroupIssueCount,
quickActions,
enableQuickIssueCreate,
disableIssueCreation,
@@ -78,9 +72,6 @@ export const CalendarWeekDays: React.FC<Props> = observer((props) => {
date={date}
issues={issues}
groupedIssueIds={groupedIssueIds}
loadMoreIssues={loadMoreIssues}
getPaginationData={getPaginationData}
getGroupIssueCount={getGroupIssueCount}
quickActions={quickActions}
enableQuickIssueCreate={enableQuickIssueCreate}
disableIssueCreation={disableIssueCreation}

View File

@@ -3,7 +3,7 @@ import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// plane constants
import { ALL_ISSUES } from "@plane/constants";
import { TIssue } from "@plane/types";
import { TIssue, TUngroupedIssues } from "@plane/types";
// hooks
import { ChartDataType, GanttChartRoot, IBlockUpdateData, IssueGanttSidebar } from "@/components/gantt-chart";
import { getMonthChartItemPositionWidthInMonth } from "@/components/gantt-chart/views";
@@ -48,12 +48,7 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
// plane web hooks
const isBulkOperationsEnabled = useBulkOperationStatus();
useEffect(() => {
fetchIssues("init-loader", { canGroup: false, perPageCount: 100 }, viewId);
}, [fetchIssues, storeType, viewId]);
const issuesIds = (issues.groupedIssueIds?.[ALL_ISSUES] as string[]) ?? [];
const nextPageResults = issues.getPaginationData(undefined, undefined)?.nextPageResults;
const issuesIds = (issues.groupedIssueIds as TUngroupedIssues) ?? [];
const { enableIssueCreation } = issues?.viewFlags || {};
@@ -111,8 +106,6 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
enableAddBlock={isAllowed}
enableSelection={isBulkOperationsEnabled && isAllowed}
quickAdd={quickAdd}
loadMoreBlocks={loadMoreIssues}
canLoadMoreBlocks={nextPageResults}
showAllBlocks
/>
</div>

View File

@@ -40,13 +40,13 @@ export const IssueLayoutHOC = observer((props: Props) => {
const storeType = useIssueStoreType();
const { issues } = useIssues(storeType);
const issueCount = issues.getGroupIssueCount(undefined, undefined, false);
const issueCount = issues.issueIds?.length;
if (issues?.getIssueLoader() === "init-loader" || issueCount === undefined) {
return <ActiveLoader layout={layout} />;
}
if (issues.getGroupIssueCount(undefined, undefined, false) === 0 && layout !== EIssueLayoutTypes.CALENDAR) {
if (issueCount === 0 && layout !== EIssueLayoutTypes.CALENDAR) {
return <IssueLayoutEmptyState storeType={storeType} />;
}

View File

@@ -26,6 +26,8 @@ import { IQuickActionProps, TRenderQuickActions } from "../list/list-view-types"
import { getSourceFromDropPayload } from "../utils";
import { KanBan } from "./default";
import { KanBanSwimLanes } from "./swimlanes";
import { TGroupedIssues, TSubGroupedIssues } from "@plane/types";
import useSWR from "swr";
export type KanbanStoreType =
| EIssuesStoreType.PROJECT
@@ -57,17 +59,8 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
const {
issue: { getIssueById },
} = useIssueDetail();
const {
fetchIssues,
fetchNextIssues,
quickAddIssue,
updateIssue,
removeIssue,
removeIssueFromView,
archiveIssue,
restoreIssue,
updateFilters,
} = useIssuesActions(storeType);
const { quickAddIssue, updateIssue, removeIssue, removeIssueFromView, archiveIssue, restoreIssue, updateFilters } =
useIssuesActions(storeType);
const deleteAreaRef = useRef<HTMLDivElement | null>(null);
const [isDragOverDelete, setIsDragOverDelete] = useState(false);
@@ -82,26 +75,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
const orderBy = displayFilters?.order_by;
useEffect(() => {
fetchIssues("init-loader", { canGroup: true, perPageCount: sub_group_by ? 10 : 30 }, viewId);
}, [fetchIssues, storeType, group_by, sub_group_by, viewId]);
const fetchMoreIssues = useCallback(
(groupId?: string, subgroupId?: string) => {
if (issues?.getIssueLoader(groupId, subgroupId) !== "pagination") {
fetchNextIssues(groupId, subgroupId);
}
},
[fetchNextIssues]
);
const debouncedFetchMoreIssues = debounce(
(groupId?: string, subgroupId?: string) => fetchMoreIssues(groupId, subgroupId),
300,
{ leading: true, trailing: false }
);
const groupedIssueIds = issues?.groupedIssueIds;
const groupedIssueIds = issues?.groupedIssueIds as TSubGroupedIssues & TGroupedIssues;
const userDisplayFilters = displayFilters || null;
@@ -257,7 +231,6 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
<KanBanView
issuesMap={issueMap}
groupedIssueIds={groupedIssueIds ?? {}}
getGroupIssueCount={issues.getGroupIssueCount}
displayProperties={displayProperties}
sub_group_by={sub_group_by}
group_by={group_by}
@@ -274,7 +247,6 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
addIssuesToView={addIssuesToView}
scrollableContainerRef={scrollableContainerRef}
handleOnDrop={handleOnDrop}
loadMoreIssues={debouncedFetchMoreIssues}
/>
</div>
</div>

View File

@@ -32,6 +32,7 @@ interface IssueBlockProps {
displayProperties: IIssueDisplayProperties | undefined;
draggableId: string;
canDropOverIssue: boolean;
shouldRenderByDefault?: boolean;
updateIssue: ((projectId: string | null, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
quickActions: TRenderQuickActions;
canEditProperties: (projectId: string | undefined) => boolean;
@@ -114,6 +115,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = observer((props) => {
updateIssue,
quickActions,
canEditProperties,
shouldRenderByDefault,
scrollableContainerRef,
} = props;
@@ -226,6 +228,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = observer((props) => {
defaultHeight="100px"
horizontalOffset={100}
verticalOffset={200}
shouldRenderByDefault={shouldRenderByDefault}
>
<KanbanIssueDetailsBlock
cardRef={cardRef}

View File

@@ -37,7 +37,7 @@ export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = observer((p
<>
{issueIds && issueIds.length > 0 ? (
<>
{issueIds.map((issueId) => {
{issueIds.slice(0, 30).map((issueId, index) => {
if (!issueId) return null;
let draggableId = issueId;
@@ -50,6 +50,7 @@ export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = observer((p
issueId={issueId}
groupId={groupId}
subGroupId={sub_group_id}
shouldRenderByDefault={index <= 10}
issuesMap={issuesMap}
displayProperties={displayProperties}
updateIssue={updateIssue}

View File

@@ -7,7 +7,6 @@ import {
TIssue,
IIssueDisplayProperties,
IIssueMap,
TSubGroupedIssues,
TIssueKanbanFilters,
TIssueGroupByOptions,
TIssueOrderByOptions,
@@ -26,12 +25,7 @@ import { KanbanGroup } from "./kanban-group";
export interface IKanBan {
issuesMap: IIssueMap;
groupedIssueIds: TGroupedIssues | TSubGroupedIssues;
getGroupIssueCount: (
groupId: string | undefined,
subGroupId: string | undefined,
isSubGroupCumulative: boolean
) => number | undefined;
groupedIssueIds: TGroupedIssues;
displayProperties: IIssueDisplayProperties | undefined;
sub_group_by: TIssueGroupByOptions | undefined;
group_by: TIssueGroupByOptions | undefined;
@@ -43,7 +37,6 @@ export interface IKanBan {
quickActions: TRenderQuickActions;
kanbanFilters: TIssueKanbanFilters;
handleKanbanFilters: any;
loadMoreIssues: (groupId?: string, subGroupId?: string) => void;
enableQuickIssueCreate?: boolean;
quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise<TIssue | undefined>;
disableIssueCreation?: boolean;
@@ -58,7 +51,6 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
const {
issuesMap,
groupedIssueIds,
getGroupIssueCount,
displayProperties,
sub_group_by,
group_by,
@@ -69,7 +61,6 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
handleKanbanFilters,
enableQuickIssueCreate,
quickAddCallback,
loadMoreIssues,
disableIssueCreation,
addIssuesToView,
canEditProperties,
@@ -108,13 +99,14 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
if (!list) return null;
const visibilityGroupBy = (_list: IGroupByColumn): { showGroup: boolean; showIssues: boolean } => {
const count = Array.isArray(groupedIssueIds?.[_list.id]) ? groupedIssueIds?.[_list.id].length : 0;
if (sub_group_by) {
const groupVisibility = {
showGroup: true,
showIssues: true,
};
if (!showEmptyGroup) {
groupVisibility.showGroup = (getGroupIssueCount(_list.id, undefined, false) ?? 0) > 0;
groupVisibility.showGroup = count > 0;
}
return groupVisibility;
} else {
@@ -123,7 +115,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
showIssues: true,
};
if (!showEmptyGroup) {
if ((getGroupIssueCount(_list.id, undefined, false) ?? 0) > 0) groupVisibility.showGroup = true;
if (count > 0) groupVisibility.showGroup = true;
else groupVisibility.showGroup = false;
}
if (kanbanFilters?.group_by.includes(_list.id)) groupVisibility.showIssues = false;
@@ -137,13 +129,13 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
<div className={`relative w-full flex gap-2 px-2 ${sub_group_by ? "h-full" : "h-full"}`}>
{list &&
list.length > 0 &&
list.map((subList: IGroupByColumn) => {
const groupByVisibilityToggle = visibilityGroupBy(subList);
list.map((group: IGroupByColumn) => {
const groupByVisibilityToggle = visibilityGroupBy(group);
if (groupByVisibilityToggle.showGroup === false) return <></>;
return (
<div
key={subList.id}
key={group.id}
className={`group relative flex flex-shrink-0 flex-col ${
groupByVisibilityToggle.showIssues ? `w-[350px]` : ``
} `}
@@ -153,11 +145,11 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
<HeaderGroupByCard
sub_group_by={sub_group_by}
group_by={group_by}
column_id={subList.id}
icon={subList.icon}
title={subList.name}
count={getGroupIssueCount(subList.id, undefined, false) ?? 0}
issuePayload={subList.payload}
column_id={group.id}
icon={group.icon}
title={group.name}
count={Array.isArray(groupedIssueIds?.[group.id]) ? groupedIssueIds?.[group.id].length : 0}
issuePayload={group.payload}
disableIssueCreation={disableIssueCreation || isGroupByCreatedBy}
addIssuesToView={addIssuesToView}
kanbanFilters={kanbanFilters}
@@ -168,17 +160,17 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
{groupByVisibilityToggle.showIssues && (
<KanbanGroup
groupId={subList.id}
groupId={group.id}
issuesMap={issuesMap}
groupedIssueIds={groupedIssueIds}
issueIds={groupedIssueIds?.[group.id] ?? []}
displayProperties={displayProperties}
sub_group_by={sub_group_by}
group_by={group_by}
orderBy={orderBy}
sub_group_id={sub_group_id}
isDragDisabled={isDragDisabled}
isDropDisabled={!!subList.isDropDisabled || !!isDropDisabled}
dropErrorMessage={subList.dropErrorMessage ?? dropErrorMessage}
isDropDisabled={!!group.isDropDisabled || !!isDropDisabled}
dropErrorMessage={group.dropErrorMessage ?? dropErrorMessage}
updateIssue={updateIssue}
quickActions={quickActions}
enableQuickIssueCreate={enableQuickIssueCreate}
@@ -186,7 +178,6 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
disableIssueCreation={disableIssueCreation}
canEditProperties={canEditProperties}
scrollableContainerRef={scrollableContainerRef}
loadMoreIssues={loadMoreIssues}
handleOnDrop={handleOnDrop}
/>
)}

View File

@@ -14,6 +14,7 @@ import {
TSubGroupedIssues,
TIssueGroupByOptions,
TIssueOrderByOptions,
TUngroupedIssues,
} from "@plane/types";
import { TOAST_TYPE, setToast } from "@plane/ui";
import { highlightIssueOnDrop } from "@/components/issues/issue-layouts/utils";
@@ -32,7 +33,7 @@ import { KanbanIssueBlocksList, KanBanQuickAddIssueForm } from ".";
interface IKanbanGroup {
groupId: string;
issuesMap: IIssueMap;
groupedIssueIds: TGroupedIssues | TSubGroupedIssues;
issueIds: TUngroupedIssues;
displayProperties: IIssueDisplayProperties | undefined;
sub_group_by: TIssueGroupByOptions | undefined;
group_by: TIssueGroupByOptions | undefined;
@@ -44,7 +45,6 @@ interface IKanbanGroup {
quickActions: TRenderQuickActions;
enableQuickIssueCreate?: boolean;
quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise<TIssue | undefined>;
loadMoreIssues: (groupId?: string, subGroupId?: string) => void;
disableIssueCreation?: boolean;
canEditProperties: (projectId: string | undefined) => boolean;
groupByVisibilityToggle?: boolean;
@@ -62,13 +62,12 @@ export const KanbanGroup = observer((props: IKanbanGroup) => {
sub_group_by,
issuesMap,
displayProperties,
groupedIssueIds,
issueIds,
isDropDisabled,
dropErrorMessage,
updateIssue,
quickActions,
canEditProperties,
loadMoreIssues,
enableQuickIssueCreate,
disableIssueCreation,
quickAddCallback,
@@ -79,26 +78,11 @@ export const KanbanGroup = observer((props: IKanbanGroup) => {
const projectState = useProjectState();
const {
issues: { getGroupIssueCount, getPaginationData, getIssueLoader },
issues: { getIssueLoader },
} = useIssuesStore();
const [intersectionElement, setIntersectionElement] = useState<HTMLSpanElement | null>(null);
const columnRef = useRef<HTMLDivElement | null>(null);
const containerRef = sub_group_by && scrollableContainerRef ? scrollableContainerRef : columnRef;
const loadMoreIssuesInThisGroup = useCallback(() => {
loadMoreIssues(groupId, sub_group_id === "null" ? undefined : sub_group_id);
}, [loadMoreIssues, groupId, sub_group_id]);
const isPaginating = !!getIssueLoader(groupId, sub_group_id);
useIntersectionObserver(
containerRef,
isPaginating ? null : intersectionElement,
loadMoreIssuesInThisGroup,
`0% 100% 100% 100%`
);
const [isDraggingOverColumn, setIsDraggingOverColumn] = useState(false);
// Enable Kanban Columns as Drop Targets
@@ -214,27 +198,8 @@ export const KanbanGroup = observer((props: IKanbanGroup) => {
const isSubGroup = !!sub_group_id && sub_group_id !== "null";
const issueIds = isSubGroup
? (groupedIssueIds as TSubGroupedIssues)?.[groupId]?.[sub_group_id] ?? []
: (groupedIssueIds as TGroupedIssues)?.[groupId] ?? [];
const groupIssueCount = Array.isArray(issueIds) ? issueIds.lastIndexOf : 0;
const groupIssueCount = getGroupIssueCount(groupId, sub_group_id, false) ?? 0;
const nextPageResults = getPaginationData(groupId, sub_group_id)?.nextPageResults;
const loadMore = isPaginating ? (
<KanbanIssueBlockLoader />
) : (
<div
className="w-full sticky bottom-0 p-3 text-sm font-medium text-custom-primary-100 hover:text-custom-primary-200 hover:underline cursor-pointer"
onClick={loadMoreIssuesInThisGroup}
>
{" "}
Load More &darr;
</div>
);
const shouldLoadMore = nextPageResults === undefined ? issueIds?.length < groupIssueCount : !!nextPageResults;
const canOverlayBeVisible = orderBy !== "sort_order" || isDropDisabled;
const shouldOverlayBeVisible = isDraggingOverColumn && canOverlayBeVisible;
@@ -269,8 +234,6 @@ export const KanbanGroup = observer((props: IKanbanGroup) => {
canDropOverIssue={!canOverlayBeVisible}
/>
{shouldLoadMore && (isSubGroup ? <>{loadMore}</> : <KanbanIssueBlockLoader ref={setIntersectionElement} />)}
{enableQuickIssueCreate && !disableIssueCreation && (
<div className="w-full bg-custom-background-90 py-0.5 sticky bottom-0">
<KanBanQuickAddIssueForm

View File

@@ -11,6 +11,7 @@ import {
TIssueKanbanFilters,
TIssueGroupByOptions,
TIssueOrderByOptions,
TIssues,
} from "@plane/types";
// hooks
import { useCycle, useLabel, useMember, useModule, useProject, useProjectState } from "@/hooks/store";
@@ -25,11 +26,7 @@ import { HeaderSubGroupByCard } from "./headers/sub-group-by-card";
// constants
interface ISubGroupSwimlaneHeader {
getGroupIssueCount: (
groupId: string | undefined,
subGroupId: string | undefined,
isSubGroupCumulative: boolean
) => number | undefined;
getSubIssuesCount: (subGroupId: string) => number;
sub_group_by: TIssueGroupByOptions | undefined;
group_by: TIssueGroupByOptions | undefined;
list: IGroupByColumn[];
@@ -51,12 +48,12 @@ const visibilitySubGroupByGroupCount = (subGroupIssueCount: number, showEmptyGro
};
const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = observer(
({ getGroupIssueCount, sub_group_by, group_by, list, kanbanFilters, handleKanbanFilters, showEmptyGroup }) => (
({ getSubIssuesCount, sub_group_by, group_by, list, kanbanFilters, handleKanbanFilters, showEmptyGroup }) => (
<div className="relative flex h-max min-h-full w-full items-center gap-2">
{list &&
list.length > 0 &&
list.map((_list: IGroupByColumn) => {
const groupCount = getGroupIssueCount(_list?.id, undefined, false) ?? 0;
const groupCount = getSubIssuesCount(_list?.id) ?? 0;
const subGroupByVisibilityToggle = visibilitySubGroupByGroupCount(groupCount, showEmptyGroup);
@@ -84,12 +81,8 @@ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = observer(
interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader {
issuesMap: IIssueMap;
groupedIssueIds: TGroupedIssues | TSubGroupedIssues;
getGroupIssueCount: (
groupId: string | undefined,
subGroupId: string | undefined,
isSubGroupCumulative: boolean
) => number | undefined;
groupedIssueIds: TSubGroupedIssues;
getSubIssuesCount: (subGroupId: string) => number;
showEmptyGroup: boolean;
displayProperties: IIssueDisplayProperties | undefined;
updateIssue: ((projectId: string | null, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
@@ -104,14 +97,13 @@ interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader {
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise<TIssue | undefined>;
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
loadMoreIssues: (groupId?: string, subGroupId?: string) => void;
}
const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
const {
issuesMap,
groupedIssueIds,
getGroupIssueCount,
getSubIssuesCount,
sub_group_by,
group_by,
list,
@@ -120,7 +112,6 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
displayProperties,
kanbanFilters,
handleKanbanFilters,
loadMoreIssues,
showEmptyGroup,
enableQuickIssueCreate,
disableIssueCreation,
@@ -154,7 +145,7 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
{list &&
list.length > 0 &&
list.map((_list: IGroupByColumn) => {
const issueCount = getGroupIssueCount(undefined, _list.id, true) ?? 0;
const issueCount = getSubIssuesCount(_list.id);
const subGroupByVisibilityToggle = visibilitySubGroupBy(_list, issueCount);
if (subGroupByVisibilityToggle.showGroup === false) return <></>;
return (
@@ -176,8 +167,7 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
<div className="relative">
<KanBan
issuesMap={issuesMap}
groupedIssueIds={groupedIssueIds}
getGroupIssueCount={getGroupIssueCount}
groupedIssueIds={groupedIssueIds?.[_list.id]}
displayProperties={displayProperties}
sub_group_by={sub_group_by}
group_by={group_by}
@@ -193,7 +183,6 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
addIssuesToView={addIssuesToView}
quickAddCallback={quickAddCallback}
scrollableContainerRef={scrollableContainerRef}
loadMoreIssues={loadMoreIssues}
handleOnDrop={handleOnDrop}
orderBy={orderBy}
isDropDisabled={_list.isDropDisabled}
@@ -210,12 +199,7 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
export interface IKanBanSwimLanes {
issuesMap: IIssueMap;
groupedIssueIds: TGroupedIssues | TSubGroupedIssues;
getGroupIssueCount: (
groupId: string | undefined,
subGroupId: string | undefined,
isSubGroupCumulative: boolean
) => number | undefined;
groupedIssueIds: TSubGroupedIssues;
displayProperties: IIssueDisplayProperties | undefined;
sub_group_by: TIssueGroupByOptions | undefined;
group_by: TIssueGroupByOptions | undefined;
@@ -223,7 +207,6 @@ export interface IKanBanSwimLanes {
quickActions: TRenderQuickActions;
kanbanFilters: TIssueKanbanFilters;
handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void;
loadMoreIssues: (groupId?: string, subGroupId?: string) => void;
showEmptyGroup: boolean;
handleOnDrop: (source: GroupDropLocation, destination: GroupDropLocation) => Promise<void>;
disableIssueCreation?: boolean;
@@ -239,7 +222,6 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
const {
issuesMap,
groupedIssueIds,
getGroupIssueCount,
displayProperties,
sub_group_by,
group_by,
@@ -248,7 +230,6 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
quickActions,
kanbanFilters,
handleKanbanFilters,
loadMoreIssues,
showEmptyGroup,
handleOnDrop,
disableIssueCreation,
@@ -293,11 +274,22 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
if (!groupByList || !subGroupByList) return null;
const getSubIssuesCount = (subGroupId: string) => {
const subGroupIssues = groupedIssueIds?.[subGroupId] as TGroupedIssues;
let count = 0;
if (!subGroupIssues) return count;
for (const groupId of Object.keys(subGroupIssues)) {
count += Array.isArray(subGroupIssues?.[groupId]) ? subGroupIssues?.[groupId].length : 0;
}
return count;
};
return (
<div className="relative">
<div className="sticky top-0 z-[4] h-[50px] bg-custom-background-90 px-2">
<SubGroupSwimlaneHeader
getGroupIssueCount={getGroupIssueCount}
getSubIssuesCount={getSubIssuesCount}
group_by={group_by}
sub_group_by={sub_group_by}
kanbanFilters={kanbanFilters}
@@ -311,8 +303,8 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
<SubGroupSwimlane
issuesMap={issuesMap}
list={subGroupByList}
getSubIssuesCount={getSubIssuesCount}
groupedIssueIds={groupedIssueIds}
getGroupIssueCount={getGroupIssueCount}
displayProperties={displayProperties}
group_by={group_by}
sub_group_by={sub_group_by}
@@ -321,7 +313,6 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
quickActions={quickActions}
kanbanFilters={kanbanFilters}
handleKanbanFilters={handleKanbanFilters}
loadMoreIssues={loadMoreIssues}
showEmptyGroup={showEmptyGroup}
handleOnDrop={handleOnDrop}
disableIssueCreation={disableIssueCreation}

View File

@@ -38,16 +38,8 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
const storeType = useIssueStoreType() as ListStoreType;
//stores
const { issuesFilter, issues } = useIssues(storeType);
const {
fetchIssues,
fetchNextIssues,
quickAddIssue,
updateIssue,
removeIssue,
removeIssueFromView,
archiveIssue,
restoreIssue,
} = useIssuesActions(storeType);
const { fetchIssues, quickAddIssue, updateIssue, removeIssue, removeIssueFromView, archiveIssue, restoreIssue } =
useIssuesActions(storeType);
// mobx store
const {
membership: { currentProjectRole },
@@ -61,10 +53,6 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
const group_by = (displayFilters?.group_by || null) as GroupByColumnTypes | null;
const showEmptyGroup = displayFilters?.show_empty_groups ?? false;
useEffect(() => {
fetchIssues("init-loader", { canGroup: true, perPageCount: group_by ? 50 : 100 }, viewId);
}, [fetchIssues, storeType, group_by, viewId]);
const groupedIssueIds = issues?.groupedIssueIds as TGroupedIssues | undefined;
// auth
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
@@ -99,13 +87,6 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
[isEditingAllowed, isCompletedCycle, removeIssue, updateIssue, removeIssueFromView, archiveIssue, restoreIssue]
);
const loadMoreIssues = useCallback(
(groupId?: string) => {
fetchNextIssues(groupId);
},
[fetchNextIssues]
);
return (
<IssueLayoutHOC layout={EIssueLayoutTypes.LIST}>
<div className={`relative size-full bg-custom-background-90`}>
@@ -117,7 +98,6 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
updateIssue={updateIssue}
quickActions={renderQuickActions}
groupedIssueIds={groupedIssueIds ?? {}}
loadMoreIssues={loadMoreIssues}
showEmptyGroup={showEmptyGroup}
quickAddCallback={quickAddIssue}
enableIssueQuickAdd={!!enableQuickAdd}

View File

@@ -46,7 +46,6 @@ export interface IList {
handleOnDrop: (source: GroupDropLocation, destination: GroupDropLocation) => Promise<void>;
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
isCompletedCycle?: boolean;
loadMoreIssues: (groupId?: string) => void;
}
export const List: React.FC<IList> = observer((props) => {
@@ -66,7 +65,6 @@ export const List: React.FC<IList> = observer((props) => {
handleOnDrop,
addIssuesToView,
isCompletedCycle = false,
loadMoreIssues,
} = props;
const storeType = useIssueStoreType();
@@ -142,7 +140,7 @@ export const List: React.FC<IList> = observer((props) => {
{groups.map((group: IGroupByColumn) => (
<ListGroup
key={group.id}
groupIssueIds={groupedIssueIds?.[group.id]}
issueIds={groupedIssueIds?.[group.id]}
issuesMap={issuesMap}
group_by={group_by}
group={group}
@@ -159,7 +157,6 @@ export const List: React.FC<IList> = observer((props) => {
disableIssueCreation={disableIssueCreation}
addIssuesToView={addIssuesToView}
isCompletedCycle={isCompletedCycle}
loadMoreIssues={loadMoreIssues}
containerRef={containerRef}
selectionHelpers={helpers}
/>

View File

@@ -39,7 +39,7 @@ import { TRenderQuickActions } from "./list-view-types";
import { ListQuickAddIssueForm } from "./quick-add-issue-form";
interface Props {
groupIssueIds: string[] | undefined;
issueIds: string[] | undefined;
group: IGroupByColumn;
issuesMap: TIssueMap;
group_by: TIssueGroupByOptions | null;
@@ -57,13 +57,12 @@ interface Props {
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
isCompletedCycle?: boolean;
showEmptyGroup?: boolean;
loadMoreIssues: (groupId?: string) => void;
selectionHelpers: TSelectionHelper;
}
export const ListGroup = observer((props: Props) => {
const {
groupIssueIds = [],
issueIds = [],
group,
issuesMap,
group_by,
@@ -81,7 +80,6 @@ export const ListGroup = observer((props: Props) => {
addIssuesToView,
isCompletedCycle,
showEmptyGroup,
loadMoreIssues,
selectionHelpers,
} = props;
@@ -92,35 +90,7 @@ export const ListGroup = observer((props: Props) => {
const projectState = useProjectState();
const {
issues: { getGroupIssueCount, getPaginationData, getIssueLoader },
} = useIssuesStore();
const [intersectionElement, setIntersectionElement] = useState<HTMLDivElement | null>(null);
const groupIssueCount = getGroupIssueCount(group.id, undefined, false) ?? 0;
const nextPageResults = getPaginationData(group.id, undefined)?.nextPageResults;
const isPaginating = !!getIssueLoader(group.id);
useIntersectionObserver(containerRef, isPaginating ? null : intersectionElement, loadMoreIssues, `100% 0% 100% 0%`);
const shouldLoadMore =
nextPageResults === undefined && groupIssueCount !== undefined && groupIssueIds
? groupIssueIds.length < groupIssueCount
: !!nextPageResults;
const loadMore = isPaginating ? (
<ListLoaderItemRow />
) : (
<div
className={
"h-11 relative flex items-center gap-3 bg-custom-background-100 border border-transparent border-t-custom-border-200 pl-8 p-3 text-sm font-medium text-custom-primary-100 hover:text-custom-primary-200 hover:underline cursor-pointer"
}
onClick={() => loadMoreIssues(group.id)}
>
Load More &darr;
</div>
);
const groupIssueCount = issueIds?.length ?? 0;
const validateEmptyIssueGroups = (issueCount: number = 0) => {
if (!showEmptyGroup && issueCount <= 0) return false;
@@ -254,9 +224,9 @@ export const ListGroup = observer((props: Props) => {
orderBy={orderBy}
isDraggingOverColumn={isDraggingOverColumn}
/>
{groupIssueIds && (
{issueIds && (
<IssueBlocksList
issueIds={groupIssueIds}
issueIds={issueIds}
groupId={group.id}
issuesMap={issuesMap}
updateIssue={updateIssue}
@@ -270,8 +240,6 @@ export const ListGroup = observer((props: Props) => {
/>
)}
{shouldLoadMore && (group_by ? <>{loadMore}</> : <ListLoaderItemRow ref={setIntersectionElement} />)}
{enableIssueQuickAdd && !disableIssueCreation && !isGroupByCreatedBy && !isCompletedCycle && (
<div className="sticky bottom-0 z-[1] w-full flex-shrink-0">
<ListQuickAddIssueForm

View File

@@ -45,21 +45,26 @@ const CycleIssueLayout = (props: {
export const CycleLayoutRoot: React.FC = observer(() => {
const { workspaceSlug, projectId, cycleId } = useParams();
// store hooks
const { issuesFilter } = useIssues(EIssuesStoreType.CYCLE);
const { issues, issuesFilter } = useIssues(EIssuesStoreType.CYCLE);
const { getCycleById } = useCycle();
// state
const [transferIssuesModal, setTransferIssuesModal] = useState(false);
useSWR(
workspaceSlug && projectId && cycleId
? `CYCLE_ISSUES_${workspaceSlug.toString()}_${projectId.toString()}_${cycleId.toString()}`
? `CYCLE_ISSUES_FILTERS_${workspaceSlug.toString()}_${projectId.toString()}_${cycleId.toString()}`
: null,
async () => {
if (workspaceSlug && projectId && cycleId) {
await issuesFilter?.fetchFilters(workspaceSlug.toString(), projectId.toString(), cycleId.toString());
await issues.fetchIssues(
workspaceSlug.toString(),
projectId.toString(),
issues.issueIds ? "mutation" : "init-loader",
cycleId.toString()
);
}
},
{ revalidateIfStale: false, revalidateOnFocus: false }
}
);
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;

View File

@@ -40,7 +40,7 @@ export const ModuleLayoutRoot: React.FC = observer(() => {
// router
const { workspaceSlug, projectId, moduleId } = useParams();
// hooks
const { issuesFilter } = useIssues(EIssuesStoreType.MODULE);
const { issues, issuesFilter } = useIssues(EIssuesStoreType.MODULE);
useSWR(
workspaceSlug && projectId && moduleId
@@ -49,9 +49,14 @@ export const ModuleLayoutRoot: React.FC = observer(() => {
async () => {
if (workspaceSlug && projectId && moduleId) {
await issuesFilter?.fetchFilters(workspaceSlug.toString(), projectId.toString(), moduleId.toString());
await issues.fetchIssues(
workspaceSlug.toString(),
projectId.toString(),
issues.issueIds ? "mutation" : "init-loader",
moduleId.toString()
);
}
},
{ revalidateIfStale: false, revalidateOnFocus: false }
}
);
if (!workspaceSlug || !projectId || !moduleId) return <></>;

View File

@@ -44,15 +44,17 @@ export const ProjectLayoutRoot: FC = observer(() => {
// hooks
const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT);
useSWR(
workspaceSlug && projectId ? `PROJECT_ISSUES_${workspaceSlug}_${projectId}` : null,
async () => {
if (workspaceSlug && projectId) {
await issuesFilter?.fetchFilters(workspaceSlug.toString(), projectId.toString());
}
},
{ revalidateIfStale: false, revalidateOnFocus: false }
);
useSWR(workspaceSlug && projectId ? `PROJECT_ISSUES${workspaceSlug}_${projectId}` : null, async () => {
if (workspaceSlug && projectId) {
await issuesFilter?.fetchFilters(workspaceSlug.toString(), projectId.toString());
await issues.fetchIssues(
workspaceSlug.toString(),
projectId.toString(),
issues.issueIds ? "mutation" : "init-loader"
);
}
});
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;

View File

@@ -41,16 +41,21 @@ export const ProjectViewLayoutRoot: React.FC = observer(() => {
// router
const { workspaceSlug, projectId, viewId } = useParams();
// hooks
const { issuesFilter } = useIssues(EIssuesStoreType.PROJECT_VIEW);
const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT_VIEW);
const { isLoading } = useSWR(
workspaceSlug && projectId && viewId ? `PROJECT_VIEW_ISSUES_${workspaceSlug}_${projectId}_${viewId}` : null,
async () => {
if (workspaceSlug && projectId && viewId) {
await issuesFilter?.fetchFilters(workspaceSlug.toString(), projectId.toString(), viewId.toString());
await issues.fetchIssues(
workspaceSlug.toString(),
projectId.toString(),
viewId.toString(),
issues.issueIds ? "mutation" : "init-loader"
);
}
},
{ revalidateIfStale: false, revalidateOnFocus: false }
}
);
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;

View File

@@ -3,7 +3,7 @@ import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// plane constants
import { ALL_ISSUES } from "@plane/constants";
import { IIssueDisplayFilterOptions } from "@plane/types";
import { IIssueDisplayFilterOptions, TUngroupedIssues } from "@plane/types";
// hooks
import { EIssueFilterType, EIssueLayoutTypes, EIssuesStoreType } from "@/constants/issue";
import { EUserProjectRoles } from "@/constants/project";
@@ -44,7 +44,6 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
const { issues, issuesFilter } = useIssues(storeType);
const {
fetchIssues,
fetchNextIssues,
quickAddIssue,
updateIssue,
removeIssue,
@@ -58,10 +57,6 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
// user role validation
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
useEffect(() => {
fetchIssues("init-loader", { canGroup: false, perPageCount: 100 }, viewId);
}, [fetchIssues, storeType, viewId]);
const canEditProperties = useCallback(
(projectId: string | undefined) => {
const isEditingAllowedBasedOnProject =
@@ -72,8 +67,7 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
[canEditPropertiesBasedOnProject, enableInlineEditing, isEditingAllowed]
);
const issueIds = issues.groupedIssueIds?.[ALL_ISSUES] ?? [];
const nextPageResults = issues.getPaginationData(ALL_ISSUES, undefined)?.nextPageResults;
const issueIds = (issues.groupedIssueIds ?? []) as TUngroupedIssues;
const handleDisplayFiltersUpdate = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
@@ -120,8 +114,6 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
quickAddCallback={quickAddIssue}
enableQuickCreateIssue={enableQuickAdd}
disableIssueCreation={!enableIssueCreation || !isEditingAllowed || isCompletedCycle}
canLoadMoreIssues={!!nextPageResults}
loadMoreIssues={fetchNextIssues}
/>
</IssueLayoutHOC>
);

View File

@@ -25,8 +25,6 @@ type Props = {
canEditProperties: (projectId: string | undefined) => boolean;
portalElement: React.MutableRefObject<HTMLDivElement | null>;
containerRef: MutableRefObject<HTMLTableElement | null>;
canLoadMoreIssues: boolean;
loadMoreIssues: () => void;
spreadsheetColumnsList: (keyof IIssueDisplayProperties)[];
selectionHelpers: TSelectionHelper;
};
@@ -42,20 +40,13 @@ export const SpreadsheetTable = observer((props: Props) => {
quickActions,
updateIssue,
canEditProperties,
canLoadMoreIssues,
containerRef,
loadMoreIssues,
spreadsheetColumnsList,
selectionHelpers,
} = props;
// states
const isScrolled = useRef(false);
const [intersectionElement, setIntersectionElement] = useState<HTMLTableSectionElement | null>(null);
const {
issues: { getIssueLoader },
} = useIssuesStore();
const handleScroll = useCallback(() => {
if (!containerRef.current) return;
@@ -90,10 +81,6 @@ export const SpreadsheetTable = observer((props: Props) => {
};
}, [handleScroll, containerRef]);
const isPaginating = !!getIssueLoader();
useIntersectionObserver(containerRef, isPaginating ? null : intersectionElement, loadMoreIssues, `100% 0% 100% 0%`);
const handleKeyBoardNavigation = useTableKeyboardNavigation();
const ignoreFieldsForCounting: (keyof IIssueDisplayProperties)[] = ["key"];
@@ -131,11 +118,6 @@ export const SpreadsheetTable = observer((props: Props) => {
/>
))}
</tbody>
{canLoadMoreIssues && (
<tfoot ref={setIntersectionElement}>
<SpreadsheetIssueRowLoader columnCount={displayPropertiesCount} />
</tfoot>
)}
</table>
);
});

View File

@@ -28,8 +28,6 @@ type Props = {
openIssuesListModal?: (() => void) | null;
quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise<TIssue | undefined>;
canEditProperties: (projectId: string | undefined) => boolean;
canLoadMoreIssues: boolean;
loadMoreIssues: () => void;
enableQuickCreateIssue?: boolean;
disableIssueCreation?: boolean;
isWorkspaceLevel?: boolean;
@@ -47,8 +45,6 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
canEditProperties,
enableQuickCreateIssue,
disableIssueCreation,
canLoadMoreIssues,
loadMoreIssues,
isWorkspaceLevel = false,
} = props;
// refs
@@ -100,8 +96,6 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
updateIssue={updateIssue}
canEditProperties={canEditProperties}
containerRef={containerRef}
canLoadMoreIssues={canLoadMoreIssues}
loadMoreIssues={loadMoreIssues}
spreadsheetColumnsList={spreadsheetColumnsList}
selectionHelpers={helpers}
/>

197
web/core/db/local.index.ts Normal file
View File

@@ -0,0 +1,197 @@
import { rootStore } from "@/lib/store-context";
import { IssueService } from "@/services/issue";
import { IIssueDisplayFilterOptions, IIssueFilterOptions, IIssueFilters, TIssue } from "@plane/types";
import Dexie, { type EntityTable } from "dexie";
import { get, set } from "lodash";
import { keys } from "ts-transformer-keys";
type ProjectSyncState = {
id: string;
lastSynced?: Date;
isSyncing?: Promise<void>;
};
export const ISSUE_FILTER_MAP: Record<keyof IIssueFilterOptions, keyof TIssue> = {
project: "project_id",
cycle: "cycle_id",
module: "module_ids",
state: "state_id",
state_group: "state_group" as keyof TIssue, // state_detail.group is only being used for state_group display,
priority: "priority",
labels: "label_ids",
created_by: "created_by",
assignees: "assignee_ids",
target_date: "target_date",
start_date: "start_date",
};
type DexieDB = Dexie & {
issues: EntityTable<
TIssue,
"id" // primary key "id" (for the typings only)
>;
projects: EntityTable<
ProjectSyncState,
"id" // primary key "id" (for the typings only)
>;
};
class IssueDB {
db: DexieDB;
issueService: IssueService;
projectSyncMap: Record<string, ProjectSyncState> = {};
constructor() {
this.db = this.getNewInstantiatedDB();
this.db.version(1).stores({
projects: "id,lastSynced",
issues:
"id,sequence_id,name,sort_order,state_id,priority,label_ids,assignee_ids,estimate_point,sub_issues_count,attachment_count,link_count,project_id,parent_id,cycle_id,module_ids,created_at,updated_at,start_date,target_date,completed_at,archived_at,created_by,updated_by,is_draft,description_html,is_subscribed,parent,issue_reactions,issue_attachment,issue_link",
});
this.issueService = new IssueService();
this.initiateProjectMap();
}
getNewInstantiatedDB() {
this.clearDB();
return new Dexie("IssuesDB") as DexieDB;
}
clearDB() {
this.db?.delete();
}
async initiateProjectMap() {
const projectSyncDetails = await this.db.projects.toArray();
for (const syncState of projectSyncDetails) {
set(this.projectSyncMap, syncState.id, syncState);
}
}
async updateProjectSyncState(syncState: ProjectSyncState) {
await this.db.projects.bulkPut([syncState]);
}
async loadIssues(workSpaceSlug: string, projectId: string, params?: any) {
try {
const issueResponse = await this.issueService.getIssues(workSpaceSlug, projectId, {
cursor: "1000:0:0",
per_page: 1000,
...params,
});
await this.db.issues.bulkPut(issueResponse.results as TIssue[]);
if (issueResponse.total_pages > 1) {
const promiseArray = [];
for (let i = 1; i < issueResponse.total_pages; i++) {
promiseArray.push(
this.issueService.getIssues(workSpaceSlug, projectId, { cursor: `1000:${i}:0`, per_page: 1000, ...params })
);
}
const responseArray = await Promise.all(promiseArray);
for (const currIssueResponse of responseArray) {
await this.db.issues.bulkPut(currIssueResponse.results as TIssue[]);
}
}
} catch (e) {
console.error("error while syncing project, ", projectId, e);
throw new Error("Error while syncing Project Issues" + projectId);
}
}
async syncIssues(workSpaceSlug: string, projectId: string, lastSynced: Date) {
await this.loadIssues(workSpaceSlug, projectId, { updated_at__gt: lastSynced });
//this.issueService.getDeletedIssues(workSpaceSlug, projectId, )
}
async getIssues(workSpaceSlug: string, projectId: string, issueFilters: IIssueFilters | undefined) {
try {
const startTime = performance.now();
let projectSyncState: ProjectSyncState | undefined = get(this.projectSyncMap, projectId);
if (!projectSyncState) {
set(this.projectSyncMap, projectId, { id: projectId });
projectSyncState = get(this.projectSyncMap, projectId);
}
const syncTime = new Date();
// if (projectSyncState?.isSyncing) await projectSyncState?.isSyncing;
// else
if (projectSyncState?.lastSynced) {
projectSyncState.isSyncing = this.syncIssues(workSpaceSlug, projectId, projectSyncState?.lastSynced);
await projectSyncState?.isSyncing;
projectSyncState.lastSynced = syncTime;
} else {
projectSyncState.isSyncing = this.loadIssues(workSpaceSlug, projectId);
await projectSyncState?.isSyncing;
projectSyncState.lastSynced = syncTime;
}
projectSyncState.isSyncing = undefined;
this.updateProjectSyncState(projectSyncState);
const filterFunction = this.issueFilterByValues(issueFilters?.filters, issueFilters?.displayFilters);
const queryStartTime = performance.now();
let query: any = this.db.issues;
if (filterFunction) query = this.db.issues.filter(filterFunction);
const issueArray = (await query.toArray()) as TIssue[];
console.log("### IndexDB Querying Took, ", performance.now() - queryStartTime, "ms");
console.log("### getIssues Took, ", performance.now() - startTime, "ms");
return issueArray;
} catch (e) {
console.error("error while fetching issues", projectId, issueFilters, e);
throw new Error("Error while fetching Issues" + projectId);
}
}
issueFilterByValues = (
filters: IIssueFilterOptions | undefined,
displayFilters: IIssueDisplayFilterOptions | undefined
) => {
if (!filters || !displayFilters) return;
return (issue: TIssue) => {
const filterKeys = Object.keys(filters) as (keyof IIssueFilterOptions)[];
for (const filterKey of filterKeys) {
const filterIssueKey = ISSUE_FILTER_MAP[filterKey];
const filterValue = filters[filterKey];
const issueValue = issue[filterIssueKey] as string | string[] | null | undefined;
if (!filterValue || filterValue.length <= 0) continue;
if (!issueValue || this.shouldFilterOutIssue(issueValue, filterValue, filterKey)) return false;
}
if (!displayFilters.sub_issue && issue.parent) return false;
return true;
};
};
shouldFilterOutIssue(issueValue: string | string[], filterValue: string[], filterKey: keyof IIssueFilterOptions) {
if (filterKey.endsWith("date")) {
} else if (filterKey === "state_group" && !Array.isArray(issueValue)) {
const issueStateGroup = rootStore?.state?.stateMap?.[issueValue]?.group;
if (!issueStateGroup || !filterValue.includes(issueStateGroup)) return true;
} else {
if (Array.isArray(issueValue)) {
if (!filterValue.every((value: string) => issueValue.includes(value))) return true;
} else if (!filterValue.includes(issueValue)) {
return true;
}
}
return false;
}
}
export const issueDB = new IssueDB();

View File

@@ -40,6 +40,16 @@ export class IssueService extends APIService {
});
}
async getDeletedIssues(workspaceSlug: string, projectId: string, queries?: any): Promise<TIssuesResponse> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/deleted-issues/`, {
params: queries,
})
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async getIssuesWithParams(
workspaceSlug: string,
projectId: string,

View File

@@ -88,7 +88,7 @@ export class CycleIssuesFilter extends IssueFilterHelperStore implements ICycleI
const displayFilters = this.filters[cycleId] || undefined;
if (isEmpty(displayFilters)) return undefined;
const _filters: IIssueFilters = this.computedIssueFilters(displayFilters);
const _filters: IIssueFilters = this.computedIssueFilters(displayFilters, { cycle: [cycleId] });
return _filters;
}
@@ -192,12 +192,7 @@ export class CycleIssuesFilter extends IssueFilterHelperStore implements ICycleI
const appliedFilters = _filters.filters || {};
const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0);
this.rootIssueStore.cycleIssues.fetchIssuesWithExistingPagination(
workspaceSlug,
projectId,
isEmpty(filteredFilters) ? "init-loader" : "mutation",
cycleId
);
this.rootIssueStore.cycleIssues.fetchIssues(workspaceSlug, projectId, "mutation", cycleId);
await this.issueFilterService.patchCycleIssueFilters(workspaceSlug, projectId, cycleId, {
filters: _filters.filters,
});
@@ -237,12 +232,7 @@ export class CycleIssuesFilter extends IssueFilterHelperStore implements ICycleI
});
if (this.getShouldReFetchIssues(updatedDisplayFilters)) {
this.rootIssueStore.cycleIssues.fetchIssuesWithExistingPagination(
workspaceSlug,
projectId,
"mutation",
cycleId
);
this.rootIssueStore.cycleIssues.fetchIssues(workspaceSlug, projectId, "mutation", cycleId);
}
await this.issueFilterService.patchCycleIssueFilters(workspaceSlug, projectId, cycleId, {

View File

@@ -24,6 +24,7 @@ import { BaseIssuesStore, IBaseIssuesStore } from "../helpers/base-issues.store"
//
import { IIssueRootStore } from "../root.store";
import { ICycleIssuesFilter } from "./filter.store";
import { issueDB } from "@/db/local.index";
export const ACTIVE_CYCLE_ISSUES = "ACTIVE_CYCLE_ISSUES";
@@ -41,27 +42,7 @@ export interface ICycleIssues extends IBaseIssuesStore {
//action helpers
getActiveCycleById: (cycleId: string) => ActiveCycleIssueDetails | undefined;
// actions
getIssueIds: (groupId?: string, subGroupId?: string) => string[] | undefined;
fetchIssues: (
workspaceSlug: string,
projectId: string,
loadType: TLoader,
options: IssuePaginationOptions,
cycleId: string
) => Promise<TIssuesResponse | undefined>;
fetchIssuesWithExistingPagination: (
workspaceSlug: string,
projectId: string,
loadType: TLoader,
cycleId: string
) => Promise<TIssuesResponse | undefined>;
fetchNextIssues: (
workspaceSlug: string,
projectId: string,
cycleId: string,
groupId?: string,
subGroupId?: string
) => Promise<TIssuesResponse | undefined>;
fetchIssues: (workspaceSlug: string, projectId: string, loadType: TLoader, cycleId: string) => Promise<TIssue[]>;
fetchActiveCycleIssues: (
workspaceSlug: string,
@@ -115,8 +96,6 @@ export class CycleIssues extends BaseIssuesStore implements ICycleIssues {
activeCycleIds: observable,
// action
fetchIssues: action,
fetchNextIssues: action,
fetchIssuesWithExistingPagination: action,
transferIssuesFromCycle: action,
fetchActiveCycleIssues: action,
@@ -163,30 +142,18 @@ export class CycleIssues extends BaseIssuesStore implements ICycleIssues {
* @param cycleId
* @returns
*/
fetchIssues = async (
workspaceSlug: string,
projectId: string,
loadType: TLoader,
options: IssuePaginationOptions,
cycleId: string,
isExistingPaginationOptions: boolean = false
) => {
fetchIssues = async (workspaceSlug: string, projectId: string, loadType: TLoader, cycleId: string) => {
try {
// set loader and clear store
runInAction(() => {
this.setLoader(loadType);
});
this.clear(!isExistingPaginationOptions);
if (loadType === "init-loader") this.clear();
// get params from pagination options
const params = this.issueFilterStore?.getFilterParams(options, cycleId, undefined, undefined, undefined);
// call the fetch issues API with the params
const response = await this.cycleService.getCycleIssues(workspaceSlug, projectId, cycleId, params, {
signal: this.controller.signal,
});
const response = await issueDB.getIssues(workspaceSlug, projectId, this.issueFilterStore.issueFilters);
// after fetching issues, call the base method to process the response further
this.onfetchIssues(response, options, workspaceSlug, projectId, cycleId);
this.onfetchIssues(response);
return response;
} catch (error) {
// set loader to undefined once errored out
@@ -195,71 +162,6 @@ export class CycleIssues extends BaseIssuesStore implements ICycleIssues {
}
};
/**
* This method is called subsequent pages of pagination
* if groupId/subgroupId is provided, only that specific group's next page is fetched
* else all the groups' next page is fetched
* @param workspaceSlug
* @param projectId
* @param cycleId
* @param groupId
* @param subGroupId
* @returns
*/
fetchNextIssues = async (
workspaceSlug: string,
projectId: string,
cycleId: string,
groupId?: string,
subGroupId?: string
) => {
const cursorObject = this.getPaginationData(groupId, subGroupId);
// if there are no pagination options and the next page results do not exist the return
if (!this.paginationOptions || (cursorObject && !cursorObject?.nextPageResults)) return;
try {
// set Loader
this.setLoader("pagination", groupId, subGroupId);
// get params from stored pagination options
const params = this.issueFilterStore?.getFilterParams(
this.paginationOptions,
cycleId,
this.getNextCursor(groupId, subGroupId),
groupId,
subGroupId
);
// call the fetch issues API with the params for next page in issues
const response = await this.cycleService.getCycleIssues(workspaceSlug, projectId, cycleId, params);
// after the next page of issues are fetched, call the base method to process the response
this.onfetchNexIssues(response, groupId, subGroupId);
return response;
} catch (error) {
// set Loader as undefined if errored out
this.setLoader(undefined, groupId, subGroupId);
throw error;
}
};
/**
* This Method exists to fetch the first page of the issues with the existing stored pagination
* This is useful for refetching when filters, groupBy, orderBy etc changes
* @param workspaceSlug
* @param projectId
* @param loadType
* @param cycleId
* @returns
*/
fetchIssuesWithExistingPagination = async (
workspaceSlug: string,
projectId: string,
loadType: TLoader,
cycleId: string
) => {
if (!this.paginationOptions) return;
return await this.fetchIssues(workspaceSlug, projectId, loadType, this.paginationOptions, cycleId, true);
};
/**
* Override inherited create issue, to also add issue to cycle
* @param workspaceSlug
@@ -304,8 +206,7 @@ export class CycleIssues extends BaseIssuesStore implements ICycleIssues {
payload
);
// call fetch issues
this.paginationOptions &&
(await this.fetchIssues(workspaceSlug, projectId, "mutation", this.paginationOptions, cycleId));
await this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId);
return response;
} catch (error) {
@@ -333,11 +234,11 @@ export class CycleIssues extends BaseIssuesStore implements ICycleIssues {
const response = await this.cycleService.getCycleIssues(workspaceSlug, projectId, cycleId, params);
// Process issue response
const { issueList, groupedIssues } = this.processIssueResponse(response);
const issueList = this.processIssueResponse(response);
// add issues to the main Issue Map
this.rootIssueStore.issues.addIssue(issueList);
const activeIssueIds = groupedIssues[ALL_ISSUES] as string[];
const activeIssueIds = issueList?.map((issue) => issue.id);
// store the processed data in the current store
set(this.activeCycleIds, [cycleId], {
@@ -376,21 +277,20 @@ export class CycleIssues extends BaseIssuesStore implements ICycleIssues {
const response = await this.cycleService.getCycleIssues(workspaceSlug, projectId, cycleId, params);
// Process the response
const { issueList, groupedIssues } = this.processIssueResponse(response);
const issueList = this.processIssueResponse(response);
// add issues to main issue Map
this.rootIssueStore.issues.addIssue(issueList);
const activeIssueIds = groupedIssues[ALL_ISSUES] as string[];
// store the processed data for subsequent pages
set(this.activeCycleIds, [cycleId, "issueCount"], response.total_count);
set(this.activeCycleIds, [cycleId, "nextCursor"], response.next_cursor);
set(this.activeCycleIds, [cycleId, "nextPageResults"], response.next_page_results);
set(this.activeCycleIds, [cycleId, "issueCount"], response.total_count);
update(this.activeCycleIds, [cycleId, "issueIds"], (issueIds: string[] = []) =>
this.issuesSortWithOrderBy(uniq(concat(issueIds, activeIssueIds)), this.orderBy)
);
// TODO: FIXME
// update(this.activeCycleIds, [cycleId, "issueIds"], (issueIds: TIssue[] = []) =>
// this.issuesSortWithOrderBy(uniq(concat(issueIds, activeIssueIds)), this.orderBy)
// );
return response;
} catch (error) {

File diff suppressed because it is too large Load Diff

View File

@@ -58,12 +58,15 @@ export class IssueFilterHelperStore implements IIssueFilterHelperStore {
* @param {IIssueFilters} filters
* @returns {IIssueFilters}
*/
computedIssueFilters = (filters: IIssueFilters): IIssueFilters => ({
filters: isEmpty(filters?.filters) ? undefined : filters?.filters,
displayFilters: isEmpty(filters?.displayFilters) ? undefined : filters?.displayFilters,
displayProperties: isEmpty(filters?.displayProperties) ? undefined : filters?.displayProperties,
kanbanFilters: isEmpty(filters?.kanbanFilters) ? undefined : filters?.kanbanFilters,
});
computedIssueFilters = (filters: IIssueFilters, defaultFilters?: IIssueFilterOptions): IIssueFilters => {
const currFilters = isEmpty(filters?.filters) ? defaultFilters : { ...filters?.filters, ...defaultFilters };
return {
filters: currFilters,
displayFilters: isEmpty(filters?.displayFilters) ? undefined : filters?.displayFilters,
displayProperties: isEmpty(filters?.displayProperties) ? undefined : filters?.displayProperties,
kanbanFilters: isEmpty(filters?.kanbanFilters) ? undefined : filters?.kanbanFilters,
};
};
/**
* @description This method is used to convert the filters array params to string params

File diff suppressed because it is too large Load Diff

View File

@@ -88,7 +88,7 @@ export class ModuleIssuesFilter extends IssueFilterHelperStore implements IModul
const displayFilters = this.filters[moduleId] || undefined;
if (isEmpty(displayFilters)) return undefined;
const _filters: IIssueFilters = this.computedIssueFilters(displayFilters);
const _filters: IIssueFilters = this.computedIssueFilters(displayFilters, { module: [moduleId] });
return _filters;
}
@@ -191,12 +191,7 @@ export class ModuleIssuesFilter extends IssueFilterHelperStore implements IModul
});
const appliedFilters = _filters.filters || {};
const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0);
this.rootIssueStore.moduleIssues.fetchIssuesWithExistingPagination(
workspaceSlug,
projectId,
isEmpty(filteredFilters) ? "init-loader" : "mutation",
moduleId
);
this.rootIssueStore.moduleIssues.fetchIssues(workspaceSlug, projectId, "mutation", moduleId);
await this.issueFilterService.patchModuleIssueFilters(workspaceSlug, projectId, moduleId, {
filters: _filters.filters,
});
@@ -236,12 +231,7 @@ export class ModuleIssuesFilter extends IssueFilterHelperStore implements IModul
});
if (this.getShouldReFetchIssues(updatedDisplayFilters)) {
this.rootIssueStore.moduleIssues.fetchIssuesWithExistingPagination(
workspaceSlug,
projectId,
"mutation",
moduleId
);
this.rootIssueStore.moduleIssues.fetchIssues(workspaceSlug, projectId, "mutation", moduleId);
}
await this.issueFilterService.patchModuleIssueFilters(workspaceSlug, projectId, moduleId, {

View File

@@ -14,31 +14,12 @@ import { BaseIssuesStore, IBaseIssuesStore } from "../helpers/base-issues.store"
//
import { IIssueRootStore } from "../root.store";
import { IModuleIssuesFilter } from "./filter.store";
import { issueDB } from "@/db/local.index";
export interface IModuleIssues extends IBaseIssuesStore {
viewFlags: ViewFlags;
// actions
getIssueIds: (groupId?: string, subGroupId?: string) => string[] | undefined;
fetchIssues: (
workspaceSlug: string,
projectId: string,
loadType: TLoader,
options: IssuePaginationOptions,
moduleId: string
) => Promise<TIssuesResponse | undefined>;
fetchIssuesWithExistingPagination: (
workspaceSlug: string,
projectId: string,
loadType: TLoader,
moduleId: string
) => Promise<TIssuesResponse | undefined>;
fetchNextIssues: (
workspaceSlug: string,
projectId: string,
moduleId: string,
groupId?: string,
subGroupId?: string
) => Promise<TIssuesResponse | undefined>;
fetchIssues: (workspaceSlug: string, projectId: string, loadType: TLoader, moduleId: string) => Promise<TIssue[]>;
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>, moduleId: string) => Promise<TIssue>;
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
@@ -69,8 +50,6 @@ export class ModuleIssues extends BaseIssuesStore implements IModuleIssues {
makeObservable(this, {
// action
fetchIssues: action,
fetchNextIssues: action,
fetchIssuesWithExistingPagination: action,
quickAddIssue: action,
});
@@ -120,30 +99,18 @@ export class ModuleIssues extends BaseIssuesStore implements IModuleIssues {
* @param moduleId
* @returns
*/
fetchIssues = async (
workspaceSlug: string,
projectId: string,
loadType: TLoader,
options: IssuePaginationOptions,
moduleId: string,
isExistingPaginationOptions: boolean = false
) => {
fetchIssues = async (workspaceSlug: string, projectId: string, loadType: TLoader, moduleId: string) => {
try {
// set loader and clear store
runInAction(() => {
this.setLoader(loadType);
});
this.clear(!isExistingPaginationOptions);
if (loadType === "init-loader") this.clear();
// get params from pagination options
const params = this.issueFilterStore?.getFilterParams(options, moduleId, undefined, undefined, undefined);
// call the fetch issues API with the params
const response = await this.moduleService.getModuleIssues(workspaceSlug, projectId, moduleId, params, {
signal: this.controller.signal,
});
const response = await issueDB.getIssues(workspaceSlug, projectId, this.issueFilterStore.issueFilters);
// after fetching issues, call the base method to process the response further
this.onfetchIssues(response, options, workspaceSlug, projectId, moduleId);
this.onfetchIssues(response);
return response;
} catch (error) {
// set loader to undefined once errored out
@@ -152,71 +119,6 @@ export class ModuleIssues extends BaseIssuesStore implements IModuleIssues {
}
};
/**
* This method is called subsequent pages of pagination
* if groupId/subgroupId is provided, only that specific group's next page is fetched
* else all the groups' next page is fetched
* @param workspaceSlug
* @param projectId
* @param moduleId
* @param groupId
* @param subGroupId
* @returns
*/
fetchNextIssues = async (
workspaceSlug: string,
projectId: string,
moduleId: string,
groupId?: string,
subGroupId?: string
) => {
const cursorObject = this.getPaginationData(groupId, subGroupId);
// if there are no pagination options and the next page results do not exist the return
if (!this.paginationOptions || (cursorObject && !cursorObject?.nextPageResults)) return;
try {
// set Loader
this.setLoader("pagination", groupId, subGroupId);
// get params from stored pagination options
const params = this.issueFilterStore?.getFilterParams(
this.paginationOptions,
moduleId,
this.getNextCursor(groupId, subGroupId),
groupId,
subGroupId
);
// call the fetch issues API with the params for next page in issues
const response = await this.moduleService.getModuleIssues(workspaceSlug, projectId, moduleId, params);
// after the next page of issues are fetched, call the base method to process the response
this.onfetchNexIssues(response, groupId, subGroupId);
return response;
} catch (error) {
// set Loader as undefined if errored out
this.setLoader(undefined, groupId, subGroupId);
throw error;
}
};
/**
* This Method exists to fetch the first page of the issues with the existing stored pagination
* This is useful for refetching when filters, groupBy, orderBy etc changes
* @param workspaceSlug
* @param projectId
* @param loadType
* @param moduleId
* @returns
*/
fetchIssuesWithExistingPagination = async (
workspaceSlug: string,
projectId: string,
loadType: TLoader,
moduleId: string
) => {
if (!this.paginationOptions) return;
return await this.fetchIssues(workspaceSlug, projectId, loadType, this.paginationOptions, moduleId, true);
};
/**
* Override inherited create issue, to also add issue to module
* @param workspaceSlug

View File

@@ -86,9 +86,10 @@ export class ProjectViewIssuesFilter extends IssueFilterHelperStore implements I
getIssueFilters(viewId: string) {
const displayFilters = this.filters[viewId] || undefined;
if (isEmpty(displayFilters)) return undefined;
const projectId = this.rootIssueStore.projectId;
if (isEmpty(displayFilters) || !projectId) return undefined;
const _filters: IIssueFilters = this.computedIssueFilters(displayFilters);
const _filters: IIssueFilters = this.computedIssueFilters(displayFilters, { project: [projectId] });
return _filters;
}
@@ -190,12 +191,7 @@ export class ProjectViewIssuesFilter extends IssueFilterHelperStore implements I
const appliedFilters = _filters.filters || {};
const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0);
this.rootIssueStore.projectViewIssues.fetchIssuesWithExistingPagination(
workspaceSlug,
projectId,
viewId,
isEmpty(filteredFilters) ? "init-loader" : "mutation"
);
this.rootIssueStore.projectViewIssues.fetchIssues(workspaceSlug, projectId, viewId, "mutation");
break;
}
case EIssueFilterType.DISPLAY_FILTERS: {
@@ -232,12 +228,7 @@ export class ProjectViewIssuesFilter extends IssueFilterHelperStore implements I
});
if (this.getShouldReFetchIssues(updatedDisplayFilters)) {
this.rootIssueStore.projectViewIssues.fetchIssuesWithExistingPagination(
workspaceSlug,
projectId,
viewId,
"mutation"
);
this.rootIssueStore.projectViewIssues.fetchIssues(workspaceSlug, projectId, viewId, "mutation");
}
break;

View File

@@ -13,30 +13,12 @@ import {
import { BaseIssuesStore, IBaseIssuesStore } from "../helpers/base-issues.store";
import { IIssueRootStore } from "../root.store";
import { IProjectViewIssuesFilter } from "./filter.store";
import { issueDB } from "@/db/local.index";
export interface IProjectViewIssues extends IBaseIssuesStore {
viewFlags: ViewFlags;
// actions
fetchIssues: (
workspaceSlug: string,
projectId: string,
viewId: string,
loadType: TLoader,
options: IssuePaginationOptions
) => Promise<TIssuesResponse | undefined>;
fetchIssuesWithExistingPagination: (
workspaceSlug: string,
projectId: string,
viewId: string,
loadType: TLoader
) => Promise<TIssuesResponse | undefined>;
fetchNextIssues: (
workspaceSlug: string,
projectId: string,
viewId: string,
groupId?: string,
subGroupId?: string
) => Promise<TIssuesResponse | undefined>;
fetchIssues: (workspaceSlug: string, projectId: string, viewId: string, loadType: TLoader) => Promise<TIssue[]>;
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
@@ -61,8 +43,6 @@ export class ProjectViewIssues extends BaseIssuesStore implements IProjectViewIs
makeObservable(this, {
// action
fetchIssues: action,
fetchNextIssues: action,
fetchIssuesWithExistingPagination: action,
});
//filter store
this.issueFilterStore = issueFilterStore;
@@ -81,30 +61,18 @@ export class ProjectViewIssues extends BaseIssuesStore implements IProjectViewIs
* @param options
* @returns
*/
fetchIssues = async (
workspaceSlug: string,
projectId: string,
viewId: string,
loadType: TLoader,
options: IssuePaginationOptions,
isExistingPaginationOptions: boolean = false
) => {
fetchIssues = async (workspaceSlug: string, projectId: string, viewId: string, loadType: TLoader) => {
try {
// set loader and clear store
runInAction(() => {
this.setLoader(loadType);
});
this.clear(!isExistingPaginationOptions);
if (loadType === "init-loader") this.clear();
// get params from pagination options
const params = this.issueFilterStore?.getFilterParams(options, viewId, undefined, undefined, undefined);
// call the fetch issues API with the params
const response = await this.issueService.getIssues(workspaceSlug, projectId, params, {
signal: this.controller.signal,
});
const response = await issueDB.getIssues(workspaceSlug, projectId, this.issueFilterStore.issueFilters);
// after fetching issues, call the base method to process the response further
this.onfetchIssues(response, options, workspaceSlug, projectId);
this.onfetchIssues(response);
return response;
} catch (error) {
// set loader to undefined if errored out
@@ -113,69 +81,6 @@ export class ProjectViewIssues extends BaseIssuesStore implements IProjectViewIs
}
};
/**
* This method is called subsequent pages of pagination
* if groupId/subgroupId is provided, only that specific group's next page is fetched
* else all the groups' next page is fetched
* @param workspaceSlug
* @param projectId
* @param groupId
* @param subGroupId
* @returns
*/
fetchNextIssues = async (
workspaceSlug: string,
projectId: string,
viewId: string,
groupId?: string,
subGroupId?: string
) => {
const cursorObject = this.getPaginationData(groupId, subGroupId);
// if there are no pagination options and the next page results do not exist the return
if (!this.paginationOptions || (cursorObject && !cursorObject?.nextPageResults)) return;
try {
// set Loader
this.setLoader("pagination", groupId, subGroupId);
// get params from stored pagination options
const params = this.issueFilterStore?.getFilterParams(
this.paginationOptions,
viewId,
this.getNextCursor(groupId, subGroupId),
groupId,
subGroupId
);
// call the fetch issues API with the params for next page in issues
const response = await this.issueService.getIssues(workspaceSlug, projectId, params);
// after the next page of issues are fetched, call the base method to process the response
this.onfetchNexIssues(response, groupId, subGroupId);
return response;
} catch (error) {
// set Loader as undefined if errored out
this.setLoader(undefined, groupId, subGroupId);
throw error;
}
};
/**
* This Method exists to fetch the first page of the issues with the existing stored pagination
* This is useful for refetching when filters, groupBy, orderBy etc changes
* @param workspaceSlug
* @param projectId
* @param loadType
* @returns
*/
fetchIssuesWithExistingPagination = async (
workspaceSlug: string,
projectId: string,
viewId: string,
loadType: TLoader
) => {
if (!this.paginationOptions) return;
return await this.fetchIssues(workspaceSlug, projectId, viewId, loadType, this.paginationOptions, true);
};
// Using aliased names as they cannot be overridden in other stores
archiveBulkIssues = this.bulkArchiveIssues;
quickAddIssue = this.issueQuickAdd;

View File

@@ -87,7 +87,7 @@ export class ProjectIssuesFilter extends IssueFilterHelperStore implements IProj
const displayFilters = this.filters[projectId] || undefined;
if (isEmpty(displayFilters)) return undefined;
return this.computedIssueFilters(displayFilters);
return this.computedIssueFilters(displayFilters, { project: [projectId] });
}
getAppliedFilters(projectId: string) {
@@ -185,7 +185,7 @@ export class ProjectIssuesFilter extends IssueFilterHelperStore implements IProj
const appliedFilters = _filters.filters || {};
const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0);
this.rootIssueStore.projectIssues.fetchIssuesWithExistingPagination(
this.rootIssueStore.projectIssues.fetchIssues(
workspaceSlug,
projectId,
isEmpty(filteredFilters) ? "init-loader" : "mutation"
@@ -229,7 +229,7 @@ export class ProjectIssuesFilter extends IssueFilterHelperStore implements IProj
});
if (this.getShouldReFetchIssues(updatedDisplayFilters)) {
this.rootIssueStore.projectIssues.fetchIssuesWithExistingPagination(workspaceSlug, projectId, "mutation");
this.rootIssueStore.projectIssues.fetchIssues(workspaceSlug, projectId, "mutation");
}
await this.issueFilterService.patchProjectIssueFilters(workspaceSlug, projectId, {

View File

@@ -14,27 +14,12 @@ import { BaseIssuesStore, IBaseIssuesStore } from "../helpers/base-issues.store"
// services
import { IIssueRootStore } from "../root.store";
import { IProjectIssuesFilter } from "./filter.store";
import { issueDB } from "@/db/local.index";
export interface IProjectIssues extends IBaseIssuesStore {
viewFlags: ViewFlags;
// action
fetchIssues: (
workspaceSlug: string,
projectId: string,
loadType: TLoader,
option: IssuePaginationOptions
) => Promise<TIssuesResponse | undefined>;
fetchIssuesWithExistingPagination: (
workspaceSlug: string,
projectId: string,
loadType: TLoader
) => Promise<TIssuesResponse | undefined>;
fetchNextIssues: (
workspaceSlug: string,
projectId: string,
groupId?: string,
subGroupId?: string
) => Promise<TIssuesResponse | undefined>;
fetchIssues: (workspaceSlug: string, projectId: string, loadType: TLoader) => Promise<TIssue[]>;
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
@@ -60,8 +45,6 @@ export class ProjectIssues extends BaseIssuesStore implements IProjectIssues {
super(_rootStore, issueFilterStore);
makeObservable(this, {
fetchIssues: action,
fetchNextIssues: action,
fetchIssuesWithExistingPagination: action,
quickAddIssue: action,
});
@@ -90,29 +73,18 @@ export class ProjectIssues extends BaseIssuesStore implements IProjectIssues {
* @param options
* @returns
*/
fetchIssues = async (
workspaceSlug: string,
projectId: string,
loadType: TLoader = "init-loader",
options: IssuePaginationOptions,
isExistingPaginationOptions: boolean = false
) => {
fetchIssues = async (workspaceSlug: string, projectId: string, loadType: TLoader = "init-loader") => {
try {
// set loader and clear store
runInAction(() => {
this.setLoader(loadType);
});
this.clear(!isExistingPaginationOptions);
if (loadType === "init-loader") this.clear();
// get params from pagination options
const params = this.issueFilterStore?.getFilterParams(options, projectId, undefined, undefined, undefined);
// call the fetch issues API with the params
const response = await this.issueService.getIssues(workspaceSlug, projectId, params, {
signal: this.controller.signal,
});
const response = await issueDB.getIssues(workspaceSlug, projectId, this.issueFilterStore.issueFilters);
// after fetching issues, call the base method to process the response further
this.onfetchIssues(response, options, workspaceSlug, projectId);
this.onfetchIssues(response);
return response;
} catch (error) {
// set loader to undefined if errored out
@@ -121,62 +93,6 @@ export class ProjectIssues extends BaseIssuesStore implements IProjectIssues {
}
};
/**
* This method is called subsequent pages of pagination
* if groupId/subgroupId is provided, only that specific group's next page is fetched
* else all the groups' next page is fetched
* @param workspaceSlug
* @param projectId
* @param groupId
* @param subGroupId
* @returns
*/
fetchNextIssues = async (workspaceSlug: string, projectId: string, groupId?: string, subGroupId?: string) => {
const cursorObject = this.getPaginationData(groupId, subGroupId);
// if there are no pagination options and the next page results do not exist the return
if (!this.paginationOptions || (cursorObject && !cursorObject?.nextPageResults)) return;
try {
// set Loader
this.setLoader("pagination", groupId, subGroupId);
// get params from stored pagination options
const params = this.issueFilterStore?.getFilterParams(
this.paginationOptions,
projectId,
this.getNextCursor(groupId, subGroupId),
groupId,
subGroupId
);
// call the fetch issues API with the params for next page in issues
const response = await this.issueService.getIssues(workspaceSlug, projectId, params);
// after the next page of issues are fetched, call the base method to process the response
this.onfetchNexIssues(response, groupId, subGroupId);
return response;
} catch (error) {
// set Loader as undefined if errored out
this.setLoader(undefined, groupId, subGroupId);
throw error;
}
};
/**
* This Method exists to fetch the first page of the issues with the existing stored pagination
* This is useful for refetching when filters, groupBy, orderBy etc changes
* @param workspaceSlug
* @param projectId
* @param loadType
* @returns
*/
fetchIssuesWithExistingPagination = async (
workspaceSlug: string,
projectId: string,
loadType: TLoader = "mutation"
) => {
if (!this.paginationOptions) return;
return await this.fetchIssues(workspaceSlug, projectId, loadType, this.paginationOptions, true);
};
/**
* Override inherited create issue, to update list only if user is on current project
* @param workspaceSlug

View File

@@ -35,6 +35,7 @@
"clsx": "^2.0.0",
"cmdk": "^1.0.0",
"date-fns": "^2.30.0",
"dexie": "^4.0.8",
"dotenv": "^16.0.3",
"isomorphic-dompurify": "^2.12.0",
"js-cookie": "^3.0.1",
@@ -60,6 +61,7 @@
"smooth-scroll-into-view-if-needed": "^2.0.2",
"swr": "^2.1.3",
"tailwind-merge": "^2.0.0",
"ts-transformer-keys": "^0.4.4",
"use-debounce": "^9.0.4",
"use-font-face-observer": "^1.2.2",
"uuid": "^9.0.0",