From 2de3c38c494e5aea768e16cf807a608f9228cc9d Mon Sep 17 00:00:00 2001 From: NarayanBavisetti Date: Wed, 26 Jun 2024 13:17:00 +0530 Subject: [PATCH 1/4] chore: pages custom error codes --- apiserver/plane/app/views/issue/archive.py | 9 +++++--- .../plane/app/views/issue/bulk_operations.py | 23 ++++++++++++++----- apiserver/plane/app/views/page/base.py | 16 +++++++++---- apiserver/plane/utils/error_codes.py | 10 ++++++++ 4 files changed, 44 insertions(+), 14 deletions(-) create mode 100644 apiserver/plane/utils/error_codes.py diff --git a/apiserver/plane/app/views/issue/archive.py b/apiserver/plane/app/views/issue/archive.py index 584edd8f938..dba06028b0f 100644 --- a/apiserver/plane/app/views/issue/archive.py +++ b/apiserver/plane/app/views/issue/archive.py @@ -25,7 +25,7 @@ from plane.app.serializers import ( IssueFlatSerializer, IssueSerializer, - IssueDetailSerializer + IssueDetailSerializer, ) from plane.bgtasks.issue_activites_task import issue_activity from plane.db.models import ( @@ -46,6 +46,7 @@ GroupedOffsetPaginator, SubGroupedOffsetPaginator, ) +from plane.utils.error_codes import ERROR_CODES # Module imports from .. import BaseViewSet, BaseAPIView @@ -341,8 +342,10 @@ def post(self, request, slug, project_id): if issue.state.group not in ["completed", "cancelled"]: return Response( { - "error_code": 4091, - "error_message": "INVALID_ARCHIVE_STATE_GROUP" + "error_code": ERROR_CODES[ + "INVALID_ARCHIVE_STATE_GROUP" + ], + "error_message": "INVALID_ARCHIVE_STATE_GROUP", }, status=status.HTTP_400_BAD_REQUEST, ) diff --git a/apiserver/plane/app/views/issue/bulk_operations.py b/apiserver/plane/app/views/issue/bulk_operations.py index ea663782607..06ec0b4c8c5 100644 --- a/apiserver/plane/app/views/issue/bulk_operations.py +++ b/apiserver/plane/app/views/issue/bulk_operations.py @@ -21,6 +21,7 @@ IssueAssignee, ) from plane.bgtasks.issue_activites_task import issue_activity +from plane.utils.error_codes import ERROR_CODES class BulkIssueOperationsEndpoint(BaseAPIView): @@ -59,14 +60,20 @@ def post(self, request, slug, project_id): properties = request.data.get("properties", {}) - if properties.get("start_date", False) and properties.get("target_date", False): + if properties.get("start_date", False) and properties.get( + "target_date", False + ): if ( - datetime.strptime(properties.get("start_date"), "%Y-%m-%d").date() - > datetime.strptime(properties.get("target_date"), "%Y-%m-%d").date() + datetime.strptime( + properties.get("start_date"), "%Y-%m-%d" + ).date() + > datetime.strptime( + properties.get("target_date"), "%Y-%m-%d" + ).date() ): return Response( { - "error_code": 4100, + "error_code": ERROR_CODES["INVALID_ISSUE_DATES"], "error_message": "INVALID_ISSUE_DATES", }, status=status.HTTP_400_BAD_REQUEST, @@ -124,7 +131,9 @@ def post(self, request, slug, project_id): ): return Response( { - "error_code": 4101, + "error_code": ERROR_CODES[ + "INVALID_ISSUE_START_DATE" + ], "error_message": "INVALID_ISSUE_START_DATE", }, status=status.HTTP_400_BAD_REQUEST, @@ -158,7 +167,9 @@ def post(self, request, slug, project_id): ): return Response( { - "error_code": 4102, + "error_code": ERROR_CODES[ + "INVALID_ISSUE_TARGET_DATE" + ], "error_message": "INVALID_ISSUE_TARGET_DATE", }, status=status.HTTP_400_BAD_REQUEST, diff --git a/apiserver/plane/app/views/page/base.py b/apiserver/plane/app/views/page/base.py index 60fb81eebc8..6228b109e77 100644 --- a/apiserver/plane/app/views/page/base.py +++ b/apiserver/plane/app/views/page/base.py @@ -33,7 +33,7 @@ ProjectMember, ProjectPage, ) - +from plane.utils.error_codes import ERROR_CODES # Module imports from ..base import BaseAPIView, BaseViewSet @@ -465,14 +465,20 @@ def partial_update(self, request, slug, project_id, pk): if page.is_locked: return Response( - {"error": "Page is locked"}, - status=471, + { + "error_code": ERROR_CODES["PAGE_LOCKED"], + "error_message": "PAGE_LOCKED", + }, + status=status.HTTP_400_BAD_REQUEST, ) if page.archived_at: return Response( - {"error": "Page is archived"}, - status=472, + { + "error_code": ERROR_CODES["PAGE_ARCHIVED"], + "error_message": "PAGE_ARCHIVED", + }, + status=status.HTTP_400_BAD_REQUEST, ) base64_data = request.data.get("description_binary") diff --git a/apiserver/plane/utils/error_codes.py b/apiserver/plane/utils/error_codes.py new file mode 100644 index 00000000000..15d38f6bf96 --- /dev/null +++ b/apiserver/plane/utils/error_codes.py @@ -0,0 +1,10 @@ +ERROR_CODES = { + # issues + "INVALID_ARCHIVE_STATE_GROUP": 4091, + "INVALID_ISSUE_DATES": 4100, + "INVALID_ISSUE_START_DATE": 4101, + "INVALID_ISSUE_TARGET_DATE": 4102, + # pages + "PAGE_LOCKED": 4701, + "PAGE_ARCHIVED": 4702, +} From cf756d957eb91500ff2aea551b0badb2d930be18 Mon Sep 17 00:00:00 2001 From: gakshita Date: Tue, 20 Aug 2024 14:05:46 +0530 Subject: [PATCH 2/4] fix: project archive issue --- .../sidebar/favorites/favorite-folder.tsx | 13 ++- .../sidebar/favorites/favorites-menu.tsx | 12 +- web/core/store/favorite.store.ts | 104 ++++++------------ web/core/store/project/project.store.ts | 1 + 4 files changed, 51 insertions(+), 79 deletions(-) diff --git a/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx b/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx index 63a683c5fc3..4921506be86 100644 --- a/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx @@ -5,6 +5,7 @@ import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; import { attachClosestEdge, extractClosestEdge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge"; +import uniqBy from "lodash/uniqBy"; import { useParams } from "next/navigation"; import { PenSquare, Star, MoreHorizontal, ChevronRight, GripVertical } from "lucide-react"; import { Disclosure, Transition } from "@headlessui/react"; @@ -37,7 +38,7 @@ export const FavoriteFolder: React.FC = (props) => { const { sidebarCollapsed: isSidebarCollapsed } = useAppTheme(); const { isMobile } = usePlatformOS(); - const { moveFavorite, getGroupedFavorites, favoriteMap, moveFavoriteFolder } = useFavorite(); + const { moveFavorite, getGroupedFavorites, groupedFavorites, moveFavoriteFolder } = useFavorite(); const { workspaceSlug } = useParams(); // states const [isMenuActive, setIsMenuActive] = useState(false); @@ -110,7 +111,9 @@ export const FavoriteFolder: React.FC = (props) => { const edge = extractClosestEdge(destinationData) || undefined; const payload = { id: favorite.id, - sequence: Math.round(getDestinationStateSequence(favoriteMap, destinationData.id as string, edge) || 0), + sequence: Math.round( + getDestinationStateSequence(groupedFavorites, destinationData.id as string, edge) || 0 + ), }; handleOnDropFolder(payload); @@ -146,7 +149,7 @@ export const FavoriteFolder: React.FC = (props) => { if (source.data.is_folder) return; if (sourceId === destinationId) return; if (!sourceId || !destinationId) return; - if (favoriteMap[sourceId].parent === destinationId) return; + if (groupedFavorites[sourceId].parent === destinationId) return; handleOnDrop(sourceId, destinationId); }, }) @@ -313,14 +316,14 @@ export const FavoriteFolder: React.FC = (props) => { "px-2": !isSidebarCollapsed, })} > - {favorite.children.map((child) => ( + {uniqBy(favorite.children, "id").map((child) => ( ))} diff --git a/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx b/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx index 17380b079cc..08386e3fcf0 100644 --- a/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx @@ -3,7 +3,7 @@ import React, { useEffect, useRef, useState } from "react"; import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; import { dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; -import { orderBy, uniqBy } from "lodash"; +import { orderBy } from "lodash"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { ChevronRight, FolderPlus } from "lucide-react"; @@ -33,7 +33,7 @@ export const SidebarFavoritesMenu = observer(() => { // store hooks const { sidebarCollapsed } = useAppTheme(); - const { favoriteIds, favoriteMap, deleteFavorite, removeFromFavoriteFolder } = useFavorite(); + const { favoriteIds, groupedFavorites, deleteFavorite, removeFromFavoriteFolder } = useFavorite(); const { workspaceSlug } = useParams(); const { isMobile } = usePlatformOS(); @@ -108,7 +108,7 @@ export const SidebarFavoritesMenu = observer(() => { setIsDragging(false); const sourceId = source?.data?.id as string | undefined; console.log({ sourceId }); - if (!sourceId || !favoriteMap[sourceId].parent) return; + if (!sourceId || !groupedFavorites[sourceId].parent) return; handleRemoveFromFavoritesFolder(sourceId); }, }) @@ -170,14 +170,14 @@ export const SidebarFavoritesMenu = observer(() => { static > {createNewFolder && } - {Object.keys(favoriteMap).length === 0 ? ( + {Object.keys(groupedFavorites).length === 0 ? ( <> {!sidebarCollapsed && ( No favorites yet )} ) : ( - uniqBy(orderBy(Object.values(favoriteMap), "sequence", "desc"), "id") + orderBy(Object.values(groupedFavorites), "sequence", "desc") .filter((fav) => !fav.parent) .map((fav, index) => ( { favorite={fav} handleRemoveFromFavorites={handleRemoveFromFavorites} handleRemoveFromFavoritesFolder={handleRemoveFromFavoritesFolder} - favoriteMap={favoriteMap} + favoriteMap={groupedFavorites} /> )} diff --git a/web/core/store/favorite.store.ts b/web/core/store/favorite.store.ts index 30917afcb24..1a85f521516 100644 --- a/web/core/store/favorite.store.ts +++ b/web/core/store/favorite.store.ts @@ -18,6 +18,7 @@ export interface IFavoriteStore { }; // computed actions existingFolders: string[]; + groupedFavorites: { [favoriteId: string]: IFavorite }; // actions fetchFavorite: (workspaceSlug: string) => Promise; // CRUD actions @@ -57,6 +58,7 @@ export class FavoriteStore implements IFavoriteStore { favoriteIds: observable, //computed existingFolders: computed, + groupedFavorites: computed, // action fetchFavorite: action, // CRUD actions @@ -80,6 +82,23 @@ export class FavoriteStore implements IFavoriteStore { return Object.values(this.favoriteMap).map((fav) => fav.name); } + get groupedFavorites() { + const data: { [favoriteId: string]: IFavorite } = JSON.parse(JSON.stringify(this.favoriteMap)); + + Object.values(data).forEach((fav) => { + if (fav.parent && data[fav.parent]) { + if (data[fav.parent].children) { + if (!data[fav.parent].children.some((f) => f.id === fav.id)) { + data[fav.parent].children.push(fav); + } + } else { + data[fav.parent].children = [fav]; + } + } + }); + return data; + } + /** * Creates a favorite in the workspace and adds it to the store * @param workspaceSlug @@ -151,22 +170,8 @@ export class FavoriteStore implements IFavoriteStore { */ moveFavorite = async (workspaceSlug: string, favoriteId: string, data: Partial) => { const oldParent = this.favoriteMap[favoriteId].parent; - const favorite = this.favoriteMap[favoriteId]; try { runInAction(() => { - // add the favorite to the new parent - if (!data.parent) return; - set(this.favoriteMap, [data.parent, "children"], [favorite, ...this.favoriteMap[data.parent].children]); - - // remove the favorite from the old parent - if (oldParent) { - set( - this.favoriteMap, - [oldParent, "children"], - this.favoriteMap[oldParent].children.filter((child) => child.id !== favoriteId) - ); - } - // add parent of the favorite set(this.favoriteMap, [favoriteId, "parent"], data.parent); }); @@ -177,21 +182,6 @@ export class FavoriteStore implements IFavoriteStore { // revert the changes runInAction(() => { if (!data.parent) return; - // remove the favorite from the new parent - set( - this.favoriteMap, - [data.parent, "children"], - this.favoriteMap[data.parent].children.filter((child) => child.id !== favoriteId) - ); - - // add the favorite back to the old parent - if (oldParent) { - set( - this.favoriteMap, - [oldParent, "children"], - [...this.favoriteMap[oldParent].children, this.favoriteMap[favoriteId]] - ); - } // revert the parent set(this.favoriteMap, [favoriteId, "parent"], oldParent); @@ -223,28 +213,13 @@ export class FavoriteStore implements IFavoriteStore { runInAction(() => { //remove parent set(this.favoriteMap, [favoriteId, "parent"], null); - - //remove children from parent - if (parent) { - set( - this.favoriteMap, - [parent, "children"], - this.favoriteMap[parent].children.filter((child) => child.id !== favoriteId) - ); - } }); await this.favoriteService.updateFavorite(workspaceSlug, favoriteId, data); } catch (error) { console.error("Failed to move favorite"); runInAction(() => { set(this.favoriteMap, [favoriteId, "parent"], parent); - if (parent) { - set( - this.favoriteMap, - [parent, "children"], - [...this.favoriteMap[parent].children, this.favoriteMap[favoriteId]] - ); - } + throw error; }); throw error; @@ -262,7 +237,7 @@ export class FavoriteStore implements IFavoriteStore { case "cycle": return (this.cycleStore.cycleMap[entity_identifier].is_favorite = false); case "project": - return (this.projectStore.projectMap[entity_identifier].is_favorite = false); + return (this.projectStore.projectMap[entity_identifier]!.is_favorite = false); default: return; } @@ -276,19 +251,12 @@ export class FavoriteStore implements IFavoriteStore { */ deleteFavorite = async (workspaceSlug: string, favoriteId: string) => { const parent = this.favoriteMap[favoriteId].parent; - const children = this.favoriteMap[favoriteId].children; + const children = this.groupedFavorites[favoriteId].children; const entity_identifier = this.favoriteMap[favoriteId].entity_identifier; const initialState = this.favoriteMap[favoriteId]; try { runInAction(() => { - if (parent) { - set( - this.favoriteMap, - [parent, "children"], - this.favoriteMap[parent].children.filter((child) => child.id !== favoriteId) - ); - } delete this.favoriteMap[favoriteId]; entity_identifier && delete this.entityMap[entity_identifier]; this.favoriteIds = this.favoriteIds.filter((id) => id !== favoriteId); @@ -298,7 +266,6 @@ export class FavoriteStore implements IFavoriteStore { entity_identifier && this.removeFavoriteEntityFromStore(entity_identifier, initialState.entity_type); if (children) { children.forEach((child) => { - console.log(child.entity_type); if (!child.entity_identifier) return; this.removeFavoriteEntityFromStore(child.entity_identifier, child.entity_type); }); @@ -341,19 +308,22 @@ export class FavoriteStore implements IFavoriteStore { removeFavoriteFromStore = (entity_identifier: string) => { try { - const favoriteId = this.entityMap[entity_identifier].id; - const favorite = this.favoriteMap[favoriteId]; - const parent = favorite.parent; - + const favoriteId = this.entityMap[entity_identifier]?.id; + const oldData = this.favoriteMap[favoriteId]; + const projectData = Object.values(this.favoriteMap).filter( + (fav) => fav.project_id === entity_identifier && fav.entity_type !== "project" + ); runInAction(() => { - if (parent) { - set( - this.favoriteMap, - [parent, "children"], - this.favoriteMap[parent].children.filter((child) => child.id !== favoriteId) - ); - } + projectData && + projectData.forEach(async (fav) => { + this.removeFavoriteFromStore(fav.entity_identifier!); + this.removeFavoriteEntityFromStore(fav.entity_identifier!, fav.entity_type); + }); + + if (!favoriteId) return; delete this.favoriteMap[favoriteId]; + this.removeFavoriteEntityFromStore(entity_identifier!, oldData.entity_type); + delete this.entityMap[entity_identifier]; this.favoriteIds = this.favoriteIds.filter((id) => id !== favoriteId); }); @@ -373,8 +343,6 @@ export class FavoriteStore implements IFavoriteStore { try { const response = await this.favoriteService.getGroupedFavorites(workspaceSlug, favoriteId); runInAction(() => { - // add children to the favorite - set(this.favoriteMap, [favoriteId, "children"], response); // add the favorites to the map response.forEach((favorite) => { set(this.favoriteMap, [favorite.id], favorite); diff --git a/web/core/store/project/project.store.ts b/web/core/store/project/project.store.ts index 6e1751f71ce..ef817dab126 100644 --- a/web/core/store/project/project.store.ts +++ b/web/core/store/project/project.store.ts @@ -418,6 +418,7 @@ export class ProjectStore implements IProjectStore { .then((response) => { runInAction(() => { set(this.projectMap, [projectId, "archived_at"], response.archived_at); + this.rootStore.favorite.removeFavoriteFromStore(projectId); }); }) .catch((error) => { From dfc5df18a88dfc3cf76c2d68a420175f44965deb Mon Sep 17 00:00:00 2001 From: gakshita Date: Tue, 20 Aug 2024 17:33:23 +0530 Subject: [PATCH 3/4] fix: delete issue + dropdown z-index fix --- .../dropdowns/member/member-options.tsx | 2 +- web/core/store/cycle.store.ts | 1 + web/core/store/favorite.store.ts | 26 ++++++++++++------- web/core/store/module.store.ts | 1 + web/core/store/pages/page.ts | 1 + 5 files changed, 21 insertions(+), 10 deletions(-) diff --git a/web/core/components/dropdowns/member/member-options.tsx b/web/core/components/dropdowns/member/member-options.tsx index 8e7003f24a2..a2d262130e0 100644 --- a/web/core/components/dropdowns/member/member-options.tsx +++ b/web/core/components/dropdowns/member/member-options.tsx @@ -88,7 +88,7 @@ export const MemberOptions = observer((props: Props) => { return createPortal(
{ runInAction(() => { set(this.cycleMap, [cycleId, "archived_at"], response.archived_at); + if (this.rootStore.favorite.entityMap[cycleId]) this.rootStore.favorite.removeFavoriteFromStore(cycleId); }); }) .catch((error) => { diff --git a/web/core/store/favorite.store.ts b/web/core/store/favorite.store.ts index 1a85f521516..9d04ed1303e 100644 --- a/web/core/store/favorite.store.ts +++ b/web/core/store/favorite.store.ts @@ -229,15 +229,26 @@ export class FavoriteStore implements IFavoriteStore { removeFavoriteEntityFromStore = (entity_identifier: string, entity_type: string) => { switch (entity_type) { case "view": - return (this.viewStore.viewMap[entity_identifier].is_favorite = false); + return ( + this.viewStore.viewMap[entity_identifier] && (this.viewStore.viewMap[entity_identifier].is_favorite = false) + ); case "module": - return (this.moduleStore.moduleMap[entity_identifier].is_favorite = false); + return ( + this.moduleStore.moduleMap[entity_identifier] && + (this.moduleStore.moduleMap[entity_identifier].is_favorite = false) + ); case "page": - return (this.pageStore.data[entity_identifier].is_favorite = false); + return this.pageStore.data[entity_identifier] && (this.pageStore.data[entity_identifier].is_favorite = false); case "cycle": - return (this.cycleStore.cycleMap[entity_identifier].is_favorite = false); + return ( + this.cycleStore.cycleMap[entity_identifier] && + (this.cycleStore.cycleMap[entity_identifier].is_favorite = false) + ); case "project": - return (this.projectStore.projectMap[entity_identifier]!.is_favorite = false); + return ( + this.projectStore.projectMap[entity_identifier] && + (this.projectStore.projectMap[entity_identifier].is_favorite = false) + ); default: return; } @@ -256,12 +267,12 @@ export class FavoriteStore implements IFavoriteStore { const initialState = this.favoriteMap[favoriteId]; try { + await this.favoriteService.deleteFavorite(workspaceSlug, favoriteId); runInAction(() => { delete this.favoriteMap[favoriteId]; entity_identifier && delete this.entityMap[entity_identifier]; this.favoriteIds = this.favoriteIds.filter((id) => id !== favoriteId); }); - await this.favoriteService.deleteFavorite(workspaceSlug, favoriteId); runInAction(() => { entity_identifier && this.removeFavoriteEntityFromStore(entity_identifier, initialState.entity_type); if (children) { @@ -293,9 +304,6 @@ export class FavoriteStore implements IFavoriteStore { const initialState = this.entityMap[entityId]; try { const favoriteId = this.entityMap[entityId].id; - runInAction(() => { - delete this.entityMap[entityId]; - }); await this.deleteFavorite(workspaceSlug, favoriteId); } catch (error) { console.error("Failed to remove favorite entity from favorite store", error); diff --git a/web/core/store/module.store.ts b/web/core/store/module.store.ts index 23e5cb0127a..c3401dfbe92 100644 --- a/web/core/store/module.store.ts +++ b/web/core/store/module.store.ts @@ -557,6 +557,7 @@ export class ModulesStore implements IModuleStore { .then((response) => { runInAction(() => { set(this.moduleMap, [moduleId, "archived_at"], response.archived_at); + if (this.rootStore.favorite.entityMap[moduleId]) this.rootStore.favorite.removeFavoriteFromStore(moduleId); }); }) .catch((error) => { diff --git a/web/core/store/pages/page.ts b/web/core/store/pages/page.ts index 507259de75a..47096912656 100644 --- a/web/core/store/pages/page.ts +++ b/web/core/store/pages/page.ts @@ -443,6 +443,7 @@ export class Page implements IPage { runInAction(() => { this.archived_at = response.archived_at; }); + if (this.rootStore.favorite.entityMap[this.id]) this.rootStore.favorite.removeFavoriteFromStore(this.id); }; /** From 696db600e8de10f6f0680c44c720687aaa1718b4 Mon Sep 17 00:00:00 2001 From: gakshita Date: Tue, 20 Aug 2024 20:14:16 +0530 Subject: [PATCH 4/4] fix: import issue --- .../components/workspace/sidebar/favorites/favorites-menu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx b/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx index 08386e3fcf0..f7b620f7a49 100644 --- a/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx @@ -3,7 +3,7 @@ import React, { useEffect, useRef, useState } from "react"; import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; import { dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; -import { orderBy } from "lodash"; +import orderBy from "lodash/orderBy"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { ChevronRight, FolderPlus } from "lucide-react";