From 2cd16c61bab21628bc14144652e81d55aad1d81c Mon Sep 17 00:00:00 2001 From: Manish Gupta <59428681+mguptahub@users.noreply.github.com> Date: Thu, 15 Feb 2024 14:52:18 +0530 Subject: [PATCH 01/13] arm build for release tag (#3676) --- .github/workflows/build-branch.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-branch.yml b/.github/workflows/build-branch.yml index f02062189c4..0d3f970689f 100644 --- a/.github/workflows/build-branch.yml +++ b/.github/workflows/build-branch.yml @@ -28,7 +28,7 @@ jobs: - id: set_env_variables name: Set Environment Variables run: | - if [ "${{ env.TARGET_BRANCH }}" == "master" ]; then + if [ "${{ env.TARGET_BRANCH }}" == "master" ] || [ "${{ github.event_name }}" == "release" ]; then echo "BUILDX_DRIVER=cloud" >> $GITHUB_OUTPUT echo "BUILDX_VERSION=lab:latest" >> $GITHUB_OUTPUT echo "BUILDX_PLATFORMS=linux/amd64,linux/arm64" >> $GITHUB_OUTPUT From 7628419a26e9798bcb2a1000b0e9383c7f14f56b Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Thu, 15 Feb 2024 19:31:38 +0530 Subject: [PATCH 02/13] fix: inbox description and mutation (#3677) * fix: inbox status mutation fix * fix: inbox description fix * chore: added description in inbox issue --------- Co-authored-by: NarayanBavisetti --- apiserver/plane/app/views/inbox.py | 4 ++-- web/components/inbox/inbox-issue-status.tsx | 5 +++-- web/components/issues/description-form.tsx | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/apiserver/plane/app/views/inbox.py b/apiserver/plane/app/views/inbox.py index 01eee78e393..f76c74d9c1d 100644 --- a/apiserver/plane/app/views/inbox.py +++ b/apiserver/plane/app/views/inbox.py @@ -27,7 +27,7 @@ InboxSerializer, InboxIssueSerializer, IssueCreateSerializer, - IssueStateInboxSerializer, + IssueDetailSerializer, ) from plane.utils.issue_filters import issue_filters from plane.bgtasks.issue_activites_task import issue_activity @@ -333,7 +333,7 @@ def partial_update(self, request, slug, project_id, inbox_id, issue_id): def retrieve(self, request, slug, project_id, inbox_id, issue_id): issue = self.get_queryset().filter(pk=issue_id).first() - serializer = IssueSerializer(issue, expand=self.expand,) + serializer = IssueDetailSerializer(issue, expand=self.expand,) return Response(serializer.data, status=status.HTTP_200_OK) def destroy(self, request, slug, project_id, inbox_id, issue_id): diff --git a/web/components/inbox/inbox-issue-status.tsx b/web/components/inbox/inbox-issue-status.tsx index 301583b4ba7..2d101f2aaf7 100644 --- a/web/components/inbox/inbox-issue-status.tsx +++ b/web/components/inbox/inbox-issue-status.tsx @@ -1,4 +1,5 @@ import React from "react"; +import { observer } from "mobx-react"; // hooks import { useInboxIssues } from "hooks/store"; // constants @@ -13,7 +14,7 @@ type Props = { showDescription?: boolean; }; -export const InboxIssueStatus: React.FC = (props) => { +export const InboxIssueStatus: React.FC = observer((props) => { const { workspaceSlug, projectId, inboxId, issueId, iconSize = 18, showDescription = false } = props; // hooks const { @@ -52,4 +53,4 @@ export const InboxIssueStatus: React.FC = (props) => { )} ); -}; +}); diff --git a/web/components/issues/description-form.tsx b/web/components/issues/description-form.tsx index 4d20fd97858..c64c147ea90 100644 --- a/web/components/issues/description-form.tsx +++ b/web/components/issues/description-form.tsx @@ -108,7 +108,7 @@ export const IssueDescriptionForm: FC = observer((props) => { description_html: issue.description_html === "" ? "

" : issue.description_html, }); setLocalTitleValue(issue.name); - }, [issue, reset]); + }, [issue, issue.description_html, reset]); // ADDING handleDescriptionFormSubmit TO DEPENDENCY ARRAY PRODUCES ADVERSE EFFECTS // TODO: Verify the exhaustive-deps warning From 85a8af5125012df46767128bafd1871b9fd05b0f Mon Sep 17 00:00:00 2001 From: rahulramesha <71900764+rahulramesha@users.noreply.github.com> Date: Fri, 16 Feb 2024 18:20:44 +0530 Subject: [PATCH 03/13] fix: spreadsheet views sorting (#3683) * fix sorting in spreadsheet all issues * removing focus border since it is being handled globally --- .../issues/issue-layouts/roots/all-issue-layout-root.tsx | 2 +- .../issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx | 2 +- .../issues/issue-layouts/spreadsheet/issue-column.tsx | 2 +- .../issue-layouts/spreadsheet/spreadsheet-header-column.tsx | 2 +- web/store/issue/helpers/issue-helper.store.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx b/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx index 59cf5b9af11..1a77ed5faa8 100644 --- a/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx @@ -159,7 +159,7 @@ export const AllIssueLayoutRoot: React.FC = observer(() => { globalViewId.toString() ); }, - [updateFilters, workspaceSlug] + [updateFilters, workspaceSlug, globalViewId] ); const renderQuickActions = useCallback( diff --git a/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx b/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx index e4efc513746..a94455a0b03 100644 --- a/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx @@ -88,7 +88,7 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => { viewId ); }, - [issueFiltersStore, projectId, workspaceSlug, viewId] + [issueFiltersStore?.updateFilters, projectId, workspaceSlug, viewId] ); const renderQuickActions = useCallback( diff --git a/web/components/issues/issue-layouts/spreadsheet/issue-column.tsx b/web/components/issues/issue-layouts/spreadsheet/issue-column.tsx index 5d2e62fa55e..20dd946dfc6 100644 --- a/web/components/issues/issue-layouts/spreadsheet/issue-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/issue-column.tsx @@ -38,7 +38,7 @@ export const IssueColumn = observer((props: Props) => { > { shouldRenderProperty={shouldRenderProperty} > diff --git a/web/store/issue/helpers/issue-helper.store.ts b/web/store/issue/helpers/issue-helper.store.ts index ff5dba9dd2e..3fca8aae8b8 100644 --- a/web/store/issue/helpers/issue-helper.store.ts +++ b/web/store/issue/helpers/issue-helper.store.ts @@ -202,7 +202,7 @@ export class IssueHelperStore implements TIssueHelperStore { if (!memberMap) break; for (const dataId of dataIdsArray) { const member = memberMap[dataId]; - if (memberMap && member.first_name) dataValues.push(member.first_name.toLocaleLowerCase()); + if (member && member.first_name) dataValues.push(member.first_name.toLocaleLowerCase()); } break; } From 665a07f15ab98b33291dcc3b57b21a76ec43de7a Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Fri, 16 Feb 2024 20:01:58 +0530 Subject: [PATCH 04/13] chore: dropdown and peek overview improvement (#3682) * chore: dropdown focus state improvement * fix: peek overview dropdown placement fix --- web/components/dropdowns/cycle.tsx | 19 ++++++++++--------- web/components/dropdowns/estimate.tsx | 13 ++++++++++--- .../dropdowns/member/project-member.tsx | 13 ++++++++++--- .../dropdowns/member/workspace-member.tsx | 17 ++++++++++------- web/components/dropdowns/module.tsx | 19 ++++++++++--------- web/components/dropdowns/priority.tsx | 17 ++++++++++------- web/components/dropdowns/project.tsx | 17 ++++++++++------- web/components/dropdowns/state.tsx | 13 ++++++++++--- web/components/issues/peek-overview/view.tsx | 14 +++----------- web/components/issues/select/label.tsx | 11 ++++++++++- 10 files changed, 93 insertions(+), 60 deletions(-) diff --git a/web/components/dropdowns/cycle.tsx b/web/components/dropdowns/cycle.tsx index e3aa6df11db..5086d2d26b7 100644 --- a/web/components/dropdowns/cycle.tsx +++ b/web/components/dropdowns/cycle.tsx @@ -61,6 +61,7 @@ export const CycleDropdown: React.FC = observer((props) => { const [isOpen, setIsOpen] = useState(false); // refs const dropdownRef = useRef(null); + const inputRef = useRef(null); // popper-js refs const [referenceElement, setReferenceElement] = useState(null); const [popperElement, setPopperElement] = useState(null); @@ -111,23 +112,15 @@ export const CycleDropdown: React.FC = observer((props) => { const filteredOptions = query === "" ? options : options?.filter((o) => o.query.toLowerCase().includes(query.toLowerCase())); - // fetch cycles of the project if not already present in the store - useEffect(() => { - if (!workspaceSlug) return; - - if (!cycleIds) fetchAllCycles(workspaceSlug, projectId); - }, [cycleIds, fetchAllCycles, projectId, workspaceSlug]); - const selectedCycle = value ? getCycleById(value) : null; const onOpen = () => { - if (referenceElement) referenceElement.focus(); + if (workspaceSlug && !cycleIds) fetchAllCycles(workspaceSlug, projectId); }; const handleClose = () => { if (!isOpen) return; setIsOpen(false); - if (referenceElement) referenceElement.blur(); onClose && onClose(); }; @@ -151,6 +144,12 @@ export const CycleDropdown: React.FC = observer((props) => { useOutsideClickDetector(dropdownRef, handleClose); + useEffect(() => { + if (isOpen && inputRef.current) { + inputRef.current.focus(); + } + }, [isOpen]); + return ( = observer((props) => {
setQuery(e.target.value)} diff --git a/web/components/dropdowns/estimate.tsx b/web/components/dropdowns/estimate.tsx index 48558a683f7..2674fa902d1 100644 --- a/web/components/dropdowns/estimate.tsx +++ b/web/components/dropdowns/estimate.tsx @@ -1,4 +1,4 @@ -import { Fragment, ReactNode, useRef, useState } from "react"; +import { Fragment, ReactNode, useEffect, useRef, useState } from "react"; import { observer } from "mobx-react-lite"; import { Combobox } from "@headlessui/react"; import { usePopper } from "react-popper"; @@ -60,6 +60,7 @@ export const EstimateDropdown: React.FC = observer((props) => { const [isOpen, setIsOpen] = useState(false); // refs const dropdownRef = useRef(null); + const inputRef = useRef(null); // popper-js refs const [referenceElement, setReferenceElement] = useState(null); const [popperElement, setPopperElement] = useState(null); @@ -110,13 +111,11 @@ export const EstimateDropdown: React.FC = observer((props) => { const onOpen = () => { if (!activeEstimate && workspaceSlug) fetchProjectEstimates(workspaceSlug, projectId); - if (referenceElement) referenceElement.focus(); }; const handleClose = () => { if (!isOpen) return; setIsOpen(false); - if (referenceElement) referenceElement.blur(); onClose && onClose(); }; @@ -140,6 +139,12 @@ export const EstimateDropdown: React.FC = observer((props) => { useOutsideClickDetector(dropdownRef, handleClose); + useEffect(() => { + if (isOpen && inputRef.current) { + inputRef.current.focus(); + } + }, [isOpen]); + return ( = observer((props) => {
setQuery(e.target.value)} diff --git a/web/components/dropdowns/member/project-member.tsx b/web/components/dropdowns/member/project-member.tsx index 44cd3a7016a..d1f285aa555 100644 --- a/web/components/dropdowns/member/project-member.tsx +++ b/web/components/dropdowns/member/project-member.tsx @@ -1,4 +1,4 @@ -import { Fragment, useRef, useState } from "react"; +import { Fragment, useEffect, useRef, useState } from "react"; import { observer } from "mobx-react-lite"; import { Combobox } from "@headlessui/react"; import { usePopper } from "react-popper"; @@ -50,6 +50,7 @@ export const ProjectMemberDropdown: React.FC = observer((props) => { const [isOpen, setIsOpen] = useState(false); // refs const dropdownRef = useRef(null); + const inputRef = useRef(null); // popper-js refs const [referenceElement, setReferenceElement] = useState(null); const [popperElement, setPopperElement] = useState(null); @@ -103,13 +104,11 @@ export const ProjectMemberDropdown: React.FC = observer((props) => { const onOpen = () => { if (!projectMemberIds && workspaceSlug) fetchProjectMembers(workspaceSlug, projectId); - if (referenceElement) referenceElement.focus(); }; const handleClose = () => { if (!isOpen) return; setIsOpen(false); - if (referenceElement) referenceElement.blur(); onClose && onClose(); }; @@ -133,6 +132,12 @@ export const ProjectMemberDropdown: React.FC = observer((props) => { useOutsideClickDetector(dropdownRef, handleClose); + useEffect(() => { + if (isOpen && inputRef.current) { + inputRef.current.focus(); + } + }, [isOpen]); + return ( = observer((props) => {
setQuery(e.target.value)} diff --git a/web/components/dropdowns/member/workspace-member.tsx b/web/components/dropdowns/member/workspace-member.tsx index d126b60f72b..7a2628ccaff 100644 --- a/web/components/dropdowns/member/workspace-member.tsx +++ b/web/components/dropdowns/member/workspace-member.tsx @@ -1,4 +1,4 @@ -import { Fragment, useRef, useState } from "react"; +import { Fragment, useEffect, useRef, useState } from "react"; import { observer } from "mobx-react-lite"; import { Combobox } from "@headlessui/react"; import { usePopper } from "react-popper"; @@ -44,6 +44,7 @@ export const WorkspaceMemberDropdown: React.FC = observer(( const [isOpen, setIsOpen] = useState(false); // refs const dropdownRef = useRef(null); + const inputRef = useRef(null); // popper-js refs const [referenceElement, setReferenceElement] = useState(null); const [popperElement, setPopperElement] = useState(null); @@ -91,19 +92,13 @@ export const WorkspaceMemberDropdown: React.FC = observer(( }; if (multiple) comboboxProps.multiple = true; - const onOpen = () => { - if (referenceElement) referenceElement.focus(); - }; - const handleClose = () => { if (!isOpen) return; setIsOpen(false); - if (referenceElement) referenceElement.blur(); onClose && onClose(); }; const toggleDropdown = () => { - if (!isOpen) onOpen(); setIsOpen((prevIsOpen) => !prevIsOpen); }; @@ -122,6 +117,12 @@ export const WorkspaceMemberDropdown: React.FC = observer(( useOutsideClickDetector(dropdownRef, handleClose); + useEffect(() => { + if (isOpen && inputRef.current) { + inputRef.current.focus(); + } + }, [isOpen]); + return ( = observer((
setQuery(e.target.value)} diff --git a/web/components/dropdowns/module.tsx b/web/components/dropdowns/module.tsx index a9b64c1f1f7..c05eeb97e65 100644 --- a/web/components/dropdowns/module.tsx +++ b/web/components/dropdowns/module.tsx @@ -166,6 +166,7 @@ export const ModuleDropdown: React.FC = observer((props) => { const [isOpen, setIsOpen] = useState(false); // refs const dropdownRef = useRef(null); + const inputRef = useRef(null); // popper-js refs const [referenceElement, setReferenceElement] = useState(null); const [popperElement, setPopperElement] = useState(null); @@ -216,21 +217,13 @@ export const ModuleDropdown: React.FC = observer((props) => { const filteredOptions = query === "" ? options : options?.filter((o) => o.query.toLowerCase().includes(query.toLowerCase())); - // fetch modules of the project if not already present in the store - useEffect(() => { - if (!workspaceSlug) return; - - if (!moduleIds) fetchModules(workspaceSlug, projectId); - }, [moduleIds, fetchModules, projectId, workspaceSlug]); - const onOpen = () => { - if (referenceElement) referenceElement.focus(); + if (!moduleIds && workspaceSlug) fetchModules(workspaceSlug, projectId); }; const handleClose = () => { if (!isOpen) return; setIsOpen(false); - if (referenceElement) referenceElement.blur(); onClose && onClose(); }; @@ -261,6 +254,12 @@ export const ModuleDropdown: React.FC = observer((props) => { }; if (multiple) comboboxProps.multiple = true; + useEffect(() => { + if (isOpen && inputRef.current) { + inputRef.current.focus(); + } + }, [isOpen]); + return ( = observer((props) => {
setQuery(e.target.value)} diff --git a/web/components/dropdowns/priority.tsx b/web/components/dropdowns/priority.tsx index 1bab9a21e9c..d519ad9f18c 100644 --- a/web/components/dropdowns/priority.tsx +++ b/web/components/dropdowns/priority.tsx @@ -1,4 +1,4 @@ -import { Fragment, ReactNode, useRef, useState } from "react"; +import { Fragment, ReactNode, useEffect, useRef, useState } from "react"; import { Combobox } from "@headlessui/react"; import { usePopper } from "react-popper"; import { Check, ChevronDown, Search } from "lucide-react"; @@ -272,6 +272,7 @@ export const PriorityDropdown: React.FC = (props) => { const [isOpen, setIsOpen] = useState(false); // refs const dropdownRef = useRef(null); + const inputRef = useRef(null); // popper-js refs const [referenceElement, setReferenceElement] = useState(null); const [popperElement, setPopperElement] = useState(null); @@ -305,19 +306,13 @@ export const PriorityDropdown: React.FC = (props) => { const filteredOptions = query === "" ? options : options.filter((o) => o.query.toLowerCase().includes(query.toLowerCase())); - const onOpen = () => { - if (referenceElement) referenceElement.focus(); - }; - const handleClose = () => { if (!isOpen) return; setIsOpen(false); - if (referenceElement) referenceElement.blur(); onClose && onClose(); }; const toggleDropdown = () => { - if (!isOpen) onOpen(); setIsOpen((prevIsOpen) => !prevIsOpen); }; @@ -342,6 +337,12 @@ export const PriorityDropdown: React.FC = (props) => { ? BackgroundButton : TransparentButton; + useEffect(() => { + if (isOpen && inputRef.current) { + inputRef.current.focus(); + } + }, [isOpen]); + return ( = (props) => {
setQuery(e.target.value)} diff --git a/web/components/dropdowns/project.tsx b/web/components/dropdowns/project.tsx index 7991c44024c..f6fb9205e86 100644 --- a/web/components/dropdowns/project.tsx +++ b/web/components/dropdowns/project.tsx @@ -1,4 +1,4 @@ -import { Fragment, ReactNode, useRef, useState } from "react"; +import { Fragment, ReactNode, useEffect, useRef, useState } from "react"; import { observer } from "mobx-react-lite"; import { Combobox } from "@headlessui/react"; import { usePopper } from "react-popper"; @@ -50,6 +50,7 @@ export const ProjectDropdown: React.FC = observer((props) => { const [isOpen, setIsOpen] = useState(false); // refs const dropdownRef = useRef(null); + const inputRef = useRef(null); // popper-js refs const [referenceElement, setReferenceElement] = useState(null); const [popperElement, setPopperElement] = useState(null); @@ -94,19 +95,13 @@ export const ProjectDropdown: React.FC = observer((props) => { const selectedProject = value ? getProjectById(value) : null; - const onOpen = () => { - if (referenceElement) referenceElement.focus(); - }; - const handleClose = () => { if (!isOpen) return; setIsOpen(false); onClose && onClose(); - if (referenceElement) referenceElement.blur(); }; const toggleDropdown = () => { - if (!isOpen) onOpen(); setIsOpen((prevIsOpen) => !prevIsOpen); }; @@ -125,6 +120,12 @@ export const ProjectDropdown: React.FC = observer((props) => { useOutsideClickDetector(dropdownRef, handleClose); + useEffect(() => { + if (isOpen && inputRef.current) { + inputRef.current.focus(); + } + }, [isOpen]); + return ( = observer((props) => {
setQuery(e.target.value)} diff --git a/web/components/dropdowns/state.tsx b/web/components/dropdowns/state.tsx index a7f54adfbe0..fa068fdd01b 100644 --- a/web/components/dropdowns/state.tsx +++ b/web/components/dropdowns/state.tsx @@ -1,4 +1,4 @@ -import { Fragment, ReactNode, useRef, useState } from "react"; +import { Fragment, ReactNode, useEffect, useRef, useState } from "react"; import { observer } from "mobx-react-lite"; import { Combobox } from "@headlessui/react"; import { usePopper } from "react-popper"; @@ -52,6 +52,7 @@ export const StateDropdown: React.FC = observer((props) => { const [isOpen, setIsOpen] = useState(false); // refs const dropdownRef = useRef(null); + const inputRef = useRef(null); // popper-js refs const [referenceElement, setReferenceElement] = useState(null); const [popperElement, setPopperElement] = useState(null); @@ -92,14 +93,12 @@ export const StateDropdown: React.FC = observer((props) => { const onOpen = () => { if (!statesList && workspaceSlug) fetchProjectStates(workspaceSlug, projectId); - if (referenceElement) referenceElement.focus(); }; const handleClose = () => { if (!isOpen) return; setIsOpen(false); onClose && onClose(); - if (referenceElement) referenceElement.blur(); }; const toggleDropdown = () => { @@ -122,6 +121,12 @@ export const StateDropdown: React.FC = observer((props) => { useOutsideClickDetector(dropdownRef, handleClose); + useEffect(() => { + if (isOpen && inputRef.current) { + inputRef.current.focus(); + } + }, [isOpen]); + return ( = observer((props) => {
setQuery(e.target.value)} diff --git a/web/components/issues/peek-overview/view.tsx b/web/components/issues/peek-overview/view.tsx index 82bda41d5f5..7b6c851ffd8 100644 --- a/web/components/issues/peek-overview/view.tsx +++ b/web/components/issues/peek-overview/view.tsx @@ -126,7 +126,7 @@ export const IssueView: FC = observer((props) => { /> )} -
+
{issueId && (
= observer((props) => { disabled={disabled} /> - +
) : (
@@ -250,11 +246,7 @@ export const IssueView: FC = observer((props) => { setIsSubmitting={(value) => setIsSubmitting(value)} /> - +
= observer((props) => { const [isDropdownOpen, setIsDropdownOpen] = useState(false); // refs const dropdownRef = useRef(null); + const inputRef = useRef(null); // popper const { styles, attributes } = usePopper(referenceElement, popperElement, { placement: "bottom-start", @@ -76,6 +77,12 @@ export const IssueLabelSelect: React.FC = observer((props) => { useOutsideClickDetector(dropdownRef, handleClose); + useEffect(() => { + if (isDropdownOpen && inputRef.current) { + inputRef.current.focus(); + } + }, [isDropdownOpen]); + return ( = observer((props) => {
setQuery(event.target.value)} placeholder="Search" From a94c60703195379ef91f37d3c87b5c4343adc0bc Mon Sep 17 00:00:00 2001 From: guru_sainath Date: Fri, 16 Feb 2024 20:07:04 +0530 Subject: [PATCH 05/13] fix: updated issue title and description components (#3687) * fix: issue description fixes * chore: description html in the archive issue * chore: changed retrieve viewset * chore: implemented new issue title description components in inbox, issue-detail and fixed issue in archived store * chore: removed consoles and empty description update in issue detail * fix: draft issue empty state image --------- Co-authored-by: sriram veeraghanta Co-authored-by: NarayanBavisetti Co-authored-by: Anmol Singh Bhatia --- apiserver/plane/app/views/issue.py | 12 +-- web/components/issues/description-input.tsx | 95 +++++++++++++++++++ .../issue-detail/inbox/main-content.tsx | 23 +++-- .../comments/comment-create.tsx | 1 - .../issues/issue-detail/main-content.tsx | 23 +++-- .../empty-states/draft-issues.tsx | 2 +- .../issues/issue-layouts/list/block.tsx | 5 +- .../roots/archived-issue-layout-root.tsx | 2 +- .../issues/peek-overview/issue-detail.tsx | 38 +++++--- web/components/issues/peek-overview/root.tsx | 22 +++-- web/components/issues/title-input.tsx | 70 ++++++++++++++ .../pages/create-update-page-modal.tsx | 1 - web/components/project/form.tsx | 2 +- .../profile/preferences/layout.tsx | 77 +++++++-------- 14 files changed, 287 insertions(+), 86 deletions(-) create mode 100644 web/components/issues/description-input.tsx create mode 100644 web/components/issues/title-input.tsx diff --git a/apiserver/plane/app/views/issue.py b/apiserver/plane/app/views/issue.py index c8845150a52..edefade16e4 100644 --- a/apiserver/plane/app/views/issue.py +++ b/apiserver/plane/app/views/issue.py @@ -1209,13 +1209,13 @@ def list(self, request, slug, project_id): return Response(issues, status=status.HTTP_200_OK) def retrieve(self, request, slug, project_id, pk=None): - issue = Issue.objects.get( - workspace__slug=slug, - project_id=project_id, - archived_at__isnull=False, - pk=pk, + issue = self.get_queryset().filter(pk=pk).first() + return Response( + IssueDetailSerializer( + issue, fields=self.fields, expand=self.expand + ).data, + status=status.HTTP_200_OK, ) - return Response(IssueSerializer(issue).data, status=status.HTTP_200_OK) def unarchive(self, request, slug, project_id, pk=None): issue = Issue.objects.get( diff --git a/web/components/issues/description-input.tsx b/web/components/issues/description-input.tsx new file mode 100644 index 00000000000..8f3dc864482 --- /dev/null +++ b/web/components/issues/description-input.tsx @@ -0,0 +1,95 @@ +import { FC, useState, useEffect } from "react"; +import { observer } from "mobx-react"; +// components +import { Loader } from "@plane/ui"; +import { RichReadOnlyEditor, RichTextEditor } from "@plane/rich-text-editor"; +// store hooks +import { useMention, useWorkspace } from "hooks/store"; +// services +import { FileService } from "services/file.service"; +const fileService = new FileService(); +// types +import { TIssueOperations } from "./issue-detail"; +// hooks +import useDebounce from "hooks/use-debounce"; +import useReloadConfirmations from "hooks/use-reload-confirmation"; + +export type IssueDescriptionInputProps = { + disabled?: boolean; + value: string | undefined | null; + workspaceSlug: string; + setIsSubmitting: (value: "submitting" | "submitted" | "saved") => void; + issueOperations: TIssueOperations; + projectId: string; + issueId: string; +}; + +export const IssueDescriptionInput: FC = observer((props) => { + const { disabled, value, workspaceSlug, setIsSubmitting, issueId, issueOperations, projectId } = props; + // states + const [descriptionHTML, setDescriptionHTML] = useState(value); + // store hooks + const { mentionHighlights, mentionSuggestions } = useMention(); + const workspaceStore = useWorkspace(); + // hooks + const { setShowAlert } = useReloadConfirmations(); + const debouncedValue = useDebounce(descriptionHTML, 1500); + // computed values + const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug)?.id as string; + + useEffect(() => { + setDescriptionHTML(value); + }, [value]); + + useEffect(() => { + if (debouncedValue || debouncedValue === "") { + issueOperations + .update(workspaceSlug, projectId, issueId, { description_html: debouncedValue }, false) + .finally(() => { + setIsSubmitting("saved"); + }); + } + // DO NOT Add more dependencies here. It will cause multiple requests to be sent. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [debouncedValue]); + + if (!descriptionHTML && descriptionHTML !== "") { + return ( + + + + ); + } + + if (disabled) { + return ( + + ); + } + + return ( + { + setShowAlert(true); + setIsSubmitting("submitting"); + setDescriptionHTML(description_html); + }} + mentionSuggestions={mentionSuggestions} + mentionHighlights={mentionHighlights} + /> + ); +}); diff --git a/web/components/issues/issue-detail/inbox/main-content.tsx b/web/components/issues/issue-detail/inbox/main-content.tsx index 4a1f79bee5f..d25fe92606c 100644 --- a/web/components/issues/issue-detail/inbox/main-content.tsx +++ b/web/components/issues/issue-detail/inbox/main-content.tsx @@ -3,7 +3,9 @@ import { observer } from "mobx-react-lite"; // hooks import { useIssueDetail, useProjectState, useUser } from "hooks/store"; // components -import { IssueDescriptionForm, IssueUpdateStatus, TIssueOperations } from "components/issues"; +import { IssueUpdateStatus, TIssueOperations } from "components/issues"; +import { IssueTitleInput } from "../../title-input"; +import { IssueDescriptionInput } from "../../description-input"; import { IssueReaction } from "../reactions"; import { IssueActivity } from "../issue-activity"; import { InboxIssueStatus } from "../../../inbox/inbox-issue-status"; @@ -57,15 +59,24 @@ export const InboxIssueMainContent: React.FC = observer((props) => {
- setIsSubmitting(value)} + issueOperations={issueOperations} + disabled={!is_editable} + value={issue.name} + /> + + setIsSubmitting(value)} - isSubmitting={isSubmitting} - issue={issue} issueOperations={issueOperations} disabled={!is_editable} + value={issue.description_html} /> {currentUser && ( diff --git a/web/components/issues/issue-detail/issue-activity/comments/comment-create.tsx b/web/components/issues/issue-detail/issue-activity/comments/comment-create.tsx index bb79c981769..bf5b15266f1 100644 --- a/web/components/issues/issue-detail/issue-activity/comments/comment-create.tsx +++ b/web/components/issues/issue-detail/issue-activity/comments/comment-create.tsx @@ -81,7 +81,6 @@ export const IssueCommentCreate: FC = (props) => { render={({ field: { value, onChange } }) => ( { - console.log("yo"); handleSubmit(onSubmit)(e); }} cancelUploadImage={fileService.cancelUpload} diff --git a/web/components/issues/issue-detail/main-content.tsx b/web/components/issues/issue-detail/main-content.tsx index 07552580177..14860a0cf64 100644 --- a/web/components/issues/issue-detail/main-content.tsx +++ b/web/components/issues/issue-detail/main-content.tsx @@ -3,7 +3,9 @@ import { observer } from "mobx-react-lite"; // hooks import { useIssueDetail, useProjectState, useUser } from "hooks/store"; // components -import { IssueDescriptionForm, IssueAttachmentRoot, IssueUpdateStatus } from "components/issues"; +import { IssueAttachmentRoot, IssueUpdateStatus } from "components/issues"; +import { IssueTitleInput } from "../title-input"; +import { IssueDescriptionInput } from "../description-input"; import { IssueParentDetail } from "./parent"; import { IssueReaction } from "./reactions"; import { SubIssuesRoot } from "../sub-issues"; @@ -61,15 +63,24 @@ export const IssueMainContent: React.FC = observer((props) => {
- setIsSubmitting(value)} - isSubmitting={isSubmitting} - issue={issue} issueOperations={issueOperations} disabled={!is_editable} + value={issue.name} + /> + + setIsSubmitting(value)} + issueOperations={issueOperations} + disabled={!is_editable} + value={issue.description_html} /> {currentUser && ( diff --git a/web/components/issues/issue-layouts/empty-states/draft-issues.tsx b/web/components/issues/issue-layouts/empty-states/draft-issues.tsx index 347778d8f25..c496cc5fe2d 100644 --- a/web/components/issues/issue-layouts/empty-states/draft-issues.tsx +++ b/web/components/issues/issue-layouts/empty-states/draft-issues.tsx @@ -42,7 +42,7 @@ export const ProjectDraftEmptyState: React.FC = observer(() => { const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light"; const currentLayoutEmptyStateImagePath = getEmptyStateImagePath("empty-filters", activeLayout ?? "list", isLightMode); - const EmptyStateImagePath = getEmptyStateImagePath("draft", "empty-issues", isLightMode); + const EmptyStateImagePath = getEmptyStateImagePath("draft", "draft-issues-empty", isLightMode); const issueFilterCount = size( Object.fromEntries( diff --git a/web/components/issues/issue-layouts/list/block.tsx b/web/components/issues/issue-layouts/list/block.tsx index ceec7b219cc..2e48e1f1c8c 100644 --- a/web/components/issues/issue-layouts/list/block.tsx +++ b/web/components/issues/issue-layouts/list/block.tsx @@ -50,9 +50,8 @@ export const IssueBlock: React.FC = observer((props: IssueBlock return (
{displayProperties && displayProperties?.key && ( diff --git a/web/components/issues/issue-layouts/roots/archived-issue-layout-root.tsx b/web/components/issues/issue-layouts/roots/archived-issue-layout-root.tsx index 5f049d4c39c..2ae7ae510ad 100644 --- a/web/components/issues/issue-layouts/roots/archived-issue-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/archived-issue-layout-root.tsx @@ -54,7 +54,7 @@ export const ArchivedIssueLayoutRoot: React.FC = observer(() => {
- + )}
diff --git a/web/components/issues/peek-overview/issue-detail.tsx b/web/components/issues/peek-overview/issue-detail.tsx index 8c510193843..7bc1b1b03b0 100644 --- a/web/components/issues/peek-overview/issue-detail.tsx +++ b/web/components/issues/peek-overview/issue-detail.tsx @@ -1,10 +1,15 @@ -import { FC } from "react"; -// hooks +import { FC, useCallback, useEffect, useState } from "react"; +import { observer } from "mobx-react"; +// store hooks import { useIssueDetail, useProject, useUser } from "hooks/store"; +// hooks +import useReloadConfirmations from "hooks/use-reload-confirmation"; // components -import { IssueDescriptionForm, TIssueOperations } from "components/issues"; +import { TIssueOperations } from "components/issues"; import { IssueReaction } from "../issue-detail/reactions"; -import { observer } from "mobx-react"; +import { IssueTitleInput } from "../title-input"; +import { IssueDescriptionInput } from "../description-input"; +import { debounce } from "lodash"; interface IPeekOverviewIssueDetails { workspaceSlug: string; @@ -17,17 +22,18 @@ interface IPeekOverviewIssueDetails { } export const PeekOverviewIssueDetails: FC = observer((props) => { - const { workspaceSlug, projectId, issueId, issueOperations, disabled, isSubmitting, setIsSubmitting } = props; + const { workspaceSlug, issueId, issueOperations, disabled, setIsSubmitting } = props; // store hooks const { getProjectById } = useProject(); const { currentUser } = useUser(); const { issue: { getIssueById }, } = useIssueDetail(); - // derived values const issue = getIssueById(issueId); + if (!issue) return <>; + const projectDetails = getProjectById(issue?.project_id); return ( @@ -35,20 +41,28 @@ export const PeekOverviewIssueDetails: FC = observer( {projectDetails?.identifier}-{issue?.sequence_id} - setIsSubmitting(value)} + issueOperations={issueOperations} + disabled={disabled} + value={issue.name} + /> + setIsSubmitting(value)} - isSubmitting={isSubmitting} - issue={issue} issueOperations={issueOperations} disabled={disabled} + value={issue.description_html} /> {currentUser && ( diff --git a/web/components/issues/peek-overview/root.tsx b/web/components/issues/peek-overview/root.tsx index b491ebe3638..c49c0a50330 100644 --- a/web/components/issues/peek-overview/root.tsx +++ b/web/components/issues/peek-overview/root.tsx @@ -69,20 +69,11 @@ export const IssuePeekOverview: FC = observer((props) => { // state const [loader, setLoader] = useState(false); - useEffect(() => { - if (peekIssue) { - setLoader(true); - fetchIssue(peekIssue.workspaceSlug, peekIssue.projectId, peekIssue.issueId).finally(() => { - setLoader(false); - }); - } - }, [peekIssue, fetchIssue]); - const issueOperations: TIssuePeekOperations = useMemo( () => ({ fetch: async (workspaceSlug: string, projectId: string, issueId: string) => { try { - await fetchIssue(workspaceSlug, projectId, issueId); + await fetchIssue(workspaceSlug, projectId, issueId, is_archived); } catch (error) { console.error("Error fetching the parent issue"); } @@ -324,9 +315,20 @@ export const IssuePeekOverview: FC = observer((props) => { removeModulesFromIssue, setToastAlert, onIssueUpdate, + captureIssueEvent, + router.asPath, ] ); + useEffect(() => { + if (peekIssue) { + setLoader(true); + issueOperations.fetch(peekIssue.workspaceSlug, peekIssue.projectId, peekIssue.issueId).finally(() => { + setLoader(false); + }); + } + }, [peekIssue, issueOperations]); + if (!peekIssue?.workspaceSlug || !peekIssue?.projectId || !peekIssue?.issueId) return <>; const issue = getIssueById(peekIssue.issueId) || undefined; diff --git a/web/components/issues/title-input.tsx b/web/components/issues/title-input.tsx new file mode 100644 index 00000000000..2cd031b4f87 --- /dev/null +++ b/web/components/issues/title-input.tsx @@ -0,0 +1,70 @@ +import { FC, useState, useEffect, useCallback } from "react"; +import { observer } from "mobx-react"; +// components +import { TextArea } from "@plane/ui"; +// types +import { TIssueOperations } from "./issue-detail"; +// hooks +import useDebounce from "hooks/use-debounce"; +import useReloadConfirmations from "hooks/use-reload-confirmation"; + +export type IssueTitleInputProps = { + disabled?: boolean; + value: string | undefined | null; + workspaceSlug: string; + setIsSubmitting: (value: "submitting" | "submitted" | "saved") => void; + issueOperations: TIssueOperations; + projectId: string; + issueId: string; +}; + +export const IssueTitleInput: FC = observer((props) => { + const { disabled, value, workspaceSlug, setIsSubmitting, issueId, issueOperations, projectId } = props; + // states + const [title, setTitle] = useState(""); + // hooks + const { setShowAlert } = useReloadConfirmations(); + const debouncedValue = useDebounce(title, 1500); + + useEffect(() => { + if (value) setTitle(value); + }, [value]); + + useEffect(() => { + if (debouncedValue) { + issueOperations.update(workspaceSlug, projectId, issueId, { name: debouncedValue }, false).finally(() => { + setIsSubmitting("saved"); + }); + } + // DO NOT Add more dependencies here. It will cause multiple requests to be sent. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [debouncedValue]); + + const handleTitleChange = useCallback( + (e: React.ChangeEvent) => { + setShowAlert(true); + setIsSubmitting("submitting"); + setTitle(e.target.value); + }, + [setIsSubmitting, setShowAlert] + ); + + return ( +
+