From c9cbca5ec8dde1a65886714013e877fa3814208b Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Thu, 13 Jul 2023 11:34:37 +0530 Subject: [PATCH] feat: auto-archive and auto-close (#1502) * chore: issue archive services and types added * chore: project type and constant updated * feat: auto-close and auto-archive feature added * feat: implement rendering of archived issues * feat: implemented rendering of only list view for archived issues , chore: update types and services * feat: implemented archive issue detail page and unarchive issue functionality , chore: refactor code * feat: activity for issue archive and issue restore added * fix: redirection and delete fix * fix: merge conflict * fix: restore issue redirection fix * fix: disable modification of issue properties for archived issues, style: disable properties styling * fix: hide empty group, switch to list view on redirct to archived issues * fix: remove unnecessary header buttons for archived issue * fix: auto-close dropdown fix --- .../automation/auto-archive-automation.tsx | 96 ++++++++ .../automation/auto-close-automation.tsx | 193 ++++++++++++++++ apps/app/components/automation/index.ts | 3 + .../automation/select-month-modal.tsx | 147 ++++++++++++ apps/app/components/core/feeds.tsx | 41 +++- .../core/filters/issues-view-filter.tsx | 35 +-- .../core/list-view/single-issue.tsx | 21 +- .../components/core/list-view/single-list.tsx | 5 +- apps/app/components/issues/activity.tsx | 50 +++-- .../components/issues/attachment-upload.tsx | 12 +- .../components/issues/comment/add-comment.tsx | 5 +- .../components/issues/delete-issue-modal.tsx | 32 ++- apps/app/components/issues/main-content.tsx | 26 ++- .../issues/sidebar-select/assignee.tsx | 10 +- .../issues/sidebar-select/blocked.tsx | 4 +- .../issues/sidebar-select/blocker.tsx | 4 +- .../issues/sidebar-select/cycle.tsx | 4 +- .../issues/sidebar-select/estimate.tsx | 12 +- .../issues/sidebar-select/module.tsx | 4 +- .../issues/sidebar-select/parent.tsx | 4 +- .../issues/sidebar-select/priority.tsx | 10 +- .../issues/sidebar-select/state.tsx | 10 +- apps/app/components/issues/sidebar.tsx | 32 ++- .../app/components/issues/sub-issues-list.tsx | 5 +- .../project/single-sidebar-project.tsx | 14 +- apps/app/constants/fetch-keys.ts | 9 + apps/app/constants/project.ts | 9 +- apps/app/hooks/use-issues-view.tsx | 19 +- apps/app/layouts/settings-navbar.tsx | 4 + .../archived-issues/[archivedIssueId].tsx | 211 ++++++++++++++++++ .../[projectId]/archived-issues/index.tsx | 77 +++++++ .../[projectId]/settings/automations.tsx | 81 +++++++ apps/app/services/issues.service.ts | 46 ++++ apps/app/types/issues.d.ts | 1 + apps/app/types/projects.d.ts | 3 + 35 files changed, 1151 insertions(+), 88 deletions(-) create mode 100644 apps/app/components/automation/auto-archive-automation.tsx create mode 100644 apps/app/components/automation/auto-close-automation.tsx create mode 100644 apps/app/components/automation/index.ts create mode 100644 apps/app/components/automation/select-month-modal.tsx create mode 100644 apps/app/pages/[workspaceSlug]/projects/[projectId]/archived-issues/[archivedIssueId].tsx create mode 100644 apps/app/pages/[workspaceSlug]/projects/[projectId]/archived-issues/index.tsx create mode 100644 apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/automations.tsx diff --git a/apps/app/components/automation/auto-archive-automation.tsx b/apps/app/components/automation/auto-archive-automation.tsx new file mode 100644 index 000000000..8a78fc543 --- /dev/null +++ b/apps/app/components/automation/auto-archive-automation.tsx @@ -0,0 +1,96 @@ +import React, { useState } from "react"; + +// component +import { CustomSelect, ToggleSwitch } from "components/ui"; +import { SelectMonthModal } from "components/automation"; +// icons +import { ChevronDownIcon } from "@heroicons/react/24/outline"; +// constants +import { PROJECT_AUTOMATION_MONTHS } from "constants/project"; +// types +import { IProject } from "types"; + +type Props = { + projectDetails: IProject | undefined; + handleChange: (formData: Partial) => Promise; +}; + +export const AutoArchiveAutomation: React.FC = ({ projectDetails, handleChange }) => { + const [monthModal, setmonthModal] = useState(false); + + const initialValues: Partial = { archive_in: 1 }; + return ( + <> + setmonthModal(false)} + handleChange={handleChange} + /> +
+
+
+

Auto-archive closed issues

+

+ Plane will automatically archive issues that have been completed or canceled for the + configured time period +

+
+ { + if (projectDetails?.archive_in === 0) { + handleChange({ archive_in: 1 }); + } else { + handleChange({ archive_in: 0 }); + } + }} + size="sm" + /> +
+ {projectDetails?.archive_in !== 0 && ( +
+
+ Auto-archive issues that are closed for +
+
+ + {`${projectDetails?.archive_in} Months`} + +
+
+ )} +
+ + ); +}; diff --git a/apps/app/components/automation/auto-close-automation.tsx b/apps/app/components/automation/auto-close-automation.tsx new file mode 100644 index 000000000..11451d045 --- /dev/null +++ b/apps/app/components/automation/auto-close-automation.tsx @@ -0,0 +1,193 @@ +import React, { useState } from "react"; + +import useSWR from "swr"; + +import { useRouter } from "next/router"; + +// component +import { CustomSearchSelect, CustomSelect, ToggleSwitch } from "components/ui"; +import { SelectMonthModal } from "components/automation"; +// icons +import { ChevronDownIcon, Squares2X2Icon } from "@heroicons/react/24/outline"; +import { getStateGroupIcon } from "components/icons"; +// services +import stateService from "services/state.service"; +// constants +import { PROJECT_AUTOMATION_MONTHS } from "constants/project"; +import { STATES_LIST } from "constants/fetch-keys"; +// types +import { IProject } from "types"; +// helper +import { getStatesList } from "helpers/state.helper"; + +type Props = { + projectDetails: IProject | undefined; + handleChange: (formData: Partial) => Promise; +}; + +export const AutoCloseAutomation: React.FC = ({ projectDetails, handleChange }) => { + const [monthModal, setmonthModal] = useState(false); + + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + + const { data: stateGroups } = useSWR( + workspaceSlug && projectId ? STATES_LIST(projectId as string) : null, + workspaceSlug && projectId + ? () => stateService.getStates(workspaceSlug as string, projectId as string) + : null + ); + + const states = getStatesList(stateGroups ?? {}); + + const options = states + ?.filter((state) => state.group === "cancelled") + .map((state) => ({ + value: state.id, + query: state.name, + content: ( +
+ {getStateGroupIcon(state.group, "16", "16", state.color)} + {state.name} +
+ ), + })); + + const multipleOptions = options.length > 1; + + const defaultState = stateGroups && stateGroups.cancelled ? stateGroups.cancelled[0].id : null; + + const selectedOption = states?.find( + (s) => s.id === projectDetails?.default_state ?? defaultState + ); + const currentDefaultState = states.find((s) => s.id === defaultState); + + const initialValues: Partial = { + close_in: 1, + default_state: defaultState, + }; + + return ( + <> + setmonthModal(false)} + handleChange={handleChange} + /> + +
+
+
+

