Compare commits

..

11 Commits

Author SHA1 Message Date
pablohashescobar e2ecd4db0b dev: update server configuration commands 2024-02-07 11:57:40 +05:30
Nikhil 751b15a7a7 dev: update the response for conflicting errors (#3568) 2024-02-06 17:00:42 +05:30
Bavisetti Narayan ac22769220 chore: email trigger for new assignee (#3572) 2024-02-06 16:47:14 +05:30
Manish Gupta 46ae0f98dc app_release value handled (#3571) 2024-02-06 15:35:05 +05:30
Anmol Singh Bhatia 30aaec9097 chore: module date validation (#3565) 2024-02-05 20:46:01 +05:30
Anmol Singh Bhatia 4041c5bc5b chore: cycle and module sidebar improvement (#3562) 2024-02-05 19:13:21 +05:30
Bavisetti Narayan 403595a897 chore: removed email notification for new users (#3561) 2024-02-05 19:13:02 +05:30
Aaryan Khandelwal 0ee93dfd8c chore: added None filter option to the dashboard widgets (#3556)
* chore: added tab change animation

* chore: widgets filtering logic updated

* refactor: issues list widget

* fix: tab navigation transition

* fix: extra top spacing on opening the peek overview
2024-02-05 19:12:33 +05:30
Anmol Singh Bhatia ee0e3e2e25 chore: active cycle issue transfer validation (#3560)
* fix: completed cycle list layout validation

* fix: completed cycle kanban layout validation

* fix: completed cycle spreadsheet layout validation

* fix: date dropdown disabled fix

* chore: quick action validation added for list, kanban and spreadsheet layout

* fix: calendar layout validation added
2024-02-05 14:47:40 +05:30
Lakhan Baheti 0165abab3e chore: posthog events improved (#3554)
* chore: events naming convention changed

* chore: track element added for project related events

* chore: track element added for cycle related events

* chore: track element added for module related events

* chore: issue related events updated

* refactor: event tracker store

* refactor: event-tracker store

* fix: posthog changes

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2024-02-05 13:19:07 +05:30
Anmol Singh Bhatia 7d07afd59c chore: cycle and module sidebar analytics improvement (#3559)
* chore: cycle and module store update action updated

* chore: cycle and module issue store actions updated

* chore: cycle and module retrieve endpoints updated

* fix: app sidebar z index and priority icon fix

* chore: cycle and module sidebar and stats updated
2024-02-05 12:34:53 +05:30
194 changed files with 2117 additions and 4871 deletions
+1
View File
@@ -41,6 +41,7 @@ USER captain
# Add in Django deps and generate Django's static files
COPY manage.py manage.py
COPY server.py server.py
COPY plane plane/
COPY templates templates/
COPY package.json package.json
+1 -1
View File
@@ -28,4 +28,4 @@ python manage.py configure_instance
# Create the default bucket
python manage.py create_bucket
exec gunicorn -w $GUNICORN_WORKERS -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:${PORT:-8000} --max-requests 1200 --max-requests-jitter 1000 --access-logfile -
python server.py
+2 -2
View File
@@ -262,7 +262,7 @@ class CycleAPIEndpoint(WebhookMixin, BaseAPIView):
return Response(
{
"error": "Cycle with the same external id and external source already exists",
"cycle": str(cycle.id),
"id": str(cycle.id),
},
status=status.HTTP_409_CONFLICT,
)
@@ -325,7 +325,7 @@ class CycleAPIEndpoint(WebhookMixin, BaseAPIView):
return Response(
{
"error": "Cycle with the same external id and external source already exists",
"cycle_id": str(cycle.id),
"id": str(cycle.id),
},
status=status.HTTP_409_CONFLICT,
)
+56 -5
View File
@@ -239,7 +239,7 @@ class IssueAPIEndpoint(WebhookMixin, BaseAPIView):
return Response(
{
"error": "Issue with the same external id and external source already exists",
"issue_id": str(issue.id),
"id": str(issue.id),
},
status=status.HTTP_409_CONFLICT,
)
@@ -286,14 +286,16 @@ class IssueAPIEndpoint(WebhookMixin, BaseAPIView):
and Issue.objects.filter(
project_id=project_id,
workspace__slug=slug,
external_source=request.data.get("external_source", issue.external_source),
external_source=request.data.get(
"external_source", issue.external_source
),
external_id=request.data.get("external_id"),
).exists()
):
return Response(
{
"error": "Issue with the same external id and external source already exists",
"issue_id": str(issue.id),
"id": str(issue.id),
},
status=status.HTTP_409_CONFLICT,
)
@@ -362,6 +364,30 @@ class LabelAPIEndpoint(BaseAPIView):
try:
serializer = LabelSerializer(data=request.data)
if serializer.is_valid():
if (
request.data.get("external_id")
and request.data.get("external_source")
and Label.objects.filter(
project_id=project_id,
workspace__slug=slug,
external_source=request.data.get("external_source"),
external_id=request.data.get("external_id"),
).exists()
):
label = Label.objects.filter(
workspace__slug=slug,
project_id=project_id,
external_id=request.data.get("external_id"),
external_source=request.data.get("external_source"),
).first()
return Response(
{
"error": "Label with the same external id and external source already exists",
"id": str(label.id),
},
status=status.HTTP_409_CONFLICT,
)
serializer.save(project_id=project_id)
return Response(
serializer.data, status=status.HTTP_201_CREATED
@@ -370,11 +396,17 @@ class LabelAPIEndpoint(BaseAPIView):
serializer.errors, status=status.HTTP_400_BAD_REQUEST
)
except IntegrityError:
label = Label.objects.filter(
workspace__slug=slug,
project_id=project_id,
name=request.data.get("name"),
).first()
return Response(
{
"error": "Label with the same name already exists in the project"
"error": "Label with the same name already exists in the project",
"id": str(label.id),
},
status=status.HTTP_400_BAD_REQUEST,
status=status.HTTP_409_CONFLICT,
)
def get(self, request, slug, project_id, pk=None):
@@ -401,6 +433,25 @@ class LabelAPIEndpoint(BaseAPIView):
label = self.get_queryset().get(pk=pk)
serializer = LabelSerializer(label, data=request.data, partial=True)
if serializer.is_valid():
if (
str(request.data.get("external_id"))
and (label.external_id != str(request.data.get("external_id")))
and Issue.objects.filter(
project_id=project_id,
workspace__slug=slug,
external_source=request.data.get(
"external_source", label.external_source
),
external_id=request.data.get("external_id"),
).exists()
):
return Response(
{
"error": "Label with the same external id and external source already exists",
"id": str(label.id),
},
status=status.HTTP_409_CONFLICT,
)
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+2 -2
View File
@@ -151,7 +151,7 @@ class ModuleAPIEndpoint(WebhookMixin, BaseAPIView):
return Response(
{
"error": "Module with the same external id and external source already exists",
"module_id": str(module.id),
"id": str(module.id),
},
status=status.HTTP_409_CONFLICT,
)
@@ -185,7 +185,7 @@ class ModuleAPIEndpoint(WebhookMixin, BaseAPIView):
return Response(
{
"error": "Module with the same external id and external source already exists",
"module_id": str(module.id),
"id": str(module.id),
},
status=status.HTTP_409_CONFLICT,
)
+2 -2
View File
@@ -57,7 +57,7 @@ class StateAPIEndpoint(BaseAPIView):
return Response(
{
"error": "State with the same external id and external source already exists",
"state_id": str(state.id),
"id": str(state.id),
},
status=status.HTTP_409_CONFLICT,
)
@@ -128,7 +128,7 @@ class StateAPIEndpoint(BaseAPIView):
return Response(
{
"error": "State with the same external id and external source already exists",
"state_id": str(state.id),
"id": str(state.id),
},
status=status.HTTP_409_CONFLICT,
)
+12 -12
View File
@@ -242,13 +242,13 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
.values("display_name", "assignee_id", "avatar")
.annotate(
total_issues=Count(
"assignee_id",
"id",
filter=Q(archived_at__isnull=True, is_draft=False),
),
)
.annotate(
completed_issues=Count(
"assignee_id",
"id",
filter=Q(
completed_at__isnull=False,
archived_at__isnull=True,
@@ -258,7 +258,7 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
)
.annotate(
pending_issues=Count(
"assignee_id",
"id",
filter=Q(
completed_at__isnull=True,
archived_at__isnull=True,
@@ -281,13 +281,13 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
.values("label_name", "color", "label_id")
.annotate(
total_issues=Count(
"label_id",
"id",
filter=Q(archived_at__isnull=True, is_draft=False),
)
)
.annotate(
completed_issues=Count(
"label_id",
"id",
filter=Q(
completed_at__isnull=False,
archived_at__isnull=True,
@@ -297,7 +297,7 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
)
.annotate(
pending_issues=Count(
"label_id",
"id",
filter=Q(
completed_at__isnull=True,
archived_at__isnull=True,
@@ -419,13 +419,13 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
)
.annotate(
total_issues=Count(
"assignee_id",
"id",
filter=Q(archived_at__isnull=True, is_draft=False),
),
)
.annotate(
completed_issues=Count(
"assignee_id",
"id",
filter=Q(
completed_at__isnull=False,
archived_at__isnull=True,
@@ -435,7 +435,7 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
)
.annotate(
pending_issues=Count(
"assignee_id",
"id",
filter=Q(
completed_at__isnull=True,
archived_at__isnull=True,
@@ -459,13 +459,13 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
.values("label_name", "color", "label_id")
.annotate(
total_issues=Count(
"label_id",
"id",
filter=Q(archived_at__isnull=True, is_draft=False),
),
)
.annotate(
completed_issues=Count(
"label_id",
"id",
filter=Q(
completed_at__isnull=False,
archived_at__isnull=True,
@@ -475,7 +475,7 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
)
.annotate(
pending_issues=Count(
"label_id",
"id",
filter=Q(
completed_at__isnull=True,
archived_at__isnull=True,
+34
View File
@@ -145,6 +145,23 @@ def dashboard_assigned_issues(self, request, slug):
)
).order_by("priority_order")
if issue_type == "pending":
pending_issues_count = assigned_issues.filter(
state__group__in=["backlog", "started", "unstarted"]
).count()
pending_issues = assigned_issues.filter(
state__group__in=["backlog", "started", "unstarted"]
)[:5]
return Response(
{
"issues": IssueSerializer(
pending_issues, many=True, expand=self.expand
).data,
"count": pending_issues_count,
},
status=status.HTTP_200_OK,
)
if issue_type == "completed":
completed_issues_count = assigned_issues.filter(
state__group__in=["completed"]
@@ -257,6 +274,23 @@ def dashboard_created_issues(self, request, slug):
)
).order_by("priority_order")
if issue_type == "pending":
pending_issues_count = created_issues.filter(
state__group__in=["backlog", "started", "unstarted"]
).count()
pending_issues = created_issues.filter(
state__group__in=["backlog", "started", "unstarted"]
)[:5]
return Response(
{
"issues": IssueSerializer(
pending_issues, many=True, expand=self.expand
).data,
"count": pending_issues_count,
},
status=status.HTTP_200_OK,
)
if issue_type == "completed":
completed_issues_count = created_issues.filter(
state__group__in=["completed"]
+6 -6
View File
@@ -197,7 +197,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
)
.annotate(
total_issues=Count(
"assignee_id",
"id",
filter=Q(
archived_at__isnull=True,
is_draft=False,
@@ -206,7 +206,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
)
.annotate(
completed_issues=Count(
"assignee_id",
"id",
filter=Q(
completed_at__isnull=False,
archived_at__isnull=True,
@@ -216,7 +216,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
)
.annotate(
pending_issues=Count(
"assignee_id",
"id",
filter=Q(
completed_at__isnull=True,
archived_at__isnull=True,
@@ -239,7 +239,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
.values("label_name", "color", "label_id")
.annotate(
total_issues=Count(
"label_id",
"id",
filter=Q(
archived_at__isnull=True,
is_draft=False,
@@ -248,7 +248,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
)
.annotate(
completed_issues=Count(
"label_id",
"id",
filter=Q(
completed_at__isnull=False,
archived_at__isnull=True,
@@ -258,7 +258,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
)
.annotate(
pending_issues=Count(
"label_id",
"id",
filter=Q(
completed_at__isnull=True,
archived_at__isnull=True,
@@ -353,13 +353,18 @@ def track_assignees(
issue_activities,
epoch,
):
requested_assignees = set(
[str(asg) for asg in requested_data.get("assignee_ids", [])]
requested_assignees = (
set([str(asg) for asg in requested_data.get("assignee_ids", [])])
if requested_data is not None
else set()
)
current_assignees = set(
[str(asg) for asg in current_instance.get("assignee_ids", [])]
current_assignees = (
set([str(asg) for asg in current_instance.get("assignee_ids", [])])
if current_instance is not None
else set()
)
added_assignees = requested_assignees - current_assignees
dropped_assginees = current_assignees - requested_assignees
@@ -547,6 +552,20 @@ def create_issue_activity(
epoch=epoch,
)
)
requested_data = (
json.loads(requested_data) if requested_data is not None else None
)
if requested_data.get("assignee_ids") is not None:
track_assignees(
requested_data,
current_instance,
issue_id,
project_id,
workspace_id,
actor_id,
issue_activities,
epoch,
)
def update_issue_activity(
+5
View File
@@ -172,4 +172,9 @@ def create_user_notification(sender, instance, created, **kwargs):
from plane.db.models import UserNotificationPreference
UserNotificationPreference.objects.create(
user=instance,
property_change=False,
state_change=False,
comment=False,
mention=False,
issue_completed=False,
)
+17
View File
@@ -0,0 +1,17 @@
import os
import uvicorn
if __name__ == "__main__":
os.environ.setdefault(
"DJANGO_SETTINGS_MODULE", "plane.settings.production"
)
uvicorn.run(
"plane.asgi:application",
host=os.environ.get("HOST", "0.0.0.0"),
port=os.environ.get("PORT", 8000),
ws="auto",
workers=int(os.environ.get("GUNICORN_WORKERS", 1)),
log_level=os.environ.get("LOG_LEVEL", "info"),
lifespan="off",
access_log="on",
)
+6 -1
View File
@@ -49,7 +49,7 @@ function buildLocalImage() {
cd $PLANE_TEMP_CODE_DIR
if [ "$BRANCH" == "master" ];
then
APP_RELEASE=latest
export APP_RELEASE=latest
fi
docker compose -f build.yml build --no-cache >&2
@@ -205,6 +205,11 @@ else
PULL_POLICY=never
fi
if [ "$BRANCH" == "master" ];
then
export APP_RELEASE=latest
fi
# REMOVE SPECIAL CHARACTERS FROM BRANCH NAME
if [ "$BRANCH" != "master" ];
then
-24
View File
@@ -1,24 +0,0 @@
import { APIService } from "../api.service";
// types
import { GptApiResponse } from "@plane/types";
export class AIService extends APIService {
constructor(BASE_URL: string) {
super(BASE_URL);
}
async createGptTask(
workspaceSlug: string,
projectId: string,
data: { prompt: string; task: string }
): Promise<GptApiResponse> {
return this.post(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/ai-assistant/`,
data
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response;
});
}
}
@@ -1,63 +0,0 @@
// services
import { APIService } from "services/api.service";
// types
import {
IAnalyticsParams,
IAnalyticsResponse,
IDefaultAnalyticsResponse,
IExportAnalyticsFormData,
ISaveAnalyticsFormData,
} from "@plane/types";
// helpers
import { API_BASE_URL } from "helpers/common.helper";
export class AnalyticsService extends APIService {
constructor() {
super(API_BASE_URL);
}
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;
});
}
}
-94
View File
@@ -1,94 +0,0 @@
import axios from "axios";
import Cookies from "js-cookie";
export abstract class APIService {
protected baseURL: string;
protected headers: any = {};
constructor(baseURL: string) {
this.baseURL = baseURL;
}
setRefreshToken(token: string) {
Cookies.set("refreshToken", token, { expires: 30 });
}
getRefreshToken() {
return Cookies.get("refreshToken");
}
purgeRefreshToken() {
Cookies.remove("refreshToken", { path: "/" });
}
setAccessToken(token: string) {
Cookies.set("accessToken", token, { expires: 30 });
}
getAccessToken() {
return Cookies.get("accessToken");
}
purgeAccessToken() {
Cookies.remove("accessToken", { path: "/" });
}
getHeaders() {
return {
Authorization: `Bearer ${this.getAccessToken()}`,
};
}
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,
});
}
request(config = {}) {
return axios(config);
}
}
-15
View File
@@ -1,15 +0,0 @@
import { AIService } from "./ai/ai.service";
import { WorkspaceService } from "./workspace/workspace.service";
export class Client {
ai;
workspace;
constructor(BASE_URL: string | undefined) {
this.user = new UserService(BASE_URL || "");
this.instance = new InstanceService(BASE_URL || "");
this.ai = new AIService(BASE_URL || "");
this.workspace = new WorkspaceService(BASE_URL || "");
}
}
-124
View File
@@ -1,124 +0,0 @@
// services
import { APIService } from "services/api.service";
// types
import type { CycleDateCheckData, ICycle, TIssue, TIssueMap } from "@plane/types";
// helpers
import { API_BASE_URL } from "helpers/common.helper";
export class CycleService extends APIService {
constructor() {
super(API_BASE_URL);
}
async createCycle(workspaceSlug: string, projectId: string, data: any): Promise<ICycle> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async getCyclesWithParams(workspaceSlug: string, projectId: string, cycleType?: "current"): 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<TIssue[]> {
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<TIssue[]> {
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 patchCycle(workspaceSlug: string, projectId: string, cycleId: string, data: Partial<ICycle>): Promise<any> {
return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async deleteCycle(workspaceSlug: string, projectId: string, cycleId: string): Promise<any> {
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/`)
.then((response) => 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;
});
}
}
@@ -1,112 +0,0 @@
import { APIService } from "services/api.service";
// helpers
import { API_BASE_URL } from "helpers/common.helper";
// types
import type { TInboxIssueFilterOptions, TInboxIssueExtendedDetail, TIssue, TInboxDetailedStatus } from "@plane/types";
export class InboxIssueService extends APIService {
constructor() {
super(API_BASE_URL);
}
async fetchInboxIssues(
workspaceSlug: string,
projectId: string,
inboxId: string,
params?: TInboxIssueFilterOptions | {}
): Promise<TInboxIssueExtendedDetail[]> {
return this.get(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/inboxes/${inboxId}/inbox-issues/?expand=issue_inbox`,
{
params,
}
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async fetchInboxIssueById(
workspaceSlug: string,
projectId: string,
inboxId: string,
inboxIssueId: string
): Promise<TInboxIssueExtendedDetail> {
return this.get(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/inboxes/${inboxId}/inbox-issues/${inboxIssueId}/?expand=issue_inbox`
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async createInboxIssue(
workspaceSlug: string,
projectId: string,
inboxId: string,
data: {
source: string;
issue: Partial<TIssue>;
}
): Promise<TInboxIssueExtendedDetail> {
return this.post(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/inboxes/${inboxId}/inbox-issues/?expand=issue_inbox`,
data
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async updateInboxIssue(
workspaceSlug: string,
projectId: string,
inboxId: string,
inboxIssueId: string,
data: { issue: Partial<TIssue> }
): Promise<TInboxIssueExtendedDetail> {
return this.patch(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/inboxes/${inboxId}/inbox-issues/${inboxIssueId}/?expand=issue_inbox`,
data
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async removeInboxIssue(
workspaceSlug: string,
projectId: string,
inboxId: string,
inboxIssueId: string
): Promise<void> {
return this.delete(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/inboxes/${inboxId}/inbox-issues/${inboxIssueId}/`
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async updateInboxIssueStatus(
workspaceSlug: string,
projectId: string,
inboxId: string,
inboxIssueId: string,
data: TInboxDetailedStatus
): Promise<TInboxIssueExtendedDetail> {
return this.patch(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/inboxes/${inboxId}/inbox-issues/${inboxIssueId}/?expand=issue_inbox`,
data
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
}
@@ -1,122 +0,0 @@
import { APIService } from "services/api.service";
// helpers
import { API_BASE_URL } from "helpers/common.helper";
// types
import type { IInboxIssue, IInbox, TInboxStatus, IInboxQueryParams } from "@plane/types";
export class InboxService extends APIService {
constructor() {
super(API_BASE_URL);
}
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<IInboxIssue> {
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
): Promise<any> {
return this.delete(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/inboxes/${inboxId}/inbox-issues/${inboxIssueId}/`
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async markInboxStatus(
workspaceSlug: string,
projectId: string,
inboxId: string,
inboxIssueId: string,
data: TInboxStatus
): Promise<IInboxIssue> {
return this.patch(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/inboxes/${inboxId}/inbox-issues/${inboxIssueId}/`,
data
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async patchInboxIssue(
workspaceSlug: string,
projectId: string,
inboxId: string,
inboxIssueId: string,
data: { issue: Partial<IInboxIssue> }
): Promise<any> {
return this.patch(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/inboxes/${inboxId}/inbox-issues/${inboxIssueId}/`,
data
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async createInboxIssue(workspaceSlug: string, projectId: string, inboxId: string, data: any): Promise<IInboxIssue> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/inboxes/${inboxId}/inbox-issues/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
}
-35
View File
@@ -1,35 +0,0 @@
import { APIService } from "services/api.service";
// helpers
import { API_BASE_URL } from "helpers/common.helper";
// types
import type { TInbox } from "@plane/types";
export class InboxService extends APIService {
constructor() {
super(API_BASE_URL);
}
async fetchInboxes(workspaceSlug: string, projectId: string): Promise<TInbox[]> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/inboxes/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async fetchInboxById(workspaceSlug: string, projectId: string, inboxId: string): Promise<TInbox> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/inboxes/${inboxId}/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async updateInbox(workspaceSlug: string, projectId: string, inboxId: string, data: Partial<TInbox>): Promise<TInbox> {
return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/inboxes/${inboxId}/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
}
-2
View File
@@ -1,2 +0,0 @@
export * from "./inbox.service";
export * from "./inbox-issue.service";
@@ -1,49 +0,0 @@
import { APIService } from "../api.service";
import { IApiToken } from "@plane/types";
export class APITokenService extends APIService {
constructor(BASE_URL) {
super(BASE_URL);
}
async getApiTokens(workspaceSlug: string): Promise<IApiToken[]> {
return this.get(`/api/workspaces/${workspaceSlug}/api-tokens/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async retrieveApiToken(
workspaceSlug: string,
tokenId: String
): Promise<IApiToken> {
return this.get(`/api/workspaces/${workspaceSlug}/api-tokens/${tokenId}`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async createApiToken(
workspaceSlug: string,
data: Partial<IApiToken>
): Promise<IApiToken> {
return this.post(`/api/workspaces/${workspaceSlug}/api-tokens/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async deleteApiToken(
workspaceSlug: string,
tokenId: String
): Promise<IApiToken> {
return this.delete(`/api/workspaces/${workspaceSlug}/api-tokens/${tokenId}`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
}
@@ -1,24 +0,0 @@
// services
import { APIService } from "services/api.service";
// helper
import { API_BASE_URL } from "helpers/common.helper";
// types
import { IAppConfig } from "@plane/types";
export class AppConfigService extends APIService {
constructor() {
super(API_BASE_URL);
}
async envConfig(): Promise<IAppConfig> {
return this.get("/api/configs/", {
headers: {
"Content-Type": "application/json",
},
})
.then((response) => response.data)
.catch((error) => {
throw error?.response?.data;
});
}
}
-187
View File
@@ -1,187 +0,0 @@
// services
import { APIService } from "services/api.service";
// helpers
import { API_BASE_URL } from "helpers/common.helper";
import axios from "axios";
export 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;
}
export interface UnSplashImageUrls {
raw: string;
full: string;
regular: string;
small: string;
thumb: string;
small_s3: string;
}
export class FileService extends APIService {
private cancelSource: any;
constructor() {
super(API_BASE_URL);
this.uploadFile = this.uploadFile.bind(this);
this.deleteImage = this.deleteImage.bind(this);
this.restoreImage = this.restoreImage.bind(this);
this.cancelUpload = this.cancelUpload.bind(this);
}
async uploadFile(workspaceSlug: string, file: FormData): Promise<any> {
this.cancelSource = axios.CancelToken.source();
return this.post(`/api/workspaces/${workspaceSlug}/file-assets/`, file, {
headers: {
...this.getHeaders(),
"Content-Type": "multipart/form-data",
},
cancelToken: this.cancelSource.token,
})
.then((response) => response?.data)
.catch((error) => {
if (axios.isCancel(error)) {
console.log(error.message);
} else {
console.log(error);
throw error?.response?.data;
}
});
}
cancelUpload() {
this.cancelSource.cancel("Upload cancelled");
}
getUploadFileFunction(workspaceSlug: string): (file: File) => Promise<string> {
return async (file: File) => {
try {
const formData = new FormData();
formData.append("asset", file);
formData.append("attributes", JSON.stringify({}));
const data = await this.uploadFile(workspaceSlug, formData);
return data.asset;
} catch (e) {
console.error(e);
}
};
}
getDeleteImageFunction(workspaceId: string) {
return async (src: string) => {
try {
const assetUrlWithWorkspaceId = `${workspaceId}/${this.extractAssetIdFromUrl(src, workspaceId)}`;
const data = await this.deleteImage(assetUrlWithWorkspaceId);
return data;
} catch (e) {
console.error(e);
}
};
}
getRestoreImageFunction(workspaceId: string) {
return async (src: string) => {
try {
const assetUrlWithWorkspaceId = `${workspaceId}/${this.extractAssetIdFromUrl(src, workspaceId)}`;
const data = await this.restoreImage(assetUrlWithWorkspaceId);
return data;
} catch (e) {
console.error(e);
}
};
}
extractAssetIdFromUrl(src: string, workspaceId: string): string {
const indexWhereAssetIdStarts = src.indexOf(workspaceId) + workspaceId.length + 1;
if (indexWhereAssetIdStarts === -1) {
throw new Error("Workspace ID not found in source string");
}
const assetUrl = src.substring(indexWhereAssetIdStarts);
return assetUrl;
}
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 restoreImage(assetUrlWithWorkspaceId: string): Promise<any> {
return this.post(`/api/workspaces/file-assets/${assetUrlWithWorkspaceId}/restore/`, {
headers: this.getHeaders(),
"Content-Type": "application/json",
})
.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.post(`/api/users/file-assets/`, file, {
headers: {
...this.getHeaders(),
"Content-Type": "multipart/form-data",
},
})
.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(query?: string): Promise<UnSplashImage[]> {
return this.get(`/api/unsplash/`, {
params: {
query,
},
})
.then((res) => res?.data?.results ?? res?.data)
.catch((err) => {
throw err?.response?.data;
});
}
async getProjectCoverImages(): Promise<string[]> {
return this.get(`/api/project-covers/`)
.then((res) => res?.data)
.catch((err) => {
throw err?.response?.data;
});
}
}
@@ -1,53 +0,0 @@
import { APIService } from "services/api.service";
// helpers
import { API_BASE_URL } from "helpers/common.helper";
// types
import type { IFormattedInstanceConfiguration, IInstance, IInstanceAdmin, IInstanceConfiguration } from "@plane/types";
export class InstanceService extends APIService {
constructor() {
super(API_BASE_URL);
}
async getInstanceInfo(): Promise<IInstance> {
return this.get("/api/instances/", { headers: {} })
.then((response) => response.data)
.catch((error) => {
throw error;
});
}
async getInstanceAdmins(): Promise<IInstanceAdmin[]> {
return this.get("/api/instances/admins/")
.then((response) => response.data)
.catch((error) => {
throw error;
});
}
async updateInstanceInfo(data: Partial<IInstance>): Promise<IInstance> {
return this.patch("/api/instances/", data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async getInstanceConfigurations() {
return this.get("/api/instances/configurations/")
.then((response) => response.data)
.catch((error) => {
throw error;
});
}
async updateInstanceConfigurations(
data: Partial<IFormattedInstanceConfiguration>
): Promise<IInstanceConfiguration[]> {
return this.patch("/api/instances/configurations/", data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
}
@@ -1,60 +0,0 @@
// api services
import { APIService } from "services/api.service";
// helpers
import { API_BASE_URL } from "helpers/common.helper";
// types
import { IWebhook } from "@plane/types";
export class WebhookService extends APIService {
constructor() {
super(API_BASE_URL);
}
async fetchWebhooksList(workspaceSlug: string): Promise<IWebhook[]> {
return this.get(`/api/workspaces/${workspaceSlug}/webhooks/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async fetchWebhookDetails(workspaceSlug: string, webhookId: string): Promise<IWebhook> {
return this.get(`/api/workspaces/${workspaceSlug}/webhooks/${webhookId}/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async createWebhook(workspaceSlug: string, data: {}): Promise<IWebhook> {
return this.post(`/api/workspaces/${workspaceSlug}/webhooks/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async updateWebhook(workspaceSlug: string, webhookId: string, data: {}): Promise<IWebhook> {
return this.patch(`/api/workspaces/${workspaceSlug}/webhooks/${webhookId}/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async deleteWebhook(workspaceSlug: string, webhookId: string): Promise<void> {
return this.delete(`/api/workspaces/${workspaceSlug}/webhooks/${webhookId}/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async regenerateSecretKey(workspaceSlug: string, webhookId: string): Promise<IWebhook> {
return this.post(`/api/workspaces/${workspaceSlug}/webhooks/${webhookId}/regenerate/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
}
@@ -1,39 +0,0 @@
import { APIService } from "services/api.service";
// helpers
import { API_BASE_URL } from "helpers/common.helper";
// types
import { IGithubRepoInfo, IGithubServiceImportFormData } from "@plane/types";
const integrationServiceType: string = "github";
export class GithubIntegrationService extends APIService {
constructor() {
super(API_BASE_URL);
}
async listAllRepositories(workspaceSlug: string, integrationSlug: string): Promise<any> {
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> {
return this.get(`/api/workspaces/${workspaceSlug}/importers/${integrationServiceType}/`, {
params,
})
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async createGithubServiceImport(workspaceSlug: string, data: IGithubServiceImportFormData): Promise<any> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/importers/${integrationServiceType}/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
}
-3
View File
@@ -1,3 +0,0 @@
export * from "./github.service";
export * from "./integration.service";
export * from "./jira.service";
@@ -1,67 +0,0 @@
import { APIService } from "services/api.service";
// types
import { IAppIntegration, IImporterService, IWorkspaceIntegration, IExportServiceResponse } from "@plane/types";
// helper
import { API_BASE_URL } from "helpers/common.helper";
export class IntegrationService extends APIService {
constructor() {
super(API_BASE_URL);
}
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): Promise<any> {
return this.delete(`/api/workspaces/${workspaceSlug}/importers/${service}/${importerId}/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
}
@@ -1,28 +0,0 @@
import { APIService } from "services/api.service";
import { API_BASE_URL } from "helpers/common.helper";
// types
import { IJiraMetadata, IJiraResponse, IJiraImporterForm } from "@plane/types";
export class JiraImporterService extends APIService {
constructor() {
super(API_BASE_URL);
}
async getJiraProjectInfo(workspaceSlug: string, params: IJiraMetadata): Promise<IJiraResponse> {
return this.get(`/api/workspaces/${workspaceSlug}/importers/jira`, {
params,
})
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async createJiraImporter(workspaceSlug: string, data: IJiraImporterForm): Promise<IJiraResponse> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/importers/jira/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
}
-9
View File
@@ -1,9 +0,0 @@
export * from "./issue_archive.service";
export * from "./issue.service";
export * from "./issue_draft.service";
export * from "./issue_reaction.service";
export * from "./issue_label.service";
export * from "./issue_attachment.service";
export * from "./issue_activity.service";
export * from "./issue_comment.service";
export * from "./issue_relation.service";
-238
View File
@@ -1,238 +0,0 @@
// services
import { APIService } from "services/api.service";
// type
import type {
TIssue,
IIssueDisplayProperties,
ILinkDetails,
TIssueLink,
TIssueSubIssues,
TIssueActivity,
} from "@plane/types";
// helper
import { API_BASE_URL } from "helpers/common.helper";
export class IssueService extends APIService {
constructor() {
super(API_BASE_URL);
}
async createIssue(workspaceSlug: string, projectId: string, data: any): Promise<any> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async getIssues(workspaceSlug: string, projectId: string, queries?: any): Promise<TIssue[]> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/`, {
params: queries,
})
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async getIssuesWithParams(
workspaceSlug: string,
projectId: string,
queries?: any
): Promise<TIssue[] | { [key: string]: TIssue[] }> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/`, {
params: queries,
})
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async retrieve(workspaceSlug: string, projectId: string, issueId: string, queries?: any): Promise<TIssue> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/`, {
params: queries,
})
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async getIssueActivities(workspaceSlug: string, projectId: string, issueId: string): Promise<TIssueActivity[]> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/history/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async addIssueToCycle(
workspaceSlug: string,
projectId: string,
cycleId: string,
data: {
issues: string[];
}
) {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/cycle-issues/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async removeIssueFromCycle(workspaceSlug: string, projectId: string, cycleId: string, bridgeId: string) {
return this.delete(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/cycle-issues/${bridgeId}/`
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async createIssueRelation(
workspaceSlug: string,
projectId: string,
issueId: string,
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) => response?.data)
.catch((error) => {
throw error?.response;
});
}
async deleteIssueRelation(workspaceSlug: string, projectId: string, issueId: string, relationId: string) {
return this.delete(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-relation/${relationId}/`
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response;
});
}
async getIssueDisplayProperties(workspaceSlug: string, projectId: string): Promise<any> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-display-properties/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async updateIssueDisplayProperties(
workspaceSlug: string,
projectId: string,
data: IIssueDisplayProperties
): Promise<any> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-display-properties/`, {
properties: data,
})
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async patchIssue(workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>): Promise<any> {
return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async deleteIssue(workspaceSlug: string, projectId: string, issuesId: string): Promise<any> {
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issuesId}/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async bulkDeleteIssues(workspaceSlug: string, projectId: string, data: any): Promise<any> {
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/bulk-delete-issues/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async subIssues(workspaceSlug: string, projectId: string, issueId: string): Promise<TIssueSubIssues> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/sub-issues/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async addSubIssues(
workspaceSlug: string,
projectId: string,
issueId: string,
data: { sub_issue_ids: string[] }
): Promise<TIssueSubIssues> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/sub-issues/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async fetchIssueLinks(workspaceSlug: string, projectId: string, issueId: string): Promise<TIssueLink[]> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-links/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response;
});
}
async createIssueLink(
workspaceSlug: string,
projectId: string,
issueId: string,
data: Partial<TIssueLink>
): Promise<ILinkDetails> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-links/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response;
});
}
async updateIssueLink(
workspaceSlug: string,
projectId: string,
issueId: string,
linkId: string,
data: Partial<TIssueLink>
): Promise<ILinkDetails> {
return this.patch(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-links/${linkId}/`,
data
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response;
});
}
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}/`
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
}
@@ -1,33 +0,0 @@
import { APIService } from "services/api.service";
// types
import { TIssueActivity } from "@plane/types";
// helper
import { API_BASE_URL } from "helpers/common.helper";
export class IssueActivityService extends APIService {
constructor() {
super(API_BASE_URL);
}
async getIssueActivities(
workspaceSlug: string,
projectId: string,
issueId: string,
params:
| {
created_at__gt: string;
}
| {} = {}
): Promise<TIssueActivity[]> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/history/`, {
params: {
activity_type: "issue-property",
...params,
},
})
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
}
@@ -1,43 +0,0 @@
import { APIService } from "services/api.service";
// type
import { API_BASE_URL } from "helpers/common.helper";
export class IssueArchiveService extends APIService {
constructor() {
super(API_BASE_URL);
}
async getArchivedIssues(workspaceSlug: string, projectId: string, queries?: any): Promise<any> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/archived-issues/`, {
params: { ...queries },
})
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async unarchiveIssue(workspaceSlug: string, projectId: string, issueId: string): Promise<any> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/unarchive/${issueId}/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
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}/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
}
@@ -1,56 +0,0 @@
import { APIService } from "services/api.service";
// helper
import { API_BASE_URL } from "helpers/common.helper";
// types
import { TIssueAttachment } from "@plane/types";
export class IssueAttachmentService extends APIService {
constructor() {
super(API_BASE_URL);
}
async uploadIssueAttachment(
workspaceSlug: string,
projectId: string,
issueId: string,
file: FormData
): Promise<TIssueAttachment> {
return this.post(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-attachments/`,
file,
{
headers: {
...this.getHeaders(),
"Content-Type": "multipart/form-data",
},
}
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async getIssueAttachment(workspaceSlug: string, projectId: string, issueId: string): Promise<TIssueAttachment[]> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-attachments/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async deleteIssueAttachment(
workspaceSlug: string,
projectId: string,
issueId: string,
assetId: string
): Promise<TIssueAttachment> {
return this.delete(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-attachments/${assetId}/`
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
}
@@ -1,73 +0,0 @@
import { APIService } from "services/api.service";
// types
import { TIssueComment } from "@plane/types";
// helper
import { API_BASE_URL } from "helpers/common.helper";
export class IssueCommentService extends APIService {
constructor() {
super(API_BASE_URL);
}
async getIssueComments(
workspaceSlug: string,
projectId: string,
issueId: string,
params:
| {
created_at__gt: string;
}
| {} = {}
): Promise<TIssueComment[]> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/history/`, {
params: {
activity_type: "issue-comment",
...params,
},
})
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async createIssueComment(
workspaceSlug: string,
projectId: string,
issueId: string,
data: Partial<TIssueComment>
): Promise<any> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/comments/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async patchIssueComment(
workspaceSlug: string,
projectId: string,
issueId: string,
commentId: string,
data: Partial<TIssueComment>
): Promise<any> {
return this.patch(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/comments/${commentId}/`,
data
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async deleteIssueComment(workspaceSlug: string, projectId: string, issueId: string, commentId: string): Promise<any> {
return this.delete(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/comments/${commentId}/`
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
}
@@ -1,52 +0,0 @@
import { APIService } from "services/api.service";
// helpers
import { API_BASE_URL } from "helpers/common.helper";
import { TIssue } from "@plane/types";
export class IssueDraftService extends APIService {
constructor() {
super(API_BASE_URL);
}
async getDraftIssues(workspaceSlug: string, projectId: string, query?: any): Promise<TIssue[]> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-drafts/`, {
params: { ...query },
})
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async createDraftIssue(workspaceSlug: string, projectId: string, data: any): 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): 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): 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;
});
}
}
@@ -1,103 +0,0 @@
// services
import { APIService } from "services/api.service";
// types
import type { IIssueFiltersResponse } from "@plane/types";
import { API_BASE_URL } from "helpers/common.helper";
export class IssueFiltersService extends APIService {
constructor() {
super(API_BASE_URL);
}
// // workspace issue filters
// async fetchWorkspaceFilters(workspaceSlug: string): Promise<IIssueFiltersResponse> {
// return this.get(`/api/workspaces/${workspaceSlug}/user-properties/`)
// .then((response) => response?.data)
// .catch((error) => {
// throw error?.response?.data;
// });
// }
// async patchWorkspaceFilters(
// workspaceSlug: string,
// data: Partial<IIssueFiltersResponse>
// ): Promise<IIssueFiltersResponse> {
// return this.patch(`/api/workspaces/${workspaceSlug}/user-properties/`, data)
// .then((response) => response?.data)
// .catch((error) => {
// throw error?.response?.data;
// });
// }
// project issue filters
async fetchProjectIssueFilters(workspaceSlug: string, projectId: string): Promise<IIssueFiltersResponse> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/user-properties/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async patchProjectIssueFilters(
workspaceSlug: string,
projectId: string,
data: Partial<IIssueFiltersResponse>
): Promise<any> {
return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/user-properties/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
// cycle issue filters
async fetchCycleIssueFilters(
workspaceSlug: string,
projectId: string,
cycleId: string
): Promise<IIssueFiltersResponse> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/user-properties/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async patchCycleIssueFilters(
workspaceSlug: string,
projectId: string,
cycleId: string,
data: Partial<IIssueFiltersResponse>
): Promise<any> {
return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/user-properties/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
// module issue filters
async fetchModuleIssueFilters(
workspaceSlug: string,
projectId: string,
moduleId: string
): Promise<IIssueFiltersResponse> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/user-properties/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async patchModuleIssueFilters(
workspaceSlug: string,
projectId: string,
moduleId: string,
data: Partial<IIssueFiltersResponse>
): Promise<any> {
return this.patch(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/user-properties/`,
data
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
}
@@ -1,51 +0,0 @@
import { API_BASE_URL } from "helpers/common.helper";
// services
import { APIService } from "services/api.service";
// types
import { IIssueLabel } from "@plane/types";
export class IssueLabelService extends APIService {
constructor() {
super(API_BASE_URL);
}
async getWorkspaceIssueLabels(workspaceSlug: string): Promise<IIssueLabel[]> {
return this.get(`/api/workspaces/${workspaceSlug}/labels/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async getProjectLabels(workspaceSlug: string, projectId: string): Promise<IIssueLabel[]> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-labels/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async createIssueLabel(workspaceSlug: string, projectId: string, data: any): Promise<IIssueLabel> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-labels/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async patchIssueLabel(workspaceSlug: string, projectId: string, labelId: string, data: any): Promise<any> {
return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-labels/${labelId}/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async deleteIssueLabel(workspaceSlug: string, projectId: string, labelId: string): Promise<any> {
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-labels/${labelId}/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
}
@@ -1,82 +0,0 @@
import { API_BASE_URL } from "helpers/common.helper";
// services
import { APIService } from "services/api.service";
// types
import type { TIssueCommentReaction, TIssueReaction } from "@plane/types";
export class IssueReactionService extends APIService {
constructor() {
super(API_BASE_URL);
}
async createIssueReaction(
workspaceSlug: string,
projectId: string,
issueId: string,
data: Partial<TIssueReaction>
): Promise<any> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/reactions/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async listIssueReactions(workspaceSlug: string, projectId: string, issueId: string): Promise<TIssueReaction[]> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/reactions/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async deleteIssueReaction(workspaceSlug: string, projectId: string, issueId: string, reaction: string): Promise<any> {
return this.delete(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/reactions/${reaction}/`
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async createIssueCommentReaction(
workspaceSlug: string,
projectId: string,
commentId: string,
data: Partial<TIssueCommentReaction>
): Promise<any> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/comments/${commentId}/reactions/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async listIssueCommentReactions(
workspaceSlug: string,
projectId: string,
commentId: string
): Promise<TIssueCommentReaction[]> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/comments/${commentId}/reactions/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async deleteIssueCommentReaction(
workspaceSlug: string,
projectId: string,
commentId: string,
reaction: string
): Promise<any> {
return this.delete(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/comments/${commentId}/reactions/${reaction}/`
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
}
@@ -1,45 +0,0 @@
import { API_BASE_URL } from "helpers/common.helper";
// services
import { APIService } from "services/api.service";
// types
import type { TIssueRelation, TIssue, TIssueRelationTypes } from "@plane/types";
export class IssueRelationService extends APIService {
constructor() {
super(API_BASE_URL);
}
async listIssueRelations(workspaceSlug: string, projectId: string, issueId: string): Promise<TIssueRelation> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-relation/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async createIssueRelations(
workspaceSlug: string,
projectId: string,
issueId: string,
data: { relation_type: TIssueRelationTypes; issues: string[] }
): Promise<TIssue[]> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-relation/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async deleteIssueRelation(
workspaceSlug: string,
projectId: string,
issueId: string,
data: { relation_type: TIssueRelationTypes; related_issue: string }
): Promise<any> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/remove-relation/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
}
-213
View File
@@ -1,213 +0,0 @@
// services
import { APIService } from "services/api.service";
// types
import type { IModule, TIssue, ILinkDetails, ModuleLink } from "@plane/types";
import { API_BASE_URL } from "helpers/common.helper";
export class ModuleService extends APIService {
constructor() {
super(API_BASE_URL);
}
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): Promise<IModule> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async updateModule(workspaceSlug: string, projectId: string, moduleId: string, data: any): Promise<any> {
return this.put(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/`, data)
.then((response) => 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>
): Promise<IModule> {
return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async deleteModule(workspaceSlug: string, projectId: string, moduleId: string): Promise<any> {
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async getModuleIssues(workspaceSlug: string, projectId: string, moduleId: string, queries?: any): Promise<TIssue[]> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/issues/`, {
params: queries,
})
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async addIssuesToModule(
workspaceSlug: string,
projectId: string,
moduleId: string,
data: { issues: string[] }
): Promise<void> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/issues/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async addModulesToIssue(
workspaceSlug: string,
projectId: string,
issueId: string,
data: { modules: string[] }
): Promise<void> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/modules/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async removeIssueFromModule(
workspaceSlug: string,
projectId: string,
moduleId: string,
issueId: string
): Promise<any> {
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/issues/${issueId}/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async removeIssuesFromModuleBulk(
workspaceSlug: string,
projectId: string,
moduleId: string,
issueIds: string[]
): Promise<any> {
const promiseDataUrls: any = [];
issueIds.forEach((issueId) => {
promiseDataUrls.push(
this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/issues/${issueId}/`)
);
});
return await Promise.all(promiseDataUrls)
.then((response) => response)
.catch((error) => {
throw error?.response?.data;
});
}
async removeModulesFromIssueBulk(
workspaceSlug: string,
projectId: string,
issueId: string,
moduleIds: string[]
): Promise<any> {
const promiseDataUrls: any = [];
moduleIds.forEach((moduleId) => {
promiseDataUrls.push(
this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/issues/${issueId}/`)
);
});
return await Promise.all(promiseDataUrls)
.then((response) => response)
.catch((error) => {
throw error?.response?.data;
});
}
async createModuleLink(
workspaceSlug: string,
projectId: string,
moduleId: string,
data: Partial<ModuleLink>
): Promise<ILinkDetails> {
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: Partial<ModuleLink>
): Promise<ILinkDetails> {
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;
});
}
}
-203
View File
@@ -1,203 +0,0 @@
import { API_BASE_URL } from "helpers/common.helper";
// services
import { APIService } from "services/api.service";
// types
import { IPage, IPageBlock, TIssue } from "@plane/types";
export class PageService extends APIService {
constructor() {
super(API_BASE_URL);
}
async createPage(workspaceSlug: string, projectId: string, data: Partial<IPage>): Promise<IPage> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async patchPage(workspaceSlug: string, projectId: string, pageId: string, data: Partial<IPage>): Promise<IPage> {
return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/`, data)
.then((response) => response?.data)
.catch((error) => {
console.error("error", error?.response?.data);
throw error?.response?.data;
});
}
async deletePage(workspaceSlug: string, projectId: string, pageId: string): Promise<any> {
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async addPageToFavorites(workspaceSlug: string, projectId: string, pageId: string): Promise<any> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/user-favorite-pages/`, { page: pageId })
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async removePageFromFavorites(workspaceSlug: string, projectId: string, pageId: string) {
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/user-favorite-pages/${pageId}`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async getProjectPages(workspaceSlug: string, projectId: string): Promise<IPage[]> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async getPagesWithParams(
workspaceSlug: string,
projectId: string,
pageType: "all" | "favorite" | "private" | "shared"
): Promise<IPage[]> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/`, {
params: {
page_view: pageType,
},
})
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async getPageDetails(workspaceSlug: string, projectId: string, pageId: string): Promise<IPage> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async createPageBlock(
workspaceSlug: string,
projectId: string,
pageId: string,
data: Partial<IPageBlock>
): Promise<IPageBlock> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/page-blocks/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async getPageBlock(
workspaceSlug: string,
projectId: string,
pageId: string,
pageBlockId: string
): Promise<IPageBlock[]> {
return this.get(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/page-blocks/${pageBlockId}/`
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async patchPageBlock(
workspaceSlug: string,
projectId: string,
pageId: string,
pageBlockId: string,
data: Partial<IPageBlock>
): Promise<IPage> {
return this.patch(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/page-blocks/${pageBlockId}/`,
data
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async deletePageBlock(workspaceSlug: string, projectId: string, pageId: string, pageBlockId: string): Promise<any> {
return this.delete(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/page-blocks/${pageBlockId}/`
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
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;
});
}
async convertPageBlockToIssue(
workspaceSlug: string,
projectId: string,
pageId: string,
blockId: string
): Promise<TIssue> {
return this.post(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/page-blocks/${blockId}/issues/`
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
// =============== Archiving & Unarchiving Pages =================
async archivePage(workspaceSlug: string, projectId: string, pageId: string): Promise<void> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/archive/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async restorePage(workspaceSlug: string, projectId: string, pageId: string): Promise<void> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/unarchive/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async getArchivedPages(workspaceSlug: string, projectId: string): Promise<IPage[]> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/archived-pages/`)
.then((response) => response?.data)
.catch((error) => {
throw error;
});
}
// ==================== Pages Locking Services ==========================
async lockPage(workspaceSlug: string, projectId: string, pageId: string): Promise<any> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/lock/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async unlockPage(workspaceSlug: string, projectId: string, pageId: string): Promise<any> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/unlock/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
}
-6
View File
@@ -1,6 +0,0 @@
export * from "./project.service";
export * from "./project-estimate.service";
export * from "./project-export.service";
export * from "./project-member.service";
export * from "./project-state.service";
export * from "./project-publish.service";
@@ -1,72 +0,0 @@
// services
import { APIService } from "services/api.service";
// types
import type { IEstimate, IEstimateFormData, IEstimatePoint } from "@plane/types";
// helpers
import { API_BASE_URL } from "helpers/common.helper";
export class ProjectEstimateService extends APIService {
constructor() {
super(API_BASE_URL);
}
async createEstimate(
workspaceSlug: string,
projectId: string,
data: IEstimateFormData
): Promise<{
estimate: IEstimate;
estimate_points: IEstimatePoint[];
}> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response;
});
}
async patchEstimate(
workspaceSlug: string,
projectId: string,
estimateId: string,
data: IEstimateFormData
): Promise<any> {
return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
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;
});
}
async getEstimatesList(workspaceSlug: string, projectId: string): Promise<IEstimate[]> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async deleteEstimate(workspaceSlug: string, projectId: string, estimateId: string): Promise<any> {
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async getWorkspaceEstimatesList(workspaceSlug: string): Promise<IEstimate[]> {
return this.get(`/api/workspaces/${workspaceSlug}/estimates/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
}
@@ -1,23 +0,0 @@
import { APIService } from "services/api.service";
// helpers
import { API_BASE_URL } from "helpers/common.helper";
export class ProjectExportService extends APIService {
constructor() {
super(API_BASE_URL);
}
async csvExport(
workspaceSlug: string,
data: {
provider: string;
project: string[];
}
): Promise<any> {
return this.post(`/api/workspaces/${workspaceSlug}/export-issues/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
}
@@ -1,68 +0,0 @@
import { API_BASE_URL } from "helpers/common.helper";
// services
import { APIService } from "services/api.service";
// types
import type { IProjectBulkAddFormData, IProjectMember, IProjectMembership } from "@plane/types";
export class ProjectMemberService extends APIService {
constructor() {
super(API_BASE_URL);
}
async fetchProjectMembers(workspaceSlug: string, projectId: string): Promise<IProjectMembership[]> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/members/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async bulkAddMembersToProject(
workspaceSlug: string,
projectId: string,
data: IProjectBulkAddFormData
): Promise<IProjectMembership[]> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/members/`, data)
.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;
});
}
}
@@ -1,61 +0,0 @@
import { API_BASE_URL } from "helpers/common.helper";
// services
import { APIService } from "services/api.service";
// types
import { IProjectPublishSettings } from "store/project/project-publish.store";
export class ProjectPublishService extends APIService {
constructor() {
super(API_BASE_URL);
}
async getProjectSettingsAsync(workspace_slug: string, project_slug: string): Promise<any> {
return this.get(`/api/workspaces/${workspace_slug}/projects/${project_slug}/project-deploy-boards/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response;
});
}
async createProjectSettingsAsync(
workspace_slug: string,
project_slug: string,
data: IProjectPublishSettings
): Promise<any> {
return this.post(`/api/workspaces/${workspace_slug}/projects/${project_slug}/project-deploy-boards/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response;
});
}
async updateProjectSettingsAsync(
workspace_slug: string,
project_slug: string,
project_publish_id: string,
data: IProjectPublishSettings
): Promise<any> {
return this.patch(
`/api/workspaces/${workspace_slug}/projects/${project_slug}/project-deploy-boards/${project_publish_id}/`,
data
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response;
});
}
async deleteProjectSettingsAsync(
workspace_slug: string,
project_slug: string,
project_publish_id: string
): Promise<any> {
return this.delete(
`/api/workspaces/${workspace_slug}/projects/${project_slug}/project-deploy-boards/${project_publish_id}/`
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response;
});
}
}
@@ -1,76 +0,0 @@
// services
import { APIService } from "services/api.service";
// helpers
import { API_BASE_URL } from "helpers/common.helper";
// types
import type { IState } from "@plane/types";
export class ProjectStateService extends APIService {
constructor() {
super(API_BASE_URL);
}
async createState(workspaceSlug: string, projectId: string, data: any): Promise<IState> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/states/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response;
});
}
async markDefault(workspaceSlug: string, projectId: string, stateId: string): Promise<void> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/states/${stateId}/mark-default/`, {})
.then((response) => response?.data)
.catch((error) => {
throw error?.response;
});
}
async getStates(workspaceSlug: string, projectId: string): Promise<IState[]> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/states/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async getState(workspaceSlug: string, projectId: string, stateId: string): Promise<any> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/states/${stateId}/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async updateState(workspaceSlug: string, projectId: string, stateId: string, data: IState): Promise<any> {
return this.put(`/api/workspaces/${workspaceSlug}/projects/${projectId}/states/${stateId}/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response;
});
}
async patchState(workspaceSlug: string, projectId: string, stateId: string, data: Partial<IState>): Promise<any> {
return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/states/${stateId}/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async deleteState(workspaceSlug: string, projectId: string, stateId: string): Promise<any> {
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/states/${stateId}/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response;
});
}
async getWorkspaceStates(workspaceSlug: string): Promise<IState[]> {
return this.get(`/api/workspaces/${workspaceSlug}/states/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
}
@@ -1,168 +0,0 @@
import { API_BASE_URL } from "helpers/common.helper";
// services
import { APIService } from "services/api.service";
// types
import type {
GithubRepositoriesResponse,
IProject,
ISearchIssueResponse,
ProjectPreferences,
IProjectViewProps,
TProjectIssuesSearchParams,
} from "@plane/types";
export class ProjectService extends APIService {
constructor() {
super(API_BASE_URL);
}
async createProject(workspaceSlug: string, data: Partial<IProject>): Promise<IProject> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/`, data)
.then((response) => 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): Promise<IProject[]> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/`)
.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>): Promise<IProject> {
return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async deleteProject(workspaceSlug: string, projectId: string): Promise<any> {
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/`)
.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.request({
method: "get",
url,
headers: this.getAccessToken() ? this.getHeaders() : {},
})
.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 getUserProjectFavorites(workspaceSlug: string): Promise<any[]> {
return this.get(`/api/workspaces/${workspaceSlug}/user-favorite-projects/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async addProjectToFavorites(workspaceSlug: string, project: string): Promise<any> {
return this.post(`/api/workspaces/${workspaceSlug}/user-favorite-projects/`, { project })
.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;
});
}
}
-148
View File
@@ -1,148 +0,0 @@
// services
import { APIService } from "services/api.service";
// helpers
import { API_BASE_URL } from "helpers/common.helper";
// types
import {
IEmailCheckData,
IEmailCheckResponse,
ILoginTokenResponse,
IMagicSignInData,
IPasswordSignInData,
} from "@plane/types";
export class AuthService extends APIService {
constructor() {
super(API_BASE_URL);
}
async emailCheck(data: IEmailCheckData): Promise<IEmailCheckResponse> {
return this.post("/api/email-check/", data, { headers: {} })
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async passwordSignIn(data: IPasswordSignInData): Promise<ILoginTokenResponse> {
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 sendResetPasswordLink(data: { email: string }): Promise<any> {
return this.post(`/api/forgot-password/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response;
});
}
async setPassword(data: { password: string }): Promise<any> {
return this.post(`/api/users/me/set-password/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async resetPassword(
uidb64: string,
token: string,
data: {
new_password: string;
}
): Promise<ILoginTokenResponse> {
return this.post(`/api/reset-password/${uidb64}/${token}/`, data, { headers: {} })
.then((response) => {
if (response?.status === 200) {
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 }): Promise<ILoginTokenResponse> {
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<ILoginTokenResponse> {
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 generateUniqueCode(data: { email: string }): Promise<any> {
return this.post("/api/magic-generate/", data, { headers: {} })
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async magicSignIn(data: IMagicSignInData): Promise<any> {
return await this.post("/api/magic-sign-in/", data, { headers: {} })
.then((response) => {
if (response?.status === 200) {
this.setAccessToken(response?.data?.access_token);
this.setRefreshToken(response?.data?.refresh_token);
return response?.data;
}
})
.catch((error) => {
throw error?.response?.data;
});
}
async instanceAdminSignIn(data: IPasswordSignInData): Promise<ILoginTokenResponse> {
return await this.post("/api/instances/admins/sign-in/", data, { headers: {} })
.then((response) => {
if (response?.status === 200) {
this.setAccessToken(response?.data?.access_token);
this.setRefreshToken(response?.data?.refresh_token);
return response?.data;
}
})
.catch((error) => {
throw error?.response?.data;
});
}
async signOut(): Promise<any> {
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;
});
}
}
@@ -1,144 +0,0 @@
// services
import { APIService } from "services/api.service";
// types
import type {
IUserNotification,
INotificationParams,
NotificationCount,
PaginatedUserNotification,
IMarkAllAsReadPayload,
} from "@plane/types";
// helpers
import { API_BASE_URL } from "helpers/common.helper";
export class NotificationService extends APIService {
constructor() {
super(API_BASE_URL);
}
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;
});
}
}
-212
View File
@@ -1,212 +0,0 @@
// services
import { APIService } from "services/api.service";
// types
import type {
TIssue,
IUser,
IUserActivityResponse,
IInstanceAdminStatus,
IUserProfileData,
IUserProfileProjectSegregation,
IUserSettings,
IUserWorkspaceDashboard,
IUserEmailNotificationSettings,
} from "@plane/types";
// helpers
import { API_BASE_URL } from "helpers/common.helper";
export class UserService extends APIService {
constructor() {
super(API_BASE_URL);
}
currentUserConfig() {
return {
url: `${this.baseURL}/api/users/me/`,
headers: this.getHeaders(),
};
}
async userIssues(
workspaceSlug: string,
params: any
): Promise<
| {
[key: string]: TIssue[];
}
| TIssue[]
> {
return this.get(`/api/workspaces/${workspaceSlug}/my-issues/`, {
params,
})
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async currentUser(): Promise<IUser> {
return this.get("/api/users/me/")
.then((response) => response?.data)
.catch((error) => {
throw error?.response;
});
}
async currentUserInstanceAdminStatus(): Promise<IInstanceAdminStatus> {
return this.get("/api/users/me/instance-admin/")
.then((response) => response?.data)
.catch((error) => {
throw error?.response;
});
}
async currentUserSettings(): Promise<IUserSettings> {
return this.get("/api/users/me/settings/")
.then((response) => response?.data)
.catch((error) => {
throw error?.response;
});
}
async currentUserEmailNotificationSettings(): Promise<IUserEmailNotificationSettings> {
return this.get("/api/users/me/notification-preferences/")
.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(): Promise<any> {
return this.patch("/api/users/me/onboard/", {
is_onboarded: true,
})
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async updateUserTourCompleted(): Promise<any> {
return this.patch("/api/users/me/tour-completed/", {
is_tour_completed: true,
})
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async updateCurrentUserEmailNotificationSettings(data: Partial<IUserEmailNotificationSettings>): Promise<any> {
return this.patch("/api/users/me/notification-preferences/", data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async getUserActivity(): Promise<IUserActivityResponse> {
return this.get(`/api/users/me/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 changePassword(data: { old_password: string; new_password: string; confirm_password: string }): Promise<any> {
return this.post(`/api/users/me/change-password/`, 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<TIssue[]> {
return this.get(`/api/workspaces/${workspaceSlug}/user-issues/${userId}/`, {
params,
})
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async deactivateAccount() {
return this.delete(`/api/users/me/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async leaveWorkspace(workspaceSlug: string) {
return this.post(`/api/workspaces/${workspaceSlug}/members/leave/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async joinProject(workspaceSlug: string, project_ids: string[]): Promise<any> {
return this.post(`/api/users/me/workspaces/${workspaceSlug}/projects/invitations/`, { project_ids })
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async leaveProject(workspaceSlug: string, projectId: string) {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/members/leave/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
}
-81
View File
@@ -1,81 +0,0 @@
import { APIService } from "services/api.service";
// types
import { IProjectView } from "@plane/types";
// helpers
import { API_BASE_URL } from "helpers/common.helper";
export class ViewService extends APIService {
constructor() {
super(API_BASE_URL);
}
async createView(workspaceSlug: string, projectId: string, data: Partial<IProjectView>): Promise<any> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/views/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async patchView(workspaceSlug: string, projectId: string, viewId: string, data: Partial<IProjectView>): Promise<any> {
return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/views/${viewId}/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async deleteView(workspaceSlug: string, projectId: string, viewId: string): Promise<any> {
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/views/${viewId}/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async getViews(workspaceSlug: string, projectId: string): Promise<IProjectView[]> {
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<IProjectView> {
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;
});
}
}
@@ -1,63 +0,0 @@
// services
import { APIService } from "services/api.service";
// helpers
import { API_BASE_URL } from "helpers/common.helper";
export class AppInstallationService extends APIService {
constructor() {
super(API_BASE_URL);
}
async addInstallationApp(workspaceSlug: string, provider: string, data: any): Promise<any> {
return this.post(`/api/workspaces/${workspaceSlug}/workspace-integrations/${provider}/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response;
});
}
async addSlackChannel(
workspaceSlug: string,
projectId: string,
integrationId: string | null | undefined,
data: any
): Promise<any> {
return this.post(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/workspace-integrations/${integrationId}/project-slack-sync/`,
data
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response;
});
}
async getSlackChannelDetail(
workspaceSlug: string,
projectId: string,
integrationId: string | null | undefined
): Promise<any> {
return this.get(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/workspace-integrations/${integrationId}/project-slack-sync/`
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response;
});
}
async removeSlackChannel(
workspaceSlug: string,
projectId: string,
integrationId: string | null | undefined,
slackSyncId: string | undefined
): Promise<any> {
return this.delete(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/workspace-integrations/${integrationId}/project-slack-sync/${slackSyncId}`
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response;
});
}
}
@@ -1,53 +0,0 @@
import { APIService } from "services/api.service";
// helpers
import { API_BASE_URL } from "helpers/common.helper";
// types
import { THomeDashboardResponse, TWidget, TWidgetStatsResponse, TWidgetStatsRequestParams } from "@plane/types";
export class DashboardService extends APIService {
constructor() {
super(API_BASE_URL);
}
async getHomeDashboardWidgets(workspaceSlug: string): Promise<THomeDashboardResponse> {
return this.get(`/api/workspaces/${workspaceSlug}/dashboard/`, {
params: {
dashboard_type: "home",
},
})
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async getWidgetStats(
workspaceSlug: string,
dashboardId: string,
params: TWidgetStatsRequestParams
): Promise<TWidgetStatsResponse> {
return this.get(`/api/workspaces/${workspaceSlug}/dashboard/${dashboardId}/`, {
params,
})
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async getDashboardDetails(dashboardId: string): Promise<TWidgetStatsResponse> {
return this.get(`/api/dashboard/${dashboardId}/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async updateDashboardWidget(dashboardId: string, widgetId: string, data: Partial<TWidget>): Promise<TWidget> {
return this.patch(`/api/dashboard/${dashboardId}/widgets/${widgetId}/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
}
@@ -1,397 +0,0 @@
// services
import { APIService } from "../api.service";
// types
import {
IWorkspace,
IWorkspaceMemberMe,
IWorkspaceMember,
IWorkspaceMemberInvitation,
ILastActiveWorkspaceDetails,
IWorkspaceSearchResults,
IProductUpdateResponse,
IWorkspaceBulkInviteFormData,
IWorkspaceViewProps,
IUserProjectsRole,
TIssue,
IWorkspaceView,
} from "@plane/types";
export interface IWorkspaceService {
list(): Promise<IWorkspace[]>;
retrieve(workspaceSlug: string): Promise<IWorkspace>;
create(data: Partial<IWorkspace>): Promise<IWorkspace>;
update(workspaceSlug: string, data: Partial<IWorkspace>): Promise<IWorkspace>;
delete(workspaceSlug: string): Promise<undefined>;
invite(
workspaceSlug: string,
data: IWorkspaceBulkInviteFormData
): Promise<any>;
join(workspaceSlug: string, invitationId: string, data: any): Promise<any>;
joinMany(data: any): Promise<any>;
getLastActiveWorkspaceAndProjects(): Promise<ILastActiveWorkspaceDetails>;
userWorkspaceInvitations(): Promise<IWorkspaceMemberInvitation[]>;
workspaceMemberMe(workspaceSlug: string): Promise<IWorkspaceMemberMe>;
updateWorkspaceView(
workspaceSlug: string,
data: { view_props: IWorkspaceViewProps }
): Promise<any>;
fetchWorkspaceMembers(workspaceSlug: string): Promise<IWorkspaceMember[]>;
updateWorkspaceMember(
workspaceSlug: string,
memberId: string,
data: Partial<IWorkspaceMember>
): Promise<IWorkspaceMember>;
deleteWorkspaceMember(workspaceSlug: string, memberId: string): Promise<any>;
workspaceInvitations(
workspaceSlug: string
): Promise<IWorkspaceMemberInvitation[]>;
getWorkspaceInvitation(
workspaceSlug: string,
invitationId: string
): Promise<IWorkspaceMemberInvitation>;
updateWorkspaceInvitation(
workspaceSlug: string,
invitationId: string,
data: Partial<IWorkspaceMember>
): Promise<any>;
deleteWorkspaceInvitations(
workspaceSlug: string,
invitationId: string
): Promise<any>;
workspaceSlugCheck(slug: string): Promise<any>;
searchWorkspace(
workspaceSlug: string,
params: { project_id?: string; search: string; workspace_search: boolean }
): Promise<IWorkspaceSearchResults>;
getProductUpdates(): Promise<IProductUpdateResponse[]>;
createView(
workspaceSlug: string,
data: Partial<IWorkspaceView>
): Promise<IWorkspaceView>;
updateView(
workspaceSlug: string,
viewId: string,
data: Partial<IWorkspaceView>
): Promise<IWorkspaceView>;
deleteView(workspaceSlug: string, viewId: string): Promise<any>;
getAllViews(workspaceSlug: string): Promise<IWorkspaceView[]>;
getViewDetails(
workspaceSlug: string,
viewId: string
): Promise<IWorkspaceView>;
getViewIssues(workspaceSlug: string, params: any): Promise<TIssue[]>;
getWorkspaceUserProjectsRole(
workspaceSlug: string
): Promise<IUserProjectsRole>;
}
export class WorkspaceService extends APIService implements IWorkspaceService {
constructor(BASE_URL: string) {
super(BASE_URL);
}
async list(): Promise<IWorkspace[]> {
return this.get("/api/users/me/workspaces/")
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async retrieve(workspaceSlug: string): Promise<IWorkspace> {
return this.get(`/api/workspaces/${workspaceSlug}/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response;
});
}
async create(data: Partial<IWorkspace>): Promise<IWorkspace> {
return this.post("/api/workspaces/", data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async update(
workspaceSlug: string,
data: Partial<IWorkspace>
): Promise<IWorkspace> {
return this.patch(`/api/workspaces/${workspaceSlug}/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async delete(workspaceSlug: string): Promise<any> {
return this.delete(`/api/workspaces/${workspaceSlug}/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async invite(
workspaceSlug: string,
data: IWorkspaceBulkInviteFormData
): Promise<any> {
return this.post(`/api/workspaces/${workspaceSlug}/invitations/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async join(
workspaceSlug: string,
invitationId: string,
data: any
): Promise<any> {
return this.post(
`/api/workspaces/${workspaceSlug}/invitations/${invitationId}/join/`,
data,
{
headers: {},
}
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async joinMany(data: any): Promise<any> {
return this.post("/api/users/me/workspaces/invitations/", 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/workspaces/invitations/")
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async workspaceMemberMe(workspaceSlug: string): Promise<IWorkspaceMemberMe> {
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 fetchWorkspaceMembers(
workspaceSlug: string
): Promise<IWorkspaceMember[]> {
return this.get(`/api/workspaces/${workspaceSlug}/members/`)
.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 getWorkspaceInvitation(
workspaceSlug: string,
invitationId: string
): Promise<IWorkspaceMemberInvitation> {
return this.get(
`/api/workspaces/${workspaceSlug}/invitations/${invitationId}/join/`,
{ headers: {} }
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async updateWorkspaceInvitation(
workspaceSlug: string,
invitationId: string,
data: Partial<IWorkspaceMember>
): Promise<any> {
return this.patch(
`/api/workspaces/${workspaceSlug}/invitations/${invitationId}/`,
data
)
.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;
});
}
async createView(
workspaceSlug: string,
data: Partial<IWorkspaceView>
): Promise<IWorkspaceView> {
return this.post(`/api/workspaces/${workspaceSlug}/views/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async updateView(
workspaceSlug: string,
viewId: string,
data: Partial<IWorkspaceView>
): Promise<IWorkspaceView> {
return this.patch(`/api/workspaces/${workspaceSlug}/views/${viewId}/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async deleteView(workspaceSlug: string, viewId: string): Promise<any> {
return this.delete(`/api/workspaces/${workspaceSlug}/views/${viewId}/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async getAllViews(workspaceSlug: string): Promise<IWorkspaceView[]> {
return this.get(`/api/workspaces/${workspaceSlug}/views/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async getViewDetails(
workspaceSlug: string,
viewId: string
): Promise<IWorkspaceView> {
return this.get(`/api/workspaces/${workspaceSlug}/views/${viewId}/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async getViewIssues(workspaceSlug: string, params: any): Promise<TIssue[]> {
return this.get(`/api/workspaces/${workspaceSlug}/issues/`, {
params,
})
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async getWorkspaceUserProjectsRole(
workspaceSlug: string
): Promise<IUserProjectsRole> {
return this.get(`/api/users/me/workspaces/${workspaceSlug}/project-roles/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
}
+2 -2
View File
@@ -1,9 +1,9 @@
import { IProjectLite, IWorkspaceLite } from "@plane/types";
export type GptApiResponse = {
export interface IGptResponse {
response: string;
response_html: string;
count: number;
project_detail: IProjectLite;
workspace_detail: IWorkspaceLite;
};
}
+2 -1
View File
@@ -13,9 +13,10 @@ export type TWidgetKeys =
| "recent_projects"
| "recent_collaborators";
export type TIssuesListTypes = "upcoming" | "overdue" | "completed";
export type TIssuesListTypes = "pending" | "upcoming" | "overdue" | "completed";
export type TDurationFilterOptions =
| "none"
| "today"
| "this_week"
| "this_month"
-1
View File
@@ -47,7 +47,6 @@ export const PriorityIcon: React.FC<IPriorityIcon> = (props) => {
>
<Icon
size={size}
viewBox="0 0 23.5 24"
className={cn(
{
"text-white": priority === "urgent",
@@ -1,7 +1,7 @@
import { Command } from "cmdk";
import { ContrastIcon, FileText } from "lucide-react";
// hooks
import { useApplication } from "hooks/store";
import { useApplication, useEventTracker } from "hooks/store";
// ui
import { DiceIcon, PhotoFilterIcon } from "@plane/ui";
@@ -14,8 +14,8 @@ export const CommandPaletteProjectActions: React.FC<Props> = (props) => {
const {
commandPalette: { toggleCreateCycleModal, toggleCreateModuleModal, toggleCreatePageModal, toggleCreateViewModal },
eventTracker: { setTrackElement },
} = useApplication();
const { setTrackElement } = useEventTracker();
return (
<>
@@ -23,7 +23,7 @@ export const CommandPaletteProjectActions: React.FC<Props> = (props) => {
<Command.Item
onSelect={() => {
closePalette();
setTrackElement("COMMAND_PALETTE");
setTrackElement("Command palette");
toggleCreateCycleModal(true);
}}
className="focus:outline-none"
@@ -39,6 +39,7 @@ export const CommandPaletteProjectActions: React.FC<Props> = (props) => {
<Command.Item
onSelect={() => {
closePalette();
setTrackElement("Command palette");
toggleCreateModuleModal(true);
}}
className="focus:outline-none"
@@ -54,6 +55,7 @@ export const CommandPaletteProjectActions: React.FC<Props> = (props) => {
<Command.Item
onSelect={() => {
closePalette();
setTrackElement("Command palette");
toggleCreateViewModal(true);
}}
className="focus:outline-none"
@@ -69,6 +71,7 @@ export const CommandPaletteProjectActions: React.FC<Props> = (props) => {
<Command.Item
onSelect={() => {
closePalette();
setTrackElement("Command palette");
toggleCreatePageModal(true);
}}
className="focus:outline-none"
@@ -6,7 +6,7 @@ import { Dialog, Transition } from "@headlessui/react";
import { observer } from "mobx-react-lite";
import { FolderPlus, Search, Settings } from "lucide-react";
// hooks
import { useApplication, useProject } from "hooks/store";
import { useApplication, useEventTracker, useProject } from "hooks/store";
// services
import { WorkspaceService } from "services/workspace.service";
import { IssueService } from "services/issue";
@@ -64,8 +64,8 @@ export const CommandModal: React.FC = observer(() => {
toggleCreateIssueModal,
toggleCreateProjectModal,
},
eventTracker: { setTrackElement },
} = useApplication();
const { setTrackElement } = useEventTracker();
// router
const router = useRouter();
@@ -278,7 +278,7 @@ export const CommandModal: React.FC = observer(() => {
<Command.Item
onSelect={() => {
closePalette();
setTrackElement("COMMAND_PALETTE");
setTrackElement("Command Palette");
toggleCreateIssueModal(true);
}}
className="focus:bg-custom-background-80"
@@ -296,7 +296,7 @@ export const CommandModal: React.FC = observer(() => {
<Command.Item
onSelect={() => {
closePalette();
setTrackElement("COMMAND_PALETTE");
setTrackElement("Command palette");
toggleCreateProjectModal(true);
}}
className="focus:outline-none"
@@ -3,7 +3,7 @@ import { useRouter } from "next/router";
import useSWR from "swr";
import { observer } from "mobx-react-lite";
// hooks
import { useApplication, useIssues, useUser } from "hooks/store";
import { useApplication, useEventTracker, useIssues, useUser } from "hooks/store";
import useToast from "hooks/use-toast";
// components
import { CommandModal, ShortcutsModal } from "components/command-palette";
@@ -32,8 +32,8 @@ export const CommandPalette: FC = observer(() => {
const {
commandPalette,
theme: { toggleSidebar },
eventTracker: { setTrackElement },
} = useApplication();
const { setTrackElement } = useEventTracker();
const { currentUser } = useUser();
const {
issues: { removeIssue },
@@ -118,11 +118,10 @@ export const CommandPalette: FC = observer(() => {
toggleSidebar();
}
} else if (!isAnyModalOpen) {
setTrackElement("Shortcut key");
if (keyPressed === "c") {
setTrackElement("SHORTCUT_KEY");
toggleCreateIssueModal(true);
} else if (keyPressed === "p") {
setTrackElement("SHORTCUT_KEY");
toggleCreateProjectModal(true);
} else if (keyPressed === "h") {
toggleShortcutModal(true);
+12 -7
View File
@@ -86,12 +86,15 @@ const ProgressChart: React.FC<Props> = ({ distribution, startDate, endDate, tota
{
id: "pending",
color: "#3F76FF",
data: chartData.map((item, index) => ({
index,
x: item.currentDate,
y: item.pending,
color: "#3F76FF",
})),
data:
chartData.length > 0
? chartData.map((item, index) => ({
index,
x: item.currentDate,
y: item.pending,
color: "#3F76FF",
}))
: [],
enableArea: true,
},
{
@@ -121,7 +124,9 @@ const ProgressChart: React.FC<Props> = ({ distribution, startDate, endDate, tota
enableArea
colors={(datum) => datum.color ?? "#3F76FF"}
customYAxisTickValues={[0, totalIssues]}
gridXValues={chartData.map((item, index) => (index % 2 === 0 ? item.currentDate : ""))}
gridXValues={
chartData.length > 0 ? chartData.map((item, index) => (index % 2 === 0 ? item.currentDate : "")) : undefined
}
enableSlices="x"
sliceTooltip={(datum) => (
<div className="rounded-md border border-custom-border-200 bg-custom-background-80 p-2 text-xs">
@@ -125,7 +125,7 @@ export const SidebarProgressStats: React.FC<Props> = ({
</Tab>
</Tab.List>
<Tab.Panels className="flex w-full items-center justify-between text-custom-text-200">
<Tab.Panel as="div" className="flex h-44 w-full flex-col gap-1.5 overflow-y-auto pt-3.5">
<Tab.Panel as="div" className="flex min-h-44 w-full flex-col gap-1.5 overflow-y-auto pt-3.5">
{distribution?.assignees.length > 0 ? (
distribution.assignees.map((assignee, index) => {
if (assignee.assignee_id)
+4 -5
View File
@@ -2,7 +2,7 @@ import { FC, MouseEvent, useState } from "react";
import { useRouter } from "next/router";
import Link from "next/link";
// hooks
import { useApplication, useCycle, useUser } from "hooks/store";
import { useEventTracker, useCycle, useUser } from "hooks/store";
import useToast from "hooks/use-toast";
// components
import { CycleCreateUpdateModal, CycleDeleteModal } from "components/cycles";
@@ -33,9 +33,7 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = (props) => {
// router
const router = useRouter();
// store
const {
eventTracker: { setTrackElement },
} = useApplication();
const { setTrackElement } = useEventTracker();
const {
membership: { currentProjectRole },
} = useUser();
@@ -117,14 +115,15 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = (props) => {
const handleEditCycle = (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
e.stopPropagation();
setTrackElement("Cycles page board layout");
setUpdateModal(true);
};
const handleDeleteCycle = (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
e.stopPropagation();
setTrackElement("Cycles page board layout");
setDeleteModal(true);
setTrackElement("CYCLE_PAGE_BOARD_LAYOUT");
};
const openCycleOverview = (e: MouseEvent<HTMLButtonElement>) => {
+4 -5
View File
@@ -2,7 +2,7 @@ import { FC, MouseEvent, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
// hooks
import { useApplication, useCycle, useUser } from "hooks/store";
import { useEventTracker, useCycle, useUser } from "hooks/store";
import useToast from "hooks/use-toast";
// components
import { CycleCreateUpdateModal, CycleDeleteModal } from "components/cycles";
@@ -37,9 +37,7 @@ export const CyclesListItem: FC<TCyclesListItem> = (props) => {
// router
const router = useRouter();
// store hooks
const {
eventTracker: { setTrackElement },
} = useApplication();
const { setTrackElement } = useEventTracker();
const {
membership: { currentProjectRole },
} = useUser();
@@ -90,14 +88,15 @@ export const CyclesListItem: FC<TCyclesListItem> = (props) => {
const handleEditCycle = (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
e.stopPropagation();
setTrackElement("Cycles page list layout");
setUpdateModal(true);
};
const handleDeleteCycle = (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
e.stopPropagation();
setTrackElement("Cycles page list layout");
setDeleteModal(true);
setTrackElement("CYCLE_PAGE_LIST_LAYOUT");
};
const openCycleOverview = (e: MouseEvent<HTMLButtonElement>) => {
+8 -8
View File
@@ -4,7 +4,7 @@ import { Dialog, Transition } from "@headlessui/react";
import { observer } from "mobx-react-lite";
import { AlertTriangle } from "lucide-react";
// hooks
import { useApplication, useCycle } from "hooks/store";
import { useEventTracker, useCycle } from "hooks/store";
import useToast from "hooks/use-toast";
// components
import { Button } from "@plane/ui";
@@ -27,9 +27,7 @@ export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
const router = useRouter();
const { cycleId, peekCycle } = router.query;
// store hooks
const {
eventTracker: { postHogEventTracker },
} = useApplication();
const { captureCycleEvent } = useEventTracker();
const { deleteCycle } = useCycle();
// toast alert
const { setToastAlert } = useToast();
@@ -46,13 +44,15 @@ export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
title: "Success!",
message: "Cycle deleted successfully.",
});
postHogEventTracker("CYCLE_DELETE", {
state: "SUCCESS",
captureCycleEvent({
eventName: "Cycle deleted",
payload: { ...cycle, state: "SUCCESS" },
});
})
.catch(() => {
postHogEventTracker("CYCLE_DELETE", {
state: "FAILED",
captureCycleEvent({
eventName: "Cycle deleted",
payload: { ...cycle, state: "FAILED" },
});
});
+8 -9
View File
@@ -3,7 +3,7 @@ import { Dialog, Transition } from "@headlessui/react";
// services
import { CycleService } from "services/cycle.service";
// hooks
import { useApplication, useCycle, useProject } from "hooks/store";
import { useEventTracker, useCycle, useProject } from "hooks/store";
import useToast from "hooks/use-toast";
import useLocalStorage from "hooks/use-local-storage";
// components
@@ -27,9 +27,7 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
// states
const [activeProject, setActiveProject] = useState<string | null>(null);
// store hooks
const {
eventTracker: { postHogEventTracker },
} = useApplication();
const { captureCycleEvent } = useEventTracker();
const { workspaceProjectIds } = useProject();
const { createCycle, updateCycleDetails } = useCycle();
// toast alert
@@ -48,9 +46,9 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
title: "Success!",
message: "Cycle created successfully.",
});
postHogEventTracker("CYCLE_CREATE", {
...res,
state: "SUCCESS",
captureCycleEvent({
eventName: "Cycle created",
payload: { ...res, state: "SUCCESS" },
});
})
.catch((err) => {
@@ -59,8 +57,9 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
title: "Error!",
message: err.detail ?? "Error in creating cycle. Please try again.",
});
postHogEventTracker("CYCLE_CREATE", {
state: "FAILED",
captureCycleEvent({
eventName: "Cycle created",
payload: { ...payload, state: "FAILED" },
});
});
};
+14 -22
View File
@@ -6,7 +6,7 @@ import { Disclosure, Popover, Transition } from "@headlessui/react";
// services
import { CycleService } from "services/cycle.service";
// hooks
import { useApplication, useCycle, useMember, useUser } from "hooks/store";
import { useEventTracker, useCycle, useUser, useMember } from "hooks/store";
import useToast from "hooks/use-toast";
// components
import { SidebarProgressStats } from "components/core";
@@ -66,9 +66,7 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
const router = useRouter();
const { workspaceSlug, projectId, peekCycle } = router.query;
// store hooks
const {
eventTracker: { setTrackElement },
} = useApplication();
const { setTrackElement } = useEventTracker();
const {
membership: { currentProjectRole },
} = useUser();
@@ -319,13 +317,7 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
const currentCycle = CYCLE_STATUS.find((status) => status.value === cycleStatus);
const issueCount =
cycleDetails.total_issues === 0
? "0 Issue"
: cycleDetails.total_issues === cycleDetails.completed_issues
? cycleDetails.total_issues > 1
? `${cycleDetails.total_issues}`
: `${cycleDetails.total_issues}`
: `${cycleDetails.completed_issues}/${cycleDetails.total_issues}`;
cycleDetails.total_issues === 0 ? "0 Issue" : `${cycleDetails.completed_issues}/${cycleDetails.total_issues}`;
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
@@ -575,7 +567,9 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
<Transition show={open}>
<Disclosure.Panel>
<div className="flex flex-col gap-3">
{isStartValid && isEndValid ? (
{cycleDetails.distribution?.completion_chart &&
cycleDetails.start_date &&
cycleDetails.end_date ? (
<div className="h-full w-full pt-4">
<div className="flex items-start gap-4 py-2 text-xs">
<div className="flex items-center gap-3 text-custom-text-100">
@@ -589,16 +583,14 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
</div>
</div>
</div>
{cycleDetails && cycleDetails.distribution && (
<div className="relative h-40 w-80">
<ProgressChart
distribution={cycleDetails.distribution?.completion_chart ?? {}}
startDate={cycleDetails.start_date ?? ""}
endDate={cycleDetails.end_date ?? ""}
totalIssues={cycleDetails.total_issues}
/>
</div>
)}
<div className="relative h-40 w-80">
<ProgressChart
distribution={cycleDetails.distribution?.completion_chart}
startDate={cycleDetails.start_date}
endDate={cycleDetails.end_date}
totalIssues={cycleDetails.total_issues}
/>
</div>
</div>
) : (
""
@@ -1,7 +1,7 @@
import Image from "next/image";
import { observer } from "mobx-react-lite";
// hooks
import { useApplication, useUser } from "hooks/store";
import { useApplication, useEventTracker, useUser } from "hooks/store";
// ui
import { Button } from "@plane/ui";
// assets
@@ -14,6 +14,7 @@ export const DashboardProjectEmptyState = observer(() => {
const {
commandPalette: { toggleCreateProjectModal },
} = useApplication();
const { setTrackElement } = useEventTracker();
const {
membership: { currentWorkspaceRole },
} = useUser();
@@ -31,7 +32,13 @@ export const DashboardProjectEmptyState = observer(() => {
<Image src={ProjectEmptyStateImage} className="w-full" alt="Project empty state" />
{canCreateProject && (
<div className="flex justify-center">
<Button variant="primary" onClick={() => toggleCreateProjectModal(true)}>
<Button
variant="primary"
onClick={() => {
setTrackElement("Project empty state");
toggleCreateProjectModal(true);
}}
>
Build your first project
</Button>
</div>
@@ -17,7 +17,7 @@ import { getCustomDates, getRedirectionFilters } from "helpers/dashboard.helper"
// types
import { TAssignedIssuesWidgetFilters, TAssignedIssuesWidgetResponse } from "@plane/types";
// constants
import { ISSUES_TABS_LIST } from "constants/dashboard";
import { FILTERED_ISSUES_TABS_LIST, UNFILTERED_ISSUES_TABS_LIST } from "constants/dashboard";
const WIDGET_KEY = "assigned_issues";
@@ -30,6 +30,8 @@ export const AssignedIssuesWidget: React.FC<WidgetProps> = observer((props) => {
// derived values
const widgetDetails = getWidgetDetails(workspaceSlug, dashboardId, WIDGET_KEY);
const widgetStats = getWidgetStats<TAssignedIssuesWidgetResponse>(workspaceSlug, dashboardId, WIDGET_KEY);
const selectedTab = widgetDetails?.widget_filters.tab ?? "pending";
const selectedDurationFilter = widgetDetails?.widget_filters.target_date ?? "none";
const handleUpdateFilters = async (filters: Partial<TAssignedIssuesWidgetFilters>) => {
if (!widgetDetails) return;
@@ -41,68 +43,79 @@ export const AssignedIssuesWidget: React.FC<WidgetProps> = observer((props) => {
filters,
});
const filterDates = getCustomDates(filters.target_date ?? selectedDurationFilter);
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
issue_type: filters.tab ?? widgetDetails.widget_filters.tab ?? "upcoming",
target_date: getCustomDates(filters.target_date ?? widgetDetails.widget_filters.target_date ?? "this_week"),
issue_type: filters.tab ?? selectedTab,
...(filterDates.trim() !== "" ? { target_date: filterDates } : {}),
expand: "issue_relation",
}).finally(() => setFetching(false));
};
useEffect(() => {
const filterDates = getCustomDates(widgetDetails?.widget_filters.target_date ?? "this_week");
const filterDates = getCustomDates(selectedDurationFilter);
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
issue_type: widgetDetails?.widget_filters.tab ?? "upcoming",
target_date: filterDates,
issue_type: selectedTab,
...(filterDates.trim() !== "" ? { target_date: filterDates } : {}),
expand: "issue_relation",
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const filterParams = getRedirectionFilters(widgetDetails?.widget_filters.tab ?? "upcoming");
const filterParams = getRedirectionFilters(selectedTab);
const tabsList = selectedDurationFilter === "none" ? UNFILTERED_ISSUES_TABS_LIST : FILTERED_ISSUES_TABS_LIST;
if (!widgetDetails || !widgetStats) return <WidgetLoader widgetKey={WIDGET_KEY} />;
return (
<div className="bg-custom-background-100 rounded-xl border-[0.5px] border-custom-border-200 w-full hover:shadow-custom-shadow-4xl duration-300 flex flex-col min-h-96">
<div className="flex items-start justify-between gap-2 p-6 pl-7">
<div>
<Link
href={`/${workspaceSlug}/workspace-views/assigned/${filterParams}`}
className="text-lg font-semibold text-custom-text-300 hover:underline"
>
Assigned to you
</Link>
<p className="mt-3 text-xs font-medium text-custom-text-300">
Filtered by{" "}
<span className="border-[0.5px] border-custom-border-300 rounded py-1 px-2 ml-0.5">Due date</span>
</p>
</div>
<div className="flex items-center justify-between gap-2 p-6 pl-7">
<Link
href={`/${workspaceSlug}/workspace-views/assigned/${filterParams}`}
className="text-lg font-semibold text-custom-text-300 hover:underline"
>
Assigned to you
</Link>
<DurationFilterDropdown
value={widgetDetails.widget_filters.target_date ?? "this_week"}
onChange={(val) =>
handleUpdateFilters({
target_date: val,
})
}
value={selectedDurationFilter}
onChange={(val) => {
if (val === selectedDurationFilter) return;
// switch to pending tab if target date is changed to none
if (val === "none" && selectedTab !== "completed") {
handleUpdateFilters({ target_date: val, tab: "pending" });
return;
}
// switch to upcoming tab if target date is changed to other than none
if (val !== "none" && selectedDurationFilter === "none" && selectedTab !== "completed") {
handleUpdateFilters({
target_date: val,
tab: "upcoming",
});
return;
}
handleUpdateFilters({ target_date: val });
}}
/>
</div>
<Tab.Group
as="div"
defaultIndex={ISSUES_TABS_LIST.findIndex((t) => t.key === widgetDetails.widget_filters.tab ?? "upcoming")}
selectedIndex={tabsList.findIndex((tab) => tab.key === selectedTab)}
onChange={(i) => {
const selectedTab = ISSUES_TABS_LIST[i];
handleUpdateFilters({ tab: selectedTab.key ?? "upcoming" });
const selectedTab = tabsList[i];
handleUpdateFilters({ tab: selectedTab?.key ?? "pending" });
}}
className="h-full flex flex-col"
>
<div className="px-6">
<TabsList />
<TabsList durationFilter={selectedDurationFilter} selectedTab={selectedTab} />
</div>
<Tab.Panels as="div" className="h-full">
{ISSUES_TABS_LIST.map((tab) => (
{tabsList.map((tab) => (
<Tab.Panel key={tab.key} as="div" className="h-full flex flex-col">
<WidgetIssuesList
issues={widgetStats.issues}
@@ -17,7 +17,7 @@ import { getCustomDates, getRedirectionFilters } from "helpers/dashboard.helper"
// types
import { TCreatedIssuesWidgetFilters, TCreatedIssuesWidgetResponse } from "@plane/types";
// constants
import { ISSUES_TABS_LIST } from "constants/dashboard";
import { FILTERED_ISSUES_TABS_LIST, UNFILTERED_ISSUES_TABS_LIST } from "constants/dashboard";
const WIDGET_KEY = "created_issues";
@@ -30,6 +30,8 @@ export const CreatedIssuesWidget: React.FC<WidgetProps> = observer((props) => {
// derived values
const widgetDetails = getWidgetDetails(workspaceSlug, dashboardId, WIDGET_KEY);
const widgetStats = getWidgetStats<TCreatedIssuesWidgetResponse>(workspaceSlug, dashboardId, WIDGET_KEY);
const selectedTab = widgetDetails?.widget_filters.tab ?? "pending";
const selectedDurationFilter = widgetDetails?.widget_filters.target_date ?? "none";
const handleUpdateFilters = async (filters: Partial<TCreatedIssuesWidgetFilters>) => {
if (!widgetDetails) return;
@@ -41,64 +43,76 @@ export const CreatedIssuesWidget: React.FC<WidgetProps> = observer((props) => {
filters,
});
const filterDates = getCustomDates(filters.target_date ?? selectedDurationFilter);
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
issue_type: filters.tab ?? widgetDetails.widget_filters.tab ?? "upcoming",
target_date: getCustomDates(filters.target_date ?? widgetDetails.widget_filters.target_date ?? "this_week"),
issue_type: filters.tab ?? selectedTab,
...(filterDates.trim() !== "" ? { target_date: filterDates } : {}),
}).finally(() => setFetching(false));
};
useEffect(() => {
const filterDates = getCustomDates(selectedDurationFilter);
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
issue_type: widgetDetails?.widget_filters.tab ?? "upcoming",
target_date: getCustomDates(widgetDetails?.widget_filters.target_date ?? "this_week"),
issue_type: selectedTab,
...(filterDates.trim() !== "" ? { target_date: filterDates } : {}),
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const filterParams = getRedirectionFilters(widgetDetails?.widget_filters.tab ?? "upcoming");
const filterParams = getRedirectionFilters(selectedTab);
const tabsList = selectedDurationFilter === "none" ? UNFILTERED_ISSUES_TABS_LIST : FILTERED_ISSUES_TABS_LIST;
if (!widgetDetails || !widgetStats) return <WidgetLoader widgetKey={WIDGET_KEY} />;
return (
<div className="bg-custom-background-100 rounded-xl border-[0.5px] border-custom-border-200 w-full hover:shadow-custom-shadow-4xl duration-300 flex flex-col min-h-96">
<div className="flex items-start justify-between gap-2 p-6 pl-7">
<div>
<Link
href={`/${workspaceSlug}/workspace-views/created/${filterParams}`}
className="text-lg font-semibold text-custom-text-300 hover:underline"
>
Created by you
</Link>
<p className="mt-3 text-xs font-medium text-custom-text-300">
Filtered by{" "}
<span className="border-[0.5px] border-custom-border-300 rounded py-1 px-2 ml-0.5">Due date</span>
</p>
</div>
<div className="flex items-center justify-between gap-2 p-6 pl-7">
<Link
href={`/${workspaceSlug}/workspace-views/created/${filterParams}`}
className="text-lg font-semibold text-custom-text-300 hover:underline"
>
Created by you
</Link>
<DurationFilterDropdown
value={widgetDetails.widget_filters.target_date ?? "this_week"}
onChange={(val) =>
handleUpdateFilters({
target_date: val,
})
}
value={selectedDurationFilter}
onChange={(val) => {
if (val === selectedDurationFilter) return;
// switch to pending tab if target date is changed to none
if (val === "none" && selectedTab !== "completed") {
handleUpdateFilters({ target_date: val, tab: "pending" });
return;
}
// switch to upcoming tab if target date is changed to other than none
if (val !== "none" && selectedDurationFilter === "none" && selectedTab !== "completed") {
handleUpdateFilters({
target_date: val,
tab: "upcoming",
});
return;
}
handleUpdateFilters({ target_date: val });
}}
/>
</div>
<Tab.Group
as="div"
defaultIndex={ISSUES_TABS_LIST.findIndex((t) => t.key === widgetDetails.widget_filters.tab ?? "upcoming")}
selectedIndex={tabsList.findIndex((tab) => tab.key === selectedTab)}
onChange={(i) => {
const selectedTab = ISSUES_TABS_LIST[i];
handleUpdateFilters({ tab: selectedTab.key ?? "upcoming" });
const selectedTab = tabsList[i];
handleUpdateFilters({ tab: selectedTab.key ?? "pending" });
}}
className="h-full flex flex-col"
>
<div className="px-6">
<TabsList />
<TabsList durationFilter={selectedDurationFilter} selectedTab={selectedTab} />
</div>
<Tab.Panels as="div" className="h-full">
{ISSUES_TABS_LIST.map((tab) => (
{tabsList.map((tab) => (
<Tab.Panel key={tab.key} as="div" className="h-full flex flex-col">
<WidgetIssuesList
issues={widgetStats.issues}
@@ -14,7 +14,7 @@ import {
IssueListItemProps,
} from "components/dashboard/widgets";
// ui
import { Loader, getButtonStyling } from "@plane/ui";
import { getButtonStyling } from "@plane/ui";
// helpers
import { cn } from "helpers/common.helper";
import { getRedirectionFilters } from "helpers/dashboard.helper";
@@ -41,16 +41,18 @@ export const WidgetIssuesList: React.FC<WidgetIssuesListProps> = (props) => {
const filterParams = getRedirectionFilters(tab);
const ISSUE_LIST_ITEM: {
[key in string]: {
[key: string]: {
[key in TIssuesListTypes]: React.FC<IssueListItemProps>;
};
} = {
assigned: {
pending: AssignedUpcomingIssueListItem,
upcoming: AssignedUpcomingIssueListItem,
overdue: AssignedOverdueIssueListItem,
completed: AssignedCompletedIssueListItem,
},
created: {
pending: CreatedUpcomingIssueListItem,
upcoming: CreatedUpcomingIssueListItem,
overdue: CreatedOverdueIssueListItem,
completed: CreatedCompletedIssueListItem,
@@ -61,12 +63,7 @@ export const WidgetIssuesList: React.FC<WidgetIssuesListProps> = (props) => {
<>
<div className="h-full">
{isLoading ? (
<Loader className="mt-7 mx-6 space-y-4">
<Loader.Item height="25px" />
<Loader.Item height="25px" />
<Loader.Item height="25px" />
<Loader.Item height="25px" />
</Loader>
<></>
) : issues.length > 0 ? (
<>
<div className="mt-7 mx-6 border-b-[0.5px] border-custom-border-200 grid grid-cols-6 gap-1 text-xs text-custom-text-300 pb-1">
@@ -81,7 +78,7 @@ export const WidgetIssuesList: React.FC<WidgetIssuesListProps> = (props) => {
{totalIssues}
</span>
</h6>
{tab === "upcoming" && <h6 className="text-center">Due date</h6>}
{["upcoming", "pending"].includes(tab) && <h6 className="text-center">Due date</h6>}
{tab === "overdue" && <h6 className="text-center">Due by</h6>}
{type === "assigned" && tab !== "completed" && <h6 className="text-center">Blocked by</h6>}
{type === "created" && <h6 className="text-center">Assigned to</h6>}
@@ -1,26 +1,63 @@
import { observer } from "mobx-react";
import { Tab } from "@headlessui/react";
// helpers
import { cn } from "helpers/common.helper";
// types
import { TDurationFilterOptions, TIssuesListTypes } from "@plane/types";
// constants
import { ISSUES_TABS_LIST } from "constants/dashboard";
import { FILTERED_ISSUES_TABS_LIST, UNFILTERED_ISSUES_TABS_LIST } from "constants/dashboard";
export const TabsList = () => (
<Tab.List
as="div"
className="border-[0.5px] border-custom-border-200 rounded grid grid-cols-3 bg-custom-background-80"
>
{ISSUES_TABS_LIST.map((tab) => (
<Tab
key={tab.key}
className={({ selected }) =>
cn("font-semibold text-xs rounded py-1.5 focus:outline-none", {
"bg-custom-background-100 text-custom-text-300 shadow-[2px_0_8px_rgba(167,169,174,0.15)]": selected,
"text-custom-text-400": !selected,
})
}
>
{tab.label}
</Tab>
))}
</Tab.List>
);
type Props = {
durationFilter: TDurationFilterOptions;
selectedTab: TIssuesListTypes;
};
export const TabsList: React.FC<Props> = observer((props) => {
const { durationFilter, selectedTab } = props;
const tabsList = durationFilter === "none" ? UNFILTERED_ISSUES_TABS_LIST : FILTERED_ISSUES_TABS_LIST;
const selectedTabIndex = tabsList.findIndex((tab) => tab.key === (selectedTab ?? "pending"));
return (
<Tab.List
as="div"
className="relative border-[0.5px] border-custom-border-200 rounded bg-custom-background-80 grid"
style={{
gridTemplateColumns: `repeat(${tabsList.length}, 1fr)`,
}}
>
<div
className={cn("absolute bg-custom-background-100 rounded transition-all duration-500 ease-in-out", {
// right shadow
"shadow-[2px_0_8px_rgba(167,169,174,0.15)]": selectedTabIndex !== tabsList.length - 1,
// left shadow
"shadow-[-2px_0_8px_rgba(167,169,174,0.15)]": selectedTabIndex !== 0,
})}
style={{
height: "calc(100% - 1px)",
width: `${100 / tabsList.length}%`,
transform: `translateX(${selectedTabIndex * 100}%)`,
}}
/>
{tabsList.map((tab) => (
<Tab
key={tab.key}
className={cn(
"relative z-[1] font-semibold text-xs rounded py-1.5 text-custom-text-400 focus:outline-none",
"transition duration-500",
{
"text-custom-text-100 bg-custom-background-100": selectedTab === tab.key,
"hover:text-custom-text-300": selectedTab !== tab.key,
// // right shadow
// "shadow-[2px_0_8px_rgba(167,169,174,0.15)]": selectedTabIndex !== tabsList.length - 1,
// // left shadow
// "shadow-[-2px_0_8px_rgba(167,169,174,0.15)]": selectedTabIndex !== 0,
}
)}
>
<span className="scale-110">{tab.label}</span>
</Tab>
))}
</Tab.List>
);
});
@@ -84,16 +84,18 @@ export const IssuesByPriorityWidget: React.FC<WidgetProps> = observer((props) =>
filters,
});
const filterDates = getCustomDates(filters.target_date ?? widgetDetails.widget_filters.target_date ?? "none");
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
target_date: getCustomDates(filters.target_date ?? widgetDetails.widget_filters.target_date ?? "this_week"),
...(filterDates.trim() !== "" ? { target_date: filterDates } : {}),
});
};
useEffect(() => {
const filterDates = getCustomDates(widgetDetails?.widget_filters.target_date ?? "none");
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
target_date: getCustomDates(widgetDetails?.widget_filters.target_date ?? "this_week"),
...(filterDates.trim() !== "" ? { target_date: filterDates } : {}),
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -129,21 +131,15 @@ export const IssuesByPriorityWidget: React.FC<WidgetProps> = observer((props) =>
return (
<div className="bg-custom-background-100 rounded-xl border-[0.5px] border-custom-border-200 w-full py-6 hover:shadow-custom-shadow-4xl duration-300 overflow-hidden min-h-96 flex flex-col">
<div className="flex items-start justify-between gap-2 pl-7 pr-6">
<div>
<Link
href={`/${workspaceSlug}/workspace-views/assigned`}
className="text-lg font-semibold text-custom-text-300 hover:underline"
>
Assigned by priority
</Link>
<p className="mt-3 text-xs font-medium text-custom-text-300">
Filtered by{" "}
<span className="border-[0.5px] border-custom-border-300 rounded py-1 px-2 ml-0.5">Due date</span>
</p>
</div>
<div className="flex items-center justify-between gap-2 pl-7 pr-6">
<Link
href={`/${workspaceSlug}/workspace-views/assigned`}
className="text-lg font-semibold text-custom-text-300 hover:underline"
>
Assigned by priority
</Link>
<DurationFilterDropdown
value={widgetDetails.widget_filters.target_date ?? "this_week"}
value={widgetDetails.widget_filters.target_date ?? "none"}
onChange={(val) =>
handleUpdateFilters({
target_date: val,
@@ -43,17 +43,19 @@ export const IssuesByStateGroupWidget: React.FC<WidgetProps> = observer((props)
filters,
});
const filterDates = getCustomDates(filters.target_date ?? widgetDetails.widget_filters.target_date ?? "none");
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
target_date: getCustomDates(filters.target_date ?? widgetDetails.widget_filters.target_date ?? "this_week"),
...(filterDates.trim() !== "" ? { target_date: filterDates } : {}),
});
};
// fetch widget stats
useEffect(() => {
const filterDates = getCustomDates(widgetDetails?.widget_filters.target_date ?? "none");
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
target_date: getCustomDates(widgetDetails?.widget_filters.target_date ?? "this_week"),
...(filterDates.trim() !== "" ? { target_date: filterDates } : {}),
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -72,14 +74,14 @@ export const IssuesByStateGroupWidget: React.FC<WidgetProps> = observer((props)
startedCount > 0
? "started"
: unStartedCount > 0
? "unstarted"
: backlogCount > 0
? "backlog"
: completedCount > 0
? "completed"
: canceledCount > 0
? "cancelled"
: null;
? "unstarted"
: backlogCount > 0
? "backlog"
: completedCount > 0
? "completed"
: canceledCount > 0
? "cancelled"
: null;
setActiveStateGroup(stateGroup);
setDefaultStateGroup(stateGroup);
@@ -128,21 +130,15 @@ export const IssuesByStateGroupWidget: React.FC<WidgetProps> = observer((props)
return (
<div className="bg-custom-background-100 rounded-xl border-[0.5px] border-custom-border-200 w-full py-6 hover:shadow-custom-shadow-4xl duration-300 overflow-hidden min-h-96 flex flex-col">
<div className="flex items-start justify-between gap-2 pl-7 pr-6">
<div>
<Link
href={`/${workspaceSlug}/workspace-views/assigned`}
className="text-lg font-semibold text-custom-text-300 hover:underline"
>
Assigned by state
</Link>
<p className="mt-3 text-xs font-medium text-custom-text-300">
Filtered by{" "}
<span className="border-[0.5px] border-custom-border-300 rounded py-1 px-2 ml-0.5">Due date</span>
</p>
</div>
<div className="flex items-center justify-between gap-2 pl-7 pr-6">
<Link
href={`/${workspaceSlug}/workspace-views/assigned`}
className="text-lg font-semibold text-custom-text-300 hover:underline"
>
Assigned by state
</Link>
<DurationFilterDropdown
value={widgetDetails.widget_filters.target_date ?? "this_week"}
value={widgetDetails.widget_filters.target_date ?? "none"}
onChange={(val) =>
handleUpdateFilters({
target_date: val,
@@ -3,7 +3,7 @@ import Link from "next/link";
import { observer } from "mobx-react-lite";
import { Plus } from "lucide-react";
// hooks
import { useApplication, useDashboard, useProject, useUser } from "hooks/store";
import { useApplication, useEventTracker, useDashboard, useProject, useUser } from "hooks/store";
// components
import { WidgetLoader, WidgetProps } from "components/dashboard/widgets";
// ui
@@ -72,6 +72,7 @@ export const RecentProjectsWidget: React.FC<WidgetProps> = observer((props) => {
const {
commandPalette: { toggleCreateProjectModal },
} = useApplication();
const { setTrackElement } = useEventTracker();
const {
membership: { currentWorkspaceRole },
} = useUser();
@@ -105,6 +106,7 @@ export const RecentProjectsWidget: React.FC<WidgetProps> = observer((props) => {
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setTrackElement("Sidebar");
toggleCreateProjectModal(true);
}}
>
+1
View File
@@ -105,6 +105,7 @@ export const DateDropdown: React.FC<Props> = (props) => {
tabIndex={tabIndex}
className={cn("h-full", className)}
onKeyDown={handleKeyDown}
disabled={disabled}
>
<Combobox.Button as={React.Fragment}>
<button
+3 -2
View File
@@ -5,6 +5,7 @@ import Link from "next/link";
// hooks
import {
useApplication,
useEventTracker,
useCycle,
useLabel,
useMember,
@@ -70,8 +71,8 @@ export const CycleIssuesHeader: React.FC = observer(() => {
const { currentProjectCycleIds, getCycleById } = useCycle();
const {
commandPalette: { toggleCreateIssueModal },
eventTracker: { setTrackElement },
} = useApplication();
const { setTrackElement } = useEventTracker();
const {
membership: { currentProjectRole },
} = useUser();
@@ -238,7 +239,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
</Button>
<Button
onClick={() => {
setTrackElement("CYCLE_PAGE_HEADER");
setTrackElement("Cycle issues page");
toggleCreateIssueModal(true, EIssuesStoreType.CYCLE);
}}
size="sm"
+3 -3
View File
@@ -3,7 +3,7 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { Plus } from "lucide-react";
// hooks
import { useApplication, useProject, useUser } from "hooks/store";
import { useApplication, useEventTracker, useProject, useUser } from "hooks/store";
// ui
import { Breadcrumbs, Button, ContrastIcon } from "@plane/ui";
// helpers
@@ -20,8 +20,8 @@ export const CyclesHeader: FC = observer(() => {
// store hooks
const {
commandPalette: { toggleCreateCycleModal },
eventTracker: { setTrackElement },
} = useApplication();
const { setTrackElement } = useEventTracker();
const {
membership: { currentProjectRole },
} = useUser();
@@ -70,7 +70,7 @@ export const CyclesHeader: FC = observer(() => {
size="sm"
prependIcon={<Plus />}
onClick={() => {
setTrackElement("CYCLES_PAGE_HEADER");
setTrackElement("Cycles page");
toggleCreateCycleModal(true);
}}
>
+3 -2
View File
@@ -5,6 +5,7 @@ import Link from "next/link";
// hooks
import {
useApplication,
useEventTracker,
useLabel,
useMember,
useModule,
@@ -73,8 +74,8 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
const { projectModuleIds, getModuleById } = useModule();
const {
commandPalette: { toggleCreateIssueModal },
eventTracker: { setTrackElement },
} = useApplication();
const { setTrackElement } = useEventTracker();
const {
membership: { currentProjectRole },
} = useUser();
@@ -241,7 +242,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
</Button>
<Button
onClick={() => {
setTrackElement("MODULE_PAGE_HEADER");
setTrackElement("Module issues page");
toggleCreateIssueModal(true, EIssuesStoreType.MODULE);
}}
size="sm"
+6 -2
View File
@@ -2,7 +2,7 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { Plus } from "lucide-react";
// hooks
import { useApplication, useProject, useUser } from "hooks/store";
import { useApplication, useEventTracker, useProject, useUser } from "hooks/store";
import useLocalStorage from "hooks/use-local-storage";
// ui
import { Breadcrumbs, Button, Tooltip, DiceIcon } from "@plane/ui";
@@ -21,6 +21,7 @@ export const ModulesListHeader: React.FC = observer(() => {
const { workspaceSlug } = router.query;
// store hooks
const { commandPalette: commandPaletteStore } = useApplication();
const { setTrackElement } = useEventTracker();
const {
membership: { currentProjectRole },
} = useUser();
@@ -90,7 +91,10 @@ export const ModulesListHeader: React.FC = observer(() => {
variant="primary"
size="sm"
prependIcon={<Plus />}
onClick={() => commandPaletteStore.toggleCreateModuleModal(true)}
onClick={() => {
setTrackElement("Modules page");
commandPaletteStore.toggleCreateModuleModal(true);
}}
>
Add Module
</Button>
+12 -3
View File
@@ -4,7 +4,16 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { ArrowLeft, Briefcase, Circle, ExternalLink, Plus } from "lucide-react";
// hooks
import { useApplication, useLabel, useProject, useProjectState, useUser, useInbox, useMember } from "hooks/store";
import {
useApplication,
useEventTracker,
useLabel,
useProject,
useProjectState,
useUser,
useInbox,
useMember,
} from "hooks/store";
// components
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "components/issues";
import { ProjectAnalyticsModal } from "components/analytics";
@@ -36,8 +45,8 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
} = useIssues(EIssuesStoreType.PROJECT);
const {
commandPalette: { toggleCreateIssueModal },
eventTracker: { setTrackElement },
} = useApplication();
const { setTrackElement } = useEventTracker();
const {
membership: { currentProjectRole },
} = useUser();
@@ -221,7 +230,7 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
</Button>
<Button
onClick={() => {
setTrackElement("PROJECT_PAGE_HEADER");
setTrackElement("Project issues page");
toggleCreateIssueModal(true, EIssuesStoreType.PROJECT);
}}
size="sm"
@@ -6,6 +6,7 @@ import Link from "next/link";
// hooks
import {
useApplication,
useEventTracker,
useIssues,
useLabel,
useMember,
@@ -41,9 +42,9 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
const {
issuesFilter: { issueFilters, updateFilters },
} = useIssues(EIssuesStoreType.PROJECT_VIEW);
const { setTrackElement } = useEventTracker();
const {
commandPalette: { toggleCreateIssueModal },
eventTracker: { setTrackElement },
} = useApplication();
const {
membership: { currentProjectRole },
+4 -6
View File
@@ -1,7 +1,7 @@
import { observer } from "mobx-react-lite";
import { Search, Plus, Briefcase } from "lucide-react";
// hooks
import { useApplication, useProject, useUser } from "hooks/store";
import { useApplication, useEventTracker, useProject, useUser } from "hooks/store";
// ui
import { Breadcrumbs, Button } from "@plane/ui";
// constants
@@ -12,10 +12,8 @@ import { BreadcrumbLink } from "components/common";
export const ProjectsHeader = observer(() => {
// store hooks
const {
commandPalette: commandPaletteStore,
eventTracker: { setTrackElement },
} = useApplication();
const { commandPalette: commandPaletteStore } = useApplication();
const { setTrackElement } = useEventTracker();
const {
membership: { currentWorkspaceRole },
} = useUser();
@@ -53,7 +51,7 @@ export const ProjectsHeader = observer(() => {
prependIcon={<Plus />}
size="sm"
onClick={() => {
setTrackElement("PROJECTS_PAGE_HEADER");
setTrackElement("Projects page");
commandPaletteStore.toggleCreateProjectModal(true);
}}
>
+19 -17
View File
@@ -4,7 +4,7 @@ import { observer } from "mobx-react-lite";
import DatePicker from "react-datepicker";
import { Popover } from "@headlessui/react";
// hooks
import { useApplication, useUser, useInboxIssues, useIssueDetail, useWorkspace } from "hooks/store";
import { useUser, useInboxIssues, useIssueDetail, useWorkspace, useEventTracker } from "hooks/store";
import useToast from "hooks/use-toast";
// components
import {
@@ -38,9 +38,7 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
// router
const router = useRouter();
// hooks
const {
eventTracker: { postHogEventTracker },
} = useApplication();
const { captureIssueEvent } = useEventTracker();
const { currentWorkspace } = useWorkspace();
const {
issues: { getInboxIssuesByInboxId, getInboxIssueByIssueId, updateInboxIssueStatus, removeInboxIssue },
@@ -87,17 +85,19 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
if (!workspaceSlug || !projectId || !inboxId || !inboxIssueId || !currentWorkspace)
throw new Error("Missing required parameters");
await removeInboxIssue(workspaceSlug, projectId, inboxId, inboxIssueId);
postHogEventTracker(
"ISSUE_DELETED",
{
captureIssueEvent({
eventName: "Issue deleted",
payload: {
id: inboxIssueId,
state: "SUCCESS",
element: "Inbox page",
},
{
group: {
isGrouping: true,
groupType: "Workspace_metrics",
groupId: currentWorkspace?.id!,
}
);
},
});
router.push({
pathname: `/${workspaceSlug}/projects/${projectId}/inbox/${inboxId}`,
});
@@ -107,17 +107,19 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
title: "Error!",
message: "Something went wrong while deleting inbox issue. Please try again.",
});
postHogEventTracker(
"ISSUE_DELETED",
{
captureIssueEvent({
eventName: "Issue deleted",
payload: {
id: inboxIssueId,
state: "FAILED",
element: "Inbox page",
},
{
group: {
isGrouping: true,
groupType: "Workspace_metrics",
groupId: currentWorkspace?.id!,
}
);
},
});
}
},
}),
@@ -130,7 +132,7 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
updateInboxIssueStatus,
removeInboxIssue,
setToastAlert,
postHogEventTracker,
captureIssueEvent,
router,
]
);
@@ -6,7 +6,7 @@ import { Controller, useForm } from "react-hook-form";
import { RichTextEditorWithRef } from "@plane/rich-text-editor";
import { Sparkle } from "lucide-react";
// hooks
import { useApplication, useWorkspace, useInboxIssues, useMention } from "hooks/store";
import { useApplication, useEventTracker, useWorkspace, useInboxIssues, useMention } from "hooks/store";
import useToast from "hooks/use-toast";
// services
import { FileService } from "services/file.service";
@@ -63,8 +63,8 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
} = useInboxIssues();
const {
config: { envConfig },
eventTracker: { postHogEventTracker },
} = useApplication();
const { captureIssueEvent } = useEventTracker();
const { currentWorkspace } = useWorkspace();
const {
@@ -93,32 +93,37 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
router.push(`/${workspaceSlug}/projects/${projectId}/inbox/${inboxId}?inboxIssueId=${res.id}`);
handleClose();
} else reset(defaultValues);
postHogEventTracker(
"ISSUE_CREATED",
{
...res,
captureIssueEvent({
eventName: "Issue created",
payload: {
...formData,
state: "SUCCESS",
element: "Inbox page",
},
{
group: {
isGrouping: true,
groupType: "Workspace_metrics",
groupId: currentWorkspace?.id!,
}
);
},
path: router.pathname,
});
})
.catch((error) => {
console.error(error);
postHogEventTracker(
"ISSUE_CREATED",
{
captureIssueEvent({
eventName: "Issue created",
payload: {
...formData,
state: "FAILED",
element: "Inbox page",
},
{
group: {
isGrouping: true,
groupType: "Workspace_metrics",
groupId: currentWorkspace?.id!,
}
);
},
path: router.pathname,
});
});
};
@@ -4,7 +4,7 @@ import { observer } from "mobx-react-lite";
import { useFormContext, Controller } from "react-hook-form";
import { Plus } from "lucide-react";
// hooks
import { useApplication, useProject } from "hooks/store";
import { useApplication, useEventTracker, useProject } from "hooks/store";
// components
import { CustomSelect, Input } from "@plane/ui";
// helpers
@@ -14,10 +14,8 @@ import { IJiraImporterForm } from "@plane/types";
export const JiraGetImportDetail: React.FC = observer(() => {
// store hooks
const {
commandPalette: commandPaletteStore,
eventTracker: { setTrackElement },
} = useApplication();
const { commandPalette: commandPaletteStore } = useApplication();
const { setTrackElement } = useEventTracker();
const { workspaceProjectIds, getProjectById } = useProject();
// form info
const {
@@ -202,7 +200,7 @@ export const JiraGetImportDetail: React.FC = observer(() => {
<button
type="button"
onClick={() => {
setTrackElement("JIRA_IMPORT_DETAIL");
setTrackElement("Jira import detail page");
commandPaletteStore.toggleCreateProjectModal(true);
}}
className="flex cursor-pointer select-none items-center space-x-2 truncate rounded px-1 py-1.5 text-custom-text-200"
+31 -2
View File
@@ -1,6 +1,6 @@
import { FC, useMemo } from "react";
// hooks
import { useIssueDetail } from "hooks/store";
import { useEventTracker, useIssueDetail } from "hooks/store";
import useToast from "hooks/use-toast";
// components
import { IssueAttachmentUpload } from "./attachment-upload";
@@ -23,6 +23,7 @@ export const IssueAttachmentRoot: FC<TIssueAttachmentRoot> = (props) => {
const { workspaceSlug, projectId, issueId, disabled = false } = props;
// hooks
const { createAttachment, removeAttachment } = useIssueDetail();
const { captureIssueEvent } = useEventTracker();
const { setToastAlert } = useToast();
const handleAttachmentOperations: TAttachmentOperations = useMemo(
@@ -30,13 +31,25 @@ export const IssueAttachmentRoot: FC<TIssueAttachmentRoot> = (props) => {
create: async (data: FormData) => {
try {
if (!workspaceSlug || !projectId || !issueId) throw new Error("Missing required fields");
await createAttachment(workspaceSlug, projectId, issueId, data);
const res = await createAttachment(workspaceSlug, projectId, issueId, data);
setToastAlert({
message: "The attachment has been successfully uploaded",
type: "success",
title: "Attachment uploaded",
});
captureIssueEvent({
eventName: "Issue updated",
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
updates: {
changed_property: "attachment",
change_details: res.id,
},
});
} catch (error) {
captureIssueEvent({
eventName: "Issue updated",
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
});
setToastAlert({
message: "The attachment could not be uploaded",
type: "error",
@@ -53,7 +66,23 @@ export const IssueAttachmentRoot: FC<TIssueAttachmentRoot> = (props) => {
type: "success",
title: "Attachment removed",
});
captureIssueEvent({
eventName: "Issue updated",
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
updates: {
changed_property: "attachment",
change_details: "",
},
});
} catch (error) {
captureIssueEvent({
eventName: "Issue updated",
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
updates: {
changed_property: "attachment",
change_details: "",
},
});
setToastAlert({
message: "The Attachment could not be removed",
type: "error",
@@ -1,10 +1,11 @@
import { FC, useMemo } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
// components
import { InboxIssueMainContent } from "./main-content";
import { InboxIssueDetailsSidebar } from "./sidebar";
// hooks
import { useInboxIssues, useIssueDetail, useUser } from "hooks/store";
import { useEventTracker, useInboxIssues, useIssueDetail, useUser } from "hooks/store";
import useToast from "hooks/use-toast";
// types
import { TIssue } from "@plane/types";
@@ -21,6 +22,8 @@ export type TInboxIssueDetailRoot = {
export const InboxIssueDetailRoot: FC<TInboxIssueDetailRoot> = (props) => {
const { workspaceSlug, projectId, inboxId, issueId } = props;
// router
const router = useRouter();
// hooks
const {
issues: { fetchInboxIssueById, updateInboxIssue, removeInboxIssue },
@@ -28,6 +31,7 @@ export const InboxIssueDetailRoot: FC<TInboxIssueDetailRoot> = (props) => {
const {
issue: { getIssueById },
} = useIssueDetail();
const { captureIssueEvent } = useEventTracker();
const { setToastAlert } = useToast();
const {
membership: { currentProjectRole },
@@ -50,7 +54,7 @@ export const InboxIssueDetailRoot: FC<TInboxIssueDetailRoot> = (props) => {
showToast: boolean = true
) => {
try {
await updateInboxIssue(workspaceSlug, projectId, inboxId, issueId, data);
const response = await updateInboxIssue(workspaceSlug, projectId, inboxId, issueId, data);
if (showToast) {
setToastAlert({
title: "Issue updated successfully",
@@ -58,12 +62,30 @@ export const InboxIssueDetailRoot: FC<TInboxIssueDetailRoot> = (props) => {
message: "Issue updated successfully",
});
}
captureIssueEvent({
eventName: "Inbox issue updated",
payload: { ...response, state: "SUCCESS", element: "Inbox" },
updates: {
changed_property: Object.keys(data).join(","),
change_details: Object.values(data).join(","),
},
path: router.asPath,
});
} catch (error) {
setToastAlert({
title: "Issue update failed",
type: "error",
message: "Issue update failed",
});
captureIssueEvent({
eventName: "Inbox issue updated",
payload: { state: "SUCCESS", element: "Inbox" },
updates: {
changed_property: Object.keys(data).join(","),
change_details: Object.values(data).join(","),
},
path: router.asPath,
});
}
},
remove: async (workspaceSlug: string, projectId: string, issueId: string) => {
@@ -74,7 +96,17 @@ export const InboxIssueDetailRoot: FC<TInboxIssueDetailRoot> = (props) => {
type: "success",
message: "Issue deleted successfully",
});
captureIssueEvent({
eventName: "Inbox issue deleted",
payload: { id: issueId, state: "SUCCESS", element: "Inbox" },
path: router.asPath,
});
} catch (error) {
captureIssueEvent({
eventName: "Inbox issue deleted",
payload: { id: issueId, state: "FAILED", element: "Inbox" },
path: router.asPath,
});
setToastAlert({
title: "Issue delete failed",
type: "error",
+109 -7
View File
@@ -9,7 +9,7 @@ import { EmptyState } from "components/common";
// images
import emptyIssue from "public/empty-state/issue.svg";
// hooks
import { useIssueDetail, useIssues, useUser } from "hooks/store";
import { useEventTracker, useIssueDetail, useIssues, useUser } from "hooks/store";
import useToast from "hooks/use-toast";
// types
import { TIssue } from "@plane/types";
@@ -70,6 +70,7 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = (props) => {
const {
issues: { removeIssue: removeArchivedIssue },
} = useIssues(EIssuesStoreType.ARCHIVED);
const { captureIssueEvent } = useEventTracker();
const { setToastAlert } = useToast();
const {
membership: { currentProjectRole },
@@ -92,7 +93,7 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = (props) => {
showToast: boolean = true
) => {
try {
await updateIssue(workspaceSlug, projectId, issueId, data);
const response = await updateIssue(workspaceSlug, projectId, issueId, data);
if (showToast) {
setToastAlert({
title: "Issue updated successfully",
@@ -100,7 +101,25 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = (props) => {
message: "Issue updated successfully",
});
}
captureIssueEvent({
eventName: "Issue updated",
payload: { ...response, state: "SUCCESS", element: "Issue detail page" },
updates: {
changed_property: Object.keys(data).join(","),
change_details: Object.values(data).join(","),
},
path: router.asPath,
});
} catch (error) {
captureIssueEvent({
eventName: "Issue updated",
payload: { state: "FAILED", element: "Issue detail page" },
updates: {
changed_property: Object.keys(data).join(","),
change_details: Object.values(data).join(","),
},
path: router.asPath,
});
setToastAlert({
title: "Issue update failed",
type: "error",
@@ -110,30 +129,59 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = (props) => {
},
remove: async (workspaceSlug: string, projectId: string, issueId: string) => {
try {
if (is_archived) await removeArchivedIssue(workspaceSlug, projectId, issueId);
else await removeIssue(workspaceSlug, projectId, issueId);
let response;
if (is_archived) response = await removeArchivedIssue(workspaceSlug, projectId, issueId);
else response = await removeIssue(workspaceSlug, projectId, issueId);
setToastAlert({
title: "Issue deleted successfully",
type: "success",
message: "Issue deleted successfully",
});
captureIssueEvent({
eventName: "Issue deleted",
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
path: router.asPath,
});
} catch (error) {
setToastAlert({
title: "Issue delete failed",
type: "error",
message: "Issue delete failed",
});
captureIssueEvent({
eventName: "Issue deleted",
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
path: router.asPath,
});
}
},
addIssueToCycle: async (workspaceSlug: string, projectId: string, cycleId: string, issueIds: string[]) => {
try {
await addIssueToCycle(workspaceSlug, projectId, cycleId, issueIds);
const response = await addIssueToCycle(workspaceSlug, projectId, cycleId, issueIds);
setToastAlert({
title: "Cycle added to issue successfully",
type: "success",
message: "Issue added to issue successfully",
});
captureIssueEvent({
eventName: "Issue updated",
payload: { ...response, state: "SUCCESS", element: "Issue detail page" },
updates: {
changed_property: "cycle_id",
change_details: cycleId,
},
path: router.asPath,
});
} catch (error) {
captureIssueEvent({
eventName: "Issue updated",
payload: { state: "FAILED", element: "Issue detail page" },
updates: {
changed_property: "cycle_id",
change_details: cycleId,
},
path: router.asPath,
});
setToastAlert({
title: "Cycle add to issue failed",
type: "error",
@@ -143,13 +191,31 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = (props) => {
},
removeIssueFromCycle: async (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => {
try {
await removeIssueFromCycle(workspaceSlug, projectId, cycleId, issueId);
const response = await removeIssueFromCycle(workspaceSlug, projectId, cycleId, issueId);
setToastAlert({
title: "Cycle removed from issue successfully",
type: "success",
message: "Cycle removed from issue successfully",
});
captureIssueEvent({
eventName: "Issue updated",
payload: { ...response, state: "SUCCESS", element: "Issue detail page" },
updates: {
changed_property: "cycle_id",
change_details: "",
},
path: router.asPath,
});
} catch (error) {
captureIssueEvent({
eventName: "Issue updated",
payload: { state: "FAILED", element: "Issue detail page" },
updates: {
changed_property: "cycle_id",
change_details: "",
},
path: router.asPath,
});
setToastAlert({
title: "Cycle remove from issue failed",
type: "error",
@@ -159,13 +225,31 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = (props) => {
},
addModulesToIssue: async (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => {
try {
await addModulesToIssue(workspaceSlug, projectId, issueId, moduleIds);
const response = await addModulesToIssue(workspaceSlug, projectId, issueId, moduleIds);
setToastAlert({
title: "Module added to issue successfully",
type: "success",
message: "Module added to issue successfully",
});
captureIssueEvent({
eventName: "Issue updated",
payload: { ...response, state: "SUCCESS", element: "Issue detail page" },
updates: {
changed_property: "module_id",
change_details: moduleIds,
},
path: router.asPath,
});
} catch (error) {
captureIssueEvent({
eventName: "Issue updated",
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
updates: {
changed_property: "module_id",
change_details: moduleIds,
},
path: router.asPath,
});
setToastAlert({
title: "Module add to issue failed",
type: "error",
@@ -181,7 +265,25 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = (props) => {
type: "success",
message: "Module removed from issue successfully",
});
captureIssueEvent({
eventName: "Issue updated",
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
updates: {
changed_property: "module_id",
change_details: "",
},
path: router.asPath,
});
} catch (error) {
captureIssueEvent({
eventName: "Issue updated",
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
updates: {
changed_property: "module_id",
change_details: "",
},
path: router.asPath,
});
setToastAlert({
title: "Module remove from issue failed",
type: "error",
@@ -11,11 +11,12 @@ import { TGroupedIssues, TIssue } from "@plane/types";
import { IQuickActionProps } from "../list/list-view-types";
import { EIssueActions } from "../types";
import { handleDragDrop } from "./utils";
import { useIssues } from "hooks/store";
import { useIssues, useUser } from "hooks/store";
import { ICycleIssues, ICycleIssuesFilter } from "store/issue/cycle";
import { IModuleIssues, IModuleIssuesFilter } from "store/issue/module";
import { IProjectIssues, IProjectIssuesFilter } from "store/issue/project";
import { IProjectViewIssues, IProjectViewIssuesFilter } from "store/issue/project-views";
import { EUserProjectRoles } from "constants/project";
interface IBaseCalendarRoot {
issueStore: IProjectIssues | IModuleIssues | ICycleIssues | IProjectViewIssues;
@@ -27,10 +28,11 @@ interface IBaseCalendarRoot {
[EIssueActions.REMOVE]?: (issue: TIssue) => Promise<void>;
};
viewId?: string;
isCompletedCycle?: boolean;
}
export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
const { issueStore, issuesFilterStore, QuickActions, issueActions, viewId } = props;
const { issueStore, issuesFilterStore, QuickActions, issueActions, viewId, isCompletedCycle = false } = props;
// router
const router = useRouter();
@@ -39,6 +41,11 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
// hooks
const { setToastAlert } = useToast();
const { issueMap } = useIssues();
const {
membership: { currentProjectRole },
} = useUser();
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
const displayFilters = issuesFilterStore.issueFilters?.displayFilters;
@@ -107,10 +114,12 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
? async () => handleIssues(issue.target_date ?? "", issue, EIssueActions.REMOVE)
: undefined
}
readOnly={!isEditingAllowed || isCompletedCycle}
/>
)}
quickAddCallback={issueStore.quickAddIssue}
viewId={viewId}
readOnly={!isEditingAllowed || isCompletedCycle}
/>
</DragDropContext>
</div>
@@ -31,11 +31,21 @@ type Props = {
viewId?: string
) => Promise<TIssue | undefined>;
viewId?: string;
readOnly?: boolean;
};
export const CalendarChart: React.FC<Props> = observer((props) => {
const { issuesFilterStore, issues, groupedIssueIds, layout, showWeekends, quickActions, quickAddCallback, viewId } =
props;
const {
issuesFilterStore,
issues,
groupedIssueIds,
layout,
showWeekends,
quickActions,
quickAddCallback,
viewId,
readOnly = false,
} = props;
// store hooks
const {
issues: { viewFlags },
@@ -80,6 +90,7 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
quickActions={quickActions}
quickAddCallback={quickAddCallback}
viewId={viewId}
readOnly={readOnly}
/>
))}
</div>
@@ -95,6 +106,7 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
quickActions={quickActions}
quickAddCallback={quickAddCallback}
viewId={viewId}
readOnly={readOnly}
/>
)}
</div>
@@ -28,6 +28,7 @@ type Props = {
viewId?: string
) => Promise<TIssue | undefined>;
viewId?: string;
readOnly?: boolean;
};
export const CalendarDayTile: React.FC<Props> = observer((props) => {
@@ -41,6 +42,7 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
disableIssueCreation,
quickAddCallback,
viewId,
readOnly = false,
} = props;
const [showAllIssues, setShowAllIssues] = useState(false);
const calendarLayout = issuesFilterStore?.issueFilters?.displayFilters?.calendar?.layout ?? "month";
@@ -73,7 +75,7 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
{/* content */}
<div className="h-full w-full">
<Droppable droppableId={formattedDatePayload} isDropDisabled={false}>
<Droppable droppableId={formattedDatePayload} isDropDisabled={readOnly}>
{(provided, snapshot) => (
<div
className={`h-full w-full select-none overflow-y-auto ${
@@ -89,9 +91,10 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
issueIdList={issueIdList}
quickActions={quickActions}
showAllIssues={showAllIssues}
isDragDisabled={readOnly}
/>
{enableQuickIssueCreate && !disableIssueCreation && (
{enableQuickIssueCreate && !disableIssueCreation && !readOnly && (
<div className="px-2 py-1">
<CalendarQuickAddIssueForm
formKey="target_date"
@@ -17,10 +17,11 @@ type Props = {
issueIdList: string[] | null;
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
showAllIssues?: boolean;
isDragDisabled?: boolean;
};
export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
const { issues, issueIdList, quickActions, showAllIssues = false } = props;
const { issues, issueIdList, quickActions, showAllIssues = false, isDragDisabled = false } = props;
// hooks
const {
router: { workspaceSlug, projectId },
@@ -65,7 +66,7 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
getProjectStates(issue?.project_id)?.find((state) => state?.id == issue?.state_id)?.color || "";
return (
<Draggable key={issue.id} draggableId={issue.id} index={index}>
<Draggable key={issue.id} draggableId={issue.id} index={index} isDragDisabled={isDragDisabled}>
{(provided, snapshot) => (
<div
className="relative cursor-pointer p-1 px-2"
@@ -3,7 +3,7 @@ import { useRouter } from "next/router";
import { useForm } from "react-hook-form";
import { observer } from "mobx-react-lite";
// hooks
import { useProject } from "hooks/store";
import { useEventTracker, useProject } from "hooks/store";
import useToast from "hooks/use-toast";
import useKeypress from "hooks/use-keypress";
import useOutsideClickDetector from "hooks/use-outside-click-detector";
@@ -64,6 +64,7 @@ export const CalendarQuickAddIssueForm: React.FC<Props> = observer((props) => {
const { workspaceSlug, projectId } = router.query;
// store hooks
const { getProjectById } = useProject();
const { captureIssueEvent } = useEventTracker();
// refs
const ref = useRef<HTMLDivElement>(null);
// states
@@ -126,7 +127,13 @@ export const CalendarQuickAddIssueForm: React.FC<Props> = observer((props) => {
...payload,
},
viewId
));
).then((res) => {
captureIssueEvent({
eventName: "Issue created",
payload: { ...res, state: "SUCCESS", element: "Calendar quick add" },
path: router.asPath,
});
}));
setToastAlert({
type: "success",
title: "Success!",
@@ -134,6 +141,11 @@ export const CalendarQuickAddIssueForm: React.FC<Props> = observer((props) => {
});
} catch (err: any) {
console.error(err);
captureIssueEvent({
eventName: "Issue created",
payload: { ...payload, state: "FAILED", element: "Calendar quick add" },
path: router.asPath,
});
setToastAlert({
type: "error",
title: "Error!",
@@ -1,7 +1,7 @@
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
//hooks
import { useIssues } from "hooks/store";
import { useCycle, useIssues } from "hooks/store";
// components
import { CycleIssueQuickActions } from "components/issues";
// types
@@ -13,6 +13,7 @@ import { useMemo } from "react";
export const CycleCalendarLayout: React.FC = observer(() => {
const { issues, issuesFilter } = useIssues(EIssuesStoreType.CYCLE);
const { currentProjectCompletedCycleIds } = useCycle();
const router = useRouter();
const { workspaceSlug, projectId, cycleId } = router.query;
@@ -38,6 +39,9 @@ export const CycleCalendarLayout: React.FC = observer(() => {
if (!cycleId) return null;
const isCompletedCycle =
cycleId && currentProjectCompletedCycleIds ? currentProjectCompletedCycleIds.includes(cycleId.toString()) : false;
return (
<BaseCalendarRoot
issueStore={issues}
@@ -45,6 +49,7 @@ export const CycleCalendarLayout: React.FC = observer(() => {
QuickActions={CycleIssueQuickActions}
issueActions={issueActions}
viewId={cycleId.toString()}
isCompletedCycle={isCompletedCycle}
/>
);
});
@@ -26,6 +26,7 @@ type Props = {
viewId?: string
) => Promise<TIssue | undefined>;
viewId?: string;
readOnly?: boolean;
};
export const CalendarWeekDays: React.FC<Props> = observer((props) => {
@@ -39,6 +40,7 @@ export const CalendarWeekDays: React.FC<Props> = observer((props) => {
disableIssueCreation,
quickAddCallback,
viewId,
readOnly = false,
} = props;
const calendarLayout = issuesFilterStore?.issueFilters?.displayFilters?.calendar?.layout ?? "month";
@@ -67,6 +69,7 @@ export const CalendarWeekDays: React.FC<Props> = observer((props) => {
disableIssueCreation={disableIssueCreation}
quickAddCallback={quickAddCallback}
viewId={viewId}
readOnly={readOnly}
/>
);
})}

Some files were not shown because too many files have changed in this diff Show More