diff --git a/apiserver/back_migration.py b/apiserver/back_migration.py index 33513f15c..57ded0ba4 100644 --- a/apiserver/back_migration.py +++ b/apiserver/back_migration.py @@ -1,5 +1,5 @@ # All the python scripts that are used for back migrations - +from plane.db.models import ProjectIdentifier from plane.db.models import Issue, IssueComment # Update description and description html values for old descriptions @@ -40,3 +40,21 @@ def update_comments(): except Exception as e: print(e) print("Failed") + + +def update_project_identifiers(): + try: + project_identifiers = ProjectIdentifier.objects.filter(workspace_id=None).select_related("project", "project__workspace") + updated_identifiers = [] + + for identifier in project_identifiers: + identifier.workspace_id = identifier.project.workspace_id + updated_identifiers.append(identifier) + + ProjectIdentifier.objects.bulk_update( + updated_identifiers, ["workspace_id"], batch_size=50 + ) + print("Success") + except Exception as e: + print(e) + print("Failed") diff --git a/apps/app/components/account/email-signin-form.tsx b/apps/app/components/account/email-signin-form.tsx index 3d13ca5a1..5e47f36d3 100644 --- a/apps/app/components/account/email-signin-form.tsx +++ b/apps/app/components/account/email-signin-form.tsx @@ -28,7 +28,7 @@ export const EmailSignInForm: FC = (props) => { or -
+ {/*
-
+
*/} ); diff --git a/apps/app/components/common/bulk-delete-issues-modal.tsx b/apps/app/components/common/bulk-delete-issues-modal.tsx index 8b8125b48..64a65c22a 100644 --- a/apps/app/components/common/bulk-delete-issues-modal.tsx +++ b/apps/app/components/common/bulk-delete-issues-modal.tsx @@ -24,8 +24,7 @@ import { IIssue, IssueResponse } from "types"; import { PROJECT_ISSUES_LIST, PROJECT_DETAILS } from "constants/fetch-keys"; type FormInput = { - issue_ids: string[]; - cycleId: string; + delete_issue_ids: string[]; }; type Props = { @@ -61,17 +60,27 @@ const BulkDeleteIssuesModal: React.FC = ({ isOpen, setIsOpen }) => { const { setToastAlert } = useToast(); const { - register, handleSubmit, + watch, reset, + setValue, formState: { isSubmitting }, - } = useForm(); + } = useForm({ + defaultValues: { + delete_issue_ids: [], + }, + }); const filteredIssues: IIssue[] = query === "" ? issues?.results ?? [] - : issues?.results.filter((issue) => issue.name.toLowerCase().includes(query.toLowerCase())) ?? - []; + : issues?.results.filter( + (issue) => + issue.name.toLowerCase().includes(query.toLowerCase()) || + `${issue.project_detail.identifier}-${issue.sequence_id}` + .toLowerCase() + .includes(query.toLowerCase()) + ) ?? []; const handleClose = () => { setIsOpen(false); @@ -80,7 +89,7 @@ const BulkDeleteIssuesModal: React.FC = ({ isOpen, setIsOpen }) => { }; const handleDelete: SubmitHandler = async (data) => { - if (!data.issue_ids || data.issue_ids.length === 0) { + if (!data.delete_issue_ids || data.delete_issue_ids.length === 0) { setToastAlert({ title: "Error", type: "error", @@ -89,30 +98,34 @@ const BulkDeleteIssuesModal: React.FC = ({ isOpen, setIsOpen }) => { return; } - if (!Array.isArray(data.issue_ids)) data.issue_ids = [data.issue_ids]; + if (!Array.isArray(data.delete_issue_ids)) data.delete_issue_ids = [data.delete_issue_ids]; if (workspaceSlug && projectId) { await issuesServices - .bulkDeleteIssues(workspaceSlug as string, projectId as string, data) + .bulkDeleteIssues(workspaceSlug as string, projectId as string, { + issue_ids: data.delete_issue_ids, + }) .then((res) => { setToastAlert({ title: "Success", type: "success", message: res.message, }); + mutate( PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string), (prevData) => ({ ...(prevData as IssueResponse), count: (prevData?.results ?? []).filter( - (p) => !data.issue_ids.some((id) => p.id === id) + (p) => !data.delete_issue_ids.some((id) => p.id === id) ).length, results: (prevData?.results ?? []).filter( - (p) => !data.issue_ids.some((id) => p.id === id) + (p) => !data.delete_issue_ids.some((id) => p.id === id) ), }), false ); + handleClose(); }) .catch((e) => { console.log(e); @@ -123,18 +136,6 @@ const BulkDeleteIssuesModal: React.FC = ({ isOpen, setIsOpen }) => { return ( setQuery("")} appear> - -
- -
= ({ isOpen, setIsOpen }) => { leaveFrom="opacity-100 scale-100" leaveTo="opacity-0 scale-95" > - +
- + { + const selectedIssues = watch("delete_issue_ids"); + if (selectedIssues.includes(val)) + setValue( + "delete_issue_ids", + selectedIssues.filter((i) => i !== val) + ); + else { + const newToDelete = selectedIssues; + newToDelete.push(val); + + setValue("delete_issue_ids", newToDelete); + } + }} + >
-
diff --git a/apps/app/components/project/issues/issue-detail/add-as-sub-issue.tsx b/apps/app/components/project/issues/issue-detail/add-as-sub-issue.tsx index 46dc55594..95eae4555 100644 --- a/apps/app/components/project/issues/issue-detail/add-as-sub-issue.tsx +++ b/apps/app/components/project/issues/issue-detail/add-as-sub-issue.tsx @@ -11,7 +11,7 @@ import { RectangleStackIcon, MagnifyingGlassIcon } from "@heroicons/react/24/out // services import issuesServices from "services/issues.service"; // types -import { IIssue } from "types"; +import { IIssue, IssueResponse } from "types"; // constants import { PROJECT_ISSUES_LIST, SUB_ISSUES } from "constants/fetch-keys"; @@ -54,6 +54,22 @@ const AddAsSubIssue: React.FC = ({ isOpen, setIsOpen, parent }) => { .patchIssue(workspaceSlug as string, projectId as string, issueId, { parent: parent?.id }) .then((res) => { mutate(SUB_ISSUES(parent?.id ?? "")); + mutate( + PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string), + (prevData) => ({ + ...(prevData as IssueResponse), + results: (prevData?.results ?? []).map((p) => { + if (p.id === res.id) + return { + ...p, + ...res, + }; + + return p; + }), + }), + false + ); }) .catch((e) => { console.log(e); diff --git a/apps/app/components/project/issues/issue-detail/comment/issue-comment-card.tsx b/apps/app/components/project/issues/issue-detail/comment/issue-comment-card.tsx index e2b17ea00..ec270ff25 100644 --- a/apps/app/components/project/issues/issue-detail/comment/issue-comment-card.tsx +++ b/apps/app/components/project/issues/issue-detail/comment/issue-comment-card.tsx @@ -1,6 +1,5 @@ -// react import React, { useEffect, useState } from "react"; -// next + import Image from "next/image"; import dynamic from "next/dynamic"; @@ -8,14 +7,14 @@ import dynamic from "next/dynamic"; import { useForm } from "react-hook-form"; // icons import { CheckIcon, XMarkIcon } from "@heroicons/react/24/outline"; -// types -import type { IIssueComment } from "types"; // hooks import useUser from "hooks/use-user"; // ui import { CustomMenu } from "components/ui"; // helpers import { timeAgo } from "helpers/date-time.helper"; +// types +import type { IIssueComment } from "types"; const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { ssr: false }); @@ -76,7 +75,7 @@ const CommentCard: React.FC = ({ comment, onSubmit, handleCommentDeletion {timeAgo(comment.created_at)}

-
+
{isEditing ? ( import("components/rich-text-editor"), { ssr: false, @@ -83,7 +83,7 @@ const AddIssueComment: React.FC<{ return (
-
+
= ({ issueDetail, handleCycleChange }) => { return (
- +

Cycle

diff --git a/apps/app/components/project/sidebar-list.tsx b/apps/app/components/project/sidebar-list.tsx index 17fbb84c8..e5ff937f4 100644 --- a/apps/app/components/project/sidebar-list.tsx +++ b/apps/app/components/project/sidebar-list.tsx @@ -99,7 +99,9 @@ export const ProjectSidebarList: FC = () => { {!sidebarCollapse && ( - {project?.name} + + {project?.name} + (
- {/*
- -
*/} -
+ {/*
-
+
*/}
); diff --git a/apps/app/components/sidebar/projects-list.tsx b/apps/app/components/sidebar/projects-list.tsx index f98cbff70..11a0b8fd5 100644 --- a/apps/app/components/sidebar/projects-list.tsx +++ b/apps/app/components/sidebar/projects-list.tsx @@ -79,8 +79,10 @@ const ProjectsList: React.FC = ({ navigation, sidebarCollapse }) => { )} {!sidebarCollapse && ( - - {project?.name} + + + {project?.name} + {
{workspace.name}
- {workspace.total_members} members + {workspace.total_members}{" "} + {workspace.total_members > 1 ? "members" : "member"}
diff --git a/apps/app/layouts/app-layout/app-header.tsx b/apps/app/layouts/app-layout/app-header.tsx index 4bafcc544..439db92ba 100644 --- a/apps/app/layouts/app-layout/app-header.tsx +++ b/apps/app/layouts/app-layout/app-header.tsx @@ -10,28 +10,24 @@ type Props = { setToggleSidebar: React.Dispatch>; }; -const Header: React.FC = ({ breadcrumbs, left, right, setToggleSidebar }) => { - return ( - <> -
-
-
- -
- {breadcrumbs} - {left} -
- {right} +const Header: React.FC = ({ breadcrumbs, left, right, setToggleSidebar }) => ( +
+
+
+
- - ); -}; + {breadcrumbs} + {left} +
+ {right} +
+); export default Header; diff --git a/apps/app/layouts/app-layout/app-sidebar.tsx b/apps/app/layouts/app-layout/app-sidebar.tsx index 48370636e..5a58384a6 100644 --- a/apps/app/layouts/app-layout/app-sidebar.tsx +++ b/apps/app/layouts/app-layout/app-sidebar.tsx @@ -1,5 +1,3 @@ -import { Dispatch, SetStateAction } from "react"; - // hooks import useTheme from "hooks/use-theme"; // components diff --git a/apps/app/pages/[workspaceSlug]/index.tsx b/apps/app/pages/[workspaceSlug]/index.tsx index 4ce65369b..83f9ff4dd 100644 --- a/apps/app/pages/[workspaceSlug]/index.tsx +++ b/apps/app/pages/[workspaceSlug]/index.tsx @@ -199,7 +199,9 @@ const WorkspacePage: NextPage = () => { {project?.name.charAt(0)} )} -

{project.name}

+

+ {project.name} +

diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx index f4512a985..1438a6831 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx @@ -10,13 +10,6 @@ import { requiredAdmin, requiredAuth } from "lib/auth"; import AppLayout from "layouts/app-layout"; // contexts import { IssueViewContextProvider } from "contexts/issue-view.context"; -// icons -import { - ArrowLeftIcon, - ArrowPathIcon, - ListBulletIcon, - PlusIcon, -} from "@heroicons/react/24/outline"; // components import CyclesListView from "components/project/cycles/list-view"; import CyclesBoardView from "components/project/cycles/board-view"; @@ -33,6 +26,8 @@ import projectService from "services/project.service"; import { CustomMenu, EmptySpace, EmptySpaceItem, Spinner } from "components/ui"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; // icons +import { ArrowLeftIcon, ListBulletIcon, PlusIcon } from "@heroicons/react/24/outline"; +import { CyclesIcon } from "components/icons"; // types import { CycleIssueResponse, IIssue, SelectIssue, UserAuth } from "types"; import { NextPageContext } from "next"; @@ -203,7 +198,7 @@ const SingleCycle: React.FC = (props) => { - + {cycles?.find((c) => c.id === cycleId)?.name} } @@ -268,7 +263,7 @@ const SingleCycle: React.FC = (props) => { { : null ); - const { data: issues } = useSWR( - workspaceSlug && projectId - ? PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string) - : null, - workspaceSlug && projectId - ? () => issuesService.getIssues(workspaceSlug as string, projectId as string) - : null - ); - const { data: issueActivities, mutate: mutateIssueActivities } = useSWR( workspaceSlug && projectId && issueId ? PROJECT_ISSUES_ACTIVITY(issueId as string) : null, workspaceSlug && projectId && issueId @@ -102,17 +83,22 @@ const IssueDetailsPage: NextPage = () => { : null ); + const { data: siblingIssues } = useSWR( + workspaceSlug && projectId && issueDetails?.parent ? SUB_ISSUES(issueDetails.parent) : null, + workspaceSlug && projectId && issueDetails?.parent + ? () => + issuesService.subIssues( + workspaceSlug as string, + projectId as string, + issueDetails.parent ?? "" + ) + : null + ); + const { reset, control, watch } = useForm({ defaultValues, }); - const prevIssue = issues?.results[issues?.results.findIndex((issue) => issue.id === issueId) - 1]; - const nextIssue = issues?.results[issues?.results.findIndex((issue) => issue.id === issueId) + 1]; - - const siblingIssues = - issueDetails && - issues?.results.filter((i) => i.parent === issueDetails.parent && i.id !== issueId); - useEffect(() => { if (issueDetails) { mutateIssueActivities(); @@ -168,6 +154,23 @@ const IssueDetailsPage: NextPage = () => { .then((res) => { mutate(SUB_ISSUES(issueDetails?.id ?? "")); mutateIssueActivities(); + + mutate( + PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string), + (prevData) => ({ + ...(prevData as IssueResponse), + results: (prevData?.results ?? []).map((p) => { + if (p.id === res.id) + return { + ...p, + ...res, + }; + + return p; + }), + }), + false + ); }) .catch((e) => { console.error(e); @@ -191,32 +194,6 @@ const IssueDetailsPage: NextPage = () => { /> } - right={ -
- { - if (!prevIssue) return; - router.push(`/${workspaceSlug}/projects/${prevIssue.project}/issues/${prevIssue.id}`); - }} - /> - { - if (!nextIssue) return; - router.push( - `/${workspaceSlug}/projects/${nextIssue.project}/issues/${nextIssue?.id}` - ); - }} - position="reverse" - /> -
- } > {isOpen && ( { /> {issueDetails.project_detail.identifier}- - {issues?.results.find((i) => i.id === issueDetails.parent)?.sequence_id} + {issueDetails.parent_detail?.sequence_id} - {issues?.results - .find((i) => i.id === issueDetails.parent) - ?.name.substring(0, 50)} + {issueDetails.parent_detail?.name.substring(0, 50)} {siblingIssues && siblingIssues.length > 0 ? ( - siblingIssues.map((issue) => ( + siblingIssues.map((issue: IIssue) => ( { )}
) : ( -
+
{step === 4 ? ( diff --git a/apps/app/styles/editor.css b/apps/app/styles/editor.css index 07fcec355..45deb0dc6 100644 --- a/apps/app/styles/editor.css +++ b/apps/app/styles/editor.css @@ -354,6 +354,10 @@ img.ProseMirror-separator { margin-bottom: 1em; } +.remirror-editor-wrapper .remirror-editor { + min-height: 150px; +} + .issue-comments-section .remirror-editor-wrapper .remirror-editor { min-height: 50px; } diff --git a/apps/app/types/issues.d.ts b/apps/app/types/issues.d.ts index c2703d566..4ac7ef9c4 100644 --- a/apps/app/types/issues.d.ts +++ b/apps/app/types/issues.d.ts @@ -50,6 +50,16 @@ export interface IIssueCycle { workspace: string; } +export interface IIssueParent { + description: any; + id: string; + name: string; + priority: string | null; + sequence_id: number; + start_date: string | null; + target_date: string | null; +} + export interface IIssue { assignees: any[] | null; assignee_details: IUser[]; @@ -77,7 +87,7 @@ export interface IIssue { module: string | null; name: string; parent: string | null; - parent_detail: IProject | null; + parent_detail: IIssueParent | null; priority: string | null; project: string; project_detail: IProject; diff --git a/apps/docs/src/components/Search.jsx b/apps/docs/src/components/Search.jsx index 78b345d69..3b63d8a4d 100644 --- a/apps/docs/src/components/Search.jsx +++ b/apps/docs/src/components/Search.jsx @@ -41,7 +41,7 @@ function useAutocomplete() { return item.query }, getItemUrl({ item }) { - let url = new URL(item.url) + const url = new URL(item.url) return `${url.pathname}${url.hash}` }, onSelect({ itemUrl }) { diff --git a/turbo.json b/turbo.json index 7df2292e8..ddc3b9cc5 100644 --- a/turbo.json +++ b/turbo.json @@ -3,7 +3,10 @@ "globalEnv": [ "NEXT_PUBLIC_GITHUB_ID", "NEXT_PUBLIC_GOOGLE_CLIENTID", - "NEXT_PUBLIC_API_BASE_URL" + "NEXT_PUBLIC_API_BASE_URL", + "NEXT_PUBLIC_DOCSEARCH_API_KEY", + "NEXT_PUBLIC_DOCSEARCH_APP_ID", + "NEXT_PUBLIC_DOCSEARCH_INDEX_NAME" ], "pipeline": { "build": {