fix: archived issues and minor bug fixes (#3451)

* make computedFn without optional arguments

* fix archived issues

* fix activity changes with proper context

* fix display filters that require server side filtering
This commit is contained in:
rahulramesha 2024-01-24 18:50:54 +05:30 committed by GitHub
parent 338d58f79d
commit 6a2be6afc4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 118 additions and 39 deletions

View File

@ -94,7 +94,7 @@ const EstimatePoint = observer((props: { point: string }) => {
const { areEstimatesEnabledForCurrentProject, getEstimatePointValue } = useEstimate();
const currentPoint = Number(point) + 1;
const estimateValue = getEstimatePointValue(Number(point));
const estimateValue = getEstimatePointValue(Number(point), null);
return (
<span className="font-medium text-custom-text-100">
@ -142,8 +142,18 @@ const activityDetails: {
},
archived_at: {
message: (activity) => {
if (activity.new_value === "restore") return "restored the issue.";
else return "archived the issue.";
if (activity.new_value === "restore")
return (
<>
restored <IssueLink activity={activity} />
</>
);
else
return (
<>
archived <IssueLink activity={activity} />
</>
);
},
icon: <ArchiveIcon size={12} color="#6b7280" aria-hidden="true" />,
},
@ -229,8 +239,18 @@ const activityDetails: {
},
issue: {
message: (activity) => {
if (activity.verb === "created") return "created the issue.";
else return "deleted an issue.";
if (activity.verb === "created")
return (
<>
created <IssueLink activity={activity} />
</>
);
else
return (
<>
deleted <IssueLink activity={activity} />
</>
);
},
icon: <LayersIcon width={12} height={12} color="#6b7280" aria-hidden="true" />,
},
@ -371,7 +391,7 @@ const activityDetails: {
else
return (
<>
removed the issue from the cycle{" "}
removed <IssueLink activity={activity} /> from the cycle{" "}
<a
href={`/${workspaceSlug}/projects/${activity.project}/cycles/${activity.old_identifier}`}
target="_blank"
@ -418,7 +438,7 @@ const activityDetails: {
else
return (
<>
removed the issue from the module{" "}
removed <IssueLink activity={activity} /> from the module{" "}
<a
href={`/${workspaceSlug}/projects/${activity.project}/modules/${activity.old_identifier}`}
target="_blank"

View File

@ -20,7 +20,7 @@ export const IssueEstimateActivity: FC<TIssueEstimateActivity> = observer((props
if (!activity) return <></>;
const estimateValue = getEstimatePointValue(Number(activity.new_value));
const estimateValue = getEstimatePointValue(Number(activity.new_value), null);
const currentPoint = Number(activity.new_value) + 1;
return (

View File

@ -18,12 +18,11 @@ type Props = {
projectId: string;
issueId: string;
issueOperations: TIssueOperations;
is_archived: boolean;
is_editable: boolean;
};
export const IssueMainContent: React.FC<Props> = observer((props) => {
const { workspaceSlug, projectId, issueId, issueOperations, is_archived, is_editable } = props;
const { workspaceSlug, projectId, issueId, issueOperations, is_editable } = props;
// states
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
// hooks

View File

@ -217,8 +217,7 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = (props) => {
projectId={projectId}
issueId={issueId}
issueOperations={issueOperations}
is_archived={is_archived}
is_editable={is_editable}
is_editable={!is_archived && is_editable}
/>
</div>
<div className="h-full w-1/3 space-y-5 overflow-hidden border-l border-custom-border-300 py-5">
@ -228,7 +227,7 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = (props) => {
issueId={issueId}
issueOperations={issueOperations}
is_archived={is_archived}
is_editable={is_editable}
is_editable={!is_archived && is_editable}
/>
</div>
</div>

View File

@ -28,6 +28,8 @@ export const ArchivedIssueListLayout: FC = observer(() => {
[issues, workspaceSlug, projectId]
);
const canEditPropertiesBasedOnProject = () => false;
return (
<BaseListRoot
issuesFilter={issuesFilter}
@ -35,6 +37,7 @@ export const ArchivedIssueListLayout: FC = observer(() => {
QuickActions={ArchivedIssueQuickActions}
issueActions={issueActions}
storeType={EIssuesStoreType.PROJECT}
canEditPropertiesBasedOnProject={canEditPropertiesBasedOnProject}
/>
);
});

View File

@ -53,13 +53,16 @@ export const AllIssueLayoutRoot: React.FC = observer(() => {
}
);
const canEditProperties = (projectId: string | undefined) => {
if (!projectId) return false;
const canEditProperties = useCallback(
(projectId: string | undefined) => {
if (!projectId) return false;
const currentProjectRole = currentWorkspaceAllProjectsRole && currentWorkspaceAllProjectsRole[projectId];
const currentProjectRole = currentWorkspaceAllProjectsRole && currentWorkspaceAllProjectsRole[projectId];
return !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
};
return !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
},
[currentWorkspaceAllProjectsRole]
);
const issueFilters = globalViewId ? filters?.[globalViewId.toString()] : undefined;

View File

@ -5,7 +5,12 @@ import useSWR from "swr";
// mobx store
import { useIssues } from "hooks/store";
// components
import { ArchivedIssueListLayout, ArchivedIssueAppliedFiltersRoot, ProjectEmptyState } from "components/issues";
import {
ArchivedIssueListLayout,
ArchivedIssueAppliedFiltersRoot,
ProjectEmptyState,
IssuePeekOverview,
} from "components/issues";
import { EIssuesStoreType } from "constants/issue";
// ui
import { Spinner } from "@plane/ui";
@ -46,9 +51,14 @@ export const ArchivedIssueLayoutRoot: React.FC = observer(() => {
// TODO: Replace this with project view empty state
<ProjectEmptyState />
) : (
<div className="relative h-full w-full overflow-auto">
<ArchivedIssueListLayout />
</div>
<>
<div className="relative h-full w-full overflow-auto">
<ArchivedIssueListLayout />
</div>
{/* peek overview */}
<IssuePeekOverview is_archived />
</>
)}
</>
)}

View File

@ -10,7 +10,6 @@ interface IPeekOverviewIssueDetails {
projectId: string;
issueId: string;
issueOperations: TIssueOperations;
is_archived: boolean;
disabled: boolean;
isSubmitting: "submitting" | "submitted" | "saved";
setIsSubmitting: (value: "submitting" | "submitted" | "saved") => void;

View File

@ -208,7 +208,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
issueId={peekIssue.issueId}
isLoading={isLoading}
is_archived={is_archived}
disabled={!is_editable}
disabled={is_archived || !is_editable}
issueOperations={issueOperations}
/>
</Fragment>

View File

@ -64,14 +64,13 @@ export const IssueView: FC<IIssueView> = observer((props) => {
// ref
const issuePeekOverviewRef = useRef<HTMLDivElement>(null);
// store hooks
const { activity, setPeekIssue, isAnyModalOpen, isDeleteIssueModalOpen, toggleDeleteIssueModal } = useIssueDetail();
const { setPeekIssue, isAnyModalOpen, isDeleteIssueModalOpen, toggleDeleteIssueModal } = useIssueDetail();
const { currentUser } = useUser();
const {
issue: { getIssueById },
} = useIssueDetail();
const { setToastAlert } = useToast();
// derived values
const issueActivity = activity.getActivitiesByIssueId(issueId);
const currentMode = PEEK_OPTIONS.find((m) => m.key === peekMode);
const issue = getIssueById(issueId);
@ -213,15 +212,11 @@ export const IssueView: FC<IIssueView> = observer((props) => {
<>
{["side-peek", "modal"].includes(peekMode) ? (
<div className="relative flex flex-col gap-3 px-8 py-5">
{is_archived && (
<div className="absolute left-0 top-0 z-[9] flex h-full min-h-full w-full items-center justify-center bg-custom-background-100 opacity-60" />
)}
<PeekOverviewIssueDetails
workspaceSlug={workspaceSlug}
projectId={projectId}
issueId={issueId}
issueOperations={issueOperations}
is_archived={is_archived}
disabled={disabled}
isSubmitting={isSubmitting}
setIsSubmitting={(value) => setIsSubmitting(value)}
@ -243,15 +238,14 @@ export const IssueView: FC<IIssueView> = observer((props) => {
/>
</div>
) : (
<div className={`flex h-full w-full overflow-auto ${is_archived ? "opacity-60" : ""}`}>
<div className={`flex h-full w-full overflow-auto`}>
<div className="relative h-full w-full space-y-6 overflow-auto p-4 py-5">
<div className={is_archived ? "pointer-events-none" : ""}>
<div>
<PeekOverviewIssueDetails
workspaceSlug={workspaceSlug}
projectId={projectId}
issueId={issueId}
issueOperations={issueOperations}
is_archived={is_archived}
disabled={disabled}
isSubmitting={isSubmitting}
setIsSubmitting={(value) => setIsSubmitting(value)}

View File

@ -59,6 +59,7 @@ const IssueDetailsPage: NextPageWithLayout = observer(() => {
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
issueId={issueId.toString()}
is_archived={!!issue?.archived_at}
/>
)
)}

View File

@ -18,7 +18,7 @@ export interface IEstimateStore {
activeEstimateDetails: IEstimate | null;
// computed actions
areEstimatesEnabledForProject: (projectId: string) => boolean;
getEstimatePointValue: (estimateKey: number | null, projectId?: string) => string;
getEstimatePointValue: (estimateKey: number | null, projectId: string | null) => string;
getProjectEstimateById: (estimateId: string) => IEstimate | null;
getProjectActiveEstimateDetails: (projectId: string) => IEstimate | null;
// fetch actions
@ -109,7 +109,7 @@ export class EstimateStore implements IEstimateStore {
/**
* @description returns the point value for the given estimate key to display in the UI
*/
getEstimatePointValue = computedFn((estimateKey: number | null, projectId?: string) => {
getEstimatePointValue = computedFn((estimateKey: number | null, projectId: string | null) => {
if (estimateKey === null) return "None";
const activeEstimate = projectId ? this.getProjectActiveEstimateDetails(projectId) : this.activeEstimateDetails;
return activeEstimate?.points?.find((point) => point.key === estimateKey)?.value || "None";

View File

@ -194,6 +194,9 @@ export class ArchivedIssuesFilter extends IssueFilterHelperStore implements IArc
});
});
if (this.requiresServerUpdate(updatedDisplayFilters))
this.rootIssueStore.archivedIssues.fetchIssues(workspaceSlug, projectId, "mutation");
this.handleIssuesLocalFilters.set(EIssuesStoreType.ARCHIVED, type, workspaceSlug, projectId, undefined, {
display_filters: _filters.displayFilters,
});

View File

@ -90,11 +90,15 @@ export class ArchivedIssues extends IssueHelperStore implements IArchivedIssues
const response = await this.archivedIssueService.getArchivedIssues(workspaceSlug, projectId, params);
runInAction(() => {
set(this.issues, [projectId], Object.keys(response));
set(
this.issues,
[projectId],
response.map((issue: TIssue) => issue.id)
);
this.loader = undefined;
});
this.rootIssueStore.issues.addIssue(Object.values(response));
this.rootIssueStore.issues.addIssue(response);
return response;
} catch (error) {

View File

@ -205,6 +205,9 @@ export class CycleIssuesFilter extends IssueFilterHelperStore implements ICycleI
});
});
if (this.requiresServerUpdate(updatedDisplayFilters))
this.rootIssueStore.cycleIssues.fetchIssues(workspaceSlug, projectId, "mutation", cycleId);
await this.issueFilterService.patchCycleIssueFilters(workspaceSlug, projectId, cycleId, {
display_filters: _filters.displayFilters,
});
@ -259,3 +262,5 @@ export class CycleIssuesFilter extends IssueFilterHelperStore implements ICycleI
}
};
}

View File

@ -189,6 +189,9 @@ export class DraftIssuesFilter extends IssueFilterHelperStore implements IDraftI
});
});
if (this.requiresServerUpdate(updatedDisplayFilters))
this.rootIssueStore.draftIssues.fetchIssues(workspaceSlug, projectId, "mutation");
this.handleIssuesLocalFilters.set(EIssuesStoreType.DRAFT, type, workspaceSlug, projectId, undefined, {
display_filters: _filters.displayFilters,
});

View File

@ -183,6 +183,20 @@ export class IssueFilterHelperStore implements IIssueFilterHelperStore {
updated_on: displayProperties?.updated_on ?? true,
});
/**
* This Method returns true if the display properties changed requires a server side update
* @param displayFilters
* @returns
*/
requiresServerUpdate = (displayFilters: IIssueDisplayFilterOptions) => {
const SERVER_DISPLAY_FILTERS = ["sub_issue", "type"];
const displayFilterKeys = Object.keys(displayFilters);
return SERVER_DISPLAY_FILTERS.some((serverDisplayfilter: string) =>
displayFilterKeys.includes(serverDisplayfilter)
);
};
handleIssuesLocalFilters = {
fetchFiltersFromStorage: () => {
const _filters = storage.get("issue_local_filters");

View File

@ -9,7 +9,7 @@ export interface IIssueKanBanViewStore {
subgroupByIssuesVisibility: string[];
};
// computed
getCanUserDragDrop: (order_by: string | null, group_by: string | null, sub_group_by?: string | null) => boolean;
getCanUserDragDrop: (group_by: string | null, sub_group_by: string | null) => boolean;
canUserDragDropVertically: boolean;
canUserDragDropHorizontally: boolean;
// actions
@ -38,7 +38,7 @@ export class IssueKanBanViewStore implements IIssueKanBanViewStore {
this.rootStore = _rootStore;
}
getCanUserDragDrop = computedFn((group_by: string | null, sub_group_by?: string | null) => {
getCanUserDragDrop = computedFn((group_by: string | null, sub_group_by: string | null) => {
if (group_by && ["state", "priority"].includes(group_by)) {
if (!sub_group_by) return true;
if (sub_group_by && ["state", "priority"].includes(sub_group_by)) return true;

View File

@ -204,6 +204,9 @@ export class ModuleIssuesFilter extends IssueFilterHelperStore implements IModul
});
});
if (this.requiresServerUpdate(updatedDisplayFilters))
this.rootIssueStore.moduleIssues.fetchIssues(workspaceSlug, projectId, "mutation", moduleId);
await this.issueFilterService.patchModuleIssueFilters(workspaceSlug, projectId, moduleId, {
display_filters: _filters.displayFilters,
});

View File

@ -199,6 +199,15 @@ export class ProfileIssuesFilter extends IssueFilterHelperStore implements IProf
});
});
if (this.requiresServerUpdate(updatedDisplayFilters))
this.rootIssueStore.profileIssues.fetchIssues(
workspaceSlug,
undefined,
"mutation",
userId,
this.rootIssueStore.profileIssues.currentView
);
this.handleIssuesLocalFilters.set(EIssuesStoreType.PROFILE, type, workspaceSlug, userId, undefined, {
display_filters: _filters.displayFilters,
});

View File

@ -203,6 +203,9 @@ export class ProjectViewIssuesFilter extends IssueFilterHelperStore implements I
});
});
if (this.requiresServerUpdate(updatedDisplayFilters))
this.rootIssueStore.projectViewIssues.fetchIssues(workspaceSlug, projectId, "mutation", viewId);
await this.issueFilterService.patchView(workspaceSlug, projectId, viewId, {
display_filters: _filters.displayFilters,
});

View File

@ -201,6 +201,9 @@ export class ProjectIssuesFilter extends IssueFilterHelperStore implements IProj
});
});
if (this.requiresServerUpdate(updatedDisplayFilters))
this.rootIssueStore.projectIssues.fetchIssues(workspaceSlug, projectId, "mutation");
await this.issueFilterService.patchProjectIssueFilters(workspaceSlug, projectId, {
display_filters: _filters.displayFilters,
});

View File

@ -222,6 +222,10 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo
);
});
});
if (this.requiresServerUpdate(updatedDisplayFilters))
this.rootIssueStore.workspaceIssues.fetchIssues(workspaceSlug, viewId, "mutation");
if (["all-issues", "assigned", "created", "subscribed"].includes(viewId))
this.handleIssuesLocalFilters.set(EIssuesStoreType.GLOBAL, type, workspaceSlug, undefined, viewId, {
display_filters: _filters.displayFilters,