Auto-close inactive issues

+

+ Plane will automatically close the issues that have not been updated for the + configured time period. +

+
+ { + if (projectDetails?.close_in === 0) { + handleChange({ close_in: 1, default_state: defaultState }); + } else { + handleChange({ close_in: 0, default_state: null }); + } + }} + size="sm" + /> +
+ {projectDetails?.close_in !== 0 && ( +
+
+
+ Auto-close issues that are inactive for +
+
+ + {`${projectDetails?.close_in} Months`} + +
+
+
+
Auto-close Status
+
+ +
+ {selectedOption ? ( + getStateGroupIcon(selectedOption.group, "16", "16", selectedOption.color) + ) : currentDefaultState ? ( + getStateGroupIcon( + currentDefaultState.group, + "16", + "16", + currentDefaultState.color + ) + ) : ( + + )} + {selectedOption?.name + ? selectedOption.name + : currentDefaultState?.name ?? ( + State + )} +
+ {multipleOptions && ( +
+
+
+ )} +
+ + ); +}; diff --git a/apps/app/components/automation/index.ts b/apps/app/components/automation/index.ts new file mode 100644 index 000000000..73decae11 --- /dev/null +++ b/apps/app/components/automation/index.ts @@ -0,0 +1,3 @@ +export * from "./auto-close-automation"; +export * from "./auto-archive-automation"; +export * from "./select-month-modal"; diff --git a/apps/app/components/automation/select-month-modal.tsx b/apps/app/components/automation/select-month-modal.tsx new file mode 100644 index 000000000..ceb7ffa1a --- /dev/null +++ b/apps/app/components/automation/select-month-modal.tsx @@ -0,0 +1,147 @@ +import React from "react"; + +import { useRouter } from "next/router"; + +// react-hook-form +import { useForm } from "react-hook-form"; +// headless ui +import { Dialog, Transition } from "@headlessui/react"; +// ui +import { Input, PrimaryButton, SecondaryButton } from "components/ui"; +// types +import type { IProject } from "types"; + +// types +type Props = { + isOpen: boolean; + type: "auto-close" | "auto-archive"; + initialValues: Partial; + handleClose: () => void; + handleChange: (formData: Partial) => Promise; +}; + +export const SelectMonthModal: React.FC = ({ + type, + initialValues, + isOpen, + handleClose, + handleChange, +}) => { + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + + const { + register, + formState: { errors, isSubmitting }, + handleSubmit, + reset, + } = useForm({ + defaultValues: initialValues, + }); + + const onClose = () => { + handleClose(); + reset(initialValues); + }; + + const onSubmit = (formData: Partial) => { + if (!workspaceSlug && !projectId) return; + handleChange(formData); + onClose(); + }; + + const inputSection = (name: string) => ( +
+ + Months +
+ ); + + return ( + + + +
+ + +
+
+ + +
+
+ + Customize Time Range + +
+
+ {type === "auto-close" ? ( + <> + {inputSection("close_in")} + {errors.close_in && ( + + Select a month between 1 and 12. + + )} + + ) : ( + <> + {inputSection("archive_in")} + {errors.archive_in && ( + + Select a month between 1 and 12. + + )} + + )} +
+
+
+
+ Cancel + + {isSubmitting ? "Submitting..." : "Submit"} + +
+
+
+
+
+
+
+
+ ); +}; diff --git a/apps/app/components/core/feeds.tsx b/apps/app/components/core/feeds.tsx index bc915d294..27be9ba31 100644 --- a/apps/app/components/core/feeds.tsx +++ b/apps/app/components/core/feeds.tsx @@ -23,6 +23,7 @@ import { renderShortDateWithYearFormat, timeAgo } from "helpers/date-time.helper import { addSpaceIfCamelCase } from "helpers/string.helper"; // types import RemirrorRichTextEditor from "components/rich-text-editor"; +import { Icon } from "components/ui"; const activityDetails: { [key: string]: { @@ -105,6 +106,10 @@ const activityDetails: { message: "updated the attachment", icon: