forked from github/plane
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:
parent
338d58f79d
commit
6a2be6afc4
@ -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"
|
||||
|
@ -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 (
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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)}
|
||||
|
@ -59,6 +59,7 @@ const IssueDetailsPage: NextPageWithLayout = observer(() => {
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={projectId.toString()}
|
||||
issueId={issueId.toString()}
|
||||
is_archived={!!issue?.archived_at}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
|
@ -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";
|
||||
|
@ -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,
|
||||
});
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
@ -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,
|
||||
});
|
||||
|
@ -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");
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
});
|
||||
|
@ -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,
|
||||
});
|
||||
|
@ -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,
|
||||
});
|
||||
|
@ -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,
|
||||
});
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user