chore: code refactor and build fix (#6285)
* chore: code refactor and build fix * chore: code refactor * chore: code refactor
This commit is contained in:
parent
3c6bbaef3c
commit
211d5e1cd0
33 changed files with 292 additions and 101 deletions
|
|
@ -11,7 +11,6 @@ import { PROJECT_ERROR_MESSAGES } from "@/constants/project";
|
|||
import { useIssues, useProject, useUser, useUserPermissions } from "@/hooks/store";
|
||||
// plane-web
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
handleClose: () => void;
|
||||
|
|
@ -19,10 +18,11 @@ type Props = {
|
|||
data?: TIssue | TDeDupeIssue;
|
||||
isSubIssue?: boolean;
|
||||
onSubmit?: () => Promise<void>;
|
||||
isEpic?: boolean;
|
||||
};
|
||||
|
||||
export const DeleteIssueModal: React.FC<Props> = (props) => {
|
||||
const { dataId, data, isOpen, handleClose, isSubIssue = false, onSubmit } = props;
|
||||
const { dataId, data, isOpen, handleClose, isSubIssue = false, onSubmit, isEpic = false } = props;
|
||||
// states
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
// store hooks
|
||||
|
|
@ -70,12 +70,14 @@ export const DeleteIssueModal: React.FC<Props> = (props) => {
|
|||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Success!",
|
||||
message: `${isSubIssue ? "Sub-issue" : "Issue"} deleted successfully`,
|
||||
message: `${isSubIssue ? "Sub-issue" : isEpic ? "Epic" : "Issue"} deleted successfully`,
|
||||
});
|
||||
onClose();
|
||||
})
|
||||
.catch((errors) => {
|
||||
const isPermissionError = errors?.error === "Only admin or creator can delete the issue";
|
||||
const isPermissionError =
|
||||
errors?.error ===
|
||||
`Only admin or creator can delete the ${isSubIssue ? "sub-issue" : isEpic ? "epic" : "issue"}`;
|
||||
const currentError = isPermissionError
|
||||
? PROJECT_ERROR_MESSAGES.permissionError
|
||||
: PROJECT_ERROR_MESSAGES.issueDeleteError;
|
||||
|
|
@ -94,14 +96,14 @@ export const DeleteIssueModal: React.FC<Props> = (props) => {
|
|||
handleSubmit={handleIssueDelete}
|
||||
isSubmitting={isDeleting}
|
||||
isOpen={isOpen}
|
||||
title="Delete issue"
|
||||
title={`Delete ${isEpic ? "epic" : "issue"}`}
|
||||
content={
|
||||
<>
|
||||
Are you sure you want to delete issue{" "}
|
||||
{`Are you sure you want to delete ${isEpic ? "epic" : "issue"} `}
|
||||
<span className="break-words font-medium text-custom-text-100">
|
||||
{projectDetails?.identifier}-{issue?.sequence_id}
|
||||
</span>
|
||||
{""}? All of the data related to the issue will be permanently removed. This action cannot be undone.
|
||||
{` ? All of the data related to the ${isEpic ? "epic" : "issue"} will be permanently removed. This action cannot be undone.`}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -123,6 +123,7 @@ const HeaderFilters = observer((props: Props) => {
|
|||
states={projectStates}
|
||||
cycleViewDisabled={!currentProjectDetails?.cycle_view}
|
||||
moduleViewDisabled={!currentProjectDetails?.module_view}
|
||||
isEpic={storeType === EIssuesStoreType.EPIC}
|
||||
/>
|
||||
</FiltersDropdown>
|
||||
<FiltersDropdown title="Display" placement="bottom-end">
|
||||
|
|
@ -134,6 +135,7 @@ const HeaderFilters = observer((props: Props) => {
|
|||
handleDisplayPropertiesUpdate={handleDisplayProperties}
|
||||
cycleViewDisabled={!currentProjectDetails?.cycle_view}
|
||||
moduleViewDisabled={!currentProjectDetails?.module_view}
|
||||
isEpic={storeType === EIssuesStoreType.EPIC}
|
||||
/>
|
||||
</FiltersDropdown>
|
||||
{canUserCreateIssue ? (
|
||||
|
|
|
|||
|
|
@ -53,11 +53,10 @@ export const SubIssuesCollapsibleContent: FC<Props> = observer((props) => {
|
|||
},
|
||||
});
|
||||
// store hooks
|
||||
const { toggleCreateIssueModal, toggleDeleteIssueModal } = useIssueDetail();
|
||||
const {
|
||||
subIssues: { subIssueHelpersByIssueId, setSubIssueHelpers },
|
||||
toggleCreateIssueModal,
|
||||
toggleDeleteIssueModal,
|
||||
} = useIssueDetail();
|
||||
} = useIssueDetail(issueServiceType);
|
||||
|
||||
// helpers
|
||||
const subIssueOperations = useSubIssueOperations(issueServiceType);
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export const SubIssuesCollapsibleTitle: FC<Props> = observer((props) => {
|
|||
return (
|
||||
<CollapsibleButton
|
||||
isOpen={isOpen}
|
||||
title="Sub-issues"
|
||||
title={`${issueServiceType === EIssueServiceType.EPICS ? "Issues" : "Sub-issues"}`}
|
||||
indicatorElement={
|
||||
<div className="flex items-center gap-1.5 text-custom-text-300 text-sm">
|
||||
<CircularProgressIndicator size={18} percentage={percentage} strokeWidth={3} />
|
||||
|
|
|
|||
|
|
@ -9,18 +9,24 @@ import { cn } from "@/helpers/common.helper";
|
|||
export type TActivitySortRoot = {
|
||||
sortOrder: "asc" | "desc";
|
||||
toggleSort: () => void;
|
||||
className?: string;
|
||||
iconClassName?: string;
|
||||
};
|
||||
export const ActivitySortRoot: FC<TActivitySortRoot> = memo((props) => (
|
||||
<div
|
||||
className={cn(getButtonStyling("neutral-primary", "sm"), "px-2 text-custom-text-300 cursor-pointer")}
|
||||
className={cn(
|
||||
getButtonStyling("neutral-primary", "sm"),
|
||||
"px-2 text-custom-text-300 cursor-pointer",
|
||||
props.className
|
||||
)}
|
||||
onClick={() => {
|
||||
props.toggleSort();
|
||||
}}
|
||||
>
|
||||
{props.sortOrder === "asc" ? (
|
||||
<ArrowUpWideNarrow className="size-4 " />
|
||||
<ArrowUpWideNarrow className={cn("size-4", props.iconClassName)} />
|
||||
) : (
|
||||
<ArrowDownWideNarrow className="size-4 " />
|
||||
<ArrowDownWideNarrow className={cn("size-4", props.iconClassName)} />
|
||||
)}
|
||||
</div>
|
||||
));
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
|
|||
const { workspaceSlug } = useParams();
|
||||
|
||||
// hooks
|
||||
const storeType = useIssueStoreType() as CalendarStoreType;
|
||||
const storeType = isEpic ? EIssuesStoreType.EPIC : (useIssueStoreType() as CalendarStoreType);
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const { issues, issuesFilter, issueMap } = useIssues(storeType);
|
||||
const {
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
|
|||
}}
|
||||
quickAddCallback={quickAddCallback}
|
||||
addIssuesToView={addIssuesToView}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ type Props = {
|
|||
ignoreGroupedFilters?: Partial<TIssueGroupByOptions>[];
|
||||
cycleViewDisabled?: boolean;
|
||||
moduleViewDisabled?: boolean;
|
||||
isEpic?: boolean;
|
||||
};
|
||||
|
||||
export const DisplayFiltersSelection: React.FC<Props> = observer((props) => {
|
||||
|
|
@ -37,6 +38,7 @@ export const DisplayFiltersSelection: React.FC<Props> = observer((props) => {
|
|||
ignoreGroupedFilters = [],
|
||||
cycleViewDisabled = false,
|
||||
moduleViewDisabled = false,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
|
||||
const isDisplayFilterEnabled = (displayFilter: keyof IIssueDisplayFilterOptions) =>
|
||||
|
|
@ -61,6 +63,7 @@ export const DisplayFiltersSelection: React.FC<Props> = observer((props) => {
|
|||
handleUpdate={handleDisplayPropertiesUpdate}
|
||||
cycleViewDisabled={cycleViewDisabled}
|
||||
moduleViewDisabled={moduleViewDisabled}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ type Props = {
|
|||
handleUpdate: (updatedDisplayProperties: Partial<IIssueDisplayProperties>) => void;
|
||||
cycleViewDisabled?: boolean;
|
||||
moduleViewDisabled?: boolean;
|
||||
isEpic?: boolean;
|
||||
};
|
||||
|
||||
export const FilterDisplayProperties: React.FC<Props> = observer((props) => {
|
||||
|
|
@ -25,6 +26,7 @@ export const FilterDisplayProperties: React.FC<Props> = observer((props) => {
|
|||
handleUpdate,
|
||||
cycleViewDisabled = false,
|
||||
moduleViewDisabled = false,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
// router
|
||||
const { workspaceSlug, projectId: routerProjectId } = useParams();
|
||||
|
|
@ -45,6 +47,11 @@ export const FilterDisplayProperties: React.FC<Props> = observer((props) => {
|
|||
default:
|
||||
return shouldRenderDisplayProperty({ workspaceSlug: workspaceSlug?.toString(), projectId, key: property.key });
|
||||
}
|
||||
}).map((property) => {
|
||||
if (isEpic && property.key === "sub_issue_count") {
|
||||
return { ...property, title: "Issue count" };
|
||||
}
|
||||
return property;
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -11,10 +11,11 @@ import { ISSUE_FILTER_OPTIONS } from "@/constants/issue";
|
|||
type Props = {
|
||||
selectedIssueType: TIssueGroupingFilters | undefined;
|
||||
handleUpdate: (val: TIssueGroupingFilters) => void;
|
||||
isEpic?: boolean;
|
||||
};
|
||||
|
||||
export const FilterIssueGrouping: React.FC<Props> = observer((props) => {
|
||||
const { selectedIssueType, handleUpdate } = props;
|
||||
const { selectedIssueType, handleUpdate, isEpic = false } = props;
|
||||
|
||||
const [previewEnabled, setPreviewEnabled] = React.useState(true);
|
||||
|
||||
|
|
@ -23,7 +24,7 @@ export const FilterIssueGrouping: React.FC<Props> = observer((props) => {
|
|||
return (
|
||||
<>
|
||||
<FilterHeader
|
||||
title="Issue Grouping"
|
||||
title={`${isEpic ? "Epic" : "Issue"} Grouping`}
|
||||
isPreviewEnabled={previewEnabled}
|
||||
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
|
||||
/>
|
||||
|
|
@ -34,7 +35,7 @@ export const FilterIssueGrouping: React.FC<Props> = observer((props) => {
|
|||
key={issueType?.key}
|
||||
isChecked={activeIssueType === issueType?.key ? true : false}
|
||||
onClick={() => handleUpdate(issueType?.key)}
|
||||
title={issueType.title}
|
||||
title={`${issueType.title} ${isEpic ? "Epics" : "Issues"}`}
|
||||
multiple={false}
|
||||
/>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ type Props = {
|
|||
states?: IState[] | undefined;
|
||||
cycleViewDisabled?: boolean;
|
||||
moduleViewDisabled?: boolean;
|
||||
isEpic?: boolean;
|
||||
};
|
||||
|
||||
export const FilterSelection: React.FC<Props> = observer((props) => {
|
||||
|
|
@ -56,6 +57,7 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
|
|||
states,
|
||||
cycleViewDisabled = false,
|
||||
moduleViewDisabled = false,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
// hooks
|
||||
const { isMobile } = usePlatformOS();
|
||||
|
|
@ -234,6 +236,7 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
|
|||
type: val,
|
||||
})
|
||||
}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
|
|||
target_date: renderFormattedPayloadDate(targetDate),
|
||||
}}
|
||||
quickAddCallback={quickAddIssue}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
) : undefined;
|
||||
|
||||
|
|
@ -120,8 +121,8 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
|
|||
<div className="h-full w-full">
|
||||
<GanttChartRoot
|
||||
border={false}
|
||||
title="Issues"
|
||||
loaderTitle="Issues"
|
||||
title={isEpic ? "Epics" : "Issues"}
|
||||
loaderTitle={isEpic ? "Epics" : "Issues"}
|
||||
blockIds={issuesIds}
|
||||
blockUpdateHandler={updateIssueBlockStructure}
|
||||
blockToRender={(data: TIssue) => <IssueGanttBlock issueId={data.id} isEpic={isEpic} />}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ type Props = {
|
|||
dropErrorMessage?: string;
|
||||
orderBy: TIssueOrderByOptions | undefined;
|
||||
isDraggingOverColumn: boolean;
|
||||
isEpic?: boolean;
|
||||
};
|
||||
|
||||
export const GroupDragOverlay = (props: Props) => {
|
||||
|
|
@ -27,6 +28,7 @@ export const GroupDragOverlay = (props: Props) => {
|
|||
dropErrorMessage,
|
||||
orderBy,
|
||||
isDraggingOverColumn,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
|
||||
const shouldOverlayBeVisible = isDraggingOverColumn && canOverlayBeVisible;
|
||||
|
|
@ -68,7 +70,7 @@ export const GroupDragOverlay = (props: Props) => {
|
|||
The layout is ordered by <span className="font-semibold">{readableOrderBy}</span>.
|
||||
</span>
|
||||
)}
|
||||
<span>Drop here to move the issue.</span>
|
||||
<span>{`Drop here to move the ${isEpic ? "epic" : "issue"}.`}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -284,6 +284,7 @@ export const ListGroup = observer((props: Props) => {
|
|||
dropErrorMessage={group.dropErrorMessage}
|
||||
orderBy={orderBy}
|
||||
isDraggingOverColumn={isDraggingOverColumn}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
{groupIssueIds && (
|
||||
<IssueBlocksList
|
||||
|
|
@ -312,6 +313,7 @@ export const ListGroup = observer((props: Props) => {
|
|||
prePopulatedData={prePopulateQuickAddData(group_by, group.id)}
|
||||
containerClassName="border-b border-t border-custom-border-200 bg-custom-background-100 "
|
||||
quickAddCallback={quickAddCallback}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useCallback, useMemo, SyntheticEvent } from "react";
|
||||
import xor from "lodash/xor";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
|
|
@ -245,7 +245,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||
|
||||
const redirectToIssueDetail = () => {
|
||||
router.push(
|
||||
`/${workspaceSlug}/projects/${issue.project_id}/${issue.archived_at ? "archives/" : ""}issues/${issue.id}#sub-issues`
|
||||
`/${workspaceSlug}/projects/${issue.project_id}/${issue.archived_at ? "archives/" : ""}${isEpic ? "epics" : "issues"}/${issue.id}#sub-issues`
|
||||
);
|
||||
// router.push({
|
||||
// pathname: `/${workspaceSlug}/projects/${issue.project_id}/${issue.archived_at ? "archives/" : ""}issues/${
|
||||
|
|
@ -265,7 +265,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||
const maxDate = getDate(issue.target_date);
|
||||
maxDate?.setDate(maxDate.getDate());
|
||||
|
||||
const handleEventPropagation = (e: React.MouseEvent) => {
|
||||
const handleEventPropagation = (e: SyntheticEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
};
|
||||
|
|
@ -275,7 +275,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||
{/* basic properties */}
|
||||
{/* state */}
|
||||
<WithDisplayPropertiesHOC displayProperties={displayProperties} displayPropertyKey="state">
|
||||
<div className="h-5" onClick={handleEventPropagation}>
|
||||
<div className="h-5" onFocus={handleEventPropagation} onClick={handleEventPropagation}>
|
||||
<StateDropdown
|
||||
buttonContainerClassName="truncate max-w-40"
|
||||
value={issue.state_id}
|
||||
|
|
@ -291,7 +291,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||
|
||||
{/* priority */}
|
||||
<WithDisplayPropertiesHOC displayProperties={displayProperties} displayPropertyKey="priority">
|
||||
<div className="h-5" onClick={handleEventPropagation}>
|
||||
<div className="h-5" onFocus={handleEventPropagation} onClick={handleEventPropagation}>
|
||||
<PriorityDropdown
|
||||
value={issue?.priority}
|
||||
onChange={handlePriority}
|
||||
|
|
@ -306,7 +306,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||
|
||||
{/* start date */}
|
||||
<WithDisplayPropertiesHOC displayProperties={displayProperties} displayPropertyKey="start_date">
|
||||
<div className="h-5" onClick={handleEventPropagation}>
|
||||
<div className="h-5" onFocus={handleEventPropagation} onClick={handleEventPropagation}>
|
||||
<DateDropdown
|
||||
value={issue.start_date ?? null}
|
||||
onChange={handleStartDate}
|
||||
|
|
@ -324,7 +324,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||
|
||||
{/* target/due date */}
|
||||
<WithDisplayPropertiesHOC displayProperties={displayProperties} displayPropertyKey="due_date">
|
||||
<div className="h-5" onClick={handleEventPropagation}>
|
||||
<div className="h-5" onFocus={handleEventPropagation} onClick={handleEventPropagation}>
|
||||
<DateDropdown
|
||||
value={issue?.target_date ?? null}
|
||||
onChange={handleTargetDate}
|
||||
|
|
@ -344,7 +344,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||
|
||||
{/* assignee */}
|
||||
<WithDisplayPropertiesHOC displayProperties={displayProperties} displayPropertyKey="assignee">
|
||||
<div className="h-5" onClick={handleEventPropagation}>
|
||||
<div className="h-5" onFocus={handleEventPropagation} onClick={handleEventPropagation}>
|
||||
<MemberDropdown
|
||||
projectId={issue?.project_id}
|
||||
value={issue?.assignee_ids}
|
||||
|
|
@ -362,52 +362,54 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||
</div>
|
||||
</WithDisplayPropertiesHOC>
|
||||
|
||||
{!isEpic && (
|
||||
<>
|
||||
{/* modules */}
|
||||
{projectDetails?.module_view && (
|
||||
<WithDisplayPropertiesHOC displayProperties={displayProperties} displayPropertyKey="modules">
|
||||
<div className="h-5" onClick={handleEventPropagation}>
|
||||
<ModuleDropdown
|
||||
buttonContainerClassName="truncate max-w-40"
|
||||
projectId={issue?.project_id}
|
||||
value={issue?.module_ids ?? []}
|
||||
onChange={handleModule}
|
||||
disabled={isReadOnly}
|
||||
renderByDefault={isMobile}
|
||||
multiple
|
||||
buttonVariant="border-with-text"
|
||||
showCount
|
||||
showTooltip
|
||||
/>
|
||||
</div>
|
||||
</WithDisplayPropertiesHOC>
|
||||
)}
|
||||
<>
|
||||
{!isEpic && (
|
||||
<>
|
||||
{/* modules */}
|
||||
{projectDetails?.module_view && (
|
||||
<WithDisplayPropertiesHOC displayProperties={displayProperties} displayPropertyKey="modules">
|
||||
<div className="h-5" onFocus={handleEventPropagation} onClick={handleEventPropagation}>
|
||||
<ModuleDropdown
|
||||
buttonContainerClassName="truncate max-w-40"
|
||||
projectId={issue?.project_id}
|
||||
value={issue?.module_ids ?? []}
|
||||
onChange={handleModule}
|
||||
disabled={isReadOnly}
|
||||
renderByDefault={isMobile}
|
||||
multiple
|
||||
buttonVariant="border-with-text"
|
||||
showCount
|
||||
showTooltip
|
||||
/>
|
||||
</div>
|
||||
</WithDisplayPropertiesHOC>
|
||||
)}
|
||||
|
||||
{/* cycles */}
|
||||
{projectDetails?.cycle_view && (
|
||||
<WithDisplayPropertiesHOC displayProperties={displayProperties} displayPropertyKey="cycle">
|
||||
<div className="h-5" onClick={handleEventPropagation}>
|
||||
<CycleDropdown
|
||||
buttonContainerClassName="truncate max-w-40"
|
||||
projectId={issue?.project_id}
|
||||
value={issue?.cycle_id}
|
||||
onChange={handleCycle}
|
||||
disabled={isReadOnly}
|
||||
buttonVariant="border-with-text"
|
||||
renderByDefault={isMobile}
|
||||
showTooltip
|
||||
/>
|
||||
</div>
|
||||
</WithDisplayPropertiesHOC>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{/* cycles */}
|
||||
{projectDetails?.cycle_view && (
|
||||
<WithDisplayPropertiesHOC displayProperties={displayProperties} displayPropertyKey="cycle">
|
||||
<div className="h-5" onFocus={handleEventPropagation} onClick={handleEventPropagation}>
|
||||
<CycleDropdown
|
||||
buttonContainerClassName="truncate max-w-40"
|
||||
projectId={issue?.project_id}
|
||||
value={issue?.cycle_id}
|
||||
onChange={handleCycle}
|
||||
disabled={isReadOnly}
|
||||
buttonVariant="border-with-text"
|
||||
renderByDefault={isMobile}
|
||||
showTooltip
|
||||
/>
|
||||
</div>
|
||||
</WithDisplayPropertiesHOC>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
||||
{/* estimates */}
|
||||
{projectId && areEstimateEnabledByProjectId(projectId?.toString()) && (
|
||||
<WithDisplayPropertiesHOC displayProperties={displayProperties} displayPropertyKey="estimate">
|
||||
<div className="h-5" onClick={handleEventPropagation}>
|
||||
<div className="h-5" onFocus={handleEventPropagation} onClick={handleEventPropagation}>
|
||||
<EstimateDropdown
|
||||
value={issue.estimate_point ?? undefined}
|
||||
onChange={handleEstimate}
|
||||
|
|
@ -429,12 +431,13 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||
shouldRenderProperty={(properties) => !!properties.sub_issue_count && !!subIssueCount}
|
||||
>
|
||||
<Tooltip
|
||||
tooltipHeading="Sub-issues"
|
||||
tooltipHeading={isEpic ? "Issues" : "Sub-issues"}
|
||||
tooltipContent={`${subIssueCount}`}
|
||||
isMobile={isMobile}
|
||||
renderByDefault={false}
|
||||
>
|
||||
<div
|
||||
onFocus={handleEventPropagation}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
|
@ -467,6 +470,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||
>
|
||||
<div
|
||||
className="flex h-5 flex-shrink-0 items-center justify-center gap-2 overflow-hidden rounded border-[0.5px] border-custom-border-300 px-2.5 py-1"
|
||||
onFocus={handleEventPropagation}
|
||||
onClick={handleEventPropagation}
|
||||
>
|
||||
<Paperclip className="h-3 w-3 flex-shrink-0" strokeWidth={2} />
|
||||
|
|
@ -489,6 +493,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||
>
|
||||
<div
|
||||
className="flex h-5 flex-shrink-0 items-center justify-center gap-2 overflow-hidden rounded border-[0.5px] border-custom-border-300 px-2.5 py-1"
|
||||
onFocus={handleEventPropagation}
|
||||
onClick={handleEventPropagation}
|
||||
>
|
||||
<Link className="h-3 w-3 flex-shrink-0" strokeWidth={2} />
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { Row } from "@plane/ui";
|
|||
import { TQuickAddIssueButton } from "../root";
|
||||
|
||||
export const GanttQuickAddIssueButton: FC<TQuickAddIssueButton> = observer((props) => {
|
||||
const { onClick } = props;
|
||||
const { onClick, isEpic = false } = props;
|
||||
|
||||
return (
|
||||
<button
|
||||
|
|
@ -15,7 +15,7 @@ export const GanttQuickAddIssueButton: FC<TQuickAddIssueButton> = observer((prop
|
|||
>
|
||||
<Row className="flex py-2 gap-2">
|
||||
<PlusIcon className="h-3.5 w-3.5 stroke-2 my-auto" />
|
||||
<span className="text-sm font-medium">New Issue</span>
|
||||
<span className="text-sm font-medium">{`New ${isEpic ? "Epic" : "Issue"}`}</span>
|
||||
</Row>
|
||||
</button>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { PlusIcon } from "lucide-react";
|
|||
import { TQuickAddIssueButton } from "../root";
|
||||
|
||||
export const KanbanQuickAddIssueButton: FC<TQuickAddIssueButton> = observer((props) => {
|
||||
const { onClick } = props;
|
||||
const { onClick, isEpic = false } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -12,7 +12,7 @@ export const KanbanQuickAddIssueButton: FC<TQuickAddIssueButton> = observer((pro
|
|||
onClick={onClick}
|
||||
>
|
||||
<PlusIcon className="h-3.5 w-3.5 stroke-2" />
|
||||
<span className="text-sm font-medium">New Issue</span>
|
||||
<span className="text-sm font-medium">{`New ${isEpic ? "Epic" : "Issue"}`}</span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { Row } from "@plane/ui";
|
|||
import { TQuickAddIssueButton } from "../root";
|
||||
|
||||
export const ListQuickAddIssueButton: FC<TQuickAddIssueButton> = observer((props) => {
|
||||
const { onClick } = props;
|
||||
const { onClick, isEpic = false } = props;
|
||||
|
||||
return (
|
||||
<Row
|
||||
|
|
@ -13,7 +13,7 @@ export const ListQuickAddIssueButton: FC<TQuickAddIssueButton> = observer((props
|
|||
onClick={onClick}
|
||||
>
|
||||
<PlusIcon className="h-3.5 w-3.5 stroke-2" />
|
||||
<span className="text-sm font-medium">New Issue</span>
|
||||
<span className="text-sm font-medium">{`New ${isEpic ? "Epic" : "Issue"}`}</span>
|
||||
</Row>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { PlusIcon } from "lucide-react";
|
|||
import { TQuickAddIssueButton } from "../root";
|
||||
|
||||
export const SpreadsheetAddIssueButton: FC<TQuickAddIssueButton> = observer((props) => {
|
||||
const { onClick } = props;
|
||||
const { onClick, isEpic = false } = props;
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
|
|
@ -14,7 +14,7 @@ export const SpreadsheetAddIssueButton: FC<TQuickAddIssueButton> = observer((pro
|
|||
onClick={onClick}
|
||||
>
|
||||
<PlusIcon className="h-3.5 w-3.5 stroke-2" />
|
||||
<span className="text-sm font-medium">New Issue</span>
|
||||
<span className="text-sm font-medium">{`New ${isEpic ? "Epic" : "Issue"}`}</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -15,10 +15,11 @@ interface Props {
|
|||
displayFilters: IIssueDisplayFilterOptions;
|
||||
handleDisplayFilterUpdate: (data: Partial<IIssueDisplayFilterOptions>) => void;
|
||||
onClose: () => void;
|
||||
isEpic?: boolean;
|
||||
}
|
||||
|
||||
export const HeaderColumn = (props: Props) => {
|
||||
const { displayFilters, handleDisplayFilterUpdate, property, onClose } = props;
|
||||
const { displayFilters, handleDisplayFilterUpdate, property, onClose, isEpic = false } = props;
|
||||
|
||||
const { storedValue: selectedMenuItem, setValue: setSelectedMenuItem } = useLocalStorage(
|
||||
"spreadsheetViewSorting",
|
||||
|
|
@ -46,7 +47,7 @@ export const HeaderColumn = (props: Props) => {
|
|||
<Row className="flex w-full cursor-pointer items-center justify-between gap-1.5 py-2 text-sm text-custom-text-200 hover:text-custom-text-100">
|
||||
<div className="flex items-center gap-1.5">
|
||||
{<propertyDetails.icon className="h-4 w-4 text-custom-text-400" />}
|
||||
{propertyDetails.title}
|
||||
{propertyDetails.title === "Sub-issue" && isEpic ? "Issues" : propertyDetails.title}
|
||||
</div>
|
||||
<div className="ml-3 flex">
|
||||
{activeSortingProperty === property && (
|
||||
|
|
|
|||
|
|
@ -18,16 +18,19 @@ export const SpreadsheetSubIssueColumn: React.FC<Props> = observer((props: Props
|
|||
// router
|
||||
const router = useAppRouter();
|
||||
// hooks
|
||||
const { workspaceSlug } = useParams();
|
||||
const { workspaceSlug, epicId } = useParams();
|
||||
// derived values
|
||||
const subIssueCount = issue?.sub_issues_count ?? 0;
|
||||
|
||||
const redirectToIssueDetail = () => {
|
||||
router.push(
|
||||
`/${workspaceSlug?.toString()}/projects/${issue.project_id}/${issue.archived_at ? "archives/" : ""}issues/${issue.id}#sub-issues`
|
||||
`/${workspaceSlug?.toString()}/projects/${issue.project_id}/${issue.archived_at ? "archives/" : ""}${epicId ? "epics" : "issues"}/${issue.id}#sub-issues`
|
||||
);
|
||||
};
|
||||
|
||||
const issueLabel = epicId ? "issue" : "sub-issue";
|
||||
const label = `${subIssueCount} ${issueLabel}${subIssueCount !== 1 ? "s" : ""}`;
|
||||
|
||||
return (
|
||||
<Row
|
||||
onClick={subIssueCount ? redirectToIssueDetail : () => {}}
|
||||
|
|
@ -38,7 +41,7 @@ export const SpreadsheetSubIssueColumn: React.FC<Props> = observer((props: Props
|
|||
}
|
||||
)}
|
||||
>
|
||||
{subIssueCount} {subIssueCount === 1 ? "sub-issue" : "sub-issues"}
|
||||
{label}
|
||||
</Row>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,9 +12,17 @@ interface Props {
|
|||
isEstimateEnabled: boolean;
|
||||
displayFilters: IIssueDisplayFilterOptions;
|
||||
handleDisplayFilterUpdate: (data: Partial<IIssueDisplayFilterOptions>) => void;
|
||||
isEpic?: boolean;
|
||||
}
|
||||
export const SpreadsheetHeaderColumn = observer((props: Props) => {
|
||||
const { displayProperties, displayFilters, property, isEstimateEnabled, handleDisplayFilterUpdate } = props;
|
||||
const {
|
||||
displayProperties,
|
||||
displayFilters,
|
||||
property,
|
||||
isEstimateEnabled,
|
||||
handleDisplayFilterUpdate,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
|
||||
//hooks
|
||||
const tableHeaderCellRef = useRef<HTMLTableCellElement | null>(null);
|
||||
|
|
@ -39,6 +47,7 @@ export const SpreadsheetHeaderColumn = observer((props: Props) => {
|
|||
onClose={() => {
|
||||
tableHeaderCellRef?.current?.focus();
|
||||
}}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
</th>
|
||||
</WithDisplayPropertiesHOC>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ interface Props {
|
|||
isEstimateEnabled: boolean;
|
||||
spreadsheetColumnsList: (keyof IIssueDisplayProperties)[];
|
||||
selectionHelpers: TSelectionHelper;
|
||||
isEpic?: boolean;
|
||||
}
|
||||
|
||||
export const SpreadsheetHeader = observer((props: Props) => {
|
||||
|
|
@ -32,6 +33,7 @@ export const SpreadsheetHeader = observer((props: Props) => {
|
|||
isEstimateEnabled,
|
||||
spreadsheetColumnsList,
|
||||
selectionHelpers,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
// router
|
||||
const { projectId } = useParams();
|
||||
|
|
@ -62,7 +64,7 @@ export const SpreadsheetHeader = observer((props: Props) => {
|
|||
/>
|
||||
</div>
|
||||
)}
|
||||
<span className="flex h-full w-full flex-grow items-center py-2.5">Issues</span>
|
||||
<span className="flex h-full w-full flex-grow items-center py-2.5">{`${isEpic ? "Epics" : "Issues"}`}</span>
|
||||
</Row>
|
||||
</th>
|
||||
|
||||
|
|
@ -74,6 +76,7 @@ export const SpreadsheetHeader = observer((props: Props) => {
|
|||
displayFilters={displayFilters}
|
||||
handleDisplayFilterUpdate={handleDisplayFilterUpdate}
|
||||
isEstimateEnabled={isEstimateEnabled}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
))}
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -112,6 +112,7 @@ export const SpreadsheetTable = observer((props: Props) => {
|
|||
isEstimateEnabled={isEstimateEnabled}
|
||||
spreadsheetColumnsList={spreadsheetColumnsList}
|
||||
selectionHelpers={selectionHelpers}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
<tbody>
|
||||
{issueIds.map((id) => (
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
|
|||
layout={EIssueLayoutTypes.SPREADSHEET}
|
||||
QuickAddButton={SpreadsheetAddIssueButton}
|
||||
quickAddCallback={quickAddCallback}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ export const ParentIssuesListModal: React.FC<Props> = ({
|
|||
projectService
|
||||
.projectIssuesSearch(workspaceSlug as string, projectId as string, {
|
||||
search: debouncedSearchTerm,
|
||||
parent: true,
|
||||
parent: searchEpic ? undefined : true,
|
||||
issue_id: issueId,
|
||||
workspace_search: false,
|
||||
epic: searchEpic ? true : undefined,
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ export const IssueList: FC<IIssueList> = observer((props) => {
|
|||
disabled={disabled}
|
||||
handleIssueCrudState={handleIssueCrudState}
|
||||
subIssueOperations={subIssueOperations}
|
||||
issueServiceType={issueServiceType}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@ export enum EmptyStateType {
|
|||
WORKSPACE_DASHBOARD = "workspace-dashboard",
|
||||
WORKSPACE_ANALYTICS = "workspace-analytics",
|
||||
WORKSPACE_PROJECTS = "workspace-projects",
|
||||
WORKSPACE_TEAMS = "workspace-teams",
|
||||
WORKSPACE_INITIATIVES = "workspace-initiatives",
|
||||
WORKSPACE_INITIATIVES_EMPTY_SEARCH = "workspace-initiatives-empty-search",
|
||||
WORKSPACE_ALL_ISSUES = "workspace-all-issues",
|
||||
WORKSPACE_ASSIGNED = "workspace-assigned",
|
||||
WORKSPACE_CREATED = "workspace-created",
|
||||
|
|
@ -96,6 +99,7 @@ export enum EmptyStateType {
|
|||
ACTIVE_CYCLE_ASSIGNEE_EMPTY_STATE = "active-cycle-assignee-empty-state",
|
||||
ACTIVE_CYCLE_LABEL_EMPTY_STATE = "active-cycle-label-empty-state",
|
||||
|
||||
WORKSPACE_ACTIVE_CYCLES = "workspace-active-cycles",
|
||||
DISABLED_PROJECT_INBOX = "disabled-project-inbox",
|
||||
DISABLED_PROJECT_CYCLE = "disabled-project-cycle",
|
||||
DISABLED_PROJECT_MODULE = "disabled-project-module",
|
||||
|
|
@ -110,6 +114,11 @@ export enum EmptyStateType {
|
|||
WORKSPACE_DRAFT_ISSUES = "workspace-draft-issues",
|
||||
|
||||
PROJECT_NO_EPICS = "project-no-epics",
|
||||
// Teams
|
||||
TEAM_NO_ISSUES = "team-no-issues",
|
||||
TEAM_EMPTY_FILTER = "team-empty-filter",
|
||||
TEAM_VIEW = "team-view",
|
||||
TEAM_PAGE = "team-page",
|
||||
}
|
||||
|
||||
const emptyStateDetails = {
|
||||
|
|
@ -165,6 +174,35 @@ const emptyStateDetails = {
|
|||
accessType: "workspace",
|
||||
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
},
|
||||
[EmptyStateType.WORKSPACE_TEAMS]: {
|
||||
key: EmptyStateType.WORKSPACE_TEAMS,
|
||||
title: "Teams",
|
||||
description: "Teams are groups of people who collaborate on projects. Create a team to get started.",
|
||||
path: "/empty-state/teams/teams",
|
||||
primaryButton: {
|
||||
text: "Create new team",
|
||||
},
|
||||
accessType: "workspace",
|
||||
access: [EUserPermissions.ADMIN],
|
||||
},
|
||||
[EmptyStateType.WORKSPACE_INITIATIVES]: {
|
||||
key: EmptyStateType.WORKSPACE_INITIATIVES,
|
||||
title: "Organize work at the highest level with Initiatives",
|
||||
description:
|
||||
"When you need to organize work spanning several projects and teams, Initiatives come in handy. Connect projects and epics to initiatives, see automatically rolled up updates, and see the forests before you get to the trees.",
|
||||
path: "/empty-state/initiatives/initiatives",
|
||||
primaryButton: {
|
||||
text: "Create an initiative",
|
||||
},
|
||||
accessType: "workspace",
|
||||
access: [EUserPermissions.ADMIN],
|
||||
},
|
||||
[EmptyStateType.WORKSPACE_INITIATIVES_EMPTY_SEARCH]: {
|
||||
key: EmptyStateType.WORKSPACE_INITIATIVES_EMPTY_SEARCH,
|
||||
title: "No matching initiatives",
|
||||
description: "No initiatives detected with the matching criteria. \n Create a new initiative instead.",
|
||||
path: "/empty-state/search/project",
|
||||
},
|
||||
// all-issues
|
||||
[EmptyStateType.WORKSPACE_ALL_ISSUES]: {
|
||||
key: EmptyStateType.WORKSPACE_ALL_ISSUES,
|
||||
|
|
@ -695,6 +733,13 @@ const emptyStateDetails = {
|
|||
title: "Add labels to issues to see the \n breakdown of work by labels.",
|
||||
path: "/empty-state/active-cycle/label",
|
||||
},
|
||||
[EmptyStateType.WORKSPACE_ACTIVE_CYCLES]: {
|
||||
key: EmptyStateType.WORKSPACE_ACTIVE_CYCLES,
|
||||
title: "No active cycles",
|
||||
description:
|
||||
"Cycles of your projects that includes any period that encompasses today's date within its range. Find the progress and details of all your active cycle here.",
|
||||
path: "/empty-state/onboarding/workspace-active-cycles",
|
||||
},
|
||||
[EmptyStateType.DISABLED_PROJECT_INBOX]: {
|
||||
key: EmptyStateType.DISABLED_PROJECT_INBOX,
|
||||
title: "Intake is not enabled for the project.",
|
||||
|
|
@ -795,9 +840,63 @@ const emptyStateDetails = {
|
|||
description:
|
||||
"For larger bodies of work that span several cycles and can live across modules, create an epic. Link issues and sub-issues in a project to an epic and jump into an issue from the overview.",
|
||||
path: "/empty-state/onboarding/issues",
|
||||
primaryButton: {
|
||||
text: "Create an Epic",
|
||||
},
|
||||
accessType: "project",
|
||||
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
},
|
||||
// Teams
|
||||
[EmptyStateType.TEAM_NO_ISSUES]: {
|
||||
key: EmptyStateType.TEAM_NO_ISSUES,
|
||||
title: "Create an issue in your team projects and assign it to someone, even yourself",
|
||||
description:
|
||||
"Think of issues as jobs, tasks, work, or JTBD. Which we like. An issue and its sub-issues are usually time-based actionables assigned to members of your team. Your team creates, assigns, and completes issues to move your project towards its goal.",
|
||||
path: "/empty-state/onboarding/issues",
|
||||
primaryButton: {
|
||||
text: "Create your first issue",
|
||||
comicBox: {
|
||||
title: "Issues are building blocks in Plane.",
|
||||
description:
|
||||
"Redesign the Plane UI, Rebrand the company, or Launch the new fuel injection system are examples of issues that likely have sub-issues.",
|
||||
},
|
||||
},
|
||||
accessType: "workspace",
|
||||
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
},
|
||||
[EmptyStateType.TEAM_EMPTY_FILTER]: {
|
||||
key: EmptyStateType.TEAM_EMPTY_FILTER,
|
||||
title: "No issues found matching the filters applied",
|
||||
path: "/empty-state/empty-filters/",
|
||||
secondaryButton: {
|
||||
text: "Clear all filters",
|
||||
},
|
||||
accessType: "workspace",
|
||||
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
},
|
||||
[EmptyStateType.TEAM_VIEW]: {
|
||||
key: EmptyStateType.TEAM_VIEW,
|
||||
title: "Save filtered views for your team. Create as many as you need",
|
||||
description:
|
||||
"Views are a set of saved filters that you use frequently or want easy access to. All your colleagues in a team can see everyone’s views and choose whichever suits their needs best.",
|
||||
path: "/empty-state/onboarding/views",
|
||||
primaryButton: {
|
||||
text: "Create your first view",
|
||||
comicBox: {
|
||||
title: "Views work atop Issue properties.",
|
||||
description: "You can create a view from here with as many properties as filters as you see fit.",
|
||||
},
|
||||
},
|
||||
accessType: "workspace",
|
||||
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
},
|
||||
[EmptyStateType.TEAM_PAGE]: {
|
||||
key: EmptyStateType.TEAM_PAGE,
|
||||
title: "Team pages are coming soon!",
|
||||
description:
|
||||
"Write a note, a doc, or a full knowledge base. Get Galileo, Plane’s AI assistant, to help you get started. Pages are thoughts potting space in Plane. Take down meeting notes, format them easily, embed issues, lay them out using a library of components, and keep them all in your project’s context. To make short work of any doc, invoke Galileo, Plane’s AI, with a shortcut or the click of a button.",
|
||||
path: "/empty-state/onboarding/pages",
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const EMPTY_STATE_DETAILS: Record<EmptyStateType, EmptyStateDetails> = emptyStateDetails;
|
||||
|
|
|
|||
|
|
@ -78,8 +78,8 @@ export const ISSUE_FILTER_OPTIONS: {
|
|||
title: string;
|
||||
}[] = [
|
||||
{ key: null, title: "All" },
|
||||
{ key: "active", title: "Active Issues" },
|
||||
{ key: "backlog", title: "Backlog Issues" },
|
||||
{ key: "active", title: "Active" },
|
||||
{ key: "backlog", title: "Backlog" },
|
||||
// { key: "draft", title: "Draft Issues" },
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export class IssueService extends APIService {
|
|||
): Promise<TIssuesResponse> {
|
||||
const path =
|
||||
(queries.expand as string)?.includes("issue_relation") && !queries.group_by
|
||||
? `/api/workspaces/${workspaceSlug}/projects/${projectId}/issues-detail/`
|
||||
? `/api/workspaces/${workspaceSlug}/projects/${projectId}/${this.serviceType}-detail/`
|
||||
: `/api/workspaces/${workspaceSlug}/projects/${projectId}/${this.serviceType}/`;
|
||||
return this.get(
|
||||
path,
|
||||
|
|
@ -76,8 +76,9 @@ export class IssueService extends APIService {
|
|||
}
|
||||
|
||||
async getIssues(workspaceSlug: string, projectId: string, queries?: any, config = {}): Promise<TIssuesResponse> {
|
||||
if (getIssuesShouldFallbackToServer(queries))
|
||||
if (getIssuesShouldFallbackToServer(queries) || this.serviceType !== EIssueServiceType.ISSUES) {
|
||||
return await this.getIssuesFromServer(workspaceSlug, projectId, queries, config);
|
||||
}
|
||||
|
||||
const response = await persistence.getIssues(workspaceSlug, projectId, queries, config);
|
||||
return response as TIssuesResponse;
|
||||
|
|
@ -112,7 +113,8 @@ export class IssueService extends APIService {
|
|||
params: queries,
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.data) {
|
||||
// skip issue update when the service type is epic
|
||||
if (response.data && this.serviceType === EIssueServiceType.ISSUES) {
|
||||
updateIssue({ ...response.data, is_local_update: 1 });
|
||||
}
|
||||
return response?.data;
|
||||
|
|
@ -127,7 +129,7 @@ export class IssueService extends APIService {
|
|||
params: { issues: issueIds.join(",") },
|
||||
})
|
||||
.then((response) => {
|
||||
if (response?.data && Array.isArray(response?.data)) {
|
||||
if (response?.data && Array.isArray(response?.data) && this.serviceType === EIssueServiceType.ISSUES) {
|
||||
addIssuesBulk(response.data);
|
||||
}
|
||||
return response?.data;
|
||||
|
|
@ -233,7 +235,9 @@ export class IssueService extends APIService {
|
|||
}
|
||||
|
||||
async deleteIssue(workspaceSlug: string, projectId: string, issuesId: string): Promise<any> {
|
||||
deleteIssueFromLocal(issuesId);
|
||||
if (this.serviceType === EIssueServiceType.ISSUES) {
|
||||
deleteIssueFromLocal(issuesId);
|
||||
}
|
||||
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/${this.serviceType}/${issuesId}/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
|
|
@ -335,7 +339,9 @@ export class IssueService extends APIService {
|
|||
async bulkOperations(workspaceSlug: string, projectId: string, data: TBulkOperationsPayload): Promise<any> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/bulk-operation-issues/`, data)
|
||||
.then((response) => {
|
||||
persistence.syncIssues(projectId);
|
||||
if (this.serviceType === EIssueServiceType.ISSUES) {
|
||||
persistence.syncIssues(projectId);
|
||||
}
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
|
|
@ -352,7 +358,9 @@ export class IssueService extends APIService {
|
|||
): Promise<any> {
|
||||
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/bulk-delete-issues/`, data)
|
||||
.then((response) => {
|
||||
persistence.syncIssues(projectId);
|
||||
if (this.serviceType === EIssueServiceType.ISSUES) {
|
||||
persistence.syncIssues(projectId);
|
||||
}
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
|
|
@ -371,7 +379,9 @@ export class IssueService extends APIService {
|
|||
}> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/bulk-archive-issues/`, data)
|
||||
.then((response) => {
|
||||
persistence.syncIssues(projectId);
|
||||
if (this.serviceType === EIssueServiceType.ISSUES) {
|
||||
persistence.syncIssues(projectId);
|
||||
}
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
|
|
@ -411,4 +421,18 @@ export class IssueService extends APIService {
|
|||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async bulkSubscribeIssues(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
data: {
|
||||
issue_ids: string[];
|
||||
}
|
||||
): Promise<any> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/bulk-subscribe-issues/`, data)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -672,6 +672,7 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
|
|||
const issueBeforeRemoval = clone(this.rootIssueStore.issues.getIssueById(issueId));
|
||||
// update parent stats optimistically
|
||||
this.updateParentStats(issueBeforeRemoval, undefined);
|
||||
|
||||
// Male API call
|
||||
await this.issueService.deleteIssue(workspaceSlug, projectId, issueId);
|
||||
// Remove from Respective issue Id list
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ export class IssueStore implements IIssueStore {
|
|||
// services
|
||||
serviceType;
|
||||
issueService;
|
||||
epicService;
|
||||
issueArchiveService;
|
||||
issueDraftService;
|
||||
|
||||
|
|
@ -62,6 +63,7 @@ export class IssueStore implements IIssueStore {
|
|||
// services
|
||||
this.serviceType = serviceType;
|
||||
this.issueService = new IssueService(serviceType);
|
||||
this.epicService = new IssueService(EIssueServiceType.EPICS);
|
||||
this.issueArchiveService = new IssueArchiveService(serviceType);
|
||||
this.issueDraftService = new IssueDraftService();
|
||||
}
|
||||
|
|
@ -93,7 +95,9 @@ export class IssueStore implements IIssueStore {
|
|||
let issue: TIssue | undefined;
|
||||
|
||||
// fetch issue from local db
|
||||
issue = await persistence.getIssue(issueId);
|
||||
if (this.serviceType === EIssueServiceType.ISSUES) {
|
||||
issue = await persistence.getIssue(issueId);
|
||||
}
|
||||
|
||||
this.fetchingIssueDetails = issueId;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import set from "lodash/set";
|
|||
import uniq from "lodash/uniq";
|
||||
import update from "lodash/update";
|
||||
import { action, makeObservable, observable, runInAction } from "mobx";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
// types
|
||||
import {
|
||||
TIssue,
|
||||
|
|
@ -64,6 +65,7 @@ export class IssueSubIssuesStore implements IIssueSubIssuesStore {
|
|||
// root store
|
||||
rootIssueDetailStore: IIssueDetail;
|
||||
// services
|
||||
serviceType;
|
||||
issueService;
|
||||
|
||||
constructor(rootStore: IIssueDetail, serviceType: TIssueServiceType) {
|
||||
|
|
@ -84,6 +86,7 @@ export class IssueSubIssuesStore implements IIssueSubIssuesStore {
|
|||
// root store
|
||||
this.rootIssueDetailStore = rootStore;
|
||||
// services
|
||||
this.serviceType = serviceType;
|
||||
this.issueService = new IssueService(serviceType);
|
||||
}
|
||||
|
||||
|
|
@ -182,7 +185,10 @@ export class IssueSubIssuesStore implements IIssueSubIssuesStore {
|
|||
[parentIssueId, "sub_issues_count"],
|
||||
this.subIssues[parentIssueId].length
|
||||
);
|
||||
updatePersistentLayer([parentIssueId, ...issueIds]);
|
||||
|
||||
if (this.serviceType === EIssueServiceType.ISSUES) {
|
||||
updatePersistentLayer([parentIssueId, ...issueIds]);
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
|
|
@ -280,7 +286,9 @@ export class IssueSubIssuesStore implements IIssueSubIssuesStore {
|
|||
);
|
||||
});
|
||||
|
||||
updatePersistentLayer([parentIssueId]);
|
||||
if (this.serviceType === EIssueServiceType.ISSUES) {
|
||||
updatePersistentLayer([parentIssueId]);
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
|
|
@ -315,7 +323,9 @@ export class IssueSubIssuesStore implements IIssueSubIssuesStore {
|
|||
);
|
||||
});
|
||||
|
||||
updatePersistentLayer([parentIssueId]);
|
||||
if (this.serviceType === EIssueServiceType.ISSUES) {
|
||||
updatePersistentLayer([parentIssueId]);
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue