[WEB-2001] chore: Code refactor for noload changes. (#5683)
* use common getIssues from issue service instead of multiple different services for modules and cycles * add group by to server constants * change issue detail's overview's is loading logic to the loader from the store * add extra method in local storage * Kanban render 10 issues by default per column * fix height in group virtualization * remove debounced code for Kanban fetching more issues per column * fix lint errors
This commit is contained in:
parent
5ca794b648
commit
6170a80757
15 changed files with 105 additions and 66 deletions
|
|
@ -19,12 +19,12 @@ const ArchivedIssueDetailsPage = observer(() => {
|
|||
// hooks
|
||||
const {
|
||||
fetchIssue,
|
||||
issue: { getIssueById },
|
||||
issue: { getIssueById, isFetchingIssueDetails },
|
||||
} = useIssueDetail();
|
||||
|
||||
const { getProjectById } = useProject();
|
||||
|
||||
const { isLoading } = useSWR(
|
||||
useSWR(
|
||||
workspaceSlug && projectId && archivedIssueId
|
||||
? `ARCHIVED_ISSUE_DETAIL_${workspaceSlug}_${projectId}_${archivedIssueId}`
|
||||
: null,
|
||||
|
|
@ -40,7 +40,7 @@ const ArchivedIssueDetailsPage = observer(() => {
|
|||
|
||||
if (!issue) return <></>;
|
||||
|
||||
const issueLoader = !issue || isLoading ? true : false;
|
||||
const issueLoader = !issue || isFetchingIssueDetails ? true : false;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -27,12 +27,12 @@ const IssueDetailsPage = observer(() => {
|
|||
// store hooks
|
||||
const {
|
||||
fetchIssue,
|
||||
issue: { getIssueById },
|
||||
issue: { getIssueById, isFetchingIssueDetails },
|
||||
} = useIssueDetail();
|
||||
const { getProjectById } = useProject();
|
||||
const { toggleIssueDetailSidebar, issueDetailSidebarCollapsed } = useAppTheme();
|
||||
// fetching issue details
|
||||
const { isLoading, error } = useSWR(
|
||||
const { error } = useSWR(
|
||||
workspaceSlug && projectId && issueId ? `ISSUE_DETAIL_${workspaceSlug}_${projectId}_${issueId}` : null,
|
||||
workspaceSlug && projectId && issueId
|
||||
? () => fetchIssue(workspaceSlug.toString(), projectId.toString(), issueId.toString())
|
||||
|
|
@ -41,7 +41,7 @@ const IssueDetailsPage = observer(() => {
|
|||
// derived values
|
||||
const issue = getIssueById(issueId?.toString() || "") || undefined;
|
||||
const project = (issue?.project_id && getProjectById(issue?.project_id)) || undefined;
|
||||
const issueLoader = !issue || isLoading ? true : false;
|
||||
const issueLoader = !issue || isFetchingIssueDetails ? true : false;
|
||||
const pageTitle = project && issue ? `${project?.identifier}-${issue?.sequence_id} ${issue?.name}` : undefined;
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@
|
|||
import { FC } from "react";
|
||||
// emoji-picker-react
|
||||
import { Emoji } from "emoji-picker-react";
|
||||
// import { icons } from "lucide-react";
|
||||
import useFontFaceObserver from "use-font-face-observer";
|
||||
import { TLogoProps } from "@plane/types";
|
||||
// helpers
|
||||
import { LUCIDE_ICONS_LIST } from "@plane/ui";
|
||||
import { emojiCodeToUnicode } from "@/helpers/emoji.helper";
|
||||
// import { icons } from "lucide-react";
|
||||
import useFontFaceObserver from "use-font-face-observer";
|
||||
|
||||
type Props = {
|
||||
logo: TLogoProps;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import { FC, useCallback, useEffect, useRef, useState } from "react";
|
|||
import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
|
||||
import { dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
||||
import { autoScrollForElements } from "@atlaskit/pragmatic-drag-and-drop-auto-scroll/element";
|
||||
import debounce from "lodash/debounce";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
import { DeleteIssueModal } from "@/components/issues";
|
||||
|
|
@ -93,12 +92,6 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
|||
[fetchNextIssues]
|
||||
);
|
||||
|
||||
const debouncedFetchMoreIssues = debounce(
|
||||
(groupId?: string, subgroupId?: string) => fetchMoreIssues(groupId, subgroupId),
|
||||
300,
|
||||
{ leading: true, trailing: false }
|
||||
);
|
||||
|
||||
const groupedIssueIds = issues?.groupedIssueIds;
|
||||
|
||||
const userDisplayFilters = displayFilters || null;
|
||||
|
|
@ -275,7 +268,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
|||
addIssuesToView={addIssuesToView}
|
||||
scrollableContainerRef={scrollableContainerRef}
|
||||
handleOnDrop={handleOnDrop}
|
||||
loadMoreIssues={debouncedFetchMoreIssues}
|
||||
loadMoreIssues={fetchMoreIssues}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ interface IssueBlockProps {
|
|||
quickActions: TRenderQuickActions;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
|
||||
shouldRenderByDefault?: boolean;
|
||||
}
|
||||
|
||||
interface IssueDetailsBlockProps {
|
||||
|
|
@ -114,6 +115,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = observer((props) => {
|
|||
quickActions,
|
||||
canEditProperties,
|
||||
scrollableContainerRef,
|
||||
shouldRenderByDefault,
|
||||
} = props;
|
||||
|
||||
const cardRef = useRef<HTMLAnchorElement | null>(null);
|
||||
|
|
@ -222,6 +224,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = observer((props) => {
|
|||
defaultHeight="100px"
|
||||
horizontalOffset={100}
|
||||
verticalOffset={200}
|
||||
defaultValue={shouldRenderByDefault}
|
||||
>
|
||||
<KanbanIssueDetailsBlock
|
||||
cardRef={cardRef}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = observer((p
|
|||
<>
|
||||
{issueIds && issueIds.length > 0 ? (
|
||||
<>
|
||||
{issueIds.map((issueId) => {
|
||||
{issueIds.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}
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
|||
verticalOffset={100}
|
||||
horizontalOffset={100}
|
||||
root={scrollableContainerRef}
|
||||
classNames="relative h-full"
|
||||
classNames="h-full min-h-[120px]"
|
||||
defaultHeight={`${groupHeight}px`}
|
||||
placeholderChildren={
|
||||
<KanbanColumnLoader
|
||||
|
|
|
|||
|
|
@ -35,14 +35,13 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
|||
const {
|
||||
peekIssue,
|
||||
setPeekIssue,
|
||||
issue: { fetchIssue },
|
||||
issue: { fetchIssue, isFetchingIssueDetails },
|
||||
fetchActivities,
|
||||
} = useIssueDetail();
|
||||
|
||||
const { issues } = useIssuesStore();
|
||||
const { captureIssueEvent } = useEventTracker();
|
||||
// state
|
||||
const [loader, setLoader] = useState(true);
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
const removeRoutePeekId = () => {
|
||||
|
|
@ -54,7 +53,6 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
|||
() => ({
|
||||
fetch: async (workspaceSlug: string, projectId: string, issueId: string, loader = true) => {
|
||||
try {
|
||||
setLoader(loader);
|
||||
setError(false);
|
||||
await fetchIssue(
|
||||
workspaceSlug,
|
||||
|
|
@ -62,10 +60,8 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
|||
issueId,
|
||||
is_archived ? "ARCHIVED" : is_draft ? "DRAFT" : "DEFAULT"
|
||||
);
|
||||
setLoader(false);
|
||||
setError(false);
|
||||
} catch (error) {
|
||||
setLoader(false);
|
||||
setError(true);
|
||||
console.error("Error fetching the parent issue");
|
||||
}
|
||||
|
|
@ -348,7 +344,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
|||
workspaceSlug={peekIssue.workspaceSlug}
|
||||
projectId={peekIssue.projectId}
|
||||
issueId={peekIssue.issueId}
|
||||
isLoading={loader}
|
||||
isLoading={isFetchingIssueDetails}
|
||||
isError={error}
|
||||
is_archived={is_archived}
|
||||
disabled={!isEditable}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useState, useEffect, useCallback } from "react";
|
||||
|
||||
const getValueFromLocalStorage = (key: string, defaultValue: any) => {
|
||||
export const getValueFromLocalStorage = (key: string, defaultValue: any) => {
|
||||
if (typeof window === undefined || typeof window === "undefined") return defaultValue;
|
||||
try {
|
||||
const item = window.localStorage.getItem(key);
|
||||
|
|
@ -11,6 +11,16 @@ const getValueFromLocalStorage = (key: string, defaultValue: any) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const setValueIntoLocalStorage = (key: string, value: any) => {
|
||||
if (typeof window === undefined || typeof window === "undefined") return false;
|
||||
try {
|
||||
window.localStorage.setItem(key, JSON.stringify(value));
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const useLocalStorage = <T,>(key: string, initialValue: T) => {
|
||||
const [storedValue, setStoredValue] = useState<T | null>(() => getValueFromLocalStorage(key, initialValue));
|
||||
|
||||
|
|
|
|||
|
|
@ -119,7 +119,12 @@ export class CycleIssuesFilter extends IssueFilterHelperStore implements ICycleI
|
|||
groupId: string | undefined,
|
||||
subGroupId: string | undefined
|
||||
) => {
|
||||
const filterParams = this.getAppliedFilters(cycleId);
|
||||
let filterParams = this.getAppliedFilters(cycleId);
|
||||
|
||||
if (!filterParams) {
|
||||
filterParams = {};
|
||||
}
|
||||
filterParams["cycle"] = cycleId;
|
||||
|
||||
const paginationParams = this.getPaginationParams(filterParams, options, cursor, groupId, subGroupId);
|
||||
return paginationParams;
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ export class CycleIssues extends BaseIssuesStore implements ICycleIssues {
|
|||
// 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, {
|
||||
const response = await this.issueService.getIssues(workspaceSlug, projectId, params, {
|
||||
signal: this.controller.signal,
|
||||
});
|
||||
|
||||
|
|
@ -233,7 +233,7 @@ export class CycleIssues extends BaseIssuesStore implements ICycleIssues {
|
|||
subGroupId
|
||||
);
|
||||
// call the fetch issues API with the params for next page in issues
|
||||
const response = await this.cycleService.getCycleIssues(workspaceSlug, projectId, cycleId, params);
|
||||
const response = await this.issueService.getIssues(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);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { makeObservable } from "mobx";
|
||||
import { makeObservable, observable } from "mobx";
|
||||
import { computedFn } from "mobx-utils";
|
||||
// types
|
||||
import { TIssue } from "@plane/types";
|
||||
|
|
@ -32,11 +32,13 @@ export interface IIssueStoreActions {
|
|||
}
|
||||
|
||||
export interface IIssueStore extends IIssueStoreActions {
|
||||
isFetchingIssueDetails: boolean;
|
||||
// helper methods
|
||||
getIssueById: (issueId: string) => TIssue | undefined;
|
||||
}
|
||||
|
||||
export class IssueStore implements IIssueStore {
|
||||
isFetchingIssueDetails: boolean = false;
|
||||
// root store
|
||||
rootIssueDetailStore: IIssueDetail;
|
||||
// services
|
||||
|
|
@ -45,7 +47,9 @@ export class IssueStore implements IIssueStore {
|
|||
issueDraftService;
|
||||
|
||||
constructor(rootStore: IIssueDetail) {
|
||||
makeObservable(this, {});
|
||||
makeObservable(this, {
|
||||
isFetchingIssueDetails: observable.ref,
|
||||
});
|
||||
// root store
|
||||
this.rootIssueDetailStore = rootStore;
|
||||
// services
|
||||
|
|
@ -66,7 +70,9 @@ export class IssueStore implements IIssueStore {
|
|||
expand: "issue_reactions,issue_attachment,issue_link,parent",
|
||||
};
|
||||
|
||||
let issue: TIssue;
|
||||
let issue: TIssue | undefined;
|
||||
|
||||
this.isFetchingIssueDetails = true;
|
||||
|
||||
if (issueType === "ARCHIVED")
|
||||
issue = await this.issueArchiveService.retrieveArchivedIssue(workspaceSlug, projectId, issueId, query);
|
||||
|
|
@ -76,38 +82,7 @@ export class IssueStore implements IIssueStore {
|
|||
|
||||
if (!issue) throw new Error("Issue not found");
|
||||
|
||||
const issuePayload: TIssue = {
|
||||
id: issue?.id,
|
||||
sequence_id: issue?.sequence_id,
|
||||
name: issue?.name,
|
||||
description_html: issue?.description_html,
|
||||
sort_order: issue?.sort_order,
|
||||
state_id: issue?.state_id,
|
||||
priority: issue?.priority,
|
||||
label_ids: issue?.label_ids,
|
||||
assignee_ids: issue?.assignee_ids,
|
||||
estimate_point: issue?.estimate_point,
|
||||
sub_issues_count: issue?.sub_issues_count,
|
||||
attachment_count: issue?.attachment_count,
|
||||
link_count: issue?.link_count,
|
||||
project_id: issue?.project_id,
|
||||
parent_id: issue?.parent_id,
|
||||
cycle_id: issue?.cycle_id,
|
||||
module_ids: issue?.module_ids,
|
||||
type_id: issue?.type_id,
|
||||
created_at: issue?.created_at,
|
||||
updated_at: issue?.updated_at,
|
||||
start_date: issue?.start_date,
|
||||
target_date: issue?.target_date,
|
||||
completed_at: issue?.completed_at,
|
||||
archived_at: issue?.archived_at,
|
||||
created_by: issue?.created_by,
|
||||
updated_by: issue?.updated_by,
|
||||
is_draft: issue?.is_draft,
|
||||
is_subscribed: issue?.is_subscribed,
|
||||
};
|
||||
|
||||
this.rootIssueDetailStore.rootIssueStore.issues.addIssue([issuePayload]);
|
||||
this.addIssueToStore(issue);
|
||||
|
||||
// store handlers from issue detail
|
||||
// parent
|
||||
|
|
@ -150,6 +125,44 @@ export class IssueStore implements IIssueStore {
|
|||
return issue;
|
||||
};
|
||||
|
||||
addIssueToStore = (issue: TIssue) => {
|
||||
const issuePayload: TIssue = {
|
||||
id: issue?.id,
|
||||
sequence_id: issue?.sequence_id,
|
||||
name: issue?.name,
|
||||
description_html: issue?.description_html,
|
||||
sort_order: issue?.sort_order,
|
||||
state_id: issue?.state_id,
|
||||
priority: issue?.priority,
|
||||
label_ids: issue?.label_ids,
|
||||
assignee_ids: issue?.assignee_ids,
|
||||
estimate_point: issue?.estimate_point,
|
||||
sub_issues_count: issue?.sub_issues_count,
|
||||
attachment_count: issue?.attachment_count,
|
||||
link_count: issue?.link_count,
|
||||
project_id: issue?.project_id,
|
||||
parent_id: issue?.parent_id,
|
||||
cycle_id: issue?.cycle_id,
|
||||
module_ids: issue?.module_ids,
|
||||
type_id: issue?.type_id,
|
||||
created_at: issue?.created_at,
|
||||
updated_at: issue?.updated_at,
|
||||
start_date: issue?.start_date,
|
||||
target_date: issue?.target_date,
|
||||
completed_at: issue?.completed_at,
|
||||
archived_at: issue?.archived_at,
|
||||
created_by: issue?.created_by,
|
||||
updated_by: issue?.updated_by,
|
||||
is_draft: issue?.is_draft,
|
||||
is_subscribed: issue?.is_subscribed,
|
||||
};
|
||||
|
||||
this.rootIssueDetailStore.rootIssueStore.issues.addIssue([issuePayload]);
|
||||
this.isFetchingIssueDetails = false;
|
||||
|
||||
return issuePayload;
|
||||
};
|
||||
|
||||
updateIssue = async (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => {
|
||||
await this.rootIssueDetailStore.rootIssueStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data);
|
||||
await this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId);
|
||||
|
|
|
|||
|
|
@ -119,7 +119,12 @@ export class ModuleIssuesFilter extends IssueFilterHelperStore implements IModul
|
|||
groupId: string | undefined,
|
||||
subGroupId: string | undefined
|
||||
) => {
|
||||
const filterParams = this.getAppliedFilters(moduleId);
|
||||
let filterParams = this.getAppliedFilters(moduleId);
|
||||
|
||||
if (!filterParams) {
|
||||
filterParams = {};
|
||||
}
|
||||
filterParams["module"] = moduleId;
|
||||
|
||||
const paginationParams = this.getPaginationParams(filterParams, options, cursor, groupId, subGroupId);
|
||||
return paginationParams;
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ export class ModuleIssues extends BaseIssuesStore implements IModuleIssues {
|
|||
// 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, {
|
||||
const response = await this.issueService.getIssues(workspaceSlug, projectId, params, {
|
||||
signal: this.controller.signal,
|
||||
});
|
||||
|
||||
|
|
@ -190,7 +190,7 @@ export class ModuleIssues extends BaseIssuesStore implements IModuleIssues {
|
|||
subGroupId
|
||||
);
|
||||
// call the fetch issues API with the params for next page in issues
|
||||
const response = await this.moduleService.getModuleIssues(workspaceSlug, projectId, moduleId, params);
|
||||
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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue