Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WEB-419] feat: manual issue archival #3801

Merged
merged 24 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
102c183
fix: issue archive without automation
NarayanBavisetti Feb 22, 2024
e5310f8
fix: unarchive issue endpoint change
NarayanBavisetti Feb 22, 2024
925e920
Merge branch 'develop' of https://github.com/makeplane/plane into fea…
aaryan610 Feb 22, 2024
f11873a
Merge branch 'develop' of github.com:makeplane/plane into feat/issue-…
NarayanBavisetti Feb 22, 2024
c4aa508
Merge branch 'develop' of https://github.com/makeplane/plane into fea…
aaryan610 Feb 23, 2024
847ce50
chore: archiving logic implemented in the quick-actions dropdowns
aaryan610 Feb 23, 2024
63c857e
chore: peek overview archive button
aaryan610 Feb 23, 2024
d7627c0
Merge branch 'feat/issue-archive' of github.com:makeplane/plane into …
NarayanBavisetti Feb 26, 2024
79781b8
chore: issue archive completed at state
NarayanBavisetti Feb 26, 2024
dd3aea5
fix: merge conflicts resolved from develop
aaryan610 Feb 26, 2024
2a9481d
chore: updated archiving icon and added archive option everywhere
aaryan610 Feb 26, 2024
ad5e311
chore: all issues quick actions dropdown
aaryan610 Feb 26, 2024
f7480bb
Merge branch 'develop' of github.com:makeplane/plane into feat/issue-…
NarayanBavisetti Feb 26, 2024
a11cdfa
Merge branch 'feat/issue-archive' of github.com:makeplane/plane into …
NarayanBavisetti Feb 26, 2024
64b0227
chore: archive and unarchive response
NarayanBavisetti Feb 26, 2024
fba2420
fix: archival mutation
aaryan610 Feb 26, 2024
a2007c4
fix: restore issue from peek overview
aaryan610 Feb 26, 2024
4650754
chore: update notification content for archive/restore
aaryan610 Feb 26, 2024
af94cef
refactor: activity user name
aaryan610 Feb 26, 2024
d6816cf
fix: all issues mutation
aaryan610 Feb 26, 2024
58307da
fix: merge conflicts resolved from develop
aaryan610 Feb 27, 2024
ecb586d
fix: restore issue auth
aaryan610 Feb 27, 2024
c3f88d3
chore: close peek overview on archival
aaryan610 Feb 27, 2024
8f8fa9e
Merge branch 'develop' of gurusainath:makeplane/plane into feat/issue…
gurusainath Feb 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 4 additions & 12 deletions apiserver/plane/app/urls/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,23 +259,15 @@
name="project-issue-archive",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/archived-issues/<uuid:pk>/",
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:pk>/archive/",
IssueArchiveViewSet.as_view(
{
"get": "retrieve",
"delete": "destroy",
"post": "archive",
"delete": "unarchive",
}
),
name="project-issue-archive",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/unarchive/<uuid:pk>/",
IssueArchiveViewSet.as_view(
{
"post": "unarchive",
}
),
name="project-issue-archive",
name="project-issue-archive-unarchive",
),
## End Issue Archives
## Issue Relation
Expand Down
32 changes: 31 additions & 1 deletion apiserver/plane/app/views/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -1647,6 +1647,36 @@ def retrieve(self, request, slug, project_id, pk=None):
serializer = IssueDetailSerializer(issue, expand=self.expand)
return Response(serializer.data, status=status.HTTP_200_OK)

def archive(self, request, slug, project_id, pk=None):
issue = Issue.issue_objects.get(
workspace__slug=slug,
project_id=project_id,
pk=pk,
)
if issue.state.group not in ["completed", "cancelled"]:
return Response(
{"error": "Can only archive completed or cancelled state group issue"},
status=status.HTTP_400_BAD_REQUEST,
)
issue_activity.delay(
type="issue.activity.updated",
requested_data=json.dumps({"archived_at": str(timezone.now().date()), "automation": False}),
actor_id=str(request.user.id),
issue_id=str(issue.id),
project_id=str(project_id),
current_instance=json.dumps(
IssueSerializer(issue).data, cls=DjangoJSONEncoder
),
epoch=int(timezone.now().timestamp()),
notification=True,
origin=request.META.get("HTTP_ORIGIN"),
)
issue.archived_at = timezone.now().date()
issue.save()

return Response({"archived_at": str(issue.archived_at)}, status=status.HTTP_200_OK)


