fix: refactor related bugs (#3384)

* fix sub issues inside issue detail

* close peek over view after opening issue detail

* fix error while opening peek overview

* fix saving project views

---------

Co-authored-by: Rahul R <rahulr@Rahuls-MacBook-Pro.local>
This commit is contained in:
rahulramesha 2024-01-16 21:16:12 +05:30 committed by sriram veeraghanta
parent c4093d29a7
commit fadda7cf04
15 changed files with 105 additions and 124 deletions

View File

@ -51,7 +51,7 @@ export const IssueSubscription: FC<TIssueSubscription> = observer((props) => {
} }
}; };
if (issue?.created_by === currentUserId || issue?.assignee_ids.includes(currentUserId)) return <></>; if (issue?.created_by === currentUserId || issue?.assignee_ids?.includes(currentUserId)) return <></>;
return ( return (
<div> <div>

View File

@ -28,9 +28,9 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
project: { projectLabels }, project: { projectLabels },
} = useLabel(); } = useLabel();
const { projectStates } = useProjectState(); const { projectStates } = useProjectState();
const { getViewById, updateView } = useProjectView(); const { viewMap, updateView } = useProjectView();
// derived values // derived values
const viewDetails = viewId ? getViewById(viewId.toString()) : null; const viewDetails = viewId ? viewMap[viewId.toString()] : null;
const userFilters = issueFilters?.filters; const userFilters = issueFilters?.filters;
// filters whose value not null or empty array // filters whose value not null or empty array
const appliedFilters: IIssueFilterOptions = {}; const appliedFilters: IIssueFilterOptions = {};
@ -43,18 +43,30 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => { const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId) return;
if (!value) { if (!value) {
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { updateFilters(
[key]: null, workspaceSlug,
}); projectId,
EIssueFilterType.FILTERS,
{
[key]: null,
},
viewId
);
return; return;
} }
let newValues = issueFilters?.filters?.[key] ?? []; let newValues = issueFilters?.filters?.[key] ?? [];
newValues = newValues.filter((val) => val !== value); newValues = newValues.filter((val) => val !== value);
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { updateFilters(
[key]: newValues, workspaceSlug,
}); projectId,
EIssueFilterType.FILTERS,
{
[key]: newValues,
},
viewId
);
}; };
const handleClearAllFilters = () => { const handleClearAllFilters = () => {
@ -67,14 +79,14 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
}; };
// return if no filters are applied // return if no filters are applied
if (Object.keys(appliedFilters).length === 0) return null; if (Object.keys(appliedFilters).length === 0 && !areFiltersDifferent(appliedFilters, viewDetails?.filters ?? {}))
return null;
const handleUpdateView = () => { const handleUpdateView = () => {
if (!workspaceSlug || !projectId || !viewId || !viewDetails) return; if (!workspaceSlug || !projectId || !viewId || !viewDetails) return;
updateView(workspaceSlug.toString(), projectId.toString(), viewId.toString(), { updateView(workspaceSlug.toString(), projectId.toString(), viewId.toString(), {
query_data: { filters: {
...viewDetails.query_data,
...(appliedFilters ?? {}), ...(appliedFilters ?? {}),
}, },
}); });
@ -90,15 +102,13 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
states={projectStates} states={projectStates}
/> />
{appliedFilters && {viewDetails?.filters && areFiltersDifferent(appliedFilters, viewDetails?.filters ?? {}) && (
viewDetails?.query_data && <div className="flex flex-shrink-0 items-center justify-center">
areFiltersDifferent(appliedFilters, viewDetails?.query_data ?? {}) && ( <Button variant="primary" size="sm" onClick={handleUpdateView}>
<div className="flex flex-shrink-0 items-center justify-center"> Update view
<Button variant="primary" size="sm" onClick={handleUpdateView}> </Button>
Update view </div>
</Button> )}
</div>
)}
</div> </div>
); );
}); });

View File

