Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a0d8a171e |
@@ -1,3 +1,38 @@
|
||||
# Frontend
|
||||
# Extra image domains that need to be added for Next Image
|
||||
NEXT_PUBLIC_EXTRA_IMAGE_DOMAINS=
|
||||
# Google Client ID for Google OAuth
|
||||
NEXT_PUBLIC_GOOGLE_CLIENTID=""
|
||||
# Github ID for Github OAuth
|
||||
NEXT_PUBLIC_GITHUB_ID=""
|
||||
# Github App Name for GitHub Integration
|
||||
NEXT_PUBLIC_GITHUB_APP_NAME=""
|
||||
# Sentry DSN for error monitoring
|
||||
NEXT_PUBLIC_SENTRY_DSN=""
|
||||
# Enable/Disable OAUTH - default 0 for selfhosted instance
|
||||
NEXT_PUBLIC_ENABLE_OAUTH=0
|
||||
# Enable/Disable sentry
|
||||
NEXT_PUBLIC_ENABLE_SENTRY=0
|
||||
# Enable/Disable session recording
|
||||
NEXT_PUBLIC_ENABLE_SESSION_RECORDER=0
|
||||
# Enable/Disable event tracking
|
||||
NEXT_PUBLIC_TRACK_EVENTS=0
|
||||
# Slack for Slack Integration
|
||||
NEXT_PUBLIC_SLACK_CLIENT_ID=""
|
||||
# For Telemetry, set it to "app.plane.so"
|
||||
NEXT_PUBLIC_PLAUSIBLE_DOMAIN=""
|
||||
# public boards deploy url
|
||||
NEXT_PUBLIC_DEPLOY_URL=""
|
||||
# plane deploy using nginx
|
||||
NEXT_PUBLIC_DEPLOY_WITH_NGINX=1
|
||||
|
||||
# Backend
|
||||
# Debug value for api server use it as 0 for production use
|
||||
DEBUG=0
|
||||
|
||||
# Error logs
|
||||
SENTRY_DSN=""
|
||||
|
||||
# Database Settings
|
||||
PGUSER="plane"
|
||||
PGPASSWORD="plane"
|
||||
@@ -10,6 +45,15 @@ REDIS_HOST="plane-redis"
|
||||
REDIS_PORT="6379"
|
||||
REDIS_URL="redis://${REDIS_HOST}:6379/"
|
||||
|
||||
# Email Settings
|
||||
EMAIL_HOST=""
|
||||
EMAIL_HOST_USER=""
|
||||
EMAIL_HOST_PASSWORD=""
|
||||
EMAIL_PORT=587
|
||||
EMAIL_FROM="Team Plane <team@mailer.plane.so>"
|
||||
EMAIL_USE_TLS="1"
|
||||
EMAIL_USE_SSL="0"
|
||||
|
||||
# AWS Settings
|
||||
AWS_REGION=""
|
||||
AWS_ACCESS_KEY_ID="access-key"
|
||||
@@ -25,6 +69,9 @@ OPENAI_API_BASE="https://api.openai.com/v1" # change if using a custom endpoint
|
||||
OPENAI_API_KEY="sk-" # add your openai key here
|
||||
GPT_ENGINE="gpt-3.5-turbo" # use "gpt-4" if you have access
|
||||
|
||||
# Github
|
||||
GITHUB_CLIENT_SECRET="" # For fetching release notes
|
||||
|
||||
# Settings related to Docker
|
||||
DOCKERIZED=1
|
||||
# set to 1 If using the pre-configured minio setup
|
||||
@@ -33,3 +80,10 @@ USE_MINIO=1
|
||||
# Nginx Configuration
|
||||
NGINX_PORT=80
|
||||
|
||||
# Default Creds
|
||||
DEFAULT_EMAIL="captain@plane.so"
|
||||
DEFAULT_PASSWORD="password123"
|
||||
|
||||
# SignUps
|
||||
ENABLE_SIGNUP="1"
|
||||
# Auto generated and Required that will be generated from setup.sh
|
||||
|
||||
@@ -33,9 +33,14 @@ jobs:
|
||||
deploy:
|
||||
- space/**
|
||||
|
||||
- name: Setup .npmrc for repository
|
||||
run: |
|
||||
echo -e "@tiptap-pro:registry=https://registry.tiptap.dev/\n//registry.tiptap.dev/:_authToken=${{ secrets.TIPTAP_TOKEN }}" > .npmrc
|
||||
|
||||
- name: Build Plane's Main App
|
||||
if: steps.changed-files.outputs.web_any_changed == 'true'
|
||||
run: |
|
||||
mv ./.npmrc ./web
|
||||
cd web
|
||||
yarn
|
||||
yarn build
|
||||
|
||||
@@ -22,6 +22,10 @@ jobs:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Setup .npmrc for repository
|
||||
run: |
|
||||
echo -e "@tiptap-pro:registry=https://registry.tiptap.dev/\n//registry.tiptap.dev/:_authToken=${{ secrets.TIPTAP_TOKEN }}" > .npmrc
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker (Docker Hub) from Github Release
|
||||
id: metaFrontend
|
||||
uses: docker/metadata-action@v4.3.0
|
||||
|
||||
@@ -59,6 +59,17 @@ chmod +x setup.sh
|
||||
|
||||
> If running in a cloud env replace localhost with public facing IP address of the VM
|
||||
|
||||
- Setup Tiptap Pro
|
||||
|
||||
Visit [Tiptap Pro](https://collab.tiptap.dev/pro-extensions) and signup (it is free).
|
||||
|
||||
Create a **`.npmrc`** file, copy the following and replace your registry token generated from Tiptap Pro.
|
||||
|
||||
```
|
||||
@tiptap-pro:registry=https://registry.tiptap.dev/
|
||||
//registry.tiptap.dev/:_authToken=YOUR_REGISTRY_TOKEN
|
||||
```
|
||||
|
||||
- Run Docker compose up
|
||||
|
||||
```bash
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
# Backend
|
||||
# Debug value for api server use it as 0 for production use
|
||||
DEBUG=0
|
||||
DJANGO_SETTINGS_MODULE="plane.settings.selfhosted"
|
||||
|
||||
# Error logs
|
||||
SENTRY_DSN=""
|
||||
|
||||
# Database Settings
|
||||
PGUSER="plane"
|
||||
PGPASSWORD="plane"
|
||||
PGHOST="plane-db"
|
||||
PGDATABASE="plane"
|
||||
DATABASE_URL=postgresql://${PGUSER}:${PGPASSWORD}@${PGHOST}/${PGDATABASE}
|
||||
|
||||
# Redis Settings
|
||||
REDIS_HOST="plane-redis"
|
||||
REDIS_PORT="6379"
|
||||
REDIS_URL="redis://${REDIS_HOST}:6379/"
|
||||
|
||||
# Email Settings
|
||||
EMAIL_HOST=""
|
||||
EMAIL_HOST_USER=""
|
||||
EMAIL_HOST_PASSWORD=""
|
||||
EMAIL_PORT=587
|
||||
EMAIL_FROM="Team Plane <team@mailer.plane.so>"
|
||||
EMAIL_USE_TLS="1"
|
||||
EMAIL_USE_SSL="0"
|
||||
|
||||
# AWS Settings
|
||||
AWS_REGION=""
|
||||
AWS_ACCESS_KEY_ID="access-key"
|
||||
AWS_SECRET_ACCESS_KEY="secret-key"
|
||||
AWS_S3_ENDPOINT_URL="http://plane-minio:9000"
|
||||
# Changing this requires change in the nginx.conf for uploads if using minio setup
|
||||
AWS_S3_BUCKET_NAME="uploads"
|
||||
# Maximum file upload limit
|
||||
FILE_SIZE_LIMIT=5242880
|
||||
|
||||
# GPT settings
|
||||
OPENAI_API_BASE="https://api.openai.com/v1" # change if using a custom endpoint
|
||||
OPENAI_API_KEY="sk-" # add your openai key here
|
||||
GPT_ENGINE="gpt-3.5-turbo" # use "gpt-4" if you have access
|
||||
|
||||
# Github
|
||||
GITHUB_CLIENT_SECRET="" # For fetching release notes
|
||||
|
||||
# Settings related to Docker
|
||||
DOCKERIZED=1
|
||||
# set to 1 If using the pre-configured minio setup
|
||||
USE_MINIO=1
|
||||
|
||||
# Nginx Configuration
|
||||
NGINX_PORT=80
|
||||
|
||||
# Default Creds
|
||||
DEFAULT_EMAIL="captain@plane.so"
|
||||
DEFAULT_PASSWORD="password123"
|
||||
|
||||
# SignUps
|
||||
ENABLE_SIGNUP="1"
|
||||
@@ -23,7 +23,7 @@ from .project import (
|
||||
ProjectPublicMemberSerializer
|
||||
)
|
||||
from .state import StateSerializer, StateLiteSerializer
|
||||
from .view import GlobalViewSerializer, IssueViewSerializer, IssueViewFavoriteSerializer
|
||||
from .view import IssueViewSerializer, IssueViewFavoriteSerializer
|
||||
from .cycle import CycleSerializer, CycleIssueSerializer, CycleFavoriteSerializer, CycleWriteSerializer
|
||||
from .asset import FileAssetSerializer
|
||||
from .issue import (
|
||||
|
||||
@@ -49,7 +49,6 @@ class IssueFlatSerializer(BaseSerializer):
|
||||
"target_date",
|
||||
"sequence_id",
|
||||
"sort_order",
|
||||
"is_draft",
|
||||
]
|
||||
|
||||
|
||||
@@ -293,12 +292,12 @@ class IssueLabelSerializer(BaseSerializer):
|
||||
|
||||
|
||||
class IssueRelationSerializer(BaseSerializer):
|
||||
issue_detail = IssueProjectLiteSerializer(read_only=True, source="related_issue")
|
||||
related_issue_detail = IssueProjectLiteSerializer(read_only=True, source="related_issue")
|
||||
|
||||
class Meta:
|
||||
model = IssueRelation
|
||||
fields = [
|
||||
"issue_detail",
|
||||
"related_issue_detail",
|
||||
"relation_type",
|
||||
"related_issue",
|
||||
"issue",
|
||||
|
||||
@@ -5,39 +5,10 @@ from rest_framework import serializers
|
||||
from .base import BaseSerializer
|
||||
from .workspace import WorkspaceLiteSerializer
|
||||
from .project import ProjectLiteSerializer
|
||||
from plane.db.models import GlobalView, IssueView, IssueViewFavorite
|
||||
from plane.db.models import IssueView, IssueViewFavorite
|
||||
from plane.utils.issue_filters import issue_filters
|
||||
|
||||
|
||||
class GlobalViewSerializer(BaseSerializer):
|
||||
workspace_detail = WorkspaceLiteSerializer(source="workspace", read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = GlobalView
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"query",
|
||||
]
|
||||
|
||||
def create(self, validated_data):
|
||||
query_params = validated_data.get("query_data", {})
|
||||
if bool(query_params):
|
||||
validated_data["query"] = issue_filters(query_params, "POST")
|
||||
else:
|
||||
validated_data["query"] = dict()
|
||||
return GlobalView.objects.create(**validated_data)
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
query_params = validated_data.get("query_data", {})
|
||||
if bool(query_params):
|
||||
validated_data["query"] = issue_filters(query_params, "POST")
|
||||
else:
|
||||
validated_data["query"] = dict()
|
||||
validated_data["query"] = issue_filters(query_params, "PATCH")
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
|
||||
class IssueViewSerializer(BaseSerializer):
|
||||
is_favorite = serializers.BooleanField(read_only=True)
|
||||
project_detail = ProjectLiteSerializer(source="project", read_only=True)
|
||||
|
||||
@@ -102,8 +102,6 @@ from plane.api.views import (
|
||||
BulkEstimatePointEndpoint,
|
||||
## End Estimates
|
||||
# Views
|
||||
GlobalViewViewSet,
|
||||
GlobalViewIssuesViewSet,
|
||||
IssueViewViewSet,
|
||||
ViewIssuesEndpoint,
|
||||
IssueViewFavoriteViewSet,
|
||||
@@ -186,6 +184,7 @@ from plane.api.views import (
|
||||
## Exporter
|
||||
ExportIssuesEndpoint,
|
||||
## End Exporter
|
||||
|
||||
)
|
||||
|
||||
|
||||
@@ -242,11 +241,7 @@ urlpatterns = [
|
||||
UpdateUserTourCompletedEndpoint.as_view(),
|
||||
name="user-tour",
|
||||
),
|
||||
path(
|
||||
"users/workspaces/<str:slug>/activities/",
|
||||
UserActivityEndpoint.as_view(),
|
||||
name="user-activities",
|
||||
),
|
||||
path("users/workspaces/<str:slug>/activities/", UserActivityEndpoint.as_view(), name="user-activities"),
|
||||
# user workspaces
|
||||
path(
|
||||
"users/me/workspaces/",
|
||||
@@ -654,37 +649,6 @@ urlpatterns = [
|
||||
ViewIssuesEndpoint.as_view(),
|
||||
name="project-view-issues",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/views/",
|
||||
GlobalViewViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
name="global-view",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/views/<uuid:pk>/",
|
||||
GlobalViewViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"put": "update",
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
}
|
||||
),
|
||||
name="global-view",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/issues/",
|
||||
GlobalViewIssuesViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
}
|
||||
),
|
||||
name="global-view-issues",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-views/",
|
||||
IssueViewFavoriteViewSet.as_view(
|
||||
@@ -803,6 +767,11 @@ urlpatterns = [
|
||||
),
|
||||
name="project-issue",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/issues/",
|
||||
WorkSpaceIssuesEndpoint.as_view(),
|
||||
name="workspace-issue",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/issue-labels/",
|
||||
LabelViewSet.as_view(
|
||||
@@ -1069,7 +1038,6 @@ urlpatterns = [
|
||||
IssueDraftViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
name="project-issue-draft",
|
||||
@@ -1079,7 +1047,6 @@ urlpatterns = [
|
||||
IssueDraftViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
}
|
||||
),
|
||||
|
||||
@@ -56,7 +56,7 @@ from .workspace import (
|
||||
LeaveWorkspaceEndpoint,
|
||||
)
|
||||
from .state import StateViewSet
|
||||
from .view import GlobalViewViewSet, GlobalViewIssuesViewSet, IssueViewViewSet, ViewIssuesEndpoint, IssueViewFavoriteViewSet
|
||||
from .view import IssueViewViewSet, ViewIssuesEndpoint, IssueViewFavoriteViewSet
|
||||
from .cycle import (
|
||||
CycleViewSet,
|
||||
CycleIssueViewSet,
|
||||
|
||||
@@ -80,7 +80,6 @@ class CycleViewSet(BaseViewSet):
|
||||
issue_id=str(self.kwargs.get("pk", None)),
|
||||
project_id=str(self.kwargs.get("project_id", None)),
|
||||
current_instance=None,
|
||||
epoch = int(timezone.now().timestamp())
|
||||
)
|
||||
|
||||
return super().perform_destroy(instance)
|
||||
@@ -488,7 +487,6 @@ class CycleIssueViewSet(BaseViewSet):
|
||||
issue_id=str(self.kwargs.get("pk", None)),
|
||||
project_id=str(self.kwargs.get("project_id", None)),
|
||||
current_instance=None,
|
||||
epoch = int(timezone.now().timestamp())
|
||||
)
|
||||
return super().perform_destroy(instance)
|
||||
|
||||
@@ -519,7 +517,6 @@ class CycleIssueViewSet(BaseViewSet):
|
||||
try:
|
||||
order_by = request.GET.get("order_by", "created_at")
|
||||
group_by = request.GET.get("group_by", False)
|
||||
sub_group_by = request.GET.get("sub_group_by", False)
|
||||
filters = issue_filters(request.query_params, "GET")
|
||||
issues = (
|
||||
Issue.issue_objects.filter(issue_cycle__cycle_id=cycle_id)
|
||||
@@ -558,15 +555,9 @@ class CycleIssueViewSet(BaseViewSet):
|
||||
|
||||
issues_data = IssueStateSerializer(issues, many=True).data
|
||||
|
||||
if sub_group_by and sub_group_by == group_by:
|
||||
return Response(
|
||||
{"error": "Group by and sub group by cannot be same"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
if group_by:
|
||||
return Response(
|
||||
group_results(issues_data, group_by, sub_group_by),
|
||||
group_results(issues_data, group_by),
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
@@ -664,7 +655,6 @@ class CycleIssueViewSet(BaseViewSet):
|
||||
),
|
||||
}
|
||||
),
|
||||
epoch = int(timezone.now().timestamp())
|
||||
)
|
||||
|
||||
# Return all Cycle Issues
|
||||
|
||||
@@ -213,7 +213,6 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
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(
|
||||
@@ -278,7 +277,6 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
IssueSerializer(current_instance).data,
|
||||
cls=DjangoJSONEncoder,
|
||||
),
|
||||
epoch = int(timezone.now().timestamp())
|
||||
)
|
||||
issue_serializer.save()
|
||||
else:
|
||||
@@ -520,7 +518,6 @@ class InboxIssuePublicViewSet(BaseViewSet):
|
||||
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(
|
||||
@@ -585,7 +582,6 @@ class InboxIssuePublicViewSet(BaseViewSet):
|
||||
IssueSerializer(current_instance).data,
|
||||
cls=DjangoJSONEncoder,
|
||||
),
|
||||
epoch = int(timezone.now().timestamp())
|
||||
)
|
||||
issue_serializer.save()
|
||||
return Response(issue_serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@@ -4,7 +4,6 @@ import random
|
||||
from itertools import chain
|
||||
|
||||
# Django imports
|
||||
from django.utils import timezone
|
||||
from django.db.models import (
|
||||
Prefetch,
|
||||
OuterRef,
|
||||
@@ -54,7 +53,6 @@ from plane.api.serializers import (
|
||||
CommentReactionSerializer,
|
||||
IssueVoteSerializer,
|
||||
IssueRelationSerializer,
|
||||
RelatedIssueSerializer,
|
||||
IssuePublicSerializer,
|
||||
)
|
||||
from plane.api.permissions import (
|
||||
@@ -130,7 +128,6 @@ class IssueViewSet(BaseViewSet):
|
||||
current_instance=json.dumps(
|
||||
IssueSerializer(current_instance).data, cls=DjangoJSONEncoder
|
||||
),
|
||||
epoch = int(timezone.now().timestamp())
|
||||
)
|
||||
|
||||
return super().perform_update(serializer)
|
||||
@@ -151,7 +148,6 @@ class IssueViewSet(BaseViewSet):
|
||||
current_instance=json.dumps(
|
||||
IssueSerializer(current_instance).data, cls=DjangoJSONEncoder
|
||||
),
|
||||
epoch = int(timezone.now().timestamp())
|
||||
)
|
||||
return super().perform_destroy(instance)
|
||||
|
||||
@@ -273,16 +269,9 @@ class IssueViewSet(BaseViewSet):
|
||||
|
||||
## Grouping the results
|
||||
group_by = request.GET.get("group_by", False)
|
||||
sub_group_by = request.GET.get("sub_group_by", False)
|
||||
if sub_group_by and sub_group_by == group_by:
|
||||
return Response(
|
||||
{"error": "Group by and sub group by cannot be same"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
if group_by:
|
||||
return Response(
|
||||
group_results(issues, group_by, sub_group_by), status=status.HTTP_200_OK
|
||||
group_results(issues, group_by), status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
return Response(issues, status=status.HTTP_200_OK)
|
||||
@@ -318,7 +307,6 @@ class IssueViewSet(BaseViewSet):
|
||||
issue_id=str(serializer.data.get("id", None)),
|
||||
project_id=str(project_id),
|
||||
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)
|
||||
@@ -330,12 +318,7 @@ class IssueViewSet(BaseViewSet):
|
||||
|
||||
def retrieve(self, request, slug, project_id, pk=None):
|
||||
try:
|
||||
issue = 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")
|
||||
).get(
|
||||
issue = Issue.issue_objects.get(
|
||||
workspace__slug=slug, project_id=project_id, pk=pk
|
||||
)
|
||||
return Response(IssueSerializer(issue).data, status=status.HTTP_200_OK)
|
||||
@@ -463,16 +446,9 @@ class UserWorkSpaceIssues(BaseAPIView):
|
||||
|
||||
## Grouping the results
|
||||
group_by = request.GET.get("group_by", False)
|
||||
sub_group_by = request.GET.get("sub_group_by", False)
|
||||
if sub_group_by and sub_group_by == group_by:
|
||||
return Response(
|
||||
{"error": "Group by and sub group by cannot be same"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
if group_by:
|
||||
return Response(
|
||||
group_results(issues, group_by, sub_group_by), status=status.HTTP_200_OK
|
||||
group_results(issues, group_by), status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
return Response(issues, status=status.HTTP_200_OK)
|
||||
@@ -518,7 +494,7 @@ class IssueActivityEndpoint(BaseAPIView):
|
||||
issue_activities = (
|
||||
IssueActivity.objects.filter(issue_id=issue_id)
|
||||
.filter(
|
||||
~Q(field__in=["comment", "vote", "reaction", "draft"]),
|
||||
~Q(field__in=["comment", "vote", "reaction"]),
|
||||
project__project_projectmember__member=self.request.user,
|
||||
)
|
||||
.select_related("actor", "workspace", "issue", "project")
|
||||
@@ -577,7 +553,6 @@ class IssueCommentViewSet(BaseViewSet):
|
||||
issue_id=str(self.kwargs.get("issue_id")),
|
||||
project_id=str(self.kwargs.get("project_id")),
|
||||
current_instance=None,
|
||||
epoch = int(timezone.now().timestamp())
|
||||
)
|
||||
|
||||
def perform_update(self, serializer):
|
||||
@@ -596,7 +571,6 @@ class IssueCommentViewSet(BaseViewSet):
|
||||
IssueCommentSerializer(current_instance).data,
|
||||
cls=DjangoJSONEncoder,
|
||||
),
|
||||
epoch = int(timezone.now().timestamp())
|
||||
)
|
||||
|
||||
return super().perform_update(serializer)
|
||||
@@ -618,7 +592,6 @@ class IssueCommentViewSet(BaseViewSet):
|
||||
IssueCommentSerializer(current_instance).data,
|
||||
cls=DjangoJSONEncoder,
|
||||
),
|
||||
epoch = int(timezone.now().timestamp())
|
||||
)
|
||||
return super().perform_destroy(instance)
|
||||
|
||||
@@ -902,7 +875,6 @@ class IssueLinkViewSet(BaseViewSet):
|
||||
issue_id=str(self.kwargs.get("issue_id")),
|
||||
project_id=str(self.kwargs.get("project_id")),
|
||||
current_instance=None,
|
||||
epoch = int(timezone.now().timestamp())
|
||||
)
|
||||
|
||||
def perform_update(self, serializer):
|
||||
@@ -921,7 +893,6 @@ class IssueLinkViewSet(BaseViewSet):
|
||||
IssueLinkSerializer(current_instance).data,
|
||||
cls=DjangoJSONEncoder,
|
||||
),
|
||||
epoch = int(timezone.now().timestamp())
|
||||
)
|
||||
|
||||
return super().perform_update(serializer)
|
||||
@@ -943,7 +914,6 @@ class IssueLinkViewSet(BaseViewSet):
|
||||
IssueLinkSerializer(current_instance).data,
|
||||
cls=DjangoJSONEncoder,
|
||||
),
|
||||
epoch = int(timezone.now().timestamp())
|
||||
)
|
||||
return super().perform_destroy(instance)
|
||||
|
||||
@@ -1022,7 +992,6 @@ class IssueAttachmentEndpoint(BaseAPIView):
|
||||
serializer.data,
|
||||
cls=DjangoJSONEncoder,
|
||||
),
|
||||
epoch = int(timezone.now().timestamp())
|
||||
)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
@@ -1045,7 +1014,6 @@ class IssueAttachmentEndpoint(BaseAPIView):
|
||||
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(status=status.HTTP_204_NO_CONTENT)
|
||||
@@ -1248,7 +1216,6 @@ class IssueArchiveViewSet(BaseViewSet):
|
||||
issue_id=str(issue.id),
|
||||
project_id=str(project_id),
|
||||
current_instance=None,
|
||||
epoch = int(timezone.now().timestamp())
|
||||
)
|
||||
|
||||
return Response(IssueSerializer(issue).data, status=status.HTTP_200_OK)
|
||||
@@ -1453,7 +1420,6 @@ class IssueReactionViewSet(BaseViewSet):
|
||||
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())
|
||||
)
|
||||
|
||||
def destroy(self, request, slug, project_id, issue_id, reaction_code):
|
||||
@@ -1477,7 +1443,6 @@ class IssueReactionViewSet(BaseViewSet):
|
||||
"identifier": str(issue_reaction.id),
|
||||
}
|
||||
),
|
||||
epoch = int(timezone.now().timestamp())
|
||||
)
|
||||
issue_reaction.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
@@ -1526,7 +1491,6 @@ class CommentReactionViewSet(BaseViewSet):
|
||||
issue_id=None,
|
||||
project_id=str(self.kwargs.get("project_id", None)),
|
||||
current_instance=None,
|
||||
epoch = int(timezone.now().timestamp())
|
||||
)
|
||||
|
||||
def destroy(self, request, slug, project_id, comment_id, reaction_code):
|
||||
@@ -1551,7 +1515,6 @@ class CommentReactionViewSet(BaseViewSet):
|
||||
"comment_id": str(comment_id),
|
||||
}
|
||||
),
|
||||
epoch = int(timezone.now().timestamp())
|
||||
)
|
||||
comment_reaction.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
@@ -1648,7 +1611,6 @@ class IssueCommentPublicViewSet(BaseViewSet):
|
||||
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,
|
||||
@@ -1698,7 +1660,6 @@ class IssueCommentPublicViewSet(BaseViewSet):
|
||||
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)
|
||||
@@ -1732,7 +1693,6 @@ class IssueCommentPublicViewSet(BaseViewSet):
|
||||
IssueCommentSerializer(comment).data,
|
||||
cls=DjangoJSONEncoder,
|
||||
),
|
||||
epoch = int(timezone.now().timestamp())
|
||||
)
|
||||
comment.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
@@ -1807,7 +1767,6 @@ class IssueReactionPublicViewSet(BaseViewSet):
|
||||
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)
|
||||
@@ -1852,7 +1811,6 @@ class IssueReactionPublicViewSet(BaseViewSet):
|
||||
"identifier": str(issue_reaction.id),
|
||||
}
|
||||
),
|
||||
epoch = int(timezone.now().timestamp())
|
||||
)
|
||||
issue_reaction.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
@@ -1926,7 +1884,6 @@ class CommentReactionPublicViewSet(BaseViewSet):
|
||||
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)
|
||||
@@ -1978,7 +1935,6 @@ class CommentReactionPublicViewSet(BaseViewSet):
|
||||
"comment_id": str(comment_id),
|
||||
}
|
||||
),
|
||||
epoch = int(timezone.now().timestamp())
|
||||
)
|
||||
comment_reaction.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
@@ -2042,7 +1998,6 @@ class IssueVotePublicViewSet(BaseViewSet):
|
||||
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)
|
||||
@@ -2077,7 +2032,6 @@ class IssueVotePublicViewSet(BaseViewSet):
|
||||
"identifier": str(issue_vote.id),
|
||||
}
|
||||
),
|
||||
epoch = int(timezone.now().timestamp())
|
||||
)
|
||||
issue_vote.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
@@ -2111,17 +2065,15 @@ class IssueRelationViewSet(BaseViewSet):
|
||||
IssueRelationSerializer(current_instance).data,
|
||||
cls=DjangoJSONEncoder,
|
||||
),
|
||||
epoch = int(timezone.now().timestamp())
|
||||
)
|
||||
return super().perform_destroy(instance)
|
||||
|
||||
def create(self, request, slug, project_id, issue_id):
|
||||
try:
|
||||
related_list = request.data.get("related_list", [])
|
||||
relation = request.data.get("relation", None)
|
||||
project = Project.objects.get(pk=project_id)
|
||||
|
||||
issue_relation = IssueRelation.objects.bulk_create(
|
||||
issueRelation = IssueRelation.objects.bulk_create(
|
||||
[
|
||||
IssueRelation(
|
||||
issue_id=related_issue["issue"],
|
||||
@@ -2145,19 +2097,12 @@ class IssueRelationViewSet(BaseViewSet):
|
||||
issue_id=str(issue_id),
|
||||
project_id=str(project_id),
|
||||
current_instance=None,
|
||||
epoch = int(timezone.now().timestamp())
|
||||
)
|
||||
|
||||
if relation == "blocking":
|
||||
return Response(
|
||||
RelatedIssueSerializer(issue_relation, many=True).data,
|
||||
status=status.HTTP_201_CREATED,
|
||||
)
|
||||
else:
|
||||
return Response(
|
||||
IssueRelationSerializer(issue_relation, many=True).data,
|
||||
status=status.HTTP_201_CREATED,
|
||||
)
|
||||
|
||||
return Response(
|
||||
IssueRelationSerializer(issueRelation, many=True).data,
|
||||
status=status.HTTP_201_CREATED,
|
||||
)
|
||||
except IntegrityError as e:
|
||||
if "already exists" in str(e):
|
||||
return Response(
|
||||
@@ -2190,8 +2135,6 @@ class IssueRelationViewSet(BaseViewSet):
|
||||
.select_related("issue")
|
||||
.distinct()
|
||||
)
|
||||
|
||||
|
||||
class IssueRetrievePublicEndpoint(BaseAPIView):
|
||||
permission_classes = [
|
||||
AllowAny,
|
||||
@@ -2401,48 +2344,6 @@ class IssueDraftViewSet(BaseViewSet):
|
||||
serializer_class = IssueFlatSerializer
|
||||
model = Issue
|
||||
|
||||
|
||||
def perform_update(self, serializer):
|
||||
requested_data = json.dumps(self.request.data, cls=DjangoJSONEncoder)
|
||||
current_instance = (
|
||||
self.get_queryset().filter(pk=self.kwargs.get("pk", None)).first()
|
||||
)
|
||||
if current_instance is not None:
|
||||
issue_activity.delay(
|
||||
type="issue_draft.activity.updated",
|
||||
requested_data=requested_data,
|
||||
actor_id=str(self.request.user.id),
|
||||
issue_id=str(self.kwargs.get("pk", None)),
|
||||
project_id=str(self.kwargs.get("project_id", None)),
|
||||
current_instance=json.dumps(
|
||||
IssueSerializer(current_instance).data, cls=DjangoJSONEncoder
|
||||
),
|
||||
epoch = int(timezone.now().timestamp())
|
||||
)
|
||||
|
||||
return super().perform_update(serializer)
|
||||
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
current_instance = (
|
||||
self.get_queryset().filter(pk=self.kwargs.get("pk", None)).first()
|
||||
)
|
||||
if current_instance is not None:
|
||||
issue_activity.delay(
|
||||
type="issue_draft.activity.deleted",
|
||||
requested_data=json.dumps(
|
||||
{"issue_id": str(self.kwargs.get("pk", None))}
|
||||
),
|
||||
actor_id=str(self.request.user.id),
|
||||
issue_id=str(self.kwargs.get("pk", None)),
|
||||
project_id=str(self.kwargs.get("project_id", None)),
|
||||
current_instance=json.dumps(
|
||||
IssueSerializer(current_instance).data, cls=DjangoJSONEncoder
|
||||
),
|
||||
)
|
||||
return super().perform_destroy(instance)
|
||||
|
||||
|
||||
def get_queryset(self):
|
||||
return (
|
||||
Issue.objects.annotate(
|
||||
@@ -2468,7 +2369,6 @@ class IssueDraftViewSet(BaseViewSet):
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@method_decorator(gzip_page)
|
||||
def list(self, request, slug, project_id):
|
||||
try:
|
||||
@@ -2578,41 +2478,6 @@ class IssueDraftViewSet(BaseViewSet):
|
||||
)
|
||||
|
||||
|
||||
def create(self, request, slug, project_id):
|
||||
try:
|
||||
project = Project.objects.get(pk=project_id)
|
||||
|
||||
serializer = IssueCreateSerializer(
|
||||
data=request.data,
|
||||
context={
|
||||
"project_id": project_id,
|
||||
"workspace_id": project.workspace_id,
|
||||
"default_assignee_id": project.default_assignee_id,
|
||||
},
|
||||
)
|
||||
|
||||
if serializer.is_valid():
|
||||
serializer.save(is_draft=True)
|
||||
|
||||
# Track the issue
|
||||
issue_activity.delay(
|
||||
type="issue_draft.activity.created",
|
||||
requested_data=json.dumps(self.request.data, cls=DjangoJSONEncoder),
|
||||
actor_id=str(request.user.id),
|
||||
issue_id=str(serializer.data.get("id", None)),
|
||||
project_id=str(project_id),
|
||||
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)
|
||||
|
||||
except Project.DoesNotExist:
|
||||
return Response(
|
||||
{"error": "Project was not found"}, status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
|
||||
|
||||
def retrieve(self, request, slug, project_id, pk=None):
|
||||
try:
|
||||
issue = Issue.objects.get(
|
||||
|
||||
@@ -2,7 +2,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
|
||||
@@ -130,7 +129,6 @@ class ModuleViewSet(BaseViewSet):
|
||||
issue_id=str(self.kwargs.get("pk", None)),
|
||||
project_id=str(self.kwargs.get("project_id", None)),
|
||||
current_instance=None,
|
||||
epoch = int(timezone.now().timestamp())
|
||||
)
|
||||
|
||||
return super().perform_destroy(instance)
|
||||
@@ -279,7 +277,6 @@ class ModuleIssueViewSet(BaseViewSet):
|
||||
issue_id=str(self.kwargs.get("pk", None)),
|
||||
project_id=str(self.kwargs.get("project_id", None)),
|
||||
current_instance=None,
|
||||
epoch = int(timezone.now().timestamp())
|
||||
)
|
||||
return super().perform_destroy(instance)
|
||||
|
||||
@@ -311,7 +308,6 @@ class ModuleIssueViewSet(BaseViewSet):
|
||||
try:
|
||||
order_by = request.GET.get("order_by", "created_at")
|
||||
group_by = request.GET.get("group_by", False)
|
||||
sub_group_by = request.GET.get("sub_group_by", False)
|
||||
filters = issue_filters(request.query_params, "GET")
|
||||
issues = (
|
||||
Issue.issue_objects.filter(issue_module__module_id=module_id)
|
||||
@@ -350,15 +346,9 @@ class ModuleIssueViewSet(BaseViewSet):
|
||||
|
||||
issues_data = IssueStateSerializer(issues, many=True).data
|
||||
|
||||
if sub_group_by and sub_group_by == group_by:
|
||||
return Response(
|
||||
{"error": "Group by and sub group by cannot be same"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
if group_by:
|
||||
return Response(
|
||||
group_results(issues_data, group_by, sub_group_by),
|
||||
group_results(issues_data, group_by),
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
@@ -447,7 +437,6 @@ class ModuleIssueViewSet(BaseViewSet):
|
||||
),
|
||||
}
|
||||
),
|
||||
epoch = int(timezone.now().timestamp())
|
||||
)
|
||||
|
||||
return Response(
|
||||
|
||||
@@ -1,18 +1,4 @@
|
||||
# Django imports
|
||||
from django.db.models import (
|
||||
Prefetch,
|
||||
OuterRef,
|
||||
Func,
|
||||
F,
|
||||
Case,
|
||||
Value,
|
||||
CharField,
|
||||
When,
|
||||
Exists,
|
||||
Max,
|
||||
)
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.gzip import gzip_page
|
||||
from django.db import IntegrityError
|
||||
from django.db.models import Prefetch, OuterRef, Exists
|
||||
|
||||
@@ -24,192 +10,18 @@ from sentry_sdk import capture_exception
|
||||
# Module imports
|
||||
from . import BaseViewSet, BaseAPIView
|
||||
from plane.api.serializers import (
|
||||
GlobalViewSerializer,
|
||||
IssueViewSerializer,
|
||||
IssueLiteSerializer,
|
||||
IssueViewFavoriteSerializer,
|
||||
)
|
||||
from plane.api.permissions import WorkspaceEntityPermission, ProjectEntityPermission
|
||||
from plane.api.permissions import ProjectEntityPermission
|
||||
from plane.db.models import (
|
||||
Workspace,
|
||||
GlobalView,
|
||||
IssueView,
|
||||
Issue,
|
||||
IssueViewFavorite,
|
||||
IssueReaction,
|
||||
IssueLink,
|
||||
IssueAttachment,
|
||||
)
|
||||
from plane.utils.issue_filters import issue_filters
|
||||
from plane.utils.grouper import group_results
|
||||
|
||||
|
||||
class GlobalViewViewSet(BaseViewSet):
|
||||
serializer_class = GlobalViewSerializer
|
||||
model = GlobalView
|
||||
permission_classes = [
|
||||
WorkspaceEntityPermission,
|
||||
]
|
||||
|
||||
def perform_create(self, serializer):
|
||||
workspace = Workspace.objects.get(slug=self.kwargs.get("slug"))
|
||||
serializer.save(workspace_id=workspace.id)
|
||||
|
||||
def get_queryset(self):
|
||||
return self.filter_queryset(
|
||||
super()
|
||||
.get_queryset()
|
||||
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||
.select_related("workspace")
|
||||
.order_by("-created_at")
|
||||
.distinct()
|
||||
)
|
||||
|
||||
|
||||
class GlobalViewIssuesViewSet(BaseViewSet):
|
||||
permission_classes = [
|
||||
WorkspaceEntityPermission,
|
||||
]
|
||||
|
||||
def get_queryset(self):
|
||||
return (
|
||||
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(workspace__slug=self.kwargs.get("slug"))
|
||||
.select_related("project")
|
||||
.select_related("workspace")
|
||||
.select_related("state")
|
||||
.select_related("parent")
|
||||
.prefetch_related("assignees")
|
||||
.prefetch_related("labels")
|
||||
.prefetch_related(
|
||||
Prefetch(
|
||||
"issue_reactions",
|
||||
queryset=IssueReaction.objects.select_related("actor"),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@method_decorator(gzip_page)
|
||||
def list(self, request, slug):
|
||||
try:
|
||||
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 = (
|
||||
self.get_queryset()
|
||||
.filter(**filters)
|
||||
.filter(project__project_projectmember__member=self.request.user)
|
||||
.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 = IssueLiteSerializer(issue_queryset, many=True).data
|
||||
|
||||
## Grouping the results
|
||||
group_by = request.GET.get("group_by", False)
|
||||
sub_group_by = request.GET.get("sub_group_by", False)
|
||||
if sub_group_by and sub_group_by == group_by:
|
||||
return Response(
|
||||
{"error": "Group by and sub group by cannot be same"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
if group_by:
|
||||
return Response(
|
||||
group_results(issues, group_by, sub_group_by), status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
return Response(issues, status=status.HTTP_200_OK)
|
||||
|
||||
except Exception as e:
|
||||
capture_exception(e)
|
||||
return Response(
|
||||
{"error": "Something went wrong please try again later"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
||||
class IssueViewViewSet(BaseViewSet):
|
||||
|
||||
@@ -39,7 +39,6 @@ def track_name(
|
||||
project,
|
||||
actor,
|
||||
issue_activities,
|
||||
epoch
|
||||
):
|
||||
if current_instance.get("name") != requested_data.get("name"):
|
||||
issue_activities.append(
|
||||
@@ -53,7 +52,6 @@ def track_name(
|
||||
project=project,
|
||||
workspace=project.workspace,
|
||||
comment=f"updated the name to {requested_data.get('name')}",
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -66,7 +64,6 @@ def track_parent(
|
||||
project,
|
||||
actor,
|
||||
issue_activities,
|
||||
epoch
|
||||
):
|
||||
if current_instance.get("parent") != requested_data.get("parent"):
|
||||
if requested_data.get("parent") == None:
|
||||
@@ -84,7 +81,6 @@ def track_parent(
|
||||
comment=f"updated the parent issue to None",
|
||||
old_identifier=old_parent.id,
|
||||
new_identifier=None,
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
else:
|
||||
@@ -105,7 +101,6 @@ def track_parent(
|
||||
comment=f"updated the parent issue to {new_parent.name}",
|
||||
old_identifier=old_parent.id if old_parent is not None else None,
|
||||
new_identifier=new_parent.id,
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -118,7 +113,6 @@ def track_priority(
|
||||
project,
|
||||
actor,
|
||||
issue_activities,
|
||||
epoch
|
||||
):
|
||||
if current_instance.get("priority") != requested_data.get("priority"):
|
||||
if requested_data.get("priority") == None:
|
||||
@@ -133,7 +127,6 @@ def track_priority(
|
||||
project=project,
|
||||
workspace=project.workspace,
|
||||
comment=f"updated the priority to None",
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
else:
|
||||
@@ -148,7 +141,6 @@ def track_priority(
|
||||
project=project,
|
||||
workspace=project.workspace,
|
||||
comment=f"updated the priority to {requested_data.get('priority')}",
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -161,7 +153,6 @@ def track_state(
|
||||
project,
|
||||
actor,
|
||||
issue_activities,
|
||||
epoch
|
||||
):
|
||||
if current_instance.get("state") != requested_data.get("state"):
|
||||
new_state = State.objects.get(pk=requested_data.get("state", None))
|
||||
@@ -180,7 +171,6 @@ def track_state(
|
||||
comment=f"updated the state to {new_state.name}",
|
||||
old_identifier=old_state.id,
|
||||
new_identifier=new_state.id,
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -193,7 +183,6 @@ def track_description(
|
||||
project,
|
||||
actor,
|
||||
issue_activities,
|
||||
epoch
|
||||
):
|
||||
if current_instance.get("description_html") != requested_data.get(
|
||||
"description_html"
|
||||
@@ -214,7 +203,6 @@ def track_description(
|
||||
project=project,
|
||||
workspace=project.workspace,
|
||||
comment=f"updated the description to {requested_data.get('description_html')}",
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -227,7 +215,6 @@ def track_target_date(
|
||||
project,
|
||||
actor,
|
||||
issue_activities,
|
||||
epoch
|
||||
):
|
||||
if current_instance.get("target_date") != requested_data.get("target_date"):
|
||||
if requested_data.get("target_date") == None:
|
||||
@@ -242,7 +229,6 @@ def track_target_date(
|
||||
project=project,
|
||||
workspace=project.workspace,
|
||||
comment=f"updated the target date to None",
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
else:
|
||||
@@ -257,7 +243,6 @@ def track_target_date(
|
||||
project=project,
|
||||
workspace=project.workspace,
|
||||
comment=f"updated the target date to {requested_data.get('target_date')}",
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -270,7 +255,6 @@ def track_start_date(
|
||||
project,
|
||||
actor,
|
||||
issue_activities,
|
||||
epoch
|
||||
):
|
||||
if current_instance.get("start_date") != requested_data.get("start_date"):
|
||||
if requested_data.get("start_date") == None:
|
||||
@@ -285,7 +269,6 @@ def track_start_date(
|
||||
project=project,
|
||||
workspace=project.workspace,
|
||||
comment=f"updated the start date to None",
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
else:
|
||||
@@ -300,7 +283,6 @@ def track_start_date(
|
||||
project=project,
|
||||
workspace=project.workspace,
|
||||
comment=f"updated the start date to {requested_data.get('start_date')}",
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -313,7 +295,6 @@ def track_labels(
|
||||
project,
|
||||
actor,
|
||||
issue_activities,
|
||||
epoch
|
||||
):
|
||||
# Label Addition
|
||||
if len(requested_data.get("labels_list")) > len(current_instance.get("labels")):
|
||||
@@ -333,7 +314,6 @@ def track_labels(
|
||||
comment=f"added label {label.name}",
|
||||
new_identifier=label.id,
|
||||
old_identifier=None,
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -355,7 +335,6 @@ def track_labels(
|
||||
comment=f"removed label {label.name}",
|
||||
old_identifier=label.id,
|
||||
new_identifier=None,
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -368,7 +347,6 @@ def track_assignees(
|
||||
project,
|
||||
actor,
|
||||
issue_activities,
|
||||
epoch
|
||||
):
|
||||
# Assignee Addition
|
||||
if len(requested_data.get("assignees_list")) > len(
|
||||
@@ -389,7 +367,6 @@ def track_assignees(
|
||||
workspace=project.workspace,
|
||||
comment=f"added assignee {assignee.display_name}",
|
||||
new_identifier=assignee.id,
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -412,29 +389,27 @@ def track_assignees(
|
||||
workspace=project.workspace,
|
||||
comment=f"removed assignee {assignee.display_name}",
|
||||
old_identifier=assignee.id,
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def create_issue_activity(
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities, epoch
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities
|
||||
):
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
issue_id=issue_id,
|
||||
project=project,
|
||||
workspace=project.workspace,
|
||||
comment=f"created the issue",
|
||||
verb="created",
|
||||
actor=actor,
|
||||
epoch=epoch,
|
||||
)
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
issue_id=issue_id,
|
||||
project=project,
|
||||
workspace=project.workspace,
|
||||
comment=f"created the issue",
|
||||
verb="created",
|
||||
actor=actor,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def track_estimate_points(
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities, epoch
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities
|
||||
):
|
||||
if current_instance.get("estimate_point") != requested_data.get("estimate_point"):
|
||||
if requested_data.get("estimate_point") == None:
|
||||
@@ -449,7 +424,6 @@ def track_estimate_points(
|
||||
project=project,
|
||||
workspace=project.workspace,
|
||||
comment=f"updated the estimate point to None",
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
else:
|
||||
@@ -464,13 +438,12 @@ def track_estimate_points(
|
||||
project=project,
|
||||
workspace=project.workspace,
|
||||
comment=f"updated the estimate point to {requested_data.get('estimate_point')}",
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def track_archive_at(
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities, epoch
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities
|
||||
):
|
||||
if requested_data.get("archived_at") is None:
|
||||
issue_activities.append(
|
||||
@@ -484,7 +457,6 @@ def track_archive_at(
|
||||
field="archived_at",
|
||||
old_value="archive",
|
||||
new_value="restore",
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
else:
|
||||
@@ -499,13 +471,12 @@ def track_archive_at(
|
||||
field="archived_at",
|
||||
old_value=None,
|
||||
new_value="archive",
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def track_closed_to(
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities, epoch
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities
|
||||
):
|
||||
if requested_data.get("closed_to") is not None:
|
||||
updated_state = State.objects.get(
|
||||
@@ -525,13 +496,12 @@ def track_closed_to(
|
||||
comment=f"Plane updated the state to {updated_state.name}",
|
||||
old_identifier=None,
|
||||
new_identifier=updated_state.id,
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def update_issue_activity(
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities, epoch
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities
|
||||
):
|
||||
ISSUE_ACTIVITY_MAPPER = {
|
||||
"name": track_name,
|
||||
@@ -563,12 +533,11 @@ def update_issue_activity(
|
||||
project,
|
||||
actor,
|
||||
issue_activities,
|
||||
epoch
|
||||
)
|
||||
|
||||
|
||||
def delete_issue_activity(
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities, epoch
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities
|
||||
):
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
@@ -578,13 +547,12 @@ def delete_issue_activity(
|
||||
verb="deleted",
|
||||
actor=actor,
|
||||
field="issue",
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def create_comment_activity(
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities, epoch
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities
|
||||
):
|
||||
requested_data = json.loads(requested_data) if requested_data is not None else None
|
||||
current_instance = (
|
||||
@@ -603,13 +571,12 @@ def create_comment_activity(
|
||||
new_value=requested_data.get("comment_html", ""),
|
||||
new_identifier=requested_data.get("id", None),
|
||||
issue_comment_id=requested_data.get("id", None),
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def update_comment_activity(
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities, epoch
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities
|
||||
):
|
||||
requested_data = json.loads(requested_data) if requested_data is not None else None
|
||||
current_instance = (
|
||||
@@ -631,13 +598,12 @@ def update_comment_activity(
|
||||
new_value=requested_data.get("comment_html", ""),
|
||||
new_identifier=current_instance.get("id", None),
|
||||
issue_comment_id=current_instance.get("id", None),
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def delete_comment_activity(
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities, epoch
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities
|
||||
):
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
@@ -648,13 +614,12 @@ def delete_comment_activity(
|
||||
verb="deleted",
|
||||
actor=actor,
|
||||
field="comment",
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def create_cycle_issue_activity(
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities, epoch
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities
|
||||
):
|
||||
requested_data = json.loads(requested_data) if requested_data is not None else None
|
||||
current_instance = (
|
||||
@@ -686,7 +651,6 @@ def create_cycle_issue_activity(
|
||||
comment=f"updated cycle from {old_cycle.name} to {new_cycle.name}",
|
||||
old_identifier=old_cycle.id,
|
||||
new_identifier=new_cycle.id,
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -707,13 +671,12 @@ def create_cycle_issue_activity(
|
||||
workspace=project.workspace,
|
||||
comment=f"added cycle {cycle.name}",
|
||||
new_identifier=cycle.id,
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def delete_cycle_issue_activity(
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities, epoch
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities
|
||||
):
|
||||
requested_data = json.loads(requested_data) if requested_data is not None else None
|
||||
current_instance = (
|
||||
@@ -737,13 +700,12 @@ def delete_cycle_issue_activity(
|
||||
workspace=project.workspace,
|
||||
comment=f"removed this issue from {cycle.name if cycle is not None else None}",
|
||||
old_identifier=cycle.id if cycle is not None else None,
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def create_module_issue_activity(
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities, epoch
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities
|
||||
):
|
||||
requested_data = json.loads(requested_data) if requested_data is not None else None
|
||||
current_instance = (
|
||||
@@ -775,7 +737,6 @@ def create_module_issue_activity(
|
||||
comment=f"updated module from {old_module.name} to {new_module.name}",
|
||||
old_identifier=old_module.id,
|
||||
new_identifier=new_module.id,
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -795,13 +756,12 @@ def create_module_issue_activity(
|
||||
workspace=project.workspace,
|
||||
comment=f"added module {module.name}",
|
||||
new_identifier=module.id,
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def delete_module_issue_activity(
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities, epoch
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities
|
||||
):
|
||||
requested_data = json.loads(requested_data) if requested_data is not None else None
|
||||
current_instance = (
|
||||
@@ -825,13 +785,12 @@ def delete_module_issue_activity(
|
||||
workspace=project.workspace,
|
||||
comment=f"removed this issue from {module.name if module is not None else None}",
|
||||
old_identifier=module.id if module is not None else None,
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def create_link_activity(
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities, epoch
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities
|
||||
):
|
||||
requested_data = json.loads(requested_data) if requested_data is not None else None
|
||||
current_instance = (
|
||||
@@ -849,13 +808,12 @@ def create_link_activity(
|
||||
field="link",
|
||||
new_value=requested_data.get("url", ""),
|
||||
new_identifier=requested_data.get("id", None),
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def update_link_activity(
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities, epoch
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities
|
||||
):
|
||||
requested_data = json.loads(requested_data) if requested_data is not None else None
|
||||
current_instance = (
|
||||
@@ -876,13 +834,12 @@ def update_link_activity(
|
||||
old_identifier=current_instance.get("id"),
|
||||
new_value=requested_data.get("url", ""),
|
||||
new_identifier=current_instance.get("id", None),
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def delete_link_activity(
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities, epoch
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities
|
||||
):
|
||||
|
||||
current_instance = (
|
||||
@@ -899,14 +856,13 @@ def delete_link_activity(
|
||||
actor=actor,
|
||||
field="link",
|
||||
old_value=current_instance.get("url", ""),
|
||||
new_value="",
|
||||
epoch=epoch,
|
||||
new_value=""
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def create_attachment_activity(
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities, epoch
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities
|
||||
):
|
||||
requested_data = json.loads(requested_data) if requested_data is not None else None
|
||||
current_instance = (
|
||||
@@ -924,13 +880,12 @@ def create_attachment_activity(
|
||||
field="attachment",
|
||||
new_value=current_instance.get("asset", ""),
|
||||
new_identifier=current_instance.get("id", None),
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def delete_attachment_activity(
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities, epoch
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities
|
||||
):
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
@@ -941,12 +896,11 @@ def delete_attachment_activity(
|
||||
verb="deleted",
|
||||
actor=actor,
|
||||
field="attachment",
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
def create_issue_reaction_activity(
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities, epoch
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities
|
||||
):
|
||||
requested_data = json.loads(requested_data) if requested_data is not None else None
|
||||
if requested_data and requested_data.get("reaction") is not None:
|
||||
@@ -965,13 +919,12 @@ def create_issue_reaction_activity(
|
||||
comment="added the reaction",
|
||||
old_identifier=None,
|
||||
new_identifier=issue_reaction,
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def delete_issue_reaction_activity(
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities, epoch
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities
|
||||
):
|
||||
current_instance = (
|
||||
json.loads(current_instance) if current_instance is not None else None
|
||||
@@ -990,13 +943,12 @@ def delete_issue_reaction_activity(
|
||||
comment="removed the reaction",
|
||||
old_identifier=current_instance.get("identifier"),
|
||||
new_identifier=None,
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def create_comment_reaction_activity(
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities, epoch
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities
|
||||
):
|
||||
requested_data = json.loads(requested_data) if requested_data is not None else None
|
||||
if requested_data and requested_data.get("reaction") is not None:
|
||||
@@ -1016,13 +968,12 @@ def create_comment_reaction_activity(
|
||||
comment="added the reaction",
|
||||
old_identifier=None,
|
||||
new_identifier=comment_reaction_id,
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def delete_comment_reaction_activity(
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities, epoch
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities
|
||||
):
|
||||
current_instance = (
|
||||
json.loads(current_instance) if current_instance is not None else None
|
||||
@@ -1043,13 +994,12 @@ def delete_comment_reaction_activity(
|
||||
comment="removed the reaction",
|
||||
old_identifier=current_instance.get("identifier"),
|
||||
new_identifier=None,
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def create_issue_vote_activity(
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities, epoch
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities
|
||||
):
|
||||
requested_data = json.loads(requested_data) if requested_data is not None else None
|
||||
if requested_data and requested_data.get("vote") is not None:
|
||||
@@ -1066,13 +1016,12 @@ def create_issue_vote_activity(
|
||||
comment="added the vote",
|
||||
old_identifier=None,
|
||||
new_identifier=None,
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def delete_issue_vote_activity(
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities, epoch
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities
|
||||
):
|
||||
current_instance = (
|
||||
json.loads(current_instance) if current_instance is not None else None
|
||||
@@ -1091,13 +1040,12 @@ def delete_issue_vote_activity(
|
||||
comment="removed the vote",
|
||||
old_identifier=current_instance.get("identifier"),
|
||||
new_identifier=None,
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def create_issue_relation_activity(
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities, epoch
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities
|
||||
):
|
||||
requested_data = json.loads(requested_data) if requested_data is not None else None
|
||||
current_instance = (
|
||||
@@ -1105,25 +1053,6 @@ def create_issue_relation_activity(
|
||||
)
|
||||
if current_instance is None and requested_data.get("related_list") is not None:
|
||||
for issue_relation in requested_data.get("related_list"):
|
||||
if issue_relation.get("relation_type") == "blocked_by":
|
||||
relation_type = "blocking"
|
||||
else:
|
||||
relation_type = issue_relation.get("relation_type")
|
||||
issue = Issue.objects.get(pk=issue_relation.get("issue"))
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
issue_id=issue_relation.get("related_issue"),
|
||||
actor=actor,
|
||||
verb="created",
|
||||
old_value="",
|
||||
new_value=f"{project.identifier}-{issue.sequence_id}",
|
||||
field=relation_type,
|
||||
project=project,
|
||||
workspace=project.workspace,
|
||||
comment=f'added {relation_type} relation',
|
||||
old_identifier=issue_relation.get("issue"),
|
||||
)
|
||||
)
|
||||
issue = Issue.objects.get(pk=issue_relation.get("related_issue"))
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
@@ -1136,125 +1065,36 @@ def create_issue_relation_activity(
|
||||
project=project,
|
||||
workspace=project.workspace,
|
||||
comment=f'added {issue_relation.get("relation_type")} relation',
|
||||
old_identifier=issue_relation.get("related_issue"),
|
||||
epoch=epoch,
|
||||
old_identifier=issue_relation.get("issue"),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def delete_issue_relation_activity(
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities, epoch
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities
|
||||
):
|
||||
requested_data = json.loads(requested_data) if requested_data is not None else None
|
||||
current_instance = (
|
||||
json.loads(current_instance) if current_instance is not None else None
|
||||
)
|
||||
if current_instance is not None and requested_data.get("related_list") is None:
|
||||
if current_instance.get("relation_type") == "blocked_by":
|
||||
relation_type = "blocking"
|
||||
else:
|
||||
relation_type = current_instance.get("relation_type")
|
||||
issue = Issue.objects.get(pk=current_instance.get("issue"))
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
issue_id=current_instance.get("related_issue"),
|
||||
actor=actor,
|
||||
verb="deleted",
|
||||
old_value=f"{project.identifier}-{issue.sequence_id}",
|
||||
new_value="",
|
||||
field=relation_type,
|
||||
project=project,
|
||||
workspace=project.workspace,
|
||||
comment=f'deleted {relation_type} relation',
|
||||
old_identifier=current_instance.get("issue"),
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
issue = Issue.objects.get(pk=current_instance.get("related_issue"))
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
issue_id=current_instance.get("issue"),
|
||||
actor=actor,
|
||||
verb="deleted",
|
||||
old_value=f"{project.identifier}-{issue.sequence_id}",
|
||||
new_value="",
|
||||
field=f'{current_instance.get("relation_type")}',
|
||||
project=project,
|
||||
workspace=project.workspace,
|
||||
comment=f'deleted {current_instance.get("relation_type")} relation',
|
||||
old_identifier=current_instance.get("related_issue"),
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def create_draft_issue_activity(
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities, epoch
|
||||
):
|
||||
issue = Issue.objects.get(pk=current_instance.get("issue"))
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
issue_id=issue_id,
|
||||
issue_id=current_instance.get("issue"),
|
||||
actor=actor,
|
||||
verb="deleted",
|
||||
old_value=f"{project.identifier}-{issue.sequence_id}",
|
||||
new_value="",
|
||||
field=f'{current_instance.get("relation_type")}',
|
||||
project=project,
|
||||
workspace=project.workspace,
|
||||
comment=f"drafted the issue",
|
||||
field="draft",
|
||||
verb="created",
|
||||
actor=actor,
|
||||
epoch=epoch,
|
||||
comment=f'deleted the {current_instance.get("relation_type")} relation',
|
||||
old_identifier=current_instance.get("issue"),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def update_draft_issue_activity(
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities, epoch
|
||||
):
|
||||
requested_data = json.loads(requested_data) if requested_data is not None else None
|
||||
current_instance = (
|
||||
json.loads(current_instance) if current_instance is not None else None
|
||||
)
|
||||
if requested_data.get("is_draft") is not None and requested_data.get("is_draft") == False:
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
issue_id=issue_id,
|
||||
project=project,
|
||||
workspace=project.workspace,
|
||||
comment=f"created the issue",
|
||||
verb="updated",
|
||||
actor=actor,
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
else:
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
issue_id=issue_id,
|
||||
project=project,
|
||||
workspace=project.workspace,
|
||||
comment=f"updated the draft issue",
|
||||
field="draft",
|
||||
verb="updated",
|
||||
actor=actor,
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
def delete_draft_issue_activity(
|
||||
requested_data, current_instance, issue_id, project, actor, issue_activities, epoch
|
||||
):
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
project=project,
|
||||
workspace=project.workspace,
|
||||
comment=f"deleted the draft issue",
|
||||
field="draft",
|
||||
verb="deleted",
|
||||
actor=actor,
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
# Receive message from room group
|
||||
@shared_task
|
||||
def issue_activity(
|
||||
@@ -1264,7 +1104,6 @@ def issue_activity(
|
||||
issue_id,
|
||||
actor_id,
|
||||
project_id,
|
||||
epoch,
|
||||
subscriber=True,
|
||||
):
|
||||
try:
|
||||
@@ -1327,9 +1166,6 @@ def issue_activity(
|
||||
"comment_reaction.activity.deleted": delete_comment_reaction_activity,
|
||||
"issue_vote.activity.created": create_issue_vote_activity,
|
||||
"issue_vote.activity.deleted": delete_issue_vote_activity,
|
||||
"issue_draft.activity.created": create_draft_issue_activity,
|
||||
"issue_draft.activity.updated": update_draft_issue_activity,
|
||||
"issue_draft.activity.deleted": delete_draft_issue_activity,
|
||||
}
|
||||
|
||||
func = ACTIVITY_MAPPER.get(type)
|
||||
@@ -1341,7 +1177,6 @@ def issue_activity(
|
||||
project,
|
||||
actor,
|
||||
issue_activities,
|
||||
epoch,
|
||||
)
|
||||
|
||||
# Save all the values to database
|
||||
|
||||
@@ -77,7 +77,6 @@ def archive_old_issues():
|
||||
project_id=project_id,
|
||||
current_instance=None,
|
||||
subscriber=False,
|
||||
epoch = int(timezone.now().timestamp())
|
||||
)
|
||||
for issue in updated_issues
|
||||
]
|
||||
@@ -149,7 +148,6 @@ def close_old_issues():
|
||||
project_id=project_id,
|
||||
current_instance=None,
|
||||
subscriber=False,
|
||||
epoch = int(timezone.now().timestamp())
|
||||
)
|
||||
for issue in updated_issues
|
||||
]
|
||||
|
||||
@@ -30,16 +30,6 @@ def create_issue_relation(apps, schema_editor):
|
||||
capture_exception(e)
|
||||
|
||||
|
||||
def update_issue_priority_choice(apps, schema_editor):
|
||||
IssueModel = apps.get_model("db", "Issue")
|
||||
updated_issues = []
|
||||
for obj in IssueModel.objects.all():
|
||||
if obj.priority is None:
|
||||
obj.priority = "none"
|
||||
updated_issues.append(obj)
|
||||
IssueModel.objects.bulk_update(updated_issues, ["priority"], batch_size=100)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
@@ -69,16 +59,5 @@ class Migration(migrations.Migration):
|
||||
'unique_together': {('issue', 'related_issue')},
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='issue',
|
||||
name='is_draft',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='issue',
|
||||
name='priority',
|
||||
field=models.CharField(choices=[('urgent', 'Urgent'), ('high', 'High'), ('medium', 'Medium'), ('low', 'Low'), ('none', 'None')], default='none', max_length=30, verbose_name='Issue Priority'),
|
||||
),
|
||||
migrations.RunPython(create_issue_relation),
|
||||
migrations.RunPython(update_issue_priority_choice),
|
||||
]
|
||||
|
||||
+2
-3
@@ -1,8 +1,7 @@
|
||||
# Generated by Django 4.2.3 on 2023-09-13 07:09
|
||||
# Generated by Django 4.2.3 on 2023-09-11 16:55
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def workspace_member_props(old_props):
|
||||
new_props = {
|
||||
"filters": {
|
||||
@@ -127,7 +126,7 @@ def update_module_props(apps, schema_editor):
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('db', '0043_alter_analyticview_created_by_and_more'),
|
||||
('db', '0042_alter_analyticview_created_by_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
@@ -0,0 +1,24 @@
|
||||
# Generated by Django 4.2.3 on 2023-09-12 17:33
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def update_issue_priority_choice(apps, schema_editor):
|
||||
IssueModel = apps.get_model("db", "Issue")
|
||||
updated_issues = []
|
||||
for obj in IssueModel.objects.all():
|
||||
if obj.priority is None:
|
||||
obj.priority = "none"
|
||||
updated_issues.append(obj)
|
||||
IssueModel.objects.bulk_update(updated_issues, ["priority"], batch_size=100)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('db', '0042_alter_analyticview_created_by_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(update_issue_priority_choice),
|
||||
]
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 4.2.3 on 2023-09-12 12:33
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('db', '0042_alter_analyticview_created_by_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='issue',
|
||||
name='is_draft',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
@@ -1,24 +0,0 @@
|
||||
# Generated by Django 4.2.3 on 2023-09-15 06:55
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def update_issue_activity(apps, schema_editor):
|
||||
IssueActivityModel = apps.get_model("db", "IssueActivity")
|
||||
updated_issue_activity = []
|
||||
for obj in IssueActivityModel.objects.all():
|
||||
if obj.field == "blocks":
|
||||
obj.field = "blocked_by"
|
||||
updated_issue_activity.append(obj)
|
||||
IssueActivityModel.objects.bulk_update(updated_issue_activity, ["field"], batch_size=100)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('db', '0044_auto_20230913_0709'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(update_issue_activity),
|
||||
]
|
||||
@@ -1,53 +0,0 @@
|
||||
# Generated by Django 4.2.3 on 2023-09-19 14:21
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
def update_epoch(apps, schema_editor):
|
||||
IssueActivity = apps.get_model('db', 'IssueActivity')
|
||||
updated_issue_activity = []
|
||||
for obj in IssueActivity.objects.all():
|
||||
obj.epoch = int(obj.created_at.timestamp())
|
||||
updated_issue_activity.append(obj)
|
||||
IssueActivity.objects.bulk_update(updated_issue_activity, ["epoch"], batch_size=100)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('db', '0045_auto_20230915_0655'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='GlobalView',
|
||||
fields=[
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
|
||||
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
|
||||
('name', models.CharField(max_length=255, verbose_name='View Name')),
|
||||
('description', models.TextField(blank=True, verbose_name='View Description')),
|
||||
('query', models.JSONField(verbose_name='View Query')),
|
||||
('access', models.PositiveSmallIntegerField(choices=[(0, 'Private'), (1, 'Public')], default=1)),
|
||||
('query_data', models.JSONField(default=dict)),
|
||||
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
|
||||
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
|
||||
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='global_views', to='db.workspace')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Global View',
|
||||
'verbose_name_plural': 'Global Views',
|
||||
'db_table': 'global_views',
|
||||
'ordering': ('-created_at',),
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='issueactivity',
|
||||
name='epoch',
|
||||
field=models.FloatField(null=True),
|
||||
),
|
||||
migrations.RunPython(update_epoch),
|
||||
]
|
||||
@@ -50,7 +50,7 @@ from .state import State
|
||||
|
||||
from .cycle import Cycle, CycleIssue, CycleFavorite
|
||||
|
||||
from .view import GlobalView, IssueView, IssueViewFavorite
|
||||
from .view import IssueView, IssueViewFavorite
|
||||
|
||||
from .module import Module, ModuleMember, ModuleIssue, ModuleLink, ModuleFavorite
|
||||
|
||||
|
||||
@@ -309,7 +309,6 @@ class IssueActivity(ProjectBaseModel):
|
||||
)
|
||||
old_identifier = models.UUIDField(null=True)
|
||||
new_identifier = models.UUIDField(null=True)
|
||||
epoch = models.FloatField(null=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Issue Activity"
|
||||
|
||||
@@ -25,26 +25,13 @@ ROLE_CHOICES = (
|
||||
|
||||
def get_default_props():
|
||||
return {
|
||||
"filters": {
|
||||
"priority": None,
|
||||
"state": None,
|
||||
"state_group": None,
|
||||
"assignees": None,
|
||||
"created_by": None,
|
||||
"labels": None,
|
||||
"start_date": None,
|
||||
"target_date": None,
|
||||
"subscriber": None,
|
||||
},
|
||||
"display_filters": {
|
||||
"group_by": None,
|
||||
"order_by": '-created_at',
|
||||
"type": None,
|
||||
"sub_issue": True,
|
||||
"show_empty_groups": True,
|
||||
"layout": "list",
|
||||
"calendar_date_range": "",
|
||||
},
|
||||
"filters": {"type": None},
|
||||
"orderBy": "-created_at",
|
||||
"collapsed": True,
|
||||
"issueView": "list",
|
||||
"filterIssue": None,
|
||||
"groupByProperty": None,
|
||||
"showEmptyGroups": True,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -3,30 +3,7 @@ from django.db import models
|
||||
from django.conf import settings
|
||||
|
||||
# Module import
|
||||
from . import ProjectBaseModel, BaseModel
|
||||
|
||||
|
||||
class GlobalView(BaseModel):
|
||||
workspace = models.ForeignKey(
|
||||
"db.Workspace", on_delete=models.CASCADE, related_name="global_views"
|
||||
)
|
||||
name = models.CharField(max_length=255, verbose_name="View Name")
|
||||
description = models.TextField(verbose_name="View Description", blank=True)
|
||||
query = models.JSONField(verbose_name="View Query")
|
||||
access = models.PositiveSmallIntegerField(
|
||||
default=1, choices=((0, "Private"), (1, "Public"))
|
||||
)
|
||||
query_data = models.JSONField(default=dict)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Global View"
|
||||
verbose_name_plural = "Global Views"
|
||||
db_table = "global_views"
|
||||
ordering = ("-created_at",)
|
||||
|
||||
def __str__(self):
|
||||
"""Return name of the View"""
|
||||
return f"{self.name} <{self.workspace.name}>"
|
||||
from . import ProjectBaseModel
|
||||
|
||||
|
||||
class IssueView(ProjectBaseModel):
|
||||
|
||||
@@ -16,41 +16,26 @@ ROLE_CHOICES = (
|
||||
|
||||
def get_default_props():
|
||||
return {
|
||||
"filters": {
|
||||
"priority": None,
|
||||
"state": None,
|
||||
"state_group": None,
|
||||
"assignees": None,
|
||||
"created_by": None,
|
||||
"labels": None,
|
||||
"start_date": None,
|
||||
"target_date": None,
|
||||
"subscriber": None,
|
||||
},
|
||||
"display_filters": {
|
||||
"group_by": None,
|
||||
"order_by": '-created_at',
|
||||
"type": None,
|
||||
"sub_issue": True,
|
||||
"show_empty_groups": True,
|
||||
"layout": "list",
|
||||
"calendar_date_range": "",
|
||||
},
|
||||
"display_properties": {
|
||||
"filters": {"type": None},
|
||||
"groupByProperty": None,
|
||||
"issueView": "list",
|
||||
"orderBy": "-created_at",
|
||||
"properties": {
|
||||
"assignee": True,
|
||||
"attachment_count": True,
|
||||
"created_on": True,
|
||||
"due_date": True,
|
||||
"estimate": True,
|
||||
"key": True,
|
||||
"labels": True,
|
||||
"link": True,
|
||||
"priority": True,
|
||||
"start_date": True,
|
||||
"state": True,
|
||||
"sub_issue_count": True,
|
||||
"attachment_count": True,
|
||||
"link": True,
|
||||
"estimate": True,
|
||||
"created_on": True,
|
||||
"updated_on": True,
|
||||
}
|
||||
"start_date": True,
|
||||
},
|
||||
"showEmptyGroups": True,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
"""Production settings and globals."""
|
||||
from urllib.parse import urlparse
|
||||
import ssl
|
||||
import certifi
|
||||
|
||||
import dj_database_url
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import sentry_sdk
|
||||
from sentry_sdk.integrations.django import DjangoIntegration
|
||||
@@ -89,89 +91,112 @@ if bool(os.environ.get("SENTRY_DSN", False)):
|
||||
profiles_sample_rate=1.0,
|
||||
)
|
||||
|
||||
# The AWS region to connect to.
|
||||
AWS_REGION = os.environ.get("AWS_REGION", "")
|
||||
if DOCKERIZED and USE_MINIO:
|
||||
INSTALLED_APPS += ("storages",)
|
||||
STORAGES["default"] = {"BACKEND": "storages.backends.s3boto3.S3Boto3Storage"}
|
||||
# The AWS access key to use.
|
||||
AWS_ACCESS_KEY_ID = os.environ.get("AWS_ACCESS_KEY_ID", "access-key")
|
||||
# The AWS secret access key to use.
|
||||
AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY", "secret-key")
|
||||
# The name of the bucket to store files in.
|
||||
AWS_STORAGE_BUCKET_NAME = os.environ.get("AWS_S3_BUCKET_NAME", "uploads")
|
||||
# The full URL to the S3 endpoint. Leave blank to use the default region URL.
|
||||
AWS_S3_ENDPOINT_URL = os.environ.get(
|
||||
"AWS_S3_ENDPOINT_URL", "http://plane-minio:9000"
|
||||
)
|
||||
# Default permissions
|
||||
AWS_DEFAULT_ACL = "public-read"
|
||||
AWS_QUERYSTRING_AUTH = False
|
||||
AWS_S3_FILE_OVERWRITE = False
|
||||
|
||||
# The AWS access key to use.
|
||||
AWS_ACCESS_KEY_ID = os.environ.get("AWS_ACCESS_KEY_ID", "")
|
||||
# Custom Domain settings
|
||||
parsed_url = urlparse(os.environ.get("WEB_URL", "http://localhost"))
|
||||
AWS_S3_CUSTOM_DOMAIN = f"{parsed_url.netloc}/{AWS_STORAGE_BUCKET_NAME}"
|
||||
AWS_S3_URL_PROTOCOL = f"{parsed_url.scheme}:"
|
||||
else:
|
||||
# The AWS region to connect to.
|
||||
AWS_REGION = os.environ.get("AWS_REGION", "")
|
||||
|
||||
# The AWS secret access key to use.
|
||||
AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY", "")
|
||||
# The AWS access key to use.
|
||||
AWS_ACCESS_KEY_ID = os.environ.get("AWS_ACCESS_KEY_ID", "")
|
||||
|
||||
# The optional AWS session token to use.
|
||||
# AWS_SESSION_TOKEN = ""
|
||||
# The AWS secret access key to use.
|
||||
AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY", "")
|
||||
|
||||
# The name of the bucket to store files in.
|
||||
AWS_S3_BUCKET_NAME = os.environ.get("AWS_S3_BUCKET_NAME")
|
||||
# The optional AWS session token to use.
|
||||
# AWS_SESSION_TOKEN = ""
|
||||
|
||||
# How to construct S3 URLs ("auto", "path", "virtual").
|
||||
AWS_S3_ADDRESSING_STYLE = "auto"
|
||||
# The name of the bucket to store files in.
|
||||
AWS_S3_BUCKET_NAME = os.environ.get("AWS_S3_BUCKET_NAME")
|
||||
|
||||
# The full URL to the S3 endpoint. Leave blank to use the default region URL.
|
||||
AWS_S3_ENDPOINT_URL = os.environ.get("AWS_S3_ENDPOINT_URL", "")
|
||||
# How to construct S3 URLs ("auto", "path", "virtual").
|
||||
AWS_S3_ADDRESSING_STYLE = "auto"
|
||||
|
||||
# A prefix to be applied to every stored file. This will be joined to every filename using the "/" separator.
|
||||
AWS_S3_KEY_PREFIX = ""
|
||||
# The full URL to the S3 endpoint. Leave blank to use the default region URL.
|
||||
AWS_S3_ENDPOINT_URL = os.environ.get("AWS_S3_ENDPOINT_URL", "")
|
||||
|
||||
# Whether to enable authentication for stored files. If True, then generated URLs will include an authentication
|
||||
# token valid for `AWS_S3_MAX_AGE_SECONDS`. If False, then generated URLs will not include an authentication token,
|
||||
# and their permissions will be set to "public-read".
|
||||
AWS_S3_BUCKET_AUTH = False
|
||||
# A prefix to be applied to every stored file. This will be joined to every filename using the "/" separator.
|
||||
AWS_S3_KEY_PREFIX = ""
|
||||
|
||||
# How long generated URLs are valid for. This affects the expiry of authentication tokens if `AWS_S3_BUCKET_AUTH`
|
||||
# is True. It also affects the "Cache-Control" header of the files.
|
||||
# Important: Changing this setting will not affect existing files.
|
||||
AWS_S3_MAX_AGE_SECONDS = 60 * 60 # 1 hours.
|
||||
# Whether to enable authentication for stored files. If True, then generated URLs will include an authentication
|
||||
# token valid for `AWS_S3_MAX_AGE_SECONDS`. If False, then generated URLs will not include an authentication token,
|
||||
# and their permissions will be set to "public-read".
|
||||
AWS_S3_BUCKET_AUTH = False
|
||||
|
||||
# A URL prefix to be used for generated URLs. This is useful if your bucket is served through a CDN. This setting
|
||||
# cannot be used with `AWS_S3_BUCKET_AUTH`.
|
||||
AWS_S3_PUBLIC_URL = ""
|
||||
# How long generated URLs are valid for. This affects the expiry of authentication tokens if `AWS_S3_BUCKET_AUTH`
|
||||
# is True. It also affects the "Cache-Control" header of the files.
|
||||
# Important: Changing this setting will not affect existing files.
|
||||
AWS_S3_MAX_AGE_SECONDS = 60 * 60 # 1 hours.
|
||||
|
||||
# If True, then files will be stored with reduced redundancy. Check the S3 documentation and make sure you
|
||||
# understand the consequences before enabling.
|
||||
# Important: Changing this setting will not affect existing files.
|
||||
AWS_S3_REDUCED_REDUNDANCY = False
|
||||
# A URL prefix to be used for generated URLs. This is useful if your bucket is served through a CDN. This setting
|
||||
# cannot be used with `AWS_S3_BUCKET_AUTH`.
|
||||
AWS_S3_PUBLIC_URL = ""
|
||||
|
||||
# The Content-Disposition header used when the file is downloaded. This can be a string, or a function taking a
|
||||
# single `name` argument.
|
||||
# Important: Changing this setting will not affect existing files.
|
||||
AWS_S3_CONTENT_DISPOSITION = ""
|
||||
# If True, then files will be stored with reduced redundancy. Check the S3 documentation and make sure you
|
||||
# understand the consequences before enabling.
|
||||
# Important: Changing this setting will not affect existing files.
|
||||
AWS_S3_REDUCED_REDUNDANCY = False
|
||||
|
||||
# The Content-Language header used when the file is downloaded. This can be a string, or a function taking a
|
||||
# single `name` argument.
|
||||
# Important: Changing this setting will not affect existing files.
|
||||
AWS_S3_CONTENT_LANGUAGE = ""
|
||||
# The Content-Disposition header used when the file is downloaded. This can be a string, or a function taking a
|
||||
# single `name` argument.
|
||||
# Important: Changing this setting will not affect existing files.
|
||||
AWS_S3_CONTENT_DISPOSITION = ""
|
||||
|
||||
# A mapping of custom metadata for each file. Each value can be a string, or a function taking a
|
||||
# single `name` argument.
|
||||
# Important: Changing this setting will not affect existing files.
|
||||
AWS_S3_METADATA = {}
|
||||
# The Content-Language header used when the file is downloaded. This can be a string, or a function taking a
|
||||
# single `name` argument.
|
||||
# Important: Changing this setting will not affect existing files.
|
||||
AWS_S3_CONTENT_LANGUAGE = ""
|
||||
|
||||
# If True, then files will be stored using AES256 server-side encryption.
|
||||
# If this is a string value (e.g., "aws:kms"), that encryption type will be used.
|
||||
# Otherwise, server-side encryption is not be enabled.
|
||||
# Important: Changing this setting will not affect existing files.
|
||||
AWS_S3_ENCRYPT_KEY = False
|
||||
# A mapping of custom metadata for each file. Each value can be a string, or a function taking a
|
||||
# single `name` argument.
|
||||
# Important: Changing this setting will not affect existing files.
|
||||
AWS_S3_METADATA = {}
|
||||
|
||||
# The AWS S3 KMS encryption key ID (the `SSEKMSKeyId` parameter) is set from this string if present.
|
||||
# This is only relevant if AWS S3 KMS server-side encryption is enabled (above).
|
||||
# AWS_S3_KMS_ENCRYPTION_KEY_ID = ""
|
||||
# If True, then files will be stored using AES256 server-side encryption.
|
||||
# If this is a string value (e.g., "aws:kms"), that encryption type will be used.
|
||||
# Otherwise, server-side encryption is not be enabled.
|
||||
# Important: Changing this setting will not affect existing files.
|
||||
AWS_S3_ENCRYPT_KEY = False
|
||||
|
||||
# If True, then text files will be stored using gzip content encoding. Files will only be gzipped if their
|
||||
# compressed size is smaller than their uncompressed size.
|
||||
# Important: Changing this setting will not affect existing files.
|
||||
AWS_S3_GZIP = True
|
||||
# The AWS S3 KMS encryption key ID (the `SSEKMSKeyId` parameter) is set from this string if present.
|
||||
# This is only relevant if AWS S3 KMS server-side encryption is enabled (above).
|
||||
# AWS_S3_KMS_ENCRYPTION_KEY_ID = ""
|
||||
|
||||
# The signature version to use for S3 requests.
|
||||
AWS_S3_SIGNATURE_VERSION = None
|
||||
# If True, then text files will be stored using gzip content encoding. Files will only be gzipped if their
|
||||
# compressed size is smaller than their uncompressed size.
|
||||
# Important: Changing this setting will not affect existing files.
|
||||
AWS_S3_GZIP = True
|
||||
|
||||
# If True, then files with the same name will overwrite each other. By default it's set to False to have
|
||||
# extra characters appended.
|
||||
AWS_S3_FILE_OVERWRITE = False
|
||||
# The signature version to use for S3 requests.
|
||||
AWS_S3_SIGNATURE_VERSION = None
|
||||
|
||||
STORAGES["default"] = {
|
||||
"BACKEND": "django_s3_storage.storage.S3Storage",
|
||||
}
|
||||
# If True, then files with the same name will overwrite each other. By default it's set to False to have
|
||||
# extra characters appended.
|
||||
AWS_S3_FILE_OVERWRITE = False
|
||||
|
||||
STORAGES["default"] = {
|
||||
"BACKEND": "django_s3_storage.storage.S3Storage",
|
||||
}
|
||||
|
||||
# AWS Settings End
|
||||
|
||||
@@ -193,16 +218,27 @@ CSRF_COOKIE_SECURE = True
|
||||
|
||||
REDIS_URL = os.environ.get("REDIS_URL")
|
||||
|
||||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "django_redis.cache.RedisCache",
|
||||
"LOCATION": REDIS_URL,
|
||||
"OPTIONS": {
|
||||
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
||||
"CONNECTION_POOL_KWARGS": {"ssl_cert_reqs": False},
|
||||
},
|
||||
if DOCKERIZED:
|
||||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "django_redis.cache.RedisCache",
|
||||
"LOCATION": REDIS_URL,
|
||||
"OPTIONS": {
|
||||
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
||||
},
|
||||
}
|
||||
}
|
||||
else:
|
||||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "django_redis.cache.RedisCache",
|
||||
"LOCATION": REDIS_URL,
|
||||
"OPTIONS": {
|
||||
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
||||
"CONNECTION_POOL_KWARGS": {"ssl_cert_reqs": False},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
WEB_URL = os.environ.get("WEB_URL", "https://app.plane.so")
|
||||
@@ -225,16 +261,19 @@ broker_url = (
|
||||
f"{redis_url}?ssl_cert_reqs={ssl.CERT_NONE.name}&ssl_ca_certs={certifi.where()}"
|
||||
)
|
||||
|
||||
CELERY_RESULT_BACKEND = broker_url
|
||||
CELERY_BROKER_URL = broker_url
|
||||
if DOCKERIZED:
|
||||
CELERY_BROKER_URL = REDIS_URL
|
||||
CELERY_RESULT_BACKEND = REDIS_URL
|
||||
else:
|
||||
CELERY_RESULT_BACKEND = broker_url
|
||||
CELERY_BROKER_URL = broker_url
|
||||
|
||||
GITHUB_ACCESS_TOKEN = os.environ.get("GITHUB_ACCESS_TOKEN", False)
|
||||
|
||||
# Enable or Disable signups
|
||||
|
||||
ENABLE_SIGNUP = os.environ.get("ENABLE_SIGNUP", "1") == "1"
|
||||
|
||||
# Scout Settings
|
||||
SCOUT_MONITOR = os.environ.get("SCOUT_MONITOR", False)
|
||||
SCOUT_KEY = os.environ.get("SCOUT_KEY", "")
|
||||
SCOUT_NAME = "Plane"
|
||||
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
"""Self hosted settings and globals."""
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import dj_database_url
|
||||
from urllib.parse import urlparse
|
||||
|
||||
|
||||
from .common import * # noqa
|
||||
|
||||
# Database
|
||||
DEBUG = int(os.environ.get("DEBUG", 0)) == 1
|
||||
|
||||
# Docker configurations
|
||||
DOCKERIZED = 1
|
||||
USE_MINIO = 1
|
||||
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.postgresql",
|
||||
"NAME": "plane",
|
||||
"USER": os.environ.get("PGUSER", ""),
|
||||
"PASSWORD": os.environ.get("PGPASSWORD", ""),
|
||||
"HOST": os.environ.get("PGHOST", ""),
|
||||
}
|
||||
}
|
||||
|
||||
# Parse database configuration from $DATABASE_URL
|
||||
DATABASES["default"] = dj_database_url.config()
|
||||
SITE_ID = 1
|
||||
|
||||
# File size limit
|
||||
FILE_SIZE_LIMIT = int(os.environ.get("FILE_SIZE_LIMIT", 5242880))
|
||||
|
||||
CORS_ALLOW_METHODS = [
|
||||
"DELETE",
|
||||
"GET",
|
||||
"OPTIONS",
|
||||
"PATCH",
|
||||
"POST",
|
||||
"PUT",
|
||||
]
|
||||
|
||||
CORS_ALLOW_HEADERS = [
|
||||
"accept",
|
||||
"accept-encoding",
|
||||
"authorization",
|
||||
"content-type",
|
||||
"dnt",
|
||||
"origin",
|
||||
"user-agent",
|
||||
"x-csrftoken",
|
||||
"x-requested-with",
|
||||
]
|
||||
|
||||
CORS_ALLOW_CREDENTIALS = True
|
||||
CORS_ALLOW_ALL_ORIGINS = True
|
||||
|
||||
STORAGES = {
|
||||
"staticfiles": {
|
||||
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
|
||||
},
|
||||
}
|
||||
|
||||
INSTALLED_APPS += ("storages",)
|
||||
STORAGES["default"] = {"BACKEND": "storages.backends.s3boto3.S3Boto3Storage"}
|
||||
# The AWS access key to use.
|
||||
AWS_ACCESS_KEY_ID = os.environ.get("AWS_ACCESS_KEY_ID", "access-key")
|
||||
# The AWS secret access key to use.
|
||||
AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY", "secret-key")
|
||||
# The name of the bucket to store files in.
|
||||
AWS_STORAGE_BUCKET_NAME = os.environ.get("AWS_S3_BUCKET_NAME", "uploads")
|
||||
# The full URL to the S3 endpoint. Leave blank to use the default region URL.
|
||||
AWS_S3_ENDPOINT_URL = os.environ.get(
|
||||
"AWS_S3_ENDPOINT_URL", "http://plane-minio:9000"
|
||||
)
|
||||
# Default permissions
|
||||
AWS_DEFAULT_ACL = "public-read"
|
||||
AWS_QUERYSTRING_AUTH = False
|
||||
AWS_S3_FILE_OVERWRITE = False
|
||||
|
||||
# Custom Domain settings
|
||||
parsed_url = urlparse(os.environ.get("WEB_URL", "http://localhost"))
|
||||
AWS_S3_CUSTOM_DOMAIN = f"{parsed_url.netloc}/{AWS_STORAGE_BUCKET_NAME}"
|
||||
AWS_S3_URL_PROTOCOL = f"{parsed_url.scheme}:"
|
||||
|
||||
# Honor the 'X-Forwarded-Proto' header for request.is_secure()
|
||||
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
||||
|
||||
# Allow all host headers
|
||||
ALLOWED_HOSTS = [
|
||||
"*",
|
||||
]
|
||||
|
||||
# Security settings
|
||||
SESSION_COOKIE_SECURE = True
|
||||
CSRF_COOKIE_SECURE = True
|
||||
|
||||
# Redis URL
|
||||
REDIS_URL = os.environ.get("REDIS_URL")
|
||||
|
||||
# Caches
|
||||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "django_redis.cache.RedisCache",
|
||||
"LOCATION": REDIS_URL,
|
||||
"OPTIONS": {
|
||||
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
# URL used for email redirects
|
||||
WEB_URL = os.environ.get("WEB_URL", "http://localhost")
|
||||
|
||||
# Celery settings
|
||||
CELERY_BROKER_URL = REDIS_URL
|
||||
CELERY_RESULT_BACKEND = REDIS_URL
|
||||
|
||||
# Enable or Disable signups
|
||||
ENABLE_SIGNUP = os.environ.get("ENABLE_SIGNUP", "1") == "1"
|
||||
|
||||
# Analytics
|
||||
ANALYTICS_BASE_API = False
|
||||
|
||||
# OPEN AI Settings
|
||||
OPENAI_API_BASE = os.environ.get("OPENAI_API_BASE", "https://api.openai.com/v1")
|
||||
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", False)
|
||||
GPT_ENGINE = os.environ.get("GPT_ENGINE", "gpt-3.5-turbo")
|
||||
@@ -15,7 +15,7 @@ def resolve_keys(group_keys, value):
|
||||
return value
|
||||
|
||||
|
||||
def group_results(results_data, group_by, sub_group_by=False):
|
||||
def group_results(results_data, group_by):
|
||||
"""group results data into certain group_by
|
||||
|
||||
Args:
|
||||
@@ -25,140 +25,38 @@ def group_results(results_data, group_by, sub_group_by=False):
|
||||
Returns:
|
||||
obj: grouped results
|
||||
"""
|
||||
if sub_group_by:
|
||||
main_responsive_dict = dict()
|
||||
response_dict = dict()
|
||||
|
||||
if sub_group_by == "priority":
|
||||
main_responsive_dict = {
|
||||
"urgent": {},
|
||||
"high": {},
|
||||
"medium": {},
|
||||
"low": {},
|
||||
"none": {},
|
||||
}
|
||||
if group_by == "priority":
|
||||
response_dict = {
|
||||
"urgent": [],
|
||||
"high": [],
|
||||
"medium": [],
|
||||
"low": [],
|
||||
"None": [],
|
||||
}
|
||||
|
||||
for value in results_data:
|
||||
main_group_attribute = resolve_keys(sub_group_by, value)
|
||||
group_attribute = resolve_keys(group_by, value)
|
||||
if isinstance(main_group_attribute, list) and not isinstance(group_attribute, list):
|
||||
if len(main_group_attribute):
|
||||
for attrib in main_group_attribute:
|
||||
if str(attrib) not in main_responsive_dict:
|
||||
main_responsive_dict[str(attrib)] = {}
|
||||
if str(group_attribute) in main_responsive_dict[str(attrib)]:
|
||||
main_responsive_dict[str(attrib)][str(group_attribute)].append(value)
|
||||
else:
|
||||
main_responsive_dict[str(attrib)][str(group_attribute)] = []
|
||||
main_responsive_dict[str(attrib)][str(group_attribute)].append(value)
|
||||
else:
|
||||
if str(None) not in main_responsive_dict:
|
||||
main_responsive_dict[str(None)] = {}
|
||||
|
||||
if str(group_attribute) in main_responsive_dict[str(None)]:
|
||||
main_responsive_dict[str(None)][str(group_attribute)].append(value)
|
||||
for value in results_data:
|
||||
group_attribute = resolve_keys(group_by, value)
|
||||
if isinstance(group_attribute, list):
|
||||
if len(group_attribute):
|
||||
for attrib in group_attribute:
|
||||
if str(attrib) in response_dict:
|
||||
response_dict[str(attrib)].append(value)
|
||||
else:
|
||||
main_responsive_dict[str(None)][str(group_attribute)] = []
|
||||
main_responsive_dict[str(None)][str(group_attribute)].append(value)
|
||||
|
||||
elif isinstance(group_attribute, list) and not isinstance(main_group_attribute, list):
|
||||
if str(main_group_attribute) not in main_responsive_dict:
|
||||
main_responsive_dict[str(main_group_attribute)] = {}
|
||||
if len(group_attribute):
|
||||
for attrib in group_attribute:
|
||||
if str(attrib) in main_responsive_dict[str(main_group_attribute)]:
|
||||
main_responsive_dict[str(main_group_attribute)][str(attrib)].append(value)
|
||||
else:
|
||||
main_responsive_dict[str(main_group_attribute)][str(attrib)] = []
|
||||
main_responsive_dict[str(main_group_attribute)][str(attrib)].append(value)
|
||||
else:
|
||||
if str(None) in main_responsive_dict[str(main_group_attribute)]:
|
||||
main_responsive_dict[str(main_group_attribute)][str(None)].append(value)
|
||||
else:
|
||||
main_responsive_dict[str(main_group_attribute)][str(None)] = []
|
||||
main_responsive_dict[str(main_group_attribute)][str(None)].append(value)
|
||||
|
||||
elif isinstance(group_attribute, list) and isinstance(main_group_attribute, list):
|
||||
if len(main_group_attribute):
|
||||
for main_attrib in main_group_attribute:
|
||||
if str(main_attrib) not in main_responsive_dict:
|
||||
main_responsive_dict[str(main_attrib)] = {}
|
||||
if len(group_attribute):
|
||||
for attrib in group_attribute:
|
||||
if str(attrib) in main_responsive_dict[str(main_attrib)]:
|
||||
main_responsive_dict[str(main_attrib)][str(attrib)].append(value)
|
||||
else:
|
||||
main_responsive_dict[str(main_attrib)][str(attrib)] = []
|
||||
main_responsive_dict[str(main_attrib)][str(attrib)].append(value)
|
||||
else:
|
||||
if str(None) in main_responsive_dict[str(main_attrib)]:
|
||||
main_responsive_dict[str(main_attrib)][str(None)].append(value)
|
||||
else:
|
||||
main_responsive_dict[str(main_attrib)][str(None)] = []
|
||||
main_responsive_dict[str(main_attrib)][str(None)].append(value)
|
||||
else:
|
||||
if str(None) not in main_responsive_dict:
|
||||
main_responsive_dict[str(None)] = {}
|
||||
if len(group_attribute):
|
||||
for attrib in group_attribute:
|
||||
if str(attrib) in main_responsive_dict[str(None)]:
|
||||
main_responsive_dict[str(None)][str(attrib)].append(value)
|
||||
else:
|
||||
main_responsive_dict[str(None)][str(attrib)] = []
|
||||
main_responsive_dict[str(None)][str(attrib)].append(value)
|
||||
else:
|
||||
if str(None) in main_responsive_dict[str(None)]:
|
||||
main_responsive_dict[str(None)][str(None)].append(value)
|
||||
else:
|
||||
main_responsive_dict[str(None)][str(None)] = []
|
||||
main_responsive_dict[str(None)][str(None)].append(value)
|
||||
response_dict[str(attrib)] = []
|
||||
response_dict[str(attrib)].append(value)
|
||||
else:
|
||||
main_group_attribute = resolve_keys(sub_group_by, value)
|
||||
group_attribute = resolve_keys(group_by, value)
|
||||
|
||||
if str(main_group_attribute) not in main_responsive_dict:
|
||||
main_responsive_dict[str(main_group_attribute)] = {}
|
||||
|
||||
if str(group_attribute) in main_responsive_dict[str(main_group_attribute)]:
|
||||
main_responsive_dict[str(main_group_attribute)][str(group_attribute)].append(value)
|
||||
if str(None) in response_dict:
|
||||
response_dict[str(None)].append(value)
|
||||
else:
|
||||
main_responsive_dict[str(main_group_attribute)][str(group_attribute)] = []
|
||||
main_responsive_dict[str(main_group_attribute)][str(group_attribute)].append(value)
|
||||
|
||||
return main_responsive_dict
|
||||
|
||||
else:
|
||||
response_dict = dict()
|
||||
|
||||
if group_by == "priority":
|
||||
response_dict = {
|
||||
"urgent": [],
|
||||
"high": [],
|
||||
"medium": [],
|
||||
"low": [],
|
||||
"none": [],
|
||||
}
|
||||
|
||||
for value in results_data:
|
||||
group_attribute = resolve_keys(group_by, value)
|
||||
if isinstance(group_attribute, list):
|
||||
if len(group_attribute):
|
||||
for attrib in group_attribute:
|
||||
if str(attrib) in response_dict:
|
||||
response_dict[str(attrib)].append(value)
|
||||
else:
|
||||
response_dict[str(attrib)] = []
|
||||
response_dict[str(attrib)].append(value)
|
||||
else:
|
||||
if str(None) in response_dict:
|
||||
response_dict[str(None)].append(value)
|
||||
else:
|
||||
response_dict[str(None)] = []
|
||||
response_dict[str(None)].append(value)
|
||||
response_dict[str(None)] = []
|
||||
response_dict[str(None)].append(value)
|
||||
else:
|
||||
if str(group_attribute) in response_dict:
|
||||
response_dict[str(group_attribute)].append(value)
|
||||
else:
|
||||
if str(group_attribute) in response_dict:
|
||||
response_dict[str(group_attribute)].append(value)
|
||||
else:
|
||||
response_dict[str(group_attribute)] = []
|
||||
response_dict[str(group_attribute)].append(value)
|
||||
response_dict[str(group_attribute)] = []
|
||||
response_dict[str(group_attribute)].append(value)
|
||||
|
||||
return response_dict
|
||||
return response_dict
|
||||
|
||||
@@ -4,7 +4,7 @@ x-api-and-worker-env:
|
||||
&api-and-worker-env
|
||||
DEBUG: ${DEBUG}
|
||||
SENTRY_DSN: ${SENTRY_DSN}
|
||||
DJANGO_SETTINGS_MODULE: plane.settings.selfhosted
|
||||
DJANGO_SETTINGS_MODULE: plane.settings.production
|
||||
DATABASE_URL: postgres://${PGUSER}:${PGPASSWORD}@${PGHOST}:5432/${PGDATABASE}
|
||||
REDIS_URL: redis://plane-redis:6379/
|
||||
EMAIL_HOST: ${EMAIL_HOST}
|
||||
|
||||
+66
-3
@@ -1,5 +1,37 @@
|
||||
version: "3.8"
|
||||
|
||||
x-api-and-worker-env: &api-and-worker-env
|
||||
DEBUG: ${DEBUG}
|
||||
SENTRY_DSN: ${SENTRY_DSN}
|
||||
DJANGO_SETTINGS_MODULE: plane.settings.production
|
||||
DATABASE_URL: postgres://${PGUSER}:${PGPASSWORD}@${PGHOST}:5432/${PGDATABASE}
|
||||
REDIS_URL: redis://plane-redis:6379/
|
||||
EMAIL_HOST: ${EMAIL_HOST}
|
||||
EMAIL_HOST_USER: ${EMAIL_HOST_USER}
|
||||
EMAIL_HOST_PASSWORD: ${EMAIL_HOST_PASSWORD}
|
||||
EMAIL_PORT: ${EMAIL_PORT}
|
||||
EMAIL_FROM: ${EMAIL_FROM}
|
||||
EMAIL_USE_TLS: ${EMAIL_USE_TLS}
|
||||
EMAIL_USE_SSL: ${EMAIL_USE_SSL}
|
||||
AWS_REGION: ${AWS_REGION}
|
||||
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
|
||||
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
|
||||
AWS_S3_BUCKET_NAME: ${AWS_S3_BUCKET_NAME}
|
||||
AWS_S3_ENDPOINT_URL: ${AWS_S3_ENDPOINT_URL}
|
||||
FILE_SIZE_LIMIT: ${FILE_SIZE_LIMIT}
|
||||
WEB_URL: ${WEB_URL}
|
||||
GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET}
|
||||
DISABLE_COLLECTSTATIC: 1
|
||||
DOCKERIZED: 1
|
||||
OPENAI_API_BASE: ${OPENAI_API_BASE}
|
||||
OPENAI_API_KEY: ${OPENAI_API_KEY}
|
||||
GPT_ENGINE: ${GPT_ENGINE}
|
||||
SECRET_KEY: ${SECRET_KEY}
|
||||
DEFAULT_EMAIL: ${DEFAULT_EMAIL}
|
||||
DEFAULT_PASSWORD: ${DEFAULT_PASSWORD}
|
||||
USE_MINIO: ${USE_MINIO}
|
||||
ENABLE_SIGNUP: ${ENABLE_SIGNUP}
|
||||
|
||||
services:
|
||||
plane-web:
|
||||
container_name: planefrontend
|
||||
@@ -8,8 +40,23 @@ services:
|
||||
dockerfile: ./web/Dockerfile.web
|
||||
args:
|
||||
DOCKER_BUILDKIT: 1
|
||||
NEXT_PUBLIC_API_BASE_URL: http://localhost:8000
|
||||
NEXT_PUBLIC_DEPLOY_URL: http://localhost/spaces
|
||||
restart: always
|
||||
command: /usr/local/bin/start.sh web/server.js web
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
NEXT_PUBLIC_API_BASE_URL: ${NEXT_PUBLIC_API_BASE_URL}
|
||||
NEXT_PUBLIC_DEPLOY_URL: ${NEXT_PUBLIC_DEPLOY_URL}
|
||||
NEXT_PUBLIC_GOOGLE_CLIENTID: "0"
|
||||
NEXT_PUBLIC_GITHUB_APP_NAME: "0"
|
||||
NEXT_PUBLIC_GITHUB_ID: "0"
|
||||
NEXT_PUBLIC_SENTRY_DSN: "0"
|
||||
NEXT_PUBLIC_ENABLE_OAUTH: "0"
|
||||
NEXT_PUBLIC_ENABLE_SENTRY: "0"
|
||||
NEXT_PUBLIC_ENABLE_SESSION_RECORDER: "0"
|
||||
NEXT_PUBLIC_TRACK_EVENTS: "0"
|
||||
depends_on:
|
||||
- plane-api
|
||||
- plane-worker
|
||||
@@ -21,8 +68,14 @@ services:
|
||||
dockerfile: ./space/Dockerfile.space
|
||||
args:
|
||||
DOCKER_BUILDKIT: 1
|
||||
NEXT_PUBLIC_DEPLOY_WITH_NGINX: 1
|
||||
NEXT_PUBLIC_API_BASE_URL: http://localhost:8000
|
||||
restart: always
|
||||
command: /usr/local/bin/start.sh space/server.js space
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- NEXT_PUBLIC_API_BASE_URL=${NEXT_PUBLIC_API_BASE_URL}
|
||||
depends_on:
|
||||
- plane-api
|
||||
- plane-worker
|
||||
@@ -38,7 +91,9 @@ services:
|
||||
restart: always
|
||||
command: ./bin/takeoff
|
||||
env_file:
|
||||
- ./apiserver/.env
|
||||
- .env
|
||||
environment:
|
||||
<<: *api-and-worker-env
|
||||
depends_on:
|
||||
- plane-db
|
||||
- plane-redis
|
||||
@@ -53,7 +108,9 @@ services:
|
||||
restart: always
|
||||
command: ./bin/worker
|
||||
env_file:
|
||||
- ./apiserver/.env
|
||||
- .env
|
||||
environment:
|
||||
<<: *api-and-worker-env
|
||||
depends_on:
|
||||
- plane-api
|
||||
- plane-db
|
||||
@@ -69,7 +126,9 @@ services:
|
||||
restart: always
|
||||
command: ./bin/beat
|
||||
env_file:
|
||||
- ./apiserver/.env
|
||||
- .env
|
||||
environment:
|
||||
<<: *api-and-worker-env
|
||||
depends_on:
|
||||
- plane-api
|
||||
- plane-db
|
||||
@@ -104,6 +163,8 @@ services:
|
||||
command: server /export --console-address ":9090"
|
||||
volumes:
|
||||
- uploads:/export
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${AWS_ACCESS_KEY_ID}
|
||||
MINIO_ROOT_PASSWORD: ${AWS_SECRET_ACCESS_KEY}
|
||||
@@ -126,6 +187,8 @@ services:
|
||||
restart: always
|
||||
ports:
|
||||
- ${NGINX_PORT}:80
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
FILE_SIZE_LIMIT: ${FILE_SIZE_LIMIT:-5242880}
|
||||
BUCKET_NAME: ${AWS_S3_BUCKET_NAME:-uploads}
|
||||
|
||||
+22
-21
@@ -1,29 +1,30 @@
|
||||
events { }
|
||||
|
||||
|
||||
http {
|
||||
sendfile on;
|
||||
sendfile on;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
root /www/data/;
|
||||
access_log /var/log/nginx/access.log;
|
||||
server {
|
||||
listen 80;
|
||||
root /www/data/;
|
||||
access_log /var/log/nginx/access.log;
|
||||
|
||||
client_max_body_size ${FILE_SIZE_LIMIT};
|
||||
client_max_body_size ${FILE_SIZE_LIMIT};
|
||||
|
||||
location / {
|
||||
proxy_pass http://planefrontend:3000/;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://planebackend:8000/api/;
|
||||
}
|
||||
|
||||
location /spaces/ {
|
||||
proxy_pass http://planedeploy:3000/spaces/;
|
||||
}
|
||||
|
||||
location /${BUCKET_NAME}/ {
|
||||
proxy_pass http://plane-minio:9000/uploads/;
|
||||
}
|
||||
location / {
|
||||
proxy_pass http://planefrontend:3000/;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://planebackend:8000/api/;
|
||||
}
|
||||
|
||||
location /spaces/ {
|
||||
proxy_pass http://planedeploy:3000/spaces/;
|
||||
}
|
||||
|
||||
location /${BUCKET_NAME}/ {
|
||||
proxy_pass http://plane-minio:9000/uploads/;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import APIService from "./api.service";
|
||||
import trackEventServices from "./track-event.service";
|
||||
// types
|
||||
import { ICurrentUserResponse, IGptResponse } from "types";
|
||||
|
||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||
|
||||
const trackEvent =
|
||||
process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" ||
|
||||
process.env.NEXT_PUBLIC_TRACK_EVENTS === "1";
|
||||
|
||||
export class AIServices extends APIService {
|
||||
constructor() {
|
||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async createGptTask(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
data: { prompt: string; task: string },
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<IGptResponse> {
|
||||
return this.post(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/ai-assistant/`,
|
||||
data
|
||||
)
|
||||
.then((response) => {
|
||||
if (trackEvent)
|
||||
trackEventServices.trackAskGptEvent(response?.data, "ASK_GPT", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import APIService from "./api.service";
|
||||
// types
|
||||
import {
|
||||
IAnalyticsParams,
|
||||
IAnalyticsResponse,
|
||||
IDefaultAnalyticsResponse,
|
||||
IExportAnalyticsFormData,
|
||||
ISaveAnalyticsFormData,
|
||||
} from "types";
|
||||
|
||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||
|
||||
export class AnalyticsServices extends APIService {
|
||||
constructor() {
|
||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async getAnalytics(
|
||||
workspaceSlug: string,
|
||||
params: IAnalyticsParams
|
||||
): Promise<IAnalyticsResponse> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/analytics/`, {
|
||||
params: {
|
||||
...params,
|
||||
project: params?.project ? params.project.toString() : null,
|
||||
},
|
||||
})
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getDefaultAnalytics(
|
||||
workspaceSlug: string,
|
||||
params?: Partial<IAnalyticsParams>
|
||||
): Promise<IDefaultAnalyticsResponse> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/default-analytics/`, {
|
||||
params: {
|
||||
...params,
|
||||
project: params?.project ? params.project.toString() : null,
|
||||
},
|
||||
})
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async saveAnalytics(
|
||||
workspaceSlug: string,
|
||||
data: ISaveAnalyticsFormData
|
||||
): Promise<any> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/analytic-view/`, data)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async exportAnalytics(
|
||||
workspaceSlug: string,
|
||||
data: IExportAnalyticsFormData
|
||||
): Promise<any> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/export-analytics/`, data)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
import axios from "axios";
|
||||
import Cookies from "js-cookie";
|
||||
|
||||
const unAuthorizedStatus = [401];
|
||||
const nonValidatedRoutes = [
|
||||
"/",
|
||||
"/magic-sign-in",
|
||||
"/reset-password",
|
||||
"/workspace-member-invitation",
|
||||
"/sign-up",
|
||||
"/m/",
|
||||
];
|
||||
|
||||
const validateRouteCheck = (route: string): boolean => {
|
||||
let validationToggle = false;
|
||||
|
||||
let routeCheck = false;
|
||||
nonValidatedRoutes.forEach((_route: string) => {
|
||||
if (route.includes(_route)) {
|
||||
routeCheck = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (routeCheck) validationToggle = true;
|
||||
return validationToggle;
|
||||
};
|
||||
|
||||
axios.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
const { status }: any = error.response;
|
||||
if (!validateRouteCheck(window.location.pathname)) {
|
||||
if (unAuthorizedStatus.includes(status)) {
|
||||
Cookies.remove("refreshToken", { path: "/" });
|
||||
Cookies.remove("accessToken", { path: "/" });
|
||||
window.location.href = `/?next_url=${window.location.pathname}`;
|
||||
}
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
abstract class APIService {
|
||||
protected baseURL: string;
|
||||
protected headers: any = {};
|
||||
|
||||
constructor(baseURL: string) {
|
||||
this.baseURL = baseURL;
|
||||
}
|
||||
|
||||
setRefreshToken(token: string) {
|
||||
Cookies.set("refreshToken", token);
|
||||
}
|
||||
|
||||
getRefreshToken() {
|
||||
return Cookies.get("refreshToken");
|
||||
}
|
||||
|
||||
purgeRefreshToken() {
|
||||
Cookies.remove("refreshToken", { path: "/" });
|
||||
}
|
||||
|
||||
setAccessToken(token: string) {
|
||||
Cookies.set("accessToken", token);
|
||||
}
|
||||
|
||||
getAccessToken() {
|
||||
return Cookies.get("accessToken");
|
||||
}
|
||||
|
||||
purgeAccessToken() {
|
||||
Cookies.remove("accessToken", { path: "/" });
|
||||
}
|
||||
|
||||
getHeaders() {
|
||||
return {
|
||||
Authorization: `Bearer ${this.getAccessToken()}`,
|
||||
};
|
||||
}
|
||||
|
||||
getWithoutBase(url: string, config = {}): Promise<any> {
|
||||
return axios({
|
||||
method: "get",
|
||||
url: url,
|
||||
headers: this.getAccessToken() ? this.getHeaders() : {},
|
||||
...config,
|
||||
});
|
||||
}
|
||||
|
||||
get(url: string, config = {}): Promise<any> {
|
||||
return axios({
|
||||
method: "get",
|
||||
url: this.baseURL + url,
|
||||
headers: this.getAccessToken() ? this.getHeaders() : {},
|
||||
...config,
|
||||
});
|
||||
}
|
||||
|
||||
post(url: string, data = {}, config = {}): Promise<any> {
|
||||
return axios({
|
||||
method: "post",
|
||||
url: this.baseURL + url,
|
||||
data,
|
||||
headers: this.getAccessToken() ? this.getHeaders() : {},
|
||||
...config,
|
||||
});
|
||||
}
|
||||
|
||||
put(url: string, data = {}, config = {}): Promise<any> {
|
||||
return axios({
|
||||
method: "put",
|
||||
url: this.baseURL + url,
|
||||
data,
|
||||
headers: this.getAccessToken() ? this.getHeaders() : {},
|
||||
...config,
|
||||
});
|
||||
}
|
||||
|
||||
patch(url: string, data = {}, config = {}): Promise<any> {
|
||||
return axios({
|
||||
method: "patch",
|
||||
url: this.baseURL + url,
|
||||
data,
|
||||
headers: this.getAccessToken() ? this.getHeaders() : {},
|
||||
...config,
|
||||
});
|
||||
}
|
||||
|
||||
delete(url: string, data?: any, config = {}): Promise<any> {
|
||||
return axios({
|
||||
method: "delete",
|
||||
url: this.baseURL + url,
|
||||
data: data,
|
||||
headers: this.getAccessToken() ? this.getHeaders() : {},
|
||||
...config,
|
||||
});
|
||||
}
|
||||
|
||||
mediaUpload(url: string, data = {}, config = {}): Promise<any> {
|
||||
return axios({
|
||||
method: "post",
|
||||
url: this.baseURL + url,
|
||||
data,
|
||||
headers: this.getAccessToken()
|
||||
? { ...this.getHeaders(), "Content-Type": "multipart/form-data" }
|
||||
: {},
|
||||
...config,
|
||||
});
|
||||
}
|
||||
|
||||
request(config = {}) {
|
||||
return axios(config);
|
||||
}
|
||||
}
|
||||
|
||||
export default APIService;
|
||||
+5
-4
@@ -1,11 +1,12 @@
|
||||
// services
|
||||
import axios from "axios";
|
||||
import APIService from "services/api.service";
|
||||
import { API_BASE_URL } from "helpers/common.helper";
|
||||
|
||||
export class AppInstallationService extends APIService {
|
||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||
|
||||
class AppInstallationsService extends APIService {
|
||||
constructor() {
|
||||
super(API_BASE_URL);
|
||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async addInstallationApp(workspaceSlug: string, provider: string, data: any): Promise<any> {
|
||||
@@ -74,4 +75,4 @@ export class AppInstallationService extends APIService {
|
||||
}
|
||||
}
|
||||
|
||||
export default new AppInstallationService();
|
||||
export default new AppInstallationsService();
|
||||
@@ -0,0 +1,85 @@
|
||||
// services
|
||||
import APIService from "services/api.service";
|
||||
import { ICurrentUserResponse } from "types";
|
||||
|
||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||
|
||||
class AuthService extends APIService {
|
||||
constructor() {
|
||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async emailLogin(data: any) {
|
||||
return this.post("/api/sign-in/", data, { headers: {} })
|
||||
.then((response) => {
|
||||
this.setAccessToken(response?.data?.access_token);
|
||||
this.setRefreshToken(response?.data?.refresh_token);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async emailSignUp(data: { email: string; password: string }) {
|
||||
return this.post("/api/sign-up/", data, { headers: {} })
|
||||
.then((response) => {
|
||||
this.setAccessToken(response?.data?.access_token);
|
||||
this.setRefreshToken(response?.data?.refresh_token);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async socialAuth(data: any): Promise<{
|
||||
access_token: string;
|
||||
refresh_toke: string;
|
||||
user: ICurrentUserResponse;
|
||||
}> {
|
||||
return this.post("/api/social-auth/", data, { headers: {} })
|
||||
.then((response) => {
|
||||
this.setAccessToken(response?.data?.access_token);
|
||||
this.setRefreshToken(response?.data?.refresh_token);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async emailCode(data: any) {
|
||||
return this.post("/api/magic-generate/", data, { headers: {} })
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async magicSignIn(data: any) {
|
||||
const response = await this.post("/api/magic-sign-in/", data, { headers: {} });
|
||||
if (response?.status === 200) {
|
||||
this.setAccessToken(response?.data?.access_token);
|
||||
this.setRefreshToken(response?.data?.refresh_token);
|
||||
return response?.data;
|
||||
}
|
||||
throw response.response.data;
|
||||
}
|
||||
|
||||
async signOut() {
|
||||
return this.post("/api/sign-out/", { refresh_token: this.getRefreshToken() })
|
||||
.then((response) => {
|
||||
this.purgeAccessToken();
|
||||
this.purgeRefreshToken();
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
this.purgeAccessToken();
|
||||
this.purgeRefreshToken();
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new AuthService();
|
||||
@@ -1,11 +1,16 @@
|
||||
import APIService from "services/api.service";
|
||||
import trackEventServices from "services/track_event.service";
|
||||
import { ICurrentUserResponse } from "types";
|
||||
import { API_BASE_URL } from "helpers/common.helper";
|
||||
import trackEventServices from "services/track-event.service";
|
||||
|
||||
export class CSVIntegrationService extends APIService {
|
||||
import { ICurrentUserResponse } from "types";
|
||||
|
||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||
|
||||
const trackEvent =
|
||||
process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1";
|
||||
|
||||
class CSVIntegrationService extends APIService {
|
||||
constructor() {
|
||||
super(API_BASE_URL);
|
||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async exportCSVService(
|
||||
@@ -18,13 +23,14 @@ export class CSVIntegrationService extends APIService {
|
||||
): Promise<any> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/export-issues/`, data)
|
||||
.then((response) => {
|
||||
trackEventServices.trackExporterEvent(
|
||||
{
|
||||
workspaceSlug,
|
||||
},
|
||||
"CSV_EXPORTER_CREATE",
|
||||
user
|
||||
);
|
||||
if (trackEvent)
|
||||
trackEventServices.trackExporterEvent(
|
||||
{
|
||||
workspaceSlug,
|
||||
},
|
||||
"CSV_EXPORTER_CREATE",
|
||||
user
|
||||
);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -0,0 +1,213 @@
|
||||
// services
|
||||
import APIService from "services/api.service";
|
||||
import trackEventServices from "services/track-event.service";
|
||||
|
||||
// types
|
||||
import type { CycleDateCheckData, ICurrentUserResponse, ICycle, IIssue } from "types";
|
||||
|
||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||
|
||||
const trackEvent =
|
||||
process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1";
|
||||
|
||||
class ProjectCycleServices extends APIService {
|
||||
constructor() {
|
||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async createCycle(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
data: any,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/`, data)
|
||||
.then((response) => {
|
||||
if (trackEvent) trackEventServices.trackCycleEvent(response?.data, "CYCLE_CREATE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getCyclesWithParams(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
cycleType: "all" | "current" | "upcoming" | "draft" | "completed" | "incomplete"
|
||||
): Promise<ICycle[]> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/`, {
|
||||
params: {
|
||||
cycle_view: cycleType,
|
||||
},
|
||||
})
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getCycleDetails(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
cycleId: string
|
||||
): Promise<ICycle> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/`)
|
||||
.then((res) => res?.data)
|
||||
.catch((err) => {
|
||||
throw err?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getCycleIssues(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
cycleId: string
|
||||
): Promise<IIssue[]> {
|
||||
return this.get(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/cycle-issues/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getCycleIssuesWithParams(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
cycleId: string,
|
||||
queries?: any
|
||||
): Promise<IIssue[] | { [key: string]: IIssue[] }> {
|
||||
return this.get(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/cycle-issues/`,
|
||||
{ params: queries }
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async updateCycle(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
cycleId: string,
|
||||
data: any,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.put(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/`,
|
||||
data
|
||||
)
|
||||
.then((response) => {
|
||||
if (trackEvent) trackEventServices.trackCycleEvent(response?.data, "CYCLE_UPDATE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async patchCycle(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
cycleId: string,
|
||||
data: Partial<ICycle>,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.patch(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/`,
|
||||
data
|
||||
)
|
||||
.then((response) => {
|
||||
if (trackEvent) trackEventServices.trackCycleEvent(response?.data, "CYCLE_UPDATE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async deleteCycle(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
cycleId: string,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/`)
|
||||
.then((response) => {
|
||||
if (trackEvent) trackEventServices.trackCycleEvent(response?.data, "CYCLE_DELETE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async cycleDateCheck(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
data: CycleDateCheckData
|
||||
): Promise<any> {
|
||||
return this.post(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/date-check/`,
|
||||
data
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async addCycleToFavorites(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
data: {
|
||||
cycle: string;
|
||||
}
|
||||
): Promise<any> {
|
||||
return this.post(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/user-favorite-cycles/`,
|
||||
data
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async transferIssues(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
cycleId: string,
|
||||
data: {
|
||||
new_cycle_id: string;
|
||||
}
|
||||
): Promise<any> {
|
||||
return this.post(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/transfer-issues/`,
|
||||
data
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async removeCycleFromFavorites(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
cycleId: string
|
||||
): Promise<any> {
|
||||
return this.delete(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/user-favorite-cycles/${cycleId}/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new ProjectCycleServices();
|
||||
+29
-12
@@ -1,14 +1,17 @@
|
||||
// services
|
||||
import APIService from "services/api.service";
|
||||
import trackEventServices from "services/track_event.service";
|
||||
// types
|
||||
import type { ICurrentUserResponse, IEstimate, IEstimateFormData } from "types";
|
||||
// helpers
|
||||
import { API_BASE_URL } from "helpers/common.helper";
|
||||
import trackEventServices from "services/track-event.service";
|
||||
|
||||
export class ProjectEstimateServices extends APIService {
|
||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||
|
||||
const trackEvent =
|
||||
process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1";
|
||||
|
||||
class ProjectEstimateServices extends APIService {
|
||||
constructor() {
|
||||
super(API_BASE_URL);
|
||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async createEstimate(
|
||||
@@ -19,7 +22,8 @@ export class ProjectEstimateServices extends APIService {
|
||||
): Promise<any> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/`, data)
|
||||
.then((response) => {
|
||||
trackEventServices.trackIssueEstimateEvent(response?.data, "ESTIMATE_CREATE", user);
|
||||
if (trackEvent)
|
||||
trackEventServices.trackIssueEstimateEvent(response?.data, "ESTIMATE_CREATE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -34,9 +38,13 @@ export class ProjectEstimateServices extends APIService {
|
||||
data: IEstimateFormData,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/`, data)
|
||||
return this.patch(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/`,
|
||||
data
|
||||
)
|
||||
.then((response) => {
|
||||
trackEventServices.trackIssueEstimateEvent(response?.data, "ESTIMATE_UPDATE", user);
|
||||
if (trackEvent)
|
||||
trackEventServices.trackIssueEstimateEvent(response?.data, "ESTIMATE_UPDATE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -44,8 +52,14 @@ export class ProjectEstimateServices extends APIService {
|
||||
});
|
||||
}
|
||||
|
||||
async getEstimateDetails(workspaceSlug: string, projectId: string, estimateId: string): Promise<IEstimate> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/`)
|
||||
async getEstimateDetails(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
estimateId: string
|
||||
): Promise<IEstimate> {
|
||||
return this.get(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
@@ -66,9 +80,12 @@ export class ProjectEstimateServices extends APIService {
|
||||
estimateId: string,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/`)
|
||||
return this.delete(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/`
|
||||
)
|
||||
.then((response) => {
|
||||
trackEventServices.trackIssueEstimateEvent(response?.data, "ESTIMATE_DELETE", user);
|
||||
if (trackEvent)
|
||||
trackEventServices.trackIssueEstimateEvent(response?.data, "ESTIMATE_DELETE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -0,0 +1,99 @@
|
||||
// services
|
||||
import APIService from "services/api.service";
|
||||
|
||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||
|
||||
interface UnSplashImage {
|
||||
id: string;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
promoted_at: Date;
|
||||
width: number;
|
||||
height: number;
|
||||
color: string;
|
||||
blur_hash: string;
|
||||
description: null;
|
||||
alt_description: string;
|
||||
urls: UnSplashImageUrls;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface UnSplashImageUrls {
|
||||
raw: string;
|
||||
full: string;
|
||||
regular: string;
|
||||
small: string;
|
||||
thumb: string;
|
||||
small_s3: string;
|
||||
}
|
||||
|
||||
class FileServices extends APIService {
|
||||
constructor() {
|
||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async uploadFile(workspaceSlug: string, file: FormData): Promise<any> {
|
||||
return this.mediaUpload(`/api/workspaces/${workspaceSlug}/file-assets/`, file)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async deleteImage(assetUrlWithWorkspaceId: string): Promise<any> {
|
||||
return this.delete(`/api/workspaces/file-assets/${assetUrlWithWorkspaceId}/`)
|
||||
.then((response) => response?.status)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async deleteFile(workspaceId: string, assetUrl: string): Promise<any> {
|
||||
const lastIndex = assetUrl.lastIndexOf("/");
|
||||
const assetId = assetUrl.substring(lastIndex + 1);
|
||||
|
||||
return this.delete(`/api/workspaces/file-assets/${workspaceId}/${assetId}/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
async uploadUserFile(file: FormData): Promise<any> {
|
||||
return this.mediaUpload(`/api/users/file-assets/`, file)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async deleteUserFile(assetUrl: string): Promise<any> {
|
||||
const lastIndex = assetUrl.lastIndexOf("/");
|
||||
const assetId = assetUrl.substring(lastIndex + 1);
|
||||
|
||||
return this.delete(`/api/users/file-assets/${assetId}`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getUnsplashImages(page: number = 1, query?: string): Promise<UnSplashImage[]> {
|
||||
const url = "/api/unsplash";
|
||||
|
||||
return this.request({
|
||||
method: "get",
|
||||
url,
|
||||
params: {
|
||||
page,
|
||||
per_page: 20,
|
||||
query,
|
||||
},
|
||||
})
|
||||
.then((response) => response?.data?.results ?? response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new FileServices();
|
||||
@@ -1,24 +1,33 @@
|
||||
import APIService from "services/api.service";
|
||||
import trackEventServices from "services/track_event.service";
|
||||
import { API_BASE_URL } from "helpers/common.helper";
|
||||
import trackEventServices from "services/track-event.service";
|
||||
|
||||
import { ICurrentUserResponse, IGithubRepoInfo, IGithubServiceImportFormData } from "types";
|
||||
|
||||
const integrationServiceType: string = "github";
|
||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||
|
||||
export class GithubIntegrationService extends APIService {
|
||||
const trackEvent =
|
||||
process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1";
|
||||
|
||||
const integrationServiceType: string = "github";
|
||||
class GithubIntegrationService extends APIService {
|
||||
constructor() {
|
||||
super(API_BASE_URL);
|
||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async listAllRepositories(workspaceSlug: string, integrationSlug: string): Promise<any> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/workspace-integrations/${integrationSlug}/github-repositories`)
|
||||
return this.get(
|
||||
`/api/workspaces/${workspaceSlug}/workspace-integrations/${integrationSlug}/github-repositories`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getGithubRepoInfo(workspaceSlug: string, params: { owner: string; repo: string }): Promise<IGithubRepoInfo> {
|
||||
async getGithubRepoInfo(
|
||||
workspaceSlug: string,
|
||||
params: { owner: string; repo: string }
|
||||
): Promise<IGithubRepoInfo> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/importers/${integrationServiceType}/`, {
|
||||
params,
|
||||
})
|
||||
@@ -33,9 +42,13 @@ export class GithubIntegrationService extends APIService {
|
||||
data: IGithubServiceImportFormData,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/importers/${integrationServiceType}/`, data)
|
||||
return this.post(
|
||||
`/api/workspaces/${workspaceSlug}/projects/importers/${integrationServiceType}/`,
|
||||
data
|
||||
)
|
||||
.then((response) => {
|
||||
trackEventServices.trackImporterEvent(response?.data, "GITHUB_IMPORTER_CREATE", user);
|
||||
if (trackEvent)
|
||||
trackEventServices.trackImporterEvent(response?.data, "GITHUB_IMPORTER_CREATE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -0,0 +1,183 @@
|
||||
import APIService from "services/api.service";
|
||||
import trackEventServices from "services/track-event.service";
|
||||
|
||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||
|
||||
const trackEvent =
|
||||
process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1";
|
||||
|
||||
// types
|
||||
import type {
|
||||
IInboxIssue,
|
||||
IInbox,
|
||||
TInboxStatus,
|
||||
IInboxIssueDetail,
|
||||
ICurrentUserResponse,
|
||||
IInboxFilterOptions,
|
||||
IInboxQueryParams,
|
||||
} from "types";
|
||||
|
||||
class InboxServices extends APIService {
|
||||
constructor() {
|
||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async getInboxes(workspaceSlug: string, projectId: string): Promise<IInbox[]> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/inboxes/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getInboxById(workspaceSlug: string, projectId: string, inboxId: string): Promise<IInbox> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/inboxes/${inboxId}/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async patchInbox(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
inboxId: string,
|
||||
data: Partial<IInbox>
|
||||
): Promise<any> {
|
||||
return this.patch(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/inboxes/${inboxId}/`,
|
||||
data
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getInboxIssues(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
inboxId: string,
|
||||
params?: IInboxQueryParams
|
||||
): Promise<IInboxIssue[]> {
|
||||
return this.get(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/inboxes/${inboxId}/inbox-issues/`,
|
||||
{ params }
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getInboxIssueById(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
inboxId: string,
|
||||
inboxIssueId: string
|
||||
): Promise<IInboxIssueDetail> {
|
||||
return this.get(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/inboxes/${inboxId}/inbox-issues/${inboxIssueId}/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async deleteInboxIssue(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
inboxId: string,
|
||||
inboxIssueId: string,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.delete(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/inboxes/${inboxId}/inbox-issues/${inboxIssueId}/`
|
||||
)
|
||||
.then((response) => {
|
||||
if (trackEvent)
|
||||
trackEventServices.trackInboxEvent(response?.data, "INBOX_ISSUE_DELETE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async markInboxStatus(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
inboxId: string,
|
||||
inboxIssueId: string,
|
||||
data: TInboxStatus,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<IInboxIssue> {
|
||||
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";
|
||||
if (trackEvent) trackEventServices.trackInboxEvent(response?.data, action, user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async patchInboxIssue(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
inboxId: string,
|
||||
inboxIssueId: string,
|
||||
data: { issue: Partial<IInboxIssue> },
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<IInboxIssue> {
|
||||
return this.patch(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/inboxes/${inboxId}/inbox-issues/${inboxIssueId}/`,
|
||||
data
|
||||
)
|
||||
.then((response) => {
|
||||
if (trackEvent)
|
||||
trackEventServices.trackInboxEvent(response?.data, "INBOX_ISSUE_UPDATE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async createInboxIssue(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
inboxId: string,
|
||||
data: any,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<IInboxIssueDetail> {
|
||||
return this.post(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/inboxes/${inboxId}/inbox-issues/`,
|
||||
data
|
||||
)
|
||||
.then((response) => {
|
||||
if (trackEvent)
|
||||
trackEventServices.trackInboxEvent(response?.data, "INBOX_ISSUE_CREATE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const inboxServices = new InboxServices();
|
||||
|
||||
export default inboxServices;
|
||||
@@ -0,0 +1 @@
|
||||
export
|
||||
@@ -0,0 +1,107 @@
|
||||
import APIService from "./api.service";
|
||||
import trackEventServices from "./track-event.service";
|
||||
// types
|
||||
import {
|
||||
IAppIntegration,
|
||||
ICurrentUserResponse,
|
||||
IImporterService,
|
||||
IWorkspaceIntegration,
|
||||
IExportServiceResponse,
|
||||
} from "types";
|
||||
|
||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||
|
||||
const trackEvent =
|
||||
process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" ||
|
||||
process.env.NEXT_PUBLIC_TRACK_EVENTS === "1";
|
||||
|
||||
class IntegrationService extends APIService {
|
||||
constructor() {
|
||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async getAppIntegrationsList(): Promise<IAppIntegration[]> {
|
||||
return this.get(`/api/integrations/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getWorkspaceIntegrationsList(
|
||||
workspaceSlug: string
|
||||
): Promise<IWorkspaceIntegration[]> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/workspace-integrations/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async deleteWorkspaceIntegration(
|
||||
workspaceSlug: string,
|
||||
integrationId: string
|
||||
): Promise<any> {
|
||||
return this.delete(
|
||||
`/api/workspaces/${workspaceSlug}/workspace-integrations/${integrationId}/provider/`
|
||||
)
|
||||
.then((res) => res?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getImporterServicesList(
|
||||
workspaceSlug: string
|
||||
): Promise<IImporterService[]> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/importers/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
async getExportsServicesList(
|
||||
workspaceSlug: string,
|
||||
cursor: string,
|
||||
per_page: number
|
||||
): Promise<IExportServiceResponse> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/export-issues`, {
|
||||
params: {
|
||||
per_page,
|
||||
cursor,
|
||||
},
|
||||
})
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async deleteImporterService(
|
||||
workspaceSlug: string,
|
||||
service: string,
|
||||
importerId: string,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.delete(
|
||||
`/api/workspaces/${workspaceSlug}/importers/${service}/${importerId}/`
|
||||
)
|
||||
.then((response) => {
|
||||
const eventName =
|
||||
service === "github"
|
||||
? "GITHUB_IMPORTER_DELETE"
|
||||
: "JIRA_IMPORTER_DELETE";
|
||||
|
||||
if (trackEvent)
|
||||
trackEventServices.trackImporterEvent(
|
||||
response?.data,
|
||||
eventName,
|
||||
user
|
||||
);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
// services
|
||||
import APIService from "services/api.service";
|
||||
import trackEventServices from "services/track_event.service";
|
||||
import trackEventServices from "services/track-event.service";
|
||||
// type
|
||||
import type {
|
||||
ICurrentUserResponse,
|
||||
@@ -10,11 +10,15 @@ import type {
|
||||
IIssueLabels,
|
||||
ISubIssueResponse,
|
||||
} from "types";
|
||||
import { API_BASE_URL } from "helpers/common.helper";
|
||||
|
||||
export class IssueService extends APIService {
|
||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||
|
||||
const trackEvent =
|
||||
process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1";
|
||||
|
||||
class ProjectIssuesServices extends APIService {
|
||||
constructor() {
|
||||
super(API_BASE_URL);
|
||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async createIssues(
|
||||
@@ -25,7 +29,7 @@ export class IssueService extends APIService {
|
||||
): Promise<any> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/`, data)
|
||||
.then((response) => {
|
||||
trackEventServices.trackIssueEvent(response.data, "ISSUE_CREATE", user);
|
||||
if (trackEvent) trackEventServices.trackIssueEvent(response.data, "ISSUE_CREATE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -63,8 +67,14 @@ export class IssueService extends APIService {
|
||||
});
|
||||
}
|
||||
|
||||
async getIssueActivities(workspaceSlug: string, projectId: string, issueId: string): Promise<IIssueActivity[]> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/history/`)
|
||||
async getIssueActivities(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string
|
||||
): Promise<IIssueActivity[]> {
|
||||
return this.get(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/history/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
@@ -72,7 +82,9 @@ export class IssueService extends APIService {
|
||||
}
|
||||
|
||||
async getIssueComments(workspaceSlug: string, projectId: string, issueId: string): Promise<any> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/comments/`)
|
||||
return this.get(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/comments/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
@@ -96,21 +108,25 @@ export class IssueService extends APIService {
|
||||
},
|
||||
user: ICurrentUserResponse | undefined
|
||||
) {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/cycle-issues/`, data)
|
||||
return this.post(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/cycle-issues/`,
|
||||
data
|
||||
)
|
||||
.then((response) => {
|
||||
trackEventServices.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
|
||||
);
|
||||
if (trackEvent)
|
||||
trackEventServices.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
|
||||
);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -118,7 +134,12 @@ export class IssueService extends APIService {
|
||||
});
|
||||
}
|
||||
|
||||
async removeIssueFromCycle(workspaceSlug: string, projectId: string, cycleId: string, bridgeId: string) {
|
||||
async removeIssueFromCycle(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
cycleId: string,
|
||||
bridgeId: string
|
||||
) {
|
||||
return this.delete(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/cycle-issues/${bridgeId}/`
|
||||
)
|
||||
@@ -128,50 +149,11 @@ export class IssueService extends APIService {
|
||||
});
|
||||
}
|
||||
|
||||
async createIssueRelation(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string,
|
||||
user: ICurrentUserResponse,
|
||||
data: {
|
||||
related_list: Array<{
|
||||
relation_type: "duplicate" | "relates_to" | "blocked_by";
|
||||
related_issue: string;
|
||||
}>;
|
||||
relation?: "blocking" | null;
|
||||
}
|
||||
) {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-relation/`, data)
|
||||
.then((response) => {
|
||||
trackEventServices.trackIssueRelationEvent(response.data, "ISSUE_RELATION_CREATE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response;
|
||||
});
|
||||
}
|
||||
|
||||
async deleteIssueRelation(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string,
|
||||
relationId: string,
|
||||
user: ICurrentUserResponse
|
||||
) {
|
||||
return this.delete(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-relation/${relationId}/`
|
||||
)
|
||||
.then((response) => {
|
||||
trackEventServices.trackIssueRelationEvent(response.data, "ISSUE_RELATION_DELETE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response;
|
||||
});
|
||||
}
|
||||
|
||||
async createIssueProperties(workspaceSlug: string, projectId: string, data: any): Promise<any> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-properties/`, data)
|
||||
return this.post(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-properties/`,
|
||||
data
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
@@ -185,7 +167,8 @@ export class IssueService extends APIService {
|
||||
data: any
|
||||
): Promise<any> {
|
||||
return this.patch(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-properties/` + `${issuePropertyId}/`,
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-properties/` +
|
||||
`${issuePropertyId}/`,
|
||||
data
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
@@ -201,9 +184,13 @@ export class IssueService extends APIService {
|
||||
data: Partial<IIssueComment>,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/comments/`, data)
|
||||
return this.post(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/comments/`,
|
||||
data
|
||||
)
|
||||
.then((response) => {
|
||||
trackEventServices.trackIssueCommentEvent(response.data, "ISSUE_COMMENT_CREATE", user);
|
||||
if (trackEvent)
|
||||
trackEventServices.trackIssueCommentEvent(response.data, "ISSUE_COMMENT_CREATE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -224,7 +211,8 @@ export class IssueService extends APIService {
|
||||
data
|
||||
)
|
||||
.then((response) => {
|
||||
trackEventServices.trackIssueCommentEvent(response.data, "ISSUE_COMMENT_UPDATE", user);
|
||||
if (trackEvent)
|
||||
trackEventServices.trackIssueCommentEvent(response.data, "ISSUE_COMMENT_UPDATE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -243,14 +231,15 @@ export class IssueService extends APIService {
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/comments/${commentId}/`
|
||||
)
|
||||
.then((response) => {
|
||||
trackEventServices.trackIssueCommentEvent(
|
||||
{
|
||||
issueId,
|
||||
commentId,
|
||||
},
|
||||
"ISSUE_COMMENT_DELETE",
|
||||
user
|
||||
);
|
||||
if (trackEvent)
|
||||
trackEventServices.trackIssueCommentEvent(
|
||||
{
|
||||
issueId,
|
||||
commentId,
|
||||
},
|
||||
"ISSUE_COMMENT_DELETE",
|
||||
user
|
||||
);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -282,20 +271,21 @@ export class IssueService extends APIService {
|
||||
): Promise<IIssueLabels> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-labels/`, data)
|
||||
.then((response: { data: IIssueLabels; [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
|
||||
);
|
||||
if (trackEvent)
|
||||
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
|
||||
);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -310,22 +300,26 @@ export class IssueService extends APIService {
|
||||
data: any,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-labels/${labelId}/`, data)
|
||||
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
|
||||
);
|
||||
if (trackEvent)
|
||||
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
|
||||
);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -339,16 +333,19 @@ export class IssueService extends APIService {
|
||||
labelId: string,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-labels/${labelId}/`)
|
||||
return this.delete(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-labels/${labelId}/`
|
||||
)
|
||||
.then((response) => {
|
||||
trackEventServices.trackIssueLabelEvent(
|
||||
{
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
},
|
||||
"ISSUE_LABEL_DELETE",
|
||||
user
|
||||
);
|
||||
if (trackEvent)
|
||||
trackEventServices.trackIssueLabelEvent(
|
||||
{
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
},
|
||||
"ISSUE_LABEL_DELETE",
|
||||
user
|
||||
);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -363,9 +360,12 @@ export class IssueService extends APIService {
|
||||
data: Partial<IIssue>,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/`, data)
|
||||
return this.patch(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/`,
|
||||
data
|
||||
)
|
||||
.then((response) => {
|
||||
trackEventServices.trackIssueEvent(response.data, "ISSUE_UPDATE", user);
|
||||
if (trackEvent) trackEventServices.trackIssueEvent(response.data, "ISSUE_UPDATE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -381,7 +381,7 @@ export class IssueService extends APIService {
|
||||
): Promise<any> {
|
||||
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issuesId}/`)
|
||||
.then((response) => {
|
||||
trackEventServices.trackIssueEvent({ issuesId }, "ISSUE_DELETE", user);
|
||||
if (trackEvent) trackEventServices.trackIssueEvent({ issuesId }, "ISSUE_DELETE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -395,9 +395,12 @@ export class IssueService extends APIService {
|
||||
data: any,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/bulk-delete-issues/`, data)
|
||||
return this.delete(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/bulk-delete-issues/`,
|
||||
data
|
||||
)
|
||||
.then((response) => {
|
||||
trackEventServices.trackIssueBulkDeleteEvent(data, user);
|
||||
if (trackEvent) trackEventServices.trackIssueBulkDeleteEvent(data, user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -405,8 +408,14 @@ export class IssueService extends APIService {
|
||||
});
|
||||
}
|
||||
|
||||
async subIssues(workspaceSlug: string, projectId: string, issueId: string): Promise<ISubIssueResponse> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/sub-issues/`)
|
||||
async subIssues(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string
|
||||
): Promise<ISubIssueResponse> {
|
||||
return this.get(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/sub-issues/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
@@ -419,7 +428,10 @@ export class IssueService extends APIService {
|
||||
issueId: string,
|
||||
data: { sub_issue_ids: string[] }
|
||||
): Promise<any> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/sub-issues/`, data)
|
||||
return this.post(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/sub-issues/`,
|
||||
data
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
@@ -436,7 +448,10 @@ export class IssueService extends APIService {
|
||||
url: string;
|
||||
}
|
||||
): Promise<any> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-links/`, data)
|
||||
return this.post(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-links/`,
|
||||
data
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response;
|
||||
@@ -464,7 +479,12 @@ export class IssueService extends APIService {
|
||||
});
|
||||
}
|
||||
|
||||
async deleteIssueLink(workspaceSlug: string, projectId: string, issueId: string, linkId: string): Promise<any> {
|
||||
async deleteIssueLink(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string,
|
||||
linkId: string
|
||||
): Promise<any> {
|
||||
return this.delete(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-links/${linkId}/`
|
||||
)
|
||||
@@ -474,16 +494,15 @@ export class IssueService extends APIService {
|
||||
});
|
||||
}
|
||||
|
||||
async uploadIssueAttachment(workspaceSlug: string, projectId: string, issueId: string, file: FormData): Promise<any> {
|
||||
return this.post(
|
||||
async uploadIssueAttachment(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string,
|
||||
file: FormData
|
||||
): Promise<any> {
|
||||
return this.mediaUpload(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-attachments/`,
|
||||
file,
|
||||
{
|
||||
headers: {
|
||||
...this.getHeaders(),
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
}
|
||||
file
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
@@ -491,8 +510,14 @@ export class IssueService extends APIService {
|
||||
});
|
||||
}
|
||||
|
||||
async getIssueAttachment(workspaceSlug: string, projectId: string, issueId: string): Promise<any> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-attachments/`)
|
||||
async getIssueAttachment(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string
|
||||
): Promise<any> {
|
||||
return this.get(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-attachments/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
@@ -532,81 +557,33 @@ export class IssueService extends APIService {
|
||||
});
|
||||
}
|
||||
|
||||
async retrieveArchivedIssue(workspaceSlug: string, projectId: string, issueId: string): Promise<any> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/archived-issues/${issueId}/`)
|
||||
async retrieveArchivedIssue(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string
|
||||
): Promise<any> {
|
||||
return this.get(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/archived-issues/${issueId}/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async deleteArchivedIssue(workspaceSlug: string, projectId: string, issuesId: string): Promise<any> {
|
||||
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/archived-issues/${issuesId}/`)
|
||||
async deleteArchivedIssue(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issuesId: string
|
||||
): Promise<any> {
|
||||
return this.delete(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/archived-issues/${issuesId}/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getDraftIssues(workspaceSlug: string, projectId: string, params?: any): Promise<any> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-drafts/`, {
|
||||
params,
|
||||
})
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response;
|
||||
});
|
||||
}
|
||||
|
||||
async createDraftIssue(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
data: any,
|
||||
user: ICurrentUserResponse
|
||||
): Promise<any> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-drafts/`, data)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response;
|
||||
});
|
||||
}
|
||||
|
||||
async updateDraftIssue(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string,
|
||||
data: any,
|
||||
user: ICurrentUserResponse
|
||||
): Promise<any> {
|
||||
return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-drafts/${issueId}/`, data)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response;
|
||||
});
|
||||
}
|
||||
|
||||
async deleteDraftIssue(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string,
|
||||
user: ICurrentUserResponse
|
||||
): Promise<any> {
|
||||
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-drafts/${issueId}/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response;
|
||||
});
|
||||
}
|
||||
|
||||
async getDraftIssueById(workspaceSlug: string, projectId: string, issueId: string): Promise<any> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-drafts/${issueId}/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const issueService = new IssueService();
|
||||
|
||||
export default issueService;
|
||||
export default new ProjectIssuesServices();
|
||||
@@ -1,12 +1,17 @@
|
||||
import APIService from "services/api.service";
|
||||
import trackEventServices from "services/track_event.service";
|
||||
import { API_BASE_URL } from "helpers/common.helper";
|
||||
import trackEventServices from "services/track-event.service";
|
||||
|
||||
// types
|
||||
import { IJiraMetadata, IJiraResponse, IJiraImporterForm, ICurrentUserResponse } from "types";
|
||||
|
||||
export class JiraImporterService extends APIService {
|
||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||
|
||||
const trackEvent =
|
||||
process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1";
|
||||
|
||||
class JiraImportedService extends APIService {
|
||||
constructor() {
|
||||
super(API_BASE_URL);
|
||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async getJiraProjectInfo(workspaceSlug: string, params: IJiraMetadata): Promise<IJiraResponse> {
|
||||
@@ -26,7 +31,8 @@ export class JiraImporterService extends APIService {
|
||||
): Promise<IJiraResponse> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/importers/jira/`, data)
|
||||
.then((response) => {
|
||||
trackEventServices.trackImporterEvent(response?.data, "JIRA_IMPORTER_CREATE", user);
|
||||
if (trackEvent)
|
||||
trackEventServices.trackImporterEvent(response?.data, "JIRA_IMPORTER_CREATE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -34,5 +40,7 @@ export class JiraImporterService extends APIService {
|
||||
});
|
||||
}
|
||||
}
|
||||
const jiraService = new JiraImporterService();
|
||||
export default jiraService;
|
||||
|
||||
const jiraImporterService = new JiraImportedService();
|
||||
|
||||
export default jiraImporterService;
|
||||
@@ -0,0 +1,283 @@
|
||||
// services
|
||||
import APIService from "services/api.service";
|
||||
import trackEventServices from "./track-event.service";
|
||||
|
||||
// types
|
||||
import type { IModule, IIssue, ICurrentUserResponse } from "types";
|
||||
|
||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||
|
||||
const trackEvent =
|
||||
process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1";
|
||||
|
||||
class ProjectIssuesServices extends APIService {
|
||||
constructor() {
|
||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async getModules(workspaceSlug: string, projectId: string): Promise<IModule[]> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async createModule(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
data: any,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/`, data)
|
||||
.then((response) => {
|
||||
if (trackEvent) trackEventServices.trackModuleEvent(response?.data, "MODULE_CREATE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async updateModule(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
moduleId: string,
|
||||
data: any,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.put(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/`,
|
||||
data
|
||||
)
|
||||
.then((response) => {
|
||||
if (trackEvent) trackEventServices.trackModuleEvent(response?.data, "MODULE_UPDATE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getModuleDetails(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
moduleId: string
|
||||
): Promise<IModule> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async patchModule(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
moduleId: string,
|
||||
data: Partial<IModule>,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.patch(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/`,
|
||||
data
|
||||
)
|
||||
.then((response) => {
|
||||
if (trackEvent) trackEventServices.trackModuleEvent(response?.data, "MODULE_UPDATE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async deleteModule(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
moduleId: string,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.delete(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/`
|
||||
)
|
||||
.then((response) => {
|
||||
if (trackEvent) trackEventServices.trackModuleEvent(response?.data, "MODULE_DELETE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getModuleIssues(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
moduleId: string
|
||||
): Promise<IIssue[]> {
|
||||
return this.get(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/module-issues/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getModuleIssuesWithParams(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
moduleId: string,
|
||||
queries?: any
|
||||
): Promise<
|
||||
| IIssue[]
|
||||
| {
|
||||
[key: string]: IIssue[];
|
||||
}
|
||||
> {
|
||||
return this.get(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/module-issues/`,
|
||||
{ params: queries }
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async addIssuesToModule(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
moduleId: string,
|
||||
data: { issues: string[] },
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.post(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/module-issues/`,
|
||||
data
|
||||
)
|
||||
.then((response) => {
|
||||
if (trackEvent)
|
||||
trackEventServices.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
|
||||
);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async removeIssueFromModule(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
moduleId: string,
|
||||
bridgeId: string
|
||||
): Promise<any> {
|
||||
return this.delete(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/module-issues/${bridgeId}/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async createModuleLink(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
moduleId: string,
|
||||
data: {
|
||||
metadata: any;
|
||||
title: string;
|
||||
url: string;
|
||||
}
|
||||
): Promise<any> {
|
||||
return this.post(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/module-links/`,
|
||||
data
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response;
|
||||
});
|
||||
}
|
||||
|
||||
async updateModuleLink(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
moduleId: string,
|
||||
linkId: string,
|
||||
data: {
|
||||
metadata: any;
|
||||
title: string;
|
||||
url: string;
|
||||
}
|
||||
): Promise<any> {
|
||||
return this.patch(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/module-links/${linkId}/`,
|
||||
data
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response;
|
||||
});
|
||||
}
|
||||
|
||||
async deleteModuleLink(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
moduleId: string,
|
||||
linkId: string
|
||||
): Promise<any> {
|
||||
return this.delete(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/module-links/${linkId}/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async addModuleToFavorites(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
data: {
|
||||
module: string;
|
||||
}
|
||||
): Promise<any> {
|
||||
return this.post(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/user-favorite-modules/`,
|
||||
data
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async removeModuleFromFavorites(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
moduleId: string
|
||||
): Promise<any> {
|
||||
return this.delete(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/user-favorite-modules/${moduleId}/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new ProjectIssuesServices();
|
||||
@@ -0,0 +1,193 @@
|
||||
// services
|
||||
import APIService from "services/api.service";
|
||||
|
||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||
|
||||
// types
|
||||
import type {
|
||||
IUserNotification,
|
||||
INotificationParams,
|
||||
NotificationCount,
|
||||
PaginatedUserNotification,
|
||||
IMarkAllAsReadPayload,
|
||||
} from "types";
|
||||
|
||||
class UserNotificationsServices extends APIService {
|
||||
constructor() {
|
||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async getUserNotifications(
|
||||
workspaceSlug: string,
|
||||
params: INotificationParams
|
||||
): Promise<IUserNotification[]> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/users/notifications`, {
|
||||
params,
|
||||
})
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getUserNotificationDetailById(
|
||||
workspaceSlug: string,
|
||||
notificationId: string
|
||||
): Promise<IUserNotification> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/users/notifications/${notificationId}/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async markUserNotificationAsRead(
|
||||
workspaceSlug: string,
|
||||
notificationId: string
|
||||
): Promise<IUserNotification> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/users/notifications/${notificationId}/read/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async markUserNotificationAsUnread(
|
||||
workspaceSlug: string,
|
||||
notificationId: string
|
||||
): Promise<IUserNotification> {
|
||||
return this.delete(
|
||||
`/api/workspaces/${workspaceSlug}/users/notifications/${notificationId}/read/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async markUserNotificationAsArchived(
|
||||
workspaceSlug: string,
|
||||
notificationId: string
|
||||
): Promise<IUserNotification> {
|
||||
return this.post(
|
||||
`/api/workspaces/${workspaceSlug}/users/notifications/${notificationId}/archive/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async markUserNotificationAsUnarchived(
|
||||
workspaceSlug: string,
|
||||
notificationId: string
|
||||
): Promise<IUserNotification> {
|
||||
return this.delete(
|
||||
`/api/workspaces/${workspaceSlug}/users/notifications/${notificationId}/archive/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async patchUserNotification(
|
||||
workspaceSlug: string,
|
||||
notificationId: string,
|
||||
data: Partial<IUserNotification>
|
||||
): Promise<IUserNotification> {
|
||||
return this.patch(
|
||||
`/api/workspaces/${workspaceSlug}/users/notifications/${notificationId}/`,
|
||||
data
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async deleteUserNotification(workspaceSlug: string, notificationId: string): Promise<any> {
|
||||
return this.delete(`/api/workspaces/${workspaceSlug}/users/notifications/${notificationId}/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async subscribeToIssueNotifications(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string
|
||||
): Promise<any> {
|
||||
return this.post(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/subscribe/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getIssueNotificationSubscriptionStatus(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string
|
||||
): Promise<{
|
||||
subscribed: boolean;
|
||||
}> {
|
||||
return this.get(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/subscribe/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async unsubscribeFromIssueNotifications(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string
|
||||
): Promise<any> {
|
||||
return this.delete(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/subscribe/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getUnreadNotificationsCount(workspaceSlug: string): Promise<NotificationCount> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/users/notifications/unread/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getNotifications(url: string): Promise<PaginatedUserNotification> {
|
||||
return this.get(url)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async markAllNotificationsAsRead(
|
||||
workspaceSlug: string,
|
||||
payload: IMarkAllAsReadPayload
|
||||
): Promise<any> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/users/notifications/mark-all-read/`, {
|
||||
...payload,
|
||||
})
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const userNotificationServices = new UserNotificationsServices();
|
||||
|
||||
export default userNotificationServices;
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "plane-services",
|
||||
"version": "0.0.1",
|
||||
"main": "index.ts",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^1.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,18 @@
|
||||
import { API_BASE_URL } from "helpers/common.helper";
|
||||
// services
|
||||
import APIService from "services/api.service";
|
||||
import trackEventServices from "services/track_event.service";
|
||||
import trackEventServices from "services/track-event.service";
|
||||
|
||||
// types
|
||||
import { IPage, IPageBlock, RecentPagesResponse, IIssue, ICurrentUserResponse } from "types";
|
||||
|
||||
export class PageService extends APIService {
|
||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||
|
||||
const trackEvent =
|
||||
process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1";
|
||||
|
||||
class PageServices extends APIService {
|
||||
constructor() {
|
||||
super(API_BASE_URL);
|
||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async createPage(
|
||||
@@ -18,7 +23,7 @@ export class PageService extends APIService {
|
||||
): Promise<IPage> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/`, data)
|
||||
.then((response) => {
|
||||
trackEventServices.trackPageEvent(response?.data, "PAGE_CREATE", user);
|
||||
if (trackEvent) trackEventServices.trackPageEvent(response?.data, "PAGE_CREATE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -33,9 +38,12 @@ export class PageService extends APIService {
|
||||
data: Partial<IPage>,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<IPage> {
|
||||
return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/`, data)
|
||||
return this.patch(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/`,
|
||||
data
|
||||
)
|
||||
.then((response) => {
|
||||
trackEventServices.trackPageEvent(response?.data, "PAGE_UPDATE", user);
|
||||
if (trackEvent) trackEventServices.trackPageEvent(response?.data, "PAGE_UPDATE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -51,7 +59,7 @@ export class PageService extends APIService {
|
||||
): Promise<any> {
|
||||
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/`)
|
||||
.then((response) => {
|
||||
trackEventServices.trackPageEvent(response?.data, "PAGE_DELETE", user);
|
||||
if (trackEvent) trackEventServices.trackPageEvent(response?.data, "PAGE_DELETE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -66,7 +74,10 @@ export class PageService extends APIService {
|
||||
page: string;
|
||||
}
|
||||
): Promise<any> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/user-favorite-pages/`, data)
|
||||
return this.post(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/user-favorite-pages/`,
|
||||
data
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
@@ -74,7 +85,9 @@ export class PageService extends APIService {
|
||||
}
|
||||
|
||||
async removePageFromFavorites(workspaceSlug: string, projectId: string, pageId: string) {
|
||||
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/user-favorite-pages/${pageId}`)
|
||||
return this.delete(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/user-favorite-pages/${pageId}`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
@@ -124,9 +137,13 @@ export class PageService extends APIService {
|
||||
data: Partial<IPageBlock>,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<IPageBlock> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/page-blocks/`, data)
|
||||
return this.post(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/page-blocks/`,
|
||||
data
|
||||
)
|
||||
.then((response) => {
|
||||
trackEventServices.trackPageBlockEvent(response?.data, "PAGE_BLOCK_CREATE", user);
|
||||
if (trackEvent)
|
||||
trackEventServices.trackPageBlockEvent(response?.data, "PAGE_BLOCK_CREATE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -162,7 +179,8 @@ export class PageService extends APIService {
|
||||
data
|
||||
)
|
||||
.then((response) => {
|
||||
trackEventServices.trackPageBlockEvent(response?.data, "PAGE_BLOCK_UPDATE", user);
|
||||
if (trackEvent)
|
||||
trackEventServices.trackPageBlockEvent(response?.data, "PAGE_BLOCK_UPDATE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -181,7 +199,8 @@ export class PageService extends APIService {
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/page-blocks/${pageBlockId}/`
|
||||
)
|
||||
.then((response) => {
|
||||
trackEventServices.trackPageBlockEvent(response?.data, "PAGE_BLOCK_DELETE", user);
|
||||
if (trackEvent)
|
||||
trackEventServices.trackPageBlockEvent(response?.data, "PAGE_BLOCK_DELETE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -189,8 +208,14 @@ export class PageService extends APIService {
|
||||
});
|
||||
}
|
||||
|
||||
async listPageBlocks(workspaceSlug: string, projectId: string, pageId: string): Promise<IPageBlock[]> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/page-blocks/`)
|
||||
async listPageBlocks(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
pageId: string
|
||||
): Promise<IPageBlock[]> {
|
||||
return this.get(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/page-blocks/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
@@ -208,7 +233,12 @@ export class PageService extends APIService {
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/page-blocks/${blockId}/issues/`
|
||||
)
|
||||
.then((response) => {
|
||||
trackEventServices.trackPageBlockEvent(response?.data, "PAGE_BLOCK_CONVERTED_TO_ISSUE", user);
|
||||
if (trackEvent)
|
||||
trackEventServices.trackPageBlockEvent(
|
||||
response?.data,
|
||||
"PAGE_BLOCK_CONVERTED_TO_ISSUE",
|
||||
user
|
||||
);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -217,4 +247,4 @@ export class PageService extends APIService {
|
||||
}
|
||||
}
|
||||
|
||||
export default new PageService();
|
||||
export default new PageServices();
|
||||
+45
-29
@@ -1,14 +1,18 @@
|
||||
import { API_BASE_URL } from "helpers/common.helper";
|
||||
// services
|
||||
import APIService from "services/api.service";
|
||||
import trackEventServices from "services/track_event.service";
|
||||
import trackEventServices from "services/track-event.service";
|
||||
// types
|
||||
import { ICurrentUserResponse } from "types";
|
||||
import { IProjectPublishSettings } from "store/project_publish";
|
||||
import { IProjectPublishSettings } from "store/project-publish";
|
||||
|
||||
export class ProjectPublishServices extends APIService {
|
||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||
|
||||
const trackEvent =
|
||||
process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1";
|
||||
|
||||
class ProjectServices extends APIService {
|
||||
constructor() {
|
||||
super(API_BASE_URL);
|
||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async getProjectSettingsAsync(
|
||||
@@ -16,13 +20,17 @@ export class ProjectPublishServices extends APIService {
|
||||
project_slug: string,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.get(`/api/workspaces/${workspace_slug}/projects/${project_slug}/project-deploy-boards/`)
|
||||
return this.get(
|
||||
`/api/workspaces/${workspace_slug}/projects/${project_slug}/project-deploy-boards/`
|
||||
)
|
||||
.then((response) => {
|
||||
// trackEventServices.trackProjectPublishSettingsEvent(
|
||||
// response.data,
|
||||
// "GET_PROJECT_PUBLISH_SETTINGS",
|
||||
// user
|
||||
// );
|
||||
if (trackEvent) {
|
||||
// trackEventServices.trackProjectPublishSettingsEvent(
|
||||
// response.data,
|
||||
// "GET_PROJECT_PUBLISH_SETTINGS",
|
||||
// user
|
||||
// );
|
||||
}
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -36,14 +44,18 @@ export class ProjectPublishServices extends APIService {
|
||||
data: IProjectPublishSettings,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.post(`/api/workspaces/${workspace_slug}/projects/${project_slug}/project-deploy-boards/`, data)
|
||||
return this.post(
|
||||
`/api/workspaces/${workspace_slug}/projects/${project_slug}/project-deploy-boards/`,
|
||||
data
|
||||
)
|
||||
.then((response) => {
|
||||
// trackEventServices.trackProjectPublishSettingsEvent(
|
||||
// response.data,
|
||||
// "CREATE_PROJECT_PUBLISH_SETTINGS",
|
||||
// user
|
||||
// );
|
||||
|
||||
if (trackEvent) {
|
||||
// trackEventServices.trackProjectPublishSettingsEvent(
|
||||
// response.data,
|
||||
// "CREATE_PROJECT_PUBLISH_SETTINGS",
|
||||
// user
|
||||
// );
|
||||
}
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -63,11 +75,13 @@ export class ProjectPublishServices extends APIService {
|
||||
data
|
||||
)
|
||||
.then((response) => {
|
||||
// trackEventServices.trackProjectPublishSettingsEvent(
|
||||
// response.data,
|
||||
// "UPDATE_PROJECT_PUBLISH_SETTINGS",
|
||||
// user
|
||||
// );
|
||||
if (trackEvent) {
|
||||
// trackEventServices.trackProjectPublishSettingsEvent(
|
||||
// response.data,
|
||||
// "UPDATE_PROJECT_PUBLISH_SETTINGS",
|
||||
// user
|
||||
// );
|
||||
}
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -85,11 +99,13 @@ export class ProjectPublishServices extends APIService {
|
||||
`/api/workspaces/${workspace_slug}/projects/${project_slug}/project-deploy-boards/${project_publish_id}/`
|
||||
)
|
||||
.then((response) => {
|
||||
// trackEventServices.trackProjectPublishSettingsEvent(
|
||||
// response.data,
|
||||
// "DELETE_PROJECT_PUBLISH_SETTINGS",
|
||||
// user
|
||||
// );
|
||||
if (trackEvent) {
|
||||
// trackEventServices.trackProjectPublishSettingsEvent(
|
||||
// response.data,
|
||||
// "DELETE_PROJECT_PUBLISH_SETTINGS",
|
||||
// user
|
||||
// );
|
||||
}
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -98,4 +114,4 @@ export class ProjectPublishServices extends APIService {
|
||||
}
|
||||
}
|
||||
|
||||
export default new ProjectPublishServices();
|
||||
export default ProjectServices;
|
||||
@@ -0,0 +1,392 @@
|
||||
// services
|
||||
import APIService from "services/api.service";
|
||||
import trackEventServices from "services/track-event.service";
|
||||
|
||||
// types
|
||||
import type {
|
||||
GithubRepositoriesResponse,
|
||||
ICurrentUserResponse,
|
||||
IProject,
|
||||
IProjectBulkInviteFormData,
|
||||
IProjectMember,
|
||||
IProjectMemberInvitation,
|
||||
ISearchIssueResponse,
|
||||
ProjectPreferences,
|
||||
IProjectViewProps,
|
||||
TProjectIssuesSearchParams,
|
||||
} from "types";
|
||||
|
||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||
|
||||
const trackEvent =
|
||||
process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1";
|
||||
|
||||
export class ProjectServices extends APIService {
|
||||
constructor() {
|
||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async createProject(
|
||||
workspaceSlug: string,
|
||||
data: Partial<IProject>,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<IProject> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/`, data)
|
||||
.then((response) => {
|
||||
if (trackEvent) trackEventServices.trackProjectEvent(response.data, "CREATE_PROJECT", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response;
|
||||
});
|
||||
}
|
||||
|
||||
async checkProjectIdentifierAvailability(workspaceSlug: string, data: string): Promise<any> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/project-identifiers`, {
|
||||
params: {
|
||||
name: data,
|
||||
},
|
||||
})
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getProjects(
|
||||
workspaceSlug: string,
|
||||
params: {
|
||||
is_favorite: "all" | boolean;
|
||||
}
|
||||
): Promise<IProject[]> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/`, {
|
||||
params,
|
||||
})
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getProject(workspaceSlug: string, projectId: string): Promise<IProject> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async updateProject(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
data: Partial<IProject>,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<IProject> {
|
||||
return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/`, data)
|
||||
.then((response) => {
|
||||
if (trackEvent) trackEventServices.trackProjectEvent(response.data, "UPDATE_PROJECT", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async deleteProject(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/`)
|
||||
.then((response) => {
|
||||
if (trackEvent) trackEventServices.trackProjectEvent({ projectId }, "DELETE_PROJECT", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async inviteProject(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
data: IProjectBulkInviteFormData,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/members/add/`, data)
|
||||
.then((response) => {
|
||||
if (trackEvent)
|
||||
trackEventServices.trackProjectEvent(
|
||||
{
|
||||
workspaceId: response?.data?.workspace?.id,
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
projectName: response?.data?.project?.name,
|
||||
memberEmail: response?.data?.member?.email,
|
||||
},
|
||||
"PROJECT_MEMBER_INVITE",
|
||||
user
|
||||
);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async joinProject(workspaceSlug: string, data: any): Promise<any> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/join/`, data)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async leaveProject(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
user: ICurrentUserResponse
|
||||
): Promise<any> {
|
||||
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/members/leave/`)
|
||||
.then((response) => {
|
||||
if (trackEvent)
|
||||
trackEventServices.trackProjectEvent(
|
||||
"PROJECT_MEMBER_LEAVE",
|
||||
{
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
...response?.data,
|
||||
},
|
||||
user
|
||||
);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async joinProjects(data: any): Promise<any> {
|
||||
return this.post("/api/users/me/invitations/projects/", data)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async projectMembers(workspaceSlug: string, projectId: string): Promise<IProjectMember[]> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/project-members/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async projectMembersWithEmail(
|
||||
workspaceSlug: string,
|
||||
projectId: string
|
||||
): Promise<IProjectMember[]> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/members/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async projectMemberMe(workspaceSlug: string, projectId: string): Promise<IProjectMember> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/project-members/me/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response;
|
||||
});
|
||||
}
|
||||
|
||||
async getProjectMember(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
memberId: string
|
||||
): Promise<IProjectMember> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/members/${memberId}/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async updateProjectMember(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
memberId: string,
|
||||
data: Partial<IProjectMember>
|
||||
): Promise<IProjectMember> {
|
||||
return this.patch(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/members/${memberId}/`,
|
||||
data
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async deleteProjectMember(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
memberId: string
|
||||
): Promise<any> {
|
||||
return this.delete(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/members/${memberId}/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async projectInvitations(
|
||||
workspaceSlug: string,
|
||||
projectId: string
|
||||
): Promise<IProjectMemberInvitation[]> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/invitations/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async projectInvitationsWithEmail(
|
||||
workspaceSlug: string,
|
||||
projectId: string
|
||||
): Promise<IProjectMemberInvitation[]> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/invitations/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async updateProjectInvitation(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
invitationId: string
|
||||
): Promise<any> {
|
||||
return this.put(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/invitations/${invitationId}/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async deleteProjectInvitation(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
invitationId: string
|
||||
): Promise<any> {
|
||||
return this.delete(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/invitations/${invitationId}/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async setProjectView(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
data: {
|
||||
view_props?: IProjectViewProps;
|
||||
default_props?: IProjectViewProps;
|
||||
preferences?: ProjectPreferences;
|
||||
sort_order?: number;
|
||||
}
|
||||
): Promise<any> {
|
||||
await this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/project-views/`, data)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getGithubRepositories(url: string): Promise<GithubRepositoriesResponse> {
|
||||
return this.getWithoutBase(url)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async syncGithubRepository(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
workspaceIntegrationId: string,
|
||||
data: {
|
||||
name: string;
|
||||
owner: string;
|
||||
repository_id: string;
|
||||
url: string;
|
||||
}
|
||||
): Promise<any> {
|
||||
return this.post(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/workspace-integrations/${workspaceIntegrationId}/github-repository-sync/`,
|
||||
data
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getProjectGithubRepository(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
integrationId: string
|
||||
): Promise<any> {
|
||||
return this.get(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/workspace-integrations/${integrationId}/github-repository-sync/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async addProjectToFavorites(
|
||||
workspaceSlug: string,
|
||||
data: {
|
||||
project: string;
|
||||
}
|
||||
): Promise<any> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/user-favorite-projects/`, data)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async removeProjectFromFavorites(workspaceSlug: string, projectId: string): Promise<any> {
|
||||
return this.delete(`/api/workspaces/${workspaceSlug}/user-favorite-projects/${projectId}/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async projectIssuesSearch(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
params: TProjectIssuesSearchParams
|
||||
): Promise<ISearchIssueResponse[]> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/search-issues/`, {
|
||||
params,
|
||||
})
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new ProjectServices();
|
||||
+46
-15
@@ -1,7 +1,7 @@
|
||||
import { API_BASE_URL } from "helpers/common.helper";
|
||||
// services
|
||||
import APIService from "services/api.service";
|
||||
import trackEventServices from "services/track_event.service";
|
||||
import trackEventServices from "services/track-event.service";
|
||||
|
||||
// types
|
||||
import type {
|
||||
ICurrentUserResponse,
|
||||
@@ -11,9 +11,14 @@ import type {
|
||||
IssueCommentReactionForm,
|
||||
} from "types";
|
||||
|
||||
export class IssueReactionService extends APIService {
|
||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||
|
||||
const trackEvent =
|
||||
process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1";
|
||||
|
||||
class ReactionService extends APIService {
|
||||
constructor() {
|
||||
super(API_BASE_URL);
|
||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async createIssueReaction(
|
||||
@@ -23,9 +28,13 @@ export class IssueReactionService extends APIService {
|
||||
data: IssueReactionForm,
|
||||
user?: ICurrentUserResponse
|
||||
): Promise<any> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/reactions/`, data)
|
||||
return this.post(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/reactions/`,
|
||||
data
|
||||
)
|
||||
.then((response) => {
|
||||
trackEventServices.trackReactionEvent(response?.data, "ISSUE_REACTION_CREATE", user);
|
||||
if (trackEvent)
|
||||
trackEventServices.trackReactionEvent(response?.data, "ISSUE_REACTION_CREATE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -33,8 +42,14 @@ export class IssueReactionService extends APIService {
|
||||
});
|
||||
}
|
||||
|
||||
async listIssueReactions(workspaceSlug: string, projectId: string, issueId: string): Promise<IssueReaction[]> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/reactions/`)
|
||||
async listIssueReactions(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string
|
||||
): Promise<IssueReaction[]> {
|
||||
return this.get(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/reactions/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
@@ -52,7 +67,8 @@ export class IssueReactionService extends APIService {
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/reactions/${reaction}/`
|
||||
)
|
||||
.then((response) => {
|
||||
trackEventServices.trackReactionEvent(response?.data, "ISSUE_REACTION_DELETE", user);
|
||||
if (trackEvent)
|
||||
trackEventServices.trackReactionEvent(response?.data, "ISSUE_REACTION_DELETE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -67,9 +83,17 @@ export class IssueReactionService extends APIService {
|
||||
data: IssueCommentReactionForm,
|
||||
user?: ICurrentUserResponse
|
||||
): Promise<any> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/comments/${commentId}/reactions/`, data)
|
||||
return this.post(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/comments/${commentId}/reactions/`,
|
||||
data
|
||||
)
|
||||
.then((response) => {
|
||||
trackEventServices.trackReactionEvent(response?.data, "ISSUE_COMMENT_REACTION_CREATE", user);
|
||||
if (trackEvent)
|
||||
trackEventServices.trackReactionEvent(
|
||||
response?.data,
|
||||
"ISSUE_COMMENT_REACTION_CREATE",
|
||||
user
|
||||
);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -82,7 +106,9 @@ export class IssueReactionService extends APIService {
|
||||
projectId: string,
|
||||
commentId: string
|
||||
): Promise<IssueCommentReaction[]> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/comments/${commentId}/reactions/`)
|
||||
return this.get(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/comments/${commentId}/reactions/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
@@ -100,7 +126,12 @@ export class IssueReactionService extends APIService {
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/comments/${commentId}/reactions/${reaction}/`
|
||||
)
|
||||
.then((response) => {
|
||||
trackEventServices.trackReactionEvent(response?.data, "ISSUE_COMMENT_REACTION_DELETE", user);
|
||||
if (trackEvent)
|
||||
trackEventServices.trackReactionEvent(
|
||||
response?.data,
|
||||
"ISSUE_COMMENT_REACTION_DELETE",
|
||||
user
|
||||
);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -109,6 +140,6 @@ export class IssueReactionService extends APIService {
|
||||
}
|
||||
}
|
||||
|
||||
const issueReactionService = new IssueReactionService();
|
||||
const reactionService = new ReactionService();
|
||||
|
||||
export default issueReactionService;
|
||||
export default reactionService;
|
||||
@@ -1,14 +1,18 @@
|
||||
// services
|
||||
import APIService from "services/api.service";
|
||||
import trackEventServices from "services/track_event.service";
|
||||
// helpers
|
||||
import { API_BASE_URL } from "helpers/common.helper";
|
||||
import trackEventServices from "services/track-event.service";
|
||||
|
||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||
|
||||
const trackEvent =
|
||||
process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1";
|
||||
|
||||
// types
|
||||
import type { ICurrentUserResponse, IState, IStateResponse } from "types";
|
||||
|
||||
export class ProjectStateServices extends APIService {
|
||||
class ProjectStateServices extends APIService {
|
||||
constructor() {
|
||||
super(API_BASE_URL);
|
||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async createState(
|
||||
@@ -19,7 +23,7 @@ export class ProjectStateServices extends APIService {
|
||||
): Promise<any> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/states/`, data)
|
||||
.then((response) => {
|
||||
trackEventServices.trackStateEvent(response?.data, "STATE_CREATE", user);
|
||||
if (trackEvent) trackEventServices.trackStateEvent(response?.data, "STATE_CREATE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -59,9 +63,12 @@ export class ProjectStateServices extends APIService {
|
||||
data: IState,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.put(`/api/workspaces/${workspaceSlug}/projects/${projectId}/states/${stateId}/`, data)
|
||||
return this.put(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/states/${stateId}/`,
|
||||
data
|
||||
)
|
||||
.then((response) => {
|
||||
trackEventServices.trackStateEvent(response?.data, "STATE_UPDATE", user);
|
||||
if (trackEvent) trackEventServices.trackStateEvent(response?.data, "STATE_UPDATE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -76,9 +83,12 @@ export class ProjectStateServices extends APIService {
|
||||
data: Partial<IState>,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/states/${stateId}/`, data)
|
||||
return this.patch(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/states/${stateId}/`,
|
||||
data
|
||||
)
|
||||
.then((response) => {
|
||||
trackEventServices.trackStateEvent(response?.data, "STATE_UPDATE", user);
|
||||
if (trackEvent) trackEventServices.trackStateEvent(response?.data, "STATE_UPDATE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -94,7 +104,7 @@ export class ProjectStateServices extends APIService {
|
||||
): Promise<any> {
|
||||
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/states/${stateId}/`)
|
||||
.then((response) => {
|
||||
trackEventServices.trackStateEvent(response?.data, "STATE_DELETE", user);
|
||||
if (trackEvent) trackEventServices.trackStateEvent(response?.data, "STATE_DELETE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
+4
-70
@@ -131,8 +131,6 @@ class TrackEventServices extends APIService {
|
||||
eventName: WorkspaceEventType,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
if (!trackEvent) return;
|
||||
|
||||
let payload: any;
|
||||
if (
|
||||
eventName !== "DELETE_WORKSPACE" &&
|
||||
@@ -165,8 +163,6 @@ class TrackEventServices extends APIService {
|
||||
eventName: ProjectEventType,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
if (!trackEvent) return;
|
||||
|
||||
let payload: any;
|
||||
if (
|
||||
eventName !== "DELETE_PROJECT" &&
|
||||
@@ -199,8 +195,6 @@ class TrackEventServices extends APIService {
|
||||
data: any,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
if (!trackEvent) return;
|
||||
|
||||
return this.request({
|
||||
url: "/api/track-event",
|
||||
method: "POST",
|
||||
@@ -218,8 +212,6 @@ class TrackEventServices extends APIService {
|
||||
data: any,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
if (!trackEvent) return;
|
||||
|
||||
return this.request({
|
||||
url: "/api/track-event",
|
||||
method: "POST",
|
||||
@@ -238,8 +230,6 @@ class TrackEventServices extends APIService {
|
||||
eventName: IssueEventType,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
if (!trackEvent) return;
|
||||
|
||||
let payload: any;
|
||||
if (eventName !== "ISSUE_DELETE")
|
||||
payload = {
|
||||
@@ -313,8 +303,6 @@ class TrackEventServices extends APIService {
|
||||
eventName: IssueCommentEventType,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
if (!trackEvent) return;
|
||||
|
||||
let payload: any;
|
||||
if (eventName !== "ISSUE_COMMENT_DELETE")
|
||||
payload = {
|
||||
@@ -340,24 +328,6 @@ class TrackEventServices extends APIService {
|
||||
});
|
||||
}
|
||||
|
||||
async trackIssueRelationEvent(
|
||||
data: any,
|
||||
eventName: "ISSUE_RELATION_CREATE" | "ISSUE_RELATION_DELETE",
|
||||
user: ICurrentUserResponse
|
||||
): Promise<any> {
|
||||
if (!trackEvent) return;
|
||||
|
||||
return this.request({
|
||||
url: "/api/track-event",
|
||||
method: "POST",
|
||||
data: {
|
||||
eventName,
|
||||
extra: data,
|
||||
user: user,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async trackIssueMovedToCycleOrModuleEvent(
|
||||
data: any,
|
||||
eventName:
|
||||
@@ -367,8 +337,6 @@ class TrackEventServices extends APIService {
|
||||
| "ISSUE_MOVED_TO_MODULE_IN_BULK",
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
if (!trackEvent) return;
|
||||
|
||||
return this.request({
|
||||
url: "/api/track-event",
|
||||
method: "POST",
|
||||
@@ -383,8 +351,6 @@ class TrackEventServices extends APIService {
|
||||
}
|
||||
|
||||
async trackIssueBulkDeleteEvent(data: any, user: ICurrentUserResponse | undefined): Promise<any> {
|
||||
if (!trackEvent) return;
|
||||
|
||||
return this.request({
|
||||
url: "/api/track-event",
|
||||
method: "POST",
|
||||
@@ -403,8 +369,6 @@ class TrackEventServices extends APIService {
|
||||
eventName: IssueLabelEventType,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
if (!trackEvent) return;
|
||||
|
||||
return this.request({
|
||||
url: "/api/track-event",
|
||||
method: "POST",
|
||||
@@ -423,8 +387,6 @@ class TrackEventServices extends APIService {
|
||||
eventName: StateEventType,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
if (!trackEvent) return;
|
||||
|
||||
let payload: any;
|
||||
if (eventName !== "STATE_DELETE")
|
||||
payload = {
|
||||
@@ -456,8 +418,6 @@ class TrackEventServices extends APIService {
|
||||
eventName: CycleEventType,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
if (!trackEvent) return;
|
||||
|
||||
let payload: any;
|
||||
if (eventName !== "CYCLE_DELETE")
|
||||
payload = {
|
||||
@@ -489,8 +449,6 @@ class TrackEventServices extends APIService {
|
||||
eventName: ModuleEventType,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
if (!trackEvent) return;
|
||||
|
||||
let payload: any;
|
||||
if (eventName !== "MODULE_DELETE")
|
||||
payload = {
|
||||
@@ -522,8 +480,6 @@ class TrackEventServices extends APIService {
|
||||
eventName: PagesEventType,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
if (!trackEvent) return;
|
||||
|
||||
let payload: any;
|
||||
if (eventName !== "PAGE_DELETE")
|
||||
payload = {
|
||||
@@ -555,8 +511,6 @@ class TrackEventServices extends APIService {
|
||||
eventName: PageBlocksEventType,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
if (!trackEvent) return;
|
||||
|
||||
let payload: any;
|
||||
if (eventName !== "PAGE_BLOCK_DELETE" && eventName !== "PAGE_BLOCK_CONVERTED_TO_ISSUE")
|
||||
payload = {
|
||||
@@ -599,8 +553,6 @@ class TrackEventServices extends APIService {
|
||||
eventName: GptEventType,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
if (!trackEvent) return;
|
||||
|
||||
const payload = {
|
||||
workspaceId: data?.workspace_detail?.id,
|
||||
workspaceName: data?.workspace_detail?.name,
|
||||
@@ -673,8 +625,6 @@ class TrackEventServices extends APIService {
|
||||
eventName: ViewEventType,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
if (!trackEvent) return;
|
||||
|
||||
let payload: any;
|
||||
if (eventName === "VIEW_DELETE") payload = data;
|
||||
else
|
||||
@@ -704,8 +654,6 @@ class TrackEventServices extends APIService {
|
||||
eventName: MiscellaneousEventType,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
if (!trackEvent) return;
|
||||
|
||||
return this.request({
|
||||
url: "/api/track-event",
|
||||
method: "POST",
|
||||
@@ -724,8 +672,6 @@ class TrackEventServices extends APIService {
|
||||
eventName: IntegrationEventType,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
if (!trackEvent) return;
|
||||
|
||||
return this.request({
|
||||
url: "/api/track-event",
|
||||
method: "POST",
|
||||
@@ -744,8 +690,6 @@ class TrackEventServices extends APIService {
|
||||
eventName: GitHubSyncEventType,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
if (!trackEvent) return;
|
||||
|
||||
return this.request({
|
||||
url: "/api/track-event",
|
||||
method: "POST",
|
||||
@@ -764,8 +708,6 @@ class TrackEventServices extends APIService {
|
||||
eventName: IssueEstimateEventType,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
if (!trackEvent) return;
|
||||
|
||||
let payload: any;
|
||||
if (eventName === "ESTIMATE_DELETE") payload = data;
|
||||
else
|
||||
@@ -797,8 +739,6 @@ class TrackEventServices extends APIService {
|
||||
eventName: ImporterEventType,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
if (!trackEvent) return;
|
||||
|
||||
let payload: any;
|
||||
if (eventName === "GITHUB_IMPORTER_DELETE" || eventName === "JIRA_IMPORTER_DELETE")
|
||||
payload = data;
|
||||
@@ -830,8 +770,6 @@ class TrackEventServices extends APIService {
|
||||
eventName: AnalyticsEventType,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
if (!trackEvent) return;
|
||||
|
||||
const payload = { ...data };
|
||||
|
||||
return this.request({
|
||||
@@ -845,13 +783,12 @@ class TrackEventServices extends APIService {
|
||||
});
|
||||
}
|
||||
|
||||
// track exporter function\
|
||||
async trackExporterEvent(
|
||||
data: any,
|
||||
eventName: ExporterEventType,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
if (!trackEvent) return;
|
||||
|
||||
const payload = { ...data };
|
||||
|
||||
return this.request({
|
||||
@@ -873,8 +810,6 @@ class TrackEventServices extends APIService {
|
||||
eventName: InboxEventType,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
if (!trackEvent) return;
|
||||
|
||||
let payload: any;
|
||||
if (eventName !== "INBOX_DELETE")
|
||||
payload = {
|
||||
@@ -906,8 +841,6 @@ class TrackEventServices extends APIService {
|
||||
eventName: ReactionEventType,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
if (!trackEvent) return;
|
||||
|
||||
let payload: any;
|
||||
if (eventName === "ISSUE_REACTION_DELETE" || eventName === "ISSUE_COMMENT_REACTION_DELETE")
|
||||
payload = data;
|
||||
@@ -929,13 +862,12 @@ class TrackEventServices extends APIService {
|
||||
});
|
||||
}
|
||||
|
||||
// project publish settings track events starts
|
||||
async trackProjectPublishSettingsEvent(
|
||||
data: any,
|
||||
eventName: string,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
if (!trackEvent) return;
|
||||
|
||||
const payload: any = data;
|
||||
|
||||
return this.request({
|
||||
@@ -948,6 +880,8 @@ class TrackEventServices extends APIService {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// project publish settings track events ends
|
||||
}
|
||||
|
||||
const trackEventServices = new TrackEventServices();
|
||||
@@ -0,0 +1,200 @@
|
||||
// services
|
||||
import APIService from "services/api.service";
|
||||
import trackEventServices from "services/track-event.service";
|
||||
|
||||
import type {
|
||||
ICurrentUserResponse,
|
||||
IIssue,
|
||||
IUser,
|
||||
IUserActivityResponse,
|
||||
IUserProfileData,
|
||||
IUserProfileProjectSegregation,
|
||||
IUserWorkspaceDashboard,
|
||||
} from "types";
|
||||
|
||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||
|
||||
const trackEvent =
|
||||
process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1";
|
||||
|
||||
class UserService extends APIService {
|
||||
constructor() {
|
||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
currentUserConfig() {
|
||||
return {
|
||||
url: `${this.baseURL}/api/users/me/`,
|
||||
headers: this.getHeaders(),
|
||||
};
|
||||
}
|
||||
|
||||
async userIssues(
|
||||
workspaceSlug: string,
|
||||
params: any
|
||||
): Promise<
|
||||
| {
|
||||
[key: string]: IIssue[];
|
||||
}
|
||||
| IIssue[]
|
||||
> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/my-issues/`, {
|
||||
params,
|
||||
})
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async currentUser(): Promise<ICurrentUserResponse> {
|
||||
return this.get("/api/users/me/")
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response;
|
||||
});
|
||||
}
|
||||
|
||||
async updateUser(data: Partial<IUser>): Promise<any> {
|
||||
return this.patch("/api/users/me/", data)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async updateUserOnBoard({ userRole }: any, user: ICurrentUserResponse | undefined): Promise<any> {
|
||||
return this.patch("/api/users/me/onboard/", {
|
||||
is_onboarded: true,
|
||||
})
|
||||
.then((response) => {
|
||||
if (trackEvent)
|
||||
trackEventServices.trackUserOnboardingCompleteEvent(
|
||||
{
|
||||
user_role: userRole ?? "None",
|
||||
},
|
||||
user
|
||||
);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async updateUserTourCompleted(user: ICurrentUserResponse): Promise<any> {
|
||||
return this.patch("/api/users/me/tour-completed/", {
|
||||
is_tour_completed: true,
|
||||
})
|
||||
.then((response) => {
|
||||
if (trackEvent)
|
||||
trackEventServices.trackUserTourCompleteEvent(
|
||||
{
|
||||
user_role: user.role ?? "None",
|
||||
},
|
||||
user
|
||||
);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getUserWorkspaceActivity(workspaceSlug: string): Promise<IUserActivityResponse> {
|
||||
return this.get(`/api/users/workspaces/${workspaceSlug}/activities/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async userWorkspaceDashboard(
|
||||
workspaceSlug: string,
|
||||
month: number
|
||||
): Promise<IUserWorkspaceDashboard> {
|
||||
return this.get(`/api/users/me/workspaces/${workspaceSlug}/dashboard/`, {
|
||||
params: {
|
||||
month: month,
|
||||
},
|
||||
})
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async forgotPassword(data: { email: string }): Promise<any> {
|
||||
return this.post(`/api/forgot-password/`, data)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response;
|
||||
});
|
||||
}
|
||||
|
||||
async resetPassword(
|
||||
uidb64: string,
|
||||
token: string,
|
||||
data: {
|
||||
new_password: string;
|
||||
confirm_password: string;
|
||||
}
|
||||
): Promise<any> {
|
||||
return this.post(`/api/reset-password/${uidb64}/${token}/`, data)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getUserProfileData(workspaceSlug: string, userId: string): Promise<IUserProfileData> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/user-stats/${userId}/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getUserProfileProjectsSegregation(
|
||||
workspaceSlug: string,
|
||||
userId: string
|
||||
): Promise<IUserProfileProjectSegregation> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/user-profile/${userId}/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getUserProfileActivity(
|
||||
workspaceSlug: string,
|
||||
userId: string
|
||||
): Promise<IUserActivityResponse> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/user-activity/${userId}/?per_page=15`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getUserProfileIssues(
|
||||
workspaceSlug: string,
|
||||
userId: string,
|
||||
params: any
|
||||
): Promise<
|
||||
| {
|
||||
[key: string]: IIssue[];
|
||||
}
|
||||
| IIssue[]
|
||||
> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/user-issues/${userId}/`, {
|
||||
params,
|
||||
})
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new UserService();
|
||||
@@ -0,0 +1,146 @@
|
||||
// services
|
||||
import APIService from "services/api.service";
|
||||
import trackEventServices from "services/track-event.service";
|
||||
import { ICurrentUserResponse } from "types";
|
||||
|
||||
// types
|
||||
import { IView } from "types/views";
|
||||
|
||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||
|
||||
const trackEvent =
|
||||
process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1";
|
||||
|
||||
class ViewServices extends APIService {
|
||||
constructor() {
|
||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async createView(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
data: IView,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/views/`, data)
|
||||
.then((response) => {
|
||||
if (trackEvent) trackEventServices.trackViewEvent(response?.data, "VIEW_CREATE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async updateView(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
viewId: string,
|
||||
data: IView,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.put(`/api/workspaces/${workspaceSlug}/projects/${projectId}/views/${viewId}/`, data)
|
||||
.then((response) => {
|
||||
if (trackEvent) trackEventServices.trackViewEvent(response?.data, "VIEW_UPDATE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async patchView(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
viewId: string,
|
||||
data: Partial<IView>,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.patch(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/views/${viewId}/`,
|
||||
data
|
||||
)
|
||||
.then((response) => {
|
||||
if (trackEvent) trackEventServices.trackViewEvent(response?.data, "VIEW_UPDATE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async deleteView(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
viewId: string,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/views/${viewId}/`)
|
||||
.then((response) => {
|
||||
if (trackEvent) trackEventServices.trackViewEvent(response?.data, "VIEW_DELETE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getViews(workspaceSlug: string, projectId: string): Promise<IView[]> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/views/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getViewDetails(workspaceSlug: string, projectId: string, viewId: string): Promise<IView> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/views/${viewId}/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getViewIssues(workspaceSlug: string, projectId: string, viewId: string): Promise<any> {
|
||||
return this.get(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/views/${viewId}/issues/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async addViewToFavorites(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
data: {
|
||||
view: string;
|
||||
}
|
||||
): Promise<any> {
|
||||
return this.post(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/user-favorite-views/`,
|
||||
data
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async removeViewFromFavorites(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
viewId: string
|
||||
): Promise<any> {
|
||||
return this.delete(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/user-favorite-views/${viewId}/`
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new ViewServices();
|
||||
@@ -0,0 +1,22 @@
|
||||
// services
|
||||
import APIService from "services/api.service";
|
||||
|
||||
// types
|
||||
import { IWebWaitListResponse } from "types";
|
||||
|
||||
class WebWailtListServices extends APIService {
|
||||
constructor() {
|
||||
const origin = typeof window !== "undefined" ? window.location.origin || "" : "";
|
||||
super(origin);
|
||||
}
|
||||
|
||||
async create({ email }: { email: string }): Promise<IWebWaitListResponse> {
|
||||
return this.post(`/api/web-waitlist`, { email: email })
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new WebWailtListServices();
|
||||
@@ -0,0 +1,279 @@
|
||||
// services
|
||||
import APIService from "services/api.service";
|
||||
import trackEventServices from "services/track-event.service";
|
||||
|
||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||
|
||||
// types
|
||||
import {
|
||||
IWorkspace,
|
||||
IWorkspaceMember,
|
||||
IWorkspaceMemberInvitation,
|
||||
ILastActiveWorkspaceDetails,
|
||||
IWorkspaceSearchResults,
|
||||
IProductUpdateResponse,
|
||||
ICurrentUserResponse,
|
||||
IWorkspaceBulkInviteFormData,
|
||||
IWorkspaceViewProps,
|
||||
} from "types";
|
||||
|
||||
const trackEvent =
|
||||
process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1";
|
||||
|
||||
class WorkspaceService extends APIService {
|
||||
constructor() {
|
||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async userWorkspaces(): Promise<IWorkspace[]> {
|
||||
return this.get("/api/users/me/workspaces/")
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getWorkspace(workspaceSlug: string): Promise<IWorkspace> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response;
|
||||
});
|
||||
}
|
||||
|
||||
async createWorkspace(
|
||||
data: Partial<IWorkspace>,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<IWorkspace> {
|
||||
return this.post("/api/workspaces/", data)
|
||||
.then((response) => {
|
||||
if (trackEvent)
|
||||
trackEventServices.trackWorkspaceEvent(response.data, "CREATE_WORKSPACE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async updateWorkspace(
|
||||
workspaceSlug: string,
|
||||
data: Partial<IWorkspace>,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<IWorkspace> {
|
||||
return this.patch(`/api/workspaces/${workspaceSlug}/`, data)
|
||||
.then((response) => {
|
||||
if (trackEvent)
|
||||
trackEventServices.trackWorkspaceEvent(response.data, "UPDATE_WORKSPACE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async deleteWorkspace(
|
||||
workspaceSlug: string,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.delete(`/api/workspaces/${workspaceSlug}/`)
|
||||
.then((response) => {
|
||||
if (trackEvent)
|
||||
trackEventServices.trackWorkspaceEvent({ workspaceSlug }, "DELETE_WORKSPACE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async inviteWorkspace(
|
||||
workspaceSlug: string,
|
||||
data: IWorkspaceBulkInviteFormData,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/invite/`, data)
|
||||
.then((response) => {
|
||||
if (trackEvent)
|
||||
trackEventServices.trackWorkspaceEvent(response.data, "WORKSPACE_USER_INVITE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async joinWorkspace(
|
||||
workspaceSlug: string,
|
||||
invitationId: string,
|
||||
data: any,
|
||||
user: ICurrentUserResponse | undefined
|
||||
): Promise<any> {
|
||||
return this.post(
|
||||
`/api/users/me/invitations/workspaces/${workspaceSlug}/${invitationId}/join/`,
|
||||
data,
|
||||
{
|
||||
headers: {},
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
if (trackEvent)
|
||||
trackEventServices.trackWorkspaceEvent(
|
||||
response.data,
|
||||
"WORKSPACE_USER_INVITE_ACCEPT",
|
||||
user
|
||||
);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async joinWorkspaces(data: any): Promise<any> {
|
||||
return this.post("/api/users/me/invitations/workspaces/", data)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getLastActiveWorkspaceAndProjects(): Promise<ILastActiveWorkspaceDetails> {
|
||||
return this.get("/api/users/last-visited-workspace/")
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async userWorkspaceInvitations(): Promise<IWorkspaceMemberInvitation[]> {
|
||||
return this.get("/api/users/me/invitations/workspaces/")
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async workspaceMembers(workspaceSlug: string): Promise<IWorkspaceMember[]> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/workspace-members/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async workspaceMembersWithEmail(workspaceSlug: string): Promise<IWorkspaceMember[]> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/members/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async workspaceMemberMe(workspaceSlug: string): Promise<IWorkspaceMember> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/workspace-members/me/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response;
|
||||
});
|
||||
}
|
||||
|
||||
async updateWorkspaceView(
|
||||
workspaceSlug: string,
|
||||
data: { view_props: IWorkspaceViewProps }
|
||||
): Promise<any> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/workspace-views/`, data)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async updateWorkspaceMember(
|
||||
workspaceSlug: string,
|
||||
memberId: string,
|
||||
data: Partial<IWorkspaceMember>
|
||||
): Promise<IWorkspaceMember> {
|
||||
return this.patch(`/api/workspaces/${workspaceSlug}/members/${memberId}/`, data)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async deleteWorkspaceMember(workspaceSlug: string, memberId: string): Promise<any> {
|
||||
return this.delete(`/api/workspaces/${workspaceSlug}/members/${memberId}/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async workspaceInvitations(workspaceSlug: string): Promise<IWorkspaceMemberInvitation[]> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/invitations/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async workspaceInvitationsWithEmail(
|
||||
workspaceSlug: string
|
||||
): Promise<IWorkspaceMemberInvitation[]> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/invitations/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getWorkspaceInvitation(invitationId: string): Promise<IWorkspaceMemberInvitation> {
|
||||
return this.get(`/api/users/me/invitations/${invitationId}/`, { headers: {} })
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async deleteWorkspaceInvitations(workspaceSlug: string, invitationId: string): Promise<any> {
|
||||
return this.delete(`/api/workspaces/${workspaceSlug}/invitations/${invitationId}/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async workspaceSlugCheck(slug: string): Promise<any> {
|
||||
return this.get(`/api/workspace-slug-check/?slug=${slug}`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async searchWorkspace(
|
||||
workspaceSlug: string,
|
||||
params: {
|
||||
project_id?: string;
|
||||
search: string;
|
||||
workspace_search: boolean;
|
||||
}
|
||||
): Promise<IWorkspaceSearchResults> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/search/`, {
|
||||
params,
|
||||
})
|
||||
.then((res) => res?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
async getProductUpdates(): Promise<IProductUpdateResponse[]> {
|
||||
return this.get("/api/release-notes/")
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new WorkspaceService();
|
||||
@@ -4,9 +4,8 @@
|
||||
"extends": "./base.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "react",
|
||||
"lib": ["ES2015", "DOM"],
|
||||
"lib": ["ES2015"],
|
||||
"module": "ESNext",
|
||||
"target": "es6",
|
||||
"sourceMap": true
|
||||
"target": "es6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import * as React from "react";
|
||||
|
||||
export const Button = () => {
|
||||
return <button>button</button>;
|
||||
};
|
||||
Vendored
-41
@@ -1,41 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { FC } from 'react';
|
||||
|
||||
declare const Button: () => JSX.Element;
|
||||
|
||||
interface InputProps {
|
||||
type: string;
|
||||
id: string;
|
||||
value: string;
|
||||
name: string;
|
||||
onChange: () => void;
|
||||
className?: string;
|
||||
mode?: "primary" | "transparent" | "true-transparent";
|
||||
size?: "sm" | "md" | "lg";
|
||||
hasError?: boolean;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
declare const Input: React.ForwardRefExoticComponent<InputProps & React.RefAttributes<HTMLInputElement>>;
|
||||
|
||||
interface TextAreaProps {
|
||||
id: string;
|
||||
name: string;
|
||||
placeholder?: string;
|
||||
value?: string;
|
||||
rows?: number;
|
||||
cols?: number;
|
||||
disabled?: boolean;
|
||||
onChange: () => void;
|
||||
mode?: "primary" | "transparent";
|
||||
hasError?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
declare const TextArea: React.ForwardRefExoticComponent<TextAreaProps & React.RefAttributes<HTMLTextAreaElement>>;
|
||||
|
||||
interface IRadialProgressBar {
|
||||
progress: number;
|
||||
}
|
||||
declare const RadialProgressBar: FC<IRadialProgressBar>;
|
||||
|
||||
export { Button, Input, InputProps, RadialProgressBar, TextArea, TextAreaProps };
|
||||
Vendored
-157
@@ -1,157 +0,0 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
|
||||
// src/index.tsx
|
||||
var src_exports = {};
|
||||
__export(src_exports, {
|
||||
Button: () => Button,
|
||||
Input: () => Input,
|
||||
RadialProgressBar: () => RadialProgressBar,
|
||||
TextArea: () => TextArea
|
||||
});
|
||||
module.exports = __toCommonJS(src_exports);
|
||||
|
||||
// src/buttons/index.tsx
|
||||
var React = __toESM(require("react"));
|
||||
var Button = () => {
|
||||
return /* @__PURE__ */ React.createElement("button", null, "button");
|
||||
};
|
||||
|
||||
// src/form-fields/input.tsx
|
||||
var React2 = __toESM(require("react"));
|
||||
var Input = React2.forwardRef((props, ref) => {
|
||||
const {
|
||||
id,
|
||||
type,
|
||||
value,
|
||||
name,
|
||||
onChange,
|
||||
className = "",
|
||||
mode = "primary",
|
||||
size = "md",
|
||||
hasError = false,
|
||||
placeholder = "",
|
||||
disabled = false
|
||||
} = props;
|
||||
return /* @__PURE__ */ React2.createElement("input", {
|
||||
id,
|
||||
ref,
|
||||
type,
|
||||
value,
|
||||
name,
|
||||
onChange,
|
||||
placeholder,
|
||||
disabled,
|
||||
className: `block rounded-md bg-transparent text-sm focus:outline-none placeholder-custom-text-400 ${mode === "primary" ? "rounded-md border border-custom-border-200" : mode === "transparent" ? "rounded border-none bg-transparent ring-0 transition-all focus:ring-1 focus:ring-custom-primary" : mode === "true-transparent" ? "rounded border-none bg-transparent ring-0" : ""} ${hasError ? "border-red-500" : ""} ${hasError && mode === "primary" ? "bg-red-500/20" : ""} ${size === "sm" ? "px-3 py-2" : size === "lg" ? "p-3" : ""} ${className}`
|
||||
});
|
||||
});
|
||||
Input.displayName = "form-input-field";
|
||||
|
||||
// src/form-fields/textarea.tsx
|
||||
var React3 = __toESM(require("react"));
|
||||
var useAutoSizeTextArea = (textAreaRef, value) => {
|
||||
React3.useEffect(() => {
|
||||
if (textAreaRef) {
|
||||
textAreaRef.style.height = "0px";
|
||||
const scrollHeight = textAreaRef.scrollHeight;
|
||||
textAreaRef.style.height = scrollHeight + "px";
|
||||
}
|
||||
}, [textAreaRef, value]);
|
||||
};
|
||||
var TextArea = React3.forwardRef(
|
||||
(props, ref) => {
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
placeholder = "",
|
||||
value = "",
|
||||
rows = 1,
|
||||
cols = 1,
|
||||
disabled,
|
||||
onChange,
|
||||
mode = "primary",
|
||||
hasError = false,
|
||||
className = ""
|
||||
} = props;
|
||||
const textAreaRef = React3.useRef(ref);
|
||||
ref && useAutoSizeTextArea(textAreaRef == null ? void 0 : textAreaRef.current, value);
|
||||
return /* @__PURE__ */ React3.createElement("textarea", {
|
||||
id,
|
||||
name,
|
||||
ref: textAreaRef,
|
||||
placeholder,
|
||||
value,
|
||||
rows,
|
||||
cols,
|
||||
disabled,
|
||||
onChange,
|
||||
className: `no-scrollbar w-full bg-transparent placeholder-custom-text-400 px-3 py-2 outline-none ${mode === "primary" ? "rounded-md border border-custom-border-200" : mode === "transparent" ? "rounded border-none bg-transparent ring-0 transition-all focus:ring-1 focus:ring-theme" : ""} ${hasError ? "border-red-500" : ""} ${hasError && mode === "primary" ? "bg-red-100" : ""} ${className}`
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// src/progress/radial-progress.tsx
|
||||
var import_react = __toESM(require("react"));
|
||||
var RadialProgressBar = (props) => {
|
||||
const { progress } = props;
|
||||
const [circumference, setCircumference] = (0, import_react.useState)(0);
|
||||
(0, import_react.useEffect)(() => {
|
||||
const radius = 40;
|
||||
const circumference2 = 2 * Math.PI * radius;
|
||||
setCircumference(circumference2);
|
||||
}, []);
|
||||
const progressOffset = (100 - progress) / 100 * circumference;
|
||||
return /* @__PURE__ */ import_react.default.createElement("div", {
|
||||
className: "relative h-4 w-4"
|
||||
}, /* @__PURE__ */ import_react.default.createElement("svg", {
|
||||
className: "absolute top-0 left-0",
|
||||
viewBox: "0 0 100 100"
|
||||
}, /* @__PURE__ */ import_react.default.createElement("circle", {
|
||||
className: "stroke-current opacity-10",
|
||||
cx: "50",
|
||||
cy: "50",
|
||||
r: "40",
|
||||
strokeWidth: "12",
|
||||
fill: "none",
|
||||
strokeDasharray: `${circumference} ${circumference}`
|
||||
}), /* @__PURE__ */ import_react.default.createElement("circle", {
|
||||
className: `stroke-current`,
|
||||
cx: "50",
|
||||
cy: "50",
|
||||
r: "40",
|
||||
strokeWidth: "12",
|
||||
fill: "none",
|
||||
strokeDasharray: `${circumference} ${circumference}`,
|
||||
strokeDashoffset: progressOffset,
|
||||
transform: "rotate(-90 50 50)"
|
||||
})));
|
||||
};
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
Button,
|
||||
Input,
|
||||
RadialProgressBar,
|
||||
TextArea
|
||||
});
|
||||
Vendored
-121
@@ -1,121 +0,0 @@
|
||||
// src/buttons/index.tsx
|
||||
import * as React from "react";
|
||||
var Button = () => {
|
||||
return /* @__PURE__ */ React.createElement("button", null, "button");
|
||||
};
|
||||
|
||||
// src/form-fields/input.tsx
|
||||
import * as React2 from "react";
|
||||
var Input = React2.forwardRef((props, ref) => {
|
||||
const {
|
||||
id,
|
||||
type,
|
||||
value,
|
||||
name,
|
||||
onChange,
|
||||
className = "",
|
||||
mode = "primary",
|
||||
size = "md",
|
||||
hasError = false,
|
||||
placeholder = "",
|
||||
disabled = false
|
||||
} = props;
|
||||
return /* @__PURE__ */ React2.createElement("input", {
|
||||
id,
|
||||
ref,
|
||||
type,
|
||||
value,
|
||||
name,
|
||||
onChange,
|
||||
placeholder,
|
||||
disabled,
|
||||
className: `block rounded-md bg-transparent text-sm focus:outline-none placeholder-custom-text-400 ${mode === "primary" ? "rounded-md border border-custom-border-200" : mode === "transparent" ? "rounded border-none bg-transparent ring-0 transition-all focus:ring-1 focus:ring-custom-primary" : mode === "true-transparent" ? "rounded border-none bg-transparent ring-0" : ""} ${hasError ? "border-red-500" : ""} ${hasError && mode === "primary" ? "bg-red-500/20" : ""} ${size === "sm" ? "px-3 py-2" : size === "lg" ? "p-3" : ""} ${className}`
|
||||
});
|
||||
});
|
||||
Input.displayName = "form-input-field";
|
||||
|
||||
// src/form-fields/textarea.tsx
|
||||
import * as React3 from "react";
|
||||
var useAutoSizeTextArea = (textAreaRef, value) => {
|
||||
React3.useEffect(() => {
|
||||
if (textAreaRef) {
|
||||
textAreaRef.style.height = "0px";
|
||||
const scrollHeight = textAreaRef.scrollHeight;
|
||||
textAreaRef.style.height = scrollHeight + "px";
|
||||
}
|
||||
}, [textAreaRef, value]);
|
||||
};
|
||||
var TextArea = React3.forwardRef(
|
||||
(props, ref) => {
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
placeholder = "",
|
||||
value = "",
|
||||
rows = 1,
|
||||
cols = 1,
|
||||
disabled,
|
||||
onChange,
|
||||
mode = "primary",
|
||||
hasError = false,
|
||||
className = ""
|
||||
} = props;
|
||||
const textAreaRef = React3.useRef(ref);
|
||||
ref && useAutoSizeTextArea(textAreaRef == null ? void 0 : textAreaRef.current, value);
|
||||
return /* @__PURE__ */ React3.createElement("textarea", {
|
||||
id,
|
||||
name,
|
||||
ref: textAreaRef,
|
||||
placeholder,
|
||||
value,
|
||||
rows,
|
||||
cols,
|
||||
disabled,
|
||||
onChange,
|
||||
className: `no-scrollbar w-full bg-transparent placeholder-custom-text-400 px-3 py-2 outline-none ${mode === "primary" ? "rounded-md border border-custom-border-200" : mode === "transparent" ? "rounded border-none bg-transparent ring-0 transition-all focus:ring-1 focus:ring-theme" : ""} ${hasError ? "border-red-500" : ""} ${hasError && mode === "primary" ? "bg-red-100" : ""} ${className}`
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// src/progress/radial-progress.tsx
|
||||
import React4, { useState, useEffect as useEffect2 } from "react";
|
||||
var RadialProgressBar = (props) => {
|
||||
const { progress } = props;
|
||||
const [circumference, setCircumference] = useState(0);
|
||||
useEffect2(() => {
|
||||
const radius = 40;
|
||||
const circumference2 = 2 * Math.PI * radius;
|
||||
setCircumference(circumference2);
|
||||
}, []);
|
||||
const progressOffset = (100 - progress) / 100 * circumference;
|
||||
return /* @__PURE__ */ React4.createElement("div", {
|
||||
className: "relative h-4 w-4"
|
||||
}, /* @__PURE__ */ React4.createElement("svg", {
|
||||
className: "absolute top-0 left-0",
|
||||
viewBox: "0 0 100 100"
|
||||
}, /* @__PURE__ */ React4.createElement("circle", {
|
||||
className: "stroke-current opacity-10",
|
||||
cx: "50",
|
||||
cy: "50",
|
||||
r: "40",
|
||||
strokeWidth: "12",
|
||||
fill: "none",
|
||||
strokeDasharray: `${circumference} ${circumference}`
|
||||
}), /* @__PURE__ */ React4.createElement("circle", {
|
||||
className: `stroke-current`,
|
||||
cx: "50",
|
||||
cy: "50",
|
||||
r: "40",
|
||||
strokeWidth: "12",
|
||||
fill: "none",
|
||||
strokeDasharray: `${circumference} ${circumference}`,
|
||||
strokeDashoffset: progressOffset,
|
||||
transform: "rotate(-90 50 50)"
|
||||
})));
|
||||
};
|
||||
export {
|
||||
Button,
|
||||
Input,
|
||||
RadialProgressBar,
|
||||
TextArea
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
// import * as React from "react";
|
||||
// components
|
||||
// export * from "./breadcrumbs";
|
||||
// export * from "./button";
|
||||
// export * from "./custom-listbox";
|
||||
// export * from "./custom-menu";
|
||||
// export * from "./custom-select";
|
||||
// export * from "./empty-space";
|
||||
// export * from "./header-button";
|
||||
// export * from "./input";
|
||||
// export * from "./loader";
|
||||
// export * from "./outline-button";
|
||||
// export * from "./select";
|
||||
// export * from "./spinner";
|
||||
// export * from "./text-area";
|
||||
// export * from "./tooltip";
|
||||
export * from "./button";
|
||||
+10
-20
@@ -1,33 +1,23 @@
|
||||
{
|
||||
"name": "@plane/ui",
|
||||
"version": "0.0.1",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"sideEffects": false,
|
||||
"name": "ui",
|
||||
"version": "0.0.0",
|
||||
"main": "./index.tsx",
|
||||
"types": "./index.tsx",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"dist/**"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup src/index.tsx --format esm,cjs --dts --external react",
|
||||
"dev": "tsup src/index.tsx --format esm,cjs --watch --dts --external react",
|
||||
"lint": "eslint src/",
|
||||
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
|
||||
"lint": "eslint *.ts*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.5.2",
|
||||
"@types/react": "18.2.0",
|
||||
"@types/react-dom": "18.2.0",
|
||||
"@types/react": "^18.0.17",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@typescript-eslint/eslint-plugin": "^5.51.0",
|
||||
"classnames": "^2.3.2",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-custom": "*",
|
||||
"next": "12.3.2",
|
||||
"react": "^18.2.0",
|
||||
"tsconfig": "*",
|
||||
"tailwind-config-custom": "*",
|
||||
"tsup": "^5.10.1",
|
||||
"typescript": "4.7.4"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from "./input";
|
||||
export * from "./textarea";
|
||||
@@ -1,61 +0,0 @@
|
||||
import * as React from "react";
|
||||
|
||||
export interface InputProps {
|
||||
type: string;
|
||||
id: string;
|
||||
value: string;
|
||||
name: string;
|
||||
onChange: () => void;
|
||||
className?: string;
|
||||
mode?: "primary" | "transparent" | "true-transparent";
|
||||
size?: "sm" | "md" | "lg";
|
||||
hasError?: boolean;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {
|
||||
const {
|
||||
id,
|
||||
type,
|
||||
value,
|
||||
name,
|
||||
onChange,
|
||||
className = "",
|
||||
mode = "primary",
|
||||
size = "md",
|
||||
hasError = false,
|
||||
placeholder = "",
|
||||
disabled = false,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<input
|
||||
id={id}
|
||||
ref={ref}
|
||||
type={type}
|
||||
value={value}
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
disabled={disabled}
|
||||
className={`block rounded-md bg-transparent text-sm focus:outline-none placeholder-custom-text-400 ${
|
||||
mode === "primary"
|
||||
? "rounded-md border border-custom-border-200"
|
||||
: mode === "transparent"
|
||||
? "rounded border-none bg-transparent ring-0 transition-all focus:ring-1 focus:ring-custom-primary"
|
||||
: mode === "true-transparent"
|
||||
? "rounded border-none bg-transparent ring-0"
|
||||
: ""
|
||||
} ${hasError ? "border-red-500" : ""} ${
|
||||
hasError && mode === "primary" ? "bg-red-500/20" : ""
|
||||
} ${
|
||||
size === "sm" ? "px-3 py-2" : size === "lg" ? "p-3" : ""
|
||||
} ${className}`}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
Input.displayName = "form-input-field";
|
||||
|
||||
export { Input };
|
||||
@@ -1,80 +0,0 @@
|
||||
import * as React from "react";
|
||||
|
||||
export interface TextAreaProps {
|
||||
id: string;
|
||||
name: string;
|
||||
placeholder?: string;
|
||||
value?: string;
|
||||
rows?: number;
|
||||
cols?: number;
|
||||
disabled?: boolean;
|
||||
onChange: () => void;
|
||||
mode?: "primary" | "transparent";
|
||||
hasError?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
// Updates the height of a <textarea> when the value changes.
|
||||
const useAutoSizeTextArea = (
|
||||
textAreaRef: HTMLTextAreaElement | null,
|
||||
value: any
|
||||
) => {
|
||||
React.useEffect(() => {
|
||||
if (textAreaRef) {
|
||||
// We need to reset the height momentarily to get the correct scrollHeight for the textarea
|
||||
textAreaRef.style.height = "0px";
|
||||
const scrollHeight = textAreaRef.scrollHeight;
|
||||
|
||||
// We then set the height directly, outside of the render loop
|
||||
// Trying to set this with state or a ref will product an incorrect value.
|
||||
textAreaRef.style.height = scrollHeight + "px";
|
||||
}
|
||||
}, [textAreaRef, value]);
|
||||
};
|
||||
|
||||
const TextArea = React.forwardRef<HTMLTextAreaElement, TextAreaProps>(
|
||||
(props, ref) => {
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
placeholder = "",
|
||||
value = "",
|
||||
rows = 1,
|
||||
cols = 1,
|
||||
disabled,
|
||||
onChange,
|
||||
mode = "primary",
|
||||
hasError = false,
|
||||
className = "",
|
||||
} = props;
|
||||
|
||||
const textAreaRef = React.useRef<any>(ref);
|
||||
|
||||
ref && useAutoSizeTextArea(textAreaRef?.current, value);
|
||||
|
||||
return (
|
||||
<textarea
|
||||
id={id}
|
||||
name={name}
|
||||
ref={textAreaRef}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
rows={rows}
|
||||
cols={cols}
|
||||
disabled={disabled}
|
||||
onChange={onChange}
|
||||
className={`no-scrollbar w-full bg-transparent placeholder-custom-text-400 px-3 py-2 outline-none ${
|
||||
mode === "primary"
|
||||
? "rounded-md border border-custom-border-200"
|
||||
: mode === "transparent"
|
||||
? "rounded border-none bg-transparent ring-0 transition-all focus:ring-1 focus:ring-theme"
|
||||
: ""
|
||||
} ${hasError ? "border-red-500" : ""} ${
|
||||
hasError && mode === "primary" ? "bg-red-100" : ""
|
||||
} ${className}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export { TextArea };
|
||||
@@ -1,3 +0,0 @@
|
||||
export * from "./buttons";
|
||||
export * from "./form-fields";
|
||||
export * from "./progress";
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./radial-progress";
|
||||
@@ -1,45 +0,0 @@
|
||||
import React, { useState, useEffect, FC } from "react";
|
||||
|
||||
interface IRadialProgressBar {
|
||||
progress: number;
|
||||
}
|
||||
|
||||
export const RadialProgressBar: FC<IRadialProgressBar> = (props) => {
|
||||
const { progress } = props;
|
||||
const [circumference, setCircumference] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const radius = 40;
|
||||
const circumference = 2 * Math.PI * radius;
|
||||
setCircumference(circumference);
|
||||
}, []);
|
||||
|
||||
const progressOffset = ((100 - progress) / 100) * circumference;
|
||||
|
||||
return (
|
||||
<div className="relative h-4 w-4">
|
||||
<svg className="absolute top-0 left-0" viewBox="0 0 100 100">
|
||||
<circle
|
||||
className={"stroke-current opacity-10"}
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="40"
|
||||
strokeWidth="12"
|
||||
fill="none"
|
||||
strokeDasharray={`${circumference} ${circumference}`}
|
||||
/>
|
||||
<circle
|
||||
className={`stroke-current`}
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="40"
|
||||
strokeWidth="12"
|
||||
fill="none"
|
||||
strokeDasharray={`${circumference} ${circumference}`}
|
||||
strokeDashoffset={progressOffset}
|
||||
transform="rotate(-90 50 50)"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,8 +1,5 @@
|
||||
{
|
||||
"extends": "tsconfig/react-library.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "react"
|
||||
},
|
||||
"include": ["."],
|
||||
"exclude": ["dist", "build", "node_modules"]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
FROM=$1
|
||||
TO=$2
|
||||
DIRECTORY=$3
|
||||
|
||||
if [ "${FROM}" = "${TO}" ]; then
|
||||
echo "Nothing to replace, the value is already set to ${TO}."
|
||||
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Only perform action if $FROM and $TO are different.
|
||||
echo "Replacing all statically built instances of $FROM with this string $TO ."
|
||||
|
||||
grep -R -la "${FROM}" $DIRECTORY/.next | xargs -I{} sed -i "s|$FROM|$TO|g" "{}"
|
||||
@@ -5,9 +5,25 @@ cp ./.env.example ./.env
|
||||
export LC_ALL=C
|
||||
export LC_CTYPE=C
|
||||
|
||||
cp ./web/.env.example ./web/.env
|
||||
cp ./space/.env.example ./space/.env
|
||||
cp ./apiserver/.env.example ./apiserver/.env
|
||||
|
||||
# Generate the NEXT_PUBLIC_API_BASE_URL with given IP
|
||||
echo -e "\nNEXT_PUBLIC_API_BASE_URL=$1" >> ./.env
|
||||
|
||||
# Generate the SECRET_KEY that will be used by django
|
||||
echo -e "SECRET_KEY=\"$(tr -dc 'a-z0-9' < /dev/urandom | head -c50)\"" >> ./apiserver/.env
|
||||
echo -e "SECRET_KEY=\"$(tr -dc 'a-z0-9' < /dev/urandom | head -c50)\"" >> ./.env
|
||||
|
||||
# WEB_URL for email redirection and image saving
|
||||
echo -e "WEB_URL=$1" >> ./.env
|
||||
|
||||
# Generate Prompt for taking tiptap auth key
|
||||
echo -e "\n\e[1;38m Instructions for generating TipTap Pro Extensions Auth Token \e[0m \n"
|
||||
|
||||
echo -e "\e[1;38m 1. Head over to TipTap cloud's Pro Extensions Page, https://collab.tiptap.dev/pro-extensions \e[0m"
|
||||
echo -e "\e[1;38m 2. Copy the token given to you under the first paragraph, after 'Here it is' \e[0m \n"
|
||||
|
||||
read -p $'\e[1;32m Please Enter Your TipTap Pro Extensions Authentication Token: \e[0m \e[1;36m' authToken
|
||||
|
||||
|
||||
echo "@tiptap-pro:registry=https://registry.tiptap.dev/
|
||||
//registry.tiptap.dev/:_authToken=${authToken}" > .npmrc
|
||||
|
||||
|
||||
+5
-1
@@ -1,4 +1,8 @@
|
||||
# Base url for the API requests
|
||||
NEXT_PUBLIC_API_BASE_URL=""
|
||||
# Public boards deploy URL
|
||||
NEXT_PUBLIC_DEPLOY_URL=""
|
||||
# Google Client ID for Google OAuth
|
||||
NEXT_PUBLIC_GOOGLE_CLIENTID=""
|
||||
# Flag to toggle OAuth
|
||||
NEXT_PUBLIC_ENABLE_OAUTH=0
|
||||
NEXT_PUBLIC_ENABLE_OAUTH=1
|
||||
+11
-7
@@ -1,6 +1,7 @@
|
||||
FROM node:18-alpine AS builder
|
||||
RUN apk add --no-cache libc6-compat
|
||||
WORKDIR /app
|
||||
ENV NEXT_PUBLIC_API_BASE_URL=http://NEXT_PUBLIC_API_BASE_URL_PLACEHOLDER
|
||||
|
||||
RUN yarn global add turbo
|
||||
COPY . .
|
||||
@@ -19,16 +20,19 @@ RUN yarn install --network-timeout 500000
|
||||
|
||||
COPY --from=builder /app/out/full/ .
|
||||
COPY turbo.json turbo.json
|
||||
COPY replace-env-vars.sh /usr/local/bin/
|
||||
USER root
|
||||
RUN chmod +x /usr/local/bin/replace-env-vars.sh
|
||||
|
||||
ARG NEXT_PUBLIC_API_BASE_URL=""
|
||||
ARG NEXT_PUBLIC_API_BASE_URL=http://localhost:8000
|
||||
ARG NEXT_PUBLIC_DEPLOY_WITH_NGINX=1
|
||||
|
||||
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
|
||||
ENV NEXT_PUBLIC_DEPLOY_WITH_NGINX=$NEXT_PUBLIC_DEPLOY_WITH_NGINX
|
||||
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL BUILT_NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL NEXT_PUBLIC_DEPLOY_WITH_NGINX=$NEXT_PUBLIC_DEPLOY_WITH_NGINX
|
||||
|
||||
RUN yarn turbo run build --filter=space
|
||||
|
||||
RUN /usr/local/bin/replace-env-vars.sh http://NEXT_PUBLIC_WEBAPP_URL_PLACEHOLDER ${NEXT_PUBLIC_API_BASE_URL} space
|
||||
|
||||
FROM node:18-alpine AS runner
|
||||
WORKDIR /app
|
||||
|
||||
@@ -44,14 +48,14 @@ COPY --from=installer --chown=captain:plane /app/space/.next/standalone ./
|
||||
COPY --from=installer --chown=captain:plane /app/space/.next ./space/.next
|
||||
COPY --from=installer --chown=captain:plane /app/space/public ./space/public
|
||||
|
||||
ARG NEXT_PUBLIC_API_BASE_URL=""
|
||||
ARG NEXT_PUBLIC_API_BASE_URL=http://localhost:8000
|
||||
ARG NEXT_PUBLIC_DEPLOY_WITH_NGINX=1
|
||||
|
||||
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
|
||||
ENV NEXT_PUBLIC_DEPLOY_WITH_NGINX=$NEXT_PUBLIC_DEPLOY_WITH_NGINX
|
||||
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL BUILT_NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL NEXT_PUBLIC_DEPLOY_WITH_NGINX=$NEXT_PUBLIC_DEPLOY_WITH_NGINX
|
||||
|
||||
USER root
|
||||
COPY replace-env-vars.sh /usr/local/bin/
|
||||
COPY start.sh /usr/local/bin/
|
||||
RUN chmod +x /usr/local/bin/replace-env-vars.sh
|
||||
RUN chmod +x /usr/local/bin/start.sh
|
||||
|
||||
USER captain
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect } from "react";
|
||||
import React from "react";
|
||||
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
@@ -13,7 +13,7 @@ import useToast from "hooks/use-toast";
|
||||
// components
|
||||
import { EmailPasswordForm, GithubLoginButton, GoogleLoginButton, EmailCodeForm } from "components/accounts";
|
||||
// images
|
||||
const imagePrefix = Boolean(parseInt(process.env.NEXT_PUBLIC_DEPLOY_WITH_NGINX || "0")) ? "/spaces" : "";
|
||||
const imagePrefix = process.env.NEXT_PUBLIC_DEPLOY_WITH_NGINX ? "/spaces/" : "";
|
||||
|
||||
export const SignInView = observer(() => {
|
||||
const { user: userStore } = useMobxStore();
|
||||
|
||||
@@ -18,6 +18,7 @@ import Gapcursor from "@tiptap/extension-gapcursor";
|
||||
import ts from "highlight.js/lib/languages/typescript";
|
||||
|
||||
import "highlight.js/styles/github-dark.css";
|
||||
import UniqueID from "@tiptap-pro/extension-unique-id";
|
||||
import UpdatedImage from "./updated-image";
|
||||
import isValidHttpUrl from "../bubble-menu/utils/link-validator";
|
||||
import { CustomTableCell } from "./table/table-cell";
|
||||
@@ -120,6 +121,9 @@ export const TiptapExtensions = (
|
||||
},
|
||||
includeChildren: true,
|
||||
}),
|
||||
UniqueID.configure({
|
||||
types: ["image"],
|
||||
}),
|
||||
SlashCommand(workspaceSlug, setIsSubmitting),
|
||||
TiptapUnderline,
|
||||
TextStyle,
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL ? process.env.NEXT_PUBLIC_API_BASE_URL : "";
|
||||
@@ -17,6 +17,7 @@
|
||||
"@heroicons/react": "^2.0.12",
|
||||
"@mui/icons-material": "^5.14.1",
|
||||
"@mui/material": "^5.14.1",
|
||||
"@tiptap-pro/extension-unique-id": "^2.1.0",
|
||||
"@tiptap/extension-code-block-lowlight": "^2.0.4",
|
||||
"@tiptap/extension-color": "^2.0.4",
|
||||
"@tiptap/extension-gapcursor": "^2.1.7",
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// components
|
||||
import { OnBoardingForm } from "components/accounts/onboarding-form";
|
||||
|
||||
const imagePrefix = Boolean(parseInt(process.env.NEXT_PUBLIC_DEPLOY_WITH_NGINX || "0")) ? "/spaces" : "";
|
||||
const imagePrefix = process.env.NEXT_PUBLIC_DEPLOY_WITH_NGINX ? "/spaces/" : "";
|
||||
|
||||
const OnBoardingPage = () => {
|
||||
const { user: userStore } = useMobxStore();
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
// services
|
||||
import APIService from "services/api.service";
|
||||
import { API_BASE_URL } from "helpers/common.helper";
|
||||
|
||||
class AuthService extends APIService {
|
||||
constructor() {
|
||||
super(API_BASE_URL);
|
||||
super(process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async emailLogin(data: any) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// services
|
||||
import APIService from "services/api.service";
|
||||
import { API_BASE_URL } from "helpers/common.helper";
|
||||
|
||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||
|
||||
interface UnSplashImage {
|
||||
id: string;
|
||||
@@ -27,7 +29,7 @@ interface UnSplashImageUrls {
|
||||
|
||||
class FileServices extends APIService {
|
||||
constructor() {
|
||||
super(API_BASE_URL);
|
||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async uploadFile(workspaceSlug: string, file: FormData): Promise<any> {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
// services
|
||||
import APIService from "services/api.service";
|
||||
import { API_BASE_URL } from "helpers/common.helper";
|
||||
|
||||
class IssueService extends APIService {
|
||||
constructor() {
|
||||
super(API_BASE_URL);
|
||||
super(process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async getPublicIssues(workspace_slug: string, project_slug: string, params: any): Promise<any> {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
// services
|
||||
import APIService from "services/api.service";
|
||||
import { API_BASE_URL } from "helpers/common.helper";
|
||||
|
||||
class ProjectService extends APIService {
|
||||
constructor() {
|
||||
super(API_BASE_URL);
|
||||
super(process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async getProjectSettings(workspace_slug: string, project_slug: string): Promise<any> {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
// services
|
||||
import APIService from "services/api.service";
|
||||
import { API_BASE_URL } from "helpers/common.helper";
|
||||
|
||||
class UserService extends APIService {
|
||||
constructor() {
|
||||
super(API_BASE_URL);
|
||||
super(process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async currentUser(): Promise<any> {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#!/bin/sh
|
||||
set -x
|
||||
|
||||
# Replace the statically built BUILT_NEXT_PUBLIC_API_BASE_URL with run-time NEXT_PUBLIC_API_BASE_URL
|
||||
# NOTE: if these values are the same, this will be skipped.
|
||||
/usr/local/bin/replace-env-vars.sh "$BUILT_NEXT_PUBLIC_API_BASE_URL" "$NEXT_PUBLIC_API_BASE_URL" $2
|
||||
|
||||
echo "Starting Plane Frontend.."
|
||||
node $1
|
||||
|
||||
+7
-10
@@ -15,20 +15,17 @@
|
||||
"NEXT_PUBLIC_UNSPLASH_ACCESS",
|
||||
"NEXT_PUBLIC_UNSPLASH_ENABLED",
|
||||
"NEXT_PUBLIC_TRACK_EVENTS",
|
||||
"NEXT_PUBLIC_PLAUSIBLE_DOMAIN",
|
||||
"TRACKER_ACCESS_KEY",
|
||||
"NEXT_PUBLIC_CRISP_ID",
|
||||
"NEXT_PUBLIC_ENABLE_SESSION_RECORDER",
|
||||
"NEXT_PUBLIC_SESSION_RECORDER_KEY",
|
||||
"NEXT_PUBLIC_EXTRA_IMAGE_DOMAINS",
|
||||
"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"
|
||||
"NEXT_PUBLIC_SLACK_CLIENT_ID",
|
||||
"NEXT_PUBLIC_SLACK_CLIENT_SECRET",
|
||||
"NEXT_PUBLIC_SUPABASE_URL",
|
||||
"NEXT_PUBLIC_SUPABASE_ANON_KEY",
|
||||
"NEXT_PUBLIC_PLAUSIBLE_DOMAIN",
|
||||
"NEXT_PUBLIC_DEPLOY_WITH_NGINX"
|
||||
],
|
||||
"pipeline": {
|
||||
"build": {
|
||||
|
||||
+3
-1
@@ -1,3 +1,5 @@
|
||||
# Base url for the API requests
|
||||
NEXT_PUBLIC_API_BASE_URL=""
|
||||
# Extra image domains that need to be added for Next Image
|
||||
NEXT_PUBLIC_EXTRA_IMAGE_DOMAINS=
|
||||
# Google Client ID for Google OAuth
|
||||
@@ -21,4 +23,4 @@ NEXT_PUBLIC_SLACK_CLIENT_ID=""
|
||||
# For Telemetry, set it to "app.plane.so"
|
||||
NEXT_PUBLIC_PLAUSIBLE_DOMAIN=""
|
||||
# Public boards deploy URL
|
||||
NEXT_PUBLIC_DEPLOY_URL="http://localhost:3000/spaces"
|
||||
NEXT_PUBLIC_DEPLOY_URL=""
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"printWidth": 120,
|
||||
"printWidth": 100,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
|
||||
+20
-8
@@ -2,6 +2,7 @@ FROM node:18-alpine AS builder
|
||||
RUN apk add --no-cache libc6-compat
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
ENV NEXT_PUBLIC_API_BASE_URL=http://NEXT_PUBLIC_API_BASE_URL_PLACEHOLDER
|
||||
|
||||
RUN yarn global add turbo
|
||||
COPY . .
|
||||
@@ -13,8 +14,8 @@ FROM node:18-alpine AS installer
|
||||
|
||||
RUN apk add --no-cache libc6-compat
|
||||
WORKDIR /app
|
||||
ARG NEXT_PUBLIC_API_BASE_URL=""
|
||||
ARG NEXT_PUBLIC_DEPLOY_URL=""
|
||||
ARG NEXT_PUBLIC_API_BASE_URL=http://localhost:8000
|
||||
ARG NEXT_PUBLIC_DEPLOY_URL=http://localhost/spaces
|
||||
|
||||
# First install the dependencies (as they change less often)
|
||||
COPY .gitignore .gitignore
|
||||
@@ -25,12 +26,18 @@ RUN yarn install --network-timeout 500000
|
||||
# Build the project
|
||||
COPY --from=builder /app/out/full/ .
|
||||
COPY turbo.json turbo.json
|
||||
COPY replace-env-vars.sh /usr/local/bin/
|
||||
USER root
|
||||
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
|
||||
ENV NEXT_PUBLIC_DEPLOY_URL=$NEXT_PUBLIC_DEPLOY_URL
|
||||
RUN chmod +x /usr/local/bin/replace-env-vars.sh
|
||||
|
||||
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL \
|
||||
BUILT_NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL \
|
||||
NEXT_PUBLIC_DEPLOY_URL=$NEXT_PUBLIC_DEPLOY_URL
|
||||
|
||||
RUN yarn turbo run build --filter=web
|
||||
|
||||
RUN /usr/local/bin/replace-env-vars.sh http://NEXT_PUBLIC_WEBAPP_URL_PLACEHOLDER ${NEXT_PUBLIC_API_BASE_URL} web
|
||||
|
||||
FROM node:18-alpine AS runner
|
||||
WORKDIR /app
|
||||
|
||||
@@ -45,15 +52,20 @@ COPY --from=installer /app/web/package.json .
|
||||
# Automatically leverage output traces to reduce image size
|
||||
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
||||
COPY --from=installer --chown=captain:plane /app/web/.next/standalone ./
|
||||
|
||||
COPY --from=installer --chown=captain:plane /app/web/.next ./web/.next
|
||||
|
||||
ARG NEXT_PUBLIC_API_BASE_URL=""
|
||||
ARG NEXT_PUBLIC_DEPLOY_URL=""
|
||||
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
|
||||
ENV NEXT_PUBLIC_DEPLOY_URL=$NEXT_PUBLIC_DEPLOY_URL
|
||||
ARG NEXT_PUBLIC_API_BASE_URL=http://localhost:8000
|
||||
ARG NEXT_PUBLIC_DEPLOY_URL=http://localhost/spaces
|
||||
|
||||
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL \
|
||||
BUILT_NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL \
|
||||
NEXT_PUBLIC_DEPLOY_URL=$NEXT_PUBLIC_DEPLOY_URL
|
||||
|
||||
USER root
|
||||
COPY replace-env-vars.sh /usr/local/bin/
|
||||
COPY start.sh /usr/local/bin/
|
||||
RUN chmod +x /usr/local/bin/replace-env-vars.sh
|
||||
RUN chmod +x /usr/local/bin/start.sh
|
||||
|
||||
USER captain
|
||||
|
||||
@@ -9,6 +9,7 @@ import { findStringWithMostCharacters } from "helpers/array.helper";
|
||||
import { generateBarColor } from "helpers/analytics.helper";
|
||||
// types
|
||||
import { IAnalyticsParams, IAnalyticsResponse } from "types";
|
||||
// constants
|
||||
|
||||
type Props = {
|
||||
analytics: IAnalyticsResponse;
|
||||
|
||||
@@ -7,14 +7,19 @@ import analyticsService from "services/analytics.service";
|
||||
import projectService from "services/project.service";
|
||||
import cyclesService from "services/cycles.service";
|
||||
import modulesService from "services/modules.service";
|
||||
import trackEventServices from "services/track_event.service";
|
||||
import trackEventServices from "services/track-event.service";
|
||||
// hooks
|
||||
import useProjects from "hooks/use-projects";
|
||||
import useToast from "hooks/use-toast";
|
||||
// ui
|
||||
import { PrimaryButton, SecondaryButton } from "components/ui";
|
||||
// icons
|
||||
import { ArrowDownTrayIcon, ArrowPathIcon, CalendarDaysIcon, UserGroupIcon } from "@heroicons/react/24/outline";
|
||||
import {
|
||||
ArrowDownTrayIcon,
|
||||
ArrowPathIcon,
|
||||
CalendarDaysIcon,
|
||||
UserGroupIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { ContrastIcon, LayerDiagonalIcon } from "components/icons";
|
||||
// helpers
|
||||
import { renderShortDate } from "helpers/date-time.helper";
|
||||
@@ -41,7 +46,13 @@ type Props = {
|
||||
user: ICurrentUserResponse | undefined;
|
||||
};
|
||||
|
||||
export const AnalyticsSidebar: React.FC<Props> = ({ analytics, params, fullScreen, isProjectLevel = false, user }) => {
|
||||
export const AnalyticsSidebar: React.FC<Props> = ({
|
||||
analytics,
|
||||
params,
|
||||
fullScreen,
|
||||
isProjectLevel = false,
|
||||
user,
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
|
||||
|
||||
@@ -50,7 +61,9 @@ export const AnalyticsSidebar: React.FC<Props> = ({ analytics, params, fullScree
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const { data: projectDetails } = useSWR(
|
||||
workspaceSlug && projectId && !(cycleId || moduleId) ? PROJECT_DETAILS(projectId.toString()) : null,
|
||||
workspaceSlug && projectId && !(cycleId || moduleId)
|
||||
? PROJECT_DETAILS(projectId.toString())
|
||||
: null,
|
||||
workspaceSlug && projectId && !(cycleId || moduleId)
|
||||
? () => projectService.getProject(workspaceSlug.toString(), projectId.toString())
|
||||
: null
|
||||
@@ -59,14 +72,24 @@ export const AnalyticsSidebar: React.FC<Props> = ({ analytics, params, fullScree
|
||||
const { data: cycleDetails } = useSWR(
|
||||
workspaceSlug && projectId && cycleId ? CYCLE_DETAILS(cycleId.toString()) : null,
|
||||
workspaceSlug && projectId && cycleId
|
||||
? () => cyclesService.getCycleDetails(workspaceSlug.toString(), projectId.toString(), cycleId.toString())
|
||||
? () =>
|
||||
cyclesService.getCycleDetails(
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
cycleId.toString()
|
||||
)
|
||||
: null
|
||||
);
|
||||
|
||||
const { data: moduleDetails } = useSWR(
|
||||
workspaceSlug && projectId && moduleId ? MODULE_DETAILS(moduleId.toString()) : null,
|
||||
workspaceSlug && projectId && moduleId
|
||||
? () => modulesService.getModuleDetails(workspaceSlug.toString(), projectId.toString(), moduleId.toString())
|
||||
? () =>
|
||||
modulesService.getModuleDetails(
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
moduleId.toString()
|
||||
)
|
||||
: null
|
||||
);
|
||||
|
||||
@@ -155,7 +178,8 @@ export const AnalyticsSidebar: React.FC<Props> = ({ analytics, params, fullScree
|
||||
);
|
||||
};
|
||||
|
||||
const selectedProjects = params.project && params.project.length > 0 ? params.project : projects?.map((p) => p.id);
|
||||
const selectedProjects =
|
||||
params.project && params.project.length > 0 ? params.project : projects?.map((p) => p.id);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -212,7 +236,9 @@ export const AnalyticsSidebar: React.FC<Props> = ({ analytics, params, fullScree
|
||||
)}
|
||||
<h5 className="flex items-center gap-1">
|
||||
<p className="break-words">{truncateText(project.name, 20)}</p>
|
||||
<span className="text-custom-text-200 text-xs ml-1">({project.identifier})</span>
|
||||
<span className="text-custom-text-200 text-xs ml-1">
|
||||
({project.identifier})
|
||||
</span>
|
||||
</h5>
|
||||
</div>
|
||||
<div className="mt-4 space-y-3 pl-2 w-full">
|
||||
@@ -318,7 +344,10 @@ export const AnalyticsSidebar: React.FC<Props> = ({ analytics, params, fullScree
|
||||
<div className="space-y-4 mt-4">
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<h6 className="text-custom-text-200">Network</h6>
|
||||
<span>{NETWORK_CHOICES.find((n) => n.key === projectDetails?.network)?.label ?? ""}</span>
|
||||
<span>
|
||||
{NETWORK_CHOICES.find((n) => n.key === projectDetails?.network)?.label ??
|
||||
""}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,11 +13,15 @@ import analyticsService from "services/analytics.service";
|
||||
import projectService from "services/project.service";
|
||||
import cyclesService from "services/cycles.service";
|
||||
import modulesService from "services/modules.service";
|
||||
import trackEventServices from "services/track_event.service";
|
||||
import trackEventServices from "services/track-event.service";
|
||||
// components
|
||||
import { CustomAnalytics, ScopeAndDemand } from "components/analytics";
|
||||
// icons
|
||||
import { ArrowsPointingInIcon, ArrowsPointingOutIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||
import {
|
||||
ArrowsPointingInIcon,
|
||||
ArrowsPointingOutIcon,
|
||||
XMarkIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
// types
|
||||
import { IAnalyticsParams, IWorkspace } from "types";
|
||||
// fetch-keys
|
||||
@@ -63,7 +67,9 @@ export const AnalyticsProjectModal: React.FC<Props> = ({ isOpen, onClose }) => {
|
||||
);
|
||||
|
||||
const { data: projectDetails } = useSWR(
|
||||
workspaceSlug && projectId && !(cycleId || moduleId) ? PROJECT_DETAILS(projectId.toString()) : null,
|
||||
workspaceSlug && projectId && !(cycleId || moduleId)
|
||||
? PROJECT_DETAILS(projectId.toString())
|
||||
: null,
|
||||
workspaceSlug && projectId && !(cycleId || moduleId)
|
||||
? () => projectService.getProject(workspaceSlug.toString(), projectId.toString())
|
||||
: null
|
||||
@@ -72,14 +78,24 @@ export const AnalyticsProjectModal: React.FC<Props> = ({ isOpen, onClose }) => {
|
||||
const { data: cycleDetails } = useSWR(
|
||||
workspaceSlug && projectId && cycleId ? CYCLE_DETAILS(cycleId.toString()) : null,
|
||||
workspaceSlug && projectId && cycleId
|
||||
? () => cyclesService.getCycleDetails(workspaceSlug.toString(), projectId.toString(), cycleId.toString())
|
||||
? () =>
|
||||
cyclesService.getCycleDetails(
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
cycleId.toString()
|
||||
)
|
||||
: null
|
||||
);
|
||||
|
||||
const { data: moduleDetails } = useSWR(
|
||||
workspaceSlug && projectId && moduleId ? MODULE_DETAILS(moduleId.toString()) : null,
|
||||
workspaceSlug && projectId && moduleId
|
||||
? () => modulesService.getModuleDetails(workspaceSlug.toString(), projectId.toString(), moduleId.toString())
|
||||
? () =>
|
||||
modulesService.getModuleDetails(
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
moduleId.toString()
|
||||
)
|
||||
: null
|
||||
);
|
||||
|
||||
@@ -118,7 +134,8 @@ export const AnalyticsProjectModal: React.FC<Props> = ({ isOpen, onClose }) => {
|
||||
eventPayload.moduleName = moduleDetails.name;
|
||||
}
|
||||
|
||||
const eventType = tab === "Scope and Demand" ? "SCOPE_AND_DEMAND_ANALYTICS" : "CUSTOM_ANALYTICS";
|
||||
const eventType =
|
||||
tab === "Scope and Demand" ? "SCOPE_AND_DEMAND_ANALYTICS" : "CUSTOM_ANALYTICS";
|
||||
|
||||
trackEventServices.trackAnalyticsEvent(
|
||||
eventPayload,
|
||||
@@ -133,9 +150,9 @@ export const AnalyticsProjectModal: React.FC<Props> = ({ isOpen, onClose }) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`absolute top-0 z-30 h-full bg-custom-background-90 ${fullScreen ? "p-2 w-full" : "w-1/2"} ${
|
||||
isOpen ? "right-0" : "-right-full"
|
||||
} duration-300 transition-all`}
|
||||
className={`absolute top-0 z-30 h-full bg-custom-background-90 ${
|
||||
fullScreen ? "p-2 w-full" : "w-1/2"
|
||||
} ${isOpen ? "right-0" : "-right-full"} duration-300 transition-all`}
|
||||
>
|
||||
<div
|
||||
className={`flex h-full flex-col overflow-hidden border-custom-border-200 bg-custom-background-100 text-left ${
|
||||
@@ -144,7 +161,8 @@ export const AnalyticsProjectModal: React.FC<Props> = ({ isOpen, onClose }) => {
|
||||
>
|
||||
<div className="flex items-center justify-between gap-4 bg-custom-background-100 px-5 py-4 text-sm">
|
||||
<h3 className="break-words">
|
||||
Analytics for {cycleId ? cycleDetails?.name : moduleId ? moduleDetails?.name : projectDetails?.name}
|
||||
Analytics for{" "}
|
||||
{cycleId ? cycleDetails?.name : moduleId ? moduleDetails?.name : projectDetails?.name}
|
||||
</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
|
||||
@@ -20,15 +20,18 @@ export const AnalyticsYearWiseIssues: React.FC<Props> = ({ defaultAnalytics }) =
|
||||
{
|
||||
id: "issues_closed",
|
||||
color: "rgb(var(--color-primary-100))",
|
||||
data: Object.entries(MONTHS_LIST).map(([index, month]) => ({
|
||||
x: month.shortTitle,
|
||||
data: MONTHS_LIST.map((month) => ({
|
||||
x: month.label.substring(0, 3),
|
||||
y:
|
||||
defaultAnalytics.issue_completed_month_wise.find((data) => data.month === parseInt(index, 10))?.count ||
|
||||
0,
|
||||
defaultAnalytics.issue_completed_month_wise.find(
|
||||
(data) => data.month === month.value
|
||||
)?.count || 0,
|
||||
})),
|
||||
},
|
||||
]}
|
||||
customYAxisTickValues={defaultAnalytics.issue_completed_month_wise.map((data) => data.count)}
|
||||
customYAxisTickValues={defaultAnalytics.issue_completed_month_wise.map(
|
||||
(data) => data.count
|
||||
)}
|
||||
height="300px"
|
||||
colors={(datum) => datum.color}
|
||||
curve="monotoneX"
|
||||
|
||||
@@ -3,8 +3,8 @@ import React, { useState } from "react";
|
||||
// component
|
||||
import { CustomSelect, ToggleSwitch } from "components/ui";
|
||||
import { SelectMonthModal } from "components/automation";
|
||||
// icon
|
||||
import { ArchiveRestore } from "lucide-react";
|
||||
// icons
|
||||
import { ChevronDownIcon } from "@heroicons/react/24/outline";
|
||||
// constants
|
||||
import { PROJECT_AUTOMATION_MONTHS } from "constants/project";
|
||||
// types
|
||||
@@ -28,18 +28,14 @@ export const AutoArchiveAutomation: React.FC<Props> = ({ projectDetails, handleC
|
||||
handleClose={() => setmonthModal(false)}
|
||||
handleChange={handleChange}
|
||||
/>
|
||||
<div className="flex flex-col gap-4 border-b border-custom-border-200 px-4 py-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex items-center justify-center p-3 rounded bg-custom-background-90">
|
||||
<ArchiveRestore className="h-4 w-4 text-custom-text-100 flex-shrink-0" />
|
||||
</div>
|
||||
<div className="">
|
||||
<h4 className="text-sm font-medium">Auto-archive closed issues</h4>
|
||||
<p className="text-sm text-custom-text-200 tracking-tight">
|
||||
Plane will auto archive issues that have been completed or canceled.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-7 px-6 py-5 rounded-[10px] border border-custom-border-300 bg-custom-background-90">
|
||||
<div className="flex items-center justify-between gap-x-8 gap-y-2">
|
||||
<div className="flex flex-col gap-2.5">
|
||||
<h4 className="text-lg font-semibold">Auto-archive closed issues</h4>
|
||||
<p className="text-sm text-custom-text-200">
|
||||
Plane will automatically archive issues that have been completed or cancelled for the
|
||||
configured time period.
|
||||
</p>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
value={projectDetails?.archive_in !== 0}
|
||||
@@ -51,43 +47,40 @@ export const AutoArchiveAutomation: React.FC<Props> = ({ projectDetails, handleC
|
||||
size="sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{projectDetails?.archive_in !== 0 && (
|
||||
<div className="ml-12">
|
||||
<div className="flex items-center justify-between rounded px-5 py-4 bg-custom-background-90 border border-custom-border-200 gap-2 w-full">
|
||||
<div className="w-1/2 text-sm font-medium">
|
||||
Auto-archive issues that are closed for
|
||||
</div>
|
||||
<div className="w-1/2">
|
||||
<CustomSelect
|
||||
value={projectDetails?.archive_in}
|
||||
label={`${projectDetails?.archive_in} ${
|
||||
projectDetails?.archive_in === 1 ? "Month" : "Months"
|
||||
}`}
|
||||
onChange={(val: number) => {
|
||||
handleChange({ archive_in: val });
|
||||
}}
|
||||
input
|
||||
verticalPosition="bottom"
|
||||
width="w-full"
|
||||
>
|
||||
<>
|
||||
{PROJECT_AUTOMATION_MONTHS.map((month) => (
|
||||
<CustomSelect.Option key={month.label} value={month.value}>
|
||||
<span className="text-sm">{month.label}</span>
|
||||
</CustomSelect.Option>
|
||||
))}
|
||||
<div className="flex items-center justify-between gap-2 w-full">
|
||||
<div className="w-1/2 text-base font-medium">
|
||||
Auto-archive issues that are closed for
|
||||
</div>
|
||||
<div className="w-1/2">
|
||||
<CustomSelect
|
||||
value={projectDetails?.archive_in}
|
||||
label={`${projectDetails?.archive_in} ${
|
||||
projectDetails?.archive_in === 1 ? "Month" : "Months"
|
||||
}`}
|
||||
onChange={(val: number) => {
|
||||
handleChange({ archive_in: val });
|
||||
}}
|
||||
input
|
||||
verticalPosition="top"
|
||||
width="w-full"
|
||||
>
|
||||
<>
|
||||
{PROJECT_AUTOMATION_MONTHS.map((month) => (
|
||||
<CustomSelect.Option key={month.label} value={month.value}>
|
||||
{month.label}
|
||||
</CustomSelect.Option>
|
||||
))}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full text-sm select-none items-center rounded px-1 py-1.5 text-custom-text-200 hover:bg-custom-background-80"
|
||||
onClick={() => setmonthModal(true)}
|
||||
>
|
||||
Customise Time Range
|
||||
</button>
|
||||
</>
|
||||
</CustomSelect>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full select-none items-center rounded px-1 py-1.5 text-custom-text-200 hover:bg-custom-background-80"
|
||||
onClick={() => setmonthModal(true)}
|
||||
>
|
||||
Customize Time Range
|
||||
</button>
|
||||
</>
|
||||
</CustomSelect>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user