def unarchive(self, request, slug, project_id, pk=None):
issue = Issue.objects.get(
workspace__slug=slug,
Expand All @@ -1670,7 +1700,7 @@ def unarchive(self, request, slug, project_id, pk=None):
issue.archived_at = None
issue.save()

return Response(IssueSerializer(issue).data, status=status.HTTP_200_OK)
return Response(status=status.HTTP_204_NO_CONTENT)


class IssueSubscriberViewSet(BaseViewSet):
Expand Down
10 changes: 8 additions & 2 deletions apiserver/plane/bgtasks/issue_activites_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,17 +483,23 @@ def track_archive_at(
)
)
else:
if requested_data.get("automation"):
comment = "Plane has archived the issue"
new_value = "archive"
else:
comment = "Actor has archived the issue"
new_value = "manual_archive"
issue_activities.append(
IssueActivity(
issue_id=issue_id,
project_id=project_id,
workspace_id=workspace_id,
comment="Plane has archived the issue",
comment=comment,
verb="updated",
actor_id=actor_id,
field="archived_at",
old_value=None,
new_value="archive",
new_value=new_value,
epoch=epoch,
)
)
Expand Down
2 changes: 1 addition & 1 deletion apiserver/plane/bgtasks/issue_automation_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def archive_old_issues():
issue_activity.delay(
type="issue.activity.updated",
requested_data=json.dumps(
{"archived_at": str(archive_at)}
{"archived_at": str(archive_at), "automation": True}
),
actor_id=str(project.created_by_id),
issue_id=issue.id,
Expand Down
22 changes: 11 additions & 11 deletions packages/types/src/notifications.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,27 @@ export interface PaginatedUserNotification {
}

export interface IUserNotification {
id: string;
created_at: Date;
updated_at: Date;
archived_at: string | null;
created_at: string;
created_by: null;
data: Data;
entity_identifier: string;
entity_name: string;
title: string;
id: string;
message: null;
message_html: string;
message_stripped: null;
sender: string;
project: string;
read_at: Date | null;
archived_at: Date | null;
receiver: string;
sender: string;
snoozed_till: Date | null;
created_by: null;
updated_by: null;
workspace: string;
project: string;
title: string;
triggered_by: string;
triggered_by_details: IUserLite;
receiver: string;
updated_at: Date;
updated_by: null;
workspace: string;
}

export interface Data {
Expand Down
8 changes: 5 additions & 3 deletions packages/ui/src/dropdowns/custom-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,24 +177,26 @@ const CustomMenu = (props: ICustomMenuDropdownProps) => {
};

const MenuItem: React.FC<ICustomMenuItemProps> = (props) => {
const { children, onClick, className = "" } = props;
const { children, disabled = false, onClick, className } = props;

return (
<Menu.Item as="div">
<Menu.Item as="div" disabled={disabled}>
{({ active, close }) => (
<button
type="button"
className={cn(
"w-full select-none truncate rounded px-1 py-1.5 text-left text-custom-text-200",
{
"bg-custom-background-80": active,
"bg-custom-background-80": active && !disabled,
"text-custom-text-400": disabled,
},
className
)}
onClick={(e) => {
close();
onClick && onClick(e);
}}
disabled={disabled}
>
{children}
</button>
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/dropdowns/helper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export type ICustomSearchSelectProps = IDropdownProps &

export interface ICustomMenuItemProps {
children: React.ReactNode;
disabled?: boolean;
onClick?: (args?: any) => void;
className?: string;
}
Expand Down
6 changes: 3 additions & 3 deletions web/components/automation/auto-archive-automation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const AutoArchiveAutomation: React.FC<Props> = observer((props) => {
<div className="">
<h4 className="text-sm font-medium">Auto-archive closed issues</h4>
<p className="text-sm tracking-tight text-custom-text-200">
Plane will auto archive issues that have been completed or cancelled.
Plane will auto archive issues that have been completed or canceled.
</p>
</div>
</div>
Expand All @@ -73,7 +73,7 @@ export const AutoArchiveAutomation: React.FC<Props> = observer((props) => {
<CustomSelect
value={currentProjectDetails?.archive_in}
label={`${currentProjectDetails?.archive_in} ${
currentProjectDetails?.archive_in === 1 ? "Month" : "Months"
currentProjectDetails?.archive_in === 1 ? "month" : "months"
}`}
onChange={(val: number) => {
handleChange({ archive_in: val });
Expand All @@ -93,7 +93,7 @@ export const AutoArchiveAutomation: React.FC<Props> = observer((props) => {
className="flex w-full select-none items-center rounded px-1 py-1.5 text-sm text-custom-text-200 hover:bg-custom-background-80"
onClick={() => setmonthModal(true)}
>
Customise Time Range
Customize time range
</button>
</>
</CustomSelect>
Expand Down
6 changes: 3 additions & 3 deletions web/components/automation/auto-close-automation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
<div className="">
<h4 className="text-sm font-medium">Auto-close issues</h4>
<p className="text-sm tracking-tight text-custom-text-200">
Plane will automatically close issue that haven{"'"}t been completed or cancelled.
Plane will automatically close issue that haven{"'"}t been completed or canceled.
</p>
</div>
</div>
Expand All @@ -100,7 +100,7 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
<CustomSelect
value={currentProjectDetails?.close_in}
label={`${currentProjectDetails?.close_in} ${
currentProjectDetails?.close_in === 1 ? "Month" : "Months"
currentProjectDetails?.close_in === 1 ? "month" : "months"
}`}
onChange={(val: number) => {
handleChange({ close_in: val });
Expand All @@ -119,7 +119,7 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
className="flex w-full select-none items-center rounded px-1 py-1.5 text-custom-text-200 hover:bg-custom-background-80"
onClick={() => setmonthModal(true)}
>
Customize Time Range
Customize time range
</button>
</>
</CustomSelect>
Expand Down
2 changes: 1 addition & 1 deletion web/components/automation/select-month-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export const SelectMonthModal: React.FC<Props> = ({ type, initialValues, isOpen,
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-custom-text-100">
Customise Time Range
Customize time range
</Dialog.Title>
<div className="mt-8 flex items-center gap-2">
<div className="flex w-full flex-col justify-center gap-1">
Expand Down
2 changes: 1 addition & 1 deletion web/components/dropdowns/date.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ export const DateDropdown: React.FC<Props> = (props) => {
{BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && (
<span className="flex-grow truncate">{value ? renderFormattedDate(value) : placeholder}</span>
)}
{isClearable && isDateSelected && (
{isClearable && !disabled && isDateSelected && (
<X
className={cn("h-2 w-2 flex-shrink-0", clearIconClassName)}
onClick={(e) => {
Expand Down
2 changes: 1 addition & 1 deletion web/components/headers/project-archived-issue-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const ProjectArchivedIssueDetailsHeader: FC = observer(() => {
link={
<BreadcrumbLink
href={`/${workspaceSlug}/projects/${projectId}/archived-issues`}
label="Archived Issues"
label="Archived issues"
icon={<LayersIcon className="h-4 w-4 text-custom-text-300" />}
/>
}
Expand Down
2 changes: 1 addition & 1 deletion web/components/headers/project-archived-issues.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export const ProjectArchivedIssuesHeader: FC = observer(() => {
type="text"
link={
<BreadcrumbLink
label="Archived Issues"
label="Archived issues"
icon={<LayersIcon className="h-4 w-4 text-custom-text-300" />}
/>
}
Expand Down
106 changes: 106 additions & 0 deletions web/components/issues/archive-issue-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { useState, Fragment } from "react";
import { Dialog, Transition } from "@headlessui/react";
// hooks
import { useProject } from "hooks/store";
import { useIssues } from "hooks/store/use-issues";
import useToast from "hooks/use-toast";
// ui
import { Button } from "@plane/ui";
// types
import { TIssue } from "@plane/types";

type Props = {
data?: TIssue;
dataId?: string | null | undefined;
handleClose: () => void;
isOpen: boolean;
onSubmit?: () => Promise<void>;
};

export const ArchiveIssueModal: React.FC<Props> = (props) => {
const { dataId, data, isOpen, handleClose, onSubmit } = props;
// states
const [isArchiving, setIsArchiving] = useState(false);
// store hooks
const { getProjectById } = useProject();
const { issueMap } = useIssues();
// toast alert
const { setToastAlert } = useToast();

if (!dataId && !data) return null;

const issue = data ? data : issueMap[dataId!];
const projectDetails = getProjectById(issue.project_id);

const onClose = () => {
setIsArchiving(false);
handleClose();
};

const handleArchiveIssue = async () => {
if (!onSubmit) return;

setIsArchiving(true);
await onSubmit()
.then(() => onClose())
.catch(() =>
setToastAlert({
type: "error",
title: "Error!",
message: "Issue could not be archived. Please try again.",
})
)
.finally(() => setIsArchiving(false));
};

return (
<Transition.Root show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-20" onClose={onClose}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>

<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-full sm:max-w-lg">
<div className="px-5 py-4">
<h3 className="text-xl font-medium 2xl:text-2xl">
Archive issue {projectDetails?.identifier} {issue.sequence_id}
</h3>
<p className="text-sm text-custom-text-200 mt-3">
Are you sure you want to archive the issue? All your archived issues can be restored later.
</p>
<div className="flex justify-end gap-2 mt-3">
<Button variant="neutral-primary" size="sm" onClick={onClose}>
Cancel
</Button>
<Button size="sm" tabIndex={1} onClick={handleArchiveIssue} loading={isArchiving}>
{isArchiving ? "Archiving" : "Archive"}
</Button>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
};
Loading
Loading