@ -62,6 +62,18 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
if (workspaceSlug && projectId) fetchProjectLabels(workspaceSlug, projectId).then(() => setIsLoading(false)); if (workspaceSlug && projectId) fetchProjectLabels(workspaceSlug, projectId).then(() => setIsLoading(false));
}; };
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: placement ?? "bottom-start",
modifiers: [
{
name: "preventOverflow",
options: {
padding: 12,
},
},
],
});
if (!value) return null; if (!value) return null;
let projectLabels: IIssueLabel[] = defaultOptions; let projectLabels: IIssueLabel[] = defaultOptions;
@ -86,18 +98,6 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
const filteredOptions = const filteredOptions =
query === "" ? options : options?.filter((option) => option.query.toLowerCase().includes(query.toLowerCase())); query === "" ? options : options?.filter((option) => option.query.toLowerCase().includes(query.toLowerCase()));
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: placement ?? "bottom-start",
modifiers: [
{
name: "preventOverflow",
options: {
padding: 12,
},
},
],
});
const label = ( const label = (
<div className="flex h-5 w-full flex-wrap items-center gap-2 overflow-hidden text-custom-text-200"> <div className="flex h-5 w-full flex-wrap items-center gap-2 overflow-hidden text-custom-text-200">
{value.length > 0 ? ( {value.length > 0 ? (

View File

@ -20,7 +20,7 @@ export const SaveFilterView: FC<ISaveFilterView> = (props) => {
<CreateUpdateProjectViewModal <CreateUpdateProjectViewModal
workspaceSlug={workspaceSlug} workspaceSlug={workspaceSlug}
projectId={projectId} projectId={projectId}
preLoadedData={{ query_data: { ...filterParams } }} preLoadedData={{ filters: { ...filterParams } }}
isOpen={viewModal} isOpen={viewModal}
onClose={() => setViewModal(false)} onClose={() => setViewModal(false)}
/> />

View File

@ -84,6 +84,7 @@ export const IssueView: FC<IIssueView> = observer((props) => {
router.push({ router.push({
pathname: `/${workspaceSlug}/projects/${projectId}/${is_archived ? "archived-issues" : "issues"}/${issueId}`, pathname: `/${workspaceSlug}/projects/${projectId}/${is_archived ? "archived-issues" : "issues"}/${issueId}`,
}); });
removeRoutePeekId();
}; };
const handleCopyText = (e: React.MouseEvent<HTMLButtonElement>) => { const handleCopyText = (e: React.MouseEvent<HTMLButtonElement>) => {

View File

@ -80,8 +80,12 @@ export const IssueListItem: React.FC<ISubIssues> = observer((props) => {
) : ( ) : (
<div <div
className="flex h-full w-full cursor-pointer items-center justify-center rounded-sm transition-all hover:bg-custom-background-80" className="flex h-full w-full cursor-pointer items-center justify-center rounded-sm transition-all hover:bg-custom-background-80"
onClick={() => { onClick={async () => {
setSubIssueHelpers(parentIssueId, "preview_loader", issue.id); if (!subIssueHelpers.issue_visibility.includes(issue.id)) {
setSubIssueHelpers(parentIssueId, "preview_loader", issue.id);
await subIssueOperations.fetchSubIssues(workspaceSlug, projectId, issue.id);
setSubIssueHelpers(parentIssueId, "preview_loader", issue.id);
}
setSubIssueHelpers(parentIssueId, "issue_visibility", issue.id); setSubIssueHelpers(parentIssueId, "issue_visibility", issue.id);
}} }}
> >

View File

@ -7,7 +7,6 @@ import { IssueListItem } from "./issue-list-item";
// types // types
import { TIssue } from "@plane/types"; import { TIssue } from "@plane/types";
import { TSubIssueOperations } from "./root"; import { TSubIssueOperations } from "./root";
import useSWR from "swr";
export interface IIssueList { export interface IIssueList {
workspaceSlug: string; workspaceSlug: string;
@ -38,24 +37,12 @@ export const IssueList: FC<IIssueList> = observer((props) => {
subIssues: { subIssuesByIssueId, subIssueHelpersByIssueId }, subIssues: { subIssuesByIssueId, subIssueHelpersByIssueId },
} = useIssueDetail(); } = useIssueDetail();
useSWR(
workspaceSlug && projectId && parentIssueId
? `ISSUE_DETAIL_SUB_ISSUES_${workspaceSlug}_${projectId}_${parentIssueId}`
: null,
async () => {
workspaceSlug &&
projectId &&
parentIssueId &&
(await subIssueOperations.fetchSubIssues(workspaceSlug, projectId, parentIssueId));
}
);
const subIssueIds = subIssuesByIssueId(parentIssueId); const subIssueIds = subIssuesByIssueId(parentIssueId);
const subIssueHelpers = subIssueHelpersByIssueId(parentIssueId); const subIssueHelpers = subIssueHelpersByIssueId(parentIssueId);
return ( return (
<> <>
{subIssueHelpers.preview_loader.includes(parentIssueId) ? "Loading..." : "Hello"} {subIssueHelpers.preview_loader.includes(parentIssueId) ? "Loading..." : null}
<div className="relative"> <div className="relative">
{subIssueIds && {subIssueIds &&

View File

@ -1,6 +1,7 @@
import { FC, useMemo, useState } from "react"; import { FC, useMemo, useState } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Plus, ChevronRight, ChevronDown, Loader } from "lucide-react"; import { Plus, ChevronRight, ChevronDown, Loader } from "lucide-react";
import useSWR from "swr";
// hooks // hooks
import { useIssueDetail } from "hooks/store"; import { useIssueDetail } from "hooks/store";
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
@ -33,9 +34,7 @@ export type TSubIssueOperations = {
projectId: string, projectId: string,
parentIssueId: string, parentIssueId: string,
issueId: string, issueId: string,
currentIssue: Partial<TIssue>, data: Partial<TIssue>
oldIssue?: Partial<TIssue> | undefined,
fromModal?: boolean
) => Promise<void>; ) => Promise<void>;
removeSubIssue: (workspaceSlug: string, projectId: string, parentIssueId: string, issueId: string) => Promise<void>; removeSubIssue: (workspaceSlug: string, projectId: string, parentIssueId: string, issueId: string) => Promise<void>;
deleteSubIssue: (workspaceSlug: string, projectId: string, parentIssueId: string, issueId: string) => Promise<void>; deleteSubIssue: (workspaceSlug: string, projectId: string, parentIssueId: string, issueId: string) => Promise<void>;
@ -85,6 +84,18 @@ export const SubIssuesRoot: FC<ISubIssuesRoot> = observer((props) => {
}, },
}); });
useSWR(
workspaceSlug && projectId && parentIssueId
? `ISSUE_DETAIL_SUB_ISSUES_${workspaceSlug}_${projectId}_${parentIssueId}`
: null,
async () => {
workspaceSlug &&
projectId &&
parentIssueId &&
(await subIssueOperations.fetchSubIssues(workspaceSlug, projectId, parentIssueId));
}
);
const handleIssueCrudState = ( const handleIssueCrudState = (
key: "create" | "existing" | "update" | "delete", key: "create" | "existing" | "update" | "delete",
_parentIssueId: string | null, _parentIssueId: string | null,
@ -144,13 +155,11 @@ export const SubIssuesRoot: FC<ISubIssuesRoot> = observer((props) => {
projectId: string, projectId: string,
parentIssueId: string, parentIssueId: string,
issueId: string, issueId: string,
currentIssue: Partial<TIssue>, data: Partial<TIssue>
oldIssue: Partial<TIssue> | undefined = undefined,
fromModal: boolean = false
) => { ) => {
try { try {
setSubIssueHelpers(parentIssueId, "issue_loader", issueId); setSubIssueHelpers(parentIssueId, "issue_loader", issueId);
await updateSubIssue(workspaceSlug, projectId, parentIssueId, issueId, currentIssue, oldIssue, fromModal); await updateSubIssue(workspaceSlug, projectId, parentIssueId, issueId, data);
setToastAlert({ setToastAlert({
type: "success", type: "success",
title: "Sub-issue updated successfully", title: "Sub-issue updated successfully",
@ -386,15 +395,7 @@ export const SubIssuesRoot: FC<ISubIssuesRoot> = observer((props) => {
}} }}
data={issueCrudState?.update?.issue ?? undefined} data={issueCrudState?.update?.issue ?? undefined}
onSubmit={async (_issue: TIssue) => { onSubmit={async (_issue: TIssue) => {
await subIssueOperations.updateSubIssue( await subIssueOperations.updateSubIssue(workspaceSlug, projectId, parentIssueId, _issue.id, _issue);
workspaceSlug,
projectId,
parentIssueId,
_issue.id,
_issue,
issueCrudState?.update?.issue,
true
);
}} }}
/> />
</> </>

View File

@ -47,7 +47,7 @@ export const ProjectViewForm: React.FC<Props> = observer((props) => {
}); });
const selectedFilters: IIssueFilterOptions = {}; const selectedFilters: IIssueFilterOptions = {};
Object.entries(watch("query_data") ?? {}).forEach(([key, value]) => { Object.entries(watch("filters") ?? {}).forEach(([key, value]) => {
if (!value) return; if (!value) return;
if (Array.isArray(value) && value.length === 0) return; if (Array.isArray(value) && value.length === 0) return;
@ -59,7 +59,7 @@ export const ProjectViewForm: React.FC<Props> = observer((props) => {
const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => { const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
// If value is null then remove all the filters of that key // If value is null then remove all the filters of that key
if (!value) { if (!value) {
setValue("query_data", { setValue("filters", {
...selectedFilters, ...selectedFilters,
[key]: null, [key]: null,
}); });
@ -76,14 +76,18 @@ export const ProjectViewForm: React.FC<Props> = observer((props) => {
if (selectedFilters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1); if (selectedFilters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
} }
setValue("query_data", { setValue("filters", {
...selectedFilters, ...selectedFilters,
[key]: newValues, [key]: newValues,
}); });
}; };
const handleCreateUpdateView = async (formData: IProjectView) => { const handleCreateUpdateView = async (formData: IProjectView) => {
await handleFormSubmit(formData); await handleFormSubmit({
name: formData.name,
description: formData.description,
filters: formData.filters,
} as IProjectView);
reset({ reset({
...defaultValues, ...defaultValues,
@ -93,7 +97,7 @@ export const ProjectViewForm: React.FC<Props> = observer((props) => {
const clearAllFilters = () => { const clearAllFilters = () => {
if (!selectedFilters) return; if (!selectedFilters) return;
setValue("query_data", {}); setValue("filters", {});
}; };
useEffect(() => { useEffect(() => {
@ -156,7 +160,7 @@ export const ProjectViewForm: React.FC<Props> = observer((props) => {
<div> <div>
<Controller <Controller
control={control} control={control}
name="query_data" name="filters"
render={({ field: { onChange, value: filters } }) => ( render={({ field: { onChange, value: filters } }) => (
<FiltersDropdown title="Filters" tabIndex={3}> <FiltersDropdown title="Filters" tabIndex={3}>
<FilterSelection <FilterSelection

View File

@ -62,7 +62,7 @@ export const ProjectViewListItem: React.FC<Props> = observer((props) => {
}); });
}; };
const totalFilters = calculateTotalFilters(view.query_data ?? {}); const totalFilters = calculateTotalFilters(view.filters ?? {});
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER; const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;

View File

@ -94,7 +94,6 @@ export class IssueRelationStore implements IIssueRelationStore {
const relation_key = key as TIssueRelationTypes; const relation_key = key as TIssueRelationTypes;
const relation_issues = response[relation_key]; const relation_issues = response[relation_key];
const issues = relation_issues.flat().map((issue) => issue.issue_detail); const issues = relation_issues.flat().map((issue) => issue.issue_detail);
if (issues && issues.length > 0) this.rootIssueDetailStore.rootIssueStore.issues.addIssue(issues);
set( set(
this.relationMap, this.relationMap,
[issueId, relation_key], [issueId, relation_key],

View File

@ -208,11 +208,8 @@ export class IssueDetail implements IIssueDetail {
projectId: string, projectId: string,
parentIssueId: string, parentIssueId: string,
issueId: string, issueId: string,
oldIssue: Partial<TIssue>, data: Partial<TIssue>
currentIssue?: Partial<TIssue>, ) => this.subIssues.updateSubIssue(workspaceSlug, projectId, parentIssueId, issueId, data);
fromModal?: boolean
) =>
this.subIssues.updateSubIssue(workspaceSlug, projectId, parentIssueId, issueId, oldIssue, currentIssue, fromModal);
removeSubIssue = async (workspaceSlug: string, projectId: string, parentIssueId: string, issueId: string) => removeSubIssue = async (workspaceSlug: string, projectId: string, parentIssueId: string, issueId: string) =>
this.subIssues.removeSubIssue(workspaceSlug, projectId, parentIssueId, issueId); this.subIssues.removeSubIssue(workspaceSlug, projectId, parentIssueId, issueId);
deleteSubIssue = async (workspaceSlug: string, projectId: string, parentIssueId: string, issueId: string) => deleteSubIssue = async (workspaceSlug: string, projectId: string, parentIssueId: string, issueId: string) =>

View File

@ -28,9 +28,7 @@ export interface IIssueSubIssuesStoreActions {
projectId: string, projectId: string,
parentIssueId: string, parentIssueId: string,
issueId: string, issueId: string,
currentIssue: Partial<TIssue>, data: Partial<TIssue>
oldIssue?: Partial<TIssue> | undefined,
fromModal?: boolean
) => Promise<void>; ) => Promise<void>;
removeSubIssue: (workspaceSlug: string, projectId: string, parentIssueId: string, issueId: string) => Promise<void>; removeSubIssue: (workspaceSlug: string, projectId: string, parentIssueId: string, issueId: string) => Promise<void>;
deleteSubIssue: (workspaceSlug: string, projectId: string, parentIssueId: string, issueId: string) => Promise<void>; deleteSubIssue: (workspaceSlug: string, projectId: string, parentIssueId: string, issueId: string) => Promise<void>;
@ -118,16 +116,15 @@ export class IssueSubIssuesStore implements IIssueSubIssuesStore {
this.rootIssueDetailStore.rootIssueStore.issues.addIssue(subIssues); this.rootIssueDetailStore.rootIssueStore.issues.addIssue(subIssues);
if (subIssues.length > 0) { runInAction(() => {
runInAction(() => { set(this.subIssuesStateDistribution, parentIssueId, subIssuesStateDistribution);
set(this.subIssuesStateDistribution, parentIssueId, subIssuesStateDistribution); set(
set( this.subIssues,
this.subIssues, parentIssueId,
parentIssueId, subIssues.map((issue) => issue.id)
subIssues.map((issue) => issue.id) );
); });
});
}
return response; return response;
} catch (error) { } catch (error) {
throw error; throw error;
@ -172,39 +169,16 @@ export class IssueSubIssuesStore implements IIssueSubIssuesStore {
projectId: string, projectId: string,
parentIssueId: string, parentIssueId: string,
issueId: string, issueId: string,
currentIssue: Partial<TIssue>, data: Partial<TIssue>
oldIssue: Partial<TIssue> | undefined = undefined,
fromModal: boolean = false
) => { ) => {
try { try {
if (!fromModal) await this.rootIssueDetailStore.rootIssueStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data);
await this.rootIssueDetailStore.rootIssueStore.projectIssues.updateIssue(
workspaceSlug,
projectId,
issueId,
currentIssue
);
if (!oldIssue) return; if (data.hasOwnProperty("parent_id") && data.parent_id !== parentIssueId) {
runInAction(() => {
if (currentIssue.state_id != oldIssue.state_id) { pull(this.subIssues[parentIssueId], issueId);
});
} }
if (currentIssue.parent_id != oldIssue.parent_id) {
}
// const updateResponse = await this.rootIssueDetailStore.rootIssueStore.projectIssues.updateIssue(
// workspaceSlug,
// projectId,
// issueId,
// oldIssue
// );
// console.log("---");
// console.log("parentIssueId", parentIssueId);
// console.log("fromModal", fromModal);
// console.log("updateResponse", updateResponse);
// console.log("---");
return; return;
} catch (error) { } catch (error) {
throw error; throw error;

View File

@ -139,7 +139,7 @@ export class ProjectViewIssuesFilter extends IssueFilterHelperStore implements I
try { try {
if (!viewId) throw new Error("View id is required"); if (!viewId) throw new Error("View id is required");
if (isEmpty(this.filters) || isEmpty(this.filters[projectId]) || isEmpty(filters)) return; if (isEmpty(this.filters) || isEmpty(this.filters[viewId]) || isEmpty(filters)) return;
const _filters = { const _filters = {
filters: this.filters[viewId].filters as IIssueFilterOptions, filters: this.filters[viewId].filters as IIssueFilterOptions,

View File

@ -130,11 +130,15 @@ export class ProjectViewIssues extends IssueHelperStore implements IProjectViewI
const response = await this.issueService.getIssues(workspaceSlug, projectId, params); const response = await this.issueService.getIssues(workspaceSlug, projectId, params);
runInAction(() => { runInAction(() => {
set(this.issues, [viewId], Object.keys(response)); set(
this.issues,
[viewId],
response.map((issue) => issue.id)
);
this.loader = undefined; this.loader = undefined;
}); });
this.rootIssueStore.issues.addIssue(Object.values(response)); this.rootIssueStore.issues.addIssue(response);
return response; return response;
} catch (error) { } catch (error) {