From 51e17643a2d48c071023c7180ec28df09a645c55 Mon Sep 17 00:00:00 2001 From: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com> Date: Mon, 20 Nov 2023 11:59:20 +0530 Subject: [PATCH 1/5] fix: file structuring (#2797) * fix: file structure changes * fix: pages update * fix: license imports changed --- apiserver/plane/api/apps.py | 5 - apiserver/plane/api/urls/public_board.py | 151 ---- apiserver/plane/{api => app}/__init__.py | 0 apiserver/plane/app/apps.py | 5 + .../{api => app}/permissions/__init__.py | 0 .../plane/{api => app}/permissions/project.py | 0 .../{api => app}/permissions/workspace.py | 0 .../{api => app}/serializers/__init__.py | 0 .../{api => app}/serializers/analytic.py | 0 .../plane/{api => app}/serializers/api.py | 0 .../plane/{api => app}/serializers/asset.py | 0 .../plane/{api => app}/serializers/base.py | 0 .../plane/{api => app}/serializers/cycle.py | 0 .../{api => app}/serializers/estimate.py | 2 +- .../{api => app}/serializers/exporter.py | 0 .../{api => app}/serializers/importer.py | 0 .../plane/{api => app}/serializers/inbox.py | 0 .../serializers/integration/__init__.py | 0 .../serializers/integration/base.py | 2 +- .../serializers/integration/github.py | 2 +- .../serializers/integration/slack.py | 2 +- .../plane/{api => app}/serializers/issue.py | 0 .../plane/{api => app}/serializers/module.py | 0 .../{api => app}/serializers/notification.py | 0 .../plane/{api => app}/serializers/page.py | 0 .../plane/{api => app}/serializers/project.py | 4 +- .../plane/{api => app}/serializers/state.py | 3 +- .../plane/{api => app}/serializers/user.py | 0 .../plane/{api => app}/serializers/view.py | 0 .../plane/{api => app}/serializers/webhook.py | 0 .../{api => app}/serializers/workspace.py | 0 apiserver/plane/{api => app}/urls/__init__.py | 2 - apiserver/plane/{api => app}/urls/analytic.py | 2 +- apiserver/plane/{api => app}/urls/api.py | 2 +- apiserver/plane/{api => app}/urls/asset.py | 2 +- .../plane/{api => app}/urls/authentication.py | 2 +- apiserver/plane/{api => app}/urls/config.py | 2 +- apiserver/plane/{api => app}/urls/cycle.py | 2 +- apiserver/plane/{api => app}/urls/estimate.py | 2 +- apiserver/plane/{api => app}/urls/external.py | 6 +- apiserver/plane/{api => app}/urls/importer.py | 2 +- apiserver/plane/{api => app}/urls/inbox.py | 2 +- .../plane/{api => app}/urls/integration.py | 2 +- apiserver/plane/{api => app}/urls/issue.py | 2 +- apiserver/plane/{api => app}/urls/module.py | 2 +- .../plane/{api => app}/urls/notification.py | 2 +- apiserver/plane/{api => app}/urls/page.py | 2 +- apiserver/plane/{api => app}/urls/project.py | 26 +- apiserver/plane/{api => app}/urls/search.py | 2 +- apiserver/plane/{api => app}/urls/state.py | 2 +- apiserver/plane/{api => app}/urls/user.py | 2 +- apiserver/plane/{api => app}/urls/views.py | 2 +- apiserver/plane/{api => app}/urls/webhook.py | 2 +- .../plane/{api => app}/urls/workspace.py | 2 +- .../plane/{api => app}/urls_deprecated.py | 2 +- .../plane/{api => app}/views/__init__.py | 12 +- .../plane/{api => app}/views/analytic.py | 7 +- apiserver/plane/{api => app}/views/api.py | 4 +- apiserver/plane/{api => app}/views/asset.py | 5 +- .../plane/{api => app}/views/auth_extended.py | 2 +- .../{api => app}/views/authentication.py | 1 + apiserver/plane/{api => app}/views/base.py | 0 apiserver/plane/{api => app}/views/config.py | 1 - apiserver/plane/{api => app}/views/cycle.py | 5 +- .../plane/{api => app}/views/estimate.py | 5 +- .../plane/{api => app}/views/exporter.py | 5 +- .../plane/{api => app}/views/external.py | 6 +- .../plane/{api => app}/views/importer.py | 7 +- apiserver/plane/{api => app}/views/inbox.py | 256 +------ .../views/integration/__init__.py | 0 .../{api => app}/views/integration/base.py | 6 +- .../{api => app}/views/integration/github.py | 6 +- .../{api => app}/views/integration/slack.py | 6 +- apiserver/plane/{api => app}/views/issue.py | 608 +--------------- apiserver/plane/{api => app}/views/module.py | 6 +- .../plane/{api => app}/views/notification.py | 3 +- apiserver/plane/{api => app}/views/oauth.py | 1 - apiserver/plane/{api => app}/views/page.py | 74 +- apiserver/plane/{api => app}/views/project.py | 113 +-- apiserver/plane/{api => app}/views/search.py | 1 - apiserver/plane/{api => app}/views/state.py | 7 +- apiserver/plane/{api => app}/views/user.py | 5 +- apiserver/plane/{api => app}/views/view.py | 7 +- apiserver/plane/{api => app}/views/webhook.py | 4 +- .../plane/{api => app}/views/workspace.py | 8 +- apiserver/plane/bgtasks/importer_task.py | 2 +- .../plane/bgtasks/issue_activites_task.py | 5 +- .../plane/license/api/serializers/instance.py | 4 +- apiserver/plane/license/api/views/instance.py | 2 +- apiserver/plane/settings/common.py | 3 +- apiserver/plane/space/__init__.py | 0 apiserver/plane/space/apps.py | 5 + apiserver/plane/space/serializer/__init__.py | 5 + apiserver/plane/space/serializer/base.py | 58 ++ apiserver/plane/space/serializer/cycle.py | 18 + apiserver/plane/space/serializer/inbox.py | 47 ++ apiserver/plane/space/serializer/issue.py | 506 ++++++++++++++ apiserver/plane/space/serializer/module.py | 18 + apiserver/plane/space/serializer/project.py | 20 + apiserver/plane/space/serializer/state.py | 28 + apiserver/plane/space/serializer/user.py | 22 + apiserver/plane/space/serializer/workspace.py | 15 + apiserver/plane/space/urls/__init__.py | 10 + apiserver/plane/space/urls/inbox.py | 49 ++ apiserver/plane/space/urls/issue.py | 76 ++ apiserver/plane/space/urls/project.py | 20 + apiserver/plane/space/views/__init__.py | 15 + apiserver/plane/space/views/base.py | 212 ++++++ apiserver/plane/space/views/inbox.py | 282 ++++++++ apiserver/plane/space/views/issue.py | 656 ++++++++++++++++++ apiserver/plane/space/views/project.py | 61 ++ apiserver/plane/tests/api/base.py | 2 +- apiserver/plane/urls.py | 3 +- 113 files changed, 2306 insertions(+), 1261 deletions(-) delete mode 100644 apiserver/plane/api/apps.py delete mode 100644 apiserver/plane/api/urls/public_board.py rename apiserver/plane/{api => app}/__init__.py (100%) create mode 100644 apiserver/plane/app/apps.py rename apiserver/plane/{api => app}/permissions/__init__.py (100%) rename apiserver/plane/{api => app}/permissions/project.py (100%) rename apiserver/plane/{api => app}/permissions/workspace.py (100%) rename apiserver/plane/{api => app}/serializers/__init__.py (100%) rename apiserver/plane/{api => app}/serializers/analytic.py (100%) rename apiserver/plane/{api => app}/serializers/api.py (100%) rename apiserver/plane/{api => app}/serializers/asset.py (100%) rename apiserver/plane/{api => app}/serializers/base.py (100%) rename apiserver/plane/{api => app}/serializers/cycle.py (100%) rename apiserver/plane/{api => app}/serializers/estimate.py (94%) rename apiserver/plane/{api => app}/serializers/exporter.py (100%) rename apiserver/plane/{api => app}/serializers/importer.py (100%) rename apiserver/plane/{api => app}/serializers/inbox.py (100%) rename apiserver/plane/{api => app}/serializers/integration/__init__.py (100%) rename apiserver/plane/{api => app}/serializers/integration/base.py (90%) rename apiserver/plane/{api => app}/serializers/integration/github.py (95%) rename apiserver/plane/{api => app}/serializers/integration/slack.py (86%) rename apiserver/plane/{api => app}/serializers/issue.py (100%) rename apiserver/plane/{api => app}/serializers/module.py (100%) rename apiserver/plane/{api => app}/serializers/notification.py (100%) rename apiserver/plane/{api => app}/serializers/page.py (100%) rename apiserver/plane/{api => app}/serializers/project.py (97%) rename apiserver/plane/{api => app}/serializers/state.py (84%) rename apiserver/plane/{api => app}/serializers/user.py (100%) rename apiserver/plane/{api => app}/serializers/view.py (100%) rename apiserver/plane/{api => app}/serializers/webhook.py (100%) rename apiserver/plane/{api => app}/serializers/workspace.py (100%) rename apiserver/plane/{api => app}/urls/__init__.py (94%) rename apiserver/plane/{api => app}/urls/analytic.py (97%) rename apiserver/plane/{api => app}/urls/api.py (88%) rename apiserver/plane/{api => app}/urls/asset.py (95%) rename apiserver/plane/{api => app}/urls/authentication.py (98%) rename apiserver/plane/{api => app}/urls/config.py (75%) rename apiserver/plane/{api => app}/urls/cycle.py (98%) rename apiserver/plane/{api => app}/urls/estimate.py (96%) rename apiserver/plane/{api => app}/urls/external.py (74%) rename apiserver/plane/{api => app}/urls/importer.py (96%) rename apiserver/plane/{api => app}/urls/inbox.py (97%) rename apiserver/plane/{api => app}/urls/integration.py (99%) rename apiserver/plane/{api => app}/urls/issue.py (99%) rename apiserver/plane/{api => app}/urls/module.py (99%) rename apiserver/plane/{api => app}/urls/notification.py (98%) rename apiserver/plane/{api => app}/urls/page.py (99%) rename apiserver/plane/{api => app}/urls/project.py (85%) rename apiserver/plane/{api => app}/urls/search.py (93%) rename apiserver/plane/{api => app}/urls/state.py (95%) rename apiserver/plane/{api => app}/urls/user.py (98%) rename apiserver/plane/{api => app}/urls/views.py (98%) rename apiserver/plane/{api => app}/urls/webhook.py (95%) rename apiserver/plane/{api => app}/urls/workspace.py (99%) rename apiserver/plane/{api => app}/urls_deprecated.py (99%) rename apiserver/plane/{api => app}/views/__init__.py (92%) rename apiserver/plane/{api => app}/views/analytic.py (98%) rename apiserver/plane/{api => app}/views/api.py (95%) rename apiserver/plane/{api => app}/views/asset.py (95%) rename apiserver/plane/{api => app}/views/auth_extended.py (99%) rename apiserver/plane/{api => app}/views/authentication.py (99%) rename apiserver/plane/{api => app}/views/base.py (100%) rename apiserver/plane/{api => app}/views/config.py (98%) rename apiserver/plane/{api => app}/views/cycle.py (99%) rename apiserver/plane/{api => app}/views/estimate.py (97%) rename apiserver/plane/{api => app}/views/exporter.py (94%) rename apiserver/plane/{api => app}/views/external.py (94%) rename apiserver/plane/{api => app}/views/importer.py (99%) rename apiserver/plane/{api => app}/views/inbox.py (56%) rename apiserver/plane/{api => app}/views/integration/__init__.py (100%) rename apiserver/plane/{api => app}/views/integration/base.py (97%) rename apiserver/plane/{api => app}/views/integration/github.py (97%) rename apiserver/plane/{api => app}/views/integration/slack.py (94%) rename apiserver/plane/{api => app}/views/issue.py (73%) rename apiserver/plane/{api => app}/views/module.py (99%) rename apiserver/plane/{api => app}/views/notification.py (99%) rename apiserver/plane/{api => app}/views/oauth.py (99%) rename apiserver/plane/{api => app}/views/page.py (90%) rename apiserver/plane/{api => app}/views/project.py (96%) rename apiserver/plane/{api => app}/views/search.py (99%) rename apiserver/plane/{api => app}/views/state.py (94%) rename apiserver/plane/{api => app}/views/user.py (95%) rename apiserver/plane/{api => app}/views/view.py (97%) rename apiserver/plane/{api => app}/views/webhook.py (97%) rename apiserver/plane/{api => app}/views/workspace.py (99%) create mode 100644 apiserver/plane/space/__init__.py create mode 100644 apiserver/plane/space/apps.py create mode 100644 apiserver/plane/space/serializer/__init__.py create mode 100644 apiserver/plane/space/serializer/base.py create mode 100644 apiserver/plane/space/serializer/cycle.py create mode 100644 apiserver/plane/space/serializer/inbox.py create mode 100644 apiserver/plane/space/serializer/issue.py create mode 100644 apiserver/plane/space/serializer/module.py create mode 100644 apiserver/plane/space/serializer/project.py create mode 100644 apiserver/plane/space/serializer/state.py create mode 100644 apiserver/plane/space/serializer/user.py create mode 100644 apiserver/plane/space/serializer/workspace.py create mode 100644 apiserver/plane/space/urls/__init__.py create mode 100644 apiserver/plane/space/urls/inbox.py create mode 100644 apiserver/plane/space/urls/issue.py create mode 100644 apiserver/plane/space/urls/project.py create mode 100644 apiserver/plane/space/views/__init__.py create mode 100644 apiserver/plane/space/views/base.py create mode 100644 apiserver/plane/space/views/inbox.py create mode 100644 apiserver/plane/space/views/issue.py create mode 100644 apiserver/plane/space/views/project.py diff --git a/apiserver/plane/api/apps.py b/apiserver/plane/api/apps.py deleted file mode 100644 index 6ba36e7e5..000000000 --- a/apiserver/plane/api/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class ApiConfig(AppConfig): - name = "plane.api" diff --git a/apiserver/plane/api/urls/public_board.py b/apiserver/plane/api/urls/public_board.py deleted file mode 100644 index 272d5961c..000000000 --- a/apiserver/plane/api/urls/public_board.py +++ /dev/null @@ -1,151 +0,0 @@ -from django.urls import path - - -from plane.api.views import ( - ProjectDeployBoardViewSet, - ProjectDeployBoardPublicSettingsEndpoint, - ProjectIssuesPublicEndpoint, - IssueRetrievePublicEndpoint, - IssueCommentPublicViewSet, - IssueReactionPublicViewSet, - CommentReactionPublicViewSet, - InboxIssuePublicViewSet, - IssueVotePublicViewSet, - WorkspaceProjectDeployBoardEndpoint, -) - - -urlpatterns = [ - path( - "workspaces//projects//project-deploy-boards/", - ProjectDeployBoardViewSet.as_view( - { - "get": "list", - "post": "create", - } - ), - name="project-deploy-board", - ), - path( - "workspaces//projects//project-deploy-boards//", - ProjectDeployBoardViewSet.as_view( - { - "get": "retrieve", - "patch": "partial_update", - "delete": "destroy", - } - ), - name="project-deploy-board", - ), - path( - "public/workspaces//project-boards//settings/", - ProjectDeployBoardPublicSettingsEndpoint.as_view(), - name="project-deploy-board-settings", - ), - path( - "public/workspaces//project-boards//issues/", - ProjectIssuesPublicEndpoint.as_view(), - name="project-deploy-board", - ), - path( - "public/workspaces//project-boards//issues//", - IssueRetrievePublicEndpoint.as_view(), - name="workspace-project-boards", - ), - path( - "public/workspaces//project-boards//issues//comments/", - IssueCommentPublicViewSet.as_view( - { - "get": "list", - "post": "create", - } - ), - name="issue-comments-project-board", - ), - path( - "public/workspaces//project-boards//issues//comments//", - IssueCommentPublicViewSet.as_view( - { - "get": "retrieve", - "patch": "partial_update", - "delete": "destroy", - } - ), - name="issue-comments-project-board", - ), - path( - "public/workspaces//project-boards//issues//reactions/", - IssueReactionPublicViewSet.as_view( - { - "get": "list", - "post": "create", - } - ), - name="issue-reactions-project-board", - ), - path( - "public/workspaces//project-boards//issues//reactions//", - IssueReactionPublicViewSet.as_view( - { - "delete": "destroy", - } - ), - name="issue-reactions-project-board", - ), - path( - "public/workspaces//project-boards//comments//reactions/", - CommentReactionPublicViewSet.as_view( - { - "get": "list", - "post": "create", - } - ), - name="comment-reactions-project-board", - ), - path( - "public/workspaces//project-boards//comments//reactions//", - CommentReactionPublicViewSet.as_view( - { - "delete": "destroy", - } - ), - name="comment-reactions-project-board", - ), - path( - "public/workspaces//project-boards//inboxes//inbox-issues/", - InboxIssuePublicViewSet.as_view( - { - "get": "list", - "post": "create", - } - ), - name="inbox-issue", - ), - path( - "public/workspaces//project-boards//inboxes//inbox-issues//", - InboxIssuePublicViewSet.as_view( - { - "get": "retrieve", - "patch": "partial_update", - "delete": "destroy", - } - ), - name="inbox-issue", - ), - path( - "public/workspaces//project-boards//issues//votes/", - IssueVotePublicViewSet.as_view( - { - "get": "list", - "post": "create", - "delete": "destroy", - } - ), - name="issue-vote-project-board", - ), - path( - "public/workspaces//project-boards/", - WorkspaceProjectDeployBoardEndpoint.as_view(), - name="workspace-project-boards", - ), -] diff --git a/apiserver/plane/api/__init__.py b/apiserver/plane/app/__init__.py similarity index 100% rename from apiserver/plane/api/__init__.py rename to apiserver/plane/app/__init__.py diff --git a/apiserver/plane/app/apps.py b/apiserver/plane/app/apps.py new file mode 100644 index 000000000..6057d131a --- /dev/null +++ b/apiserver/plane/app/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class AppConfig(AppConfig): + name = "plane.app" diff --git a/apiserver/plane/api/permissions/__init__.py b/apiserver/plane/app/permissions/__init__.py similarity index 100% rename from apiserver/plane/api/permissions/__init__.py rename to apiserver/plane/app/permissions/__init__.py diff --git a/apiserver/plane/api/permissions/project.py b/apiserver/plane/app/permissions/project.py similarity index 100% rename from apiserver/plane/api/permissions/project.py rename to apiserver/plane/app/permissions/project.py diff --git a/apiserver/plane/api/permissions/workspace.py b/apiserver/plane/app/permissions/workspace.py similarity index 100% rename from apiserver/plane/api/permissions/workspace.py rename to apiserver/plane/app/permissions/workspace.py diff --git a/apiserver/plane/api/serializers/__init__.py b/apiserver/plane/app/serializers/__init__.py similarity index 100% rename from apiserver/plane/api/serializers/__init__.py rename to apiserver/plane/app/serializers/__init__.py diff --git a/apiserver/plane/api/serializers/analytic.py b/apiserver/plane/app/serializers/analytic.py similarity index 100% rename from apiserver/plane/api/serializers/analytic.py rename to apiserver/plane/app/serializers/analytic.py diff --git a/apiserver/plane/api/serializers/api.py b/apiserver/plane/app/serializers/api.py similarity index 100% rename from apiserver/plane/api/serializers/api.py rename to apiserver/plane/app/serializers/api.py diff --git a/apiserver/plane/api/serializers/asset.py b/apiserver/plane/app/serializers/asset.py similarity index 100% rename from apiserver/plane/api/serializers/asset.py rename to apiserver/plane/app/serializers/asset.py diff --git a/apiserver/plane/api/serializers/base.py b/apiserver/plane/app/serializers/base.py similarity index 100% rename from apiserver/plane/api/serializers/base.py rename to apiserver/plane/app/serializers/base.py diff --git a/apiserver/plane/api/serializers/cycle.py b/apiserver/plane/app/serializers/cycle.py similarity index 100% rename from apiserver/plane/api/serializers/cycle.py rename to apiserver/plane/app/serializers/cycle.py diff --git a/apiserver/plane/api/serializers/estimate.py b/apiserver/plane/app/serializers/estimate.py similarity index 94% rename from apiserver/plane/api/serializers/estimate.py rename to apiserver/plane/app/serializers/estimate.py index 3cb0e4713..4a1cda779 100644 --- a/apiserver/plane/api/serializers/estimate.py +++ b/apiserver/plane/app/serializers/estimate.py @@ -2,7 +2,7 @@ from .base import BaseSerializer from plane.db.models import Estimate, EstimatePoint -from plane.api.serializers import WorkspaceLiteSerializer, ProjectLiteSerializer +from plane.app.serializers import WorkspaceLiteSerializer, ProjectLiteSerializer class EstimateSerializer(BaseSerializer): diff --git a/apiserver/plane/api/serializers/exporter.py b/apiserver/plane/app/serializers/exporter.py similarity index 100% rename from apiserver/plane/api/serializers/exporter.py rename to apiserver/plane/app/serializers/exporter.py diff --git a/apiserver/plane/api/serializers/importer.py b/apiserver/plane/app/serializers/importer.py similarity index 100% rename from apiserver/plane/api/serializers/importer.py rename to apiserver/plane/app/serializers/importer.py diff --git a/apiserver/plane/api/serializers/inbox.py b/apiserver/plane/app/serializers/inbox.py similarity index 100% rename from apiserver/plane/api/serializers/inbox.py rename to apiserver/plane/app/serializers/inbox.py diff --git a/apiserver/plane/api/serializers/integration/__init__.py b/apiserver/plane/app/serializers/integration/__init__.py similarity index 100% rename from apiserver/plane/api/serializers/integration/__init__.py rename to apiserver/plane/app/serializers/integration/__init__.py diff --git a/apiserver/plane/api/serializers/integration/base.py b/apiserver/plane/app/serializers/integration/base.py similarity index 90% rename from apiserver/plane/api/serializers/integration/base.py rename to apiserver/plane/app/serializers/integration/base.py index 10ebd4620..6f6543b9e 100644 --- a/apiserver/plane/api/serializers/integration/base.py +++ b/apiserver/plane/app/serializers/integration/base.py @@ -1,5 +1,5 @@ # Module imports -from plane.api.serializers import BaseSerializer +from plane.app.serializers import BaseSerializer from plane.db.models import Integration, WorkspaceIntegration diff --git a/apiserver/plane/api/serializers/integration/github.py b/apiserver/plane/app/serializers/integration/github.py similarity index 95% rename from apiserver/plane/api/serializers/integration/github.py rename to apiserver/plane/app/serializers/integration/github.py index 8352dcee1..850bccf1b 100644 --- a/apiserver/plane/api/serializers/integration/github.py +++ b/apiserver/plane/app/serializers/integration/github.py @@ -1,5 +1,5 @@ # Module imports -from plane.api.serializers import BaseSerializer +from plane.app.serializers import BaseSerializer from plane.db.models import ( GithubIssueSync, GithubRepository, diff --git a/apiserver/plane/api/serializers/integration/slack.py b/apiserver/plane/app/serializers/integration/slack.py similarity index 86% rename from apiserver/plane/api/serializers/integration/slack.py rename to apiserver/plane/app/serializers/integration/slack.py index f535a64de..9c461c5b9 100644 --- a/apiserver/plane/api/serializers/integration/slack.py +++ b/apiserver/plane/app/serializers/integration/slack.py @@ -1,5 +1,5 @@ # Module imports -from plane.api.serializers import BaseSerializer +from plane.app.serializers import BaseSerializer from plane.db.models import SlackProjectSync diff --git a/apiserver/plane/api/serializers/issue.py b/apiserver/plane/app/serializers/issue.py similarity index 100% rename from apiserver/plane/api/serializers/issue.py rename to apiserver/plane/app/serializers/issue.py diff --git a/apiserver/plane/api/serializers/module.py b/apiserver/plane/app/serializers/module.py similarity index 100% rename from apiserver/plane/api/serializers/module.py rename to apiserver/plane/app/serializers/module.py diff --git a/apiserver/plane/api/serializers/notification.py b/apiserver/plane/app/serializers/notification.py similarity index 100% rename from apiserver/plane/api/serializers/notification.py rename to apiserver/plane/app/serializers/notification.py diff --git a/apiserver/plane/api/serializers/page.py b/apiserver/plane/app/serializers/page.py similarity index 100% rename from apiserver/plane/api/serializers/page.py rename to apiserver/plane/app/serializers/page.py diff --git a/apiserver/plane/api/serializers/project.py b/apiserver/plane/app/serializers/project.py similarity index 97% rename from apiserver/plane/api/serializers/project.py rename to apiserver/plane/app/serializers/project.py index 9ecae555c..e9bdf7be4 100644 --- a/apiserver/plane/api/serializers/project.py +++ b/apiserver/plane/app/serializers/project.py @@ -3,8 +3,8 @@ from rest_framework import serializers # Module imports from .base import BaseSerializer, DynamicBaseSerializer -from plane.api.serializers.workspace import WorkSpaceSerializer, WorkspaceLiteSerializer -from plane.api.serializers.user import UserLiteSerializer, UserAdminLiteSerializer +from plane.app.serializers.workspace import WorkspaceLiteSerializer +from plane.app.serializers.user import UserLiteSerializer, UserAdminLiteSerializer from plane.db.models import ( Project, ProjectMember, diff --git a/apiserver/plane/api/serializers/state.py b/apiserver/plane/app/serializers/state.py similarity index 84% rename from apiserver/plane/api/serializers/state.py rename to apiserver/plane/app/serializers/state.py index ad416c340..7cf645fae 100644 --- a/apiserver/plane/api/serializers/state.py +++ b/apiserver/plane/app/serializers/state.py @@ -1,7 +1,6 @@ # Module imports from .base import BaseSerializer -from .workspace import WorkspaceLiteSerializer -from .project import ProjectLiteSerializer + from plane.db.models import State diff --git a/apiserver/plane/api/serializers/user.py b/apiserver/plane/app/serializers/user.py similarity index 100% rename from apiserver/plane/api/serializers/user.py rename to apiserver/plane/app/serializers/user.py diff --git a/apiserver/plane/api/serializers/view.py b/apiserver/plane/app/serializers/view.py similarity index 100% rename from apiserver/plane/api/serializers/view.py rename to apiserver/plane/app/serializers/view.py diff --git a/apiserver/plane/api/serializers/webhook.py b/apiserver/plane/app/serializers/webhook.py similarity index 100% rename from apiserver/plane/api/serializers/webhook.py rename to apiserver/plane/app/serializers/webhook.py diff --git a/apiserver/plane/api/serializers/workspace.py b/apiserver/plane/app/serializers/workspace.py similarity index 100% rename from apiserver/plane/api/serializers/workspace.py rename to apiserver/plane/app/serializers/workspace.py diff --git a/apiserver/plane/api/urls/__init__.py b/apiserver/plane/app/urls/__init__.py similarity index 94% rename from apiserver/plane/api/urls/__init__.py rename to apiserver/plane/app/urls/__init__.py index e6088cb14..7d057ad9e 100644 --- a/apiserver/plane/api/urls/__init__.py +++ b/apiserver/plane/app/urls/__init__.py @@ -13,7 +13,6 @@ from .module import urlpatterns as module_urls from .notification import urlpatterns as notification_urls from .page import urlpatterns as page_urls from .project import urlpatterns as project_urls -from .public_board import urlpatterns as public_board_urls from .search import urlpatterns as search_urls from .state import urlpatterns as state_urls from .user import urlpatterns as user_urls @@ -43,7 +42,6 @@ urlpatterns = [ *notification_urls, *page_urls, *project_urls, - *public_board_urls, *search_urls, *state_urls, *user_urls, diff --git a/apiserver/plane/api/urls/analytic.py b/apiserver/plane/app/urls/analytic.py similarity index 97% rename from apiserver/plane/api/urls/analytic.py rename to apiserver/plane/app/urls/analytic.py index cb6155e32..668268350 100644 --- a/apiserver/plane/api/urls/analytic.py +++ b/apiserver/plane/app/urls/analytic.py @@ -1,7 +1,7 @@ from django.urls import path -from plane.api.views import ( +from plane.app.views import ( AnalyticsEndpoint, AnalyticViewViewset, SavedAnalyticEndpoint, diff --git a/apiserver/plane/api/urls/api.py b/apiserver/plane/app/urls/api.py similarity index 88% rename from apiserver/plane/api/urls/api.py rename to apiserver/plane/app/urls/api.py index 1a2862045..b77ea8530 100644 --- a/apiserver/plane/api/urls/api.py +++ b/apiserver/plane/app/urls/api.py @@ -1,5 +1,5 @@ from django.urls import path -from plane.api.views import ApiTokenEndpoint +from plane.app.views import ApiTokenEndpoint urlpatterns = [ # API Tokens diff --git a/apiserver/plane/api/urls/asset.py b/apiserver/plane/app/urls/asset.py similarity index 95% rename from apiserver/plane/api/urls/asset.py rename to apiserver/plane/app/urls/asset.py index b6ae9f42c..11ec8b8e8 100644 --- a/apiserver/plane/api/urls/asset.py +++ b/apiserver/plane/app/urls/asset.py @@ -1,7 +1,7 @@ from django.urls import path -from plane.api.views import ( +from plane.app.views import ( FileAssetEndpoint, UserAssetsEndpoint, ) diff --git a/apiserver/plane/api/urls/authentication.py b/apiserver/plane/app/urls/authentication.py similarity index 98% rename from apiserver/plane/api/urls/authentication.py rename to apiserver/plane/app/urls/authentication.py index 44b7000ea..6111075f2 100644 --- a/apiserver/plane/api/urls/authentication.py +++ b/apiserver/plane/app/urls/authentication.py @@ -3,7 +3,7 @@ from django.urls import path from rest_framework_simplejwt.views import TokenRefreshView -from plane.api.views import ( +from plane.app.views import ( # Authentication SignUpEndpoint, SignInEndpoint, diff --git a/apiserver/plane/api/urls/config.py b/apiserver/plane/app/urls/config.py similarity index 75% rename from apiserver/plane/api/urls/config.py rename to apiserver/plane/app/urls/config.py index 321a56200..12beb63aa 100644 --- a/apiserver/plane/api/urls/config.py +++ b/apiserver/plane/app/urls/config.py @@ -1,7 +1,7 @@ from django.urls import path -from plane.api.views import ConfigurationEndpoint +from plane.app.views import ConfigurationEndpoint urlpatterns = [ path( diff --git a/apiserver/plane/api/urls/cycle.py b/apiserver/plane/app/urls/cycle.py similarity index 98% rename from apiserver/plane/api/urls/cycle.py rename to apiserver/plane/app/urls/cycle.py index 7e6f014fc..0e786e291 100644 --- a/apiserver/plane/api/urls/cycle.py +++ b/apiserver/plane/app/urls/cycle.py @@ -1,7 +1,7 @@ from django.urls import path -from plane.api.views import ( +from plane.app.views import ( CycleViewSet, CycleIssueViewSet, CycleDateCheckEndpoint, diff --git a/apiserver/plane/api/urls/estimate.py b/apiserver/plane/app/urls/estimate.py similarity index 96% rename from apiserver/plane/api/urls/estimate.py rename to apiserver/plane/app/urls/estimate.py index 89363e849..d8571ff0c 100644 --- a/apiserver/plane/api/urls/estimate.py +++ b/apiserver/plane/app/urls/estimate.py @@ -1,7 +1,7 @@ from django.urls import path -from plane.api.views import ( +from plane.app.views import ( ProjectEstimatePointEndpoint, BulkEstimatePointEndpoint, ) diff --git a/apiserver/plane/api/urls/external.py b/apiserver/plane/app/urls/external.py similarity index 74% rename from apiserver/plane/api/urls/external.py rename to apiserver/plane/app/urls/external.py index c22289035..774e6fb7c 100644 --- a/apiserver/plane/api/urls/external.py +++ b/apiserver/plane/app/urls/external.py @@ -1,9 +1,9 @@ from django.urls import path -from plane.api.views import UnsplashEndpoint -from plane.api.views import ReleaseNotesEndpoint -from plane.api.views import GPTIntegrationEndpoint +from plane.app.views import UnsplashEndpoint +from plane.app.views import ReleaseNotesEndpoint +from plane.app.views import GPTIntegrationEndpoint urlpatterns = [ diff --git a/apiserver/plane/api/urls/importer.py b/apiserver/plane/app/urls/importer.py similarity index 96% rename from apiserver/plane/api/urls/importer.py rename to apiserver/plane/app/urls/importer.py index c0a9aa5b5..f3a018d78 100644 --- a/apiserver/plane/api/urls/importer.py +++ b/apiserver/plane/app/urls/importer.py @@ -1,7 +1,7 @@ from django.urls import path -from plane.api.views import ( +from plane.app.views import ( ServiceIssueImportSummaryEndpoint, ImportServiceEndpoint, UpdateServiceImportStatusEndpoint, diff --git a/apiserver/plane/api/urls/inbox.py b/apiserver/plane/app/urls/inbox.py similarity index 97% rename from apiserver/plane/api/urls/inbox.py rename to apiserver/plane/app/urls/inbox.py index 315f30601..16ea40b21 100644 --- a/apiserver/plane/api/urls/inbox.py +++ b/apiserver/plane/app/urls/inbox.py @@ -1,7 +1,7 @@ from django.urls import path -from plane.api.views import ( +from plane.app.views import ( InboxViewSet, InboxIssueViewSet, ) diff --git a/apiserver/plane/api/urls/integration.py b/apiserver/plane/app/urls/integration.py similarity index 99% rename from apiserver/plane/api/urls/integration.py rename to apiserver/plane/app/urls/integration.py index dd431b6c8..cf3f82d5a 100644 --- a/apiserver/plane/api/urls/integration.py +++ b/apiserver/plane/app/urls/integration.py @@ -1,7 +1,7 @@ from django.urls import path -from plane.api.views import ( +from plane.app.views import ( IntegrationViewSet, WorkspaceIntegrationViewSet, GithubRepositoriesEndpoint, diff --git a/apiserver/plane/api/urls/issue.py b/apiserver/plane/app/urls/issue.py similarity index 99% rename from apiserver/plane/api/urls/issue.py rename to apiserver/plane/app/urls/issue.py index 23a8e4fa6..9aa189288 100644 --- a/apiserver/plane/api/urls/issue.py +++ b/apiserver/plane/app/urls/issue.py @@ -1,7 +1,7 @@ from django.urls import path -from plane.api.views import ( +from plane.app.views import ( IssueViewSet, IssueListEndpoint, IssueListGroupedEndpoint, diff --git a/apiserver/plane/api/urls/module.py b/apiserver/plane/app/urls/module.py similarity index 99% rename from apiserver/plane/api/urls/module.py rename to apiserver/plane/app/urls/module.py index d9ca849ed..20c9d9e35 100644 --- a/apiserver/plane/api/urls/module.py +++ b/apiserver/plane/app/urls/module.py @@ -1,7 +1,7 @@ from django.urls import path -from plane.api.views import ( +from plane.app.views import ( ModuleViewSet, ModuleIssueViewSet, ModuleLinkViewSet, diff --git a/apiserver/plane/api/urls/notification.py b/apiserver/plane/app/urls/notification.py similarity index 98% rename from apiserver/plane/api/urls/notification.py rename to apiserver/plane/app/urls/notification.py index 5e1936d01..0c96e5f15 100644 --- a/apiserver/plane/api/urls/notification.py +++ b/apiserver/plane/app/urls/notification.py @@ -1,7 +1,7 @@ from django.urls import path -from plane.api.views import ( +from plane.app.views import ( NotificationViewSet, UnreadNotificationEndpoint, MarkAllReadNotificationViewSet, diff --git a/apiserver/plane/api/urls/page.py b/apiserver/plane/app/urls/page.py similarity index 99% rename from apiserver/plane/api/urls/page.py rename to apiserver/plane/app/urls/page.py index 8b08dcc79..58cec2cd4 100644 --- a/apiserver/plane/api/urls/page.py +++ b/apiserver/plane/app/urls/page.py @@ -1,7 +1,7 @@ from django.urls import path -from plane.api.views import ( +from plane.app.views import ( PageViewSet, PageFavoriteViewSet, PageLogEndpoint, diff --git a/apiserver/plane/api/urls/project.py b/apiserver/plane/app/urls/project.py similarity index 85% rename from apiserver/plane/api/urls/project.py rename to apiserver/plane/app/urls/project.py index 83bb765e6..4f0771952 100644 --- a/apiserver/plane/api/urls/project.py +++ b/apiserver/plane/app/urls/project.py @@ -1,6 +1,6 @@ from django.urls import path -from plane.api.views import ( +from plane.app.views import ( ProjectViewSet, ProjectInvitationsViewset, ProjectMemberViewSet, @@ -10,8 +10,9 @@ from plane.api.views import ( ProjectUserViewsEndpoint, ProjectIdentifierEndpoint, ProjectFavoritesViewSet, - ProjectPublicCoverImagesEndpoint, UserProjectInvitationsViewset, + ProjectPublicCoverImagesEndpoint, + ProjectDeployBoardViewSet, ) @@ -147,4 +148,25 @@ urlpatterns = [ ProjectPublicCoverImagesEndpoint.as_view(), name="project-covers", ), + path( + "workspaces//projects//project-deploy-boards/", + ProjectDeployBoardViewSet.as_view( + { + "get": "list", + "post": "create", + } + ), + name="project-deploy-board", + ), + path( + "workspaces//projects//project-deploy-boards//", + ProjectDeployBoardViewSet.as_view( + { + "get": "retrieve", + "patch": "partial_update", + "delete": "destroy", + } + ), + name="project-deploy-board", + ), ] diff --git a/apiserver/plane/api/urls/search.py b/apiserver/plane/app/urls/search.py similarity index 93% rename from apiserver/plane/api/urls/search.py rename to apiserver/plane/app/urls/search.py index 282feb046..05a79994e 100644 --- a/apiserver/plane/api/urls/search.py +++ b/apiserver/plane/app/urls/search.py @@ -1,7 +1,7 @@ from django.urls import path -from plane.api.views import ( +from plane.app.views import ( GlobalSearchEndpoint, IssueSearchEndpoint, ) diff --git a/apiserver/plane/api/urls/state.py b/apiserver/plane/app/urls/state.py similarity index 95% rename from apiserver/plane/api/urls/state.py rename to apiserver/plane/app/urls/state.py index 94aa55f24..9fec70ea1 100644 --- a/apiserver/plane/api/urls/state.py +++ b/apiserver/plane/app/urls/state.py @@ -1,7 +1,7 @@ from django.urls import path -from plane.api.views import StateViewSet +from plane.app.views import StateViewSet urlpatterns = [ diff --git a/apiserver/plane/api/urls/user.py b/apiserver/plane/app/urls/user.py similarity index 98% rename from apiserver/plane/api/urls/user.py rename to apiserver/plane/app/urls/user.py index da794d59a..c958addad 100644 --- a/apiserver/plane/api/urls/user.py +++ b/apiserver/plane/app/urls/user.py @@ -1,6 +1,6 @@ from django.urls import path -from plane.api.views import ( +from plane.app.views import ( ## User UserEndpoint, UpdateUserOnBoardedEndpoint, diff --git a/apiserver/plane/api/urls/views.py b/apiserver/plane/app/urls/views.py similarity index 98% rename from apiserver/plane/api/urls/views.py rename to apiserver/plane/app/urls/views.py index 560855e80..36372c03a 100644 --- a/apiserver/plane/api/urls/views.py +++ b/apiserver/plane/app/urls/views.py @@ -1,7 +1,7 @@ from django.urls import path -from plane.api.views import ( +from plane.app.views import ( IssueViewViewSet, GlobalViewViewSet, GlobalViewIssuesViewSet, diff --git a/apiserver/plane/api/urls/webhook.py b/apiserver/plane/app/urls/webhook.py similarity index 95% rename from apiserver/plane/api/urls/webhook.py rename to apiserver/plane/app/urls/webhook.py index 74a8da759..16cc48be8 100644 --- a/apiserver/plane/api/urls/webhook.py +++ b/apiserver/plane/app/urls/webhook.py @@ -1,6 +1,6 @@ from django.urls import path -from plane.api.views import ( +from plane.app.views import ( WebhookEndpoint, WebhookLogsEndpoint, WebhookSecretRegenerateEndpoint, diff --git a/apiserver/plane/api/urls/workspace.py b/apiserver/plane/app/urls/workspace.py similarity index 99% rename from apiserver/plane/api/urls/workspace.py rename to apiserver/plane/app/urls/workspace.py index 64e558f10..739d17c55 100644 --- a/apiserver/plane/api/urls/workspace.py +++ b/apiserver/plane/app/urls/workspace.py @@ -1,7 +1,7 @@ from django.urls import path -from plane.api.views import ( +from plane.app.views import ( UserWorkspaceInvitationsViewSet, WorkSpaceViewSet, WorkspaceJoinEndpoint, diff --git a/apiserver/plane/api/urls_deprecated.py b/apiserver/plane/app/urls_deprecated.py similarity index 99% rename from apiserver/plane/api/urls_deprecated.py rename to apiserver/plane/app/urls_deprecated.py index 1f05675a2..c6e6183fa 100644 --- a/apiserver/plane/api/urls_deprecated.py +++ b/apiserver/plane/app/urls_deprecated.py @@ -4,7 +4,7 @@ from rest_framework_simplejwt.views import TokenRefreshView # Create your urls here. -from plane.api.views import ( +from plane.app.views import ( # Authentication SignUpEndpoint, SignInEndpoint, diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/app/views/__init__.py similarity index 92% rename from apiserver/plane/api/views/__init__.py rename to apiserver/plane/app/views/__init__.py index 12b569523..f945f00e3 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/app/views/__init__.py @@ -9,10 +9,8 @@ from .project import ( ProjectUserViewsEndpoint, ProjectMemberUserEndpoint, ProjectFavoritesViewSet, - ProjectDeployBoardViewSet, - ProjectDeployBoardPublicSettingsEndpoint, - WorkspaceProjectDeployBoardEndpoint, ProjectPublicCoverImagesEndpoint, + ProjectDeployBoardViewSet, ) from .user import ( UserEndpoint, @@ -80,15 +78,9 @@ from .issue import ( IssueAttachmentEndpoint, IssueArchiveViewSet, IssueSubscriberViewSet, - IssueCommentPublicViewSet, CommentReactionViewSet, IssueReactionViewSet, - IssueReactionPublicViewSet, - CommentReactionPublicViewSet, - IssueVotePublicViewSet, IssueRelationViewSet, - IssueRetrievePublicEndpoint, - ProjectIssuesPublicEndpoint, IssueDraftViewSet, ) @@ -156,7 +148,7 @@ from .estimate import ( BulkEstimatePointEndpoint, ) -from .inbox import InboxViewSet, InboxIssueViewSet, InboxIssuePublicViewSet +from .inbox import InboxViewSet, InboxIssueViewSet from .analytic import ( AnalyticsEndpoint, diff --git a/apiserver/plane/api/views/analytic.py b/apiserver/plane/app/views/analytic.py similarity index 98% rename from apiserver/plane/api/views/analytic.py rename to apiserver/plane/app/views/analytic.py index c29a4b692..c1deb0d8f 100644 --- a/apiserver/plane/api/views/analytic.py +++ b/apiserver/plane/app/views/analytic.py @@ -5,13 +5,12 @@ from django.db.models.functions import ExtractMonth # Third party imports from rest_framework import status from rest_framework.response import Response -from sentry_sdk import capture_exception # Module imports -from plane.api.views import BaseAPIView, BaseViewSet -from plane.api.permissions import WorkSpaceAdminPermission +from plane.app.views import BaseAPIView, BaseViewSet +from plane.app.permissions import WorkSpaceAdminPermission from plane.db.models import Issue, AnalyticView, Workspace, State, Label -from plane.api.serializers import AnalyticViewSerializer +from plane.app.serializers import AnalyticViewSerializer from plane.utils.analytics_plot import build_graph_plot from plane.bgtasks.analytic_plot_export import analytic_export_task from plane.utils.issue_filters import issue_filters diff --git a/apiserver/plane/api/views/api.py b/apiserver/plane/app/views/api.py similarity index 95% rename from apiserver/plane/api/views/api.py rename to apiserver/plane/app/views/api.py index 59da6d3c4..ce2d4bd09 100644 --- a/apiserver/plane/api/views/api.py +++ b/apiserver/plane/app/views/api.py @@ -8,8 +8,8 @@ from rest_framework import status # Module import from .base import BaseAPIView from plane.db.models import APIToken, Workspace -from plane.api.serializers import APITokenSerializer, APITokenReadSerializer -from plane.api.permissions import WorkspaceOwnerPermission +from plane.app.serializers import APITokenSerializer, APITokenReadSerializer +from plane.app.permissions import WorkspaceOwnerPermission class ApiTokenEndpoint(BaseAPIView): diff --git a/apiserver/plane/api/views/asset.py b/apiserver/plane/app/views/asset.py similarity index 95% rename from apiserver/plane/api/views/asset.py rename to apiserver/plane/app/views/asset.py index 3f5dcceac..eddbb4505 100644 --- a/apiserver/plane/api/views/asset.py +++ b/apiserver/plane/app/views/asset.py @@ -2,12 +2,11 @@ from rest_framework import status from rest_framework.response import Response from rest_framework.parsers import MultiPartParser, FormParser -from sentry_sdk import capture_exception -from django.conf import settings + # Module imports from .base import BaseAPIView from plane.db.models import FileAsset, Workspace -from plane.api.serializers import FileAssetSerializer +from plane.app.serializers import FileAssetSerializer class FileAssetEndpoint(BaseAPIView): diff --git a/apiserver/plane/api/views/auth_extended.py b/apiserver/plane/app/views/auth_extended.py similarity index 99% rename from apiserver/plane/api/views/auth_extended.py rename to apiserver/plane/app/views/auth_extended.py index e2ec9d5b6..5abd696fe 100644 --- a/apiserver/plane/api/views/auth_extended.py +++ b/apiserver/plane/app/views/auth_extended.py @@ -21,7 +21,7 @@ from sentry_sdk import capture_exception ## Module imports from . import BaseAPIView -from plane.api.serializers import ( +from plane.app.serializers import ( ChangePasswordSerializer, ResetPasswordSerializer, ) diff --git a/apiserver/plane/api/views/authentication.py b/apiserver/plane/app/views/authentication.py similarity index 99% rename from apiserver/plane/api/views/authentication.py rename to apiserver/plane/app/views/authentication.py index 2ec241303..93d381117 100644 --- a/apiserver/plane/api/views/authentication.py +++ b/apiserver/plane/app/views/authentication.py @@ -5,6 +5,7 @@ import string import json import requests from requests.exceptions import RequestException + # Django imports from django.utils import timezone from django.core.exceptions import ValidationError diff --git a/apiserver/plane/api/views/base.py b/apiserver/plane/app/views/base.py similarity index 100% rename from apiserver/plane/api/views/base.py rename to apiserver/plane/app/views/base.py diff --git a/apiserver/plane/api/views/config.py b/apiserver/plane/app/views/config.py similarity index 98% rename from apiserver/plane/api/views/config.py rename to apiserver/plane/app/views/config.py index 237d8d6bf..4a6b05859 100644 --- a/apiserver/plane/api/views/config.py +++ b/apiserver/plane/app/views/config.py @@ -8,7 +8,6 @@ from django.conf import settings from rest_framework.permissions import AllowAny from rest_framework import status from rest_framework.response import Response -from sentry_sdk import capture_exception # Module imports from .base import BaseAPIView diff --git a/apiserver/plane/api/views/cycle.py b/apiserver/plane/app/views/cycle.py similarity index 99% rename from apiserver/plane/api/views/cycle.py rename to apiserver/plane/app/views/cycle.py index 06df22077..a590dc214 100644 --- a/apiserver/plane/api/views/cycle.py +++ b/apiserver/plane/app/views/cycle.py @@ -20,18 +20,17 @@ from django.views.decorators.gzip import gzip_page # Third party imports from rest_framework.response import Response from rest_framework import status -from sentry_sdk import capture_exception # Module imports from . import BaseViewSet, BaseAPIView, WebhookMixin -from plane.api.serializers import ( +from plane.app.serializers import ( CycleSerializer, CycleIssueSerializer, CycleFavoriteSerializer, IssueStateSerializer, CycleWriteSerializer, ) -from plane.api.permissions import ProjectEntityPermission +from plane.app.permissions import ProjectEntityPermission from plane.db.models import ( User, Cycle, diff --git a/apiserver/plane/api/views/estimate.py b/apiserver/plane/app/views/estimate.py similarity index 97% rename from apiserver/plane/api/views/estimate.py rename to apiserver/plane/app/views/estimate.py index 3c2cca4d5..ec9393f5b 100644 --- a/apiserver/plane/api/views/estimate.py +++ b/apiserver/plane/app/views/estimate.py @@ -1,13 +1,12 @@ # Third party imports from rest_framework.response import Response from rest_framework import status -from sentry_sdk import capture_exception # Module imports from .base import BaseViewSet, BaseAPIView -from plane.api.permissions import ProjectEntityPermission +from plane.app.permissions import ProjectEntityPermission from plane.db.models import Project, Estimate, EstimatePoint -from plane.api.serializers import ( +from plane.app.serializers import ( EstimateSerializer, EstimatePointSerializer, EstimateReadSerializer, diff --git a/apiserver/plane/api/views/exporter.py b/apiserver/plane/app/views/exporter.py similarity index 94% rename from apiserver/plane/api/views/exporter.py rename to apiserver/plane/app/views/exporter.py index 03da8932f..b709a599d 100644 --- a/apiserver/plane/api/views/exporter.py +++ b/apiserver/plane/app/views/exporter.py @@ -1,15 +1,14 @@ # Third Party imports from rest_framework.response import Response from rest_framework import status -from sentry_sdk import capture_exception # Module imports from . import BaseAPIView -from plane.api.permissions import WorkSpaceAdminPermission +from plane.app.permissions import WorkSpaceAdminPermission from plane.bgtasks.export_task import issue_export_task from plane.db.models import Project, ExporterHistory, Workspace -from plane.api.serializers import ExporterHistorySerializer +from plane.app.serializers import ExporterHistorySerializer class ExportIssuesEndpoint(BaseAPIView): diff --git a/apiserver/plane/api/views/external.py b/apiserver/plane/app/views/external.py similarity index 94% rename from apiserver/plane/api/views/external.py rename to apiserver/plane/app/views/external.py index 1953743a2..ac502c186 100644 --- a/apiserver/plane/api/views/external.py +++ b/apiserver/plane/app/views/external.py @@ -5,17 +5,15 @@ import requests from openai import OpenAI from rest_framework.response import Response from rest_framework import status -from rest_framework.permissions import AllowAny -from sentry_sdk import capture_exception # Django imports from django.conf import settings # Module imports from .base import BaseAPIView -from plane.api.permissions import ProjectEntityPermission +from plane.app.permissions import ProjectEntityPermission from plane.db.models import Workspace, Project -from plane.api.serializers import ProjectLiteSerializer, WorkspaceLiteSerializer +from plane.app.serializers import ProjectLiteSerializer, WorkspaceLiteSerializer from plane.utils.integrations.github import get_release_notes from plane.license.models import InstanceConfiguration from plane.license.utils.instance_value import get_configuration_value diff --git a/apiserver/plane/api/views/importer.py b/apiserver/plane/app/views/importer.py similarity index 99% rename from apiserver/plane/api/views/importer.py rename to apiserver/plane/app/views/importer.py index 4060b2bd5..b99d663e2 100644 --- a/apiserver/plane/api/views/importer.py +++ b/apiserver/plane/app/views/importer.py @@ -4,13 +4,12 @@ import uuid # Third party imports from rest_framework import status from rest_framework.response import Response -from sentry_sdk import capture_exception # Django imports from django.db.models import Max, Q # Module imports -from plane.api.views import BaseAPIView +from plane.app.views import BaseAPIView from plane.db.models import ( WorkspaceIntegration, Importer, @@ -30,7 +29,7 @@ from plane.db.models import ( ModuleIssue, Label, ) -from plane.api.serializers import ( +from plane.app.serializers import ( ImporterSerializer, IssueFlatSerializer, ModuleSerializer, @@ -39,7 +38,7 @@ from plane.utils.integrations.github import get_github_repo_details from plane.utils.importers.jira import jira_project_issue_summary from plane.bgtasks.importer_task import service_importer from plane.utils.html_processor import strip_tags -from plane.api.permissions import WorkSpaceAdminPermission +from plane.app.permissions import WorkSpaceAdminPermission class ServiceIssueImportSummaryEndpoint(BaseAPIView): diff --git a/apiserver/plane/api/views/inbox.py b/apiserver/plane/app/views/inbox.py similarity index 56% rename from apiserver/plane/api/views/inbox.py rename to apiserver/plane/app/views/inbox.py index 999d0a459..38c0808b5 100644 --- a/apiserver/plane/api/views/inbox.py +++ b/apiserver/plane/app/views/inbox.py @@ -9,11 +9,10 @@ from django.core.serializers.json import DjangoJSONEncoder # Third party imports from rest_framework import status from rest_framework.response import Response -from sentry_sdk import capture_exception # Module imports from .base import BaseViewSet -from plane.api.permissions import ProjectBasePermission, ProjectLitePermission +from plane.app.permissions import ProjectBasePermission, ProjectLitePermission from plane.db.models import ( Inbox, InboxIssue, @@ -22,9 +21,8 @@ from plane.db.models import ( IssueLink, IssueAttachment, ProjectMember, - ProjectDeployBoard, ) -from plane.api.serializers import ( +from plane.app.serializers import ( IssueSerializer, InboxSerializer, InboxIssueSerializer, @@ -359,253 +357,3 @@ class InboxIssueViewSet(BaseViewSet): return Response(status=status.HTTP_204_NO_CONTENT) -class InboxIssuePublicViewSet(BaseViewSet): - serializer_class = InboxIssueSerializer - model = InboxIssue - - filterset_fields = [ - "status", - ] - - def get_queryset(self): - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=self.kwargs.get("slug"), - project_id=self.kwargs.get("project_id"), - ) - if project_deploy_board is not None: - return self.filter_queryset( - super() - .get_queryset() - .filter( - Q(snoozed_till__gte=timezone.now()) | Q(snoozed_till__isnull=True), - project_id=self.kwargs.get("project_id"), - workspace__slug=self.kwargs.get("slug"), - inbox_id=self.kwargs.get("inbox_id"), - ) - .select_related("issue", "workspace", "project") - ) - return InboxIssue.objects.none() - - def list(self, request, slug, project_id, inbox_id): - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=slug, project_id=project_id - ) - if project_deploy_board.inbox is None: - return Response( - {"error": "Inbox is not enabled for this Project Board"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - filters = issue_filters(request.query_params, "GET") - issues = ( - Issue.objects.filter( - issue_inbox__inbox_id=inbox_id, - workspace__slug=slug, - project_id=project_id, - ) - .filter(**filters) - .annotate(bridge_id=F("issue_inbox__id")) - .select_related("workspace", "project", "state", "parent") - .prefetch_related("assignees", "labels") - .order_by("issue_inbox__snoozed_till", "issue_inbox__status") - .annotate( - sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id")) - .order_by() - .annotate(count=Func(F("id"), function="Count")) - .values("count") - ) - .annotate( - link_count=IssueLink.objects.filter(issue=OuterRef("id")) - .order_by() - .annotate(count=Func(F("id"), function="Count")) - .values("count") - ) - .annotate( - attachment_count=IssueAttachment.objects.filter(issue=OuterRef("id")) - .order_by() - .annotate(count=Func(F("id"), function="Count")) - .values("count") - ) - .prefetch_related( - Prefetch( - "issue_inbox", - queryset=InboxIssue.objects.only( - "status", "duplicate_to", "snoozed_till", "source" - ), - ) - ) - ) - issues_data = IssueStateInboxSerializer(issues, many=True).data - return Response( - issues_data, - status=status.HTTP_200_OK, - ) - - def create(self, request, slug, project_id, inbox_id): - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=slug, project_id=project_id - ) - if project_deploy_board.inbox is None: - return Response( - {"error": "Inbox is not enabled for this Project Board"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - if not request.data.get("issue", {}).get("name", False): - return Response( - {"error": "Name is required"}, status=status.HTTP_400_BAD_REQUEST - ) - - # Check for valid priority - if not request.data.get("issue", {}).get("priority", "none") in [ - "low", - "medium", - "high", - "urgent", - "none", - ]: - return Response( - {"error": "Invalid priority"}, status=status.HTTP_400_BAD_REQUEST - ) - - # Create or get state - state, _ = State.objects.get_or_create( - name="Triage", - group="backlog", - description="Default state for managing all Inbox Issues", - project_id=project_id, - color="#ff7700", - ) - - # create an issue - issue = Issue.objects.create( - name=request.data.get("issue", {}).get("name"), - description=request.data.get("issue", {}).get("description", {}), - description_html=request.data.get("issue", {}).get( - "description_html", "

" - ), - priority=request.data.get("issue", {}).get("priority", "low"), - project_id=project_id, - state=state, - ) - - # Create an Issue Activity - issue_activity.delay( - type="issue.activity.created", - requested_data=json.dumps(request.data, cls=DjangoJSONEncoder), - actor_id=str(request.user.id), - issue_id=str(issue.id), - project_id=str(project_id), - current_instance=None, - epoch=int(timezone.now().timestamp()), - ) - # create an inbox issue - InboxIssue.objects.create( - inbox_id=inbox_id, - project_id=project_id, - issue=issue, - source=request.data.get("source", "in-app"), - ) - - serializer = IssueStateInboxSerializer(issue) - return Response(serializer.data, status=status.HTTP_200_OK) - - def partial_update(self, request, slug, project_id, inbox_id, pk): - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=slug, project_id=project_id - ) - if project_deploy_board.inbox is None: - return Response( - {"error": "Inbox is not enabled for this Project Board"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - inbox_issue = InboxIssue.objects.get( - pk=pk, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id - ) - # Get the project member - if str(inbox_issue.created_by_id) != str(request.user.id): - return Response( - {"error": "You cannot edit inbox issues"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - # Get issue data - issue_data = request.data.pop("issue", False) - - issue = Issue.objects.get( - pk=inbox_issue.issue_id, workspace__slug=slug, project_id=project_id - ) - # viewers and guests since only viewers and guests - issue_data = { - "name": issue_data.get("name", issue.name), - "description_html": issue_data.get( - "description_html", issue.description_html - ), - "description": issue_data.get("description", issue.description), - } - - issue_serializer = IssueCreateSerializer(issue, data=issue_data, partial=True) - - if issue_serializer.is_valid(): - current_instance = issue - # Log all the updates - requested_data = json.dumps(issue_data, cls=DjangoJSONEncoder) - if issue is not None: - issue_activity.delay( - type="issue.activity.updated", - requested_data=requested_data, - actor_id=str(request.user.id), - issue_id=str(issue.id), - project_id=str(project_id), - current_instance=json.dumps( - IssueSerializer(current_instance).data, - cls=DjangoJSONEncoder, - ), - epoch=int(timezone.now().timestamp()), - ) - issue_serializer.save() - return Response(issue_serializer.data, status=status.HTTP_200_OK) - return Response(issue_serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - def retrieve(self, request, slug, project_id, inbox_id, pk): - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=slug, project_id=project_id - ) - if project_deploy_board.inbox is None: - return Response( - {"error": "Inbox is not enabled for this Project Board"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - inbox_issue = InboxIssue.objects.get( - pk=pk, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id - ) - issue = Issue.objects.get( - pk=inbox_issue.issue_id, workspace__slug=slug, project_id=project_id - ) - serializer = IssueStateInboxSerializer(issue) - return Response(serializer.data, status=status.HTTP_200_OK) - - def destroy(self, request, slug, project_id, inbox_id, pk): - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=slug, project_id=project_id - ) - if project_deploy_board.inbox is None: - return Response( - {"error": "Inbox is not enabled for this Project Board"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - inbox_issue = InboxIssue.objects.get( - pk=pk, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id - ) - - if str(inbox_issue.created_by_id) != str(request.user.id): - return Response( - {"error": "You cannot delete inbox issue"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - inbox_issue.delete() - return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/apiserver/plane/api/views/integration/__init__.py b/apiserver/plane/app/views/integration/__init__.py similarity index 100% rename from apiserver/plane/api/views/integration/__init__.py rename to apiserver/plane/app/views/integration/__init__.py diff --git a/apiserver/plane/api/views/integration/base.py b/apiserver/plane/app/views/integration/base.py similarity index 97% rename from apiserver/plane/api/views/integration/base.py rename to apiserver/plane/app/views/integration/base.py index cc911b537..b82957dfb 100644 --- a/apiserver/plane/api/views/integration/base.py +++ b/apiserver/plane/app/views/integration/base.py @@ -10,7 +10,7 @@ from rest_framework import status from sentry_sdk import capture_exception # Module imports -from plane.api.views import BaseViewSet +from plane.app.views import BaseViewSet from plane.db.models import ( Integration, WorkspaceIntegration, @@ -19,12 +19,12 @@ from plane.db.models import ( WorkspaceMember, APIToken, ) -from plane.api.serializers import IntegrationSerializer, WorkspaceIntegrationSerializer +from plane.app.serializers import IntegrationSerializer, WorkspaceIntegrationSerializer from plane.utils.integrations.github import ( get_github_metadata, delete_github_installation, ) -from plane.api.permissions import WorkSpaceAdminPermission +from plane.app.permissions import WorkSpaceAdminPermission from plane.utils.integrations.slack import slack_oauth class IntegrationViewSet(BaseViewSet): diff --git a/apiserver/plane/api/views/integration/github.py b/apiserver/plane/app/views/integration/github.py similarity index 97% rename from apiserver/plane/api/views/integration/github.py rename to apiserver/plane/app/views/integration/github.py index f2035639e..29b7a9b2f 100644 --- a/apiserver/plane/api/views/integration/github.py +++ b/apiserver/plane/app/views/integration/github.py @@ -4,7 +4,7 @@ from rest_framework.response import Response from sentry_sdk import capture_exception # Module imports -from plane.api.views import BaseViewSet, BaseAPIView +from plane.app.views import BaseViewSet, BaseAPIView from plane.db.models import ( GithubIssueSync, GithubRepositorySync, @@ -15,13 +15,13 @@ from plane.db.models import ( GithubCommentSync, Project, ) -from plane.api.serializers import ( +from plane.app.serializers import ( GithubIssueSyncSerializer, GithubRepositorySyncSerializer, GithubCommentSyncSerializer, ) from plane.utils.integrations.github import get_github_repos -from plane.api.permissions import ProjectBasePermission, ProjectEntityPermission +from plane.app.permissions import ProjectBasePermission, ProjectEntityPermission class GithubRepositoriesEndpoint(BaseAPIView): diff --git a/apiserver/plane/api/views/integration/slack.py b/apiserver/plane/app/views/integration/slack.py similarity index 94% rename from apiserver/plane/api/views/integration/slack.py rename to apiserver/plane/app/views/integration/slack.py index 6b1b47d37..3f18a2ab2 100644 --- a/apiserver/plane/api/views/integration/slack.py +++ b/apiserver/plane/app/views/integration/slack.py @@ -7,10 +7,10 @@ from rest_framework.response import Response from sentry_sdk import capture_exception # Module imports -from plane.api.views import BaseViewSet, BaseAPIView +from plane.app.views import BaseViewSet, BaseAPIView from plane.db.models import SlackProjectSync, WorkspaceIntegration, ProjectMember -from plane.api.serializers import SlackProjectSyncSerializer -from plane.api.permissions import ProjectBasePermission, ProjectEntityPermission +from plane.app.serializers import SlackProjectSyncSerializer +from plane.app.permissions import ProjectBasePermission, ProjectEntityPermission from plane.utils.integrations.slack import slack_oauth diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/app/views/issue.py similarity index 73% rename from apiserver/plane/api/views/issue.py rename to apiserver/plane/app/views/issue.py index 072fabe0e..4f7883868 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/app/views/issue.py @@ -29,12 +29,10 @@ from django.db import IntegrityError from rest_framework.response import Response from rest_framework import status from rest_framework.parsers import MultiPartParser, FormParser -from rest_framework.permissions import AllowAny, IsAuthenticated -from sentry_sdk import capture_exception # Module imports from . import BaseViewSet, BaseAPIView, WebhookMixin -from plane.api.serializers import ( +from plane.app.serializers import ( IssueCreateSerializer, IssueActivitySerializer, IssueCommentSerializer, @@ -54,7 +52,7 @@ from plane.api.serializers import ( RelatedIssueSerializer, IssuePublicSerializer, ) -from plane.api.permissions import ( +from plane.app.permissions import ( ProjectEntityPermission, WorkSpaceAdminPermission, ProjectMemberPermission, @@ -1462,432 +1460,6 @@ class CommentReactionViewSet(BaseViewSet): return Response(status=status.HTTP_204_NO_CONTENT) -class IssueCommentPublicViewSet(BaseViewSet): - serializer_class = IssueCommentSerializer - model = IssueComment - - filterset_fields = [ - "issue__id", - "workspace__id", - ] - - def get_permissions(self): - if self.action in ["list", "retrieve"]: - self.permission_classes = [ - AllowAny, - ] - else: - self.permission_classes = [ - IsAuthenticated, - ] - - return super(IssueCommentPublicViewSet, self).get_permissions() - - def get_queryset(self): - try: - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=self.kwargs.get("slug"), - project_id=self.kwargs.get("project_id"), - ) - if project_deploy_board.comments: - return self.filter_queryset( - super() - .get_queryset() - .filter(workspace__slug=self.kwargs.get("slug")) - .filter(issue_id=self.kwargs.get("issue_id")) - .filter(access="EXTERNAL") - .select_related("project") - .select_related("workspace") - .select_related("issue") - .annotate( - is_member=Exists( - ProjectMember.objects.filter( - workspace__slug=self.kwargs.get("slug"), - project_id=self.kwargs.get("project_id"), - member_id=self.request.user.id, - is_active=True, - ) - ) - ) - .distinct() - ).order_by("created_at") - return IssueComment.objects.none() - except ProjectDeployBoard.DoesNotExist: - return IssueComment.objects.none() - - def create(self, request, slug, project_id, issue_id): - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=slug, project_id=project_id - ) - - if not project_deploy_board.comments: - return Response( - {"error": "Comments are not enabled for this project"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - serializer = IssueCommentSerializer(data=request.data) - if serializer.is_valid(): - serializer.save( - project_id=project_id, - issue_id=issue_id, - actor=request.user, - access="EXTERNAL", - ) - issue_activity.delay( - type="comment.activity.created", - requested_data=json.dumps(serializer.data, cls=DjangoJSONEncoder), - actor_id=str(request.user.id), - issue_id=str(issue_id), - project_id=str(project_id), - current_instance=None, - epoch=int(timezone.now().timestamp()), - ) - if not ProjectMember.objects.filter( - project_id=project_id, - member=request.user, - is_active=True, - ).exists(): - # Add the user for workspace tracking - _ = ProjectPublicMember.objects.get_or_create( - project_id=project_id, - member=request.user, - ) - - return Response(serializer.data, status=status.HTTP_201_CREATED) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - def partial_update(self, request, slug, project_id, issue_id, pk): - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=slug, project_id=project_id - ) - - if not project_deploy_board.comments: - return Response( - {"error": "Comments are not enabled for this project"}, - status=status.HTTP_400_BAD_REQUEST, - ) - comment = IssueComment.objects.get( - workspace__slug=slug, pk=pk, actor=request.user - ) - serializer = IssueCommentSerializer(comment, data=request.data, partial=True) - if serializer.is_valid(): - serializer.save() - issue_activity.delay( - type="comment.activity.updated", - requested_data=json.dumps(request.data, cls=DjangoJSONEncoder), - actor_id=str(request.user.id), - issue_id=str(issue_id), - project_id=str(project_id), - current_instance=json.dumps( - IssueCommentSerializer(comment).data, - cls=DjangoJSONEncoder, - ), - epoch=int(timezone.now().timestamp()), - ) - return Response(serializer.data, status=status.HTTP_200_OK) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - def destroy(self, request, slug, project_id, issue_id, pk): - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=slug, project_id=project_id - ) - - if not project_deploy_board.comments: - return Response( - {"error": "Comments are not enabled for this project"}, - status=status.HTTP_400_BAD_REQUEST, - ) - comment = IssueComment.objects.get( - workspace__slug=slug, pk=pk, project_id=project_id, actor=request.user - ) - issue_activity.delay( - type="comment.activity.deleted", - requested_data=json.dumps({"comment_id": str(pk)}), - actor_id=str(request.user.id), - issue_id=str(issue_id), - project_id=str(project_id), - current_instance=json.dumps( - IssueCommentSerializer(comment).data, - cls=DjangoJSONEncoder, - ), - epoch=int(timezone.now().timestamp()), - ) - comment.delete() - return Response(status=status.HTTP_204_NO_CONTENT) - - -class IssueReactionPublicViewSet(BaseViewSet): - serializer_class = IssueReactionSerializer - model = IssueReaction - - def get_queryset(self): - try: - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=self.kwargs.get("slug"), - project_id=self.kwargs.get("project_id"), - ) - if project_deploy_board.reactions: - return ( - super() - .get_queryset() - .filter(workspace__slug=self.kwargs.get("slug")) - .filter(project_id=self.kwargs.get("project_id")) - .filter(issue_id=self.kwargs.get("issue_id")) - .order_by("-created_at") - .distinct() - ) - return IssueReaction.objects.none() - except ProjectDeployBoard.DoesNotExist: - return IssueReaction.objects.none() - - def create(self, request, slug, project_id, issue_id): - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=slug, project_id=project_id - ) - - if not project_deploy_board.reactions: - return Response( - {"error": "Reactions are not enabled for this project board"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - serializer = IssueReactionSerializer(data=request.data) - if serializer.is_valid(): - serializer.save( - project_id=project_id, issue_id=issue_id, actor=request.user - ) - if not ProjectMember.objects.filter( - project_id=project_id, - member=request.user, - is_active=True, - ).exists(): - # Add the user for workspace tracking - _ = ProjectPublicMember.objects.get_or_create( - project_id=project_id, - member=request.user, - ) - issue_activity.delay( - type="issue_reaction.activity.created", - requested_data=json.dumps(self.request.data, cls=DjangoJSONEncoder), - actor_id=str(self.request.user.id), - issue_id=str(self.kwargs.get("issue_id", None)), - project_id=str(self.kwargs.get("project_id", None)), - current_instance=None, - epoch=int(timezone.now().timestamp()), - ) - return Response(serializer.data, status=status.HTTP_201_CREATED) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - def destroy(self, request, slug, project_id, issue_id, reaction_code): - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=slug, project_id=project_id - ) - - if not project_deploy_board.reactions: - return Response( - {"error": "Reactions are not enabled for this project board"}, - status=status.HTTP_400_BAD_REQUEST, - ) - issue_reaction = IssueReaction.objects.get( - workspace__slug=slug, - issue_id=issue_id, - reaction=reaction_code, - actor=request.user, - ) - issue_activity.delay( - type="issue_reaction.activity.deleted", - requested_data=None, - actor_id=str(self.request.user.id), - issue_id=str(self.kwargs.get("issue_id", None)), - project_id=str(self.kwargs.get("project_id", None)), - current_instance=json.dumps( - { - "reaction": str(reaction_code), - "identifier": str(issue_reaction.id), - } - ), - epoch=int(timezone.now().timestamp()), - ) - issue_reaction.delete() - return Response(status=status.HTTP_204_NO_CONTENT) - - -class CommentReactionPublicViewSet(BaseViewSet): - serializer_class = CommentReactionSerializer - model = CommentReaction - - def get_queryset(self): - try: - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=self.kwargs.get("slug"), - project_id=self.kwargs.get("project_id"), - ) - if project_deploy_board.reactions: - return ( - super() - .get_queryset() - .filter(workspace__slug=self.kwargs.get("slug")) - .filter(project_id=self.kwargs.get("project_id")) - .filter(comment_id=self.kwargs.get("comment_id")) - .order_by("-created_at") - .distinct() - ) - return CommentReaction.objects.none() - except ProjectDeployBoard.DoesNotExist: - return CommentReaction.objects.none() - - def create(self, request, slug, project_id, comment_id): - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=slug, project_id=project_id - ) - - if not project_deploy_board.reactions: - return Response( - {"error": "Reactions are not enabled for this board"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - serializer = CommentReactionSerializer(data=request.data) - if serializer.is_valid(): - serializer.save( - project_id=project_id, comment_id=comment_id, actor=request.user - ) - if not ProjectMember.objects.filter( - project_id=project_id, - member=request.user, - is_active=True, - ).exists(): - # Add the user for workspace tracking - _ = ProjectPublicMember.objects.get_or_create( - project_id=project_id, - member=request.user, - ) - issue_activity.delay( - type="comment_reaction.activity.created", - requested_data=json.dumps(self.request.data, cls=DjangoJSONEncoder), - actor_id=str(self.request.user.id), - issue_id=None, - project_id=str(self.kwargs.get("project_id", None)), - current_instance=None, - epoch=int(timezone.now().timestamp()), - ) - return Response(serializer.data, status=status.HTTP_201_CREATED) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - def destroy(self, request, slug, project_id, comment_id, reaction_code): - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=slug, project_id=project_id - ) - if not project_deploy_board.reactions: - return Response( - {"error": "Reactions are not enabled for this board"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - comment_reaction = CommentReaction.objects.get( - project_id=project_id, - workspace__slug=slug, - comment_id=comment_id, - reaction=reaction_code, - actor=request.user, - ) - issue_activity.delay( - type="comment_reaction.activity.deleted", - requested_data=None, - actor_id=str(self.request.user.id), - issue_id=None, - project_id=str(self.kwargs.get("project_id", None)), - current_instance=json.dumps( - { - "reaction": str(reaction_code), - "identifier": str(comment_reaction.id), - "comment_id": str(comment_id), - } - ), - epoch=int(timezone.now().timestamp()), - ) - comment_reaction.delete() - return Response(status=status.HTTP_204_NO_CONTENT) - - -class IssueVotePublicViewSet(BaseViewSet): - model = IssueVote - serializer_class = IssueVoteSerializer - - def get_queryset(self): - try: - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=self.kwargs.get("slug"), - project_id=self.kwargs.get("project_id"), - ) - if project_deploy_board.votes: - return ( - super() - .get_queryset() - .filter(issue_id=self.kwargs.get("issue_id")) - .filter(workspace__slug=self.kwargs.get("slug")) - .filter(project_id=self.kwargs.get("project_id")) - ) - return IssueVote.objects.none() - except ProjectDeployBoard.DoesNotExist: - return IssueVote.objects.none() - - def create(self, request, slug, project_id, issue_id): - issue_vote, _ = IssueVote.objects.get_or_create( - actor_id=request.user.id, - project_id=project_id, - issue_id=issue_id, - ) - # Add the user for workspace tracking - if not ProjectMember.objects.filter( - project_id=project_id, - member=request.user, - is_active=True, - ).exists(): - _ = ProjectPublicMember.objects.get_or_create( - project_id=project_id, - member=request.user, - ) - issue_vote.vote = request.data.get("vote", 1) - issue_vote.save() - issue_activity.delay( - type="issue_vote.activity.created", - requested_data=json.dumps(self.request.data, cls=DjangoJSONEncoder), - actor_id=str(self.request.user.id), - issue_id=str(self.kwargs.get("issue_id", None)), - project_id=str(self.kwargs.get("project_id", None)), - current_instance=None, - epoch=int(timezone.now().timestamp()), - ) - serializer = IssueVoteSerializer(issue_vote) - return Response(serializer.data, status=status.HTTP_201_CREATED) - - def destroy(self, request, slug, project_id, issue_id): - issue_vote = IssueVote.objects.get( - workspace__slug=slug, - project_id=project_id, - issue_id=issue_id, - actor_id=request.user.id, - ) - issue_activity.delay( - type="issue_vote.activity.deleted", - requested_data=None, - actor_id=str(self.request.user.id), - issue_id=str(self.kwargs.get("issue_id", None)), - project_id=str(self.kwargs.get("project_id", None)), - current_instance=json.dumps( - { - "vote": str(issue_vote.vote), - "identifier": str(issue_vote.id), - } - ), - epoch=int(timezone.now().timestamp()), - ) - issue_vote.delete() - return Response(status=status.HTTP_204_NO_CONTENT) - - class IssueRelationViewSet(BaseViewSet): serializer_class = IssueRelationSerializer model = IssueRelation @@ -1973,182 +1545,6 @@ class IssueRelationViewSet(BaseViewSet): return Response(status=status.HTTP_204_NO_CONTENT) -class IssueRetrievePublicEndpoint(BaseAPIView): - permission_classes = [ - AllowAny, - ] - - def get(self, request, slug, project_id, issue_id): - issue = Issue.objects.get( - workspace__slug=slug, project_id=project_id, pk=issue_id - ) - serializer = IssuePublicSerializer(issue) - return Response(serializer.data, status=status.HTTP_200_OK) - - -class ProjectIssuesPublicEndpoint(BaseAPIView): - permission_classes = [ - AllowAny, - ] - - def get(self, request, slug, project_id): - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=slug, project_id=project_id - ) - - filters = issue_filters(request.query_params, "GET") - - # Custom ordering for priority and state - priority_order = ["urgent", "high", "medium", "low", "none"] - state_order = ["backlog", "unstarted", "started", "completed", "cancelled"] - - order_by_param = request.GET.get("order_by", "-created_at") - - issue_queryset = ( - Issue.issue_objects.annotate( - sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id")) - .order_by() - .annotate(count=Func(F("id"), function="Count")) - .values("count") - ) - .filter(project_id=project_id) - .filter(workspace__slug=slug) - .select_related("project", "workspace", "state", "parent") - .prefetch_related("assignees", "labels") - .prefetch_related( - Prefetch( - "issue_reactions", - queryset=IssueReaction.objects.select_related("actor"), - ) - ) - .prefetch_related( - Prefetch( - "votes", - queryset=IssueVote.objects.select_related("actor"), - ) - ) - .filter(**filters) - .annotate(cycle_id=F("issue_cycle__cycle_id")) - .annotate(module_id=F("issue_module__module_id")) - .annotate( - link_count=IssueLink.objects.filter(issue=OuterRef("id")) - .order_by() - .annotate(count=Func(F("id"), function="Count")) - .values("count") - ) - .annotate( - attachment_count=IssueAttachment.objects.filter(issue=OuterRef("id")) - .order_by() - .annotate(count=Func(F("id"), function="Count")) - .values("count") - ) - ) - - # Priority Ordering - if order_by_param == "priority" or order_by_param == "-priority": - priority_order = ( - priority_order if order_by_param == "priority" else priority_order[::-1] - ) - issue_queryset = issue_queryset.annotate( - priority_order=Case( - *[ - When(priority=p, then=Value(i)) - for i, p in enumerate(priority_order) - ], - output_field=CharField(), - ) - ).order_by("priority_order") - - # State Ordering - elif order_by_param in [ - "state__name", - "state__group", - "-state__name", - "-state__group", - ]: - state_order = ( - state_order - if order_by_param in ["state__name", "state__group"] - else state_order[::-1] - ) - issue_queryset = issue_queryset.annotate( - state_order=Case( - *[ - When(state__group=state_group, then=Value(i)) - for i, state_group in enumerate(state_order) - ], - default=Value(len(state_order)), - output_field=CharField(), - ) - ).order_by("state_order") - # assignee and label ordering - elif order_by_param in [ - "labels__name", - "-labels__name", - "assignees__first_name", - "-assignees__first_name", - ]: - issue_queryset = issue_queryset.annotate( - max_values=Max( - order_by_param[1::] - if order_by_param.startswith("-") - else order_by_param - ) - ).order_by( - "-max_values" if order_by_param.startswith("-") else "max_values" - ) - else: - issue_queryset = issue_queryset.order_by(order_by_param) - - issues = IssuePublicSerializer(issue_queryset, many=True).data - - state_group_order = [ - "backlog", - "unstarted", - "started", - "completed", - "cancelled", - ] - - states = ( - State.objects.filter( - ~Q(name="Triage"), - workspace__slug=slug, - project_id=project_id, - ) - .annotate( - custom_order=Case( - *[ - When(group=value, then=Value(index)) - for index, value in enumerate(state_group_order) - ], - default=Value(len(state_group_order)), - output_field=IntegerField(), - ), - ) - .values("name", "group", "color", "id") - .order_by("custom_order", "sequence") - ) - - labels = Label.objects.filter( - workspace__slug=slug, project_id=project_id - ).values("id", "name", "color", "parent") - - ## Grouping the results - group_by = request.GET.get("group_by", False) - if group_by: - issues = group_results(issues, group_by) - - return Response( - { - "issues": issues, - "states": states, - "labels": labels, - }, - status=status.HTTP_200_OK, - ) - - class IssueDraftViewSet(BaseViewSet): permission_classes = [ ProjectEntityPermission, diff --git a/apiserver/plane/api/views/module.py b/apiserver/plane/app/views/module.py similarity index 99% rename from apiserver/plane/api/views/module.py rename to apiserver/plane/app/views/module.py index f8d74dbed..cc6369fe2 100644 --- a/apiserver/plane/api/views/module.py +++ b/apiserver/plane/app/views/module.py @@ -3,7 +3,6 @@ import json # Django Imports from django.utils import timezone -from django.db import IntegrityError from django.db.models import Prefetch, F, OuterRef, Func, Exists, Count, Q from django.core import serializers from django.utils.decorators import method_decorator @@ -12,11 +11,10 @@ from django.views.decorators.gzip import gzip_page # Third party imports from rest_framework.response import Response from rest_framework import status -from sentry_sdk import capture_exception # Module imports from . import BaseViewSet, BaseAPIView, WebhookMixin -from plane.api.serializers import ( +from plane.app.serializers import ( ModuleWriteSerializer, ModuleSerializer, ModuleIssueSerializer, @@ -24,7 +22,7 @@ from plane.api.serializers import ( ModuleFavoriteSerializer, IssueStateSerializer, ) -from plane.api.permissions import ProjectEntityPermission +from plane.app.permissions import ProjectEntityPermission from plane.db.models import ( Module, ModuleIssue, diff --git a/apiserver/plane/api/views/notification.py b/apiserver/plane/app/views/notification.py similarity index 99% rename from apiserver/plane/api/views/notification.py rename to apiserver/plane/app/views/notification.py index 19dcba734..9494ea86c 100644 --- a/apiserver/plane/api/views/notification.py +++ b/apiserver/plane/app/views/notification.py @@ -5,7 +5,6 @@ from django.utils import timezone # Third party imports from rest_framework import status from rest_framework.response import Response -from sentry_sdk import capture_exception from plane.utils.paginator import BasePaginator # Module imports @@ -17,7 +16,7 @@ from plane.db.models import ( Issue, WorkspaceMember, ) -from plane.api.serializers import NotificationSerializer +from plane.app.serializers import NotificationSerializer class NotificationViewSet(BaseViewSet, BasePaginator): diff --git a/apiserver/plane/api/views/oauth.py b/apiserver/plane/app/views/oauth.py similarity index 99% rename from apiserver/plane/api/views/oauth.py rename to apiserver/plane/app/views/oauth.py index d2b65d926..d7d9fe9e0 100644 --- a/apiserver/plane/api/views/oauth.py +++ b/apiserver/plane/app/views/oauth.py @@ -29,7 +29,6 @@ from plane.db.models import ( ProjectMemberInvite, ProjectMember, ) -from plane.api.serializers import UserSerializer from .base import BaseAPIView diff --git a/apiserver/plane/api/views/page.py b/apiserver/plane/app/views/page.py similarity index 90% rename from apiserver/plane/api/views/page.py rename to apiserver/plane/app/views/page.py index d8c90fc8f..b218b6687 100644 --- a/apiserver/plane/api/views/page.py +++ b/apiserver/plane/app/views/page.py @@ -3,26 +3,18 @@ from datetime import timedelta, date, datetime # Django imports from django.db import connection -from django.db.models import Exists, OuterRef, Q, Prefetch +from django.db.models import Exists, OuterRef, Q from django.utils import timezone from django.utils.decorators import method_decorator from django.views.decorators.gzip import gzip_page -from django.db.models import ( - OuterRef, - Func, - F, - Q, - Exists, -) # Third party imports from rest_framework import status from rest_framework.response import Response -from sentry_sdk import capture_exception # Module imports from .base import BaseViewSet, BaseAPIView -from plane.api.permissions import ProjectEntityPermission +from plane.app.permissions import ProjectEntityPermission from plane.db.models import ( Page, PageFavorite, @@ -31,7 +23,7 @@ from plane.db.models import ( IssueActivity, PageLog, ) -from plane.api.serializers import ( +from plane.app.serializers import ( PageSerializer, PageFavoriteSerializer, PageLogSerializer, @@ -87,15 +79,10 @@ class PageViewSet(BaseViewSet): .annotate(is_favorite=Exists(subquery)) .order_by(self.request.GET.get("order_by", "-created_at")) .prefetch_related("labels") - .order_by("-is_favorite","-created_at") + .order_by("-is_favorite", "-created_at") .distinct() ) - def perform_create(self, serializer): - serializer.save( - project_id=self.kwargs.get("project_id"), owned_by=self.request.user - ) - def create(self, request, slug, project_id): serializer = PageSerializer( data=request.data, @@ -148,10 +135,8 @@ class PageViewSet(BaseViewSet): status=status.HTTP_400_BAD_REQUEST, ) - def lock(self, request, slug, project_id, pk): - page = Page.objects.filter( - pk=pk, workspace__slug=slug, project_id=project_id - ) + def lock(self, request, slug, project_id, page_id): + page = Page.objects.get(pk=page_id, workspace__slug=slug, project_id=project_id) # only the owner can lock the page if request.user.id != page.owned_by_id: @@ -163,8 +148,8 @@ class PageViewSet(BaseViewSet): page.save() return Response(status=status.HTTP_204_NO_CONTENT) - def unlock(self, request, slug, project_id, pk): - page = Page.objects.get(pk=pk, workspace__slug=slug, project_id=project_id) + def unlock(self, request, slug, project_id, page_id): + page = Page.objects.get(pk=page_id, workspace__slug=slug, project_id=project_id) # only the owner can unlock the page if request.user.id != page.owned_by_id: @@ -242,27 +227,31 @@ class PageViewSet(BaseViewSet): ) def archive(self, request, slug, project_id, page_id): - _ = Page.objects.get( - project_id=project_id, - owned_by_id=request.user.id, - workspace__slug=slug, - pk=page_id, - ) + page = Page.objects.get(pk=page_id, workspace__slug=slug, project_id=project_id) + + if page.owned_by_id != request.user.id: + return Response( + {"error": "Only the owner of the page can archive a page"}, + status=status.HTTP_204_NO_CONTENT, + ) unarchive_archive_page_and_descendants(page_id, datetime.now()) return Response(status=status.HTTP_204_NO_CONTENT) def unarchive(self, request, slug, project_id, page_id): - page = Page.objects.get( - project_id=project_id, - owned_by_id=request.user.id, - workspace__slug=slug, - pk=page_id, - ) + page = Page.objects.get(pk=page_id, workspace__slug=slug, project_id=project_id) - page.parent = None - page.save() + if page.owned_by_id != request.user.id: + return Response( + {"error": "Only the owner of the page can unarchive a page"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + # if parent page is archived then the page will be un archived breaking the hierarchy + if page.parent_id and page.parent.archived_at: + page.parent = None + page.save(update_fields=['parent']) unarchive_archive_page_and_descendants(page_id, None) @@ -275,20 +264,13 @@ class PageViewSet(BaseViewSet): workspace__slug=slug, ) .filter(archived_at__isnull=False) - .filter(parent_id__isnull=True) ) - if not pages: - return Response( - {"error": "No pages found"}, status=status.HTTP_400_BAD_REQUEST - ) - return Response( PageSerializer(pages, many=True).data, status=status.HTTP_200_OK ) - class PageFavoriteViewSet(BaseViewSet): permission_classes = [ ProjectEntityPermission, @@ -410,11 +392,9 @@ class SubPagesEndpoint(BaseAPIView): workspace__slug=slug, entity_name__in=["forward_link", "back_link"], ) - .filter(archived_at__isnull=True) .select_related("project") .select_related("workspace") ) return Response( SubPageSerializer(pages, many=True).data, status=status.HTTP_200_OK - ) - + ) \ No newline at end of file diff --git a/apiserver/plane/api/views/project.py b/apiserver/plane/app/views/project.py similarity index 96% rename from apiserver/plane/api/views/project.py rename to apiserver/plane/app/views/project.py index ce7750105..727aa06ba 100644 --- a/apiserver/plane/api/views/project.py +++ b/apiserver/plane/app/views/project.py @@ -27,7 +27,7 @@ from rest_framework.permissions import AllowAny # Module imports from .base import BaseViewSet, BaseAPIView, WebhookMixin -from plane.api.serializers import ( +from plane.app.serializers import ( ProjectSerializer, ProjectListSerializer, ProjectMemberSerializer, @@ -38,12 +38,9 @@ from plane.api.serializers import ( ProjectMemberAdminSerializer, ) -from plane.api.permissions import ( - WorkspaceUserPermission, +from plane.app.permissions import ( ProjectBasePermission, - ProjectEntityPermission, ProjectMemberPermission, - ProjectLitePermission, ) from plane.db.models import ( @@ -965,6 +962,37 @@ class ProjectFavoritesViewSet(BaseViewSet): return Response(status=status.HTTP_204_NO_CONTENT) +class ProjectPublicCoverImagesEndpoint(BaseAPIView): + permission_classes = [ + AllowAny, + ] + + def get(self, request): + files = [] + s3 = boto3.client( + "s3", + aws_access_key_id=settings.AWS_ACCESS_KEY_ID, + aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY, + ) + params = { + "Bucket": settings.AWS_S3_BUCKET_NAME, + "Prefix": "static/project-cover/", + } + + response = s3.list_objects_v2(**params) + # Extracting file keys from the response + if "Contents" in response: + for content in response["Contents"]: + if not content["Key"].endswith( + "/" + ): # This line ensures we're only getting files, not "sub-folders" + files.append( + f"https://{settings.AWS_S3_BUCKET_NAME}.s3.{settings.AWS_REGION}.amazonaws.com/{content['Key']}" + ) + + return Response(files, status=status.HTTP_200_OK) + + class ProjectDeployBoardViewSet(BaseViewSet): permission_classes = [ ProjectMemberPermission, @@ -1012,77 +1040,4 @@ class ProjectDeployBoardViewSet(BaseViewSet): project_deploy_board.save() serializer = ProjectDeployBoardSerializer(project_deploy_board) - return Response(serializer.data, status=status.HTTP_200_OK) - - -class ProjectDeployBoardPublicSettingsEndpoint(BaseAPIView): - permission_classes = [ - AllowAny, - ] - - def get(self, request, slug, project_id): - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=slug, project_id=project_id - ) - serializer = ProjectDeployBoardSerializer(project_deploy_board) - return Response(serializer.data, status=status.HTTP_200_OK) - - -class WorkspaceProjectDeployBoardEndpoint(BaseAPIView): - permission_classes = [ - AllowAny, - ] - - def get(self, request, slug): - projects = ( - Project.objects.filter(workspace__slug=slug) - .annotate( - is_public=Exists( - ProjectDeployBoard.objects.filter( - workspace__slug=slug, project_id=OuterRef("pk") - ) - ) - ) - .filter(is_public=True) - ).values( - "id", - "identifier", - "name", - "description", - "emoji", - "icon_prop", - "cover_image", - ) - - return Response(projects, status=status.HTTP_200_OK) - - -class ProjectPublicCoverImagesEndpoint(BaseAPIView): - permission_classes = [ - AllowAny, - ] - - def get(self, request): - files = [] - s3 = boto3.client( - "s3", - aws_access_key_id=settings.AWS_ACCESS_KEY_ID, - aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY, - ) - params = { - "Bucket": settings.AWS_S3_BUCKET_NAME, - "Prefix": "static/project-cover/", - } - - response = s3.list_objects_v2(**params) - # Extracting file keys from the response - if "Contents" in response: - for content in response["Contents"]: - if not content["Key"].endswith( - "/" - ): # This line ensures we're only getting files, not "sub-folders" - files.append( - f"https://{settings.AWS_S3_BUCKET_NAME}.s3.{settings.AWS_REGION}.amazonaws.com/{content['Key']}" - ) - - return Response(files, status=status.HTTP_200_OK) + return Response(serializer.data, status=status.HTTP_200_OK) \ No newline at end of file diff --git a/apiserver/plane/api/views/search.py b/apiserver/plane/app/views/search.py similarity index 99% rename from apiserver/plane/api/views/search.py rename to apiserver/plane/app/views/search.py index ff7431543..ac560643a 100644 --- a/apiserver/plane/api/views/search.py +++ b/apiserver/plane/app/views/search.py @@ -7,7 +7,6 @@ from django.db.models import Q # Third party imports from rest_framework import status from rest_framework.response import Response -from sentry_sdk import capture_exception # Module imports from .base import BaseAPIView diff --git a/apiserver/plane/api/views/state.py b/apiserver/plane/app/views/state.py similarity index 94% rename from apiserver/plane/api/views/state.py rename to apiserver/plane/app/views/state.py index dbb6e1d71..124bdf8fd 100644 --- a/apiserver/plane/api/views/state.py +++ b/apiserver/plane/app/views/state.py @@ -7,12 +7,11 @@ from django.db.models import Q # Third party imports from rest_framework.response import Response from rest_framework import status -from sentry_sdk import capture_exception # Module imports -from . import BaseViewSet, BaseAPIView -from plane.api.serializers import StateSerializer -from plane.api.permissions import ProjectEntityPermission +from . import BaseViewSet +from plane.app.serializers import StateSerializer +from plane.app.permissions import ProjectEntityPermission from plane.db.models import State, Issue diff --git a/apiserver/plane/api/views/user.py b/apiserver/plane/app/views/user.py similarity index 95% rename from apiserver/plane/api/views/user.py rename to apiserver/plane/app/views/user.py index e6e742a63..ed1178886 100644 --- a/apiserver/plane/api/views/user.py +++ b/apiserver/plane/app/views/user.py @@ -2,17 +2,16 @@ from rest_framework.response import Response from rest_framework import status -from sentry_sdk import capture_exception # Module imports -from plane.api.serializers import ( +from plane.app.serializers import ( UserSerializer, IssueActivitySerializer, UserMeSerializer, UserMeSettingsSerializer, ) -from plane.api.views.base import BaseViewSet, BaseAPIView +from plane.app.views.base import BaseViewSet, BaseAPIView from plane.db.models import User, IssueActivity, WorkspaceMember from plane.license.models import Instance, InstanceAdmin from plane.utils.paginator import BasePaginator diff --git a/apiserver/plane/api/views/view.py b/apiserver/plane/app/views/view.py similarity index 97% rename from apiserver/plane/api/views/view.py rename to apiserver/plane/app/views/view.py index f58f320b7..8e0e72f66 100644 --- a/apiserver/plane/api/views/view.py +++ b/apiserver/plane/app/views/view.py @@ -18,17 +18,16 @@ from django.db.models import Prefetch, OuterRef, Exists # Third party imports from rest_framework.response import Response from rest_framework import status -from sentry_sdk import capture_exception # Module imports -from . import BaseViewSet, BaseAPIView -from plane.api.serializers import ( +from . import BaseViewSet +from plane.app.serializers import ( GlobalViewSerializer, IssueViewSerializer, IssueLiteSerializer, IssueViewFavoriteSerializer, ) -from plane.api.permissions import WorkspaceEntityPermission, ProjectEntityPermission +from plane.app.permissions import WorkspaceEntityPermission, ProjectEntityPermission from plane.db.models import ( Workspace, GlobalView, diff --git a/apiserver/plane/api/views/webhook.py b/apiserver/plane/app/views/webhook.py similarity index 97% rename from apiserver/plane/api/views/webhook.py rename to apiserver/plane/app/views/webhook.py index 91a2f6729..74d23dd91 100644 --- a/apiserver/plane/api/views/webhook.py +++ b/apiserver/plane/app/views/webhook.py @@ -9,8 +9,8 @@ from rest_framework.response import Response from plane.db.models import Webhook, WebhookLog, Workspace from plane.db.models.webhook import generate_token from .base import BaseAPIView -from plane.api.permissions import WorkspaceOwnerPermission -from plane.api.serializers import WebhookSerializer, WebhookLogSerializer +from plane.app.permissions import WorkspaceOwnerPermission +from plane.app.serializers import WebhookSerializer, WebhookLogSerializer class WebhookEndpoint(BaseAPIView): diff --git a/apiserver/plane/api/views/workspace.py b/apiserver/plane/app/views/workspace.py similarity index 99% rename from apiserver/plane/api/views/workspace.py rename to apiserver/plane/app/views/workspace.py index 8804d48ef..637fc95b5 100644 --- a/apiserver/plane/api/views/workspace.py +++ b/apiserver/plane/app/views/workspace.py @@ -29,10 +29,10 @@ from django.db.models.fields import DateField # Third party modules from rest_framework import status from rest_framework.response import Response -from rest_framework.permissions import AllowAny, IsAuthenticated +from rest_framework.permissions import AllowAny # Module imports -from plane.api.serializers import ( +from plane.app.serializers import ( WorkSpaceSerializer, WorkSpaceMemberSerializer, TeamSerializer, @@ -45,7 +45,7 @@ from plane.api.serializers import ( WorkspaceMemberAdminSerializer, WorkspaceMemberMeSerializer, ) -from plane.api.views.base import BaseAPIView +from plane.app.views.base import BaseAPIView from . import BaseViewSet from plane.db.models import ( User, @@ -65,7 +65,7 @@ from plane.db.models import ( CycleIssue, IssueReaction, ) -from plane.api.permissions import ( +from plane.app.permissions import ( WorkSpaceBasePermission, WorkSpaceAdminPermission, WorkspaceEntityPermission, diff --git a/apiserver/plane/bgtasks/importer_task.py b/apiserver/plane/bgtasks/importer_task.py index f9e3df21e..84d10ecd3 100644 --- a/apiserver/plane/bgtasks/importer_task.py +++ b/apiserver/plane/bgtasks/importer_task.py @@ -13,7 +13,7 @@ from celery import shared_task from sentry_sdk import capture_exception # Module imports -from plane.api.serializers import ImporterSerializer +from plane.app.serializers import ImporterSerializer from plane.db.models import ( Importer, WorkspaceMember, diff --git a/apiserver/plane/bgtasks/issue_activites_task.py b/apiserver/plane/bgtasks/issue_activites_task.py index 4776bceab..3b2b40223 100644 --- a/apiserver/plane/bgtasks/issue_activites_task.py +++ b/apiserver/plane/bgtasks/issue_activites_task.py @@ -21,14 +21,11 @@ from plane.db.models import ( State, Cycle, Module, - IssueSubscriber, - Notification, - IssueAssignee, IssueReaction, CommentReaction, IssueComment, ) -from plane.api.serializers import IssueActivitySerializer +from plane.app.serializers import IssueActivitySerializer from plane.bgtasks.notification_task import notifications diff --git a/apiserver/plane/license/api/serializers/instance.py b/apiserver/plane/license/api/serializers/instance.py index b8c990522..f5cff760b 100644 --- a/apiserver/plane/license/api/serializers/instance.py +++ b/apiserver/plane/license/api/serializers/instance.py @@ -1,7 +1,7 @@ # Module imports from plane.license.models import Instance, InstanceAdmin, InstanceConfiguration -from plane.api.serializers import BaseSerializer -from plane.api.serializers import UserAdminLiteSerializer +from plane.app.serializers import BaseSerializer +from plane.app.serializers import UserAdminLiteSerializer class InstanceSerializer(BaseSerializer): diff --git a/apiserver/plane/license/api/views/instance.py b/apiserver/plane/license/api/views/instance.py index 309b2b9da..6b27419fd 100644 --- a/apiserver/plane/license/api/views/instance.py +++ b/apiserver/plane/license/api/views/instance.py @@ -11,7 +11,7 @@ from rest_framework import status from rest_framework.response import Response # Module imports -from plane.api.views import BaseAPIView +from plane.app.views import BaseAPIView from plane.license.models import Instance, InstanceAdmin, InstanceConfiguration from plane.license.api.serializers import ( InstanceSerializer, diff --git a/apiserver/plane/settings/common.py b/apiserver/plane/settings/common.py index f6359344d..2cf94a68c 100644 --- a/apiserver/plane/settings/common.py +++ b/apiserver/plane/settings/common.py @@ -34,7 +34,8 @@ INSTALLED_APPS = [ "django.contrib.sessions", # Inhouse apps "plane.analytics", - "plane.api", + "plane.app", + "plane.space", "plane.bgtasks", "plane.db", "plane.utils", diff --git a/apiserver/plane/space/__init__.py b/apiserver/plane/space/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apiserver/plane/space/apps.py b/apiserver/plane/space/apps.py new file mode 100644 index 000000000..6f1e76c51 --- /dev/null +++ b/apiserver/plane/space/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class SpaceConfig(AppConfig): + name = "plane.space" diff --git a/apiserver/plane/space/serializer/__init__.py b/apiserver/plane/space/serializer/__init__.py new file mode 100644 index 000000000..cd10fb5c6 --- /dev/null +++ b/apiserver/plane/space/serializer/__init__.py @@ -0,0 +1,5 @@ +from .user import UserLiteSerializer + +from .issue import LabelLiteSerializer, StateLiteSerializer + +from .state import StateSerializer, StateLiteSerializer diff --git a/apiserver/plane/space/serializer/base.py b/apiserver/plane/space/serializer/base.py new file mode 100644 index 000000000..89c9725d9 --- /dev/null +++ b/apiserver/plane/space/serializer/base.py @@ -0,0 +1,58 @@ +from rest_framework import serializers + + +class BaseSerializer(serializers.ModelSerializer): + id = serializers.PrimaryKeyRelatedField(read_only=True) + +class DynamicBaseSerializer(BaseSerializer): + + def __init__(self, *args, **kwargs): + # If 'fields' is provided in the arguments, remove it and store it separately. + # This is done so as not to pass this custom argument up to the superclass. + fields = kwargs.pop("fields", None) + + # Call the initialization of the superclass. + super().__init__(*args, **kwargs) + + # If 'fields' was provided, filter the fields of the serializer accordingly. + if fields is not None: + self.fields = self._filter_fields(fields) + + def _filter_fields(self, fields): + """ + Adjust the serializer's fields based on the provided 'fields' list. + + :param fields: List or dictionary specifying which fields to include in the serializer. + :return: The updated fields for the serializer. + """ + # Check each field_name in the provided fields. + for field_name in fields: + # If the field is a dictionary (indicating nested fields), + # loop through its keys and values. + if isinstance(field_name, dict): + for key, value in field_name.items(): + # If the value of this nested field is a list, + # perform a recursive filter on it. + if isinstance(value, list): + self._filter_fields(self.fields[key], value) + + # Create a list to store allowed fields. + allowed = [] + for item in fields: + # If the item is a string, it directly represents a field's name. + if isinstance(item, str): + allowed.append(item) + # If the item is a dictionary, it represents a nested field. + # Add the key of this dictionary to the allowed list. + elif isinstance(item, dict): + allowed.append(list(item.keys())[0]) + + # Convert the current serializer's fields and the allowed fields to sets. + existing = set(self.fields) + allowed = set(allowed) + + # Remove fields from the serializer that aren't in the 'allowed' list. + for field_name in (existing - allowed): + self.fields.pop(field_name) + + return self.fields diff --git a/apiserver/plane/space/serializer/cycle.py b/apiserver/plane/space/serializer/cycle.py new file mode 100644 index 000000000..ab4d9441d --- /dev/null +++ b/apiserver/plane/space/serializer/cycle.py @@ -0,0 +1,18 @@ +# Module imports +from .base import BaseSerializer +from plane.db.models import ( + Cycle, +) + +class CycleBaseSerializer(BaseSerializer): + class Meta: + model = Cycle + fields = "__all__" + read_only_fields = [ + "workspace", + "project", + "created_by", + "updated_by", + "created_at", + "updated_at", + ] \ No newline at end of file diff --git a/apiserver/plane/space/serializer/inbox.py b/apiserver/plane/space/serializer/inbox.py new file mode 100644 index 000000000..05d99ac55 --- /dev/null +++ b/apiserver/plane/space/serializer/inbox.py @@ -0,0 +1,47 @@ +# Third Party imports +from rest_framework import serializers + +# Module imports +from .base import BaseSerializer +from .user import UserLiteSerializer +from .state import StateLiteSerializer +from .project import ProjectLiteSerializer +from .issue import IssueFlatSerializer, LabelLiteSerializer +from plane.db.models import ( + Issue, + InboxIssue, +) + + +class InboxIssueSerializer(BaseSerializer): + issue_detail = IssueFlatSerializer(source="issue", read_only=True) + project_detail = ProjectLiteSerializer(source="project", read_only=True) + + class Meta: + model = InboxIssue + fields = "__all__" + read_only_fields = [ + "project", + "workspace", + ] + + +class InboxIssueLiteSerializer(BaseSerializer): + class Meta: + model = InboxIssue + fields = ["id", "status", "duplicate_to", "snoozed_till", "source"] + read_only_fields = fields + + +class IssueStateInboxSerializer(BaseSerializer): + state_detail = StateLiteSerializer(read_only=True, source="state") + project_detail = ProjectLiteSerializer(read_only=True, source="project") + label_details = LabelLiteSerializer(read_only=True, source="labels", many=True) + assignee_details = UserLiteSerializer(read_only=True, source="assignees", many=True) + sub_issues_count = serializers.IntegerField(read_only=True) + bridge_id = serializers.UUIDField(read_only=True) + issue_inbox = InboxIssueLiteSerializer(read_only=True, many=True) + + class Meta: + model = Issue + fields = "__all__" \ No newline at end of file diff --git a/apiserver/plane/space/serializer/issue.py b/apiserver/plane/space/serializer/issue.py new file mode 100644 index 000000000..1a9a872ef --- /dev/null +++ b/apiserver/plane/space/serializer/issue.py @@ -0,0 +1,506 @@ + +# Django imports +from django.utils import timezone + +# Third Party imports +from rest_framework import serializers + +# Module imports +from .base import BaseSerializer +from .user import UserLiteSerializer +from .state import StateSerializer, StateLiteSerializer +from .project import ProjectLiteSerializer +from .cycle import CycleBaseSerializer +from .module import ModuleBaseSerializer +from .workspace import WorkspaceLiteSerializer +from plane.db.models import ( + User, + Issue, + IssueComment, + IssueAssignee, + IssueLabel, + Label, + CycleIssue, + ModuleIssue, + IssueLink, + IssueAttachment, + IssueReaction, + CommentReaction, + IssueVote, + IssueRelation, +) + + +class IssueStateFlatSerializer(BaseSerializer): + state_detail = StateLiteSerializer(read_only=True, source="state") + project_detail = ProjectLiteSerializer(read_only=True, source="project") + + class Meta: + model = Issue + fields = [ + "id", + "sequence_id", + "name", + "state_detail", + "project_detail", + ] + + +class LabelSerializer(BaseSerializer): + workspace_detail = WorkspaceLiteSerializer(source="workspace", read_only=True) + project_detail = ProjectLiteSerializer(source="project", read_only=True) + + class Meta: + model = Label + fields = "__all__" + read_only_fields = [ + "workspace", + "project", + ] + + +class IssueProjectLiteSerializer(BaseSerializer): + project_detail = ProjectLiteSerializer(source="project", read_only=True) + + class Meta: + model = Issue + fields = [ + "id", + "project_detail", + "name", + "sequence_id", + ] + read_only_fields = fields + + +class IssueRelationSerializer(BaseSerializer): + issue_detail = IssueProjectLiteSerializer(read_only=True, source="related_issue") + + class Meta: + model = IssueRelation + fields = [ + "issue_detail", + "relation_type", + "related_issue", + "issue", + "id" + ] + read_only_fields = [ + "workspace", + "project", + ] + +class RelatedIssueSerializer(BaseSerializer): + issue_detail = IssueProjectLiteSerializer(read_only=True, source="issue") + + class Meta: + model = IssueRelation + fields = [ + "issue_detail", + "relation_type", + "related_issue", + "issue", + "id" + ] + read_only_fields = [ + "workspace", + "project", + ] + + +class IssueCycleDetailSerializer(BaseSerializer): + cycle_detail = CycleBaseSerializer(read_only=True, source="cycle") + + class Meta: + model = CycleIssue + fields = "__all__" + read_only_fields = [ + "workspace", + "project", + "created_by", + "updated_by", + "created_at", + "updated_at", + ] + + +class IssueModuleDetailSerializer(BaseSerializer): + module_detail = ModuleBaseSerializer(read_only=True, source="module") + + class Meta: + model = ModuleIssue + fields = "__all__" + read_only_fields = [ + "workspace", + "project", + "created_by", + "updated_by", + "created_at", + "updated_at", + ] + + +class IssueLinkSerializer(BaseSerializer): + created_by_detail = UserLiteSerializer(read_only=True, source="created_by") + + class Meta: + model = IssueLink + fields = "__all__" + read_only_fields = [ + "workspace", + "project", + "created_by", + "updated_by", + "created_at", + "updated_at", + "issue", + ] + + # Validation if url already exists + def create(self, validated_data): + if IssueLink.objects.filter( + url=validated_data.get("url"), issue_id=validated_data.get("issue_id") + ).exists(): + raise serializers.ValidationError( + {"error": "URL already exists for this Issue"} + ) + return IssueLink.objects.create(**validated_data) + + +class IssueAttachmentSerializer(BaseSerializer): + class Meta: + model = IssueAttachment + fields = "__all__" + read_only_fields = [ + "created_by", + "updated_by", + "created_at", + "updated_at", + "workspace", + "project", + "issue", + ] + + +class IssueReactionSerializer(BaseSerializer): + + actor_detail = UserLiteSerializer(read_only=True, source="actor") + + class Meta: + model = IssueReaction + fields = "__all__" + read_only_fields = [ + "workspace", + "project", + "issue", + "actor", + ] + + +class IssueSerializer(BaseSerializer): + project_detail = ProjectLiteSerializer(read_only=True, source="project") + state_detail = StateSerializer(read_only=True, source="state") + parent_detail = IssueStateFlatSerializer(read_only=True, source="parent") + label_details = LabelSerializer(read_only=True, source="labels", many=True) + assignee_details = UserLiteSerializer(read_only=True, source="assignees", many=True) + related_issues = IssueRelationSerializer(read_only=True, source="issue_relation", many=True) + issue_relations = RelatedIssueSerializer(read_only=True, source="issue_related", many=True) + issue_cycle = IssueCycleDetailSerializer(read_only=True) + issue_module = IssueModuleDetailSerializer(read_only=True) + issue_link = IssueLinkSerializer(read_only=True, many=True) + issue_attachment = IssueAttachmentSerializer(read_only=True, many=True) + sub_issues_count = serializers.IntegerField(read_only=True) + issue_reactions = IssueReactionSerializer(read_only=True, many=True) + + class Meta: + model = Issue + fields = "__all__" + read_only_fields = [ + "workspace", + "project", + "created_by", + "updated_by", + "created_at", + "updated_at", + ] + + +class IssueFlatSerializer(BaseSerializer): + ## Contain only flat fields + + class Meta: + model = Issue + fields = [ + "id", + "name", + "description", + "description_html", + "priority", + "start_date", + "target_date", + "sequence_id", + "sort_order", + "is_draft", + ] + + +class CommentReactionLiteSerializer(BaseSerializer): + actor_detail = UserLiteSerializer(read_only=True, source="actor") + + class Meta: + model = CommentReaction + fields = [ + "id", + "reaction", + "comment", + "actor_detail", + ] + + +class IssueCommentSerializer(BaseSerializer): + actor_detail = UserLiteSerializer(read_only=True, source="actor") + issue_detail = IssueFlatSerializer(read_only=True, source="issue") + project_detail = ProjectLiteSerializer(read_only=True, source="project") + workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace") + comment_reactions = CommentReactionLiteSerializer(read_only=True, many=True) + is_member = serializers.BooleanField(read_only=True) + + class Meta: + model = IssueComment + fields = "__all__" + read_only_fields = [ + "workspace", + "project", + "issue", + "created_by", + "updated_by", + "created_at", + "updated_at", + ] + + +##TODO: Find a better way to write this serializer +## Find a better approach to save manytomany? +class IssueCreateSerializer(BaseSerializer): + state_detail = StateSerializer(read_only=True, source="state") + created_by_detail = UserLiteSerializer(read_only=True, source="created_by") + project_detail = ProjectLiteSerializer(read_only=True, source="project") + workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace") + + assignees = serializers.ListField( + child=serializers.PrimaryKeyRelatedField(queryset=User.objects.all()), + write_only=True, + required=False, + ) + + labels = serializers.ListField( + child=serializers.PrimaryKeyRelatedField(queryset=Label.objects.all()), + write_only=True, + required=False, + ) + + class Meta: + model = Issue + fields = "__all__" + read_only_fields = [ + "workspace", + "project", + "created_by", + "updated_by", + "created_at", + "updated_at", + ] + + def to_representation(self, instance): + data = super().to_representation(instance) + data['assignees'] = [str(assignee.id) for assignee in instance.assignees.all()] + data['labels'] = [str(label.id) for label in instance.labels.all()] + return data + + def validate(self, data): + if ( + data.get("start_date", None) is not None + and data.get("target_date", None) is not None + and data.get("start_date", None) > data.get("target_date", None) + ): + raise serializers.ValidationError("Start date cannot exceed target date") + return data + + def create(self, validated_data): + assignees = validated_data.pop("assignees", None) + labels = validated_data.pop("labels", None) + + project_id = self.context["project_id"] + workspace_id = self.context["workspace_id"] + default_assignee_id = self.context["default_assignee_id"] + + issue = Issue.objects.create(**validated_data, project_id=project_id) + + # Issue Audit Users + created_by_id = issue.created_by_id + updated_by_id = issue.updated_by_id + + if assignees is not None and len(assignees): + IssueAssignee.objects.bulk_create( + [ + IssueAssignee( + assignee=user, + issue=issue, + project_id=project_id, + workspace_id=workspace_id, + created_by_id=created_by_id, + updated_by_id=updated_by_id, + ) + for user in assignees + ], + batch_size=10, + ) + else: + # Then assign it to default assignee + if default_assignee_id is not None: + IssueAssignee.objects.create( + assignee_id=default_assignee_id, + issue=issue, + project_id=project_id, + workspace_id=workspace_id, + created_by_id=created_by_id, + updated_by_id=updated_by_id, + ) + + if labels is not None and len(labels): + IssueLabel.objects.bulk_create( + [ + IssueLabel( + label=label, + issue=issue, + project_id=project_id, + workspace_id=workspace_id, + created_by_id=created_by_id, + updated_by_id=updated_by_id, + ) + for label in labels + ], + batch_size=10, + ) + + return issue + + def update(self, instance, validated_data): + assignees = validated_data.pop("assignees", None) + labels = validated_data.pop("labels", None) + + # Related models + project_id = instance.project_id + workspace_id = instance.workspace_id + created_by_id = instance.created_by_id + updated_by_id = instance.updated_by_id + + if assignees is not None: + IssueAssignee.objects.filter(issue=instance).delete() + IssueAssignee.objects.bulk_create( + [ + IssueAssignee( + assignee=user, + issue=instance, + project_id=project_id, + workspace_id=workspace_id, + created_by_id=created_by_id, + updated_by_id=updated_by_id, + ) + for user in assignees + ], + batch_size=10, + ) + + if labels is not None: + IssueLabel.objects.filter(issue=instance).delete() + IssueLabel.objects.bulk_create( + [ + IssueLabel( + label=label, + issue=instance, + project_id=project_id, + workspace_id=workspace_id, + created_by_id=created_by_id, + updated_by_id=updated_by_id, + ) + for label in labels + ], + batch_size=10, + ) + + # Time updation occues even when other related models are updated + instance.updated_at = timezone.now() + return super().update(instance, validated_data) + + +class IssueReactionSerializer(BaseSerializer): + + actor_detail = UserLiteSerializer(read_only=True, source="actor") + + class Meta: + model = IssueReaction + fields = "__all__" + read_only_fields = [ + "workspace", + "project", + "issue", + "actor", + ] + + +class CommentReactionSerializer(BaseSerializer): + class Meta: + model = CommentReaction + fields = "__all__" + read_only_fields = ["workspace", "project", "comment", "actor"] + + +class IssueVoteSerializer(BaseSerializer): + + actor_detail = UserLiteSerializer(read_only=True, source="actor") + + class Meta: + model = IssueVote + fields = ["issue", "vote", "workspace", "project", "actor", "actor_detail"] + read_only_fields = fields + + +class IssuePublicSerializer(BaseSerializer): + project_detail = ProjectLiteSerializer(read_only=True, source="project") + state_detail = StateLiteSerializer(read_only=True, source="state") + reactions = IssueReactionSerializer(read_only=True, many=True, source="issue_reactions") + votes = IssueVoteSerializer(read_only=True, many=True) + + class Meta: + model = Issue + fields = [ + "id", + "name", + "description_html", + "sequence_id", + "state", + "state_detail", + "project", + "project_detail", + "workspace", + "priority", + "target_date", + "reactions", + "votes", + ] + read_only_fields = fields + + +class LabelLiteSerializer(BaseSerializer): + class Meta: + model = Label + fields = [ + "id", + "name", + "color", + ] + + + + diff --git a/apiserver/plane/space/serializer/module.py b/apiserver/plane/space/serializer/module.py new file mode 100644 index 000000000..39ce9ec32 --- /dev/null +++ b/apiserver/plane/space/serializer/module.py @@ -0,0 +1,18 @@ +# Module imports +from .base import BaseSerializer +from plane.db.models import ( + Module, +) + +class ModuleBaseSerializer(BaseSerializer): + class Meta: + model = Module + fields = "__all__" + read_only_fields = [ + "workspace", + "project", + "created_by", + "updated_by", + "created_at", + "updated_at", + ] \ No newline at end of file diff --git a/apiserver/plane/space/serializer/project.py b/apiserver/plane/space/serializer/project.py new file mode 100644 index 000000000..be23e0ce2 --- /dev/null +++ b/apiserver/plane/space/serializer/project.py @@ -0,0 +1,20 @@ +# Module imports +from .base import BaseSerializer +from plane.db.models import ( + Project, +) + + +class ProjectLiteSerializer(BaseSerializer): + class Meta: + model = Project + fields = [ + "id", + "identifier", + "name", + "cover_image", + "icon_prop", + "emoji", + "description", + ] + read_only_fields = fields diff --git a/apiserver/plane/space/serializer/state.py b/apiserver/plane/space/serializer/state.py new file mode 100644 index 000000000..903bcc2f4 --- /dev/null +++ b/apiserver/plane/space/serializer/state.py @@ -0,0 +1,28 @@ +# Module imports +from .base import BaseSerializer +from plane.db.models import ( + State, +) + + +class StateSerializer(BaseSerializer): + + class Meta: + model = State + fields = "__all__" + read_only_fields = [ + "workspace", + "project", + ] + + +class StateLiteSerializer(BaseSerializer): + class Meta: + model = State + fields = [ + "id", + "name", + "color", + "group", + ] + read_only_fields = fields diff --git a/apiserver/plane/space/serializer/user.py b/apiserver/plane/space/serializer/user.py new file mode 100644 index 000000000..e206073f7 --- /dev/null +++ b/apiserver/plane/space/serializer/user.py @@ -0,0 +1,22 @@ +# Module imports +from .base import BaseSerializer +from plane.db.models import ( + User, +) + + +class UserLiteSerializer(BaseSerializer): + class Meta: + model = User + fields = [ + "id", + "first_name", + "last_name", + "avatar", + "is_bot", + "display_name", + ] + read_only_fields = [ + "id", + "is_bot", + ] diff --git a/apiserver/plane/space/serializer/workspace.py b/apiserver/plane/space/serializer/workspace.py new file mode 100644 index 000000000..ecf99079f --- /dev/null +++ b/apiserver/plane/space/serializer/workspace.py @@ -0,0 +1,15 @@ +# Module imports +from .base import BaseSerializer +from plane.db.models import ( + Workspace, +) + +class WorkspaceLiteSerializer(BaseSerializer): + class Meta: + model = Workspace + fields = [ + "name", + "slug", + "id", + ] + read_only_fields = fields \ No newline at end of file diff --git a/apiserver/plane/space/urls/__init__.py b/apiserver/plane/space/urls/__init__.py new file mode 100644 index 000000000..054026b00 --- /dev/null +++ b/apiserver/plane/space/urls/__init__.py @@ -0,0 +1,10 @@ +from .inbox import urlpatterns as inbox_urls +from .issue import urlpatterns as issue_urls +from .project import urlpatterns as project_urls + + +urlpatterns = [ + *inbox_urls, + *issue_urls, + *project_urls, +] diff --git a/apiserver/plane/space/urls/inbox.py b/apiserver/plane/space/urls/inbox.py new file mode 100644 index 000000000..60de040e2 --- /dev/null +++ b/apiserver/plane/space/urls/inbox.py @@ -0,0 +1,49 @@ +from django.urls import path + + +from plane.space.views import ( + InboxIssuePublicViewSet, + IssueVotePublicViewSet, + WorkspaceProjectDeployBoardEndpoint, +) + + +urlpatterns = [ + path( + "workspaces//project-boards//inboxes//inbox-issues/", + InboxIssuePublicViewSet.as_view( + { + "get": "list", + "post": "create", + } + ), + name="inbox-issue", + ), + path( + "workspaces//project-boards//inboxes//inbox-issues//", + InboxIssuePublicViewSet.as_view( + { + "get": "retrieve", + "patch": "partial_update", + "delete": "destroy", + } + ), + name="inbox-issue", + ), + path( + "workspaces//project-boards//issues//votes/", + IssueVotePublicViewSet.as_view( + { + "get": "list", + "post": "create", + "delete": "destroy", + } + ), + name="issue-vote-project-board", + ), + path( + "workspaces//project-boards/", + WorkspaceProjectDeployBoardEndpoint.as_view(), + name="workspace-project-boards", + ), +] diff --git a/apiserver/plane/space/urls/issue.py b/apiserver/plane/space/urls/issue.py new file mode 100644 index 000000000..099eace5d --- /dev/null +++ b/apiserver/plane/space/urls/issue.py @@ -0,0 +1,76 @@ +from django.urls import path + + +from plane.space.views import ( + IssueRetrievePublicEndpoint, + IssueCommentPublicViewSet, + IssueReactionPublicViewSet, + CommentReactionPublicViewSet, +) + +urlpatterns = [ + path( + "workspaces//project-boards//issues//", + IssueRetrievePublicEndpoint.as_view(), + name="workspace-project-boards", + ), + path( + "workspaces//project-boards//issues//comments/", + IssueCommentPublicViewSet.as_view( + { + "get": "list", + "post": "create", + } + ), + name="issue-comments-project-board", + ), + path( + "workspaces//project-boards//issues//comments//", + IssueCommentPublicViewSet.as_view( + { + "get": "retrieve", + "patch": "partial_update", + "delete": "destroy", + } + ), + name="issue-comments-project-board", + ), + path( + "workspaces//project-boards//issues//reactions/", + IssueReactionPublicViewSet.as_view( + { + "get": "list", + "post": "create", + } + ), + name="issue-reactions-project-board", + ), + path( + "workspaces//project-boards//issues//reactions//", + IssueReactionPublicViewSet.as_view( + { + "delete": "destroy", + } + ), + name="issue-reactions-project-board", + ), + path( + "workspaces//project-boards//comments//reactions/", + CommentReactionPublicViewSet.as_view( + { + "get": "list", + "post": "create", + } + ), + name="comment-reactions-project-board", + ), + path( + "workspaces//project-boards//comments//reactions//", + CommentReactionPublicViewSet.as_view( + { + "delete": "destroy", + } + ), + name="comment-reactions-project-board", + ), +] diff --git a/apiserver/plane/space/urls/project.py b/apiserver/plane/space/urls/project.py new file mode 100644 index 000000000..dc97b43a7 --- /dev/null +++ b/apiserver/plane/space/urls/project.py @@ -0,0 +1,20 @@ +from django.urls import path + + +from plane.space.views import ( + ProjectDeployBoardPublicSettingsEndpoint, + ProjectIssuesPublicEndpoint, +) + +urlpatterns = [ + path( + "workspaces//project-boards//settings/", + ProjectDeployBoardPublicSettingsEndpoint.as_view(), + name="project-deploy-board-settings", + ), + path( + "workspaces//project-boards//issues/", + ProjectIssuesPublicEndpoint.as_view(), + name="project-deploy-board", + ), +] diff --git a/apiserver/plane/space/views/__init__.py b/apiserver/plane/space/views/__init__.py new file mode 100644 index 000000000..5130e04d5 --- /dev/null +++ b/apiserver/plane/space/views/__init__.py @@ -0,0 +1,15 @@ +from .project import ( + ProjectDeployBoardPublicSettingsEndpoint, + WorkspaceProjectDeployBoardEndpoint, +) + +from .issue import ( + IssueCommentPublicViewSet, + IssueReactionPublicViewSet, + CommentReactionPublicViewSet, + IssueVotePublicViewSet, + IssueRetrievePublicEndpoint, + ProjectIssuesPublicEndpoint, +) + +from .inbox import InboxIssuePublicViewSet diff --git a/apiserver/plane/space/views/base.py b/apiserver/plane/space/views/base.py new file mode 100644 index 000000000..b1d749a09 --- /dev/null +++ b/apiserver/plane/space/views/base.py @@ -0,0 +1,212 @@ +# Python imports +import zoneinfo + +# Django imports +from django.urls import resolve +from django.conf import settings +from django.utils import timezone +from django.db import IntegrityError +from django.core.exceptions import ObjectDoesNotExist, ValidationError + +# Third part imports +from rest_framework import status +from rest_framework import status +from rest_framework.viewsets import ModelViewSet +from rest_framework.response import Response +from rest_framework.exceptions import APIException +from rest_framework.views import APIView +from rest_framework.filters import SearchFilter +from rest_framework.permissions import IsAuthenticated +from sentry_sdk import capture_exception +from django_filters.rest_framework import DjangoFilterBackend + +# Module imports +from plane.utils.paginator import BasePaginator + + +class TimezoneMixin: + """ + This enables timezone conversion according + to the user set timezone + """ + + def initial(self, request, *args, **kwargs): + super().initial(request, *args, **kwargs) + if request.user.is_authenticated: + timezone.activate(zoneinfo.ZoneInfo(request.user.user_timezone)) + else: + timezone.deactivate() + + +class BaseViewSet(TimezoneMixin, ModelViewSet, BasePaginator): + model = None + + permission_classes = [ + IsAuthenticated, + ] + + filter_backends = ( + DjangoFilterBackend, + SearchFilter, + ) + + filterset_fields = [] + + search_fields = [] + + def get_queryset(self): + try: + return self.model.objects.all() + except Exception as e: + capture_exception(e) + raise APIException("Please check the view", status.HTTP_400_BAD_REQUEST) + + def handle_exception(self, exc): + """ + Handle any exception that occurs, by returning an appropriate response, + or re-raising the error. + """ + try: + response = super().handle_exception(exc) + return response + except Exception as e: + if isinstance(e, IntegrityError): + return Response( + {"error": "The payload is not valid"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + if isinstance(e, ValidationError): + return Response( + {"error": "Please provide valid detail"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + if isinstance(e, ObjectDoesNotExist): + model_name = str(exc).split(" matching query does not exist.")[0] + return Response( + {"error": f"{model_name} does not exist."}, + status=status.HTTP_404_NOT_FOUND, + ) + + if isinstance(e, KeyError): + capture_exception(e) + return Response( + {"error": f"key {e} does not exist"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + print(e) if settings.DEBUG else print("Server Error") + capture_exception(e) + return Response({"error": "Something went wrong please try again later"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + + def dispatch(self, request, *args, **kwargs): + try: + response = super().dispatch(request, *args, **kwargs) + + if settings.DEBUG: + from django.db import connection + + print( + f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}" + ) + + return response + except Exception as exc: + response = self.handle_exception(exc) + return exc + + @property + def workspace_slug(self): + return self.kwargs.get("slug", None) + + @property + def project_id(self): + project_id = self.kwargs.get("project_id", None) + if project_id: + return project_id + + if resolve(self.request.path_info).url_name == "project": + return self.kwargs.get("pk", None) + + +class BaseAPIView(TimezoneMixin, APIView, BasePaginator): + permission_classes = [ + IsAuthenticated, + ] + + filter_backends = ( + DjangoFilterBackend, + SearchFilter, + ) + + filterset_fields = [] + + search_fields = [] + + def filter_queryset(self, queryset): + for backend in list(self.filter_backends): + queryset = backend().filter_queryset(self.request, queryset, self) + return queryset + + def handle_exception(self, exc): + """ + Handle any exception that occurs, by returning an appropriate response, + or re-raising the error. + """ + try: + response = super().handle_exception(exc) + return response + except Exception as e: + if isinstance(e, IntegrityError): + return Response( + {"error": "The payload is not valid"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + if isinstance(e, ValidationError): + return Response( + {"error": "Please provide valid detail"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + if isinstance(e, ObjectDoesNotExist): + model_name = str(exc).split(" matching query does not exist.")[0] + return Response( + {"error": f"{model_name} does not exist."}, + status=status.HTTP_404_NOT_FOUND, + ) + + if isinstance(e, KeyError): + return Response({"error": f"key {e} does not exist"}, status=status.HTTP_400_BAD_REQUEST) + + if settings.DEBUG: + print(e) + capture_exception(e) + return Response({"error": "Something went wrong please try again later"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + + def dispatch(self, request, *args, **kwargs): + try: + response = super().dispatch(request, *args, **kwargs) + + if settings.DEBUG: + from django.db import connection + + print( + f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}" + ) + return response + + except Exception as exc: + response = self.handle_exception(exc) + return exc + + @property + def workspace_slug(self): + return self.kwargs.get("slug", None) + + @property + def project_id(self): + return self.kwargs.get("project_id", None) diff --git a/apiserver/plane/space/views/inbox.py b/apiserver/plane/space/views/inbox.py new file mode 100644 index 000000000..53960f672 --- /dev/null +++ b/apiserver/plane/space/views/inbox.py @@ -0,0 +1,282 @@ +# Python imports +import json + +# Django import +from django.utils import timezone +from django.db.models import Q, OuterRef, Func, F, Prefetch +from django.core.serializers.json import DjangoJSONEncoder + +# Third party imports +from rest_framework import status +from rest_framework.response import Response + +# Module imports +from .base import BaseViewSet +from plane.db.models import ( + InboxIssue, + Issue, + State, + IssueLink, + IssueAttachment, + ProjectDeployBoard, +) +from plane.app.serializers import ( + IssueSerializer, + InboxIssueSerializer, + IssueCreateSerializer, + IssueStateInboxSerializer, +) +from plane.utils.issue_filters import issue_filters +from plane.bgtasks.issue_activites_task import issue_activity + + +class InboxIssuePublicViewSet(BaseViewSet): + serializer_class = InboxIssueSerializer + model = InboxIssue + + filterset_fields = [ + "status", + ] + + def get_queryset(self): + project_deploy_board = ProjectDeployBoard.objects.get( + workspace__slug=self.kwargs.get("slug"), + project_id=self.kwargs.get("project_id"), + ) + if project_deploy_board is not None: + return self.filter_queryset( + super() + .get_queryset() + .filter( + Q(snoozed_till__gte=timezone.now()) | Q(snoozed_till__isnull=True), + project_id=self.kwargs.get("project_id"), + workspace__slug=self.kwargs.get("slug"), + inbox_id=self.kwargs.get("inbox_id"), + ) + .select_related("issue", "workspace", "project") + ) + return InboxIssue.objects.none() + + def list(self, request, slug, project_id, inbox_id): + project_deploy_board = ProjectDeployBoard.objects.get( + workspace__slug=slug, project_id=project_id + ) + if project_deploy_board.inbox is None: + return Response( + {"error": "Inbox is not enabled for this Project Board"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + filters = issue_filters(request.query_params, "GET") + issues = ( + Issue.objects.filter( + issue_inbox__inbox_id=inbox_id, + workspace__slug=slug, + project_id=project_id, + ) + .filter(**filters) + .annotate(bridge_id=F("issue_inbox__id")) + .select_related("workspace", "project", "state", "parent") + .prefetch_related("assignees", "labels") + .order_by("issue_inbox__snoozed_till", "issue_inbox__status") + .annotate( + sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id")) + .order_by() + .annotate(count=Func(F("id"), function="Count")) + .values("count") + ) + .annotate( + link_count=IssueLink.objects.filter(issue=OuterRef("id")) + .order_by() + .annotate(count=Func(F("id"), function="Count")) + .values("count") + ) + .annotate( + attachment_count=IssueAttachment.objects.filter(issue=OuterRef("id")) + .order_by() + .annotate(count=Func(F("id"), function="Count")) + .values("count") + ) + .prefetch_related( + Prefetch( + "issue_inbox", + queryset=InboxIssue.objects.only( + "status", "duplicate_to", "snoozed_till", "source" + ), + ) + ) + ) + issues_data = IssueStateInboxSerializer(issues, many=True).data + return Response( + issues_data, + status=status.HTTP_200_OK, + ) + + def create(self, request, slug, project_id, inbox_id): + project_deploy_board = ProjectDeployBoard.objects.get( + workspace__slug=slug, project_id=project_id + ) + if project_deploy_board.inbox is None: + return Response( + {"error": "Inbox is not enabled for this Project Board"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + if not request.data.get("issue", {}).get("name", False): + return Response( + {"error": "Name is required"}, status=status.HTTP_400_BAD_REQUEST + ) + + # Check for valid priority + if not request.data.get("issue", {}).get("priority", "none") in [ + "low", + "medium", + "high", + "urgent", + "none", + ]: + return Response( + {"error": "Invalid priority"}, status=status.HTTP_400_BAD_REQUEST + ) + + # Create or get state + state, _ = State.objects.get_or_create( + name="Triage", + group="backlog", + description="Default state for managing all Inbox Issues", + project_id=project_id, + color="#ff7700", + ) + + # create an issue + issue = Issue.objects.create( + name=request.data.get("issue", {}).get("name"), + description=request.data.get("issue", {}).get("description", {}), + description_html=request.data.get("issue", {}).get( + "description_html", "

" + ), + priority=request.data.get("issue", {}).get("priority", "low"), + project_id=project_id, + state=state, + ) + + # Create an Issue Activity + issue_activity.delay( + type="issue.activity.created", + requested_data=json.dumps(request.data, cls=DjangoJSONEncoder), + actor_id=str(request.user.id), + issue_id=str(issue.id), + project_id=str(project_id), + current_instance=None, + epoch=int(timezone.now().timestamp()), + ) + # create an inbox issue + InboxIssue.objects.create( + inbox_id=inbox_id, + project_id=project_id, + issue=issue, + source=request.data.get("source", "in-app"), + ) + + serializer = IssueStateInboxSerializer(issue) + return Response(serializer.data, status=status.HTTP_200_OK) + + def partial_update(self, request, slug, project_id, inbox_id, pk): + project_deploy_board = ProjectDeployBoard.objects.get( + workspace__slug=slug, project_id=project_id + ) + if project_deploy_board.inbox is None: + return Response( + {"error": "Inbox is not enabled for this Project Board"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + inbox_issue = InboxIssue.objects.get( + pk=pk, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id + ) + # Get the project member + if str(inbox_issue.created_by_id) != str(request.user.id): + return Response( + {"error": "You cannot edit inbox issues"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + # Get issue data + issue_data = request.data.pop("issue", False) + + issue = Issue.objects.get( + pk=inbox_issue.issue_id, workspace__slug=slug, project_id=project_id + ) + # viewers and guests since only viewers and guests + issue_data = { + "name": issue_data.get("name", issue.name), + "description_html": issue_data.get( + "description_html", issue.description_html + ), + "description": issue_data.get("description", issue.description), + } + + issue_serializer = IssueCreateSerializer(issue, data=issue_data, partial=True) + + if issue_serializer.is_valid(): + current_instance = issue + # Log all the updates + requested_data = json.dumps(issue_data, cls=DjangoJSONEncoder) + if issue is not None: + issue_activity.delay( + type="issue.activity.updated", + requested_data=requested_data, + actor_id=str(request.user.id), + issue_id=str(issue.id), + project_id=str(project_id), + current_instance=json.dumps( + IssueSerializer(current_instance).data, + cls=DjangoJSONEncoder, + ), + epoch=int(timezone.now().timestamp()), + ) + issue_serializer.save() + return Response(issue_serializer.data, status=status.HTTP_200_OK) + return Response(issue_serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def retrieve(self, request, slug, project_id, inbox_id, pk): + project_deploy_board = ProjectDeployBoard.objects.get( + workspace__slug=slug, project_id=project_id + ) + if project_deploy_board.inbox is None: + return Response( + {"error": "Inbox is not enabled for this Project Board"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + inbox_issue = InboxIssue.objects.get( + pk=pk, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id + ) + issue = Issue.objects.get( + pk=inbox_issue.issue_id, workspace__slug=slug, project_id=project_id + ) + serializer = IssueStateInboxSerializer(issue) + return Response(serializer.data, status=status.HTTP_200_OK) + + def destroy(self, request, slug, project_id, inbox_id, pk): + project_deploy_board = ProjectDeployBoard.objects.get( + workspace__slug=slug, project_id=project_id + ) + if project_deploy_board.inbox is None: + return Response( + {"error": "Inbox is not enabled for this Project Board"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + inbox_issue = InboxIssue.objects.get( + pk=pk, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id + ) + + if str(inbox_issue.created_by_id) != str(request.user.id): + return Response( + {"error": "You cannot delete inbox issue"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + inbox_issue.delete() + return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/apiserver/plane/space/views/issue.py b/apiserver/plane/space/views/issue.py new file mode 100644 index 000000000..faab8834d --- /dev/null +++ b/apiserver/plane/space/views/issue.py @@ -0,0 +1,656 @@ +# Python imports +import json + +# Django imports +from django.utils import timezone +from django.db.models import ( + Prefetch, + OuterRef, + Func, + F, + Q, + Count, + Case, + Value, + CharField, + When, + Exists, + Max, + IntegerField, +) +from django.core.serializers.json import DjangoJSONEncoder + +# Third Party imports +from rest_framework.response import Response +from rest_framework import status +from rest_framework.permissions import AllowAny, IsAuthenticated + +# Module imports +from .base import BaseViewSet, BaseAPIView +from plane.app.serializers import ( + IssueCommentSerializer, + IssueReactionSerializer, + CommentReactionSerializer, + IssueVoteSerializer, + IssuePublicSerializer, +) + +from plane.db.models import ( + Issue, + IssueComment, + Label, + IssueLink, + IssueAttachment, + State, + ProjectMember, + IssueReaction, + CommentReaction, + ProjectDeployBoard, + IssueVote, + ProjectPublicMember, +) +from plane.bgtasks.issue_activites_task import issue_activity +from plane.utils.grouper import group_results +from plane.utils.issue_filters import issue_filters + + +class IssueCommentPublicViewSet(BaseViewSet): + serializer_class = IssueCommentSerializer + model = IssueComment + + filterset_fields = [ + "issue__id", + "workspace__id", + ] + + def get_permissions(self): + if self.action in ["list", "retrieve"]: + self.permission_classes = [ + AllowAny, + ] + else: + self.permission_classes = [ + IsAuthenticated, + ] + + return super(IssueCommentPublicViewSet, self).get_permissions() + + def get_queryset(self): + try: + project_deploy_board = ProjectDeployBoard.objects.get( + workspace__slug=self.kwargs.get("slug"), + project_id=self.kwargs.get("project_id"), + ) + if project_deploy_board.comments: + return self.filter_queryset( + super() + .get_queryset() + .filter(workspace__slug=self.kwargs.get("slug")) + .filter(issue_id=self.kwargs.get("issue_id")) + .filter(access="EXTERNAL") + .select_related("project") + .select_related("workspace") + .select_related("issue") + .annotate( + is_member=Exists( + ProjectMember.objects.filter( + workspace__slug=self.kwargs.get("slug"), + project_id=self.kwargs.get("project_id"), + member_id=self.request.user.id, + is_active=True, + ) + ) + ) + .distinct() + ).order_by("created_at") + return IssueComment.objects.none() + except ProjectDeployBoard.DoesNotExist: + return IssueComment.objects.none() + + def create(self, request, slug, project_id, issue_id): + project_deploy_board = ProjectDeployBoard.objects.get( + workspace__slug=slug, project_id=project_id + ) + + if not project_deploy_board.comments: + return Response( + {"error": "Comments are not enabled for this project"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + serializer = IssueCommentSerializer(data=request.data) + if serializer.is_valid(): + serializer.save( + project_id=project_id, + issue_id=issue_id, + actor=request.user, + access="EXTERNAL", + ) + issue_activity.delay( + type="comment.activity.created", + requested_data=json.dumps(serializer.data, cls=DjangoJSONEncoder), + actor_id=str(request.user.id), + issue_id=str(issue_id), + project_id=str(project_id), + current_instance=None, + epoch=int(timezone.now().timestamp()), + ) + if not ProjectMember.objects.filter( + project_id=project_id, + member=request.user, + is_active=True, + ).exists(): + # Add the user for workspace tracking + _ = ProjectPublicMember.objects.get_or_create( + project_id=project_id, + member=request.user, + ) + + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def partial_update(self, request, slug, project_id, issue_id, pk): + project_deploy_board = ProjectDeployBoard.objects.get( + workspace__slug=slug, project_id=project_id + ) + + if not project_deploy_board.comments: + return Response( + {"error": "Comments are not enabled for this project"}, + status=status.HTTP_400_BAD_REQUEST, + ) + comment = IssueComment.objects.get( + workspace__slug=slug, pk=pk, actor=request.user + ) + serializer = IssueCommentSerializer(comment, data=request.data, partial=True) + if serializer.is_valid(): + serializer.save() + issue_activity.delay( + type="comment.activity.updated", + requested_data=json.dumps(request.data, cls=DjangoJSONEncoder), + actor_id=str(request.user.id), + issue_id=str(issue_id), + project_id=str(project_id), + current_instance=json.dumps( + IssueCommentSerializer(comment).data, + cls=DjangoJSONEncoder, + ), + epoch=int(timezone.now().timestamp()), + ) + return Response(serializer.data, status=status.HTTP_200_OK) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def destroy(self, request, slug, project_id, issue_id, pk): + project_deploy_board = ProjectDeployBoard.objects.get( + workspace__slug=slug, project_id=project_id + ) + + if not project_deploy_board.comments: + return Response( + {"error": "Comments are not enabled for this project"}, + status=status.HTTP_400_BAD_REQUEST, + ) + comment = IssueComment.objects.get( + workspace__slug=slug, pk=pk, project_id=project_id, actor=request.user + ) + issue_activity.delay( + type="comment.activity.deleted", + requested_data=json.dumps({"comment_id": str(pk)}), + actor_id=str(request.user.id), + issue_id=str(issue_id), + project_id=str(project_id), + current_instance=json.dumps( + IssueCommentSerializer(comment).data, + cls=DjangoJSONEncoder, + ), + epoch=int(timezone.now().timestamp()), + ) + comment.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + + +class IssueReactionPublicViewSet(BaseViewSet): + serializer_class = IssueReactionSerializer + model = IssueReaction + + def get_queryset(self): + try: + project_deploy_board = ProjectDeployBoard.objects.get( + workspace__slug=self.kwargs.get("slug"), + project_id=self.kwargs.get("project_id"), + ) + if project_deploy_board.reactions: + return ( + super() + .get_queryset() + .filter(workspace__slug=self.kwargs.get("slug")) + .filter(project_id=self.kwargs.get("project_id")) + .filter(issue_id=self.kwargs.get("issue_id")) + .order_by("-created_at") + .distinct() + ) + return IssueReaction.objects.none() + except ProjectDeployBoard.DoesNotExist: + return IssueReaction.objects.none() + + def create(self, request, slug, project_id, issue_id): + project_deploy_board = ProjectDeployBoard.objects.get( + workspace__slug=slug, project_id=project_id + ) + + if not project_deploy_board.reactions: + return Response( + {"error": "Reactions are not enabled for this project board"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + serializer = IssueReactionSerializer(data=request.data) + if serializer.is_valid(): + serializer.save( + project_id=project_id, issue_id=issue_id, actor=request.user + ) + if not ProjectMember.objects.filter( + project_id=project_id, + member=request.user, + is_active=True, + ).exists(): + # Add the user for workspace tracking + _ = ProjectPublicMember.objects.get_or_create( + project_id=project_id, + member=request.user, + ) + issue_activity.delay( + type="issue_reaction.activity.created", + requested_data=json.dumps(self.request.data, cls=DjangoJSONEncoder), + actor_id=str(self.request.user.id), + issue_id=str(self.kwargs.get("issue_id", None)), + project_id=str(self.kwargs.get("project_id", None)), + current_instance=None, + epoch=int(timezone.now().timestamp()), + ) + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def destroy(self, request, slug, project_id, issue_id, reaction_code): + project_deploy_board = ProjectDeployBoard.objects.get( + workspace__slug=slug, project_id=project_id + ) + + if not project_deploy_board.reactions: + return Response( + {"error": "Reactions are not enabled for this project board"}, + status=status.HTTP_400_BAD_REQUEST, + ) + issue_reaction = IssueReaction.objects.get( + workspace__slug=slug, + issue_id=issue_id, + reaction=reaction_code, + actor=request.user, + ) + issue_activity.delay( + type="issue_reaction.activity.deleted", + requested_data=None, + actor_id=str(self.request.user.id), + issue_id=str(self.kwargs.get("issue_id", None)), + project_id=str(self.kwargs.get("project_id", None)), + current_instance=json.dumps( + { + "reaction": str(reaction_code), + "identifier": str(issue_reaction.id), + } + ), + epoch=int(timezone.now().timestamp()), + ) + issue_reaction.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + + +class CommentReactionPublicViewSet(BaseViewSet): + serializer_class = CommentReactionSerializer + model = CommentReaction + + def get_queryset(self): + try: + project_deploy_board = ProjectDeployBoard.objects.get( + workspace__slug=self.kwargs.get("slug"), + project_id=self.kwargs.get("project_id"), + ) + if project_deploy_board.reactions: + return ( + super() + .get_queryset() + .filter(workspace__slug=self.kwargs.get("slug")) + .filter(project_id=self.kwargs.get("project_id")) + .filter(comment_id=self.kwargs.get("comment_id")) + .order_by("-created_at") + .distinct() + ) + return CommentReaction.objects.none() + except ProjectDeployBoard.DoesNotExist: + return CommentReaction.objects.none() + + def create(self, request, slug, project_id, comment_id): + project_deploy_board = ProjectDeployBoard.objects.get( + workspace__slug=slug, project_id=project_id + ) + + if not project_deploy_board.reactions: + return Response( + {"error": "Reactions are not enabled for this board"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + serializer = CommentReactionSerializer(data=request.data) + if serializer.is_valid(): + serializer.save( + project_id=project_id, comment_id=comment_id, actor=request.user + ) + if not ProjectMember.objects.filter( + project_id=project_id, + member=request.user, + is_active=True, + ).exists(): + # Add the user for workspace tracking + _ = ProjectPublicMember.objects.get_or_create( + project_id=project_id, + member=request.user, + ) + issue_activity.delay( + type="comment_reaction.activity.created", + requested_data=json.dumps(self.request.data, cls=DjangoJSONEncoder), + actor_id=str(self.request.user.id), + issue_id=None, + project_id=str(self.kwargs.get("project_id", None)), + current_instance=None, + epoch=int(timezone.now().timestamp()), + ) + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def destroy(self, request, slug, project_id, comment_id, reaction_code): + project_deploy_board = ProjectDeployBoard.objects.get( + workspace__slug=slug, project_id=project_id + ) + if not project_deploy_board.reactions: + return Response( + {"error": "Reactions are not enabled for this board"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + comment_reaction = CommentReaction.objects.get( + project_id=project_id, + workspace__slug=slug, + comment_id=comment_id, + reaction=reaction_code, + actor=request.user, + ) + issue_activity.delay( + type="comment_reaction.activity.deleted", + requested_data=None, + actor_id=str(self.request.user.id), + issue_id=None, + project_id=str(self.kwargs.get("project_id", None)), + current_instance=json.dumps( + { + "reaction": str(reaction_code), + "identifier": str(comment_reaction.id), + "comment_id": str(comment_id), + } + ), + epoch=int(timezone.now().timestamp()), + ) + comment_reaction.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + + +class IssueVotePublicViewSet(BaseViewSet): + model = IssueVote + serializer_class = IssueVoteSerializer + + def get_queryset(self): + try: + project_deploy_board = ProjectDeployBoard.objects.get( + workspace__slug=self.kwargs.get("slug"), + project_id=self.kwargs.get("project_id"), + ) + if project_deploy_board.votes: + return ( + super() + .get_queryset() + .filter(issue_id=self.kwargs.get("issue_id")) + .filter(workspace__slug=self.kwargs.get("slug")) + .filter(project_id=self.kwargs.get("project_id")) + ) + return IssueVote.objects.none() + except ProjectDeployBoard.DoesNotExist: + return IssueVote.objects.none() + + def create(self, request, slug, project_id, issue_id): + issue_vote, _ = IssueVote.objects.get_or_create( + actor_id=request.user.id, + project_id=project_id, + issue_id=issue_id, + ) + # Add the user for workspace tracking + if not ProjectMember.objects.filter( + project_id=project_id, + member=request.user, + is_active=True, + ).exists(): + _ = ProjectPublicMember.objects.get_or_create( + project_id=project_id, + member=request.user, + ) + issue_vote.vote = request.data.get("vote", 1) + issue_vote.save() + issue_activity.delay( + type="issue_vote.activity.created", + requested_data=json.dumps(self.request.data, cls=DjangoJSONEncoder), + actor_id=str(self.request.user.id), + issue_id=str(self.kwargs.get("issue_id", None)), + project_id=str(self.kwargs.get("project_id", None)), + current_instance=None, + epoch=int(timezone.now().timestamp()), + ) + serializer = IssueVoteSerializer(issue_vote) + return Response(serializer.data, status=status.HTTP_201_CREATED) + + def destroy(self, request, slug, project_id, issue_id): + issue_vote = IssueVote.objects.get( + workspace__slug=slug, + project_id=project_id, + issue_id=issue_id, + actor_id=request.user.id, + ) + issue_activity.delay( + type="issue_vote.activity.deleted", + requested_data=None, + actor_id=str(self.request.user.id), + issue_id=str(self.kwargs.get("issue_id", None)), + project_id=str(self.kwargs.get("project_id", None)), + current_instance=json.dumps( + { + "vote": str(issue_vote.vote), + "identifier": str(issue_vote.id), + } + ), + epoch=int(timezone.now().timestamp()), + ) + issue_vote.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + + +class IssueRetrievePublicEndpoint(BaseAPIView): + permission_classes = [ + AllowAny, + ] + + def get(self, request, slug, project_id, issue_id): + issue = Issue.objects.get( + workspace__slug=slug, project_id=project_id, pk=issue_id + ) + serializer = IssuePublicSerializer(issue) + return Response(serializer.data, status=status.HTTP_200_OK) + + +class ProjectIssuesPublicEndpoint(BaseAPIView): + permission_classes = [ + AllowAny, + ] + + def get(self, request, slug, project_id): + project_deploy_board = ProjectDeployBoard.objects.get( + workspace__slug=slug, project_id=project_id + ) + + filters = issue_filters(request.query_params, "GET") + + # Custom ordering for priority and state + priority_order = ["urgent", "high", "medium", "low", "none"] + state_order = ["backlog", "unstarted", "started", "completed", "cancelled"] + + order_by_param = request.GET.get("order_by", "-created_at") + + issue_queryset = ( + Issue.issue_objects.annotate( + sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id")) + .order_by() + .annotate(count=Func(F("id"), function="Count")) + .values("count") + ) + .filter(project_id=project_id) + .filter(workspace__slug=slug) + .select_related("project", "workspace", "state", "parent") + .prefetch_related("assignees", "labels") + .prefetch_related( + Prefetch( + "issue_reactions", + queryset=IssueReaction.objects.select_related("actor"), + ) + ) + .prefetch_related( + Prefetch( + "votes", + queryset=IssueVote.objects.select_related("actor"), + ) + ) + .filter(**filters) + .annotate(cycle_id=F("issue_cycle__cycle_id")) + .annotate(module_id=F("issue_module__module_id")) + .annotate( + link_count=IssueLink.objects.filter(issue=OuterRef("id")) + .order_by() + .annotate(count=Func(F("id"), function="Count")) + .values("count") + ) + .annotate( + attachment_count=IssueAttachment.objects.filter(issue=OuterRef("id")) + .order_by() + .annotate(count=Func(F("id"), function="Count")) + .values("count") + ) + ) + + # Priority Ordering + if order_by_param == "priority" or order_by_param == "-priority": + priority_order = ( + priority_order if order_by_param == "priority" else priority_order[::-1] + ) + issue_queryset = issue_queryset.annotate( + priority_order=Case( + *[ + When(priority=p, then=Value(i)) + for i, p in enumerate(priority_order) + ], + output_field=CharField(), + ) + ).order_by("priority_order") + + # State Ordering + elif order_by_param in [ + "state__name", + "state__group", + "-state__name", + "-state__group", + ]: + state_order = ( + state_order + if order_by_param in ["state__name", "state__group"] + else state_order[::-1] + ) + issue_queryset = issue_queryset.annotate( + state_order=Case( + *[ + When(state__group=state_group, then=Value(i)) + for i, state_group in enumerate(state_order) + ], + default=Value(len(state_order)), + output_field=CharField(), + ) + ).order_by("state_order") + # assignee and label ordering + elif order_by_param in [ + "labels__name", + "-labels__name", + "assignees__first_name", + "-assignees__first_name", + ]: + issue_queryset = issue_queryset.annotate( + max_values=Max( + order_by_param[1::] + if order_by_param.startswith("-") + else order_by_param + ) + ).order_by( + "-max_values" if order_by_param.startswith("-") else "max_values" + ) + else: + issue_queryset = issue_queryset.order_by(order_by_param) + + issues = IssuePublicSerializer(issue_queryset, many=True).data + + state_group_order = [ + "backlog", + "unstarted", + "started", + "completed", + "cancelled", + ] + + states = ( + State.objects.filter( + ~Q(name="Triage"), + workspace__slug=slug, + project_id=project_id, + ) + .annotate( + custom_order=Case( + *[ + When(group=value, then=Value(index)) + for index, value in enumerate(state_group_order) + ], + default=Value(len(state_group_order)), + output_field=IntegerField(), + ), + ) + .values("name", "group", "color", "id") + .order_by("custom_order", "sequence") + ) + + labels = Label.objects.filter( + workspace__slug=slug, project_id=project_id + ).values("id", "name", "color", "parent") + + ## Grouping the results + group_by = request.GET.get("group_by", False) + if group_by: + issues = group_results(issues, group_by) + + return Response( + { + "issues": issues, + "states": states, + "labels": labels, + }, + status=status.HTTP_200_OK, + ) \ No newline at end of file diff --git a/apiserver/plane/space/views/project.py b/apiserver/plane/space/views/project.py new file mode 100644 index 000000000..8cd3f55c5 --- /dev/null +++ b/apiserver/plane/space/views/project.py @@ -0,0 +1,61 @@ +# Django imports +from django.db.models import ( + Exists, + OuterRef, +) + +# Third Party imports +from rest_framework.response import Response +from rest_framework import status +from rest_framework.permissions import AllowAny + +# Module imports +from .base import BaseAPIView +from plane.app.serializers import ProjectDeployBoardSerializer +from plane.app.permissions import ProjectMemberPermission +from plane.db.models import ( + Project, + ProjectDeployBoard, +) + + +class ProjectDeployBoardPublicSettingsEndpoint(BaseAPIView): + permission_classes = [ + AllowAny, + ] + + def get(self, request, slug, project_id): + project_deploy_board = ProjectDeployBoard.objects.get( + workspace__slug=slug, project_id=project_id + ) + serializer = ProjectDeployBoardSerializer(project_deploy_board) + return Response(serializer.data, status=status.HTTP_200_OK) + + +class WorkspaceProjectDeployBoardEndpoint(BaseAPIView): + permission_classes = [ + AllowAny, + ] + + def get(self, request, slug): + projects = ( + Project.objects.filter(workspace__slug=slug) + .annotate( + is_public=Exists( + ProjectDeployBoard.objects.filter( + workspace__slug=slug, project_id=OuterRef("pk") + ) + ) + ) + .filter(is_public=True) + ).values( + "id", + "identifier", + "name", + "description", + "emoji", + "icon_prop", + "cover_image", + ) + + return Response(projects, status=status.HTTP_200_OK) diff --git a/apiserver/plane/tests/api/base.py b/apiserver/plane/tests/api/base.py index fec51303a..e3209a281 100644 --- a/apiserver/plane/tests/api/base.py +++ b/apiserver/plane/tests/api/base.py @@ -3,7 +3,7 @@ from rest_framework.test import APITestCase, APIClient # Module imports from plane.db.models import User -from plane.api.views.authentication import get_tokens_for_user +from plane.app.views.authentication import get_tokens_for_user class BaseAPITest(APITestCase): diff --git a/apiserver/plane/urls.py b/apiserver/plane/urls.py index 66f6714fb..1b6f95bba 100644 --- a/apiserver/plane/urls.py +++ b/apiserver/plane/urls.py @@ -10,7 +10,8 @@ from django.conf import settings urlpatterns = [ path("", TemplateView.as_view(template_name="index.html")), - path("api/", include("plane.api.urls")), + path("api/", include("plane.app.urls")), + path("api/public/", include("plane.space.urls")), path("api/licenses/", include("plane.license.urls")), path("api/v1/", include("plane.proxy.urls")), path("", include("plane.web.urls")), From 779ef2a4aa2d8406c1350a860f7f032cd0ecfb01 Mon Sep 17 00:00:00 2001 From: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com> Date: Mon, 20 Nov 2023 12:22:43 +0530 Subject: [PATCH 2/5] fix: delete issues in spreadsheet doesn't work (#2718) Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> --- .../spreadsheet/roots/project-root.tsx | 14 +++++++++++++- .../issue-layouts/spreadsheet/spreadsheet-view.tsx | 2 -- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/web/components/issues/issue-layouts/spreadsheet/roots/project-root.tsx b/web/components/issues/issue-layouts/spreadsheet/roots/project-root.tsx index f049914df..a14a8d803 100644 --- a/web/components/issues/issue-layouts/spreadsheet/roots/project-root.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/roots/project-root.tsx @@ -41,6 +41,18 @@ export const ProjectSpreadsheetLayout: React.FC = observer(() => { [issueFilterStore, projectId, workspaceSlug] ); + const handleIssueAction = async (issue: IIssue, action: "copy" | "delete" | "edit") => { + if (!workspaceSlug || !projectId || !user) return; + + if (action === "delete") { + issueDetailStore.deleteIssue(workspaceSlug.toString(), projectId.toString(), issue.id); + issueStore.removeIssueFromStructure(null, null, issue); + } else if (action === "edit") { + issueDetailStore.updateIssue(workspaceSlug.toString(), projectId.toString(), issue.id, issue); + issueStore.updateIssueStructure(null, null, issue); + } + }; + const handleUpdateIssue = useCallback( (issue: IIssue, data: Partial) => { if (!workspaceSlug || !projectId || !user) return; @@ -65,7 +77,7 @@ export const ProjectSpreadsheetLayout: React.FC = observer(() => { members={projectMembers?.map((m) => m.member)} labels={projectLabels || undefined} states={projectId ? projectStateStore.states?.[projectId.toString()] : undefined} - handleIssueAction={() => {}} + handleIssueAction={handleIssueAction} handleUpdateIssue={handleUpdateIssue} disableUserActions={false} enableQuickCreateIssue diff --git a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx index 20fcba939..c258d5437 100644 --- a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx @@ -46,8 +46,6 @@ export const SpreadsheetView: React.FC = observer((props) => { issueId: string; } | null>(null); - const [isInlineCreateIssueFormOpen, setIsInlineCreateIssueFormOpen] = useState(false); - const [isScrolled, setIsScrolled] = useState(false); const containerRef = useRef(null); From 9db631208139165c330ef991dff4c645f0fc66c1 Mon Sep 17 00:00:00 2001 From: Nikhil <118773738+pablohashescobar@users.noreply.github.com> Date: Mon, 20 Nov 2023 12:36:48 +0530 Subject: [PATCH 3/5] fix: self hosted instance (#2795) * dev: update create bucket script * dev: update patch endpoint for instance configuration * dev: add google client secret and default values for ADMIN_EMAIL and LICENSE_ENGINE_BASE_URL --- apiserver/bin/takeoff | 2 +- apiserver/plane/license/api/views/instance.py | 23 +++++++++++-------- .../management/commands/configure_instance.py | 1 + deploy/selfhost/docker-compose.yml | 2 ++ deploy/selfhost/variables.env | 6 +++++ 5 files changed, 24 insertions(+), 10 deletions(-) diff --git a/apiserver/bin/takeoff b/apiserver/bin/takeoff index 13e557edc..637305457 100755 --- a/apiserver/bin/takeoff +++ b/apiserver/bin/takeoff @@ -15,6 +15,6 @@ if [ "$ENABLE_REGISTRATION" != "0" ]; then fi # Create the default bucket -python bin/bucket_script.py +python manage.py create_bucket exec gunicorn -w $GUNICORN_WORKERS -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --max-requests 1200 --max-requests-jitter 1000 --access-logfile - diff --git a/apiserver/plane/license/api/views/instance.py b/apiserver/plane/license/api/views/instance.py index 6b27419fd..15c779e46 100644 --- a/apiserver/plane/license/api/views/instance.py +++ b/apiserver/plane/license/api/views/instance.py @@ -230,13 +230,18 @@ class InstanceConfigurationEndpoint(BaseAPIView): return Response(serializer.data, status=status.HTTP_200_OK) def patch(self, request): - key = request.data.get("key", False) - if not key: - return Response( - {"error": "Key is required"}, status=status.HTTP_400_BAD_REQUEST - ) - configuration = InstanceConfiguration.objects.get(key=key) - configuration.value = request.data.get("value") - configuration.save() - serializer = InstanceConfigurationSerializer(configuration) + configurations = InstanceConfiguration.objects.filter(key__in=request.data.keys()) + + bulk_configurations = [] + for configuration in configurations: + configuration.value = request.data.get(configuration.key, configuration.value) + bulk_configurations.append(configuration) + + InstanceConfiguration.objects.bulk_update( + bulk_configurations, + ["value"], + batch_size=100 + ) + + serializer = InstanceConfigurationSerializer(configurations, many=True) return Response(serializer.data, status=status.HTTP_200_OK) diff --git a/apiserver/plane/license/management/commands/configure_instance.py b/apiserver/plane/license/management/commands/configure_instance.py index d71d9f590..9e3e253ad 100644 --- a/apiserver/plane/license/management/commands/configure_instance.py +++ b/apiserver/plane/license/management/commands/configure_instance.py @@ -15,6 +15,7 @@ class Command(BaseCommand): config_keys = { # Authentication Settings "GOOGLE_CLIENT_ID": os.environ.get("GOOGLE_CLIENT_ID"), + "GOOGLE_CLIENT_SECRET": os.environ.get("GOOGLE_CLIENT_SECRET"), "GITHUB_CLIENT_ID": os.environ.get("GITHUB_CLIENT_ID"), "GITHUB_CLIENT_SECRET": os.environ.get("GITHUB_CLIENT_SECRET"), "ENABLE_SIGNUP": os.environ.get("ENABLE_SIGNUP", "1"), diff --git a/deploy/selfhost/docker-compose.yml b/deploy/selfhost/docker-compose.yml index d324605ef..29676d9f8 100644 --- a/deploy/selfhost/docker-compose.yml +++ b/deploy/selfhost/docker-compose.yml @@ -13,6 +13,8 @@ x-app-env : &app-env - DOCKERIZED=${DOCKERIZED:-1} # deprecated - CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS:-""} - SENTRY_ENVIRONMENT=${SENTRY_ENVIRONMENT:-"production"} + - ADMIN_EMAIL=${ADMIN_EMAIL:-""} + - LICENSE_ENGINE_BASE_URL=${LICENSE_ENGINE_BASE_URL:-""} # Gunicorn Workers - GUNICORN_WORKERS=${GUNICORN_WORKERS:-2} #DB SETTINGS diff --git a/deploy/selfhost/variables.env b/deploy/selfhost/variables.env index 0a47395c3..c72fec098 100644 --- a/deploy/selfhost/variables.env +++ b/deploy/selfhost/variables.env @@ -64,3 +64,9 @@ FILE_SIZE_LIMIT=5242880 # Gunicorn Workers GUNICORN_WORKERS=2 + +# Admin Email +ADMIN_EMAIL="" + +# License Engine url +LICENSE_ENGINE_BASE_URL="" \ No newline at end of file From 8839e42dc0c5788d56a04d5dab2d264d6bd0ff52 Mon Sep 17 00:00:00 2001 From: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com> Date: Mon, 20 Nov 2023 12:48:30 +0530 Subject: [PATCH 4/5] fix: archive issue bugs (#2712) * fix: blur on side/modal peek view * fix: delete archive not working on list layout with group by is none * fix: show empty group has no effect * fix: filter/display options same as production * fix: disabling full-screen peek-overview for archive issues * fix: truncate in calendar view --- .../list/roots/archived-issue-root.tsx | 5 +- .../issues/issue-peek-overview/view.tsx | 64 ++++++++++--------- web/constants/issue.ts | 17 +---- web/store/archived-issues/issue.store.ts | 2 +- 4 files changed, 42 insertions(+), 46 deletions(-) diff --git a/web/components/issues/issue-layouts/list/roots/archived-issue-root.tsx b/web/components/issues/issue-layouts/list/roots/archived-issue-root.tsx index a029ceda2..0d1fce245 100644 --- a/web/components/issues/issue-layouts/list/roots/archived-issue-root.tsx +++ b/web/components/issues/issue-layouts/list/roots/archived-issue-root.tsx @@ -30,12 +30,14 @@ export const ArchivedIssueListLayout: FC = observer(() => { const issues = archivedIssueStore.getIssues; const displayProperties = archivedIssueFiltersStore?.userDisplayProperties || null; const group_by: string | null = archivedIssueFiltersStore?.userDisplayFilters?.group_by || null; + const showEmptyGroup = archivedIssueFiltersStore?.userDisplayFilters?.show_empty_groups || false; const handleIssues = (group_by: string | null, issue: IIssue, action: "delete" | "update") => { if (!workspaceSlug || !projectId) return; if (action === "delete") { - archivedIssueStore.deleteArchivedIssue(group_by, null, issue); + archivedIssueStore.deleteArchivedIssue(group_by === "null" ? null : group_by, null, issue); + archivedIssueStore.fetchIssues(workspaceSlug.toString(), projectId.toString()); } }; @@ -68,6 +70,7 @@ export const ArchivedIssueListLayout: FC = observer(() => { members={projectMembers?.map((m) => m.member) ?? null} projects={projects} estimates={estimates?.points ? orderArrayBy(estimates.points, "key") : null} + showEmptyGroup={showEmptyGroup} /> ); diff --git a/web/components/issues/issue-peek-overview/view.tsx b/web/components/issues/issue-peek-overview/view.tsx index 8afc7e600..3d29c1545 100644 --- a/web/components/issues/issue-peek-overview/view.tsx +++ b/web/components/issues/issue-peek-overview/view.tsx @@ -247,9 +247,6 @@ export const IssueView: FC = observer((props) => { {/* content */}
- {isArchived && ( -
- )} {isLoading && !issue ? (
@@ -258,7 +255,10 @@ export const IssueView: FC = observer((props) => { issue && ( <> {["side-peek", "modal"].includes(peekMode) ? ( -
+
+ {isArchived && ( +
+ )} = observer((props) => { />
) : ( -
-
- +
+
+
+ - + +
-
+
Date: Mon, 20 Nov 2023 13:29:54 +0530 Subject: [PATCH 5/5] feat: event tracking using posthog and created application provider to render multiple wrappers (#2757) * fix: event tracker changes * fix: App provider implementation using wrappers * fix: updating packages * fix: handling warning * fix: wrapper fixes and minor optimization changes * fix: chore app-provider clearnup * fix: cleanup * fix: removing jitsu tracking * fix: minor updates * fix: adding event to posthog event tracker (#2802) * dev: posthog event tracker update intitiate * fix: adding events for posthog integration * fix: event payload --------- Co-authored-by: Ramesh Kumar Chandra <31303617+rameshkumarchandra@users.noreply.github.com> --- packages/ui/package.json | 9 +- turbo.json | 7 +- .../custom-analytics/sidebar/sidebar.tsx | 14 - .../analytics/project-modal/main-content.tsx | 66 +- .../command-palette/command-modal.tsx | 4 +- .../issue/change-issue-assignee.tsx | 6 +- .../issue/change-issue-priority.tsx | 9 +- .../issue/change-issue-state.tsx | 6 +- .../core/modals/bulk-delete-issues-modal.tsx | 13 +- .../core/modals/gpt-assistant-modal.tsx | 35 +- .../cycles/gantt-chart/cycles-list-layout.tsx | 4 +- web/components/cycles/sidebar.tsx | 5 +- web/components/exporter/export-modal.tsx | 2 +- web/components/inbox/issue-activity.tsx | 6 +- .../integration/delete-import-modal.tsx | 4 +- web/components/integration/github/root.tsx | 4 +- web/components/integration/jira/root.tsx | 4 +- web/components/issues/draft-issue-form.tsx | 13 +- web/components/issues/draft-issue-modal.tsx | 26 +- web/components/issues/form.tsx | 13 +- .../kanban/headers/group-by-card.tsx | 8 +- .../list/headers/group-by-card.tsx | 8 +- web/components/issues/main-content.tsx | 6 +- .../issues/peek-overview/issue-activity.tsx | 8 +- .../issues/sidebar-select/blocked.tsx | 5 +- .../issues/sidebar-select/blocker.tsx | 5 +- .../issues/sidebar-select/duplicate.tsx | 5 +- .../issues/sidebar-select/label.tsx | 6 +- .../issues/sidebar-select/relates-to.tsx | 5 +- web/components/issues/sidebar.tsx | 24 +- .../issues/sub-issues/properties.tsx | 41 +- web/components/issues/sub-issues/root.tsx | 2 +- web/components/modules/sidebar.tsx | 3 +- web/components/onboarding/invite-members.tsx | 4 +- web/components/onboarding/join-workspaces.tsx | 12 +- web/components/pages/create-block.tsx | 14 +- .../pages/create-update-block-inline.tsx | 75 +- .../pages/create-update-page-modal.tsx | 39 +- web/components/pages/delete-page-modal.tsx | 4 +- web/components/pages/pages-view.tsx | 2 +- web/components/pages/single-page-block.tsx | 63 +- .../project/create-project-modal.tsx | 12 +- .../project/delete-project-modal.tsx | 7 + web/components/project/form.tsx | 10 +- .../project/send-project-invitation-modal.tsx | 10 +- .../project/settings/features-list.tsx | 33 - .../states/create-update-state-inline.tsx | 19 +- web/components/states/delete-state-modal.tsx | 6 +- .../workspace/create-workspace-form.tsx | 12 + .../workspace/delete-workspace-modal.tsx | 13 +- .../workspace/settings/workspace-details.tsx | 10 +- web/constants/crisp.tsx | 45 - web/contexts/issue-view.context.tsx | 119 +-- web/helpers/event-tracker.helper.ts | 13 + web/helpers/user.helper.ts | 12 + web/hooks/use-comment-reaction.tsx | 7 +- web/hooks/use-issue-reaction.tsx | 6 +- web/lib/app-provider.tsx | 46 + web/lib/auth.ts | 195 ---- web/lib/cookie.ts | 16 - web/lib/wrappers/crisp-wrapper.tsx | 40 + web/lib/wrappers/posthog-wrapper.tsx | 72 ++ .../store-wrapper.tsx} | 13 +- web/package.json | 1 + web/pages/[workspaceSlug]/analytics.tsx | 34 +- .../archived-issues/[archivedIssueId].tsx | 6 +- .../projects/[projectId]/cycles/index.tsx | 1 + .../projects/[projectId]/issues/[issueId].tsx | 8 +- .../projects/[projectId]/pages/[pageId].tsx | 43 +- .../[workspaceSlug]/settings/members.tsx | 11 +- web/pages/_app.tsx | 17 +- web/pages/api/track-event.ts | 34 - web/pages/api/unsplash.ts | 26 - web/pages/workspace-invitations/index.tsx | 13 +- web/services/ai.service.ts | 13 +- web/services/cycle.service.ts | 32 +- web/services/inbox.service.ts | 50 +- web/services/integrations/github.service.ts | 16 +- .../integrations/integration.service.ts | 18 +- web/services/integrations/jira.service.ts | 16 +- web/services/issue/issue.service.ts | 78 +- web/services/issue/issue_comment.service.ts | 41 +- web/services/issue/issue_label.service.ts | 75 +- web/services/issue/issue_reaction.service.ts | 42 +- web/services/module.service.ts | 60 +- web/services/page.service.ts | 74 +- .../project/project-estimate.service.ts | 37 +- .../project/project-export.service.ts | 19 +- .../project/project-member.service.ts | 27 +- web/services/project/project-state.service.ts | 45 +- web/services/project/project.service.ts | 39 +- web/services/track_event.service.ts | 850 ------------------ web/services/user.service.ts | 22 +- web/services/view.service.ts | 30 +- web/services/workspace.service.ts | 47 +- web/store/cycle/cycle_issue.store.ts | 14 +- web/store/cycle/cycle_issue_filters.store.ts | 4 +- web/store/cycle/cycles.store.ts | 11 +- web/store/inbox/inbox_issue_detail.store.ts | 34 +- web/store/issue/issue_detail.store.ts | 23 +- web/store/issue/issue_quick_add.store.ts | 7 +- web/store/module/module_filters.store.ts | 16 +- web/store/module/module_issue.store.ts | 14 +- web/store/module/modules.store.ts | 13 +- web/store/project-view/project_views.store.ts | 17 +- web/store/project/project-estimates.store.ts | 17 +- web/store/project/project-label.store.ts | 22 +- web/store/project/project-state.store.ts | 28 +- web/store/project/project.store.ts | 13 +- web/store/user.store.ts | 4 +- web/store/workspace/workspace-member.store.ts | 4 +- web/store/workspace/workspace.store.ts | 12 +- web/types/app.d.ts | 2 + yarn.lock | 208 +++-- 114 files changed, 825 insertions(+), 2768 deletions(-) delete mode 100644 web/constants/crisp.tsx create mode 100644 web/helpers/event-tracker.helper.ts create mode 100644 web/helpers/user.helper.ts create mode 100644 web/lib/app-provider.tsx delete mode 100644 web/lib/auth.ts delete mode 100644 web/lib/cookie.ts create mode 100644 web/lib/wrappers/crisp-wrapper.tsx create mode 100644 web/lib/wrappers/posthog-wrapper.tsx rename web/lib/{mobx/store-init.tsx => wrappers/store-wrapper.tsx} (91%) delete mode 100644 web/pages/api/track-event.ts delete mode 100644 web/pages/api/unsplash.ts delete mode 100644 web/services/track_event.service.ts diff --git a/packages/ui/package.json b/packages/ui/package.json index 72413eb7c..58b014553 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -18,10 +18,10 @@ "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist" }, "devDependencies": { - "@types/react-color": "^3.0.9", "@types/node": "^20.5.2", - "@types/react": "18.2.0", - "@types/react-dom": "18.2.0", + "@types/react": "^18.2.37", + "@types/react-color": "^3.0.9", + "@types/react-dom": "^18.2.15", "classnames": "^2.3.2", "eslint-config-custom": "*", "react": "^18.2.0", @@ -30,9 +30,6 @@ "tsup": "^5.10.1", "typescript": "4.7.4" }, - "publishConfig": { - "access": "public" - }, "dependencies": { "@blueprintjs/core": "^4.16.3", "@blueprintjs/popover2": "^1.13.3", diff --git a/turbo.json b/turbo.json index ac462d08b..454e09f14 100644 --- a/turbo.json +++ b/turbo.json @@ -1,6 +1,7 @@ { "$schema": "https://turbo.build/schema.json", "globalEnv": [ + "NODE_ENV", "NEXT_PUBLIC_API_BASE_URL", "NEXT_PUBLIC_DEPLOY_URL", "NEXT_PUBLIC_SENTRY_DSN", @@ -16,12 +17,8 @@ "NEXT_PUBLIC_DEPLOY_WITH_NGINX", "NEXT_PUBLIC_POSTHOG_KEY", "NEXT_PUBLIC_POSTHOG_HOST", - "SLACK_OAUTH_URL", - "SLACK_CLIENT_ID", - "SLACK_CLIENT_SECRET", "JITSU_TRACKER_ACCESS_KEY", - "JITSU_TRACKER_HOST", - "UNSPLASH_ACCESS_KEY" + "JITSU_TRACKER_HOST" ], "pipeline": { "build": { diff --git a/web/components/analytics/custom-analytics/sidebar/sidebar.tsx b/web/components/analytics/custom-analytics/sidebar/sidebar.tsx index c99a7cece..240baf31b 100644 --- a/web/components/analytics/custom-analytics/sidebar/sidebar.tsx +++ b/web/components/analytics/custom-analytics/sidebar/sidebar.tsx @@ -4,7 +4,6 @@ import { observer } from "mobx-react-lite"; import { mutate } from "swr"; // services import { AnalyticsService } from "services/analytics.service"; -import { TrackEventService } from "services/track_event.service"; // hooks import useToast from "hooks/use-toast"; import { useMobxStore } from "lib/mobx/store-provider"; @@ -29,7 +28,6 @@ type Props = { }; const analyticsService = new AnalyticsService(); -const trackEventService = new TrackEventService(); export const CustomAnalyticsSidebar: React.FC = observer( ({ analytics, params, fullScreen, isProjectLevel = false }) => { @@ -90,18 +88,6 @@ export const CustomAnalyticsSidebar: React.FC = observer( eventPayload.moduleId = moduleDetails.id; eventPayload.moduleName = moduleDetails.name; } - - trackEventService.trackAnalyticsEvent( - eventPayload, - cycleId - ? "CYCLE_ANALYTICS_EXPORT" - : moduleId - ? "MODULE_ANALYTICS_EXPORT" - : projectId - ? "PROJECT_ANALYTICS_EXPORT" - : "WORKSPACE_ANALYTICS_EXPORT", - user - ); }; const exportAnalytics = () => { diff --git a/web/components/analytics/project-modal/main-content.tsx b/web/components/analytics/project-modal/main-content.tsx index bdab57392..55ed1d403 100644 --- a/web/components/analytics/project-modal/main-content.tsx +++ b/web/components/analytics/project-modal/main-content.tsx @@ -1,15 +1,10 @@ import React from "react"; -import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import { Tab } from "@headlessui/react"; -// mobx store -import { useMobxStore } from "lib/mobx/store-provider"; -// services -import { TrackEventService } from "services/track_event.service"; // components import { CustomAnalytics, ScopeAndDemand } from "components/analytics"; // types -import { ICycle, IModule, IProject, IWorkspace } from "types"; +import { ICycle, IModule, IProject } from "types"; // constants import { ANALYTICS_TABS } from "constants/analytics"; @@ -20,63 +15,8 @@ type Props = { projectDetails: IProject | undefined; }; -const trackEventService = new TrackEventService(); - export const ProjectAnalyticsModalMainContent: React.FC = observer((props) => { - const { fullScreen, cycleDetails, moduleDetails, projectDetails } = props; - - const router = useRouter(); - const { workspaceSlug } = router.query; - - const { user: userStore } = useMobxStore(); - - const user = userStore.currentUser; - - const trackAnalyticsEvent = (tab: string) => { - if (!workspaceSlug || !user) return; - - const eventPayload: any = { - workspaceSlug: workspaceSlug.toString(), - }; - - if (projectDetails) { - const workspaceDetails = projectDetails.workspace as IWorkspace; - - eventPayload.workspaceId = workspaceDetails.id; - eventPayload.workspaceName = workspaceDetails.name; - eventPayload.projectId = projectDetails.id; - eventPayload.projectIdentifier = projectDetails.identifier; - eventPayload.projectName = projectDetails.name; - } - - if (cycleDetails || moduleDetails) { - const details = cycleDetails || moduleDetails; - - eventPayload.workspaceId = details?.workspace_detail?.id; - eventPayload.workspaceName = details?.workspace_detail?.name; - eventPayload.projectId = details?.project_detail.id; - eventPayload.projectIdentifier = details?.project_detail.identifier; - eventPayload.projectName = details?.project_detail.name; - } - - if (cycleDetails) { - eventPayload.cycleId = cycleDetails.id; - eventPayload.cycleName = cycleDetails.name; - } - - if (moduleDetails) { - eventPayload.moduleId = moduleDetails.id; - eventPayload.moduleName = moduleDetails.name; - } - - const eventType = tab === "scope_and_demand" ? "SCOPE_AND_DEMAND_ANALYTICS" : "CUSTOM_ANALYTICS"; - - trackEventService.trackAnalyticsEvent( - eventPayload, - cycleDetails ? `CYCLE_${eventType}` : moduleDetails ? `MODULE_${eventType}` : `PROJECT_${eventType}`, - user - ); - }; + const { fullScreen, cycleDetails, moduleDetails } = props; return ( @@ -89,7 +29,7 @@ export const ProjectAnalyticsModalMainContent: React.FC = observer((props selected ? "bg-custom-background-80" : "" }` } - onClick={() => trackAnalyticsEvent(tab.key)} + onClick={() => {}} > {tab.title} diff --git a/web/components/command-palette/command-modal.tsx b/web/components/command-palette/command-modal.tsx index 04625f77a..bd066326d 100644 --- a/web/components/command-palette/command-modal.tsx +++ b/web/components/command-palette/command-modal.tsx @@ -125,7 +125,7 @@ export const CommandModal: React.FC = observer((props) => { const payload = { ...formData }; await issueService - .patchIssue(workspaceSlug as string, projectId as string, issueId as string, payload, user) + .patchIssue(workspaceSlug as string, projectId as string, issueId as string, payload) .then(() => { mutate(PROJECT_ISSUES_ACTIVITY(issueId as string)); mutate(ISSUE_DETAILS(issueId as string)); @@ -134,7 +134,7 @@ export const CommandModal: React.FC = observer((props) => { console.error(e); }); }, - [workspaceSlug, issueId, projectId, user] + [workspaceSlug, issueId, projectId] ); const handleIssueAssignees = (assignee: string) => { diff --git a/web/components/command-palette/issue/change-issue-assignee.tsx b/web/components/command-palette/issue/change-issue-assignee.tsx index 512428310..1693a6b94 100644 --- a/web/components/command-palette/issue/change-issue-assignee.tsx +++ b/web/components/command-palette/issue/change-issue-assignee.tsx @@ -25,7 +25,7 @@ type Props = { const issueService = new IssueService(); export const ChangeIssueAssignee: FC = observer((props) => { - const { setIsPaletteOpen, issue, user } = props; + const { setIsPaletteOpen, issue } = props; // router const router = useRouter(); const { workspaceSlug, projectId, issueId } = router.query; @@ -71,7 +71,7 @@ export const ChangeIssueAssignee: FC = observer((props) => { const payload = { ...formData }; await issueService - .patchIssue(workspaceSlug as string, projectId as string, issueId as string, payload, user) + .patchIssue(workspaceSlug as string, projectId as string, issueId as string, payload) .then(() => { mutate(PROJECT_ISSUES_ACTIVITY(issueId as string)); }) @@ -79,7 +79,7 @@ export const ChangeIssueAssignee: FC = observer((props) => { console.error(e); }); }, - [workspaceSlug, issueId, projectId, user] + [workspaceSlug, issueId, projectId] ); const handleIssueAssignees = (assignee: string) => { diff --git a/web/components/command-palette/issue/change-issue-priority.tsx b/web/components/command-palette/issue/change-issue-priority.tsx index 5551a8a3a..c5e4bfa2a 100644 --- a/web/components/command-palette/issue/change-issue-priority.tsx +++ b/web/components/command-palette/issue/change-issue-priority.tsx @@ -1,9 +1,6 @@ import React, { Dispatch, SetStateAction, useCallback } from "react"; - import { useRouter } from "next/router"; - import { mutate } from "swr"; - // cmdk import { Command } from "cmdk"; // services @@ -26,7 +23,7 @@ type Props = { // services const issueService = new IssueService(); -export const ChangeIssuePriority: React.FC = ({ setIsPaletteOpen, issue, user }) => { +export const ChangeIssuePriority: React.FC = ({ setIsPaletteOpen, issue }) => { const router = useRouter(); const { workspaceSlug, projectId, issueId } = router.query; @@ -49,7 +46,7 @@ export const ChangeIssuePriority: React.FC = ({ setIsPaletteOpen, issue, const payload = { ...formData }; await issueService - .patchIssue(workspaceSlug as string, projectId as string, issueId as string, payload, user) + .patchIssue(workspaceSlug as string, projectId as string, issueId as string, payload) .then(() => { mutate(PROJECT_ISSUES_ACTIVITY(issueId as string)); }) @@ -57,7 +54,7 @@ export const ChangeIssuePriority: React.FC = ({ setIsPaletteOpen, issue, console.error(e); }); }, - [workspaceSlug, issueId, projectId, user] + [workspaceSlug, issueId, projectId] ); const handleIssueState = (priority: TIssuePriorities) => { diff --git a/web/components/command-palette/issue/change-issue-state.tsx b/web/components/command-palette/issue/change-issue-state.tsx index 688aeb49f..cbddfb688 100644 --- a/web/components/command-palette/issue/change-issue-state.tsx +++ b/web/components/command-palette/issue/change-issue-state.tsx @@ -25,7 +25,7 @@ type Props = { const issueService = new IssueService(); const stateService = new ProjectStateService(); -export const ChangeIssueState: React.FC = ({ setIsPaletteOpen, issue, user }) => { +export const ChangeIssueState: React.FC = ({ setIsPaletteOpen, issue }) => { const router = useRouter(); const { workspaceSlug, projectId, issueId } = router.query; @@ -52,7 +52,7 @@ export const ChangeIssueState: React.FC = ({ setIsPaletteOpen, issue, use const payload = { ...formData }; await issueService - .patchIssue(workspaceSlug as string, projectId as string, issueId as string, payload, user) + .patchIssue(workspaceSlug as string, projectId as string, issueId as string, payload) .then(() => { mutateStates(); mutate(PROJECT_ISSUES_ACTIVITY(issueId as string)); @@ -61,7 +61,7 @@ export const ChangeIssueState: React.FC = ({ setIsPaletteOpen, issue, use console.error(e); }); }, - [workspaceSlug, issueId, projectId, mutateStates, user] + [workspaceSlug, issueId, projectId, mutateStates] ); const handleIssueState = (stateId: string) => { diff --git a/web/components/core/modals/bulk-delete-issues-modal.tsx b/web/components/core/modals/bulk-delete-issues-modal.tsx index f1c951385..a70a722a2 100644 --- a/web/components/core/modals/bulk-delete-issues-modal.tsx +++ b/web/components/core/modals/bulk-delete-issues-modal.tsx @@ -31,7 +31,7 @@ type Props = { const issueService = new IssueService(); export const BulkDeleteIssuesModal: React.FC = (props) => { - const { isOpen, onClose, user } = props; + const { isOpen, onClose } = props; // router const router = useRouter(); const { workspaceSlug, projectId } = router.query; @@ -78,14 +78,9 @@ export const BulkDeleteIssuesModal: React.FC = (props) => { if (!Array.isArray(data.delete_issue_ids)) data.delete_issue_ids = [data.delete_issue_ids]; await issueService - .bulkDeleteIssues( - workspaceSlug as string, - projectId as string, - { - issue_ids: data.delete_issue_ids, - }, - user - ) + .bulkDeleteIssues(workspaceSlug as string, projectId as string, { + issue_ids: data.delete_issue_ids, + }) .then(() => { setToastAlert({ type: "success", diff --git a/web/components/core/modals/gpt-assistant-modal.tsx b/web/components/core/modals/gpt-assistant-modal.tsx index b98b1bc1c..d677e7daa 100644 --- a/web/components/core/modals/gpt-assistant-modal.tsx +++ b/web/components/core/modals/gpt-assistant-modal.tsx @@ -4,10 +4,8 @@ import { useRouter } from "next/router"; import { Controller, useForm } from "react-hook-form"; // services import { AIService } from "services/ai.service"; -import { TrackEventService } from "services/track_event.service"; // hooks import useToast from "hooks/use-toast"; -import useUserAuth from "hooks/use-user-auth"; // ui import { Button, Input } from "@plane/ui"; // components @@ -34,27 +32,15 @@ type FormData = { // services const aiService = new AIService(); -const trackEventService = new TrackEventService(); -export const GptAssistantModal: React.FC = ({ - isOpen, - handleClose, - inset = "top-0 left-0", - content, - htmlContent, - onResponse, - projectId, - block, - issue, -}) => { +export const GptAssistantModal: React.FC = (props) => { + const { isOpen, handleClose, inset = "top-0 left-0", content, htmlContent, onResponse, projectId } = props; const [response, setResponse] = useState(""); const [invalidResponse, setInvalidResponse] = useState(false); const router = useRouter(); const { workspaceSlug } = router.query; - const { user } = useUserAuth(); - const editorRef = useRef(null); const { setToastAlert } = useToast(); @@ -92,15 +78,10 @@ export const GptAssistantModal: React.FC = ({ } await aiService - .createGptTask( - workspaceSlug as string, - projectId as string, - { - prompt: content && content !== "" ? content : htmlContent ?? "", - task: formData.task, - }, - user - ) + .createGptTask(workspaceSlug as string, projectId as string, { + prompt: content && content !== "" ? content : htmlContent ?? "", + task: formData.task, + }) .then((res) => { setResponse(res.response_html); setFocus("task"); @@ -193,10 +174,6 @@ export const GptAssistantModal: React.FC = ({ onClick={() => { onResponse(response); onClose(); - if (block && user) - trackEventService.trackUseGPTResponseEvent(block, "USE_GPT_RESPONSE_IN_PAGE_BLOCK", user); - else if (issue && user) - trackEventService.trackUseGPTResponseEvent(issue, "USE_GPT_RESPONSE_IN_ISSUE", user); }} > Use this response diff --git a/web/components/cycles/gantt-chart/cycles-list-layout.tsx b/web/components/cycles/gantt-chart/cycles-list-layout.tsx index d5bd4e140..6a605536b 100644 --- a/web/components/cycles/gantt-chart/cycles-list-layout.tsx +++ b/web/components/cycles/gantt-chart/cycles-list-layout.tsx @@ -11,7 +11,7 @@ import useUser from "hooks/use-user"; import useProjectDetails from "hooks/use-project-details"; // components import { GanttChartRoot, IBlockUpdateData, CycleGanttSidebar } from "components/gantt-chart"; -import { CycleGanttBlock, CycleGanttSidebarBlock } from "components/cycles"; +import { CycleGanttBlock } from "components/cycles"; // types import { ICycle } from "types"; @@ -60,7 +60,7 @@ export const CyclesListGanttChartView: FC = ({ cycles, mutateCycles }) => if (newPayload.sort_order && payload.sort_order) newPayload.sort_order = payload.sort_order.newSortOrder; - cycleService.patchCycle(workspaceSlug.toString(), cycle.project, cycle.id, newPayload, user); + cycleService.patchCycle(workspaceSlug.toString(), cycle.project, cycle.id, newPayload); }; const blockFormat = (blocks: ICycle[]) => diff --git a/web/components/cycles/sidebar.tsx b/web/components/cycles/sidebar.tsx index 3288dfe0b..17e3c0d19 100644 --- a/web/components/cycles/sidebar.tsx +++ b/web/components/cycles/sidebar.tsx @@ -52,9 +52,8 @@ export const CycleDetailsSidebar: React.FC = observer((props) => { const router = useRouter(); const { workspaceSlug, projectId, peekCycle } = router.query; - const { user: userStore, cycle: cycleDetailsStore } = useMobxStore(); + const { cycle: cycleDetailsStore } = useMobxStore(); - const user = userStore.currentUser ?? undefined; const cycleDetails = cycleDetailsStore.cycle_details[cycleId] ?? undefined; const { setToastAlert } = useToast(); @@ -74,7 +73,7 @@ export const CycleDetailsSidebar: React.FC = observer((props) => { mutate(CYCLE_DETAILS(cycleId as string), (prevData) => ({ ...(prevData as ICycle), ...data }), false); cycleService - .patchCycle(workspaceSlug as string, projectId as string, cycleId as string, data, user) + .patchCycle(workspaceSlug as string, projectId as string, cycleId as string, data) .then(() => mutate(CYCLE_DETAILS(cycleId as string))) .catch((e) => console.log(e)); }; diff --git a/web/components/exporter/export-modal.tsx b/web/components/exporter/export-modal.tsx index 4cf99e0ce..2fe509071 100644 --- a/web/components/exporter/export-modal.tsx +++ b/web/components/exporter/export-modal.tsx @@ -63,7 +63,7 @@ export const Exporter: React.FC = observer((props) => { multiple: multiple, }; await projectExportService - .csvExport(workspaceSlug as string, payload, user) + .csvExport(workspaceSlug as string, payload) .then(() => { mutateServices(); router.push(`/${workspaceSlug}/settings/exports`); diff --git a/web/components/inbox/issue-activity.tsx b/web/components/inbox/issue-activity.tsx index 61102f865..d8d68313b 100644 --- a/web/components/inbox/issue-activity.tsx +++ b/web/components/inbox/issue-activity.tsx @@ -41,7 +41,7 @@ export const InboxIssueActivity: React.FC = observer(({ issueDetails }) = if (!workspaceSlug || !projectId || !inboxIssueId || !user) return; await issueCommentService - .patchIssueComment(workspaceSlug as string, projectId as string, inboxIssueId as string, commentId, data, user) + .patchIssueComment(workspaceSlug as string, projectId as string, inboxIssueId as string, commentId, data) .then(() => mutateIssueActivity()); }; @@ -51,7 +51,7 @@ export const InboxIssueActivity: React.FC = observer(({ issueDetails }) = mutateIssueActivity((prevData: any) => prevData?.filter((p: any) => p.id !== commentId), false); await issueCommentService - .deleteIssueComment(workspaceSlug as string, projectId as string, inboxIssueId as string, commentId, user) + .deleteIssueComment(workspaceSlug as string, projectId as string, inboxIssueId as string, commentId) .then(() => mutateIssueActivity()); }; @@ -59,7 +59,7 @@ export const InboxIssueActivity: React.FC = observer(({ issueDetails }) = if (!workspaceSlug || !issueDetails || !user) return; await issueCommentService - .createIssueComment(workspaceSlug.toString(), issueDetails.project, issueDetails.id, formData, user) + .createIssueComment(workspaceSlug.toString(), issueDetails.project, issueDetails.id, formData) .then(() => { mutate(PROJECT_ISSUES_ACTIVITY(issueDetails.id)); }) diff --git a/web/components/integration/delete-import-modal.tsx b/web/components/integration/delete-import-modal.tsx index 8d222c4db..9873d729f 100644 --- a/web/components/integration/delete-import-modal.tsx +++ b/web/components/integration/delete-import-modal.tsx @@ -29,7 +29,7 @@ type Props = { // services const integrationService = new IntegrationService(); -export const DeleteImportModal: React.FC = ({ isOpen, handleClose, data, user }) => { +export const DeleteImportModal: React.FC = ({ isOpen, handleClose, data }) => { const [deleteLoading, setDeleteLoading] = useState(false); const [confirmDeleteImport, setConfirmDeleteImport] = useState(false); @@ -50,7 +50,7 @@ export const DeleteImportModal: React.FC = ({ isOpen, handleClose, data, ); integrationService - .deleteImporterService(workspaceSlug as string, data.service, data.id, user) + .deleteImporterService(workspaceSlug as string, data.service, data.id) .catch(() => setToastAlert({ type: "error", diff --git a/web/components/integration/github/root.tsx b/web/components/integration/github/root.tsx index aac2b10e9..705a41255 100644 --- a/web/components/integration/github/root.tsx +++ b/web/components/integration/github/root.tsx @@ -87,7 +87,7 @@ type Props = { const integrationService = new IntegrationService(); const githubIntegrationService = new GithubIntegrationService(); -export const GithubImporterRoot: React.FC = ({ user }) => { +export const GithubImporterRoot: React.FC = () => { const [currentStep, setCurrentStep] = useState({ state: "import-configure", }); @@ -147,7 +147,7 @@ export const GithubImporterRoot: React.FC = ({ user }) => { }; await githubIntegrationService - .createGithubServiceImport(workspaceSlug as string, payload, user) + .createGithubServiceImport(workspaceSlug as string, payload) .then(() => { router.push(`/${workspaceSlug}/settings/imports`); mutate(IMPORTER_SERVICES_LIST(workspaceSlug as string)); diff --git a/web/components/integration/jira/root.tsx b/web/components/integration/jira/root.tsx index 4cd3c2490..3651a97d9 100644 --- a/web/components/integration/jira/root.tsx +++ b/web/components/integration/jira/root.tsx @@ -60,7 +60,7 @@ type Props = { // services const jiraImporterService = new JiraImporterService(); -export const JiraImporterRoot: React.FC = ({ user }) => { +export const JiraImporterRoot: React.FC = () => { const [currentStep, setCurrentStep] = useState({ state: "import-configure", }); @@ -81,7 +81,7 @@ export const JiraImporterRoot: React.FC = ({ user }) => { if (!workspaceSlug) return; await jiraImporterService - .createJiraImporter(workspaceSlug.toString(), data, user) + .createJiraImporter(workspaceSlug.toString(), data) .then(() => { mutate(IMPORTER_SERVICES_LIST(workspaceSlug.toString())); router.push(`/${workspaceSlug}/settings/imports`); diff --git a/web/components/issues/draft-issue-form.tsx b/web/components/issues/draft-issue-form.tsx index e42d8aab3..e80c4609a 100644 --- a/web/components/issues/draft-issue-form.tsx +++ b/web/components/issues/draft-issue-form.tsx @@ -231,15 +231,10 @@ export const DraftIssueForm: FC = (props) => { setIAmFeelingLucky(true); aiService - .createGptTask( - workspaceSlug as string, - projectId as string, - { - prompt: issueName, - task: "Generate a proper description for this issue.", - }, - user - ) + .createGptTask(workspaceSlug as string, projectId as string, { + prompt: issueName, + task: "Generate a proper description for this issue.", + }) .then((res) => { if (res.response === "") setToastAlert({ diff --git a/web/components/issues/draft-issue-modal.tsx b/web/components/issues/draft-issue-modal.tsx index 189706cef..5d8711426 100644 --- a/web/components/issues/draft-issue-modal.tsx +++ b/web/components/issues/draft-issue-modal.tsx @@ -226,36 +226,24 @@ export const CreateUpdateDraftIssueModal: React.FC = observer( const addIssueToCycle = async (issueId: string, cycleId: string) => { if (!workspaceSlug || !activeProject || !user) return; - await issueService.addIssueToCycle( - workspaceSlug as string, - activeProject ?? "", - cycleId, - { - issues: [issueId], - }, - user - ); + await issueService.addIssueToCycle(workspaceSlug as string, activeProject ?? "", cycleId, { + issues: [issueId], + }); }; const addIssueToModule = async (issueId: string, moduleId: string) => { if (!workspaceSlug || !activeProject || !user) return; - await moduleService.addIssuesToModule( - workspaceSlug as string, - activeProject ?? "", - moduleId as string, - { - issues: [issueId], - }, - user - ); + await moduleService.addIssuesToModule(workspaceSlug as string, activeProject ?? "", moduleId as string, { + issues: [issueId], + }); }; const createIssue = async (payload: Partial) => { if (!workspaceSlug || !activeProject || !user) return; await issueService - .createIssue(workspaceSlug.toString(), activeProject, payload, user) + .createIssue(workspaceSlug.toString(), activeProject, payload) .then(async (res) => { if (payload.cycle && payload.cycle !== "") await addIssueToCycle(res.id, payload.cycle); if (payload.module && payload.module !== "") await addIssueToModule(res.id, payload.module); diff --git a/web/components/issues/form.tsx b/web/components/issues/form.tsx index a335ef687..f33d8e6be 100644 --- a/web/components/issues/form.tsx +++ b/web/components/issues/form.tsx @@ -188,15 +188,10 @@ export const IssueForm: FC = observer((props) => { setIAmFeelingLucky(true); aiService - .createGptTask( - workspaceSlug as string, - projectId as string, - { - prompt: issueName, - task: "Generate a proper description for this issue.", - }, - user - ) + .createGptTask(workspaceSlug as string, projectId as string, { + prompt: issueName, + task: "Generate a proper description for this issue.", + }) .then((res) => { if (res.response === "") setToastAlert({ diff --git a/web/components/issues/issue-layouts/kanban/headers/group-by-card.tsx b/web/components/issues/issue-layouts/kanban/headers/group-by-card.tsx index bb5060326..a336106d3 100644 --- a/web/components/issues/issue-layouts/kanban/headers/group-by-card.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/group-by-card.tsx @@ -1,6 +1,5 @@ import React, { FC } from "react"; import { useRouter } from "next/router"; - // services import { ModuleService } from "services/module.service"; import { IssueService } from "services/issue"; @@ -11,7 +10,6 @@ import { ExistingIssuesListModal } from "components/core"; // lucide icons import { Minimize2, Maximize2, Circle, Plus } from "lucide-react"; // hooks -import useUser from "hooks/use-user"; import useToast from "hooks/use-toast"; // mobx import { observer } from "mobx-react-lite"; @@ -43,8 +41,6 @@ export const HeaderGroupByCard: FC = observer((props) => { const router = useRouter(); const { workspaceSlug, projectId, moduleId, cycleId } = router.query; - const { user } = useUser(); - const { setToastAlert } = useToast(); const renderExistingIssueModal = moduleId || cycleId; @@ -58,7 +54,7 @@ export const HeaderGroupByCard: FC = observer((props) => { }; await moduleService - .addIssuesToModule(workspaceSlug as string, projectId as string, moduleId as string, payload, user) + .addIssuesToModule(workspaceSlug as string, projectId as string, moduleId as string, payload) .catch(() => setToastAlert({ type: "error", @@ -76,7 +72,7 @@ export const HeaderGroupByCard: FC = observer((props) => { }; await issueService - .addIssueToCycle(workspaceSlug as string, projectId as string, cycleId as string, payload, user) + .addIssueToCycle(workspaceSlug as string, projectId as string, cycleId as string, payload) .catch(() => { setToastAlert({ type: "error", diff --git a/web/components/issues/issue-layouts/list/headers/group-by-card.tsx b/web/components/issues/issue-layouts/list/headers/group-by-card.tsx index 4a43fdf77..764b7bcf7 100644 --- a/web/components/issues/issue-layouts/list/headers/group-by-card.tsx +++ b/web/components/issues/issue-layouts/list/headers/group-by-card.tsx @@ -1,6 +1,5 @@ import React from "react"; import { useRouter } from "next/router"; - // services import { ModuleService } from "services/module.service"; import { IssueService } from "services/issue"; @@ -11,7 +10,6 @@ import { CreateUpdateIssueModal } from "components/issues/modal"; import { ExistingIssuesListModal } from "components/core"; import { CustomMenu } from "@plane/ui"; // hooks -import useUser from "hooks/use-user"; import useToast from "hooks/use-toast"; // mobx import { observer } from "mobx-react-lite"; @@ -34,8 +32,6 @@ export const HeaderGroupByCard = observer(({ icon, title, count, issuePayload }: const [isOpen, setIsOpen] = React.useState(false); const [openExistingIssueListModal, setOpenExistingIssueListModal] = React.useState(false); - const { user } = useUser(); - const { setToastAlert } = useToast(); const verticalAlignPosition = false; @@ -51,7 +47,7 @@ export const HeaderGroupByCard = observer(({ icon, title, count, issuePayload }: }; await moduleService - .addIssuesToModule(workspaceSlug as string, projectId as string, moduleId as string, payload, user) + .addIssuesToModule(workspaceSlug as string, projectId as string, moduleId as string, payload) .catch(() => setToastAlert({ type: "error", @@ -69,7 +65,7 @@ export const HeaderGroupByCard = observer(({ icon, title, count, issuePayload }: }; await issueService - .addIssueToCycle(workspaceSlug as string, projectId as string, cycleId as string, payload, user) + .addIssueToCycle(workspaceSlug as string, projectId as string, cycleId as string, payload) .catch(() => { setToastAlert({ type: "error", diff --git a/web/components/issues/main-content.tsx b/web/components/issues/main-content.tsx index ce1c820f5..7f1e68930 100644 --- a/web/components/issues/main-content.tsx +++ b/web/components/issues/main-content.tsx @@ -69,7 +69,7 @@ export const IssueMainContent: React.FC = observer((props) => { if (!workspaceSlug || !projectId || !issueId) return; await issueCommentService - .patchIssueComment(workspaceSlug as string, projectId as string, issueId as string, commentId, data, user) + .patchIssueComment(workspaceSlug as string, projectId as string, issueId as string, commentId, data) .then(() => mutateIssueActivity()); }; @@ -79,7 +79,7 @@ export const IssueMainContent: React.FC = observer((props) => { mutateIssueActivity((prevData: any) => prevData?.filter((p: any) => p.id !== commentId), false); await issueCommentService - .deleteIssueComment(workspaceSlug as string, projectId as string, issueId as string, commentId, user) + .deleteIssueComment(workspaceSlug as string, projectId as string, issueId as string, commentId) .then(() => mutateIssueActivity()); }; @@ -87,7 +87,7 @@ export const IssueMainContent: React.FC = observer((props) => { if (!workspaceSlug || !issueDetails || !user) return; await issueCommentService - .createIssueComment(workspaceSlug.toString(), issueDetails.project, issueDetails.id, formData, user) + .createIssueComment(workspaceSlug.toString(), issueDetails.project, issueDetails.id, formData) .then(() => { mutate(PROJECT_ISSUES_ACTIVITY(issueDetails.id)); }) diff --git a/web/components/issues/peek-overview/issue-activity.tsx b/web/components/issues/peek-overview/issue-activity.tsx index dc1d044af..e6d32d3cc 100644 --- a/web/components/issues/peek-overview/issue-activity.tsx +++ b/web/components/issues/peek-overview/issue-activity.tsx @@ -2,7 +2,6 @@ import useSWR, { mutate } from "swr"; // services import { IssueCommentService, IssueService } from "services/issue"; // hooks -import useUser from "hooks/use-user"; import useToast from "hooks/use-toast"; import useProjectDetails from "hooks/use-project-details"; // components @@ -26,7 +25,6 @@ export const PeekOverviewIssueActivity: React.FC = (props) => { // toast const { setToastAlert } = useToast(); - const { user } = useUser(); const { projectDetails } = useProjectDetails(); const { data: issueActivity, mutate: mutateIssueActivity } = useSWR( @@ -40,7 +38,7 @@ export const PeekOverviewIssueActivity: React.FC = (props) => { if (!workspaceSlug || !issue) return; await issueCommentService - .patchIssueComment(workspaceSlug as string, issue.project, issue.id, commentId, data, user) + .patchIssueComment(workspaceSlug as string, issue.project, issue.id, commentId, data) .then(() => mutateIssueActivity()); }; @@ -50,7 +48,7 @@ export const PeekOverviewIssueActivity: React.FC = (props) => { mutateIssueActivity((prevData: any) => prevData?.filter((p: any) => p.id !== commentId), false); await issueCommentService - .deleteIssueComment(workspaceSlug as string, issue.project, issue.id, commentId, user) + .deleteIssueComment(workspaceSlug as string, issue.project, issue.id, commentId) .then(() => mutateIssueActivity()); }; @@ -58,7 +56,7 @@ export const PeekOverviewIssueActivity: React.FC = (props) => { if (!workspaceSlug || !issue) return; await issueCommentService - .createIssueComment(workspaceSlug.toString(), issue.project, issue.id, formData, user) + .createIssueComment(workspaceSlug.toString(), issue.project, issue.id, formData) .then(() => { mutate(PROJECT_ISSUES_ACTIVITY(issue.id)); }) diff --git a/web/components/issues/sidebar-select/blocked.tsx b/web/components/issues/sidebar-select/blocked.tsx index 73bf747af..61f5eca62 100644 --- a/web/components/issues/sidebar-select/blocked.tsx +++ b/web/components/issues/sidebar-select/blocked.tsx @@ -68,7 +68,7 @@ export const SidebarBlockedSelect: React.FC = ({ issueId, submitChanges, if (!user) return; issueService - .createIssueRelation(workspaceSlug as string, projectId as string, issueId as string, user, { + .createIssueRelation(workspaceSlug as string, projectId as string, issueId as string, { related_list: [ ...selectedIssues.map((issue) => ({ issue: issueId as string, @@ -134,8 +134,7 @@ export const SidebarBlockedSelect: React.FC = ({ issueId, submitChanges, workspaceSlug as string, projectId as string, issueId as string, - relation.id, - user + relation.id ); }} > diff --git a/web/components/issues/sidebar-select/blocker.tsx b/web/components/issues/sidebar-select/blocker.tsx index 139212313..161fcc1d4 100644 --- a/web/components/issues/sidebar-select/blocker.tsx +++ b/web/components/issues/sidebar-select/blocker.tsx @@ -69,7 +69,7 @@ export const SidebarBlockerSelect: React.FC = ({ issueId, submitChanges, if (!user) return; issueService - .createIssueRelation(workspaceSlug as string, projectId as string, issueId as string, user, { + .createIssueRelation(workspaceSlug as string, projectId as string, issueId as string, { related_list: [ ...selectedIssues.map((issue) => ({ issue: issue.blocker_issue_detail.id, @@ -144,8 +144,7 @@ export const SidebarBlockerSelect: React.FC = ({ issueId, submitChanges, workspaceSlug as string, projectId as string, relation.issue_detail?.id as string, - relation.id, - user + relation.id ); }} > diff --git a/web/components/issues/sidebar-select/duplicate.tsx b/web/components/issues/sidebar-select/duplicate.tsx index a28d472ba..a56ea420d 100644 --- a/web/components/issues/sidebar-select/duplicate.tsx +++ b/web/components/issues/sidebar-select/duplicate.tsx @@ -67,7 +67,7 @@ export const SidebarDuplicateSelect: React.FC = (props) => { if (!user) return; issueService - .createIssueRelation(workspaceSlug as string, projectId as string, issueId as string, user, { + .createIssueRelation(workspaceSlug as string, projectId as string, issueId as string, { related_list: [ ...selectedIssues.map((issue) => ({ issue: issueId as string, @@ -137,8 +137,7 @@ export const SidebarDuplicateSelect: React.FC = (props) => { workspaceSlug as string, projectId as string, issueId as string, - relation.id, - user + relation.id ) .then(() => { submitChanges(); diff --git a/web/components/issues/sidebar-select/label.tsx b/web/components/issues/sidebar-select/label.tsx index 52a50562c..37100448c 100644 --- a/web/components/issues/sidebar-select/label.tsx +++ b/web/components/issues/sidebar-select/label.tsx @@ -7,8 +7,6 @@ import { TwitterPicker } from "react-color"; import { Popover, Transition } from "@headlessui/react"; // services import { IssueLabelService } from "services/issue"; -// hooks -import useUser from "hooks/use-user"; // ui import { Input } from "@plane/ui"; import { IssueLabelSelect } from "../select"; @@ -61,8 +59,6 @@ export const SidebarLabelSelect: React.FC = ({ defaultValues, }); - const { user } = useUser(); - const { data: issueLabels, mutate: issueLabelMutate } = useSWR( workspaceSlug && projectId ? PROJECT_ISSUE_LABELS(projectId as string) : null, workspaceSlug && projectId @@ -74,7 +70,7 @@ export const SidebarLabelSelect: React.FC = ({ if (!workspaceSlug || !projectId || isSubmitting) return; await issueLabelService - .createIssueLabel(workspaceSlug as string, projectId as string, formData, user) + .createIssueLabel(workspaceSlug as string, projectId as string, formData) .then((res) => { reset(defaultValues); diff --git a/web/components/issues/sidebar-select/relates-to.tsx b/web/components/issues/sidebar-select/relates-to.tsx index 2f543e381..bac0684a1 100644 --- a/web/components/issues/sidebar-select/relates-to.tsx +++ b/web/components/issues/sidebar-select/relates-to.tsx @@ -68,7 +68,7 @@ export const SidebarRelatesSelect: React.FC = (props) => { if (!user) return; issueService - .createIssueRelation(workspaceSlug as string, projectId as string, issueId as string, user, { + .createIssueRelation(workspaceSlug as string, projectId as string, issueId as string, { related_list: [ ...selectedIssues.map((issue) => ({ issue: issueId as string, @@ -138,8 +138,7 @@ export const SidebarRelatesSelect: React.FC = (props) => { workspaceSlug as string, projectId as string, issueId as string, - relation.id, - user + relation.id ) .then(() => { submitChanges(); diff --git a/web/components/issues/sidebar.tsx b/web/components/issues/sidebar.tsx index 3ef335309..e67b73baa 100644 --- a/web/components/issues/sidebar.tsx +++ b/web/components/issues/sidebar.tsx @@ -101,15 +101,9 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { if (!workspaceSlug || !projectId || !issueDetail || !user) return; issueService - .addIssueToCycle( - workspaceSlug as string, - projectId as string, - cycleId, - { - issues: [issueDetail.id], - }, - user - ) + .addIssueToCycle(workspaceSlug as string, projectId as string, cycleId, { + issues: [issueDetail.id], + }) .then(() => { mutate(ISSUE_DETAILS(issueId as string)); }); @@ -122,15 +116,9 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { if (!workspaceSlug || !projectId || !issueDetail || !user) return; moduleService - .addIssuesToModule( - workspaceSlug as string, - projectId as string, - moduleId, - { - issues: [issueDetail.id], - }, - user - ) + .addIssuesToModule(workspaceSlug as string, projectId as string, moduleId, { + issues: [issueDetail.id], + }) .then(() => { mutate(ISSUE_DETAILS(issueId as string)); }); diff --git a/web/components/issues/sub-issues/properties.tsx b/web/components/issues/sub-issues/properties.tsx index faf593efb..2bd76cd92 100644 --- a/web/components/issues/sub-issues/properties.tsx +++ b/web/components/issues/sub-issues/properties.tsx @@ -5,7 +5,6 @@ import { mutate } from "swr"; import { useMobxStore } from "lib/mobx/store-provider"; // services import { IssueService } from "services/issue"; -import { TrackEventService } from "services/track_event.service"; // components import { ViewDueDateSelect, ViewStartDateSelect } from "components/issues"; import { PrioritySelect } from "components/project"; @@ -25,7 +24,6 @@ export interface IIssueProperty { // services const issueService = new IssueService(); -const trackEventService = new TrackEventService(); export const IssueProperty: React.FC = observer((props) => { const { workspaceSlug, parentIssue, issue, user, editable } = props; @@ -36,18 +34,6 @@ export const IssueProperty: React.FC = observer((props) => { const handlePriorityChange = (data: any) => { partialUpdateIssue({ priority: data }); - trackEventService.trackIssuePartialPropertyUpdateEvent( - { - workspaceSlug, - workspaceId: issue.workspace, - projectId: issue.project_detail.id, - projectIdentifier: issue.project_detail.identifier, - projectName: issue.project_detail.name, - issueId: issue.id, - }, - "ISSUE_PROPERTY_UPDATE_PRIORITY", - user as IUser - ); }; const handleStateChange = (data: IState) => { @@ -55,35 +41,10 @@ export const IssueProperty: React.FC = observer((props) => { state: data.id, state_detail: data, }); - trackEventService.trackIssuePartialPropertyUpdateEvent( - { - workspaceSlug, - workspaceId: issue.workspace, - projectId: issue.project_detail.id, - projectIdentifier: issue.project_detail.identifier, - projectName: issue.project_detail.name, - issueId: issue.id, - }, - "ISSUE_PROPERTY_UPDATE_STATE", - user as IUser - ); }; const handleAssigneeChange = (data: string[]) => { partialUpdateIssue({ assignees: data }); - - trackEventService.trackIssuePartialPropertyUpdateEvent( - { - workspaceSlug, - workspaceId: issue.workspace, - projectId: issue.project_detail.id, - projectIdentifier: issue.project_detail.identifier, - projectName: issue.project_detail.name, - issueId: issue.id, - }, - "ISSUE_PROPERTY_UPDATE_ASSIGNEE", - user as IUser - ); }; const partialUpdateIssue = async (data: Partial) => { @@ -100,7 +61,7 @@ export const IssueProperty: React.FC = observer((props) => { false ); - const issueResponse = await issueService.patchIssue(workspaceSlug as string, issue.project, issue.id, data, user); + const issueResponse = await issueService.patchIssue(workspaceSlug as string, issue.project, issue.id, data); mutate( SUB_ISSUES(parentIssue.id), diff --git a/web/components/issues/sub-issues/root.tsx b/web/components/issues/sub-issues/root.tsx index 3e8a345f8..3cacdd2a9 100644 --- a/web/components/issues/sub-issues/root.tsx +++ b/web/components/issues/sub-issues/root.tsx @@ -126,7 +126,7 @@ export const SubIssuesRoot: React.FC = observer((props) => { const removeIssueFromSubIssues = async (parentIssueId: string, issue: IIssue) => { if (!workspaceSlug || !projectId || !parentIssue || !issue?.id) return; issueService - .patchIssue(workspaceSlug.toString(), projectId.toString(), issue.id, { parent: null }, user) + .patchIssue(workspaceSlug.toString(), projectId.toString(), issue.id, { parent: null }) .then(async () => { if (parentIssueId) await mutate(SUB_ISSUES(parentIssueId)); handleIssuesLoader({ key: "delete", issueId: issue?.id }); diff --git a/web/components/modules/sidebar.tsx b/web/components/modules/sidebar.tsx index f4c14871b..62613bd39 100644 --- a/web/components/modules/sidebar.tsx +++ b/web/components/modules/sidebar.tsx @@ -57,7 +57,6 @@ export const ModuleDetailsSidebar: React.FC = observer((props) => { const { module: moduleStore, user: userStore } = useMobxStore(); - const user = userStore.currentUser ?? undefined; const userRole = userStore.currentProjectRole; const moduleDetails = moduleStore.moduleDetails[moduleId] ?? undefined; @@ -80,7 +79,7 @@ export const ModuleDetailsSidebar: React.FC = observer((props) => { ); moduleService - .patchModule(workspaceSlug as string, projectId as string, moduleId as string, data, user) + .patchModule(workspaceSlug as string, projectId as string, moduleId as string, data) .then(() => mutate(MODULE_DETAILS(moduleId as string))) .catch((e) => console.log(e)); }; diff --git a/web/components/onboarding/invite-members.tsx b/web/components/onboarding/invite-members.tsx index 08617ca71..fa639af72 100644 --- a/web/components/onboarding/invite-members.tsx +++ b/web/components/onboarding/invite-members.tsx @@ -164,7 +164,7 @@ const InviteMemberForm: React.FC = (props) => { }; export const InviteMembers: React.FC = (props) => { - const { finishOnboarding, stepChange, user, workspace } = props; + const { finishOnboarding, stepChange, workspace } = props; const { setToastAlert } = useToast(); @@ -195,7 +195,7 @@ export const InviteMembers: React.FC = (props) => { const payload = { ...formData }; await workspaceService - .inviteWorkspace(workspace.slug, payload, user) + .inviteWorkspace(workspace.slug, payload) .then(async () => { setToastAlert({ type: "success", diff --git a/web/components/onboarding/join-workspaces.tsx b/web/components/onboarding/join-workspaces.tsx index d846614dd..51c965acd 100644 --- a/web/components/onboarding/join-workspaces.tsx +++ b/web/components/onboarding/join-workspaces.tsx @@ -18,6 +18,7 @@ import { IWorkspaceMemberInvitation, TOnboardingSteps } from "types"; import { USER_WORKSPACES, USER_WORKSPACE_INVITATIONS } from "constants/fetch-keys"; // constants import { ROLE } from "constants/workspace"; +import { trackEvent } from "helpers/event-tracker.helper"; type Props = { finishOnboarding: () => Promise; @@ -61,7 +62,11 @@ export const JoinWorkspaces: React.FC = ({ finishOnboarding, stepChange, await workspaceService .joinWorkspaces({ invitations: invitationsRespond }) - .then(async () => { + .then(async (res) => { + trackEvent( + 'WORKSPACE_USER_INVITE_ACCEPT', + res + ) await mutateInvitations(); await mutate(USER_WORKSPACES); await updateLastWorkspace(); @@ -83,9 +88,8 @@ export const JoinWorkspaces: React.FC = ({ finishOnboarding, stepChange, return (
handleInvitation(invitation, isSelected ? "withdraw" : "accepted")} >
diff --git a/web/components/pages/create-block.tsx b/web/components/pages/create-block.tsx index 1decabae0..0fe548d25 100644 --- a/web/components/pages/create-block.tsx +++ b/web/components/pages/create-block.tsx @@ -24,7 +24,7 @@ type Props = { const pageService = new PageService(); -export const CreateBlock: FC = ({ user }) => { +export const CreateBlock: FC = () => { // const [blockTitle, setBlockTitle] = useState(""); // router const router = useRouter(); @@ -46,15 +46,9 @@ export const CreateBlock: FC = ({ user }) => { if (!workspaceSlug || !projectId || !pageId) return; await pageService - .createPageBlock( - workspaceSlug as string, - projectId as string, - pageId as string, - { - name: watch("name"), - }, - user - ) + .createPageBlock(workspaceSlug as string, projectId as string, pageId as string, { + name: watch("name"), + }) .then((res) => { mutate( PAGE_BLOCKS_LIST(pageId as string), diff --git a/web/components/pages/create-update-block-inline.tsx b/web/components/pages/create-update-block-inline.tsx index c1b3c5155..d2533a967 100644 --- a/web/components/pages/create-update-block-inline.tsx +++ b/web/components/pages/create-update-block-inline.tsx @@ -40,14 +40,7 @@ const pagesService = new PageService(); const issueService = new IssueService(); const fileService = new FileService(); -export const CreateUpdateBlockInline: FC = ({ - handleClose, - data, - handleAiAssistance, - setIsSyncing, - focus, - user, -}) => { +export const CreateUpdateBlockInline: FC = ({ handleClose, data, handleAiAssistance, setIsSyncing, focus }) => { const [iAmFeelingLucky, setIAmFeelingLucky] = useState(false); const [gptAssistantModal, setGptAssistantModal] = useState(false); @@ -83,17 +76,11 @@ export const CreateUpdateBlockInline: FC = ({ if (!workspaceSlug || !projectId || !pageId) return; await pagesService - .createPageBlock( - workspaceSlug as string, - projectId as string, - pageId as string, - { - name: formData.name, - description: formData.description ?? "", - description_html: formData.description_html ?? "

", - }, - user - ) + .createPageBlock(workspaceSlug as string, projectId as string, pageId as string, { + name: formData.name, + description: formData.description ?? "", + description_html: formData.description_html ?? "

", + }) .then((res) => { mutate( PAGE_BLOCKS_LIST(pageId as string), @@ -111,7 +98,7 @@ export const CreateUpdateBlockInline: FC = ({ }) .finally(() => onClose()); }, - [workspaceSlug, projectId, pageId, onClose, setToastAlert, user] + [workspaceSlug, projectId, pageId, onClose, setToastAlert] ); const updatePageBlock = useCallback( @@ -132,41 +119,28 @@ export const CreateUpdateBlockInline: FC = ({ ); await pagesService - .patchPageBlock( - workspaceSlug as string, - projectId as string, - pageId as string, - data.id, - { - name: formData.name, - description: formData.description, - description_html: formData.description_html, - }, - user - ) + .patchPageBlock(workspaceSlug as string, projectId as string, pageId as string, data.id, { + name: formData.name, + description: formData.description, + description_html: formData.description_html, + }) .then((res) => { mutate(PAGE_BLOCKS_LIST(pageId as string)); editorRef.current?.setEditorValue(res.description_html); if (data.issue && data.sync) issueService - .patchIssue( - workspaceSlug as string, - projectId as string, - data.issue, - { - name: res.name, - description: res.description, - description_html: res.description_html, - }, - user - ) + .patchIssue(workspaceSlug as string, projectId as string, data.issue, { + name: res.name, + description: res.description, + description_html: res.description_html, + }) .finally(() => { if (setIsSyncing) setIsSyncing(false); }); }) .finally(() => onClose()); }, - [workspaceSlug, projectId, pageId, data, onClose, setIsSyncing, user] + [workspaceSlug, projectId, pageId, data, onClose, setIsSyncing] ); const handleAutoGenerateDescription = async () => { @@ -175,15 +149,10 @@ export const CreateUpdateBlockInline: FC = ({ setIAmFeelingLucky(true); aiService - .createGptTask( - workspaceSlug as string, - projectId as string, - { - prompt: watch("name"), - task: "Generate a proper description for this issue.", - }, - user - ) + .createGptTask(workspaceSlug as string, projectId as string, { + prompt: watch("name"), + task: "Generate a proper description for this issue.", + }) .then((res) => { if (res.response === "") setToastAlert({ diff --git a/web/components/pages/create-update-page-modal.tsx b/web/components/pages/create-update-page-modal.tsx index a5803fcad..7c5547ed5 100644 --- a/web/components/pages/create-update-page-modal.tsx +++ b/web/components/pages/create-update-page-modal.tsx @@ -1,10 +1,6 @@ import React from "react"; - import { useRouter } from "next/router"; - import { mutate } from "swr"; - -// headless ui import { Dialog, Transition } from "@headlessui/react"; // services import { PageService } from "services/page.service"; @@ -16,6 +12,7 @@ import { PageForm } from "./page-form"; import { IUser, IPage } from "types"; // fetch-keys import { ALL_PAGES_LIST, FAVORITE_PAGES_LIST, MY_PAGES_LIST, RECENT_PAGES_LIST } from "constants/fetch-keys"; +import { trackEvent } from "helpers/event-tracker.helper"; type Props = { isOpen: boolean; @@ -30,7 +27,7 @@ type Props = { const pageService = new PageService(); export const CreateUpdatePageModal: React.FC = (props) => { - const { isOpen, handleClose, data, user, workspaceSlug, projectId } = props; + const { isOpen, handleClose, data, workspaceSlug, projectId } = props; // router const router = useRouter(); @@ -42,7 +39,7 @@ export const CreateUpdatePageModal: React.FC = (props) => { const createPage = async (payload: IPage) => { await pageService - .createPage(workspaceSlug as string, projectId as string, payload, user) + .createPage(workspaceSlug as string, projectId as string, payload) .then((res) => { mutate(RECENT_PAGES_LIST(projectId as string)); mutate( @@ -64,14 +61,19 @@ export const CreateUpdatePageModal: React.FC = (props) => { false ); onClose(); - router.push(`/${workspaceSlug}/projects/${projectId}/pages/${res.id}`); - setToastAlert({ type: "success", title: "Success!", message: "Page created successfully.", }); + trackEvent( + 'PAGE_CREATE', + { + ...res, + caase: "SUCCES" + } + ) }) .catch(() => { setToastAlert({ @@ -79,12 +81,18 @@ export const CreateUpdatePageModal: React.FC = (props) => { title: "Error!", message: "Page could not be created. Please try again.", }); + trackEvent( + 'PAGE_CREATE', + { + case: "FAILED" + } + ) }); }; const updatePage = async (payload: IPage) => { await pageService - .patchPage(workspaceSlug as string, projectId as string, data?.id ?? "", payload, user) + .patchPage(workspaceSlug as string, projectId as string, data?.id ?? "", payload) .then((res) => { mutate(RECENT_PAGES_LIST(projectId as string)); mutate( @@ -124,6 +132,13 @@ export const CreateUpdatePageModal: React.FC = (props) => { title: "Success!", message: "Page updated successfully.", }); + trackEvent( + 'PAGE_UPDATE', + { + ...res, + case: "SUCCESS" + } + ) }) .catch(() => { setToastAlert({ @@ -131,6 +146,12 @@ export const CreateUpdatePageModal: React.FC = (props) => { title: "Error!", message: "Page could not be updated. Please try again.", }); + trackEvent( + 'PAGE_UPDATE', + { + case: "FAILED" + } + ) }); }; diff --git a/web/components/pages/delete-page-modal.tsx b/web/components/pages/delete-page-modal.tsx index a8b4eb21c..20073abb5 100644 --- a/web/components/pages/delete-page-modal.tsx +++ b/web/components/pages/delete-page-modal.tsx @@ -29,7 +29,7 @@ type TConfirmPageDeletionProps = { // services const pageService = new PageService(); -export const DeletePageModal: React.FC = ({ isOpen, setIsOpen, data, user }) => { +export const DeletePageModal: React.FC = ({ isOpen, setIsOpen, data }) => { const [isDeleteLoading, setIsDeleteLoading] = useState(false); const router = useRouter(); @@ -47,7 +47,7 @@ export const DeletePageModal: React.FC = ({ isOpen, s if (!data || !workspaceSlug || !projectId) return; await pageService - .deletePage(workspaceSlug as string, data.project, data.id, user) + .deletePage(workspaceSlug as string, data.project, data.id) .then(() => { mutate(RECENT_PAGES_LIST(projectId as string)); mutate( diff --git a/web/components/pages/pages-view.tsx b/web/components/pages/pages-view.tsx index 3154f7981..2c2b94713 100644 --- a/web/components/pages/pages-view.tsx +++ b/web/components/pages/pages-view.tsx @@ -182,7 +182,7 @@ export const PagesView: React.FC = observer(({ pages, viewType }) => { false ); - pageService.patchPage(workspaceSlug.toString(), projectId.toString(), page.id, formData, user).then(() => { + pageService.patchPage(workspaceSlug.toString(), projectId.toString(), page.id, formData).then(() => { mutate(RECENT_PAGES_LIST(projectId.toString())); }); }; diff --git a/web/components/pages/single-page-block.tsx b/web/components/pages/single-page-block.tsx index 6f6766f84..a03e0da65 100644 --- a/web/components/pages/single-page-block.tsx +++ b/web/components/pages/single-page-block.tsx @@ -85,33 +85,20 @@ export const SinglePageBlock: React.FC = ({ block, projectDetails, showBl ); await pageService - .patchPageBlock( - workspaceSlug as string, - projectId as string, - pageId as string, - block.id, - { - name: formData.name, - description: formData.description, - description_html: formData.description_html, - }, - user - ) + .patchPageBlock(workspaceSlug as string, projectId as string, pageId as string, block.id, { + name: formData.name, + description: formData.description, + description_html: formData.description_html, + }) .then((res) => { mutate(PAGE_BLOCKS_LIST(pageId as string)); if (block.issue && block.sync) issueService - .patchIssue( - workspaceSlug as string, - projectId as string, - block.issue, - { - name: res.name, - description: res.description, - description_html: res.description_html, - }, - user - ) + .patchIssue(workspaceSlug as string, projectId as string, block.issue, { + name: res.name, + description: res.description, + description_html: res.description_html, + }) .finally(() => setIsSyncing(false)); }); }; @@ -120,7 +107,7 @@ export const SinglePageBlock: React.FC = ({ block, projectDetails, showBl if (!workspaceSlug || !projectId || !pageId) return; await pageService - .convertPageBlockToIssue(workspaceSlug as string, projectId as string, pageId as string, block.id, user) + .convertPageBlockToIssue(workspaceSlug as string, projectId as string, pageId as string, block.id) .then((res: IIssue) => { mutate( PAGE_BLOCKS_LIST(pageId as string), @@ -158,7 +145,7 @@ export const SinglePageBlock: React.FC = ({ block, projectDetails, showBl ); await pageService - .deletePageBlock(workspaceSlug as string, projectId as string, pageId as string, block.id, user) + .deletePageBlock(workspaceSlug as string, projectId as string, pageId as string, block.id) .catch(() => { setToastAlert({ type: "error", @@ -174,15 +161,10 @@ export const SinglePageBlock: React.FC = ({ block, projectDetails, showBl setIAmFeelingLucky(true); aiService - .createGptTask( - workspaceSlug as string, - projectId as string, - { - prompt: block.name, - task: "Generate a proper description for this issue.", - }, - user - ) + .createGptTask(workspaceSlug as string, projectId as string, { + prompt: block.name, + task: "Generate a proper description for this issue.", + }) .then((res) => { if (res.response === "") setToastAlert({ @@ -246,16 +228,9 @@ export const SinglePageBlock: React.FC = ({ block, projectDetails, showBl false ); - pageService.patchPageBlock( - workspaceSlug as string, - projectId as string, - pageId as string, - block.id, - { - sync: !block.sync, - }, - user - ); + pageService.patchPageBlock(workspaceSlug as string, projectId as string, pageId as string, block.id, { + sync: !block.sync, + }); }; const handleCopyText = () => { diff --git a/web/components/project/create-project-modal.tsx b/web/components/project/create-project-modal.tsx index e1d199239..6659dca87 100644 --- a/web/components/project/create-project-modal.tsx +++ b/web/components/project/create-project-modal.tsx @@ -20,6 +20,8 @@ import { getRandomEmoji, renderEmoji } from "helpers/emoji.helper"; import { IWorkspaceMember } from "types"; // constants import { NETWORK_CHOICES, PROJECT_UNSPLASH_COVERS } from "constants/project"; +// track events +import { trackEvent } from "helpers/event-tracker.helper"; type Props = { isOpen: boolean; @@ -129,6 +131,14 @@ export const CreateProjectModal: FC = observer((props) => { return projectStore .createProject(workspaceSlug.toString(), payload) .then((res) => { + const newPayload = { + ...payload, + id: res.id + } + trackEvent( + "CREATE_PROJECT", + newPayload, + ) setToastAlert({ type: "success", title: "Success!", @@ -211,7 +221,7 @@ export const CreateProjectModal: FC = observer((props) => { )}
-
diff --git a/web/components/project/delete-project-modal.tsx b/web/components/project/delete-project-modal.tsx index 90217e69e..9c41106fc 100644 --- a/web/components/project/delete-project-modal.tsx +++ b/web/components/project/delete-project-modal.tsx @@ -12,6 +12,7 @@ import { Button, Input } from "@plane/ui"; import type { IProject } from "types"; // fetch-keys import { useMobxStore } from "lib/mobx/store-provider"; +import { trackEvent } from "helpers/event-tracker.helper"; type DeleteProjectModal = { isOpen: boolean; @@ -62,6 +63,9 @@ export const DeleteProjectModal: React.FC = (props) => { if (projectId && projectId.toString() === project.id) router.push(`/${workspaceSlug}/projects`); handleClose(); + trackEvent( + 'DELETE_PROJECT' + ) setToastAlert({ type: "success", title: "Success!", @@ -69,6 +73,9 @@ export const DeleteProjectModal: React.FC = (props) => { }); }) .catch(() => { + trackEvent( + 'DELETE_PROJECT/FAIL' + ) setToastAlert({ type: "error", title: "Error!", diff --git a/web/components/project/form.tsx b/web/components/project/form.tsx index 297ab7110..63d8a2a92 100644 --- a/web/components/project/form.tsx +++ b/web/components/project/form.tsx @@ -16,6 +16,7 @@ import { ProjectService } from "services/project"; // hooks import useToast from "hooks/use-toast"; import { useMobxStore } from "lib/mobx/store-provider"; +import { trackEvent } from "helpers/event-tracker.helper"; export interface IProjectDetailsForm { project: IProject; @@ -61,7 +62,11 @@ export const ProjectDetailsForm: FC = (props) => { return projectStore .updateProject(workspaceSlug.toString(), project.id, payload) - .then(() => { + .then((res) => { + trackEvent( + 'UPDATE_PROJECT', + res + ); setToastAlert({ type: "success", title: "Success!", @@ -69,6 +74,9 @@ export const ProjectDetailsForm: FC = (props) => { }); }) .catch((error) => { + trackEvent( + 'UPDATE_PROJECT/FAIL', + ); setToastAlert({ type: "error", title: "Error!", diff --git a/web/components/project/send-project-invitation-modal.tsx b/web/components/project/send-project-invitation-modal.tsx index 8fc895fe5..6509a4e4f 100644 --- a/web/components/project/send-project-invitation-modal.tsx +++ b/web/components/project/send-project-invitation-modal.tsx @@ -20,6 +20,7 @@ import { IUser, TUserProjectRole } from "types"; import { WORKSPACE_MEMBERS } from "constants/fetch-keys"; // constants import { ROLE } from "constants/workspace"; +import { trackEvent } from "helpers/event-tracker.helper"; type Props = { isOpen: boolean; @@ -52,7 +53,7 @@ const projectMemberService = new ProjectMemberService(); const workspaceService = new WorkspaceService(); export const SendProjectInvitationModal: React.FC = observer((props) => { - const { isOpen, setIsOpen, members, user, onSuccess } = props; + const { isOpen, setIsOpen, members, onSuccess } = props; const router = useRouter(); const { workspaceSlug, projectId } = router.query; @@ -91,9 +92,12 @@ export const SendProjectInvitationModal: React.FC = observer((props) => { const payload = { ...formData }; await projectMemberService - .bulkAddMembersToProject(workspaceSlug.toString(), projectId.toString(), payload, user) - .then(() => { + .bulkAddMembersToProject(workspaceSlug.toString(), projectId.toString(), payload) + .then((res) => { setIsOpen(false); + trackEvent( + 'PROJECT_MEMBER_INVITE', + ) setToastAlert({ title: "Success", type: "success", diff --git a/web/components/project/settings/features-list.tsx b/web/components/project/settings/features-list.tsx index fc32c6a92..105c1b2c7 100644 --- a/web/components/project/settings/features-list.tsx +++ b/web/components/project/settings/features-list.tsx @@ -5,8 +5,6 @@ import { ContrastIcon, FileText, Inbox, Layers } from "lucide-react"; import { DiceIcon, ToggleSwitch } from "@plane/ui"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; -// services -import { MiscellaneousEventType, TrackEventService } from "services/track_event.service"; // hooks import useToast from "hooks/use-toast"; // types @@ -47,26 +45,6 @@ const PROJECT_FEATURES_LIST = [ }, ]; -const getEventType = (feature: string, toggle: boolean): MiscellaneousEventType => { - switch (feature) { - case "Cycles": - return toggle ? "TOGGLE_CYCLE_ON" : "TOGGLE_CYCLE_OFF"; - case "Modules": - return toggle ? "TOGGLE_MODULE_ON" : "TOGGLE_MODULE_OFF"; - case "Views": - return toggle ? "TOGGLE_VIEW_ON" : "TOGGLE_VIEW_OFF"; - case "Pages": - return toggle ? "TOGGLE_PAGES_ON" : "TOGGLE_PAGES_OFF"; - case "Inbox": - return toggle ? "TOGGLE_INBOX_ON" : "TOGGLE_INBOX_OFF"; - default: - throw new Error("Invalid feature"); - } -}; - -// services -const trackEventService = new TrackEventService(); - export const ProjectFeaturesList: FC = observer(() => { // router const router = useRouter(); @@ -109,17 +87,6 @@ export const ProjectFeaturesList: FC = observer(() => { { - trackEventService.trackMiscellaneousEvent( - { - workspaceId: (currentProjectDetails?.workspace as any)?.id, - workspaceSlug, - projectId, - projectIdentifier: currentProjectDetails?.identifier, - projectName: currentProjectDetails?.name, - }, - getEventType(feature.title, !currentProjectDetails?.[feature.property as keyof IProject]), - currentUser - ); handleSubmit({ [feature.property]: !currentProjectDetails?.[feature.property as keyof IProject], }); diff --git a/web/components/states/create-update-state-inline.tsx b/web/components/states/create-update-state-inline.tsx index 15a268272..fa0fdd6b9 100644 --- a/web/components/states/create-update-state-inline.tsx +++ b/web/components/states/create-update-state-inline.tsx @@ -18,6 +18,7 @@ import type { IState } from "types"; import { STATES_LIST } from "constants/fetch-keys"; // constants import { GROUP_CHOICES } from "constants/project"; +import { trackEvent } from "helpers/event-tracker.helper"; type Props = { data: IState | null; @@ -87,8 +88,12 @@ export const CreateUpdateStateInline: React.FC = observer((props) => { await projectStateStore .createState(workspaceSlug.toString(), projectId.toString(), formData) - .then(() => { + .then((res) => { handleClose(); + trackEvent( + 'STATE_CREATE', + res + ) setToastAlert({ type: "success", title: "Success!", @@ -116,10 +121,13 @@ export const CreateUpdateStateInline: React.FC = observer((props) => { await projectStateStore .updateState(workspaceSlug.toString(), projectId.toString(), data.id, formData) - .then(() => { + .then((res) => { mutate(STATES_LIST(projectId.toString())); handleClose(); - + trackEvent( + 'STATE_UPDATE', + res + ) setToastAlert({ type: "success", title: "Success!", @@ -161,9 +169,8 @@ export const CreateUpdateStateInline: React.FC = observer((props) => { {({ open }) => ( <> {watch("color") && watch("color") !== "" && ( = observer((props) => { await projectStateStore .deleteState(workspaceSlug.toString(), data.project, data.id) - .then(() => { + .then((res) => { + trackEvent( + 'STATE_DELETE', + ) handleClose(); }) .catch((err) => { diff --git a/web/components/workspace/create-workspace-form.tsx b/web/components/workspace/create-workspace-form.tsx index a0792fe70..02238887e 100644 --- a/web/components/workspace/create-workspace-form.tsx +++ b/web/components/workspace/create-workspace-form.tsx @@ -14,6 +14,8 @@ import { Button, CustomSelect, Input } from "@plane/ui"; import { IWorkspace } from "types"; // constants import { ORGANIZATION_SIZE, RESTRICTED_URLS } from "constants/workspace"; +// events +import { trackEvent } from "helpers/event-tracker.helper"; type Props = { onSubmit?: (res: IWorkspace) => Promise; @@ -71,6 +73,16 @@ export const CreateWorkspaceForm: FC = observer((props) => { await workspaceStore .createWorkspace(formData) .then(async (res) => { + const payload = { + name: formData.name, + slug: formData.slug, + workspace_url: formData.url, + organization_size: formData.organization_size + }; + trackEvent( + "CREATE_WORKSPACE", + payload + ) setToastAlert({ type: "success", title: "Success!", diff --git a/web/components/workspace/delete-workspace-modal.tsx b/web/components/workspace/delete-workspace-modal.tsx index 3fb4a8a51..93b3b7351 100644 --- a/web/components/workspace/delete-workspace-modal.tsx +++ b/web/components/workspace/delete-workspace-modal.tsx @@ -12,6 +12,7 @@ import useToast from "hooks/use-toast"; import { Button, Input } from "@plane/ui"; // types import type { IWorkspace } from "types"; +import { trackEvent } from "helpers/event-tracker.helper"; type Props = { isOpen: boolean; @@ -57,11 +58,17 @@ export const DeleteWorkspaceModal: React.FC = observer((props) => { await workspaceStore .deleteWorkspace(data.slug) - .then(() => { + .then((res) => { handleClose(); - + console.log('DELETE WORKPSACE', res); router.push("/"); - + const payload = { + slug: data.slug + }; + trackEvent( + 'DELETE_WORKSPACE', + payload + ); setToastAlert({ type: "success", title: "Success!", diff --git a/web/components/workspace/settings/workspace-details.tsx b/web/components/workspace/settings/workspace-details.tsx index 69034b201..e4a2bbac6 100644 --- a/web/components/workspace/settings/workspace-details.tsx +++ b/web/components/workspace/settings/workspace-details.tsx @@ -18,6 +18,7 @@ import { Button, CustomSelect, Input, Spinner } from "@plane/ui"; import { IWorkspace } from "types"; // constants import { ORGANIZATION_SIZE } from "constants/workspace"; +import { trackEvent } from "helpers/event-tracker.helper"; const defaultValues: Partial = { name: "", @@ -66,12 +67,17 @@ export const WorkspaceDetails: FC = observer(() => { }; await updateWorkspace(currentWorkspace.slug, payload) - .then(() => + .then((res) => { + trackEvent( + 'UPDATE_WORKSPACE', + res + ) setToastAlert({ title: "Success", type: "success", message: "Workspace updated successfully", }) + } ) .catch((err) => console.error(err)); }; @@ -83,7 +89,7 @@ export const WorkspaceDetails: FC = observer(() => { fileService.deleteFile(currentWorkspace.id, url).then(() => { updateWorkspace(currentWorkspace.slug, { logo: "" }) - .then(() => { + .then((res) => { setToastAlert({ type: "success", title: "Success!", diff --git a/web/constants/crisp.tsx b/web/constants/crisp.tsx deleted file mode 100644 index 90dfda7c7..000000000 --- a/web/constants/crisp.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { useCallback, useEffect } from "react"; -import { observer } from "mobx-react-lite"; -// hooks -import { useMobxStore } from "lib/mobx/store-provider"; - -declare global { - interface Window { - $crisp: any; - CRISP_WEBSITE_ID: any; - } -} - -const Crisp = observer(() => { - const { - user: { currentUser }, - } = useMobxStore(); - - const validateCurrentUser = useCallback(() => { - if (currentUser) return currentUser.email; - return null; - }, [currentUser]); - - useEffect(() => { - if (typeof window && validateCurrentUser()) { - window.$crisp = []; - window.CRISP_WEBSITE_ID = process.env.NEXT_PUBLIC_CRISP_ID; - (function () { - var d = document; - var s = d.createElement("script"); - s.src = "https://client.crisp.chat/l.js"; - s.async = true; - d.getElementsByTagName("head")[0].appendChild(s); - // defining email when logged in - if (validateCurrentUser()) { - window.$crisp.push(["set", "user:email", [validateCurrentUser()]]); - window.$crisp.push(["do", "chat:hide"]); - window.$crisp.push(["do", "chat:close"]); - } - })(); - } - }, [validateCurrentUser]); - - return <>; -}); -export default Crisp; diff --git a/web/contexts/issue-view.context.tsx b/web/contexts/issue-view.context.tsx index c64dcf194..867f87b57 100644 --- a/web/contexts/issue-view.context.tsx +++ b/web/contexts/issue-view.context.tsx @@ -8,10 +8,8 @@ import { ProjectService, ProjectMemberService } from "services/project"; import { CycleService } from "services/cycle.service"; import { ModuleService } from "services/module.service"; import { ViewService } from "services/view.service"; -// hooks -import useUserAuth from "hooks/use-user-auth"; // types -import { IIssueFilterOptions, IProjectMember, IUser, IIssueDisplayFilterOptions, IProjectViewProps } from "types"; +import { IIssueFilterOptions, IProjectMember, IIssueDisplayFilterOptions, IProjectViewProps } from "types"; // fetch-keys import { CYCLE_DETAILS, MODULE_DETAILS, USER_PROJECT_VIEW, VIEW_DETAILS } from "constants/fetch-keys"; @@ -134,58 +132,22 @@ const saveDataToServer = async (workspaceSlug: string, projectId: string, state: }); }; -const saveCycleFilters = async ( - workspaceSlug: string, - projectId: string, - cycleId: string, - state: any, - user: IUser | undefined -) => { - await cycleService.patchCycle( - workspaceSlug, - projectId, - cycleId, - { - ...state, - }, - user - ); +const saveCycleFilters = async (workspaceSlug: string, projectId: string, cycleId: string, state: any) => { + await cycleService.patchCycle(workspaceSlug, projectId, cycleId, { + ...state, + }); }; -const saveModuleFilters = async ( - workspaceSlug: string, - projectId: string, - moduleId: string, - state: any, - user: IUser | undefined -) => { - await moduleService.patchModule( - workspaceSlug, - projectId, - moduleId, - { - ...state, - }, - user - ); +const saveModuleFilters = async (workspaceSlug: string, projectId: string, moduleId: string, state: any) => { + await moduleService.patchModule(workspaceSlug, projectId, moduleId, { + ...state, + }); }; -const saveViewFilters = async ( - workspaceSlug: string, - projectId: string, - viewId: string, - state: any, - user: IUser | undefined -) => { - await viewService.patchView( - workspaceSlug, - projectId, - viewId, - { - ...state, - }, - user - ); +const saveViewFilters = async (workspaceSlug: string, projectId: string, viewId: string, state: any) => { + await viewService.patchView(workspaceSlug, projectId, viewId, { + ...state, + }); }; const setNewDefault = async (workspaceSlug: string, projectId: string, state: any) => { @@ -214,8 +176,6 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> = const router = useRouter(); const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query; - const { user } = useUserAuth(); - const { data: myViewProps, mutate: mutateMyViewProps } = useSWR( workspaceSlug && projectId ? USER_PROJECT_VIEW(projectId as string) : null, workspaceSlug && projectId @@ -342,20 +302,14 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> = }; }, false); - saveCycleFilters( - workspaceSlug.toString(), - projectId.toString(), - cycleId.toString(), - { - view_props: { - filters: { - ...state.filters, - ...property, - }, + saveCycleFilters(workspaceSlug.toString(), projectId.toString(), cycleId.toString(), { + view_props: { + filters: { + ...state.filters, + ...property, }, }, - user - ); + }); } else if (moduleId) { mutateModuleDetails((prevData: any) => { if (!prevData) return prevData; @@ -371,20 +325,14 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> = }; }, false); - saveModuleFilters( - workspaceSlug.toString(), - projectId.toString(), - moduleId.toString(), - { - view_props: { - filters: { - ...state.filters, - ...property, - }, + saveModuleFilters(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), { + view_props: { + filters: { + ...state.filters, + ...property, }, }, - user - ); + }); } else if (viewId) { mutateViewDetails((prevData: any) => { if (!prevData) return prevData; @@ -397,18 +345,12 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> = }; }, false); if (saveToServer) - saveViewFilters( - workspaceSlug as string, - projectId as string, - viewId as string, - { - query_data: { - ...state.filters, - ...property, - }, + saveViewFilters(workspaceSlug as string, projectId as string, viewId as string, { + query_data: { + ...state.filters, + ...property, }, - user - ); + }); } else { mutateMyViewProps((prevData: any) => { if (!prevData) return prevData; @@ -445,7 +387,6 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> = mutateModuleDetails, viewId, mutateViewDetails, - user, ] ); diff --git a/web/helpers/event-tracker.helper.ts b/web/helpers/event-tracker.helper.ts new file mode 100644 index 000000000..7b3a613e8 --- /dev/null +++ b/web/helpers/event-tracker.helper.ts @@ -0,0 +1,13 @@ +import posthog from "posthog-js"; + +export const trackEvent = (eventName: string, payload: object | [] | null = null) => { + try { + console.log(eventName); + posthog?.capture(eventName, { + ...payload, + }); + console.log(payload); + } catch (error) { + console.log(error); + } +}; diff --git a/web/helpers/user.helper.ts b/web/helpers/user.helper.ts new file mode 100644 index 000000000..569da6018 --- /dev/null +++ b/web/helpers/user.helper.ts @@ -0,0 +1,12 @@ +export const getUserRole = (role: number) => { + switch (role) { + case 5: + return "GUEST"; + case 10: + return "VIEWER"; + case 15: + return "MEMBER"; + case 20: + return "ADMIN"; + } +}; diff --git a/web/hooks/use-comment-reaction.tsx b/web/hooks/use-comment-reaction.tsx index af1d0a90f..db21db17a 100644 --- a/web/hooks/use-comment-reaction.tsx +++ b/web/hooks/use-comment-reaction.tsx @@ -1,5 +1,4 @@ import useSWR from "swr"; - // fetch keys import { COMMENT_REACTION_LIST } from "constants/fetch-keys"; // services @@ -52,8 +51,7 @@ const useCommentReaction = ( workspaceSlug.toString(), projectId.toString(), commendId.toString(), - { reaction }, - user.user + { reaction } ); mutateCommentReactions((prev: any) => [...(prev || []), data]); @@ -77,8 +75,7 @@ const useCommentReaction = ( workspaceSlug.toString(), projectId.toString(), commendId.toString(), - reaction, - user.user + reaction ); mutateCommentReactions(); diff --git a/web/hooks/use-issue-reaction.tsx b/web/hooks/use-issue-reaction.tsx index c4ea210de..8ed0cd695 100644 --- a/web/hooks/use-issue-reaction.tsx +++ b/web/hooks/use-issue-reaction.tsx @@ -47,8 +47,7 @@ const useIssueReaction = ( workspaceSlug.toString(), projectId.toString(), issueId.toString(), - { reaction }, - user.user + { reaction } ); mutateReaction((prev: any) => [...(prev || []), data]); @@ -72,8 +71,7 @@ const useIssueReaction = ( workspaceSlug.toString(), projectId.toString(), issueId.toString(), - reaction, - user.user + reaction ); mutateReaction(); diff --git a/web/lib/app-provider.tsx b/web/lib/app-provider.tsx new file mode 100644 index 000000000..3a822401c --- /dev/null +++ b/web/lib/app-provider.tsx @@ -0,0 +1,46 @@ +import { FC, ReactNode } from "react"; +import dynamic from "next/dynamic"; +import Router from "next/router"; +import NProgress from "nprogress"; +import { observer } from "mobx-react-lite"; +// mobx store provider +import { useMobxStore } from "lib/mobx/store-provider"; +// dynamic imports +const StoreWrapper = dynamic(() => import("lib/wrappers/store-wrapper"), { ssr: false }); +const PosthogWrapper = dynamic(() => import("lib/wrappers/posthog-wrapper"), { ssr: false }); +const CrispWrapper = dynamic(() => import("lib/wrappers/crisp-wrapper"), { ssr: false }); + +// nprogress +NProgress.configure({ showSpinner: false }); +Router.events.on("routeChangeStart", NProgress.start); +Router.events.on("routeChangeError", NProgress.done); +Router.events.on("routeChangeComplete", NProgress.done); + +export interface IAppProvider { + children: ReactNode; +} + +export const AppProvider: FC = observer((props) => { + const { children } = props; + // store + const { + user: { currentUser, currentWorkspaceRole, currentProjectRole }, + appConfig: { envConfig }, + } = useMobxStore(); + + return ( + + + + {children} + + + + ); +}); diff --git a/web/lib/auth.ts b/web/lib/auth.ts deleted file mode 100644 index 6f84db402..000000000 --- a/web/lib/auth.ts +++ /dev/null @@ -1,195 +0,0 @@ -// cookies -import { convertCookieStringToObject } from "./cookie"; -// types -import type { IProjectMember, IUser, IWorkspace, IWorkspaceMember } from "types"; -// helper -import { API_BASE_URL } from "helpers/common.helper"; - -export const requiredAuth = async (cookie?: string) => { - const cookies = convertCookieStringToObject(cookie); - const token = cookies?.accessToken; - - if (!token) return null; - - let user: IUser | null = null; - - try { - const data = await fetch(`${API_BASE_URL}/api/users/me/`, { - method: "GET", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}`, - }, - }) - .then((res) => res.json()) - .then((data) => data); - - user = data.user; - } catch (err) { - console.error(err); - user = null; - } - - return user; -}; - -export const requiredAdmin = async (workspaceSlug: string, projectId: string, cookie?: string) => { - const user = await requiredAuth(cookie); - - if (!user) return null; - - const cookies = convertCookieStringToObject(cookie); - const token = cookies?.accessToken; - - let memberDetail: IProjectMember | null = null; - - try { - const data = await fetch( - `${API_BASE_URL}/api/workspaces/${workspaceSlug}/projects/${projectId}/project-members/me/`, - { - method: "GET", - headers: { - Authorization: `Bearer ${token}`, - }, - } - ) - .then((res) => res.json()) - .then((data) => data); - - memberDetail = data; - } catch (err) { - console.error(err); - memberDetail = null; - } - - return memberDetail || null; -}; - -export const requiredWorkspaceAdmin = async (workspaceSlug: string, cookie?: string) => { - const user = await requiredAuth(cookie); - - if (!user) return null; - - const cookies = convertCookieStringToObject(cookie); - const token = cookies?.accessToken; - - let memberDetail: IWorkspaceMember | null = null; - - try { - const data = await fetch(`${API_BASE_URL}/api/workspaces/${workspaceSlug}/workspace-members/me/`, { - method: "GET", - headers: { - Authorization: `Bearer ${token}`, - }, - }) - .then((res) => res.json()) - .then((data) => data); - - memberDetail = data; - } catch (err) { - console.error(err); - memberDetail = null; - } - - return memberDetail || null; -}; - -export const homePageRedirect = async (cookie?: string) => { - const user = await requiredAuth(cookie); - - if (!user) - return { - redirect: { - destination: "/", - permanent: false, - }, - }; - - if (!user.is_onboarded) - return { - redirect: { - destination: "/onboarding", - permanent: false, - }, - }; - - let workspaces: IWorkspace[] = []; - - const cookies = convertCookieStringToObject(cookie); - const token = cookies?.accessToken; - - try { - const data = await fetch(`${API_BASE_URL}/api/users/me/workspaces/`, { - method: "GET", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}`, - }, - }) - .then((res) => res.json()) - .then((data) => data); - - workspaces = data; - } catch (e) { - console.error(e); - return { - redirect: { - destination: "/error", - permanent: false, - }, - }; - } - - const lastActiveWorkspace = workspaces.find((workspace) => workspace.id === user.last_workspace_id); - - if (lastActiveWorkspace) { - return { - redirect: { - destination: `/${lastActiveWorkspace.slug}`, - permanent: false, - }, - }; - } else if (workspaces.length > 0) { - return { - redirect: { - destination: `/${workspaces[0].slug}`, - permanent: false, - }, - }; - } - - const invitations = await fetch(`${API_BASE_URL}/api/users/me/invitations/workspaces/`, { - method: "GET", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}`, - }, - }) - .then((res) => res.json()) - .then((data) => data) - .catch((e) => { - console.error(e); - return { - redirect: { - destination: "/error", - permanent: false, - }, - }; - }); - - if (invitations.length > 0) - return { - redirect: { - destination: "/invitations", - permanent: false, - }, - }; - else { - return { - redirect: { - destination: "/create-workspace", - permanent: false, - }, - }; - } -}; diff --git a/web/lib/cookie.ts b/web/lib/cookie.ts deleted file mode 100644 index 394cca3f4..000000000 --- a/web/lib/cookie.ts +++ /dev/null @@ -1,16 +0,0 @@ -export const convertCookieStringToObject = (cookieHeader: string | undefined) => { - const list: any = {}; - if (!cookieHeader) return list; - - cookieHeader.split(`;`).forEach(function (cookie) { - // eslint-disable-next-line prefer-const - let [name, ...rest] = cookie.split(`=`); - name = name?.trim(); - if (!name) return; - const value = rest.join(`=`).trim(); - if (!value) return; - list[name] = decodeURIComponent(value); - }); - - return list; -}; diff --git a/web/lib/wrappers/crisp-wrapper.tsx b/web/lib/wrappers/crisp-wrapper.tsx new file mode 100644 index 000000000..87480dfaa --- /dev/null +++ b/web/lib/wrappers/crisp-wrapper.tsx @@ -0,0 +1,40 @@ +import { useEffect, ReactNode, FC } from "react"; +// hooks +import { IUser } from "types"; + +declare global { + interface Window { + $crisp: any; + CRISP_WEBSITE_ID: any; + } +} + +export interface ICrispWrapper { + children: ReactNode; + user: IUser | null; +} + +const CrispWrapper: FC = (props) => { + const { children, user } = props; + + useEffect(() => { + if (typeof window && user?.email) { + window.$crisp = []; + window.CRISP_WEBSITE_ID = process.env.NEXT_PUBLIC_CRISP_ID; + (function () { + var d = document; + var s = d.createElement("script"); + s.src = "https://client.crisp.chat/l.js"; + s.async = true; + d.getElementsByTagName("head")[0].appendChild(s); + window.$crisp.push(["set", "user:email", [user.email]]); + window.$crisp.push(["do", "chat:hide"]); + window.$crisp.push(["do", "chat:close"]); + })(); + } + }, [user?.email]); + + return <>{children}; +}; + +export default CrispWrapper; diff --git a/web/lib/wrappers/posthog-wrapper.tsx b/web/lib/wrappers/posthog-wrapper.tsx new file mode 100644 index 000000000..1e7c35c0d --- /dev/null +++ b/web/lib/wrappers/posthog-wrapper.tsx @@ -0,0 +1,72 @@ +import { FC, ReactNode, useEffect } from "react"; +import { useRouter } from "next/router"; +import posthog from "posthog-js"; +import { PostHogProvider } from "posthog-js/react"; +// mobx store provider +import { IUser } from "types"; +// helpers +import { getUserRole } from "helpers/user.helper"; + +export interface IPosthogWrapper { + children: ReactNode; + user: IUser | null; + workspaceRole: number | undefined; + projectRole: number | undefined; + posthogAPIKey: string | null; + posthogHost: string | null; +} + +const PosthogWrapper: FC = (props) => { + const { children, user, workspaceRole, projectRole, posthogAPIKey, posthogHost } = props; + // router + const router = useRouter(); + + useEffect(() => { + if (user) { + // Identify sends an event, so you want may want to limit how often you call it + posthog?.identify(user.email, { + email: user.email, + first_name: user.first_name, + last_name: user.last_name, + id: user.id, + workspace_role: workspaceRole ? getUserRole(workspaceRole) : undefined, + project_role: projectRole ? getUserRole(projectRole) : undefined, + }); + } + }, [user, workspaceRole, projectRole]); + + useEffect(() => { + if (posthogAPIKey && posthogHost) { + posthog.init(posthogAPIKey, { + api_host: posthogHost || "https://app.posthog.com", + // Enable debug mode in development + loaded: (posthog) => { + if (process.env.NODE_ENV === "development") posthog.debug(); + }, + autocapture: false, + capture_pageview: false, // Disable automatic pageview capture, as we capture manually + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + // Track page views + const handleRouteChange = () => { + posthog?.capture("$pageview"); + }; + router.events.on("routeChangeComplete", handleRouteChange); + + return () => { + router.events.off("routeChangeComplete", handleRouteChange); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + if (posthogAPIKey) { + return {children}; + } + return <>{children}; +}; + +export default PosthogWrapper; diff --git a/web/lib/mobx/store-init.tsx b/web/lib/wrappers/store-wrapper.tsx similarity index 91% rename from web/lib/mobx/store-init.tsx rename to web/lib/wrappers/store-wrapper.tsx index f89aa72c7..cec08f158 100644 --- a/web/lib/mobx/store-init.tsx +++ b/web/lib/wrappers/store-wrapper.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { ReactNode, useEffect, useState, FC } from "react"; import { observer } from "mobx-react-lite"; import useSWR from "swr"; import { useTheme } from "next-themes"; @@ -8,7 +8,12 @@ import { useMobxStore } from "lib/mobx/store-provider"; // helpers import { applyTheme, unsetCustomCssVariables } from "helpers/theme.helper"; -const MobxStoreInit = observer(() => { +interface IStoreWrapper { + children: ReactNode; +} + +const StoreWrapper: FC = observer((props) => { + const { children } = props; // router const router = useRouter(); const { workspaceSlug, projectId, cycleId, moduleId, globalViewId, viewId, inboxId } = router.query; @@ -86,7 +91,7 @@ const MobxStoreInit = observer(() => { setInboxId, ]); - return <>; + return <>{children}; }); -export default MobxStoreInit; +export default StoreWrapper; diff --git a/web/package.json b/web/package.json index b2791978f..9657cdd68 100644 --- a/web/package.json +++ b/web/package.json @@ -41,6 +41,7 @@ "next-pwa": "^5.6.0", "next-themes": "^0.2.1", "nprogress": "^0.2.0", + "posthog-js": "^1.88.4", "react": "18.2.0", "react-color": "^2.19.3", "react-datepicker": "^4.8.0", diff --git a/web/pages/[workspaceSlug]/analytics.tsx b/web/pages/[workspaceSlug]/analytics.tsx index a71904e67..839991af0 100644 --- a/web/pages/[workspaceSlug]/analytics.tsx +++ b/web/pages/[workspaceSlug]/analytics.tsx @@ -1,11 +1,8 @@ -import React, { Fragment, useEffect, ReactElement } from "react"; -import { useRouter } from "next/router"; +import React, { Fragment, ReactElement } from "react"; import { observer } from "mobx-react-lite"; import { Tab } from "@headlessui/react"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; -// services -import { TrackEventService } from "services/track_event.service"; // layouts import { AppLayout } from "layouts/app-layout"; // components @@ -21,40 +18,13 @@ import { ANALYTICS_TABS } from "constants/analytics"; // type import { NextPageWithLayout } from "types/app"; -const trackEventService = new TrackEventService(); - const AnalyticsPage: NextPageWithLayout = observer(() => { - // router - const router = useRouter(); - const { workspaceSlug } = router.query; // store const { project: { workspaceProjects }, - user: { currentUser }, commandPalette: { toggleCreateProjectModal }, } = useMobxStore(); - const trackAnalyticsEvent = (tab: string) => { - if (!currentUser) return; - const eventPayload = { - workspaceSlug: workspaceSlug?.toString(), - }; - const eventType = - tab === "scope_and_demand" ? "WORKSPACE_SCOPE_AND_DEMAND_ANALYTICS" : "WORKSPACE_CUSTOM_ANALYTICS"; - trackEventService.trackAnalyticsEvent(eventPayload, eventType, currentUser); - }; - - useEffect(() => { - if (!workspaceSlug) return; - - if (currentUser && workspaceSlug) - trackEventService.trackAnalyticsEvent( - { workspaceSlug: workspaceSlug?.toString() }, - "WORKSPACE_SCOPE_AND_DEMAND_ANALYTICS", - currentUser - ); - }, [currentUser, workspaceSlug]); - return ( <> {workspaceProjects && workspaceProjects.length > 0 ? ( @@ -69,7 +39,7 @@ const AnalyticsPage: NextPageWithLayout = observer(() => { selected ? "bg-custom-background-80" : "" }` } - onClick={() => trackAnalyticsEvent(tab.key)} + onClick={() => {}} > {tab.title} diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/archived-issues/[archivedIssueId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/archived-issues/[archivedIssueId].tsx index cf0987edb..b02585182 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/archived-issues/[archivedIssueId].tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/archived-issues/[archivedIssueId].tsx @@ -5,7 +5,6 @@ import { useForm } from "react-hook-form"; // services import { IssueService, IssueArchiveService } from "services/issue"; // hooks -import useUserAuth from "hooks/use-user-auth"; import useToast from "hooks/use-toast"; // layouts import { AppLayout } from "layouts/app-layout"; @@ -45,7 +44,6 @@ const ArchivedIssueDetailsPage: NextPageWithLayout = () => { // states const [isRestoring, setIsRestoring] = useState(false); // hooks - const { user } = useUserAuth(); const { setToastAlert } = useToast(); const { data: issueDetails, mutate: mutateIssueDetails } = useSWR( @@ -86,7 +84,7 @@ const ArchivedIssueDetailsPage: NextPageWithLayout = () => { }; await issueService - .patchIssue(workspaceSlug as string, projectId as string, archivedIssueId as string, payload, user) + .patchIssue(workspaceSlug as string, projectId as string, archivedIssueId as string, payload) .then(() => { mutateIssueDetails(); mutate(PROJECT_ISSUES_ACTIVITY(archivedIssueId as string)); @@ -95,7 +93,7 @@ const ArchivedIssueDetailsPage: NextPageWithLayout = () => { console.error(e); }); }, - [workspaceSlug, archivedIssueId, projectId, mutateIssueDetails, user] + [workspaceSlug, archivedIssueId, projectId, mutateIssueDetails] ); useEffect(() => { diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx index a4e1eb296..c1314b414 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx @@ -22,6 +22,7 @@ import { NextPageWithLayout } from "types/app"; import { CYCLE_TAB_LIST, CYCLE_VIEW_LAYOUTS } from "constants/cycle"; // lib cookie import { setLocalStorage, getLocalStorage } from "lib/local-storage"; +// TODO: use-local-storage hook instead of lib file. const ProjectCyclesPage: NextPageWithLayout = observer(() => { const [createModal, setCreateModal] = useState(false); diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx index 32c11345e..753244628 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx @@ -4,8 +4,6 @@ import useSWR, { mutate } from "swr"; import { useForm } from "react-hook-form"; // services import { IssueService } from "services/issue"; -// hooks -import useUserAuth from "hooks/use-user-auth"; // layouts import { AppLayout } from "layouts/app-layout"; // components @@ -43,8 +41,6 @@ const IssueDetailsPage: NextPageWithLayout = () => { const router = useRouter(); const { workspaceSlug, projectId, issueId } = router.query; - const { user } = useUserAuth(); - const { data: issueDetails, mutate: mutateIssueDetails, @@ -85,7 +81,7 @@ const IssueDetailsPage: NextPageWithLayout = () => { delete payload.issue_relations; await issueService - .patchIssue(workspaceSlug as string, projectId as string, issueId as string, payload, user) + .patchIssue(workspaceSlug as string, projectId as string, issueId as string, payload) .then(() => { mutateIssueDetails(); mutate(PROJECT_ISSUES_ACTIVITY(issueId as string)); @@ -94,7 +90,7 @@ const IssueDetailsPage: NextPageWithLayout = () => { console.error(e); }); }, - [workspaceSlug, issueId, projectId, mutateIssueDetails, user] + [workspaceSlug, issueId, projectId, mutateIssueDetails] ); useEffect(() => { diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx index dd1efde15..e64bb628e 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx @@ -105,18 +105,16 @@ const PageDetailsPage: NextPageWithLayout = () => { if (!formData.name || formData.name.length === 0 || formData.name === "") return; - await pageService - .patchPage(workspaceSlug as string, projectId as string, pageId as string, formData, user) - .then(() => { - mutate( - PAGE_DETAILS(pageId as string), - (prevData) => ({ - ...prevData, - ...formData, - }), - false - ); - }); + await pageService.patchPage(workspaceSlug as string, projectId as string, pageId as string, formData).then(() => { + mutate( + PAGE_DETAILS(pageId as string), + (prevData) => ({ + ...prevData, + ...formData, + }), + false + ); + }); }; const partialUpdatePage = async (formData: Partial) => { @@ -131,11 +129,9 @@ const PageDetailsPage: NextPageWithLayout = () => { false ); - await pageService - .patchPage(workspaceSlug as string, projectId as string, pageId as string, formData, user) - .then(() => { - mutate(PAGE_DETAILS(pageId as string)); - }); + await pageService.patchPage(workspaceSlug as string, projectId as string, pageId as string, formData).then(() => { + mutate(PAGE_DETAILS(pageId as string)); + }); }; const handleAddToFavorites = () => { @@ -209,16 +205,9 @@ const PageDetailsPage: NextPageWithLayout = () => { false ); - pageService.patchPageBlock( - workspaceSlug as string, - projectId as string, - pageId as string, - result.draggableId, - { - sort_order: newSortOrder, - }, - user - ); + pageService.patchPageBlock(workspaceSlug as string, projectId as string, pageId as string, result.draggableId, { + sort_order: newSortOrder, + }); }; const handleCopyText = () => { diff --git a/web/pages/[workspaceSlug]/settings/members.tsx b/web/pages/[workspaceSlug]/settings/members.tsx index 303c0a9d1..93aba3db1 100644 --- a/web/pages/[workspaceSlug]/settings/members.tsx +++ b/web/pages/[workspaceSlug]/settings/members.tsx @@ -17,6 +17,7 @@ import { Search } from "lucide-react"; // types import { NextPageWithLayout } from "types/app"; import { IWorkspaceBulkInviteFormData } from "types"; +import { trackEvent } from "helpers/event-tracker.helper"; const WorkspaceMembersSettingsPage: NextPageWithLayout = observer(() => { const router = useRouter(); @@ -35,21 +36,23 @@ const WorkspaceMembersSettingsPage: NextPageWithLayout = observer(() => { if (!workspaceSlug) return; return inviteMembersToWorkspace(workspaceSlug.toString(), data) - .then(async () => { + .then(async (res) => { setInviteModal(false); + trackEvent("WORKSPACE_USER_INVITE"); setToastAlert({ type: "success", title: "Success!", message: "Invitations sent successfully.", }); }) - .catch((err) => + .catch((err) => { + trackEvent("WORKSPACE_USER_INVITE/FAIL"); setToastAlert({ type: "error", title: "Error!", message: `${err.error ?? "Something went wrong. Please try again."}`, - }) - ); + }); + }); }; return ( diff --git a/web/pages/_app.tsx b/web/pages/_app.tsx index d4dca452a..9f2cf995b 100644 --- a/web/pages/_app.tsx +++ b/web/pages/_app.tsx @@ -1,10 +1,7 @@ import { ReactElement } from "react"; import Head from "next/head"; -import dynamic from "next/dynamic"; -import Router from "next/router"; import { AppProps } from "next/app"; import { ThemeProvider } from "next-themes"; -import NProgress from "nprogress"; // styles import "styles/globals.css"; import "styles/editor.css"; @@ -19,18 +16,10 @@ import { THEMES } from "constants/themes"; import { SITE_TITLE } from "constants/seo-variables"; // mobx store provider import { MobxStoreProvider } from "lib/mobx/store-provider"; -import MobxStoreInit from "lib/mobx/store-init"; +import { AppProvider } from "lib/app-provider"; // types import { NextPageWithLayout } from "types/app"; -const CrispWithNoSSR = dynamic(() => import("constants/crisp"), { ssr: false }); - -// nprogress -NProgress.configure({ showSpinner: false }); -Router.events.on("routeChangeStart", NProgress.start); -Router.events.on("routeChangeError", NProgress.done); -Router.events.on("routeChangeComplete", NProgress.done); - type AppPropsWithLayout = AppProps & { Component: NextPageWithLayout; }; @@ -47,9 +36,7 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { - - - {getLayout()} + {getLayout()} diff --git a/web/pages/api/track-event.ts b/web/pages/api/track-event.ts deleted file mode 100644 index 4928c552e..000000000 --- a/web/pages/api/track-event.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from "next"; -// jitsu -import { createClient } from "@jitsu/nextjs"; - -const jitsuClient = createClient({ - key: process.env.JITSU_TRACKER_ACCESS_KEY || "", - tracking_host: process.env.JITSU_TRACKER_HOST || "", -}); - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - const { eventName, user, extra } = req.body; - - if (!eventName) { - return res.status(400).json({ message: "Bad request" }); - } - - if (!user) return res.status(401).json({ message: "Unauthorized" }); - - jitsuClient - .id({ - id: user?.id, - email: user?.email, - first_name: user?.first_name, - last_name: user?.last_name, - display_name: user?.display_name, - }) - .then(() => { - jitsuClient.track(eventName, { - ...extra, - }); - }); - - res.status(200).json({ message: "success" }); -} diff --git a/web/pages/api/unsplash.ts b/web/pages/api/unsplash.ts deleted file mode 100644 index 11acb2096..000000000 --- a/web/pages/api/unsplash.ts +++ /dev/null @@ -1,26 +0,0 @@ -import axios from "axios"; -import type { NextApiRequest, NextApiResponse } from "next"; - -const unsplashKey = process.env.UNSPLASH_ACCESS_KEY; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - const { query, page, per_page = 20 } = req.query; - - const url = query - ? `https://api.unsplash.com/search/photos/?client_id=${unsplashKey}&query=${query}&page=${page}&per_page=${per_page}` - : `https://api.unsplash.com/photos/?client_id=${unsplashKey}&page=${page}&per_page=${per_page}`; - - const response = await axios({ - method: "GET", - url, - headers: { - "Content-Type": "application/json", - }, - }); - - res.status(200).json(response?.data); - } catch (error) { - res.status(500).json({ message: "Failed to fetch unsplash", error }); - } -} diff --git a/web/pages/workspace-invitations/index.tsx b/web/pages/workspace-invitations/index.tsx index c2d3bd9e2..784dc166f 100644 --- a/web/pages/workspace-invitations/index.tsx +++ b/web/pages/workspace-invitations/index.tsx @@ -36,15 +36,10 @@ const WorkspaceInvitationPage: NextPageWithLayout = () => { const handleAccept = () => { if (!invitationDetail) return; workspaceService - .joinWorkspace( - invitationDetail.workspace.slug, - invitationDetail.id, - { - accepted: true, - email: invitationDetail.email, - }, - user - ) + .joinWorkspace(invitationDetail.workspace.slug, invitationDetail.id, { + accepted: true, + email: invitationDetail.email, + }) .then(() => { if (email === user?.email) { router.push("/invitations"); diff --git a/web/services/ai.service.ts b/web/services/ai.service.ts index 63bf02ee4..363da51ed 100644 --- a/web/services/ai.service.ts +++ b/web/services/ai.service.ts @@ -1,12 +1,9 @@ import { APIService } from "services/api.service"; -import { TrackEventService } from "services/track_event.service"; // types -import { IUser, IGptResponse } from "types"; +import { IGptResponse } from "types"; // helpers import { API_BASE_URL } from "helpers/common.helper"; -const trackEventService = new TrackEventService(); - export class AIService extends APIService { constructor() { super(API_BASE_URL); @@ -15,14 +12,10 @@ export class AIService extends APIService { async createGptTask( workspaceSlug: string, projectId: string, - data: { prompt: string; task: string }, - user: IUser | undefined + data: { prompt: string; task: string } ): Promise { return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/ai-assistant/`, data) - .then((response) => { - trackEventService.trackAskGptEvent(response?.data, "ASK_GPT", user as IUser); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response; }); diff --git a/web/services/cycle.service.ts b/web/services/cycle.service.ts index 2e8b0b998..892d8fb1b 100644 --- a/web/services/cycle.service.ts +++ b/web/services/cycle.service.ts @@ -1,24 +1,18 @@ // services import { APIService } from "services/api.service"; -import { TrackEventService } from "services/track_event.service"; // types -import type { CycleDateCheckData, IUser, ICycle, IIssue } from "types"; +import type { CycleDateCheckData, ICycle, IIssue } from "types"; // helpers import { API_BASE_URL } from "helpers/common.helper"; -const trackEventService = new TrackEventService(); - export class CycleService extends APIService { constructor() { super(API_BASE_URL); } - async createCycle(workspaceSlug: string, projectId: string, data: any, user: any): Promise { + async createCycle(workspaceSlug: string, projectId: string, data: any): Promise { return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/`, data) - .then((response) => { - trackEventService.trackCycleEvent(response?.data, "CYCLE_CREATE", user); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); @@ -71,29 +65,17 @@ export class CycleService extends APIService { }); } - async patchCycle( - workspaceSlug: string, - projectId: string, - cycleId: string, - data: Partial, - user: IUser | undefined - ): Promise { + async patchCycle(workspaceSlug: string, projectId: string, cycleId: string, data: Partial): Promise { return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/`, data) - .then((response) => { - if (user) trackEventService.trackCycleEvent(response?.data, "CYCLE_UPDATE", user); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); } - async deleteCycle(workspaceSlug: string, projectId: string, cycleId: string, user: IUser | undefined): Promise { + async deleteCycle(workspaceSlug: string, projectId: string, cycleId: string): Promise { return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/`) - .then((response) => { - trackEventService.trackCycleEvent(response?.data, "CYCLE_DELETE", user as IUser); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); diff --git a/web/services/inbox.service.ts b/web/services/inbox.service.ts index 05b19aa41..3cde8ffff 100644 --- a/web/services/inbox.service.ts +++ b/web/services/inbox.service.ts @@ -1,11 +1,8 @@ import { APIService } from "services/api.service"; -import { TrackEventService } from "services/track_event.service"; // helpers import { API_BASE_URL } from "helpers/common.helper"; // types -import type { IInboxIssue, IInbox, TInboxStatus, IUser, IInboxQueryParams } from "types"; - -const trackEventService = new TrackEventService(); +import type { IInboxIssue, IInbox, TInboxStatus, IInboxQueryParams } from "types"; export class InboxService extends APIService { constructor() { @@ -70,16 +67,12 @@ export class InboxService extends APIService { workspaceSlug: string, projectId: string, inboxId: string, - inboxIssueId: string, - user: IUser | undefined + inboxIssueId: string ): Promise { return this.delete( `/api/workspaces/${workspaceSlug}/projects/${projectId}/inboxes/${inboxId}/inbox-issues/${inboxIssueId}/` ) - .then((response) => { - if (user) trackEventService.trackInboxEvent(response?.data, "INBOX_ISSUE_DELETE", user); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); @@ -90,25 +83,13 @@ export class InboxService extends APIService { projectId: string, inboxId: string, inboxIssueId: string, - data: TInboxStatus, - user: IUser | undefined + data: TInboxStatus ): Promise { return this.patch( `/api/workspaces/${workspaceSlug}/projects/${projectId}/inboxes/${inboxId}/inbox-issues/${inboxIssueId}/`, data ) - .then((response) => { - const action = - data.status === -1 - ? "INBOX_ISSUE_REJECTED" - : data.status === 0 - ? "INBOX_ISSUE_SNOOZED" - : data.status === 1 - ? "INBOX_ISSUE_ACCEPTED" - : "INBOX_ISSUE_DUPLICATED"; - trackEventService.trackInboxEvent(response?.data, action, user as IUser); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); @@ -119,34 +100,21 @@ export class InboxService extends APIService { projectId: string, inboxId: string, inboxIssueId: string, - data: { issue: Partial }, - user: IUser | undefined + data: { issue: Partial } ): Promise { return this.patch( `/api/workspaces/${workspaceSlug}/projects/${projectId}/inboxes/${inboxId}/inbox-issues/${inboxIssueId}/`, data ) - .then((response) => { - if (user) trackEventService.trackInboxEvent(response?.data, "INBOX_ISSUE_UPDATE", user); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); } - async createInboxIssue( - workspaceSlug: string, - projectId: string, - inboxId: string, - data: any, - user: IUser | undefined - ): Promise { + async createInboxIssue(workspaceSlug: string, projectId: string, inboxId: string, data: any): Promise { return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/inboxes/${inboxId}/inbox-issues/`, data) - .then((response) => { - if (user) trackEventService.trackInboxEvent(response?.data, "INBOX_ISSUE_CREATE", user); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); diff --git a/web/services/integrations/github.service.ts b/web/services/integrations/github.service.ts index bec6101e5..c69f8bd7e 100644 --- a/web/services/integrations/github.service.ts +++ b/web/services/integrations/github.service.ts @@ -1,14 +1,11 @@ import { APIService } from "services/api.service"; -import { TrackEventService } from "services/track_event.service"; // helpers import { API_BASE_URL } from "helpers/common.helper"; // types -import { IUser, IGithubRepoInfo, IGithubServiceImportFormData } from "types"; +import { IGithubRepoInfo, IGithubServiceImportFormData } from "types"; const integrationServiceType: string = "github"; -const trackEventService = new TrackEventService(); - export class GithubIntegrationService extends APIService { constructor() { super(API_BASE_URL); @@ -32,16 +29,9 @@ export class GithubIntegrationService extends APIService { }); } - async createGithubServiceImport( - workspaceSlug: string, - data: IGithubServiceImportFormData, - user: IUser | undefined - ): Promise { + async createGithubServiceImport(workspaceSlug: string, data: IGithubServiceImportFormData): Promise { return this.post(`/api/workspaces/${workspaceSlug}/projects/importers/${integrationServiceType}/`, data) - .then((response) => { - trackEventService.trackImporterEvent(response?.data, "GITHUB_IMPORTER_CREATE", user); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); diff --git a/web/services/integrations/integration.service.ts b/web/services/integrations/integration.service.ts index e36ea4889..a6b4ae447 100644 --- a/web/services/integrations/integration.service.ts +++ b/web/services/integrations/integration.service.ts @@ -1,12 +1,9 @@ import { APIService } from "services/api.service"; -import { TrackEventService } from "services/track_event.service"; // types -import { IAppIntegration, IUser, IImporterService, IWorkspaceIntegration, IExportServiceResponse } from "types"; +import { IAppIntegration, IImporterService, IWorkspaceIntegration, IExportServiceResponse } from "types"; // helper import { API_BASE_URL } from "helpers/common.helper"; -const trackEventService = new TrackEventService(); - export class IntegrationService extends APIService { constructor() { super(API_BASE_URL); @@ -60,18 +57,9 @@ export class IntegrationService extends APIService { }); } - async deleteImporterService( - workspaceSlug: string, - service: string, - importerId: string, - user: IUser | undefined - ): Promise { + async deleteImporterService(workspaceSlug: string, service: string, importerId: string): Promise { return this.delete(`/api/workspaces/${workspaceSlug}/importers/${service}/${importerId}/`) - .then((response) => { - const eventName = service === "github" ? "GITHUB_IMPORTER_DELETE" : "JIRA_IMPORTER_DELETE"; - trackEventService.trackImporterEvent(response?.data, eventName, user); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); diff --git a/web/services/integrations/jira.service.ts b/web/services/integrations/jira.service.ts index 744dc2b11..50deeecf8 100644 --- a/web/services/integrations/jira.service.ts +++ b/web/services/integrations/jira.service.ts @@ -1,10 +1,7 @@ import { APIService } from "services/api.service"; -import { TrackEventService } from "services/track_event.service"; import { API_BASE_URL } from "helpers/common.helper"; // types -import { IJiraMetadata, IJiraResponse, IJiraImporterForm, IUser } from "types"; - -const trackEventService = new TrackEventService(); +import { IJiraMetadata, IJiraResponse, IJiraImporterForm } from "types"; export class JiraImporterService extends APIService { constructor() { @@ -21,16 +18,9 @@ export class JiraImporterService extends APIService { }); } - async createJiraImporter( - workspaceSlug: string, - data: IJiraImporterForm, - user: IUser | undefined - ): Promise { + async createJiraImporter(workspaceSlug: string, data: IJiraImporterForm): Promise { return this.post(`/api/workspaces/${workspaceSlug}/projects/importers/jira/`, data) - .then((response) => { - trackEventService.trackImporterEvent(response?.data, "JIRA_IMPORTER_CREATE", user); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); diff --git a/web/services/issue/issue.service.ts b/web/services/issue/issue.service.ts index 5f2844896..d1dba8ea3 100644 --- a/web/services/issue/issue.service.ts +++ b/web/services/issue/issue.service.ts @@ -1,24 +1,18 @@ // services import { APIService } from "services/api.service"; -import { TrackEventService } from "services/track_event.service"; // type -import type { IUser, IIssue, IIssueActivity, ISubIssueResponse, IIssueDisplayProperties } from "types"; +import type { IIssue, IIssueActivity, ISubIssueResponse, IIssueDisplayProperties } from "types"; // helper import { API_BASE_URL } from "helpers/common.helper"; -const trackEventService = new TrackEventService(); - export class IssueService extends APIService { constructor() { super(API_BASE_URL); } - async createIssue(workspaceSlug: string, projectId: string, data: any, user: IUser | undefined): Promise { + async createIssue(workspaceSlug: string, projectId: string, data: any): Promise { return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/`, data) - .then((response) => { - trackEventService.trackIssueEvent(response.data, "ISSUE_CREATE", user as IUser); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); @@ -68,26 +62,10 @@ export class IssueService extends APIService { cycleId: string, data: { issues: string[]; - }, - user: IUser | undefined + } ) { return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/cycle-issues/`, data) - .then((response) => { - trackEventService.trackIssueMovedToCycleOrModuleEvent( - { - workspaceSlug, - workspaceName: response?.data?.[0]?.issue_detail?.workspace_detail?.name, - projectId, - projectIdentifier: response?.data?.[0]?.issue_detail?.project_detail?.identifier, - projectName: response?.data?.[0]?.issue_detail?.project_detail?.name, - issueId: response?.data?.[0]?.issue_detail?.id, - cycleId, - }, - response.data.length > 1 ? "ISSUE_MOVED_TO_CYCLE_IN_BULK" : "ISSUE_MOVED_TO_CYCLE", - user as IUser - ); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); @@ -107,7 +85,6 @@ export class IssueService extends APIService { workspaceSlug: string, projectId: string, issueId: string, - user: IUser, data: { related_list: Array<{ relation_type: "duplicate" | "relates_to" | "blocked_by"; @@ -117,29 +94,17 @@ export class IssueService extends APIService { } ) { return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-relation/`, data) - .then((response) => { - trackEventService.trackIssueRelationEvent(response.data, "ISSUE_RELATION_CREATE", user); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response; }); } - async deleteIssueRelation( - workspaceSlug: string, - projectId: string, - issueId: string, - relationId: string, - user: IUser - ) { + async deleteIssueRelation(workspaceSlug: string, projectId: string, issueId: string, relationId: string) { return this.delete( `/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-relation/${relationId}/` ) - .then((response) => { - trackEventService.trackIssueRelationEvent(response.data, "ISSUE_RELATION_DELETE", user); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response; }); @@ -167,40 +132,25 @@ export class IssueService extends APIService { }); } - async patchIssue( - workspaceSlug: string, - projectId: string, - issueId: string, - data: Partial, - user: IUser | undefined - ): Promise { + async patchIssue(workspaceSlug: string, projectId: string, issueId: string, data: Partial): Promise { return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/`, data) - .then((response) => { - trackEventService.trackIssueEvent(response.data, "ISSUE_UPDATE", user as IUser); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); } - async deleteIssue(workspaceSlug: string, projectId: string, issuesId: string, user: IUser | undefined): Promise { + async deleteIssue(workspaceSlug: string, projectId: string, issuesId: string): Promise { return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issuesId}/`) - .then((response) => { - trackEventService.trackIssueEvent({ issuesId }, "ISSUE_DELETE", user as IUser); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); } - async bulkDeleteIssues(workspaceSlug: string, projectId: string, data: any, user: IUser | undefined): Promise { + async bulkDeleteIssues(workspaceSlug: string, projectId: string, data: any): Promise { return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/bulk-delete-issues/`, data) - .then((response) => { - trackEventService.trackIssueBulkDeleteEvent(data, user as IUser); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); diff --git a/web/services/issue/issue_comment.service.ts b/web/services/issue/issue_comment.service.ts index 12c08a517..3c2bb8669 100644 --- a/web/services/issue/issue_comment.service.ts +++ b/web/services/issue/issue_comment.service.ts @@ -1,12 +1,9 @@ import { APIService } from "services/api.service"; -import { TrackEventService } from "services/track_event.service"; // types -import { IIssueComment, IUser } from "types"; +import { IIssueComment } from "types"; // helper import { API_BASE_URL } from "helpers/common.helper"; -const trackEventService = new TrackEventService(); - export class IssueCommentService extends APIService { constructor() { super(API_BASE_URL); @@ -24,14 +21,10 @@ export class IssueCommentService extends APIService { workspaceSlug: string, projectId: string, issueId: string, - data: Partial, - user: IUser | undefined + data: Partial ): Promise { return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/comments/`, data) - .then((response) => { - trackEventService.trackIssueCommentEvent(response.data, "ISSUE_COMMENT_CREATE", user); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); @@ -42,43 +35,23 @@ export class IssueCommentService extends APIService { projectId: string, issueId: string, commentId: string, - data: Partial, - user: IUser | undefined + data: Partial ): Promise { return this.patch( `/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/comments/${commentId}/`, data ) - .then((response) => { - trackEventService.trackIssueCommentEvent(response.data, "ISSUE_COMMENT_UPDATE", user); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); } - async deleteIssueComment( - workspaceSlug: string, - projectId: string, - issueId: string, - commentId: string, - user: IUser | undefined - ): Promise { + async deleteIssueComment(workspaceSlug: string, projectId: string, issueId: string, commentId: string): Promise { return this.delete( `/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/comments/${commentId}/` ) - .then((response) => { - trackEventService.trackIssueCommentEvent( - { - issueId, - commentId, - }, - "ISSUE_COMMENT_DELETE", - user - ); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); diff --git a/web/services/issue/issue_label.service.ts b/web/services/issue/issue_label.service.ts index d6bab8348..dc55b1501 100644 --- a/web/services/issue/issue_label.service.ts +++ b/web/services/issue/issue_label.service.ts @@ -1,11 +1,8 @@ import { API_BASE_URL } from "helpers/common.helper"; // services import { APIService } from "services/api.service"; -import { TrackEventService } from "services/track_event.service"; // types -import { IIssueLabel, IUser } from "types"; - -const trackEventServices = new TrackEventService(); +import { IIssueLabel } from "types"; export class IssueLabelService extends APIService { constructor() { @@ -28,83 +25,25 @@ export class IssueLabelService extends APIService { }); } - async createIssueLabel( - workspaceSlug: string, - projectId: string, - data: any, - user: IUser | undefined - ): Promise { + async createIssueLabel(workspaceSlug: string, projectId: string, data: any): Promise { return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-labels/`, data) - .then((response: { data: IIssueLabel; [key: string]: any }) => { - trackEventServices.trackIssueLabelEvent( - { - workSpaceId: response?.data?.workspace_detail?.id, - workSpaceName: response?.data?.workspace_detail?.name, - workspaceSlug, - projectId, - projectIdentifier: response?.data?.project_detail?.identifier, - projectName: response?.data?.project_detail?.name, - labelId: response?.data?.id, - color: response?.data?.color, - }, - "ISSUE_LABEL_CREATE", - user as IUser - ); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); } - async patchIssueLabel( - workspaceSlug: string, - projectId: string, - labelId: string, - data: any, - user: IUser | undefined - ): Promise { + async patchIssueLabel(workspaceSlug: string, projectId: string, labelId: string, data: any): Promise { return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-labels/${labelId}/`, data) - .then((response) => { - trackEventServices.trackIssueLabelEvent( - { - workSpaceId: response?.data?.workspace_detail?.id, - workSpaceName: response?.data?.workspace_detail?.name, - workspaceSlug, - projectId, - projectIdentifier: response?.data?.project_detail?.identifier, - projectName: response?.data?.project_detail?.name, - labelId: response?.data?.id, - color: response?.data?.color, - }, - "ISSUE_LABEL_UPDATE", - user as IUser - ); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); } - async deleteIssueLabel( - workspaceSlug: string, - projectId: string, - labelId: string, - user: IUser | undefined - ): Promise { + async deleteIssueLabel(workspaceSlug: string, projectId: string, labelId: string): Promise { return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-labels/${labelId}/`) - .then((response) => { - trackEventServices.trackIssueLabelEvent( - { - workspaceSlug, - projectId, - }, - "ISSUE_LABEL_DELETE", - user as IUser - ); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); diff --git a/web/services/issue/issue_reaction.service.ts b/web/services/issue/issue_reaction.service.ts index 737d9879f..ce5846156 100644 --- a/web/services/issue/issue_reaction.service.ts +++ b/web/services/issue/issue_reaction.service.ts @@ -1,11 +1,8 @@ import { API_BASE_URL } from "helpers/common.helper"; // services import { APIService } from "services/api.service"; -import { TrackEventService } from "services/track_event.service"; // types -import type { IUser, IssueReaction, IssueCommentReaction, IssueReactionForm, IssueCommentReactionForm } from "types"; - -const trackEventService = new TrackEventService(); +import type { IssueReaction, IssueCommentReaction, IssueReactionForm, IssueCommentReactionForm } from "types"; export class IssueReactionService extends APIService { constructor() { @@ -16,14 +13,10 @@ export class IssueReactionService extends APIService { workspaceSlug: string, projectId: string, issueId: string, - data: IssueReactionForm, - user?: IUser + data: IssueReactionForm ): Promise { return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/reactions/`, data) - .then((response) => { - trackEventService.trackReactionEvent(response?.data, "ISSUE_REACTION_CREATE", user as IUser); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); @@ -37,20 +30,11 @@ export class IssueReactionService extends APIService { }); } - async deleteIssueReaction( - workspaceSlug: string, - projectId: string, - issueId: string, - reaction: string, - user?: IUser - ): Promise { + async deleteIssueReaction(workspaceSlug: string, projectId: string, issueId: string, reaction: string): Promise { return this.delete( `/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/reactions/${reaction}/` ) - .then((response) => { - trackEventService.trackReactionEvent(response?.data, "ISSUE_REACTION_DELETE", user as IUser); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); @@ -60,14 +44,10 @@ export class IssueReactionService extends APIService { workspaceSlug: string, projectId: string, commentId: string, - data: IssueCommentReactionForm, - user?: IUser + data: IssueCommentReactionForm ): Promise { return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/comments/${commentId}/reactions/`, data) - .then((response) => { - trackEventService.trackReactionEvent(response?.data, "ISSUE_COMMENT_REACTION_CREATE", user as IUser); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); @@ -89,16 +69,12 @@ export class IssueReactionService extends APIService { workspaceSlug: string, projectId: string, commentId: string, - reaction: string, - user?: IUser + reaction: string ): Promise { return this.delete( `/api/workspaces/${workspaceSlug}/projects/${projectId}/comments/${commentId}/reactions/${reaction}/` ) - .then((response) => { - trackEventService.trackReactionEvent(response?.data, "ISSUE_COMMENT_REACTION_DELETE", user as IUser); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); diff --git a/web/services/module.service.ts b/web/services/module.service.ts index dec4b2d8f..9a4e6049b 100644 --- a/web/services/module.service.ts +++ b/web/services/module.service.ts @@ -1,12 +1,9 @@ // services import { APIService } from "services/api.service"; -import { TrackEventService } from "services/track_event.service"; // types -import type { IModule, IIssue, IUser } from "types"; +import type { IModule, IIssue } from "types"; import { API_BASE_URL } from "helpers/common.helper"; -const trackEventService = new TrackEventService(); - export class ModuleService extends APIService { constructor() { super(API_BASE_URL); @@ -20,29 +17,17 @@ export class ModuleService extends APIService { }); } - async createModule(workspaceSlug: string, projectId: string, data: any, user: any): Promise { + async createModule(workspaceSlug: string, projectId: string, data: any): Promise { return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/`, data) - .then((response) => { - trackEventService.trackModuleEvent(response?.data, "MODULE_CREATE", user); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); } - async updateModule( - workspaceSlug: string, - projectId: string, - moduleId: string, - data: any, - user: IUser | undefined - ): Promise { + async updateModule(workspaceSlug: string, projectId: string, moduleId: string, data: any): Promise { return this.put(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/`, data) - .then((response) => { - trackEventService.trackModuleEvent(response?.data, "MODULE_UPDATE", user as IUser); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); @@ -60,25 +45,18 @@ export class ModuleService extends APIService { workspaceSlug: string, projectId: string, moduleId: string, - data: Partial, - user: IUser | undefined + data: Partial ): Promise { return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/`, data) - .then((response) => { - if (user) trackEventService.trackModuleEvent(response?.data, "MODULE_UPDATE", user); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); } - async deleteModule(workspaceSlug: string, projectId: string, moduleId: string, user: any): Promise { + async deleteModule(workspaceSlug: string, projectId: string, moduleId: string): Promise { return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/`) - .then((response) => { - trackEventService.trackModuleEvent(response?.data, "MODULE_DELETE", user); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); @@ -116,26 +94,10 @@ export class ModuleService extends APIService { workspaceSlug: string, projectId: string, moduleId: string, - data: { issues: string[] }, - user: IUser | undefined + data: { issues: string[] } ): Promise { return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/module-issues/`, data) - .then((response) => { - trackEventService.trackIssueMovedToCycleOrModuleEvent( - { - workspaceSlug, - workspaceName: response?.data?.[0]?.issue_detail?.workspace_detail?.name, - projectId, - projectIdentifier: response?.data?.[0]?.issue_detail?.project_detail?.identifier, - projectName: response?.data?.[0]?.issue_detail?.project_detail?.name, - issueId: response?.data?.[0]?.issue_detail?.id, - moduleId, - }, - response?.data?.length > 1 ? "ISSUE_MOVED_TO_MODULE_IN_BULK" : "ISSUE_MOVED_TO_MODULE", - user as IUser - ); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); diff --git a/web/services/page.service.ts b/web/services/page.service.ts index a344c69ec..e12f929e5 100644 --- a/web/services/page.service.ts +++ b/web/services/page.service.ts @@ -1,56 +1,33 @@ import { API_BASE_URL } from "helpers/common.helper"; // services import { APIService } from "services/api.service"; -import { TrackEventService } from "services/track_event.service"; // types -import { IPage, IPageBlock, RecentPagesResponse, IIssue, IUser } from "types"; - -const trackEventService = new TrackEventService(); +import { IPage, IPageBlock, RecentPagesResponse, IIssue } from "types"; export class PageService extends APIService { constructor() { super(API_BASE_URL); } - async createPage( - workspaceSlug: string, - projectId: string, - data: Partial, - user: IUser | undefined - ): Promise { + async createPage(workspaceSlug: string, projectId: string, data: Partial): Promise { return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/`, data) - .then((response) => { - trackEventService.trackPageEvent(response?.data, "PAGE_CREATE", user); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); } - async patchPage( - workspaceSlug: string, - projectId: string, - pageId: string, - data: Partial, - user: IUser | undefined - ): Promise { + async patchPage(workspaceSlug: string, projectId: string, pageId: string, data: Partial): Promise { return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/`, data) - .then((response) => { - trackEventService.trackPageEvent(response?.data, "PAGE_UPDATE", user); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); } - async deletePage(workspaceSlug: string, projectId: string, pageId: string, user: IUser | undefined): Promise { + async deletePage(workspaceSlug: string, projectId: string, pageId: string): Promise { return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/`) - .then((response) => { - trackEventService.trackPageEvent(response?.data, "PAGE_DELETE", user); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); @@ -118,14 +95,10 @@ export class PageService extends APIService { workspaceSlug: string, projectId: string, pageId: string, - data: Partial, - user: IUser | undefined + data: Partial ): Promise { return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/page-blocks/`, data) - .then((response) => { - trackEventService.trackPageBlockEvent(response?.data, "PAGE_BLOCK_CREATE", user as IUser); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); @@ -151,36 +124,23 @@ export class PageService extends APIService { projectId: string, pageId: string, pageBlockId: string, - data: Partial, - user: IUser | undefined + data: Partial ): Promise { return this.patch( `/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/page-blocks/${pageBlockId}/`, data ) - .then((response) => { - trackEventService.trackPageBlockEvent(response?.data, "PAGE_BLOCK_UPDATE", user as IUser); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); } - async deletePageBlock( - workspaceSlug: string, - projectId: string, - pageId: string, - pageBlockId: string, - user: IUser | undefined - ): Promise { + async deletePageBlock(workspaceSlug: string, projectId: string, pageId: string, pageBlockId: string): Promise { return this.delete( `/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/page-blocks/${pageBlockId}/` ) - .then((response) => { - trackEventService.trackPageBlockEvent(response?.data, "PAGE_BLOCK_DELETE", user as IUser); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); @@ -198,16 +158,12 @@ export class PageService extends APIService { workspaceSlug: string, projectId: string, pageId: string, - blockId: string, - user: IUser | undefined + blockId: string ): Promise { return this.post( `/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/page-blocks/${blockId}/issues/` ) - .then((response) => { - trackEventService.trackPageBlockEvent(response?.data, "PAGE_BLOCK_CONVERTED_TO_ISSUE", user as IUser); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); diff --git a/web/services/project/project-estimate.service.ts b/web/services/project/project-estimate.service.ts index 322d986fa..4a849868c 100644 --- a/web/services/project/project-estimate.service.ts +++ b/web/services/project/project-estimate.service.ts @@ -1,29 +1,18 @@ // services import { APIService } from "services/api.service"; -import { TrackEventService } from "services/track_event.service"; // types -import type { IUser, IEstimate, IEstimateFormData } from "types"; +import type { IEstimate, IEstimateFormData } from "types"; // helpers import { API_BASE_URL } from "helpers/common.helper"; -const trackEventService = new TrackEventService(); - export class ProjectEstimateService extends APIService { constructor() { super(API_BASE_URL); } - async createEstimate( - workspaceSlug: string, - projectId: string, - data: IEstimateFormData, - user: IUser | undefined - ): Promise { + async createEstimate(workspaceSlug: string, projectId: string, data: IEstimateFormData): Promise { return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/`, data) - .then((response) => { - trackEventService.trackIssueEstimateEvent(response?.data, "ESTIMATE_CREATE", user as IUser); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response; }); @@ -33,14 +22,10 @@ export class ProjectEstimateService extends APIService { workspaceSlug: string, projectId: string, estimateId: string, - data: IEstimateFormData, - user: IUser | undefined + data: IEstimateFormData ): Promise { return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/`, data) - .then((response) => { - trackEventService.trackIssueEstimateEvent(response?.data, "ESTIMATE_UPDATE", user as IUser); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); @@ -62,17 +47,9 @@ export class ProjectEstimateService extends APIService { }); } - async deleteEstimate( - workspaceSlug: string, - projectId: string, - estimateId: string, - user: IUser | undefined - ): Promise { + async deleteEstimate(workspaceSlug: string, projectId: string, estimateId: string): Promise { return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/`) - .then((response) => { - trackEventService.trackIssueEstimateEvent(response?.data, "ESTIMATE_DELETE", user as IUser); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); diff --git a/web/services/project/project-export.service.ts b/web/services/project/project-export.service.ts index 64e42925c..b5503a829 100644 --- a/web/services/project/project-export.service.ts +++ b/web/services/project/project-export.service.ts @@ -1,12 +1,7 @@ import { APIService } from "services/api.service"; -import { TrackEventService } from "services/track_event.service"; -// types -import { IUser } from "types"; // helpers import { API_BASE_URL } from "helpers/common.helper"; -const trackEventService = new TrackEventService(); - export class ProjectExportService extends APIService { constructor() { super(API_BASE_URL); @@ -17,20 +12,10 @@ export class ProjectExportService extends APIService { data: { provider: string; project: string[]; - }, - user: IUser + } ): Promise { return this.post(`/api/workspaces/${workspaceSlug}/export-issues/`, data) - .then((response) => { - trackEventService.trackExporterEvent( - { - workspaceSlug, - }, - "CSV_EXPORTER_CREATE", - user - ); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); diff --git a/web/services/project/project-member.service.ts b/web/services/project/project-member.service.ts index 35c7a620a..5f3e4710a 100644 --- a/web/services/project/project-member.service.ts +++ b/web/services/project/project-member.service.ts @@ -1,11 +1,8 @@ import { API_BASE_URL } from "helpers/common.helper"; // services import { APIService } from "services/api.service"; -import { TrackEventService } from "services/track_event.service"; // types -import type { IUser, IProjectBulkAddFormData, IProjectMember, IProjectMemberInvitation } from "types"; - -const trackEventService = new TrackEventService(); +import type { IProjectBulkAddFormData, IProjectMember, IProjectMemberInvitation } from "types"; export class ProjectMemberService extends APIService { constructor() { @@ -20,27 +17,9 @@ export class ProjectMemberService extends APIService { }); } - async bulkAddMembersToProject( - workspaceSlug: string, - projectId: string, - data: IProjectBulkAddFormData, - user: IUser | undefined - ): Promise { + async bulkAddMembersToProject(workspaceSlug: string, projectId: string, data: IProjectBulkAddFormData): Promise { return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/members/`, data) - .then((response) => { - trackEventService.trackProjectEvent( - { - workspaceId: response?.data?.workspace?.id, - workspaceSlug, - projectId, - projectName: response?.data?.project?.name, - memberEmail: response?.data?.member?.email, - }, - "PROJECT_MEMBER_INVITE", - user as IUser - ); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); diff --git a/web/services/project/project-state.service.ts b/web/services/project/project-state.service.ts index 44b649327..214dcc948 100644 --- a/web/services/project/project-state.service.ts +++ b/web/services/project/project-state.service.ts @@ -1,24 +1,18 @@ // services import { APIService } from "services/api.service"; -import { TrackEventService } from "services/track_event.service"; // helpers import { API_BASE_URL } from "helpers/common.helper"; // types -import type { IUser, IState } from "types"; - -const trackEventService = new TrackEventService(); +import type { IState } from "types"; export class ProjectStateService extends APIService { constructor() { super(API_BASE_URL); } - async createState(workspaceSlug: string, projectId: string, data: any, user: IUser | undefined): Promise { + async createState(workspaceSlug: string, projectId: string, data: any): Promise { return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/states/`, data) - .then((response) => { - trackEventService.trackStateEvent(response?.data, "STATE_CREATE", user as IUser); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response; }); @@ -48,46 +42,25 @@ export class ProjectStateService extends APIService { }); } - async updateState( - workspaceSlug: string, - projectId: string, - stateId: string, - data: IState, - user: IUser | undefined - ): Promise { + async updateState(workspaceSlug: string, projectId: string, stateId: string, data: IState): Promise { return this.put(`/api/workspaces/${workspaceSlug}/projects/${projectId}/states/${stateId}/`, data) - .then((response) => { - trackEventService.trackStateEvent(response?.data, "STATE_UPDATE", user as IUser); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response; }); } - async patchState( - workspaceSlug: string, - projectId: string, - stateId: string, - data: Partial, - user: IUser | undefined - ): Promise { + async patchState(workspaceSlug: string, projectId: string, stateId: string, data: Partial): Promise { return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/states/${stateId}/`, data) - .then((response) => { - trackEventService.trackStateEvent(response?.data, "STATE_UPDATE", user as IUser); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); } - async deleteState(workspaceSlug: string, projectId: string, stateId: string, user: IUser | undefined): Promise { + async deleteState(workspaceSlug: string, projectId: string, stateId: string): Promise { return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/states/${stateId}/`) - .then((response) => { - trackEventService.trackStateEvent(response?.data, "STATE_DELETE", user as IUser); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response; }); diff --git a/web/services/project/project.service.ts b/web/services/project/project.service.ts index 74d678d23..e4f1149f2 100644 --- a/web/services/project/project.service.ts +++ b/web/services/project/project.service.ts @@ -1,7 +1,6 @@ import { API_BASE_URL } from "helpers/common.helper"; // services import { APIService } from "services/api.service"; -import { TrackEventService } from "services/track_event.service"; // types import type { GithubRepositoriesResponse, @@ -12,19 +11,14 @@ import type { TProjectIssuesSearchParams, } from "types"; -const trackEventService = new TrackEventService(); - export class ProjectService extends APIService { constructor() { super(API_BASE_URL); } - async createProject(workspaceSlug: string, data: Partial, user: any): Promise { + async createProject(workspaceSlug: string, data: Partial): Promise { return this.post(`/api/workspaces/${workspaceSlug}/projects/`, data) - .then((response) => { - trackEventService.trackProjectEvent(response.data, "CREATE_PROJECT", user); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response; }); @@ -58,23 +52,17 @@ export class ProjectService extends APIService { }); } - async updateProject(workspaceSlug: string, projectId: string, data: Partial, user: any): Promise { + async updateProject(workspaceSlug: string, projectId: string, data: Partial): Promise { return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/`, data) - .then((response) => { - trackEventService.trackProjectEvent(response.data, "UPDATE_PROJECT", user); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); } - async deleteProject(workspaceSlug: string, projectId: string, user: any | undefined): Promise { + async deleteProject(workspaceSlug: string, projectId: string): Promise { return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/`) - .then((response) => { - trackEventService.trackProjectEvent({ projectId }, "DELETE_PROJECT", user); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); @@ -88,20 +76,9 @@ export class ProjectService extends APIService { }); } - async leaveProject(workspaceSlug: string, projectId: string, user: any): Promise { + async leaveProject(workspaceSlug: string, projectId: string): Promise { return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/members/leave/`) - .then((response) => { - trackEventService.trackProjectEvent( - "PROJECT_MEMBER_LEAVE", - { - workspaceSlug, - projectId, - ...response?.data, - }, - user - ); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); diff --git a/web/services/track_event.service.ts b/web/services/track_event.service.ts deleted file mode 100644 index d3a2bd743..000000000 --- a/web/services/track_event.service.ts +++ /dev/null @@ -1,850 +0,0 @@ -// services -import { APIService } from "services/api.service"; - -const trackEvent = process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1"; - -// types -import type { - IUser, - ICycle, - IEstimate, - IGptResponse, - IIssue, - IIssueComment, - IModule, - IPage, - IPageBlock, - IProject, - IState, - IProjectView, - IWorkspace, - IssueCommentReaction, - IssueReaction, -} from "types"; - -type WorkspaceEventType = - | "CREATE_WORKSPACE" - | "UPDATE_WORKSPACE" - | "DELETE_WORKSPACE" - | "WORKSPACE_USER_INVITE" - | "WORKSPACE_USER_INVITE_ACCEPT" - | "WORKSPACE_USER_BULK_INVITE_ACCEPT"; - -type ProjectEventType = - | "CREATE_PROJECT" - | "UPDATE_PROJECT" - | "DELETE_PROJECT" - | "PROJECT_MEMBER_INVITE" - | "PROJECT_MEMBER_LEAVE"; - -type IssueEventType = "ISSUE_CREATE" | "ISSUE_UPDATE" | "ISSUE_DELETE"; - -type CycleEventType = "CYCLE_CREATE" | "CYCLE_UPDATE" | "CYCLE_DELETE"; - -type StateEventType = "STATE_CREATE" | "STATE_UPDATE" | "STATE_DELETE"; - -type ModuleEventType = "MODULE_CREATE" | "MODULE_UPDATE" | "MODULE_DELETE"; - -type PagesEventType = "PAGE_CREATE" | "PAGE_UPDATE" | "PAGE_DELETE"; - -type ViewEventType = "VIEW_CREATE" | "VIEW_UPDATE" | "VIEW_DELETE"; - -type IssueCommentEventType = "ISSUE_COMMENT_CREATE" | "ISSUE_COMMENT_UPDATE" | "ISSUE_COMMENT_DELETE"; - -type Toggle = "TOGGLE_CYCLE" | "TOGGLE_MODULE" | "TOGGLE_VIEW" | "TOGGLE_PAGES" | "TOGGLE_STATE" | "TOGGLE_INBOX"; - -export type MiscellaneousEventType = `${Toggle}_ON` | `${Toggle}_OFF`; - -type IntegrationEventType = "ADD_WORKSPACE_INTEGRATION" | "REMOVE_WORKSPACE_INTEGRATION"; - -type GitHubSyncEventType = "GITHUB_REPO_SYNC"; - -type PageBlocksEventType = - | "PAGE_BLOCK_CREATE" - | "PAGE_BLOCK_UPDATE" - | "PAGE_BLOCK_DELETE" - | "PAGE_BLOCK_CONVERTED_TO_ISSUE"; - -type IssueLabelEventType = "ISSUE_LABEL_CREATE" | "ISSUE_LABEL_UPDATE" | "ISSUE_LABEL_DELETE"; - -type GptEventType = "ASK_GPT" | "USE_GPT_RESPONSE_IN_ISSUE" | "USE_GPT_RESPONSE_IN_PAGE_BLOCK"; - -type IssueEstimateEventType = "ESTIMATE_CREATE" | "ESTIMATE_UPDATE" | "ESTIMATE_DELETE"; - -type InboxEventType = - | "INBOX_CREATE" - | "INBOX_UPDATE" - | "INBOX_DELETE" - | "INBOX_ISSUE_CREATE" - | "INBOX_ISSUE_UPDATE" - | "INBOX_ISSUE_DELETE" - | "INBOX_ISSUE_DUPLICATED" - | "INBOX_ISSUE_ACCEPTED" - | "INBOX_ISSUE_SNOOZED" - | "INBOX_ISSUE_REJECTED"; - -type ImporterEventType = - | "GITHUB_IMPORTER_CREATE" - | "GITHUB_IMPORTER_DELETE" - | "JIRA_IMPORTER_CREATE" - | "JIRA_IMPORTER_DELETE"; - -type ExporterEventType = "CSV_EXPORTER_CREATE"; - -type AnalyticsEventType = - | "WORKSPACE_SCOPE_AND_DEMAND_ANALYTICS" - | "WORKSPACE_CUSTOM_ANALYTICS" - | "WORKSPACE_ANALYTICS_EXPORT" - | "PROJECT_SCOPE_AND_DEMAND_ANALYTICS" - | "PROJECT_CUSTOM_ANALYTICS" - | "PROJECT_ANALYTICS_EXPORT" - | "CYCLE_SCOPE_AND_DEMAND_ANALYTICS" - | "CYCLE_CUSTOM_ANALYTICS" - | "CYCLE_ANALYTICS_EXPORT" - | "MODULE_SCOPE_AND_DEMAND_ANALYTICS" - | "MODULE_CUSTOM_ANALYTICS" - | "MODULE_ANALYTICS_EXPORT"; - -type ReactionEventType = - | "ISSUE_REACTION_CREATE" - | "ISSUE_COMMENT_REACTION_CREATE" - | "ISSUE_REACTION_DELETE" - | "ISSUE_COMMENT_REACTION_DELETE"; - -export class TrackEventService extends APIService { - constructor() { - super("/"); - } - - async trackWorkspaceEvent(data: IWorkspace | any, eventName: WorkspaceEventType, user: IUser): Promise { - if (!trackEvent) return; - - let payload: any; - if ( - eventName !== "DELETE_WORKSPACE" && - eventName !== "WORKSPACE_USER_INVITE" && - eventName !== "WORKSPACE_USER_INVITE_ACCEPT" && - eventName !== "WORKSPACE_USER_BULK_INVITE_ACCEPT" - ) - payload = { - workspaceId: data.id, - workspaceSlug: data.slug, - workspaceName: data.name, - }; - else payload = data; - - return this.request({ - url: "/api/track-event", - method: "POST", - data: { - eventName, - extra: { - ...payload, - }, - user: user, - }, - }); - } - - async trackProjectEvent(data: Partial | any, eventName: ProjectEventType, user: IUser): Promise { - if (!trackEvent) return; - - let payload: any; - if (eventName !== "DELETE_PROJECT" && eventName !== "PROJECT_MEMBER_INVITE" && eventName !== "PROJECT_MEMBER_LEAVE") - payload = { - workspaceId: data?.workspace_detail?.id, - workspaceName: data?.workspace_detail?.name, - workspaceSlug: data?.workspace_detail?.slug, - projectId: data?.id, - projectName: data?.name, - }; - else payload = data; - - return this.request({ - url: "/api/track-event", - method: "POST", - data: { - eventName, - extra: { - ...payload, - }, - user: user, - }, - }); - } - - async trackUserOnboardingCompleteEvent(data: any, user: IUser): Promise { - if (!trackEvent) return; - - return this.request({ - url: "/api/track-event", - method: "POST", - data: { - eventName: "USER_ONBOARDING_COMPLETE", - extra: { - ...data, - }, - user: user, - }, - }); - } - - async trackUserTourCompleteEvent(data: any, user: IUser): Promise { - if (!trackEvent) return; - - return this.request({ - url: "/api/track-event", - method: "POST", - data: { - eventName: "USER_TOUR_COMPLETE", - extra: { - ...data, - }, - user: user, - }, - }); - } - - async trackIssueEvent(data: IIssue | any, eventName: IssueEventType, user: IUser): Promise { - if (!trackEvent) return; - - let payload: any; - if (eventName !== "ISSUE_DELETE") - payload = { - workspaceId: data?.workspace_detail?.id, - workspaceName: data?.workspace_detail?.name, - workspaceSlug: data?.workspace_detail?.slug, - projectId: data?.project_detail?.id, - projectName: data?.project_detail?.name, - projectIdentifier: data?.project_detail?.identifier, - issueId: data?.id, - }; - else payload = data; - - return this.request({ - url: "/api/track-event", - method: "POST", - data: { - eventName, - extra: { - ...payload, - }, - user: user, - }, - }); - } - - async trackIssueMarkedAsDoneEvent(data: any, user: IUser): Promise { - if (!trackEvent) return; - return this.request({ - url: "/api/track-event", - method: "POST", - data: { - eventName: "ISSUES_MARKED_AS_DONE", - extra: { - ...data, - }, - user: user, - }, - }); - } - - async trackIssuePartialPropertyUpdateEvent( - data: any, - propertyName: - | "ISSUE_PROPERTY_UPDATE_PRIORITY" - | "ISSUE_PROPERTY_UPDATE_STATE" - | "ISSUE_PROPERTY_UPDATE_ASSIGNEE" - | "ISSUE_PROPERTY_UPDATE_DUE_DATE" - | "ISSUE_PROPERTY_UPDATE_ESTIMATE", - user: IUser - ): Promise { - if (!trackEvent) return; - return this.request({ - url: "/api/track-event", - method: "POST", - data: { - eventName: propertyName, - extra: { - ...data, - }, - user: user, - }, - }); - } - - async trackIssueCommentEvent( - data: Partial | any, - eventName: IssueCommentEventType, - user: IUser | undefined - ): Promise { - if (!trackEvent) return; - - let payload: any; - if (eventName !== "ISSUE_COMMENT_DELETE") - payload = { - workspaceId: data?.workspace_detail?.id, - workspaceName: data?.workspace_detail?.name, - workspaceSlug: data?.workspace_detail?.slug, - projectId: data?.project_detail?.id, - projectName: data?.project_detail?.name, - projectIdentifier: data?.project_detail?.identifier, - issueId: data?.issue, - }; - else payload = data; - return this.request({ - url: "/api/track-event", - method: "POST", - data: { - eventName, - extra: { - ...payload, - }, - user: user, - }, - }); - } - - async trackIssueRelationEvent( - data: any, - eventName: "ISSUE_RELATION_CREATE" | "ISSUE_RELATION_DELETE", - user: IUser - ): Promise { - if (!trackEvent) return; - - return this.request({ - url: "/api/track-event", - method: "POST", - data: { - eventName, - extra: data, - user: user, - }, - }); - } - - async trackIssueMovedToCycleOrModuleEvent( - data: any, - eventName: - | "ISSUE_MOVED_TO_CYCLE" - | "ISSUE_MOVED_TO_MODULE" - | "ISSUE_MOVED_TO_CYCLE_IN_BULK" - | "ISSUE_MOVED_TO_MODULE_IN_BULK", - user: IUser - ): Promise { - if (!trackEvent) return; - - return this.request({ - url: "/api/track-event", - method: "POST", - data: { - eventName, - extra: { - ...data, - }, - user: user, - }, - }); - } - - async trackIssueBulkDeleteEvent(data: any, user: IUser): Promise { - if (!trackEvent) return; - - return this.request({ - url: "/api/track-event", - method: "POST", - data: { - eventName: "ISSUE_BULK_DELETE", - extra: { - ...data, - }, - user: user, - }, - }); - } - - async trackIssueLabelEvent(data: any, eventName: IssueLabelEventType, user: IUser): Promise { - if (!trackEvent) return; - - return this.request({ - url: "/api/track-event", - method: "POST", - data: { - eventName, - extra: { - ...data, - }, - user: user, - }, - }); - } - - async trackStateEvent(data: IState | any, eventName: StateEventType, user: IUser): Promise { - if (!trackEvent) return; - - let payload: any; - if (eventName !== "STATE_DELETE") - payload = { - workspaceId: data?.workspace_detail?.id, - workspaceName: data?.workspace_detail?.name, - workspaceSlug: data?.workspace_detail?.slug, - projectId: data?.project_detail?.id, - projectName: data?.project_detail?.name, - projectIdentifier: data?.project_detail?.identifier, - stateId: data.id, - }; - else payload = data; - - return this.request({ - url: "/api/track-event", - method: "POST", - data: { - eventName, - extra: { - ...payload, - }, - user: user, - }, - }); - } - - async trackCycleEvent(data: ICycle | any, eventName: CycleEventType, user: IUser): Promise { - if (!trackEvent) return; - - let payload: any; - if (eventName !== "CYCLE_DELETE") - payload = { - workspaceId: data?.workspace_detail?.id, - workspaceName: data?.workspace_detail?.name, - workspaceSlug: data?.workspace_detail?.slug, - projectId: data?.project_detail?.id, - projectName: data?.project_detail?.name, - projectIdentifier: data?.project_detail?.identifier, - cycleId: data.id, - }; - else payload = data; - - return this.request({ - url: "/api/track-event", - method: "POST", - data: { - eventName, - extra: { - ...payload, - }, - user: user, - }, - }); - } - - async trackModuleEvent(data: IModule | any, eventName: ModuleEventType, user: IUser): Promise { - if (!trackEvent) return; - - let payload: any; - if (eventName !== "MODULE_DELETE") - payload = { - workspaceId: data?.workspace_detail?.id, - workspaceName: data?.workspace_detail?.name, - workspaceSlug: data?.workspace_detail?.slug, - projectId: data?.project_detail?.id, - projectName: data.project_detail?.name, - projectIdentifier: data?.project_detail?.identifier, - moduleId: data.id, - }; - else payload = data; - - return this.request({ - url: "/api/track-event", - method: "POST", - data: { - eventName, - extra: { - ...payload, - }, - user: user, - }, - }); - } - - async trackPageEvent(data: Partial | any, eventName: PagesEventType, user: IUser | undefined): Promise { - if (!trackEvent) return; - - let payload: any; - if (eventName !== "PAGE_DELETE") - payload = { - workspaceId: data?.workspace_detail?.id, - workspaceName: data?.workspace_detail?.name, - workspaceSlug: data?.workspace_detail?.slug, - projectId: data?.project_detail?.id, - projectName: data?.project_detail?.name, - projectIdentifier: data?.project_detail?.identifier, - pageId: data.id, - }; - else payload = data; - - return this.request({ - url: "/api/track-event", - method: "POST", - data: { - eventName, - extra: { - ...payload, - }, - user: user, - }, - }); - } - - async trackPageBlockEvent( - data: Partial | IIssue, - eventName: PageBlocksEventType, - user: IUser - ): Promise { - if (!trackEvent) return; - - let payload: any; - if (eventName !== "PAGE_BLOCK_DELETE" && eventName !== "PAGE_BLOCK_CONVERTED_TO_ISSUE") - payload = { - workspaceId: data?.workspace_detail?.id, - workspaceName: data?.workspace_detail?.name, - workspaceSlug: data?.workspace_detail?.slug, - projectId: data?.project_detail?.id, - projectName: data?.project_detail?.name, - projectIdentifier: data?.project_detail?.identifier, - pageId: (data as IPageBlock)?.page, - pageBlockId: data.id, - }; - else if (eventName === "PAGE_BLOCK_CONVERTED_TO_ISSUE") { - payload = { - workspaceId: data?.workspace_detail?.id, - workspaceName: data?.workspace_detail?.name, - workspaceSlug: data?.workspace_detail?.slug, - projectId: data?.project_detail?.id, - projectName: data?.project_detail?.name, - projectIdentifier: data?.project_detail?.identifier, - issueId: data?.id, - }; - } else payload = data; - - return this.request({ - url: "/api/track-event", - method: "POST", - data: { - eventName, - extra: { - ...payload, - }, - user: user, - }, - }); - } - - async trackAskGptEvent(data: IGptResponse, eventName: GptEventType, user: IUser): Promise { - if (!trackEvent) return; - - const payload = { - workspaceId: data?.workspace_detail?.id, - workspaceName: data?.workspace_detail?.name, - workspaceSlug: data?.workspace_detail?.slug, - projectId: data?.project_detail?.id, - projectIdentifier: data?.project_detail?.identifier, - projectName: data?.project_detail?.name, - count: data?.count, - }; - return this.request({ - url: "/api/track-event", - method: "POST", - data: { - eventName, - extra: { - ...payload, - }, - user: user, - }, - }); - } - - async trackUseGPTResponseEvent(data: IIssue | IPageBlock, eventName: GptEventType, user: IUser): Promise { - if (!trackEvent) return; - - let payload: any; - - if (eventName === "USE_GPT_RESPONSE_IN_ISSUE") { - payload = { - workspaceId: data?.workspace_detail?.id, - workspaceName: data?.workspace_detail?.name, - workspaceSlug: data?.workspace_detail?.slug, - projectId: data?.project_detail?.id, - projectIdentifier: data?.project_detail?.identifier, - projectName: data?.project_detail?.name, - issueId: data.id, - }; - } else if (eventName === "USE_GPT_RESPONSE_IN_PAGE_BLOCK") { - payload = { - workspaceId: data?.workspace_detail?.id, - workspaceName: data?.workspace_detail?.name, - workspaceSlug: data?.workspace_detail?.slug, - projectId: data?.project_detail?.id, - projectIdentifier: data?.project_detail?.identifier, - projectName: data?.project_detail?.name, - pageId: (data as IPageBlock)?.page, - pageBlockId: data.id, - }; - } - - return this.request({ - url: "/api/track-event", - method: "POST", - data: { - eventName, - extra: { - ...payload, - }, - user: user, - }, - }); - } - - async trackViewEvent(data: IProjectView, eventName: ViewEventType, user: IUser): Promise { - if (!trackEvent) return; - - let payload: any; - if (eventName === "VIEW_DELETE") payload = data; - else - payload = { - labels: Boolean(data.query_data.labels), - assignees: Boolean(data.query_data.assignees), - priority: Boolean(data.query_data.priority), - state: Boolean(data.query_data.state), - created_by: Boolean(data.query_data.created_by), - }; - - return this.request({ - url: "/api/track-event", - method: "POST", - data: { - eventName, - extra: { - ...payload, - }, - user: user, - }, - }); - } - - async trackMiscellaneousEvent(data: any, eventName: MiscellaneousEventType, user: IUser | undefined): Promise { - if (!trackEvent || !user) return; - - return this.request({ - url: "/api/track-event", - method: "POST", - data: { - eventName, - extra: { - ...data, - }, - user: user, - }, - }); - } - - async trackAppIntegrationEvent(data: any, eventName: IntegrationEventType, user: IUser): Promise { - if (!trackEvent) return; - - return this.request({ - url: "/api/track-event", - method: "POST", - data: { - eventName, - extra: { - ...data, - }, - user: user, - }, - }); - } - - async trackGitHubSyncEvent(data: any, eventName: GitHubSyncEventType, user: IUser): Promise { - if (!trackEvent) return; - - return this.request({ - url: "/api/track-event", - method: "POST", - data: { - eventName, - extra: { - ...data, - }, - user: user, - }, - }); - } - - async trackIssueEstimateEvent( - data: { estimate: IEstimate }, - eventName: IssueEstimateEventType, - user: IUser - ): Promise { - if (!trackEvent) return; - - let payload: any; - if (eventName === "ESTIMATE_DELETE") payload = data; - else - payload = { - workspaceId: data?.estimate?.workspace_detail?.id, - workspaceName: data?.estimate?.workspace_detail?.name, - workspaceSlug: data?.estimate?.workspace_detail?.slug, - projectId: data?.estimate?.project_detail?.id, - projectName: data?.estimate?.project_detail?.name, - projectIdentifier: data?.estimate?.project_detail?.identifier, - estimateId: data.estimate?.id, - }; - - return this.request({ - url: "/api/track-event", - method: "POST", - data: { - eventName, - extra: { - ...payload, - }, - user: user, - }, - }); - } - - async trackImporterEvent(data: any, eventName: ImporterEventType, user: IUser | undefined): Promise { - if (!trackEvent) return; - - let payload: any; - if (eventName === "GITHUB_IMPORTER_DELETE" || eventName === "JIRA_IMPORTER_DELETE") payload = data; - else - payload = { - workspaceId: data?.workspace_detail?.id, - workspaceName: data?.workspace_detail?.name, - workspaceSlug: data?.workspace_detail?.slug, - projectId: data?.project_detail?.id, - projectName: data?.project_detail?.name, - projectIdentifier: data?.project_detail?.identifier, - }; - - return this.request({ - url: "/api/track-event", - method: "POST", - data: { - eventName, - extra: { - ...payload, - }, - user: user, - }, - }); - } - - async trackAnalyticsEvent(data: any, eventName: AnalyticsEventType, user: IUser): Promise { - if (!trackEvent) return; - - const payload = { ...data }; - - return this.request({ - url: "/api/track-event", - method: "POST", - data: { - eventName, - extra: payload, - user: user, - }, - }); - } - - async trackExporterEvent(data: any, eventName: ExporterEventType, user: IUser): Promise { - if (!trackEvent) return; - - const payload = { ...data }; - - return this.request({ - url: "/api/track-event", - method: "POST", - data: { - eventName, - extra: { - ...payload, - }, - user: user, - }, - }); - } - - // TODO: add types to the data - async trackInboxEvent(data: any, eventName: InboxEventType, user: IUser): Promise { - if (!trackEvent) return; - - let payload: any; - if (eventName !== "INBOX_DELETE") - payload = { - issue: data?.issue?.id, - inbox: data?.id, - workspaceId: data?.issue?.workspace_detail?.id, - workspaceName: data?.issue?.workspace_detail?.name, - workspaceSlug: data?.issue?.workspace_detail?.slug, - projectId: data?.issue?.project_detail?.id, - projectName: data?.issue?.project_detail?.name, - }; - else payload = data; - - return this.request({ - url: "/api/track-event", - method: "POST", - data: { - eventName, - extra: { - ...payload, - }, - user: user, - }, - }); - } - - async trackReactionEvent( - data: IssueReaction | IssueCommentReaction, - eventName: ReactionEventType, - user: IUser - ): Promise { - if (!trackEvent) return; - - let payload: any; - if (eventName === "ISSUE_REACTION_DELETE" || eventName === "ISSUE_COMMENT_REACTION_DELETE") payload = data; - else - payload = { - workspaceId: data?.workspace, - projectId: data?.project, - reaction: data?.reaction, - }; - - return this.request({ - url: "/api/track-event", - method: "POST", - data: { - eventName, - extra: payload, - user: user, - }, - }); - } - - async trackProjectPublishSettingsEvent(data: any, eventName: string, user: IUser): Promise { - if (!trackEvent) return; - - const payload: any = data; - - return this.request({ - url: "/api/track-event", - method: "POST", - data: { - eventName, - extra: payload, - user: user, - }, - }); - } -} diff --git a/web/services/user.service.ts b/web/services/user.service.ts index a2cd74697..1d9d1ce94 100644 --- a/web/services/user.service.ts +++ b/web/services/user.service.ts @@ -1,6 +1,5 @@ // services import { APIService } from "services/api.service"; -import { TrackEventService } from "services/track_event.service"; // types import type { IIssue, @@ -15,8 +14,6 @@ import type { // helpers import { API_BASE_URL } from "helpers/common.helper"; -const trackEventService = new TrackEventService(); - export class UserService extends APIService { constructor() { super(API_BASE_URL); @@ -79,32 +76,21 @@ export class UserService extends APIService { }); } - async updateUserOnBoard({ userRole }: any, user: IUser | undefined): Promise { + async updateUserOnBoard(): Promise { return this.patch("/api/users/me/onboard/", { is_onboarded: true, }) - .then((response) => { - trackEventService.trackUserOnboardingCompleteEvent( - { - user_role: userRole ?? "None", - }, - user as IUser - ); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); } - async updateUserTourCompleted(user: IUser): Promise { + async updateUserTourCompleted(): Promise { return this.patch("/api/users/me/tour-completed/", { is_tour_completed: true, }) - .then((response) => { - trackEventService.trackUserTourCompleteEvent({ user_role: user.role ?? "None" }, user); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); diff --git a/web/services/view.service.ts b/web/services/view.service.ts index 97f0db9a2..e4e28adf9 100644 --- a/web/services/view.service.ts +++ b/web/services/view.service.ts @@ -1,51 +1,33 @@ import { APIService } from "services/api.service"; -import { TrackEventService } from "services/track_event.service"; // types import { IProjectView } from "types/views"; // helpers import { API_BASE_URL } from "helpers/common.helper"; -const trackEventService = new TrackEventService(); - export class ViewService extends APIService { constructor() { super(API_BASE_URL); } - async createView(workspaceSlug: string, projectId: string, data: Partial, user: any): Promise { + async createView(workspaceSlug: string, projectId: string, data: Partial): Promise { return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/views/`, data) - .then((response) => { - trackEventService.trackViewEvent(response?.data, "VIEW_CREATE", user); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); } - async patchView( - workspaceSlug: string, - projectId: string, - viewId: string, - data: Partial, - user: any - ): Promise { + async patchView(workspaceSlug: string, projectId: string, viewId: string, data: Partial): Promise { return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/views/${viewId}/`, data) - .then((response) => { - trackEventService.trackViewEvent(response?.data, "VIEW_UPDATE", user); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); } - async deleteView(workspaceSlug: string, projectId: string, viewId: string, user: any): Promise { + async deleteView(workspaceSlug: string, projectId: string, viewId: string): Promise { return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/views/${viewId}/`) - .then((response) => { - trackEventService.trackViewEvent(response?.data, "VIEW_DELETE", user); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); diff --git a/web/services/workspace.service.ts b/web/services/workspace.service.ts index 98d85ec8a..1e5b18f5a 100644 --- a/web/services/workspace.service.ts +++ b/web/services/workspace.service.ts @@ -1,6 +1,5 @@ // services import { APIService } from "services/api.service"; -import { TrackEventService } from "services/track_event.service"; // helpers import { API_BASE_URL } from "helpers/common.helper"; // types @@ -12,7 +11,6 @@ import { ILastActiveWorkspaceDetails, IWorkspaceSearchResults, IProductUpdateResponse, - IUser, IWorkspaceBulkInviteFormData, IWorkspaceViewProps, } from "types"; @@ -20,8 +18,6 @@ import { IWorkspaceView } from "types/workspace-views"; // store import { IIssueGroupWithSubGroupsStructure, IIssueGroupedStructure, IIssueUnGroupedStructure } from "store/issue"; -const trackEventService = new TrackEventService(); - export class WorkspaceService extends APIService { constructor() { super(API_BASE_URL); @@ -43,66 +39,43 @@ export class WorkspaceService extends APIService { }); } - async createWorkspace(data: Partial, user: IUser | undefined): Promise { + async createWorkspace(data: Partial): Promise { return this.post("/api/workspaces/", data) - .then((response) => { - trackEventService.trackWorkspaceEvent(response.data, "CREATE_WORKSPACE", user as IUser); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); } - async updateWorkspace( - workspaceSlug: string, - data: Partial, - user: IUser | undefined - ): Promise { + async updateWorkspace(workspaceSlug: string, data: Partial): Promise { return this.patch(`/api/workspaces/${workspaceSlug}/`, data) - .then((response) => { - trackEventService.trackWorkspaceEvent(response.data, "UPDATE_WORKSPACE", user as IUser); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); } - async deleteWorkspace(workspaceSlug: string, user: IUser | undefined): Promise { + async deleteWorkspace(workspaceSlug: string): Promise { return this.delete(`/api/workspaces/${workspaceSlug}/`) - .then((response) => { - trackEventService.trackWorkspaceEvent({ workspaceSlug }, "DELETE_WORKSPACE", user as IUser); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); } - async inviteWorkspace( - workspaceSlug: string, - data: IWorkspaceBulkInviteFormData, - user: IUser | undefined - ): Promise { + async inviteWorkspace(workspaceSlug: string, data: IWorkspaceBulkInviteFormData): Promise { return this.post(`/api/workspaces/${workspaceSlug}/invite/`, data) - .then((response) => { - trackEventService.trackWorkspaceEvent(response.data, "WORKSPACE_USER_INVITE", user as IUser); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); } - async joinWorkspace(workspaceSlug: string, invitationId: string, data: any, user: IUser | undefined): Promise { + async joinWorkspace(workspaceSlug: string, invitationId: string, data: any): Promise { return this.post(`/api/workspaces/${workspaceSlug}/invitations/${invitationId}/join/`, data, { headers: {}, }) - .then((response) => { - trackEventService.trackWorkspaceEvent(response.data, "WORKSPACE_USER_INVITE_ACCEPT", user as IUser); - return response?.data; - }) + .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); diff --git a/web/store/cycle/cycle_issue.store.ts b/web/store/cycle/cycle_issue.store.ts index 17d8f351c..7e2db12b5 100644 --- a/web/store/cycle/cycle_issue.store.ts +++ b/web/store/cycle/cycle_issue.store.ts @@ -324,17 +324,9 @@ export class CycleIssueStore implements ICycleIssueStore { addIssueToCycle = async (workspaceSlug: string, projectId: string, cycleId: string, issueIds: string[]) => { try { - const user = this.rootStore.user.currentUser ?? undefined; - - await this.issueService.addIssueToCycle( - workspaceSlug, - projectId, - cycleId, - { - issues: issueIds, - }, - user - ); + await this.issueService.addIssueToCycle(workspaceSlug, projectId, cycleId, { + issues: issueIds, + }); this.fetchIssues(workspaceSlug, projectId, cycleId); } catch (error) { diff --git a/web/store/cycle/cycle_issue_filters.store.ts b/web/store/cycle/cycle_issue_filters.store.ts index 95fad6d98..5b8f6a536 100644 --- a/web/store/cycle/cycle_issue_filters.store.ts +++ b/web/store/cycle/cycle_issue_filters.store.ts @@ -133,9 +133,7 @@ export class CycleIssueFilterStore implements ICycleIssueFilterStore { }, }; - const user = this.rootStore.user.currentUser ?? undefined; - - await this.cycleService.patchCycle(workspaceSlug, projectId, cycleId, payload, user); + await this.cycleService.patchCycle(workspaceSlug, projectId, cycleId, payload); } catch (error) { this.fetchCycleFilters(workspaceSlug, projectId, cycleId); diff --git a/web/store/cycle/cycles.store.ts b/web/store/cycle/cycles.store.ts index 67e3fc2ca..048326841 100644 --- a/web/store/cycle/cycles.store.ts +++ b/web/store/cycle/cycles.store.ts @@ -248,12 +248,7 @@ export class CycleStore implements ICycleStore { createCycle = async (workspaceSlug: string, projectId: string, data: Partial) => { try { - const response = await this.cycleService.createCycle( - workspaceSlug, - projectId, - data, - this.rootStore.user.currentUser - ); + const response = await this.cycleService.createCycle(workspaceSlug, projectId, data); runInAction(() => { this.cycle_details = { @@ -274,7 +269,7 @@ export class CycleStore implements ICycleStore { patchCycle = async (workspaceSlug: string, projectId: string, cycleId: string, data: Partial) => { try { - const _response = await this.cycleService.patchCycle(workspaceSlug, projectId, cycleId, data, undefined); + const _response = await this.cycleService.patchCycle(workspaceSlug, projectId, cycleId, data); const _cycleDetails = { ...this.cycle_details, @@ -297,7 +292,7 @@ export class CycleStore implements ICycleStore { removeCycle = async (workspaceSlug: string, projectId: string, cycleId: string) => { try { - const _response = await this.cycleService.deleteCycle(workspaceSlug, projectId, cycleId, undefined); + const _response = await this.cycleService.deleteCycle(workspaceSlug, projectId, cycleId); const _currentView = this.cycleView === "active" ? "current" : this.cycleView; this.fetchCycles(workspaceSlug, projectId, _currentView); diff --git a/web/store/inbox/inbox_issue_detail.store.ts b/web/store/inbox/inbox_issue_detail.store.ts index 9085264f5..25653ab09 100644 --- a/web/store/inbox/inbox_issue_detail.store.ts +++ b/web/store/inbox/inbox_issue_detail.store.ts @@ -121,13 +121,7 @@ export class InboxIssueDetailsStore implements IInboxIssueDetailsStore { }; try { - const response = await this.inboxService.createInboxIssue( - workspaceSlug, - projectId, - inboxId, - payload, - this.rootStore.user.currentUser ?? undefined - ); + const response = await this.inboxService.createInboxIssue(workspaceSlug, projectId, inboxId, payload); runInAction(() => { this.issueDetails = { @@ -175,14 +169,7 @@ export class InboxIssueDetailsStore implements IInboxIssueDetailsStore { }; }); - await this.inboxService.patchInboxIssue( - workspaceSlug, - projectId, - inboxId, - issueId, - { issue: data }, - this.rootStore.user.currentUser ?? undefined - ); + await this.inboxService.patchInboxIssue(workspaceSlug, projectId, inboxId, issueId, { issue: data }); } catch (error) { runInAction(() => { this.error = error; @@ -224,14 +211,7 @@ export class InboxIssueDetailsStore implements IInboxIssueDetailsStore { }; }); - await this.inboxService.markInboxStatus( - workspaceSlug, - projectId, - inboxId, - issueId, - data, - this.rootStore.user.currentUser ?? undefined - ); + await this.inboxService.markInboxStatus(workspaceSlug, projectId, inboxId, issueId, data); } catch (error) { runInAction(() => { this.error = error; @@ -259,13 +239,7 @@ export class InboxIssueDetailsStore implements IInboxIssueDetailsStore { }; }); - await this.inboxService.deleteInboxIssue( - workspaceSlug, - projectId, - inboxId, - issueId, - this.rootStore.user.currentUser ?? undefined - ); + await this.inboxService.deleteInboxIssue(workspaceSlug, projectId, inboxId, issueId); } catch (error) { runInAction(() => { this.error = error; diff --git a/web/store/issue/issue_detail.store.ts b/web/store/issue/issue_detail.store.ts index 58d4aabe7..226a5d7d3 100644 --- a/web/store/issue/issue_detail.store.ts +++ b/web/store/issue/issue_detail.store.ts @@ -251,12 +251,7 @@ export class IssueDetailStore implements IIssueDetailStore { }); try { - const response = await this.issueService.createIssue( - workspaceSlug, - projectId, - data, - this.rootStore.user.currentUser! - ); + const response = await this.issueService.createIssue(workspaceSlug, projectId, data); runInAction(() => { this.loader = false; @@ -283,9 +278,7 @@ export class IssueDetailStore implements IIssueDetailStore { this.error = null; }); - const user = this.rootStore.user.currentUser ?? undefined; - - const response = await this.issueService.createIssue(workspaceSlug, projectId, data, user); + const response = await this.issueService.createIssue(workspaceSlug, projectId, data); runInAction(() => { this.loader = false; @@ -323,7 +316,7 @@ export class IssueDetailStore implements IIssueDetailStore { if (!user) return; - const response = await this.issueService.patchIssue(workspaceSlug, projectId, issueId, data, user); + const response = await this.issueService.patchIssue(workspaceSlug, projectId, issueId, data); runInAction(() => { this.loader = false; @@ -365,7 +358,7 @@ export class IssueDetailStore implements IIssueDetailStore { if (!user) return; - const response = await this.issueService.deleteIssue(workspaceSlug, projectId, issueId, user); + const response = await this.issueService.deleteIssue(workspaceSlug, projectId, issueId); runInAction(() => { this.loader = false; @@ -521,8 +514,7 @@ export class IssueDetailStore implements IIssueDetailStore { workspaceSlug, projectId, issueId, - data, - undefined + data ); const _issueComments = { @@ -551,8 +543,7 @@ export class IssueDetailStore implements IIssueDetailStore { projectId, issueId, commentId, - data, - undefined + data ); const _issueComments = { @@ -577,7 +568,7 @@ export class IssueDetailStore implements IIssueDetailStore { [issueId]: this.issue_comments[issueId].filter((comment: any) => comment.id != commentId), }; - await this.issueCommentService.deleteIssueComment(workspaceSlug, projectId, issueId, commentId, undefined); + await this.issueCommentService.deleteIssueComment(workspaceSlug, projectId, issueId, commentId); runInAction(() => { this.issue_comments = _issueComments; diff --git a/web/store/issue/issue_quick_add.store.ts b/web/store/issue/issue_quick_add.store.ts index 44683b578..da71ebb57 100644 --- a/web/store/issue/issue_quick_add.store.ts +++ b/web/store/issue/issue_quick_add.store.ts @@ -67,12 +67,7 @@ export class IssueQuickAddStore implements IIssueQuickAddStore { try { this.updateIssueStructure(group_id, sub_group_id, data as IIssue); - const response = await this.issueService.createIssue( - workspaceSlug, - projectId, - data, - this.rootStore.user.currentUser! - ); + const response = await this.issueService.createIssue(workspaceSlug, projectId, data); this.updateQuickAddIssueStructure(group_id, sub_group_id, { ...data, diff --git a/web/store/module/module_filters.store.ts b/web/store/module/module_filters.store.ts index ae94af59e..e205bbd30 100644 --- a/web/store/module/module_filters.store.ts +++ b/web/store/module/module_filters.store.ts @@ -152,19 +152,11 @@ export class ModuleFilterStore implements IModuleFilterStore { this.moduleFilters = newFilters; }); - const user = this.rootStore.user.currentUser ?? undefined; - - this.moduleService.patchModule( - workspaceSlug, - projectId, - moduleId, - { - view_props: { - filters: newFilters, - }, + this.moduleService.patchModule(workspaceSlug, projectId, moduleId, { + view_props: { + filters: newFilters, }, - user - ); + }); } catch (error) { this.fetchModuleFilters(workspaceSlug, projectId, moduleId); diff --git a/web/store/module/module_issue.store.ts b/web/store/module/module_issue.store.ts index 165b51b62..39311a057 100644 --- a/web/store/module/module_issue.store.ts +++ b/web/store/module/module_issue.store.ts @@ -339,17 +339,9 @@ export class ModuleIssueStore implements IModuleIssueStore { addIssueToModule = async (workspaceSlug: string, projectId: string, moduleId: string, issueIds: string[]) => { try { - const user = this.rootStore.user.currentUser ?? undefined; - - await this.moduleService.addIssuesToModule( - workspaceSlug, - projectId, - moduleId, - { - issues: issueIds, - }, - user - ); + await this.moduleService.addIssuesToModule(workspaceSlug, projectId, moduleId, { + issues: issueIds, + }); this.fetchIssues(workspaceSlug, projectId, moduleId); } catch (error) { diff --git a/web/store/module/modules.store.ts b/web/store/module/modules.store.ts index 0dc122438..59f4b08f7 100644 --- a/web/store/module/modules.store.ts +++ b/web/store/module/modules.store.ts @@ -208,12 +208,7 @@ export class ModuleStore implements IModuleStore { createModule = async (workspaceSlug: string, projectId: string, data: Partial) => { try { - const response = await this.moduleService.createModule( - workspaceSlug, - projectId, - data, - this.rootStore.user.currentUser - ); + const response = await this.moduleService.createModule(workspaceSlug, projectId, data); runInAction(() => { this.modules = { @@ -255,9 +250,7 @@ export class ModuleStore implements IModuleStore { }); }); - const user = this.rootStore.user.currentUser ?? undefined; - - const response = await this.moduleService.patchModule(workspaceSlug, projectId, moduleId, data, user); + const response = await this.moduleService.patchModule(workspaceSlug, projectId, moduleId, data); return response; } catch (error) { @@ -283,7 +276,7 @@ export class ModuleStore implements IModuleStore { }; }); - await this.moduleService.deleteModule(workspaceSlug, projectId, moduleId, this.rootStore.user.currentUser); + await this.moduleService.deleteModule(workspaceSlug, projectId, moduleId); } catch (error) { console.error("Failed to delete module in module store", error); diff --git a/web/store/project-view/project_views.store.ts b/web/store/project-view/project_views.store.ts index 76c58002d..51afee972 100644 --- a/web/store/project-view/project_views.store.ts +++ b/web/store/project-view/project_views.store.ts @@ -142,12 +142,7 @@ export class ProjectViewsStore implements IProjectViewsStore { createView = async (workspaceSlug: string, projectId: string, data: Partial): Promise => { try { - const response = await this.viewService.createView( - workspaceSlug, - projectId, - data, - this.rootStore.user.currentUser - ); + const response = await this.viewService.createView(workspaceSlug, projectId, data); runInAction(() => { this.viewsList = { @@ -191,13 +186,7 @@ export class ProjectViewsStore implements IProjectViewsStore { }; }); - const response = await this.viewService.patchView( - workspaceSlug, - projectId, - viewId, - data, - this.rootStore.user.currentUser - ); + const response = await this.viewService.patchView(workspaceSlug, projectId, viewId, data); return response; } catch (error) { @@ -220,7 +209,7 @@ export class ProjectViewsStore implements IProjectViewsStore { }; }); - await this.viewService.deleteView(workspaceSlug, projectId, viewId, this.rootStore.user.currentUser); + await this.viewService.deleteView(workspaceSlug, projectId, viewId); } catch (error) { this.fetchAllViews(workspaceSlug, projectId); diff --git a/web/store/project/project-estimates.store.ts b/web/store/project/project-estimates.store.ts index 0cb51c2dc..f634d3fd9 100644 --- a/web/store/project/project-estimates.store.ts +++ b/web/store/project/project-estimates.store.ts @@ -49,12 +49,7 @@ export class ProjectEstimatesStore implements IProjectEstimateStore { createEstimate = async (workspaceSlug: string, projectId: string, data: IEstimateFormData) => { try { - const response = await this.estimateService.createEstimate( - workspaceSlug, - projectId, - data, - this.rootStore.user.currentUser! - ); + const response = await this.estimateService.createEstimate(workspaceSlug, projectId, data); const responseEstimate = { ...response.estimate, @@ -88,13 +83,7 @@ export class ProjectEstimatesStore implements IProjectEstimateStore { }); try { - const response = await this.estimateService.patchEstimate( - workspaceSlug, - projectId, - estimateId, - data, - this.rootStore.user.currentUser! - ); + const response = await this.estimateService.patchEstimate(workspaceSlug, projectId, estimateId, data); await this.rootStore.project.fetchProjectEstimates(workspaceSlug, projectId); return response; @@ -126,7 +115,7 @@ export class ProjectEstimatesStore implements IProjectEstimateStore { try { // deleting using api - await this.estimateService.deleteEstimate(workspaceSlug, projectId, estimateId, this.rootStore.user.currentUser!); + await this.estimateService.deleteEstimate(workspaceSlug, projectId, estimateId); } catch (error) { console.log("Failed to delete estimate from project store"); // reverting back to original estimate list diff --git a/web/store/project/project-label.store.ts b/web/store/project/project-label.store.ts index d6a804a60..2bc7b9794 100644 --- a/web/store/project/project-label.store.ts +++ b/web/store/project/project-label.store.ts @@ -119,12 +119,7 @@ export class ProjectLabelStore implements IProjectLabelStore { createLabel = async (workspaceSlug: string, projectId: string, data: Partial) => { try { - const response = await this.issueLabelService.createIssueLabel( - workspaceSlug, - projectId, - data, - this.rootStore.user.currentUser! - ); + const response = await this.issueLabelService.createIssueLabel(workspaceSlug, projectId, data); runInAction(() => { this.labels = { @@ -208,13 +203,7 @@ export class ProjectLabelStore implements IProjectLabelStore { }); try { - const response = await this.issueLabelService.patchIssueLabel( - workspaceSlug, - projectId, - labelId, - data, - this.rootStore.user.currentUser! - ); + const response = await this.issueLabelService.patchIssueLabel(workspaceSlug, projectId, labelId, data); return response; } catch (error) { @@ -243,12 +232,7 @@ export class ProjectLabelStore implements IProjectLabelStore { try { // deleting using api - await this.issueLabelService.deleteIssueLabel( - workspaceSlug, - projectId, - labelId, - this.rootStore.user.currentUser! - ); + await this.issueLabelService.deleteIssueLabel(workspaceSlug, projectId, labelId); } catch (error) { console.log("Failed to delete label from project store"); // reverting back to original label list diff --git a/web/store/project/project-state.store.ts b/web/store/project/project-state.store.ts index b0b136cb2..2c4247b75 100644 --- a/web/store/project/project-state.store.ts +++ b/web/store/project/project-state.store.ts @@ -4,8 +4,7 @@ import { RootStore } from "../root"; import { IState } from "types"; // services import { ProjectService, ProjectStateService } from "services/project"; -import { groupBy, orderArrayBy, groupByField } from "helpers/array.helper"; -import { orderStateGroups } from "helpers/state.helper"; +import { groupByField } from "helpers/array.helper"; export interface IProjectStateStore { loader: boolean; @@ -104,12 +103,7 @@ export class ProjectStateStore implements IProjectStateStore { createState = async (workspaceSlug: string, projectId: string, data: Partial) => { try { - const response = await this.stateService.createState( - workspaceSlug, - projectId, - data, - this.rootStore.user.currentUser! - ); + const response = await this.stateService.createState(workspaceSlug, projectId, data); runInAction(() => { this.states = { @@ -143,13 +137,7 @@ export class ProjectStateStore implements IProjectStateStore { }; }); - const response = await this.stateService.patchState( - workspaceSlug, - projectId, - stateId, - data, - this.rootStore.user.currentUser! - ); + const response = await this.stateService.patchState(workspaceSlug, projectId, stateId, data); return response; } catch (error) { @@ -179,7 +167,7 @@ export class ProjectStateStore implements IProjectStateStore { }); // deleting using api - await this.stateService.deleteState(workspaceSlug, projectId, stateId, this.rootStore.user.currentUser!); + await this.stateService.deleteState(workspaceSlug, projectId, stateId); } catch (error) { console.log("Failed to delete state from project store"); // reverting back to original label list @@ -259,13 +247,7 @@ export class ProjectStateStore implements IProjectStateStore { }; }); - await this.stateService.patchState( - workspaceSlug, - projectId, - stateId, - { sequence: newSequence }, - this.rootStore.user.currentUser! - ); + await this.stateService.patchState(workspaceSlug, projectId, stateId, { sequence: newSequence }); } catch (err) { console.log("Failed to move state position"); // reverting back to old state group if api fails diff --git a/web/store/project/project.store.ts b/web/store/project/project.store.ts index d7979f3f5..3216a5e74 100644 --- a/web/store/project/project.store.ts +++ b/web/store/project/project.store.ts @@ -384,7 +384,7 @@ export class ProjectStore implements IProjectStore { this.loader = true; this.error = null; - const response = await this.projectService.leaveProject(workspaceSlug, projectId, this.rootStore.user); + const response = await this.projectService.leaveProject(workspaceSlug, projectId); await this.fetchProjects(workspaceSlug); runInAction(() => { @@ -402,7 +402,7 @@ export class ProjectStore implements IProjectStore { createProject = async (workspaceSlug: string, data: any) => { try { - const response = await this.projectService.createProject(workspaceSlug, data, this.rootStore.user.currentUser); + const response = await this.projectService.createProject(workspaceSlug, data); runInAction(() => { this.projects = { ...this.projects, @@ -433,12 +433,7 @@ export class ProjectStore implements IProjectStore { }; }); - const response = await this.projectService.updateProject( - workspaceSlug, - projectId, - data, - this.rootStore.user.currentUser - ); + const response = await this.projectService.updateProject(workspaceSlug, projectId, data); return response; } catch (error) { console.log("Failed to create project from project store"); @@ -451,7 +446,7 @@ export class ProjectStore implements IProjectStore { deleteProject = async (workspaceSlug: string, projectId: string) => { try { - await this.projectService.deleteProject(workspaceSlug, projectId, this.rootStore.user.currentUser); + await this.projectService.deleteProject(workspaceSlug, projectId); await this.fetchProjects(workspaceSlug); } catch (error) { console.log("Failed to delete project from project store"); diff --git a/web/store/user.store.ts b/web/store/user.store.ts index 6b7e41548..177694f53 100644 --- a/web/store/user.store.ts +++ b/web/store/user.store.ts @@ -282,7 +282,7 @@ class UserStore implements IUserStore { if (!user) return; - await this.userService.updateUserOnBoard({ userRole: user.role }, user); + await this.userService.updateUserOnBoard(); } catch (error) { this.fetchCurrentUser(); @@ -300,7 +300,7 @@ class UserStore implements IUserStore { } as IUser; }); - const response = await this.userService.updateUserTourCompleted(this.currentUser); + const response = await this.userService.updateUserTourCompleted(); return response; } diff --git a/web/store/workspace/workspace-member.store.ts b/web/store/workspace/workspace-member.store.ts index b9803d1f1..2f36e6eea 100644 --- a/web/store/workspace/workspace-member.store.ts +++ b/web/store/workspace/workspace-member.store.ts @@ -1,7 +1,7 @@ import { action, computed, observable, makeObservable, runInAction } from "mobx"; import { RootStore } from "../root"; // types -import { IUser, IWorkspaceMember, IWorkspaceMemberInvitation, IWorkspaceBulkInviteFormData } from "types"; +import { IWorkspaceMember, IWorkspaceMemberInvitation, IWorkspaceBulkInviteFormData } from "types"; // services import { WorkspaceService } from "services/workspace.service"; @@ -175,7 +175,7 @@ export class WorkspaceMemberStore implements IWorkspaceMemberStore { */ inviteMembersToWorkspace = async (workspaceSlug: string, data: IWorkspaceBulkInviteFormData) => { try { - await this.workspaceService.inviteWorkspace(workspaceSlug, data, this.rootStore.user.currentUser as IUser); + await this.workspaceService.inviteWorkspace(workspaceSlug, data); await this.fetchWorkspaceMemberInvitations(workspaceSlug); } catch (error) { throw error; diff --git a/web/store/workspace/workspace.store.ts b/web/store/workspace/workspace.store.ts index 5fa071cd3..e4e4f3ea4 100644 --- a/web/store/workspace/workspace.store.ts +++ b/web/store/workspace/workspace.store.ts @@ -212,9 +212,7 @@ export class WorkspaceStore implements IWorkspaceStore { this.error = null; }); - const user = this.rootStore.user.currentUser ?? undefined; - - const response = await this.workspaceService.createWorkspace(data, user); + const response = await this.workspaceService.createWorkspace(data); runInAction(() => { this.loader = false; @@ -247,9 +245,7 @@ export class WorkspaceStore implements IWorkspaceStore { this.error = null; }); - const user = this.rootStore.user.currentUser ?? undefined; - - const response = await this.workspaceService.updateWorkspace(workspaceSlug, data, user); + const response = await this.workspaceService.updateWorkspace(workspaceSlug, data); runInAction(() => { this.loader = false; @@ -281,9 +277,7 @@ export class WorkspaceStore implements IWorkspaceStore { this.error = null; }); - const user = this.rootStore.user.currentUser ?? undefined; - - await this.workspaceService.deleteWorkspace(workspaceSlug, user); + await this.workspaceService.deleteWorkspace(workspaceSlug); runInAction(() => { this.loader = false; diff --git a/web/types/app.d.ts b/web/types/app.d.ts index c762fb76f..d5a7953b1 100644 --- a/web/types/app.d.ts +++ b/web/types/app.d.ts @@ -9,4 +9,6 @@ export interface IAppConfig { github_client_id: string | null; magic_login: boolean; slack_client_id: string | null; + posthog_api_key: string | null; + posthog_host: string | null; } diff --git a/yarn.lock b/yarn.lock index eef1655f4..53210f2ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1519,35 +1519,35 @@ resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz#08d6c5e20cf7e4cc02fd181c4b0c225cd31dbb60" integrity sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA== -"@mui/base@5.0.0-beta.22": - version "5.0.0-beta.22" - resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.22.tgz#9ea6be6c8bfc4d8f825660da36d228f5315d4706" - integrity sha512-l4asGID5tmyerx9emJfXOKLyXzaBtdXNIFE3M+IrSZaFtGFvaQKHhc3+nxxSxPf1+G44psjczM0ekRQCdXx9HA== +"@mui/base@5.0.0-beta.23": + version "5.0.0-beta.23" + resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.23.tgz#dd10dfc609d8937749521f940965f757fa3c0f2c" + integrity sha512-9L8SQUGAWtd/Qi7Qem26+oSSgpY7f2iQTuvcz/rsGpyZjSomMMO6lwYeQSA0CpWM7+aN7eGoSY/WV6wxJiIxXw== dependencies: "@babel/runtime" "^7.23.2" "@floating-ui/react-dom" "^2.0.2" "@mui/types" "^7.2.8" - "@mui/utils" "^5.14.16" + "@mui/utils" "^5.14.17" "@popperjs/core" "^2.11.8" clsx "^2.0.0" prop-types "^15.8.1" -"@mui/core-downloads-tracker@^5.14.16": - version "5.14.16" - resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.16.tgz#03ceb422d69a33e6c1cbd7e943cf60816878be2a" - integrity sha512-97isBjzH2v1K7oB4UH2f4NOkBShOynY6dhnoR2XlUk/g6bb7ZBv2I3D1hvvqPtpEigKu93e7f/jAYr5d9LOc5w== +"@mui/core-downloads-tracker@^5.14.17": + version "5.14.17" + resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.17.tgz#68ec40ea00832fb685f09bd493648b1f68f5baf4" + integrity sha512-eE0uxrpJAEL2ZXkeGLKg8HQDafsiXY+6eNpP4lcv3yIjFfGbU6Hj9/P7Adt8jpU+6JIhmxvILGj2r27pX+zdrQ== "@mui/material@^5.14.1": - version "5.14.16" - resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.14.16.tgz#45cd62d312d10399d3813ee6dc43bd1f11179bf4" - integrity sha512-W4zZ4vnxgGk6/HqBwgsDHKU7x2l2NhX+r8gAwfg58Rhu3ikfY7NkIS6y8Gl3NkATc4GG1FNaGjjpQKfJx3U6Jw== + version "5.14.17" + resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.14.17.tgz#8e11098e52965be3e9ed117d6c107bbe511fac46" + integrity sha512-+y0VeOLWfEA4Z98We/UH6KCo8+f2HLZDK45FY+sJf8kSojLy3VntadKtC/u0itqnXXb1Pr4wKB2tSIBW02zY4Q== dependencies: "@babel/runtime" "^7.23.2" - "@mui/base" "5.0.0-beta.22" - "@mui/core-downloads-tracker" "^5.14.16" - "@mui/system" "^5.14.16" + "@mui/base" "5.0.0-beta.23" + "@mui/core-downloads-tracker" "^5.14.17" + "@mui/system" "^5.14.17" "@mui/types" "^7.2.8" - "@mui/utils" "^5.14.16" + "@mui/utils" "^5.14.17" "@types/react-transition-group" "^4.4.8" clsx "^2.0.0" csstype "^3.1.2" @@ -1555,35 +1555,35 @@ react-is "^18.2.0" react-transition-group "^4.4.5" -"@mui/private-theming@^5.14.16": - version "5.14.16" - resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.14.16.tgz#ffdc9a9d3deaa46af000f04c0a9cc3a982f73071" - integrity sha512-FNlL0pTSEBh8nXsVWreCHDSHk+jG8cBx1sxRbT8JVtL+PYbYPi802zfV4B00Kkf0LNRVRvAVQwojMWSR/MYGng== +"@mui/private-theming@^5.14.17": + version "5.14.17" + resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.14.17.tgz#2fdf9d7df54dcb27e8ae7e00f440eb8310376ec3" + integrity sha512-u4zxsCm9xmQrlhVPug+Ccrtsjv7o2+rehvrgHoh0siSguvVgVQq5O3Hh10+tp/KWQo2JR4/nCEwquSXgITS1+g== dependencies: "@babel/runtime" "^7.23.2" - "@mui/utils" "^5.14.16" + "@mui/utils" "^5.14.17" prop-types "^15.8.1" -"@mui/styled-engine@^5.14.16": - version "5.14.16" - resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.14.16.tgz#a4a78a9980f138c2e705d04d67d44051f5005f22" - integrity sha512-FfvYvTG/Zd+KXMMImbcMYEeQAbONGuX5Vx3gBmmtB6KyA7Mvm9Pma1ly3R0gc44yeoFd+2wBjn1feS8h42HW5w== +"@mui/styled-engine@^5.14.17": + version "5.14.17" + resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.14.17.tgz#7f79d863604076db5278bd38a2eeac44cd2ed336" + integrity sha512-AqpVjBEA7wnBvKPW168bNlqB6EN7HxTjLOY7oi275AzD/b1C7V0wqELy6NWoJb2yya5sRf7ENf4iNi3+T5cOgw== dependencies: "@babel/runtime" "^7.23.2" "@emotion/cache" "^11.11.0" csstype "^3.1.2" prop-types "^15.8.1" -"@mui/system@^5.14.16": - version "5.14.16" - resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.14.16.tgz#5c30c5123767416358c3b73774eb985e189119a4" - integrity sha512-uKnPfsDqDs8bbN54TviAuoGWOmFiQLwNZ3Wvj+OBkJCzwA6QnLb/sSeCB7Pk3ilH4h4jQ0BHtbR+Xpjy9wlOuA== +"@mui/system@^5.14.17": + version "5.14.17" + resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.14.17.tgz#3e212d1e699d4c777bbe5c6584ae129b1ef7d8bc" + integrity sha512-Ccz3XlbCqka6DnbHfpL3o3TfOeWQPR+ewvNAgm8gnS9M0yVMmzzmY6z0w/C1eebb+7ZP7IoLUj9vojg/GBaTPg== dependencies: "@babel/runtime" "^7.23.2" - "@mui/private-theming" "^5.14.16" - "@mui/styled-engine" "^5.14.16" + "@mui/private-theming" "^5.14.17" + "@mui/styled-engine" "^5.14.17" "@mui/types" "^7.2.8" - "@mui/utils" "^5.14.16" + "@mui/utils" "^5.14.17" clsx "^2.0.0" csstype "^3.1.2" prop-types "^15.8.1" @@ -1593,10 +1593,10 @@ resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.8.tgz#2ed4402f104d65fcd4f460ca358654c8935e2285" integrity sha512-9u0ji+xspl96WPqvrYJF/iO+1tQ1L5GTaDOeG3vCR893yy7VcWwRNiVMmPdPNpMDqx0WV1wtEW9OMwK9acWJzQ== -"@mui/utils@^5.14.16": - version "5.14.16" - resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.14.16.tgz#09a15fd45530cadc642c5c08eb6cc660ea230506" - integrity sha512-3xV31GposHkwRbQzwJJuooWpK2ybWdEdeUPtRjv/6vjomyi97F3+68l+QVj9tPTvmfSbr2sx5c/NuvDulrdRmA== +"@mui/utils@^5.14.17": + version "5.14.17" + resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.14.17.tgz#8e2e7ca58865119eec8c6bdb359f539c25aaf576" + integrity sha512-yxnWgSS4J6DMFPw2Dof85yBkG02VTbEiqsikymMsnZnXDurtVGTIhlNuV24GTmFTuJMzEyTTU9UF+O7zaL8LEQ== dependencies: "@babel/runtime" "^7.23.2" "@types/prop-types" "^15.7.9" @@ -2598,21 +2598,21 @@ integrity sha512-rhlLWwVkOodBGRMK0mAmE34l2a+BqM2Y7q1ViuQRBhs/6sZ8d83O4hARHKVwqT5stY4i1l7d7PoemV3uAGI6+g== "@types/debug@^4.0.0": - version "4.1.10" - resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.10.tgz#f23148a6eb771a34c466a4fc28379d8101e84494" - integrity sha512-tOSCru6s732pofZ+sMv9o4o3Zc+Sa8l3bxd/tweTQudFn06vAzb13ZX46Zi6m6EJ+RUbRTHvgQJ1gBtSgkaUYA== + version "4.1.11" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.11.tgz#b20d24098288f19e48fdf776c5d9ccd024629e4e" + integrity sha512-R2qflTjHDs4CL6D/6TkqBeIHr54WzZfIxN729xvCNlYIVp2LknlnCro5Yo3frNaX2E5gO9pZ3/QAPVdGmu+q9w== dependencies: "@types/ms" "*" "@types/dom4@^2.0.2": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@types/dom4/-/dom4-2.0.3.tgz#bd084dbd4c15bee49442c5cd231acdcd14efbe90" - integrity sha512-xQT2XxtDGP1WFfTB/Lti629HpguNrfZ3dg84bWXASd6JUay6WgR73Wb6DG3kmr2/iGAWZ7NNLceGVWYWfgPX0g== + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/dom4/-/dom4-2.0.4.tgz#427a4ce8590727aed5ce0fe39a64f175a57fdc1c" + integrity sha512-PD+wqNhrjWFjAlSVd18jvChZvOXB2SOwAILBmuYev5zswBats5qmzs/QFoooLKd2omj9BT05a8MeSeRmXLGY+Q== "@types/estree@*", "@types/estree@^1.0.0": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.4.tgz#d9748f5742171b26218516cf1828b8eafaf8a9fa" - integrity sha512-2JwWnHK9H+wUZNorf2Zr6ves96WHoWDJIftkcxPKsS7Djta6Zu519LarhRNljPXkpsZR2ZMwNCPeW7omW07BJw== + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== "@types/estree@0.0.39": version "0.0.39" @@ -2642,22 +2642,22 @@ "@types/unist" "*" "@types/hoist-non-react-statics@^3.3.1": - version "3.3.4" - resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.4.tgz#cc477ce0283bb9d19ea0cbfa2941fe2c8493a1be" - integrity sha512-ZchYkbieA+7tnxwX/SCBySx9WwvWR8TaP5tb2jRAzwvLb/rWchGw3v0w3pqUbUvj0GCwW2Xz/AVPSk6kUGctXQ== + version "3.3.5" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz#dab7867ef789d87e2b4b0003c9d65c49cc44a494" + integrity sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg== dependencies: "@types/react" "*" hoist-non-react-statics "^3.3.0" "@types/js-cookie@^3.0.2", "@types/js-cookie@^3.0.3": - version "3.0.5" - resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-3.0.5.tgz#5eba4033a4f17fb2b29d975892694315194eca33" - integrity sha512-dtLshqoiGRDHbHueIT9sjkd2F4tW1qPSX2xKAQK8p1e6pM+Z913GM1shv7dOqqasEMYbC5zEaClJomQe8OtQLA== + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-3.0.6.tgz#a04ca19e877687bd449f5ad37d33b104b71fdf95" + integrity sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ== "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": - version "7.0.14" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.14.tgz#74a97a5573980802f32c8e47b663530ab3b6b7d1" - integrity sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw== + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== "@types/json5@^0.0.29": version "0.0.29" @@ -2665,14 +2665,14 @@ integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== "@types/linkify-it@*": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.4.tgz#def6a9bb0ce78140860602f16ace37a9997f086a" - integrity sha512-hPpIeeHb/2UuCw06kSNAOVWgehBLXEo0/fUs0mw3W2qhqX89PI2yvok83MnuctYGCPrabGIoi0fFso4DQ+sNUQ== + version "3.0.5" + resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.5.tgz#1e78a3ac2428e6d7e6c05c1665c242023a4601d8" + integrity sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw== "@types/lodash.debounce@^4.0.7": - version "4.0.8" - resolved "https://registry.yarnpkg.com/@types/lodash.debounce/-/lodash.debounce-4.0.8.tgz#d5fe36a35aa57773e05d960b3e3c703fd9ffb8b3" - integrity sha512-REumepIJjQFSOaBUoj81U5ZzF9YIhovzE2Lm6ejUbycmwx597k2ivG1cVfPtAj4eVuSbGoZDkJR0sRIahsE6/Q== + version "4.0.9" + resolved "https://registry.yarnpkg.com/@types/lodash.debounce/-/lodash.debounce-4.0.9.tgz#0f5f21c507bce7521b5e30e7a24440975ac860a5" + integrity sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ== dependencies: "@types/lodash" "*" @@ -2697,9 +2697,9 @@ "@types/unist" "^2" "@types/mdurl@*": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-1.0.4.tgz#574bfbec51eb41ab5f444116c8555bc4347feba5" - integrity sha512-ARVxjAEX5TARFRzpDRVC6cEk0hUIXCCwaMhz8y7S1/PxU6zZS1UMjyobz7q4w/D/R552r4++EhwmXK1N2rAy0A== + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-1.0.5.tgz#3e0d2db570e9fb6ccb2dc8fde0be1d79ac810d39" + integrity sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA== "@types/minimatch@*": version "5.1.2" @@ -2707,9 +2707,9 @@ integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== "@types/ms@*": - version "0.7.33" - resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.33.tgz#80bf1da64b15f21fd8c1dc387c31929317d99ee9" - integrity sha512-AuHIyzR5Hea7ij0P9q7vx7xu4z0C28ucwjAZC0ja7JhINyCnOw8/DnvAPQQ9TfOlCtZAmCERKQX9+o1mgQhuOQ== + version "0.7.34" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.34.tgz#10964ba0dee6ac4cd462e2795b6bebd407303433" + integrity sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g== "@types/node@*", "@types/node@^20.5.2": version "20.8.10" @@ -2734,29 +2734,29 @@ integrity sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw== "@types/nprogress@^0.2.0": - version "0.2.2" - resolved "https://registry.yarnpkg.com/@types/nprogress/-/nprogress-0.2.2.tgz#c73bf540ac7926fb1b6d03f9d2725e07b3848d65" - integrity sha512-2wLrSJXLztGmr7wXwM0hA/wuIOY9DznVdd+ZFofHOiXcj9JnVt+2ZeLRJ7v5ZVlmheSkUOSg3Q3O4Ce7yji79A== + version "0.2.3" + resolved "https://registry.yarnpkg.com/@types/nprogress/-/nprogress-0.2.3.tgz#b2150b054a13622fabcba12cf6f0b54c48b14287" + integrity sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA== "@types/object.omit@^3.0.0": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/object.omit/-/object.omit-3.0.2.tgz#13d23915cc16fa54b0d4cfbcb79840f4fe1474d9" - integrity sha512-BxWU36cMP+FKD3OLFluQaj2cBev2sx2LJaHELuphHwnleq+xnEhTmuYYYx4pOT/1U/ZoR6B+RdvxWh2FD6lGGA== + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/object.omit/-/object.omit-3.0.3.tgz#cc52b1d9774c1619b5c6fc50229d087f01eabd68" + integrity sha512-xrq4bQTBGYY2cw+gV4PzoG2Lv3L0pjZ1uXStRRDQoATOYW1lCsFQHhQ+OkPhIcQoqLjAq7gYif7D14Qaa6Zbew== "@types/object.pick@^1.3.2": - version "1.3.3" - resolved "https://registry.yarnpkg.com/@types/object.pick/-/object.pick-1.3.3.tgz#f4d4a76e9ef1161e965b963d2bb33c3f6c300125" - integrity sha512-qZqHmdGEALeSATMB1djT1S5szv6Wtpb7DKpHrt2XG4iyKlV7C2Xk8GmDXr1KXakOqUfX6ohw7ceruYt4NVmB1Q== + version "1.3.4" + resolved "https://registry.yarnpkg.com/@types/object.pick/-/object.pick-1.3.4.tgz#1a38b6e69a35f36ec2dcc8b9f5ffd555c1c4d7fc" + integrity sha512-5PjwB0uP2XDp3nt5u5NJAG2DORHIRClPzWT/TTZhJ2Ekwe8M5bA9tvPdi9NO/n2uvu2/ictat8kgqvLfcIE1SA== "@types/parse-json@^4.0.0": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.1.tgz#27f7559836ad796cea31acb63163b203756a5b4e" - integrity sha512-3YmXzzPAdOTVljVMkTMBdBEvlOLg2cDQaDhnnhT3nT9uDbnJzjWhKlzb+desT12Y7tGqaN6d+AbozcKzyL36Ng== + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" + integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== "@types/prop-types@*", "@types/prop-types@^15.0.0", "@types/prop-types@^15.7.9": - version "15.7.9" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.9.tgz#b6f785caa7ea1fe4414d9df42ee0ab67f23d8a6d" - integrity sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g== + version "15.7.10" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.10.tgz#892afc9332c4d62a5ea7e897fe48ed2085bbb08a" + integrity sha512-mxSnDQxPqsZxmeShFH+uwQ4kO4gcJcGahjjMFeLbKE95IAZiiZyiEepGZjtXJ7hN/yfu0bu9xN2ajcU0JcxX6A== "@types/react-color@^3.0.6", "@types/react-color@^3.0.9": version "3.0.9" @@ -2783,13 +2783,6 @@ dependencies: "@types/react" "*" -"@types/react-dom@18.2.0": - version "18.2.0" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.0.tgz#374f28074bb117f56f58c4f3f71753bebb545156" - integrity sha512-8yQrvS6sMpSwIovhPOwfyNf2Wz6v/B62LFSVYQ85+Rq3tLsBIG7rP5geMxaijTUxSkrO6RzN/IRuIAADYQsleA== - dependencies: - "@types/react" "*" - "@types/react-dom@^18.2.14": version "18.2.14" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.14.tgz#c01ba40e5bb57fc1dc41569bb3ccdb19eab1c539" @@ -2797,6 +2790,13 @@ dependencies: "@types/react" "*" +"@types/react-dom@^18.2.15": + version "18.2.15" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.15.tgz#921af67f9ee023ac37ea84b1bc0cc40b898ea522" + integrity sha512-HWMdW+7r7MR5+PZqJF6YFNSCtjz1T0dsvo/f1BV6HkV+6erD/nA7wd9NM00KVG83zf2nJ7uATPO9ttdIPvi3gg== + dependencies: + "@types/react" "*" + "@types/react-transition-group@^4.4.8": version "4.4.8" resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.8.tgz#46f87d80512959cac793ecc610a93d80ef241ccf" @@ -2804,7 +2804,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@18.0.28", "@types/react@18.2.0", "@types/react@^18.2.35", "@types/react@^18.2.5": +"@types/react@*", "@types/react@18.0.28", "@types/react@18.2.0", "@types/react@^18.2.35", "@types/react@^18.2.37", "@types/react@^18.2.5": version "18.2.0" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.0.tgz#15cda145354accfc09a18d2f2305f9fc099ada21" integrity sha512-0FLj93y5USLHdnhIhABk83rm8XEGA7kH3cr+YUlvxoUGp1xNt/DINUMvqPxLyOQMzLmZe8i4RTHbvb8MC7NmrA== @@ -4020,9 +4020,9 @@ ejs@^3.1.6: jake "^10.8.5" electron-to-chromium@^1.4.535: - version "1.4.576" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.576.tgz#0c6940fdc0d60f7e34bd742b29d8fa847c9294d1" - integrity sha512-yXsZyXJfAqzWk1WKryr0Wl0MN2D47xodPvEEwlVePBnhU5E7raevLQR+E6b9JAD3GfL/7MbAL9ZtWQQPcLx7wA== + version "1.4.577" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.577.tgz#a732f11cf4532be96e5e3f1197dcda54c2cec7ad" + integrity sha512-/5xHPH6f00SxhHw6052r+5S1xO7gHNc89hV7tqlvnStvKbSrDqc/u6AlwPvVWWNj+s4/KL6T6y8ih+nOY0qYNA== emoji-regex@^8.0.0: version "8.0.0" @@ -4874,9 +4874,9 @@ fast-fifo@^1.1.0, fast-fifo@^1.2.0: integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ== fast-glob@^3.2.9, fast-glob@^3.3.0, fast-glob@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" - integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" @@ -4908,6 +4908,11 @@ fault@^2.0.0: dependencies: format "^0.2.0" +fflate@^0.4.1: + version "0.4.8" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae" + integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA== + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -6998,6 +7003,13 @@ postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.29: picocolors "^1.0.0" source-map-js "^1.0.2" +posthog-js@^1.88.4: + version "1.88.4" + resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.88.4.tgz#480995e18b2c3052f29142a6c5f02e1eafadc759" + integrity sha512-ZKgC0q22Bv0WGsi/BJcamDIoBR3DPpF26kCq0eFxtV3gKDMpGIlhlODQCo28bSENziGVO4LSeGrUvo/6OsQ7xA== + dependencies: + fflate "^0.4.1" + prebuild-install@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" @@ -8314,9 +8326,9 @@ tippy.js@^6.3.7: "@popperjs/core" "^2.9.0" tiptap-markdown@^0.8.2: - version "0.8.3" - resolved "https://registry.yarnpkg.com/tiptap-markdown/-/tiptap-markdown-0.8.3.tgz#51e93aad3c603c75a91111494ef4fd04115165fe" - integrity sha512-RULu1OXFTHdTJCHwdUOvDk0nDoH8YdgXulR0f+8XBd/x+SuT+EafdQuEKz2ggFlW1Mvl5niKGT5lAHTeXYFmaw== + version "0.8.4" + resolved "https://registry.yarnpkg.com/tiptap-markdown/-/tiptap-markdown-0.8.4.tgz#d6cdbd6e4aa88b58c3c149c2d0d25c70b3e11ac9" + integrity sha512-aCwr8cpVdZeb/2J0ffz8PvpLmQBcFE9GOxk2vB0Y9zlJEGWnSiGo1BWnwaeiyAdQqfBzV7NyNg+oz2A/MbC/Sg== dependencies: "@types/markdown-it" "^12.2.3" markdown-it "^13.0.1"