[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:
rahulramesha 2024-09-24 14:27:57 +05:30 committed by GitHub
parent 5ca794b648
commit 6170a80757
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 105 additions and 66 deletions

View file

@ -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 (
<>

View file

@ -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(() => {

View file

@ -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;

View file

@ -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>

View file

@ -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}

View file

@ -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}

View file

@ -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

View file

@ -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}

View file

@ -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));

View file

@ -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;

View file

@ -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);

View file

@ -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);

View file

@ -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;

View file

@ -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);