[WEB-5044] fix: ruff lint and format errors (#7868)
* fix: lint errors * fix: file formatting * fix: code refactor
This commit is contained in:
parent
1fb22bd252
commit
9237f568dd
261 changed files with 2199 additions and 6378 deletions
|
|
@ -41,26 +41,16 @@ class AdvanceAnalyticsEndpoint(AdvanceAnalyticsBaseView):
|
|||
def get_filtered_count() -> int:
|
||||
if self.filters["analytics_date_range"]:
|
||||
return queryset.filter(
|
||||
created_at__gte=self.filters["analytics_date_range"]["current"][
|
||||
"gte"
|
||||
],
|
||||
created_at__lte=self.filters["analytics_date_range"]["current"][
|
||||
"lte"
|
||||
],
|
||||
created_at__gte=self.filters["analytics_date_range"]["current"]["gte"],
|
||||
created_at__lte=self.filters["analytics_date_range"]["current"]["lte"],
|
||||
).count()
|
||||
return queryset.count()
|
||||
|
||||
def get_previous_count() -> int:
|
||||
if self.filters["analytics_date_range"] and self.filters[
|
||||
"analytics_date_range"
|
||||
].get("previous"):
|
||||
if self.filters["analytics_date_range"] and self.filters["analytics_date_range"].get("previous"):
|
||||
return queryset.filter(
|
||||
created_at__gte=self.filters["analytics_date_range"]["previous"][
|
||||
"gte"
|
||||
],
|
||||
created_at__lte=self.filters["analytics_date_range"]["previous"][
|
||||
"lte"
|
||||
],
|
||||
created_at__gte=self.filters["analytics_date_range"]["previous"]["gte"],
|
||||
created_at__lte=self.filters["analytics_date_range"]["previous"]["lte"],
|
||||
).count()
|
||||
return 0
|
||||
|
||||
|
|
@ -71,39 +61,27 @@ class AdvanceAnalyticsEndpoint(AdvanceAnalyticsBaseView):
|
|||
|
||||
def get_overview_data(self) -> Dict[str, Dict[str, int]]:
|
||||
members_query = WorkspaceMember.objects.filter(
|
||||
workspace__slug=self._workspace_slug, is_active=True
|
||||
workspace__slug=self._workspace_slug, is_active=True, member__is_bot=False
|
||||
)
|
||||
|
||||
if self.request.GET.get("project_ids", None):
|
||||
project_ids = self.request.GET.get("project_ids", None)
|
||||
project_ids = [str(project_id) for project_id in project_ids.split(",")]
|
||||
members_query = ProjectMember.objects.filter(
|
||||
project_id__in=project_ids, is_active=True
|
||||
project_id__in=project_ids, is_active=True, member__is_bot=False
|
||||
)
|
||||
|
||||
return {
|
||||
"total_users": self.get_filtered_counts(members_query),
|
||||
"total_admins": self.get_filtered_counts(
|
||||
members_query.filter(role=ROLE.ADMIN.value)
|
||||
),
|
||||
"total_members": self.get_filtered_counts(
|
||||
members_query.filter(role=ROLE.MEMBER.value)
|
||||
),
|
||||
"total_guests": self.get_filtered_counts(
|
||||
members_query.filter(role=ROLE.GUEST.value)
|
||||
),
|
||||
"total_projects": self.get_filtered_counts(
|
||||
Project.objects.filter(**self.filters["project_filters"])
|
||||
),
|
||||
"total_work_items": self.get_filtered_counts(
|
||||
Issue.issue_objects.filter(**self.filters["base_filters"])
|
||||
),
|
||||
"total_cycles": self.get_filtered_counts(
|
||||
Cycle.objects.filter(**self.filters["base_filters"])
|
||||
),
|
||||
"total_admins": self.get_filtered_counts(members_query.filter(role=ROLE.ADMIN.value)),
|
||||
"total_members": self.get_filtered_counts(members_query.filter(role=ROLE.MEMBER.value)),
|
||||
"total_guests": self.get_filtered_counts(members_query.filter(role=ROLE.GUEST.value)),
|
||||
"total_projects": self.get_filtered_counts(Project.objects.filter(**self.filters["project_filters"])),
|
||||
"total_work_items": self.get_filtered_counts(Issue.issue_objects.filter(**self.filters["base_filters"])),
|
||||
"total_cycles": self.get_filtered_counts(Cycle.objects.filter(**self.filters["base_filters"])),
|
||||
"total_intake": self.get_filtered_counts(
|
||||
Issue.objects.filter(**self.filters["base_filters"]).filter(
|
||||
issue_intake__status__in=["-2", "0"]
|
||||
issue_intake__status__in=["-2", "-1", "0", "1", "2"] # TODO: Add description for reference.
|
||||
)
|
||||
),
|
||||
}
|
||||
|
|
@ -113,18 +91,10 @@ class AdvanceAnalyticsEndpoint(AdvanceAnalyticsBaseView):
|
|||
|
||||
return {
|
||||
"total_work_items": self.get_filtered_counts(base_queryset),
|
||||
"started_work_items": self.get_filtered_counts(
|
||||
base_queryset.filter(state__group="started")
|
||||
),
|
||||
"backlog_work_items": self.get_filtered_counts(
|
||||
base_queryset.filter(state__group="backlog")
|
||||
),
|
||||
"un_started_work_items": self.get_filtered_counts(
|
||||
base_queryset.filter(state__group="unstarted")
|
||||
),
|
||||
"completed_work_items": self.get_filtered_counts(
|
||||
base_queryset.filter(state__group="completed")
|
||||
),
|
||||
"started_work_items": self.get_filtered_counts(base_queryset.filter(state__group="started")),
|
||||
"backlog_work_items": self.get_filtered_counts(base_queryset.filter(state__group="backlog")),
|
||||
"un_started_work_items": self.get_filtered_counts(base_queryset.filter(state__group="unstarted")),
|
||||
"completed_work_items": self.get_filtered_counts(base_queryset.filter(state__group="completed")),
|
||||
}
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
|
||||
|
|
@ -153,9 +123,7 @@ class AdvanceAnalyticsStatsEndpoint(AdvanceAnalyticsBaseView):
|
|||
# Apply date range filter if available
|
||||
if self.filters["chart_period_range"]:
|
||||
start_date, end_date = self.filters["chart_period_range"]
|
||||
base_queryset = base_queryset.filter(
|
||||
created_at__date__gte=start_date, created_at__date__lte=end_date
|
||||
)
|
||||
base_queryset = base_queryset.filter(created_at__date__gte=start_date, created_at__date__lte=end_date)
|
||||
|
||||
return (
|
||||
base_queryset.values("project_id", "project__name")
|
||||
|
|
@ -212,24 +180,16 @@ class AdvanceAnalyticsChartEndpoint(AdvanceAnalyticsBaseView):
|
|||
}
|
||||
|
||||
total_work_items = base_queryset.filter(**date_filter).count()
|
||||
total_cycles = Cycle.objects.filter(
|
||||
**self.filters["base_filters"], **date_filter
|
||||
).count()
|
||||
total_modules = Module.objects.filter(
|
||||
**self.filters["base_filters"], **date_filter
|
||||
).count()
|
||||
total_cycles = Cycle.objects.filter(**self.filters["base_filters"], **date_filter).count()
|
||||
total_modules = Module.objects.filter(**self.filters["base_filters"], **date_filter).count()
|
||||
total_intake = Issue.objects.filter(
|
||||
issue_intake__isnull=False, **self.filters["base_filters"], **date_filter
|
||||
).count()
|
||||
total_members = WorkspaceMember.objects.filter(
|
||||
workspace__slug=self._workspace_slug, is_active=True, **date_filter
|
||||
).count()
|
||||
total_pages = ProjectPage.objects.filter(
|
||||
**self.filters["base_filters"], **date_filter
|
||||
).count()
|
||||
total_views = IssueView.objects.filter(
|
||||
**self.filters["base_filters"], **date_filter
|
||||
).count()
|
||||
total_pages = ProjectPage.objects.filter(**self.filters["base_filters"], **date_filter).count()
|
||||
total_views = IssueView.objects.filter(**self.filters["base_filters"], **date_filter).count()
|
||||
|
||||
data = {
|
||||
"work_items": total_work_items,
|
||||
|
|
@ -255,9 +215,7 @@ class AdvanceAnalyticsChartEndpoint(AdvanceAnalyticsBaseView):
|
|||
queryset = (
|
||||
Issue.issue_objects.filter(**self.filters["base_filters"])
|
||||
.select_related("workspace", "state", "parent")
|
||||
.prefetch_related(
|
||||
"assignees", "labels", "issue_module__module", "issue_cycle__cycle"
|
||||
)
|
||||
.prefetch_related("assignees", "labels", "issue_module__module", "issue_cycle__cycle")
|
||||
)
|
||||
|
||||
workspace = Workspace.objects.get(slug=self._workspace_slug)
|
||||
|
|
@ -266,9 +224,7 @@ class AdvanceAnalyticsChartEndpoint(AdvanceAnalyticsBaseView):
|
|||
# Apply date range filter if available
|
||||
if self.filters["chart_period_range"]:
|
||||
start_date, end_date = self.filters["chart_period_range"]
|
||||
queryset = queryset.filter(
|
||||
created_at__date__gte=start_date, created_at__date__lte=end_date
|
||||
)
|
||||
queryset = queryset.filter(created_at__date__gte=start_date, created_at__date__lte=end_date)
|
||||
|
||||
# Annotate by month and count
|
||||
monthly_stats = (
|
||||
|
|
@ -311,9 +267,7 @@ class AdvanceAnalyticsChartEndpoint(AdvanceAnalyticsBaseView):
|
|||
)
|
||||
# Move to next month
|
||||
if current_month.month == 12:
|
||||
current_month = current_month.replace(
|
||||
year=current_month.year + 1, month=1
|
||||
)
|
||||
current_month = current_month.replace(year=current_month.year + 1, month=1)
|
||||
else:
|
||||
current_month = current_month.replace(month=current_month.month + 1)
|
||||
|
||||
|
|
@ -338,17 +292,13 @@ class AdvanceAnalyticsChartEndpoint(AdvanceAnalyticsBaseView):
|
|||
queryset = (
|
||||
Issue.issue_objects.filter(**self.filters["base_filters"])
|
||||
.select_related("workspace", "state", "parent")
|
||||
.prefetch_related(
|
||||
"assignees", "labels", "issue_module__module", "issue_cycle__cycle"
|
||||
)
|
||||
.prefetch_related("assignees", "labels", "issue_module__module", "issue_cycle__cycle")
|
||||
)
|
||||
|
||||
# Apply date range filter if available
|
||||
if self.filters["chart_period_range"]:
|
||||
start_date, end_date = self.filters["chart_period_range"]
|
||||
queryset = queryset.filter(
|
||||
created_at__date__gte=start_date, created_at__date__lte=end_date
|
||||
)
|
||||
queryset = queryset.filter(created_at__date__gte=start_date, created_at__date__lte=end_date)
|
||||
|
||||
return Response(
|
||||
build_analytics_chart(queryset, x_axis, group_by),
|
||||
|
|
|
|||
|
|
@ -55,25 +55,16 @@ class AnalyticsEndpoint(BaseAPIView):
|
|||
valid_yaxis = ["issue_count", "estimate"]
|
||||
|
||||
# Check for x-axis and y-axis as thery are required parameters
|
||||
if (
|
||||
not x_axis
|
||||
or not y_axis
|
||||
or x_axis not in valid_xaxis_segment
|
||||
or y_axis not in valid_yaxis
|
||||
):
|
||||
if not x_axis or not y_axis or x_axis not in valid_xaxis_segment or y_axis not in valid_yaxis:
|
||||
return Response(
|
||||
{
|
||||
"error": "x-axis and y-axis dimensions are required and the values should be valid"
|
||||
},
|
||||
{"error": "x-axis and y-axis dimensions are required and the values should be valid"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# If segment is present it cannot be same as x-axis
|
||||
if segment and (segment not in valid_xaxis_segment or x_axis == segment):
|
||||
return Response(
|
||||
{
|
||||
"error": "Both segment and x axis cannot be same and segment should be valid"
|
||||
},
|
||||
{"error": "Both segment and x axis cannot be same and segment should be valid"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
|
@ -87,9 +78,7 @@ class AnalyticsEndpoint(BaseAPIView):
|
|||
total_issues = queryset.count()
|
||||
|
||||
# Build the graph payload
|
||||
distribution = build_graph_plot(
|
||||
queryset=queryset, x_axis=x_axis, y_axis=y_axis, segment=segment
|
||||
)
|
||||
distribution = build_graph_plot(queryset=queryset, x_axis=x_axis, y_axis=y_axis, segment=segment)
|
||||
|
||||
state_details = {}
|
||||
if x_axis in ["state_id"] or segment in ["state_id"]:
|
||||
|
|
@ -118,10 +107,7 @@ class AnalyticsEndpoint(BaseAPIView):
|
|||
if x_axis in ["assignees__id"] or segment in ["assignees__id"]:
|
||||
assignee_details = (
|
||||
Issue.issue_objects.filter(
|
||||
Q(
|
||||
Q(assignees__avatar__isnull=False)
|
||||
| Q(assignees__avatar_asset__isnull=False)
|
||||
),
|
||||
Q(Q(assignees__avatar__isnull=False) | Q(assignees__avatar_asset__isnull=False)),
|
||||
workspace__slug=slug,
|
||||
**filters,
|
||||
)
|
||||
|
|
@ -171,9 +157,7 @@ class AnalyticsEndpoint(BaseAPIView):
|
|||
)
|
||||
|
||||
module_details = {}
|
||||
if x_axis in ["issue_module__module_id"] or segment in [
|
||||
"issue_module__module_id"
|
||||
]:
|
||||
if x_axis in ["issue_module__module_id"] or segment in ["issue_module__module_id"]:
|
||||
module_details = (
|
||||
Issue.issue_objects.filter(
|
||||
workspace__slug=slug,
|
||||
|
|
@ -212,9 +196,7 @@ class AnalyticViewViewset(BaseViewSet):
|
|||
serializer.save(workspace_id=workspace.id)
|
||||
|
||||
def get_queryset(self):
|
||||
return self.filter_queryset(
|
||||
super().get_queryset().filter(workspace__slug=self.kwargs.get("slug"))
|
||||
)
|
||||
return self.filter_queryset(super().get_queryset().filter(workspace__slug=self.kwargs.get("slug")))
|
||||
|
||||
|
||||
class SavedAnalyticEndpoint(BaseAPIView):
|
||||
|
|
@ -235,9 +217,7 @@ class SavedAnalyticEndpoint(BaseAPIView):
|
|||
)
|
||||
|
||||
segment = request.GET.get("segment", False)
|
||||
distribution = build_graph_plot(
|
||||
queryset=queryset, x_axis=x_axis, y_axis=y_axis, segment=segment
|
||||
)
|
||||
distribution = build_graph_plot(queryset=queryset, x_axis=x_axis, y_axis=y_axis, segment=segment)
|
||||
total_issues = queryset.count()
|
||||
return Response(
|
||||
{"total": total_issues, "distribution": distribution},
|
||||
|
|
@ -270,36 +250,23 @@ class ExportAnalyticsEndpoint(BaseAPIView):
|
|||
valid_yaxis = ["issue_count", "estimate"]
|
||||
|
||||
# Check for x-axis and y-axis as thery are required parameters
|
||||
if (
|
||||
not x_axis
|
||||
or not y_axis
|
||||
or x_axis not in valid_xaxis_segment
|
||||
or y_axis not in valid_yaxis
|
||||
):
|
||||
if not x_axis or not y_axis or x_axis not in valid_xaxis_segment or y_axis not in valid_yaxis:
|
||||
return Response(
|
||||
{
|
||||
"error": "x-axis and y-axis dimensions are required and the values should be valid"
|
||||
},
|
||||
{"error": "x-axis and y-axis dimensions are required and the values should be valid"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# If segment is present it cannot be same as x-axis
|
||||
if segment and (segment not in valid_xaxis_segment or x_axis == segment):
|
||||
return Response(
|
||||
{
|
||||
"error": "Both segment and x axis cannot be same and segment should be valid"
|
||||
},
|
||||
{"error": "Both segment and x axis cannot be same and segment should be valid"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
analytic_export_task.delay(
|
||||
email=request.user.email, data=request.data, slug=slug
|
||||
)
|
||||
analytic_export_task.delay(email=request.user.email, data=request.data, slug=slug)
|
||||
|
||||
return Response(
|
||||
{
|
||||
"message": f"Once the export is ready it will be emailed to you at {str(request.user.email)}"
|
||||
},
|
||||
{"message": f"Once the export is ready it will be emailed to you at {str(request.user.email)}"},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
|
|
@ -315,9 +282,7 @@ class DefaultAnalyticsEndpoint(BaseAPIView):
|
|||
state_groups = base_issues.annotate(state_group=F("state__group"))
|
||||
|
||||
total_issues_classified = (
|
||||
state_groups.values("state_group")
|
||||
.annotate(state_count=Count("state_group"))
|
||||
.order_by("state_group")
|
||||
state_groups.values("state_group").annotate(state_count=Count("state_group")).order_by("state_group")
|
||||
)
|
||||
|
||||
open_issues_groups = ["backlog", "unstarted", "started"]
|
||||
|
|
@ -362,9 +327,7 @@ class DefaultAnalyticsEndpoint(BaseAPIView):
|
|||
),
|
||||
),
|
||||
# If `avatar_asset` is None, fall back to using `avatar` field directly
|
||||
When(
|
||||
created_by__avatar_asset__isnull=True, then="created_by__avatar"
|
||||
),
|
||||
When(created_by__avatar_asset__isnull=True, then="created_by__avatar"),
|
||||
default=Value(None),
|
||||
output_field=models.CharField(),
|
||||
)
|
||||
|
|
@ -395,9 +358,7 @@ class DefaultAnalyticsEndpoint(BaseAPIView):
|
|||
),
|
||||
),
|
||||
# If `avatar_asset` is None, fall back to using `avatar` field directly
|
||||
When(
|
||||
assignees__avatar_asset__isnull=True, then="assignees__avatar"
|
||||
),
|
||||
When(assignees__avatar_asset__isnull=True, then="assignees__avatar"),
|
||||
default=Value(None),
|
||||
output_field=models.CharField(),
|
||||
)
|
||||
|
|
@ -422,9 +383,7 @@ class DefaultAnalyticsEndpoint(BaseAPIView):
|
|||
),
|
||||
),
|
||||
# If `avatar_asset` is None, fall back to using `avatar` field directly
|
||||
When(
|
||||
assignees__avatar_asset__isnull=True, then="assignees__avatar"
|
||||
),
|
||||
When(assignees__avatar_asset__isnull=True, then="assignees__avatar"),
|
||||
default=Value(None),
|
||||
output_field=models.CharField(),
|
||||
)
|
||||
|
|
@ -485,9 +444,7 @@ class ProjectStatsEndpoint(BaseAPIView):
|
|||
|
||||
if "completed_issues" in requested_fields:
|
||||
annotations["completed_issues"] = (
|
||||
Issue.issue_objects.filter(
|
||||
project_id=OuterRef("pk"), state__group="completed"
|
||||
)
|
||||
Issue.issue_objects.filter(project_id=OuterRef("pk"), state__group__in=["completed", "cancelled"])
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
|
|
@ -511,9 +468,7 @@ class ProjectStatsEndpoint(BaseAPIView):
|
|||
|
||||
if "total_members" in requested_fields:
|
||||
annotations["total_members"] = (
|
||||
ProjectMember.objects.filter(
|
||||
project_id=OuterRef("id"), member__is_bot=False, is_active=True
|
||||
)
|
||||
ProjectMember.objects.filter(project_id=OuterRef("id"), member__is_bot=False, is_active=True)
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
|
|
|
|||
|
|
@ -42,12 +42,8 @@ class ProjectAdvanceAnalyticsEndpoint(ProjectAdvanceAnalyticsBaseView):
|
|||
def get_filtered_count() -> int:
|
||||
if self.filters["analytics_date_range"]:
|
||||
return queryset.filter(
|
||||
created_at__gte=self.filters["analytics_date_range"]["current"][
|
||||
"gte"
|
||||
],
|
||||
created_at__lte=self.filters["analytics_date_range"]["current"][
|
||||
"lte"
|
||||
],
|
||||
created_at__gte=self.filters["analytics_date_range"]["current"]["gte"],
|
||||
created_at__lte=self.filters["analytics_date_range"]["current"]["lte"],
|
||||
).count()
|
||||
return queryset.count()
|
||||
|
||||
|
|
@ -55,42 +51,30 @@ class ProjectAdvanceAnalyticsEndpoint(ProjectAdvanceAnalyticsBaseView):
|
|||
"count": get_filtered_count(),
|
||||
}
|
||||
|
||||
def get_work_items_stats(
|
||||
self, project_id, cycle_id=None, module_id=None
|
||||
) -> Dict[str, Dict[str, int]]:
|
||||
def get_work_items_stats(self, project_id, cycle_id=None, module_id=None) -> Dict[str, Dict[str, int]]:
|
||||
"""
|
||||
Returns work item stats for the workspace, or filtered by cycle_id or module_id if provided.
|
||||
"""
|
||||
base_queryset = None
|
||||
if cycle_id is not None:
|
||||
cycle_issues = CycleIssue.objects.filter(
|
||||
**self.filters["base_filters"], cycle_id=cycle_id
|
||||
).values_list("issue_id", flat=True)
|
||||
cycle_issues = CycleIssue.objects.filter(**self.filters["base_filters"], cycle_id=cycle_id).values_list(
|
||||
"issue_id", flat=True
|
||||
)
|
||||
base_queryset = Issue.issue_objects.filter(id__in=cycle_issues)
|
||||
elif module_id is not None:
|
||||
module_issues = ModuleIssue.objects.filter(
|
||||
**self.filters["base_filters"], module_id=module_id
|
||||
).values_list("issue_id", flat=True)
|
||||
module_issues = ModuleIssue.objects.filter(**self.filters["base_filters"], module_id=module_id).values_list(
|
||||
"issue_id", flat=True
|
||||
)
|
||||
base_queryset = Issue.issue_objects.filter(id__in=module_issues)
|
||||
else:
|
||||
base_queryset = Issue.issue_objects.filter(
|
||||
**self.filters["base_filters"], project_id=project_id
|
||||
)
|
||||
base_queryset = Issue.issue_objects.filter(**self.filters["base_filters"], project_id=project_id)
|
||||
|
||||
return {
|
||||
"total_work_items": self.get_filtered_counts(base_queryset),
|
||||
"started_work_items": self.get_filtered_counts(
|
||||
base_queryset.filter(state__group="started")
|
||||
),
|
||||
"backlog_work_items": self.get_filtered_counts(
|
||||
base_queryset.filter(state__group="backlog")
|
||||
),
|
||||
"un_started_work_items": self.get_filtered_counts(
|
||||
base_queryset.filter(state__group="unstarted")
|
||||
),
|
||||
"completed_work_items": self.get_filtered_counts(
|
||||
base_queryset.filter(state__group="completed")
|
||||
),
|
||||
"started_work_items": self.get_filtered_counts(base_queryset.filter(state__group="started")),
|
||||
"backlog_work_items": self.get_filtered_counts(base_queryset.filter(state__group="backlog")),
|
||||
"un_started_work_items": self.get_filtered_counts(base_queryset.filter(state__group="unstarted")),
|
||||
"completed_work_items": self.get_filtered_counts(base_queryset.filter(state__group="completed")),
|
||||
}
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
|
|
@ -101,9 +85,7 @@ class ProjectAdvanceAnalyticsEndpoint(ProjectAdvanceAnalyticsBaseView):
|
|||
cycle_id = request.GET.get("cycle_id", None)
|
||||
module_id = request.GET.get("module_id", None)
|
||||
return Response(
|
||||
self.get_work_items_stats(
|
||||
cycle_id=cycle_id, module_id=module_id, project_id=project_id
|
||||
),
|
||||
self.get_work_items_stats(cycle_id=cycle_id, module_id=module_id, project_id=project_id),
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
|
|
@ -116,9 +98,7 @@ class ProjectAdvanceAnalyticsStatsEndpoint(ProjectAdvanceAnalyticsBaseView):
|
|||
# Apply date range filter if available
|
||||
if self.filters["chart_period_range"]:
|
||||
start_date, end_date = self.filters["chart_period_range"]
|
||||
base_queryset = base_queryset.filter(
|
||||
created_at__date__gte=start_date, created_at__date__lte=end_date
|
||||
)
|
||||
base_queryset = base_queryset.filter(created_at__date__gte=start_date, created_at__date__lte=end_date)
|
||||
|
||||
return (
|
||||
base_queryset.values("project_id", "project__name")
|
||||
|
|
@ -132,24 +112,20 @@ class ProjectAdvanceAnalyticsStatsEndpoint(ProjectAdvanceAnalyticsBaseView):
|
|||
.order_by("project_id")
|
||||
)
|
||||
|
||||
def get_work_items_stats(
|
||||
self, project_id, cycle_id=None, module_id=None
|
||||
) -> Dict[str, Dict[str, int]]:
|
||||
def get_work_items_stats(self, project_id, cycle_id=None, module_id=None) -> Dict[str, Dict[str, int]]:
|
||||
base_queryset = None
|
||||
if cycle_id is not None:
|
||||
cycle_issues = CycleIssue.objects.filter(
|
||||
**self.filters["base_filters"], cycle_id=cycle_id
|
||||
).values_list("issue_id", flat=True)
|
||||
cycle_issues = CycleIssue.objects.filter(**self.filters["base_filters"], cycle_id=cycle_id).values_list(
|
||||
"issue_id", flat=True
|
||||
)
|
||||
base_queryset = Issue.issue_objects.filter(id__in=cycle_issues)
|
||||
elif module_id is not None:
|
||||
module_issues = ModuleIssue.objects.filter(
|
||||
**self.filters["base_filters"], module_id=module_id
|
||||
).values_list("issue_id", flat=True)
|
||||
module_issues = ModuleIssue.objects.filter(**self.filters["base_filters"], module_id=module_id).values_list(
|
||||
"issue_id", flat=True
|
||||
)
|
||||
base_queryset = Issue.issue_objects.filter(id__in=module_issues)
|
||||
else:
|
||||
base_queryset = Issue.issue_objects.filter(
|
||||
**self.filters["base_filters"], project_id=project_id
|
||||
)
|
||||
base_queryset = Issue.issue_objects.filter(**self.filters["base_filters"], project_id=project_id)
|
||||
return (
|
||||
base_queryset.annotate(display_name=F("assignees__display_name"))
|
||||
.annotate(assignee_id=F("assignees__id"))
|
||||
|
|
@ -166,30 +142,18 @@ class ProjectAdvanceAnalyticsStatsEndpoint(ProjectAdvanceAnalyticsBaseView):
|
|||
),
|
||||
),
|
||||
# If `avatar_asset` is None, fall back to using `avatar` field directly
|
||||
When(
|
||||
assignees__avatar_asset__isnull=True, then="assignees__avatar"
|
||||
),
|
||||
When(assignees__avatar_asset__isnull=True, then="assignees__avatar"),
|
||||
default=Value(None),
|
||||
output_field=models.CharField(),
|
||||
)
|
||||
)
|
||||
.values("display_name", "assignee_id", "avatar_url")
|
||||
.annotate(
|
||||
cancelled_work_items=Count(
|
||||
"id", filter=Q(state__group="cancelled"), distinct=True
|
||||
),
|
||||
completed_work_items=Count(
|
||||
"id", filter=Q(state__group="completed"), distinct=True
|
||||
),
|
||||
backlog_work_items=Count(
|
||||
"id", filter=Q(state__group="backlog"), distinct=True
|
||||
),
|
||||
un_started_work_items=Count(
|
||||
"id", filter=Q(state__group="unstarted"), distinct=True
|
||||
),
|
||||
started_work_items=Count(
|
||||
"id", filter=Q(state__group="started"), distinct=True
|
||||
),
|
||||
cancelled_work_items=Count("id", filter=Q(state__group="cancelled"), distinct=True),
|
||||
completed_work_items=Count("id", filter=Q(state__group="completed"), distinct=True),
|
||||
backlog_work_items=Count("id", filter=Q(state__group="backlog"), distinct=True),
|
||||
un_started_work_items=Count("id", filter=Q(state__group="unstarted"), distinct=True),
|
||||
started_work_items=Count("id", filter=Q(state__group="started"), distinct=True),
|
||||
)
|
||||
.order_by("display_name")
|
||||
)
|
||||
|
|
@ -204,9 +168,7 @@ class ProjectAdvanceAnalyticsStatsEndpoint(ProjectAdvanceAnalyticsBaseView):
|
|||
cycle_id = request.GET.get("cycle_id", None)
|
||||
module_id = request.GET.get("module_id", None)
|
||||
return Response(
|
||||
self.get_work_items_stats(
|
||||
project_id=project_id, cycle_id=cycle_id, module_id=module_id
|
||||
),
|
||||
self.get_work_items_stats(project_id=project_id, cycle_id=cycle_id, module_id=module_id),
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
|
|
@ -214,23 +176,19 @@ class ProjectAdvanceAnalyticsStatsEndpoint(ProjectAdvanceAnalyticsBaseView):
|
|||
|
||||
|
||||
class ProjectAdvanceAnalyticsChartEndpoint(ProjectAdvanceAnalyticsBaseView):
|
||||
def work_item_completion_chart(
|
||||
self, project_id, cycle_id=None, module_id=None
|
||||
) -> Dict[str, Any]:
|
||||
def work_item_completion_chart(self, project_id, cycle_id=None, module_id=None) -> Dict[str, Any]:
|
||||
# Get the base queryset
|
||||
queryset = (
|
||||
Issue.issue_objects.filter(**self.filters["base_filters"])
|
||||
.filter(project_id=project_id)
|
||||
.select_related("workspace", "state", "parent")
|
||||
.prefetch_related(
|
||||
"assignees", "labels", "issue_module__module", "issue_cycle__cycle"
|
||||
)
|
||||
.prefetch_related("assignees", "labels", "issue_module__module", "issue_cycle__cycle")
|
||||
)
|
||||
|
||||
if cycle_id is not None:
|
||||
cycle_issues = CycleIssue.objects.filter(
|
||||
**self.filters["base_filters"], cycle_id=cycle_id
|
||||
).values_list("issue_id", flat=True)
|
||||
cycle_issues = CycleIssue.objects.filter(**self.filters["base_filters"], cycle_id=cycle_id).values_list(
|
||||
"issue_id", flat=True
|
||||
)
|
||||
cycle = Cycle.objects.filter(id=cycle_id).first()
|
||||
if cycle and cycle.start_date:
|
||||
start_date = cycle.start_date.date()
|
||||
|
|
@ -240,9 +198,9 @@ class ProjectAdvanceAnalyticsChartEndpoint(ProjectAdvanceAnalyticsBaseView):
|
|||
queryset = cycle_issues
|
||||
|
||||
elif module_id is not None:
|
||||
module_issues = ModuleIssue.objects.filter(
|
||||
**self.filters["base_filters"], module_id=module_id
|
||||
).values_list("issue_id", flat=True)
|
||||
module_issues = ModuleIssue.objects.filter(**self.filters["base_filters"], module_id=module_id).values_list(
|
||||
"issue_id", flat=True
|
||||
)
|
||||
module = Module.objects.filter(id=module_id).first()
|
||||
if module and module.start_date:
|
||||
start_date = module.start_date
|
||||
|
|
@ -264,9 +222,7 @@ class ProjectAdvanceAnalyticsChartEndpoint(ProjectAdvanceAnalyticsBaseView):
|
|||
queryset.values("created_at__date")
|
||||
.annotate(
|
||||
created_count=Count("id"),
|
||||
completed_count=Count(
|
||||
"id", filter=Q(issue__state__group="completed")
|
||||
),
|
||||
completed_count=Count("id", filter=Q(issue__state__group="completed")),
|
||||
)
|
||||
.order_by("created_at__date")
|
||||
)
|
||||
|
|
@ -285,9 +241,7 @@ class ProjectAdvanceAnalyticsChartEndpoint(ProjectAdvanceAnalyticsBaseView):
|
|||
current_date = start_date
|
||||
while current_date <= end_date:
|
||||
date_str = current_date.strftime("%Y-%m-%d")
|
||||
stats = stats_dict.get(
|
||||
date_str, {"created_count": 0, "completed_count": 0}
|
||||
)
|
||||
stats = stats_dict.get(date_str, {"created_count": 0, "completed_count": 0})
|
||||
data.append(
|
||||
{
|
||||
"key": date_str,
|
||||
|
|
@ -302,9 +256,7 @@ class ProjectAdvanceAnalyticsChartEndpoint(ProjectAdvanceAnalyticsBaseView):
|
|||
# Apply date range filter if available
|
||||
if self.filters["chart_period_range"]:
|
||||
start_date, end_date = self.filters["chart_period_range"]
|
||||
queryset = queryset.filter(
|
||||
created_at__date__gte=start_date, created_at__date__lte=end_date
|
||||
)
|
||||
queryset = queryset.filter(created_at__date__gte=start_date, created_at__date__lte=end_date)
|
||||
|
||||
# Annotate by month and count
|
||||
monthly_stats = (
|
||||
|
|
@ -335,9 +287,7 @@ class ProjectAdvanceAnalyticsChartEndpoint(ProjectAdvanceAnalyticsBaseView):
|
|||
|
||||
while current_month <= last_month:
|
||||
date_str = current_month.strftime("%Y-%m-%d")
|
||||
stats = stats_dict.get(
|
||||
date_str, {"created_count": 0, "completed_count": 0}
|
||||
)
|
||||
stats = stats_dict.get(date_str, {"created_count": 0, "completed_count": 0})
|
||||
data.append(
|
||||
{
|
||||
"key": date_str,
|
||||
|
|
@ -349,9 +299,7 @@ class ProjectAdvanceAnalyticsChartEndpoint(ProjectAdvanceAnalyticsBaseView):
|
|||
)
|
||||
# Move to next month
|
||||
if current_month.month == 12:
|
||||
current_month = current_month.replace(
|
||||
year=current_month.year + 1, month=1
|
||||
)
|
||||
current_month = current_month.replace(year=current_month.year + 1, month=1)
|
||||
else:
|
||||
current_month = current_month.replace(month=current_month.month + 1)
|
||||
|
||||
|
|
@ -376,16 +324,14 @@ class ProjectAdvanceAnalyticsChartEndpoint(ProjectAdvanceAnalyticsBaseView):
|
|||
Issue.issue_objects.filter(**self.filters["base_filters"])
|
||||
.filter(project_id=project_id)
|
||||
.select_related("workspace", "state", "parent")
|
||||
.prefetch_related(
|
||||
"assignees", "labels", "issue_module__module", "issue_cycle__cycle"
|
||||
)
|
||||
.prefetch_related("assignees", "labels", "issue_module__module", "issue_cycle__cycle")
|
||||
)
|
||||
|
||||
# Apply cycle/module filters if present
|
||||
if cycle_id is not None:
|
||||
cycle_issues = CycleIssue.objects.filter(
|
||||
**self.filters["base_filters"], cycle_id=cycle_id
|
||||
).values_list("issue_id", flat=True)
|
||||
cycle_issues = CycleIssue.objects.filter(**self.filters["base_filters"], cycle_id=cycle_id).values_list(
|
||||
"issue_id", flat=True
|
||||
)
|
||||
queryset = queryset.filter(id__in=cycle_issues)
|
||||
|
||||
elif module_id is not None:
|
||||
|
|
@ -397,9 +343,7 @@ class ProjectAdvanceAnalyticsChartEndpoint(ProjectAdvanceAnalyticsBaseView):
|
|||
# Apply date range filter if available
|
||||
if self.filters["chart_period_range"]:
|
||||
start_date, end_date = self.filters["chart_period_range"]
|
||||
queryset = queryset.filter(
|
||||
created_at__date__gte=start_date, created_at__date__lte=end_date
|
||||
)
|
||||
queryset = queryset.filter(created_at__date__gte=start_date, created_at__date__lte=end_date)
|
||||
|
||||
return Response(
|
||||
build_analytics_chart(queryset, x_axis, group_by),
|
||||
|
|
@ -412,9 +356,7 @@ class ProjectAdvanceAnalyticsChartEndpoint(ProjectAdvanceAnalyticsBaseView):
|
|||
module_id = request.GET.get("module_id", None)
|
||||
|
||||
return Response(
|
||||
self.work_item_completion_chart(
|
||||
project_id=project_id, cycle_id=cycle_id, module_id=module_id
|
||||
),
|
||||
self.work_item_completion_chart(project_id=project_id, cycle_id=cycle_id, module_id=module_id),
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -65,9 +65,7 @@ class ServiceApiTokenEndpoint(BaseAPIView):
|
|||
def post(self, request: Request, slug: str) -> Response:
|
||||
workspace = Workspace.objects.get(slug=slug)
|
||||
|
||||
api_token = APIToken.objects.filter(
|
||||
workspace=workspace, is_service=True
|
||||
).first()
|
||||
api_token = APIToken.objects.filter(workspace=workspace, is_service=True).first()
|
||||
|
||||
if api_token:
|
||||
return Response({"token": str(api_token.token)}, status=status.HTTP_200_OK)
|
||||
|
|
@ -83,6 +81,4 @@ class ServiceApiTokenEndpoint(BaseAPIView):
|
|||
user_type=user_type,
|
||||
is_service=True,
|
||||
)
|
||||
return Response(
|
||||
{"token": str(api_token.token)}, status=status.HTTP_201_CREATED
|
||||
)
|
||||
return Response({"token": str(api_token.token)}, status=status.HTTP_201_CREATED)
|
||||
|
|
|
|||
|
|
@ -20,12 +20,8 @@ class FileAssetEndpoint(BaseAPIView):
|
|||
asset_key = str(workspace_id) + "/" + asset_key
|
||||
files = FileAsset.objects.filter(asset=asset_key)
|
||||
if files.exists():
|
||||
serializer = FileAssetSerializer(
|
||||
files, context={"request": request}, many=True
|
||||
)
|
||||
return Response(
|
||||
{"data": serializer.data, "status": True}, status=status.HTTP_200_OK
|
||||
)
|
||||
serializer = FileAssetSerializer(files, context={"request": request}, many=True)
|
||||
return Response({"data": serializer.data, "status": True}, status=status.HTTP_200_OK)
|
||||
else:
|
||||
return Response(
|
||||
{"error": "Asset key does not exist", "status": False},
|
||||
|
|
@ -65,9 +61,7 @@ class UserAssetsEndpoint(BaseAPIView):
|
|||
files = FileAsset.objects.filter(asset=asset_key, created_by=request.user)
|
||||
if files.exists():
|
||||
serializer = FileAssetSerializer(files, context={"request": request})
|
||||
return Response(
|
||||
{"data": serializer.data, "status": True}, status=status.HTTP_200_OK
|
||||
)
|
||||
return Response({"data": serializer.data, "status": True}, status=status.HTTP_200_OK)
|
||||
else:
|
||||
return Response(
|
||||
{"error": "Asset key does not exist", "status": False},
|
||||
|
|
|
|||
|
|
@ -44,9 +44,7 @@ class UserAssetsV2Endpoint(BaseAPIView):
|
|||
# Save the new avatar
|
||||
user.avatar_asset_id = asset_id
|
||||
user.save()
|
||||
invalidate_cache_directly(
|
||||
path="/api/users/me/", url_params=False, user=True, request=request
|
||||
)
|
||||
invalidate_cache_directly(path="/api/users/me/", url_params=False, user=True, request=request)
|
||||
invalidate_cache_directly(
|
||||
path="/api/users/me/settings/",
|
||||
url_params=False,
|
||||
|
|
@ -64,9 +62,7 @@ class UserAssetsV2Endpoint(BaseAPIView):
|
|||
# Save the new cover image
|
||||
user.cover_image_asset_id = asset_id
|
||||
user.save()
|
||||
invalidate_cache_directly(
|
||||
path="/api/users/me/", url_params=False, user=True, request=request
|
||||
)
|
||||
invalidate_cache_directly(path="/api/users/me/", url_params=False, user=True, request=request)
|
||||
invalidate_cache_directly(
|
||||
path="/api/users/me/settings/",
|
||||
url_params=False,
|
||||
|
|
@ -82,9 +78,7 @@ class UserAssetsV2Endpoint(BaseAPIView):
|
|||
user = User.objects.get(id=asset.user_id)
|
||||
user.avatar_asset_id = None
|
||||
user.save()
|
||||
invalidate_cache_directly(
|
||||
path="/api/users/me/", url_params=False, user=True, request=request
|
||||
)
|
||||
invalidate_cache_directly(path="/api/users/me/", url_params=False, user=True, request=request)
|
||||
invalidate_cache_directly(
|
||||
path="/api/users/me/settings/",
|
||||
url_params=False,
|
||||
|
|
@ -97,9 +91,7 @@ class UserAssetsV2Endpoint(BaseAPIView):
|
|||
user = User.objects.get(id=asset.user_id)
|
||||
user.cover_image_asset_id = None
|
||||
user.save()
|
||||
invalidate_cache_directly(
|
||||
path="/api/users/me/", url_params=False, user=True, request=request
|
||||
)
|
||||
invalidate_cache_directly(path="/api/users/me/", url_params=False, user=True, request=request)
|
||||
invalidate_cache_directly(
|
||||
path="/api/users/me/settings/",
|
||||
url_params=False,
|
||||
|
|
@ -159,9 +151,7 @@ class UserAssetsV2Endpoint(BaseAPIView):
|
|||
# Get the presigned URL
|
||||
storage = S3Storage(request=request)
|
||||
# Generate a presigned URL to share an S3 object
|
||||
presigned_url = storage.generate_presigned_post(
|
||||
object_name=asset_key, file_type=type, file_size=size_limit
|
||||
)
|
||||
presigned_url = storage.generate_presigned_post(object_name=asset_key, file_type=type, file_size=size_limit)
|
||||
# Return the presigned URL
|
||||
return Response(
|
||||
{
|
||||
|
|
@ -198,9 +188,7 @@ class UserAssetsV2Endpoint(BaseAPIView):
|
|||
asset.is_deleted = True
|
||||
asset.deleted_at = timezone.now()
|
||||
# get the entity and save the asset id for the request field
|
||||
self.entity_asset_delete(
|
||||
entity_type=asset.entity_type, asset=asset, request=request
|
||||
)
|
||||
self.entity_asset_delete(entity_type=asset.entity_type, asset=asset, request=request)
|
||||
asset.save(update_fields=["is_deleted", "deleted_at"])
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
|
@ -264,18 +252,14 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
|
|||
workspace.logo = ""
|
||||
workspace.logo_asset_id = asset_id
|
||||
workspace.save()
|
||||
invalidate_cache_directly(
|
||||
path="/api/workspaces/", url_params=False, user=False, request=request
|
||||
)
|
||||
invalidate_cache_directly(path="/api/workspaces/", url_params=False, user=False, request=request)
|
||||
invalidate_cache_directly(
|
||||
path="/api/users/me/workspaces/",
|
||||
url_params=False,
|
||||
user=True,
|
||||
request=request,
|
||||
)
|
||||
invalidate_cache_directly(
|
||||
path="/api/instances/", url_params=False, user=False, request=request
|
||||
)
|
||||
invalidate_cache_directly(path="/api/instances/", url_params=False, user=False, request=request)
|
||||
return
|
||||
|
||||
# Project Cover
|
||||
|
|
@ -302,18 +286,14 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
|
|||
return
|
||||
workspace.logo_asset_id = None
|
||||
workspace.save()
|
||||
invalidate_cache_directly(
|
||||
path="/api/workspaces/", url_params=False, user=False, request=request
|
||||
)
|
||||
invalidate_cache_directly(path="/api/workspaces/", url_params=False, user=False, request=request)
|
||||
invalidate_cache_directly(
|
||||
path="/api/users/me/workspaces/",
|
||||
url_params=False,
|
||||
user=True,
|
||||
request=request,
|
||||
)
|
||||
invalidate_cache_directly(
|
||||
path="/api/instances/", url_params=False, user=False, request=request
|
||||
)
|
||||
invalidate_cache_directly(path="/api/instances/", url_params=False, user=False, request=request)
|
||||
return
|
||||
# Project Cover
|
||||
elif entity_type == FileAsset.EntityTypeContext.PROJECT_COVER:
|
||||
|
|
@ -374,17 +354,13 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
|
|||
workspace=workspace,
|
||||
created_by=request.user,
|
||||
entity_type=entity_type,
|
||||
**self.get_entity_id_field(
|
||||
entity_type=entity_type, entity_id=entity_identifier
|
||||
),
|
||||
**self.get_entity_id_field(entity_type=entity_type, entity_id=entity_identifier),
|
||||
)
|
||||
|
||||
# Get the presigned URL
|
||||
storage = S3Storage(request=request)
|
||||
# Generate a presigned URL to share an S3 object
|
||||
presigned_url = storage.generate_presigned_post(
|
||||
object_name=asset_key, file_type=type, file_size=size_limit
|
||||
)
|
||||
presigned_url = storage.generate_presigned_post(object_name=asset_key, file_type=type, file_size=size_limit)
|
||||
# Return the presigned URL
|
||||
return Response(
|
||||
{
|
||||
|
|
@ -421,9 +397,7 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
|
|||
asset.is_deleted = True
|
||||
asset.deleted_at = timezone.now()
|
||||
# get the entity and save the asset id for the request field
|
||||
self.entity_asset_delete(
|
||||
entity_type=asset.entity_type, asset=asset, request=request
|
||||
)
|
||||
self.entity_asset_delete(entity_type=asset.entity_type, asset=asset, request=request)
|
||||
asset.save(update_fields=["is_deleted", "deleted_at"])
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
|
@ -586,9 +560,7 @@ class ProjectAssetEndpoint(BaseAPIView):
|
|||
# Get the presigned URL
|
||||
storage = S3Storage(request=request)
|
||||
# Generate a presigned URL to share an S3 object
|
||||
presigned_url = storage.generate_presigned_post(
|
||||
object_name=asset_key, file_type=type, file_size=size_limit
|
||||
)
|
||||
presigned_url = storage.generate_presigned_post(object_name=asset_key, file_type=type, file_size=size_limit)
|
||||
# Return the presigned URL
|
||||
return Response(
|
||||
{
|
||||
|
|
@ -618,9 +590,7 @@ class ProjectAssetEndpoint(BaseAPIView):
|
|||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def delete(self, request, slug, project_id, pk):
|
||||
# Get the asset
|
||||
asset = FileAsset.objects.get(
|
||||
id=pk, workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
asset = FileAsset.objects.get(id=pk, workspace__slug=slug, project_id=project_id)
|
||||
# Check deleted assets
|
||||
asset.is_deleted = True
|
||||
asset.deleted_at = timezone.now()
|
||||
|
|
@ -631,9 +601,7 @@ class ProjectAssetEndpoint(BaseAPIView):
|
|||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def get(self, request, slug, project_id, pk):
|
||||
# get the asset id
|
||||
asset = FileAsset.objects.get(
|
||||
workspace__slug=slug, project_id=project_id, pk=pk
|
||||
)
|
||||
asset = FileAsset.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
|
||||
|
||||
# Check if the asset is uploaded
|
||||
if not asset.is_uploaded:
|
||||
|
|
@ -666,9 +634,7 @@ class ProjectBulkAssetEndpoint(BaseAPIView):
|
|||
|
||||
# Check if the asset ids are provided
|
||||
if not asset_ids:
|
||||
return Response(
|
||||
{"error": "No asset ids provided."}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response({"error": "No asset ids provided."}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# get the asset id
|
||||
assets = FileAsset.objects.filter(id__in=asset_ids, workspace__slug=slug)
|
||||
|
|
@ -722,9 +688,7 @@ class AssetCheckEndpoint(BaseAPIView):
|
|||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def get(self, request, slug, asset_id):
|
||||
asset = FileAsset.all_objects.filter(
|
||||
id=asset_id, workspace__slug=slug, deleted_at__isnull=True
|
||||
).exists()
|
||||
asset = FileAsset.all_objects.filter(id=asset_id, workspace__slug=slug, deleted_at__isnull=True).exists()
|
||||
return Response({"exists": asset}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -72,11 +72,7 @@ class BaseViewSet(TimezoneMixin, ReadReplicaControlMixin, ModelViewSet, BasePagi
|
|||
response = super().handle_exception(exc)
|
||||
return response
|
||||
except Exception as e:
|
||||
(
|
||||
print(e, traceback.format_exc())
|
||||
if settings.DEBUG
|
||||
else print("Server Error")
|
||||
)
|
||||
(print(e, traceback.format_exc()) if settings.DEBUG else print("Server Error"))
|
||||
if isinstance(e, IntegrityError):
|
||||
return Response(
|
||||
{"error": "The payload is not valid"},
|
||||
|
|
@ -115,9 +111,7 @@ class BaseViewSet(TimezoneMixin, ReadReplicaControlMixin, ModelViewSet, BasePagi
|
|||
if settings.DEBUG:
|
||||
from django.db import connection
|
||||
|
||||
print(
|
||||
f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}"
|
||||
)
|
||||
print(f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}")
|
||||
|
||||
return response
|
||||
except Exception as exc:
|
||||
|
|
@ -139,16 +133,12 @@ class BaseViewSet(TimezoneMixin, ReadReplicaControlMixin, ModelViewSet, BasePagi
|
|||
|
||||
@property
|
||||
def fields(self):
|
||||
fields = [
|
||||
field for field in self.request.GET.get("fields", "").split(",") if field
|
||||
]
|
||||
fields = [field for field in self.request.GET.get("fields", "").split(",") if field]
|
||||
return fields if fields else None
|
||||
|
||||
@property
|
||||
def expand(self):
|
||||
expand = [
|
||||
expand for expand in self.request.GET.get("expand", "").split(",") if expand
|
||||
]
|
||||
expand = [expand for expand in self.request.GET.get("expand", "").split(",") if expand]
|
||||
return expand if expand else None
|
||||
|
||||
|
||||
|
|
@ -216,9 +206,7 @@ class BaseAPIView(TimezoneMixin, ReadReplicaControlMixin, APIView, BasePaginator
|
|||
if settings.DEBUG:
|
||||
from django.db import connection
|
||||
|
||||
print(
|
||||
f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}"
|
||||
)
|
||||
print(f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}")
|
||||
return response
|
||||
|
||||
except Exception as exc:
|
||||
|
|
@ -235,14 +223,10 @@ class BaseAPIView(TimezoneMixin, ReadReplicaControlMixin, APIView, BasePaginator
|
|||
|
||||
@property
|
||||
def fields(self):
|
||||
fields = [
|
||||
field for field in self.request.GET.get("fields", "").split(",") if field
|
||||
]
|
||||
fields = [field for field in self.request.GET.get("fields", "").split(",") if field]
|
||||
return fields if fields else None
|
||||
|
||||
@property
|
||||
def expand(self):
|
||||
expand = [
|
||||
expand for expand in self.request.GET.get("expand", "").split(",") if expand
|
||||
]
|
||||
expand = [expand for expand in self.request.GET.get("expand", "").split(",") if expand]
|
||||
return expand if expand else None
|
||||
|
|
|
|||
|
|
@ -50,9 +50,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
issue_cycle__deleted_at__isnull=True,
|
||||
)
|
||||
.values("issue_cycle__cycle_id")
|
||||
.annotate(
|
||||
backlog_estimate_point=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.annotate(backlog_estimate_point=Sum(Cast("estimate_point__value", FloatField())))
|
||||
.values("backlog_estimate_point")[:1]
|
||||
)
|
||||
unstarted_estimate_point = (
|
||||
|
|
@ -63,11 +61,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
issue_cycle__deleted_at__isnull=True,
|
||||
)
|
||||
.values("issue_cycle__cycle_id")
|
||||
.annotate(
|
||||
unstarted_estimate_point=Sum(
|
||||
Cast("estimate_point__value", FloatField())
|
||||
)
|
||||
)
|
||||
.annotate(unstarted_estimate_point=Sum(Cast("estimate_point__value", FloatField())))
|
||||
.values("unstarted_estimate_point")[:1]
|
||||
)
|
||||
started_estimate_point = (
|
||||
|
|
@ -78,9 +72,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
issue_cycle__deleted_at__isnull=True,
|
||||
)
|
||||
.values("issue_cycle__cycle_id")
|
||||
.annotate(
|
||||
started_estimate_point=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.annotate(started_estimate_point=Sum(Cast("estimate_point__value", FloatField())))
|
||||
.values("started_estimate_point")[:1]
|
||||
)
|
||||
cancelled_estimate_point = (
|
||||
|
|
@ -91,11 +83,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
issue_cycle__deleted_at__isnull=True,
|
||||
)
|
||||
.values("issue_cycle__cycle_id")
|
||||
.annotate(
|
||||
cancelled_estimate_point=Sum(
|
||||
Cast("estimate_point__value", FloatField())
|
||||
)
|
||||
)
|
||||
.annotate(cancelled_estimate_point=Sum(Cast("estimate_point__value", FloatField())))
|
||||
.values("cancelled_estimate_point")[:1]
|
||||
)
|
||||
completed_estimate_point = (
|
||||
|
|
@ -106,11 +94,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
issue_cycle__deleted_at__isnull=True,
|
||||
)
|
||||
.values("issue_cycle__cycle_id")
|
||||
.annotate(
|
||||
completed_estimate_points=Sum(
|
||||
Cast("estimate_point__value", FloatField())
|
||||
)
|
||||
)
|
||||
.annotate(completed_estimate_points=Sum(Cast("estimate_point__value", FloatField())))
|
||||
.values("completed_estimate_points")[:1]
|
||||
)
|
||||
total_estimate_point = (
|
||||
|
|
@ -120,9 +104,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
issue_cycle__deleted_at__isnull=True,
|
||||
)
|
||||
.values("issue_cycle__cycle_id")
|
||||
.annotate(
|
||||
total_estimate_points=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.annotate(total_estimate_points=Sum(Cast("estimate_point__value", FloatField())))
|
||||
.values("total_estimate_points")[:1]
|
||||
)
|
||||
return (
|
||||
|
|
@ -138,9 +120,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
.prefetch_related(
|
||||
Prefetch(
|
||||
"issue_cycle__issue__assignees",
|
||||
queryset=User.objects.only(
|
||||
"avatar_asset", "first_name", "id"
|
||||
).distinct(),
|
||||
queryset=User.objects.only("avatar_asset", "first_name", "id").distinct(),
|
||||
)
|
||||
)
|
||||
.prefetch_related(
|
||||
|
|
@ -224,8 +204,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
.annotate(
|
||||
status=Case(
|
||||
When(
|
||||
Q(start_date__lte=timezone.now())
|
||||
& Q(end_date__gte=timezone.now()),
|
||||
Q(start_date__lte=timezone.now()) & Q(end_date__gte=timezone.now()),
|
||||
then=Value("CURRENT"),
|
||||
),
|
||||
When(start_date__gt=timezone.now(), then=Value("UPCOMING")),
|
||||
|
|
@ -279,9 +258,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
)
|
||||
)
|
||||
.annotate(
|
||||
total_estimate_points=Coalesce(
|
||||
Subquery(total_estimate_point), Value(0, output_field=FloatField())
|
||||
)
|
||||
total_estimate_points=Coalesce(Subquery(total_estimate_point), Value(0, output_field=FloatField()))
|
||||
)
|
||||
.order_by("-is_favorite", "name")
|
||||
.distinct()
|
||||
|
|
@ -322,9 +299,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
).order_by("-is_favorite", "-created_at")
|
||||
return Response(queryset, status=status.HTTP_200_OK)
|
||||
else:
|
||||
queryset = (
|
||||
self.get_queryset().filter(archived_at__isnull=False).filter(pk=pk)
|
||||
)
|
||||
queryset = self.get_queryset().filter(archived_at__isnull=False).filter(pk=pk)
|
||||
data = (
|
||||
self.get_queryset()
|
||||
.filter(pk=pk)
|
||||
|
|
@ -415,9 +390,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
)
|
||||
)
|
||||
.values("display_name", "assignee_id", "avatar_url")
|
||||
.annotate(
|
||||
total_estimates=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.annotate(total_estimates=Sum(Cast("estimate_point__value", FloatField())))
|
||||
.annotate(
|
||||
completed_estimates=Sum(
|
||||
Cast("estimate_point__value", FloatField()),
|
||||
|
|
@ -452,9 +425,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
.annotate(color=F("labels__color"))
|
||||
.annotate(label_id=F("labels__id"))
|
||||
.values("label_name", "color", "label_id")
|
||||
.annotate(
|
||||
total_estimates=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.annotate(total_estimates=Sum(Cast("estimate_point__value", FloatField())))
|
||||
.annotate(
|
||||
completed_estimates=Sum(
|
||||
Cast("estimate_point__value", FloatField()),
|
||||
|
|
@ -531,11 +502,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
"avatar_url",
|
||||
"display_name",
|
||||
)
|
||||
.annotate(
|
||||
total_issues=Count(
|
||||
"id", filter=Q(archived_at__isnull=True, is_draft=False)
|
||||
)
|
||||
)
|
||||
.annotate(total_issues=Count("id", filter=Q(archived_at__isnull=True, is_draft=False)))
|
||||
.annotate(
|
||||
completed_issues=Count(
|
||||
"id",
|
||||
|
|
@ -571,11 +538,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
.annotate(color=F("labels__color"))
|
||||
.annotate(label_id=F("labels__id"))
|
||||
.values("label_name", "color", "label_id")
|
||||
.annotate(
|
||||
total_issues=Count(
|
||||
"id", filter=Q(archived_at__isnull=True, is_draft=False)
|
||||
)
|
||||
)
|
||||
.annotate(total_issues=Count("id", filter=Q(archived_at__isnull=True, is_draft=False)))
|
||||
.annotate(
|
||||
completed_issues=Count(
|
||||
"id",
|
||||
|
|
@ -618,9 +581,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def post(self, request, slug, project_id, cycle_id):
|
||||
cycle = Cycle.objects.get(
|
||||
pk=cycle_id, project_id=project_id, workspace__slug=slug
|
||||
)
|
||||
cycle = Cycle.objects.get(pk=cycle_id, project_id=project_id, workspace__slug=slug)
|
||||
|
||||
if cycle.end_date >= timezone.now():
|
||||
return Response(
|
||||
|
|
@ -636,15 +597,11 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
project_id=project_id,
|
||||
workspace__slug=slug,
|
||||
).delete()
|
||||
return Response(
|
||||
{"archived_at": str(cycle.archived_at)}, status=status.HTTP_200_OK
|
||||
)
|
||||
return Response({"archived_at": str(cycle.archived_at)}, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def delete(self, request, slug, project_id, cycle_id):
|
||||
cycle = Cycle.objects.get(
|
||||
pk=cycle_id, project_id=project_id, workspace__slug=slug
|
||||
)
|
||||
cycle = Cycle.objects.get(pk=cycle_id, project_id=project_id, workspace__slug=slug)
|
||||
cycle.archived_at = None
|
||||
cycle.save()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
|
|
|||
|
|
@ -46,7 +46,6 @@ from plane.db.models import (
|
|||
Label,
|
||||
User,
|
||||
Project,
|
||||
ProjectMember,
|
||||
UserRecentVisit,
|
||||
)
|
||||
from plane.utils.analytics_plot import burndown_plot
|
||||
|
|
@ -97,9 +96,7 @@ class CycleViewSet(BaseViewSet):
|
|||
.prefetch_related(
|
||||
Prefetch(
|
||||
"issue_cycle__issue__assignees",
|
||||
queryset=User.objects.only(
|
||||
"avatar_asset", "first_name", "id"
|
||||
).distinct(),
|
||||
queryset=User.objects.only("avatar_asset", "first_name", "id").distinct(),
|
||||
)
|
||||
)
|
||||
.prefetch_related(
|
||||
|
|
@ -150,8 +147,7 @@ class CycleViewSet(BaseViewSet):
|
|||
.annotate(
|
||||
status=Case(
|
||||
When(
|
||||
Q(start_date__lte=current_time_in_utc)
|
||||
& Q(end_date__gte=current_time_in_utc),
|
||||
Q(start_date__lte=current_time_in_utc) & Q(end_date__gte=current_time_in_utc),
|
||||
then=Value("CURRENT"),
|
||||
),
|
||||
When(start_date__gt=current_time_in_utc, then=Value("UPCOMING")),
|
||||
|
|
@ -170,11 +166,7 @@ class CycleViewSet(BaseViewSet):
|
|||
"issue_cycle__issue__assignees__id",
|
||||
distinct=True,
|
||||
filter=~Q(issue_cycle__issue__assignees__id__isnull=True)
|
||||
& (
|
||||
Q(
|
||||
issue_cycle__issue__issue_assignee__deleted_at__isnull=True
|
||||
)
|
||||
),
|
||||
& (Q(issue_cycle__issue__issue_assignee__deleted_at__isnull=True)),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
)
|
||||
|
|
@ -205,9 +197,7 @@ class CycleViewSet(BaseViewSet):
|
|||
|
||||
# Current Cycle
|
||||
if cycle_view == "current":
|
||||
queryset = queryset.filter(
|
||||
start_date__lte=current_time_in_utc, end_date__gte=current_time_in_utc
|
||||
)
|
||||
queryset = queryset.filter(start_date__lte=current_time_in_utc, end_date__gte=current_time_in_utc)
|
||||
|
||||
data = queryset.values(
|
||||
# necessary fields
|
||||
|
|
@ -274,16 +264,10 @@ class CycleViewSet(BaseViewSet):
|
|||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def create(self, request, slug, project_id):
|
||||
if (
|
||||
request.data.get("start_date", None) is None
|
||||
and request.data.get("end_date", None) is None
|
||||
) or (
|
||||
request.data.get("start_date", None) is not None
|
||||
and request.data.get("end_date", None) is not None
|
||||
if (request.data.get("start_date", None) is None and request.data.get("end_date", None) is None) or (
|
||||
request.data.get("start_date", None) is not None and request.data.get("end_date", None) is not None
|
||||
):
|
||||
serializer = CycleWriteSerializer(
|
||||
data=request.data, context={"project_id": project_id}
|
||||
)
|
||||
serializer = CycleWriteSerializer(data=request.data, context={"project_id": project_id})
|
||||
if serializer.is_valid():
|
||||
serializer.save(project_id=project_id, owned_by=request.user)
|
||||
cycle = (
|
||||
|
|
@ -323,9 +307,7 @@ class CycleViewSet(BaseViewSet):
|
|||
project_timezone = project.timezone
|
||||
|
||||
datetime_fields = ["start_date", "end_date"]
|
||||
cycle = user_timezone_converter(
|
||||
cycle, datetime_fields, project_timezone
|
||||
)
|
||||
cycle = user_timezone_converter(cycle, datetime_fields, project_timezone)
|
||||
|
||||
# Send the model activity
|
||||
model_activity.delay(
|
||||
|
|
@ -341,17 +323,13 @@ class CycleViewSet(BaseViewSet):
|
|||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
else:
|
||||
return Response(
|
||||
{
|
||||
"error": "Both start date and end date are either required or are to be null"
|
||||
},
|
||||
{"error": "Both start date and end date are either required or are to be null"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def partial_update(self, request, slug, project_id, pk):
|
||||
queryset = self.get_queryset().filter(
|
||||
workspace__slug=slug, project_id=project_id, pk=pk
|
||||
)
|
||||
queryset = self.get_queryset().filter(workspace__slug=slug, project_id=project_id, pk=pk)
|
||||
cycle = queryset.first()
|
||||
if cycle.archived_at:
|
||||
return Response(
|
||||
|
|
@ -359,29 +337,21 @@ class CycleViewSet(BaseViewSet):
|
|||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
current_instance = json.dumps(
|
||||
CycleSerializer(cycle).data, cls=DjangoJSONEncoder
|
||||
)
|
||||
current_instance = json.dumps(CycleSerializer(cycle).data, cls=DjangoJSONEncoder)
|
||||
|
||||
request_data = request.data
|
||||
|
||||
if cycle.end_date is not None and cycle.end_date < timezone.now():
|
||||
if "sort_order" in request_data:
|
||||
# Can only change sort order for a completed cycle``
|
||||
request_data = {
|
||||
"sort_order": request_data.get("sort_order", cycle.sort_order)
|
||||
}
|
||||
request_data = {"sort_order": request_data.get("sort_order", cycle.sort_order)}
|
||||
else:
|
||||
return Response(
|
||||
{
|
||||
"error": "The Cycle has already been completed so it cannot be edited"
|
||||
},
|
||||
{"error": "The Cycle has already been completed so it cannot be edited"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
serializer = CycleWriteSerializer(
|
||||
cycle, data=request.data, partial=True, context={"project_id": project_id}
|
||||
)
|
||||
serializer = CycleWriteSerializer(cycle, data=request.data, partial=True, context={"project_id": project_id})
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
cycle = queryset.values(
|
||||
|
|
@ -481,9 +451,7 @@ class CycleViewSet(BaseViewSet):
|
|||
)
|
||||
|
||||
if data is None:
|
||||
return Response(
|
||||
{"error": "Cycle not found"}, status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
return Response({"error": "Cycle not found"}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
queryset = queryset.first()
|
||||
# Fetch the project timezone
|
||||
|
|
@ -505,11 +473,7 @@ class CycleViewSet(BaseViewSet):
|
|||
def destroy(self, request, slug, project_id, pk):
|
||||
cycle = Cycle.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
|
||||
|
||||
cycle_issues = list(
|
||||
CycleIssue.objects.filter(cycle_id=self.kwargs.get("pk")).values_list(
|
||||
"issue", flat=True
|
||||
)
|
||||
)
|
||||
cycle_issues = list(CycleIssue.objects.filter(cycle_id=self.kwargs.get("pk")).values_list("issue", flat=True))
|
||||
|
||||
issue_activity.delay(
|
||||
type="cycle.activity.deleted",
|
||||
|
|
@ -560,9 +524,7 @@ class CycleDateCheckEndpoint(BaseAPIView):
|
|||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
start_date = convert_to_utc(
|
||||
date=str(start_date), project_id=project_id, is_start_date=True
|
||||
)
|
||||
start_date = convert_to_utc(date=str(start_date), project_id=project_id, is_start_date=True)
|
||||
end_date = convert_to_utc(
|
||||
date=str(end_date),
|
||||
project_id=project_id,
|
||||
|
|
@ -581,7 +543,7 @@ class CycleDateCheckEndpoint(BaseAPIView):
|
|||
if cycles.exists():
|
||||
return Response(
|
||||
{
|
||||
"error": "You have a cycle already on the given dates, if you want to create a draft cycle you can do that by removing dates",
|
||||
"error": "You have a cycle already on the given dates, if you want to create a draft cycle you can do that by removing dates", # noqa: E501
|
||||
"status": False,
|
||||
}
|
||||
)
|
||||
|
|
@ -635,14 +597,10 @@ class TransferCycleIssueEndpoint(BaseAPIView):
|
|||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
new_cycle = Cycle.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id, pk=new_cycle_id
|
||||
).first()
|
||||
new_cycle = Cycle.objects.filter(workspace__slug=slug, project_id=project_id, pk=new_cycle_id).first()
|
||||
|
||||
old_cycle = (
|
||||
Cycle.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id, pk=cycle_id
|
||||
)
|
||||
Cycle.objects.filter(workspace__slug=slug, project_id=project_id, pk=cycle_id)
|
||||
.annotate(
|
||||
total_issues=Count(
|
||||
"issue_cycle",
|
||||
|
|
@ -755,9 +713,7 @@ class TransferCycleIssueEndpoint(BaseAPIView):
|
|||
)
|
||||
)
|
||||
.values("display_name", "assignee_id", "avatar_url")
|
||||
.annotate(
|
||||
total_estimates=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.annotate(total_estimates=Sum(Cast("estimate_point__value", FloatField())))
|
||||
.annotate(
|
||||
completed_estimates=Sum(
|
||||
Cast("estimate_point__value", FloatField()),
|
||||
|
|
@ -784,9 +740,7 @@ class TransferCycleIssueEndpoint(BaseAPIView):
|
|||
assignee_estimate_distribution = [
|
||||
{
|
||||
"display_name": item["display_name"],
|
||||
"assignee_id": (
|
||||
str(item["assignee_id"]) if item["assignee_id"] else None
|
||||
),
|
||||
"assignee_id": (str(item["assignee_id"]) if item["assignee_id"] else None),
|
||||
"avatar": item.get("avatar"),
|
||||
"avatar_url": item.get("avatar_url"),
|
||||
"total_estimates": item["total_estimates"],
|
||||
|
|
@ -807,9 +761,7 @@ class TransferCycleIssueEndpoint(BaseAPIView):
|
|||
.annotate(color=F("labels__color"))
|
||||
.annotate(label_id=F("labels__id"))
|
||||
.values("label_name", "color", "label_id")
|
||||
.annotate(
|
||||
total_estimates=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.annotate(total_estimates=Sum(Cast("estimate_point__value", FloatField())))
|
||||
.annotate(
|
||||
completed_estimates=Sum(
|
||||
Cast("estimate_point__value", FloatField()),
|
||||
|
|
@ -875,19 +827,13 @@ class TransferCycleIssueEndpoint(BaseAPIView):
|
|||
),
|
||||
),
|
||||
# If `avatar_asset` is None, fall back to using `avatar` field directly
|
||||
When(
|
||||
assignees__avatar_asset__isnull=True, then="assignees__avatar"
|
||||
),
|
||||
When(assignees__avatar_asset__isnull=True, then="assignees__avatar"),
|
||||
default=Value(None),
|
||||
output_field=models.CharField(),
|
||||
)
|
||||
)
|
||||
.values("display_name", "assignee_id", "avatar_url")
|
||||
.annotate(
|
||||
total_issues=Count(
|
||||
"id", filter=Q(archived_at__isnull=True, is_draft=False)
|
||||
)
|
||||
)
|
||||
.annotate(total_issues=Count("id", filter=Q(archived_at__isnull=True, is_draft=False)))
|
||||
.annotate(
|
||||
completed_issues=Count(
|
||||
"id",
|
||||
|
|
@ -914,9 +860,7 @@ class TransferCycleIssueEndpoint(BaseAPIView):
|
|||
assignee_distribution_data = [
|
||||
{
|
||||
"display_name": item["display_name"],
|
||||
"assignee_id": (
|
||||
str(item["assignee_id"]) if item["assignee_id"] else None
|
||||
),
|
||||
"assignee_id": (str(item["assignee_id"]) if item["assignee_id"] else None),
|
||||
"avatar": item.get("avatar"),
|
||||
"avatar_url": item.get("avatar_url"),
|
||||
"total_issues": item["total_issues"],
|
||||
|
|
@ -938,11 +882,7 @@ class TransferCycleIssueEndpoint(BaseAPIView):
|
|||
.annotate(color=F("labels__color"))
|
||||
.annotate(label_id=F("labels__id"))
|
||||
.values("label_name", "color", "label_id")
|
||||
.annotate(
|
||||
total_issues=Count(
|
||||
"id", filter=Q(archived_at__isnull=True, is_draft=False)
|
||||
)
|
||||
)
|
||||
.annotate(total_issues=Count("id", filter=Q(archived_at__isnull=True, is_draft=False)))
|
||||
.annotate(
|
||||
completed_issues=Count(
|
||||
"id",
|
||||
|
|
@ -988,9 +928,7 @@ class TransferCycleIssueEndpoint(BaseAPIView):
|
|||
cycle_id=cycle_id,
|
||||
)
|
||||
|
||||
current_cycle = Cycle.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id, pk=cycle_id
|
||||
).first()
|
||||
current_cycle = Cycle.objects.filter(workspace__slug=slug, project_id=project_id, pk=cycle_id).first()
|
||||
|
||||
current_cycle.progress_snapshot = {
|
||||
"total_issues": old_cycle.total_issues,
|
||||
|
|
@ -1018,9 +956,7 @@ class TransferCycleIssueEndpoint(BaseAPIView):
|
|||
|
||||
if new_cycle.end_date is not None and new_cycle.end_date < timezone.now():
|
||||
return Response(
|
||||
{
|
||||
"error": "The cycle where the issues are transferred is already completed"
|
||||
},
|
||||
{"error": "The cycle where the issues are transferred is already completed"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
|
@ -1044,9 +980,7 @@ class TransferCycleIssueEndpoint(BaseAPIView):
|
|||
}
|
||||
)
|
||||
|
||||
cycle_issues = CycleIssue.objects.bulk_update(
|
||||
updated_cycles, ["cycle_id"], batch_size=100
|
||||
)
|
||||
cycle_issues = CycleIssue.objects.bulk_update(updated_cycles, ["cycle_id"], batch_size=100)
|
||||
|
||||
# Capture Issue Activity
|
||||
issue_activity.delay(
|
||||
|
|
@ -1080,12 +1014,8 @@ class CycleUserPropertiesEndpoint(BaseAPIView):
|
|||
)
|
||||
|
||||
cycle_properties.filters = request.data.get("filters", cycle_properties.filters)
|
||||
cycle_properties.rich_filters = request.data.get(
|
||||
"rich_filters", cycle_properties.rich_filters
|
||||
)
|
||||
cycle_properties.display_filters = request.data.get(
|
||||
"display_filters", cycle_properties.display_filters
|
||||
)
|
||||
cycle_properties.rich_filters = request.data.get("rich_filters", cycle_properties.rich_filters)
|
||||
cycle_properties.display_filters = request.data.get("display_filters", cycle_properties.display_filters)
|
||||
cycle_properties.display_properties = request.data.get(
|
||||
"display_properties", cycle_properties.display_properties
|
||||
)
|
||||
|
|
@ -1109,13 +1039,9 @@ class CycleUserPropertiesEndpoint(BaseAPIView):
|
|||
class CycleProgressEndpoint(BaseAPIView):
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def get(self, request, slug, project_id, cycle_id):
|
||||
cycle = Cycle.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id, id=cycle_id
|
||||
).first()
|
||||
cycle = Cycle.objects.filter(workspace__slug=slug, project_id=project_id, id=cycle_id).first()
|
||||
if not cycle:
|
||||
return Response(
|
||||
{"error": "Cycle not found"}, status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
return Response({"error": "Cycle not found"}, status=status.HTTP_404_NOT_FOUND)
|
||||
aggregate_estimates = (
|
||||
Issue.issue_objects.filter(
|
||||
estimate_point__estimate__type="points",
|
||||
|
|
@ -1161,9 +1087,7 @@ class CycleProgressEndpoint(BaseAPIView):
|
|||
output_field=FloatField(),
|
||||
)
|
||||
),
|
||||
total_estimate_points=Sum(
|
||||
"value_as_float", default=Value(0), output_field=FloatField()
|
||||
),
|
||||
total_estimate_points=Sum("value_as_float", default=Value(0), output_field=FloatField()),
|
||||
)
|
||||
)
|
||||
if cycle.progress_snapshot:
|
||||
|
|
@ -1223,22 +1147,11 @@ class CycleProgressEndpoint(BaseAPIView):
|
|||
|
||||
return Response(
|
||||
{
|
||||
"backlog_estimate_points": aggregate_estimates["backlog_estimate_point"]
|
||||
or 0,
|
||||
"unstarted_estimate_points": aggregate_estimates[
|
||||
"unstarted_estimate_point"
|
||||
]
|
||||
or 0,
|
||||
"started_estimate_points": aggregate_estimates["started_estimate_point"]
|
||||
or 0,
|
||||
"cancelled_estimate_points": aggregate_estimates[
|
||||
"cancelled_estimate_point"
|
||||
]
|
||||
or 0,
|
||||
"completed_estimate_points": aggregate_estimates[
|
||||
"completed_estimate_points"
|
||||
]
|
||||
or 0,
|
||||
"backlog_estimate_points": aggregate_estimates["backlog_estimate_point"] or 0,
|
||||
"unstarted_estimate_points": aggregate_estimates["unstarted_estimate_point"] or 0,
|
||||
"started_estimate_points": aggregate_estimates["started_estimate_point"] or 0,
|
||||
"cancelled_estimate_points": aggregate_estimates["cancelled_estimate_point"] or 0,
|
||||
"completed_estimate_points": aggregate_estimates["completed_estimate_points"] or 0,
|
||||
"total_estimate_points": aggregate_estimates["total_estimate_points"],
|
||||
"backlog_issues": backlog_issues,
|
||||
"total_issues": total_issues,
|
||||
|
|
@ -1256,9 +1169,7 @@ class CycleAnalyticsEndpoint(BaseAPIView):
|
|||
def get(self, request, slug, project_id, cycle_id):
|
||||
analytic_type = request.GET.get("type", "issues")
|
||||
cycle = (
|
||||
Cycle.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id, id=cycle_id
|
||||
)
|
||||
Cycle.objects.filter(workspace__slug=slug, project_id=project_id, id=cycle_id)
|
||||
.annotate(
|
||||
total_issues=Count(
|
||||
"issue_cycle__issue__id",
|
||||
|
|
@ -1341,9 +1252,7 @@ class CycleAnalyticsEndpoint(BaseAPIView):
|
|||
)
|
||||
)
|
||||
.values("display_name", "assignee_id", "avatar_url")
|
||||
.annotate(
|
||||
total_estimates=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.annotate(total_estimates=Sum(Cast("estimate_point__value", FloatField())))
|
||||
.annotate(
|
||||
completed_estimates=Sum(
|
||||
Cast("estimate_point__value", FloatField()),
|
||||
|
|
@ -1378,9 +1287,7 @@ class CycleAnalyticsEndpoint(BaseAPIView):
|
|||
.annotate(color=F("labels__color"))
|
||||
.annotate(label_id=F("labels__id"))
|
||||
.values("label_name", "color", "label_id")
|
||||
.annotate(
|
||||
total_estimates=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.annotate(total_estimates=Sum(Cast("estimate_point__value", FloatField())))
|
||||
.annotate(
|
||||
completed_estimates=Sum(
|
||||
Cast("estimate_point__value", FloatField()),
|
||||
|
|
@ -1482,11 +1389,7 @@ class CycleAnalyticsEndpoint(BaseAPIView):
|
|||
.annotate(color=F("labels__color"))
|
||||
.annotate(label_id=F("labels__id"))
|
||||
.values("label_name", "color", "label_id")
|
||||
.annotate(
|
||||
total_issues=Count(
|
||||
"label_id", filter=Q(archived_at__isnull=True, is_draft=False)
|
||||
)
|
||||
)
|
||||
.annotate(total_issues=Count("label_id", filter=Q(archived_at__isnull=True, is_draft=False)))
|
||||
.annotate(
|
||||
completed_issues=Count(
|
||||
"label_id",
|
||||
|
|
|
|||
|
|
@ -74,9 +74,7 @@ class CycleIssueViewSet(BaseViewSet):
|
|||
return (
|
||||
issues.annotate(
|
||||
cycle_id=Subquery(
|
||||
CycleIssue.objects.filter(
|
||||
issue=OuterRef("id"), deleted_at__isnull=True
|
||||
).values("cycle_id")[:1]
|
||||
CycleIssue.objects.filter(issue=OuterRef("id"), deleted_at__isnull=True).values("cycle_id")[:1]
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
|
|
@ -100,9 +98,7 @@ class CycleIssueViewSet(BaseViewSet):
|
|||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
.prefetch_related(
|
||||
"assignees", "labels", "issue_module__module", "issue_cycle__cycle"
|
||||
)
|
||||
.prefetch_related("assignees", "labels", "issue_module__module", "issue_cycle__cycle")
|
||||
)
|
||||
|
||||
@method_decorator(gzip_page)
|
||||
|
|
@ -110,9 +106,7 @@ class CycleIssueViewSet(BaseViewSet):
|
|||
def list(self, request, slug, project_id, cycle_id):
|
||||
filters = issue_filters(request.query_params, "GET")
|
||||
issue_queryset = (
|
||||
Issue.issue_objects.filter(
|
||||
issue_cycle__cycle_id=cycle_id, issue_cycle__deleted_at__isnull=True
|
||||
)
|
||||
Issue.issue_objects.filter(issue_cycle__cycle_id=cycle_id, issue_cycle__deleted_at__isnull=True)
|
||||
.filter(project_id=project_id)
|
||||
.filter(workspace__slug=slug)
|
||||
)
|
||||
|
|
@ -140,18 +134,14 @@ class CycleIssueViewSet(BaseViewSet):
|
|||
sub_group_by = request.GET.get("sub_group_by", False)
|
||||
|
||||
# issue queryset
|
||||
issue_queryset = issue_queryset_grouper(
|
||||
queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by
|
||||
)
|
||||
issue_queryset = issue_queryset_grouper(queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by)
|
||||
|
||||
if group_by:
|
||||
# Check group and sub group value paginate
|
||||
if sub_group_by:
|
||||
if group_by == sub_group_by:
|
||||
return Response(
|
||||
{
|
||||
"error": "Group by and sub group by cannot have same parameters"
|
||||
},
|
||||
{"error": "Group by and sub group by cannot have same parameters"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
else:
|
||||
|
|
@ -223,9 +213,7 @@ class CycleIssueViewSet(BaseViewSet):
|
|||
request=request,
|
||||
queryset=issue_queryset,
|
||||
total_count_queryset=total_issue_queryset,
|
||||
on_results=lambda issues: issue_on_results(
|
||||
group_by=group_by, issues=issues, sub_group_by=sub_group_by
|
||||
),
|
||||
on_results=lambda issues: issue_on_results(group_by=group_by, issues=issues, sub_group_by=sub_group_by),
|
||||
)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
|
|
@ -233,26 +221,18 @@ class CycleIssueViewSet(BaseViewSet):
|
|||
issues = request.data.get("issues", [])
|
||||
|
||||
if not issues:
|
||||
return Response(
|
||||
{"error": "Issues are required"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response({"error": "Issues are required"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
cycle = Cycle.objects.get(
|
||||
workspace__slug=slug, project_id=project_id, pk=cycle_id
|
||||
)
|
||||
cycle = Cycle.objects.get(workspace__slug=slug, project_id=project_id, pk=cycle_id)
|
||||
|
||||
if cycle.end_date is not None and cycle.end_date < timezone.now():
|
||||
return Response(
|
||||
{
|
||||
"error": "The Cycle has already been completed so no new issues can be added"
|
||||
},
|
||||
{"error": "The Cycle has already been completed so no new issues can be added"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# Get all CycleIssues already created
|
||||
cycle_issues = list(
|
||||
CycleIssue.objects.filter(~Q(cycle_id=cycle_id), issue_id__in=issues)
|
||||
)
|
||||
cycle_issues = list(CycleIssue.objects.filter(~Q(cycle_id=cycle_id), issue_id__in=issues))
|
||||
existing_issues = [str(cycle_issue.issue_id) for cycle_issue in cycle_issues]
|
||||
new_issues = list(set(issues) - set(existing_issues))
|
||||
|
||||
|
|
@ -303,9 +283,7 @@ class CycleIssueViewSet(BaseViewSet):
|
|||
current_instance=json.dumps(
|
||||
{
|
||||
"updated_cycle_issues": update_cycle_issue_activity,
|
||||
"created_cycle_issues": serializers.serialize(
|
||||
"json", created_records
|
||||
),
|
||||
"created_cycle_issues": serializers.serialize("json", created_records),
|
||||
}
|
||||
),
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
|
|
|
|||
|
|
@ -56,9 +56,7 @@ class BulkEstimatePointEndpoint(BaseViewSet):
|
|||
serializer = EstimateReadSerializer(estimates, many=True)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@invalidate_cache(
|
||||
path="/api/workspaces/:slug/estimates/", url_params=True, user=False
|
||||
)
|
||||
@invalidate_cache(path="/api/workspaces/:slug/estimates/", url_params=True, user=False)
|
||||
def create(self, request, slug, project_id):
|
||||
estimate = request.data.get("estimate")
|
||||
estimate_name = estimate.get("name", generate_random_name())
|
||||
|
|
@ -73,9 +71,7 @@ class BulkEstimatePointEndpoint(BaseViewSet):
|
|||
|
||||
estimate_points = request.data.get("estimate_points", [])
|
||||
|
||||
serializer = EstimatePointSerializer(
|
||||
data=request.data.get("estimate_points"), many=True
|
||||
)
|
||||
serializer = EstimatePointSerializer(data=request.data.get("estimate_points"), many=True)
|
||||
if not serializer.is_valid():
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
|
@ -101,15 +97,11 @@ class BulkEstimatePointEndpoint(BaseViewSet):
|
|||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
def retrieve(self, request, slug, project_id, estimate_id):
|
||||
estimate = Estimate.objects.get(
|
||||
pk=estimate_id, workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
estimate = Estimate.objects.get(pk=estimate_id, workspace__slug=slug, project_id=project_id)
|
||||
serializer = EstimateReadSerializer(estimate)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@invalidate_cache(
|
||||
path="/api/workspaces/:slug/estimates/", url_params=True, user=False
|
||||
)
|
||||
@invalidate_cache(path="/api/workspaces/:slug/estimates/", url_params=True, user=False)
|
||||
def partial_update(self, request, slug, project_id, estimate_id):
|
||||
if not len(request.data.get("estimate_points", [])):
|
||||
return Response(
|
||||
|
|
@ -127,9 +119,7 @@ class BulkEstimatePointEndpoint(BaseViewSet):
|
|||
estimate_points_data = request.data.get("estimate_points", [])
|
||||
|
||||
estimate_points = EstimatePoint.objects.filter(
|
||||
pk__in=[
|
||||
estimate_point.get("id") for estimate_point in estimate_points_data
|
||||
],
|
||||
pk__in=[estimate_point.get("id") for estimate_point in estimate_points_data],
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
estimate_id=estimate_id,
|
||||
|
|
@ -138,34 +128,20 @@ class BulkEstimatePointEndpoint(BaseViewSet):
|
|||
updated_estimate_points = []
|
||||
for estimate_point in estimate_points:
|
||||
# Find the data for that estimate point
|
||||
estimate_point_data = [
|
||||
point
|
||||
for point in estimate_points_data
|
||||
if point.get("id") == str(estimate_point.id)
|
||||
]
|
||||
estimate_point_data = [point for point in estimate_points_data if point.get("id") == str(estimate_point.id)]
|
||||
if len(estimate_point_data):
|
||||
estimate_point.value = estimate_point_data[0].get(
|
||||
"value", estimate_point.value
|
||||
)
|
||||
estimate_point.key = estimate_point_data[0].get(
|
||||
"key", estimate_point.key
|
||||
)
|
||||
estimate_point.value = estimate_point_data[0].get("value", estimate_point.value)
|
||||
estimate_point.key = estimate_point_data[0].get("key", estimate_point.key)
|
||||
updated_estimate_points.append(estimate_point)
|
||||
|
||||
EstimatePoint.objects.bulk_update(
|
||||
updated_estimate_points, ["key", "value"], batch_size=10
|
||||
)
|
||||
EstimatePoint.objects.bulk_update(updated_estimate_points, ["key", "value"], batch_size=10)
|
||||
|
||||
estimate_serializer = EstimateReadSerializer(estimate)
|
||||
return Response(estimate_serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@invalidate_cache(
|
||||
path="/api/workspaces/:slug/estimates/", url_params=True, user=False
|
||||
)
|
||||
@invalidate_cache(path="/api/workspaces/:slug/estimates/", url_params=True, user=False)
|
||||
def destroy(self, request, slug, project_id, estimate_id):
|
||||
estimate = Estimate.objects.get(
|
||||
pk=estimate_id, workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
estimate = Estimate.objects.get(pk=estimate_id, workspace__slug=slug, project_id=project_id)
|
||||
estimate.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
|
@ -196,9 +172,7 @@ class EstimatePointEndpoint(BaseViewSet):
|
|||
project_id=project_id,
|
||||
workspace__slug=slug,
|
||||
)
|
||||
serializer = EstimatePointSerializer(
|
||||
estimate_point, data=request.data, partial=True
|
||||
)
|
||||
serializer = EstimatePointSerializer(estimate_point, data=request.data, partial=True)
|
||||
if not serializer.is_valid():
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
serializer.save()
|
||||
|
|
@ -220,24 +194,12 @@ class EstimatePointEndpoint(BaseViewSet):
|
|||
for issue in issues:
|
||||
issue_activity.delay(
|
||||
type="issue.activity.updated",
|
||||
requested_data=json.dumps(
|
||||
{
|
||||
"estimate_point": (
|
||||
str(new_estimate_id) if new_estimate_id else None
|
||||
)
|
||||
}
|
||||
),
|
||||
requested_data=json.dumps({"estimate_point": (str(new_estimate_id) if new_estimate_id else None)}),
|
||||
actor_id=str(request.user.id),
|
||||
issue_id=issue.id,
|
||||
project_id=str(project_id),
|
||||
current_instance=json.dumps(
|
||||
{
|
||||
"estimate_point": (
|
||||
str(issue.estimate_point_id)
|
||||
if issue.estimate_point_id
|
||||
else None
|
||||
)
|
||||
}
|
||||
{"estimate_point": (str(issue.estimate_point_id) if issue.estimate_point_id else None)}
|
||||
),
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
)
|
||||
|
|
@ -256,13 +218,7 @@ class EstimatePointEndpoint(BaseViewSet):
|
|||
issue_id=issue.id,
|
||||
project_id=str(project_id),
|
||||
current_instance=json.dumps(
|
||||
{
|
||||
"estimate_point": (
|
||||
str(issue.estimate_point_id)
|
||||
if issue.estimate_point_id
|
||||
else None
|
||||
)
|
||||
}
|
||||
{"estimate_point": (str(issue.estimate_point_id) if issue.estimate_point_id else None)}
|
||||
),
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
)
|
||||
|
|
@ -277,9 +233,7 @@ class EstimatePointEndpoint(BaseViewSet):
|
|||
estimate_point.key -= 1
|
||||
updated_estimate_points.append(estimate_point)
|
||||
|
||||
EstimatePoint.objects.bulk_update(
|
||||
updated_estimate_points, ["key"], batch_size=10
|
||||
)
|
||||
EstimatePoint.objects.bulk_update(updated_estimate_points, ["key"], batch_size=10)
|
||||
|
||||
old_estimate_point.delete()
|
||||
|
||||
|
|
|
|||
|
|
@ -62,18 +62,16 @@ class ExportIssuesEndpoint(BaseAPIView):
|
|||
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
|
||||
def get(self, request, slug):
|
||||
exporter_history = ExporterHistory.objects.filter(
|
||||
workspace__slug=slug, type="issue_exports"
|
||||
).select_related("workspace", "initiated_by")
|
||||
exporter_history = ExporterHistory.objects.filter(workspace__slug=slug, type="issue_exports").select_related(
|
||||
"workspace", "initiated_by"
|
||||
)
|
||||
|
||||
if request.GET.get("per_page", False) and request.GET.get("cursor", False):
|
||||
return self.paginate(
|
||||
order_by=request.GET.get("order_by", "-created_at"),
|
||||
request=request,
|
||||
queryset=exporter_history,
|
||||
on_results=lambda exporter_history: ExporterHistorySerializer(
|
||||
exporter_history, many=True
|
||||
).data,
|
||||
on_results=lambda exporter_history: ExporterHistorySerializer(exporter_history, many=True).data,
|
||||
)
|
||||
else:
|
||||
return Response(
|
||||
|
|
|
|||
23
apps/api/plane/app/views/external/base.py
vendored
23
apps/api/plane/app/views/external/base.py
vendored
|
|
@ -108,8 +108,7 @@ def get_llm_config() -> Tuple[str | None, str | None, str | None]:
|
|||
if model not in provider.models:
|
||||
log_exception(
|
||||
ValueError(
|
||||
f"Model {model} not supported by {provider.name}. "
|
||||
f"Supported models: {', '.join(provider.models)}"
|
||||
f"Model {model} not supported by {provider.name}. Supported models: {', '.join(provider.models)}"
|
||||
)
|
||||
)
|
||||
return None, None, None
|
||||
|
|
@ -117,9 +116,7 @@ def get_llm_config() -> Tuple[str | None, str | None, str | None]:
|
|||
return api_key, model, provider_key
|
||||
|
||||
|
||||
def get_llm_response(
|
||||
task, prompt, api_key: str, model: str, provider: str
|
||||
) -> Tuple[str | None, str | None]:
|
||||
def get_llm_response(task, prompt, api_key: str, model: str, provider: str) -> Tuple[str | None, str | None]:
|
||||
"""Helper to get LLM completion response"""
|
||||
final_text = task + "\n" + prompt
|
||||
try:
|
||||
|
|
@ -157,13 +154,9 @@ class GPTIntegrationEndpoint(BaseAPIView):
|
|||
|
||||
task = request.data.get("task", False)
|
||||
if not task:
|
||||
return Response(
|
||||
{"error": "Task is required"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response({"error": "Task is required"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
text, error = get_llm_response(
|
||||
task, request.data.get("prompt", False), api_key, model, provider
|
||||
)
|
||||
text, error = get_llm_response(task, request.data.get("prompt", False), api_key, model, provider)
|
||||
if not text and error:
|
||||
return Response(
|
||||
{"error": "An internal error has occurred."},
|
||||
|
|
@ -197,13 +190,9 @@ class WorkspaceGPTIntegrationEndpoint(BaseAPIView):
|
|||
|
||||
task = request.data.get("task", False)
|
||||
if not task:
|
||||
return Response(
|
||||
{"error": "Task is required"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response({"error": "Task is required"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
text, error = get_llm_response(
|
||||
task, request.data.get("prompt", False), api_key, model, provider
|
||||
)
|
||||
text, error = get_llm_response(task, request.data.get("prompt", False), api_key, model, provider)
|
||||
if not text and error:
|
||||
return Response(
|
||||
{"error": "An internal error has occurred."},
|
||||
|
|
|
|||
|
|
@ -60,11 +60,7 @@ class IntakeViewSet(BaseViewSet):
|
|||
workspace__slug=self.kwargs.get("slug"),
|
||||
project_id=self.kwargs.get("project_id"),
|
||||
)
|
||||
.annotate(
|
||||
pending_issue_count=Count(
|
||||
"issue_intake", filter=Q(issue_intake__status=-2)
|
||||
)
|
||||
)
|
||||
.annotate(pending_issue_count=Count("issue_intake", filter=Q(issue_intake__status=-2)))
|
||||
.select_related("workspace", "project")
|
||||
)
|
||||
|
||||
|
|
@ -79,9 +75,7 @@ class IntakeViewSet(BaseViewSet):
|
|||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def destroy(self, request, slug, project_id, pk):
|
||||
intake = Intake.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id, pk=pk
|
||||
).first()
|
||||
intake = Intake.objects.filter(workspace__slug=slug, project_id=project_id, pk=pk).first()
|
||||
# Handle default intake delete
|
||||
if intake.is_default:
|
||||
return Response(
|
||||
|
|
@ -109,16 +103,12 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
.prefetch_related(
|
||||
Prefetch(
|
||||
"issue_intake",
|
||||
queryset=IntakeIssue.objects.only(
|
||||
"status", "duplicate_to", "snoozed_till", "source"
|
||||
),
|
||||
queryset=IntakeIssue.objects.only("status", "duplicate_to", "snoozed_till", "source"),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
cycle_id=Subquery(
|
||||
CycleIssue.objects.filter(
|
||||
issue=OuterRef("id"), deleted_at__isnull=True
|
||||
).values("cycle_id")[:1]
|
||||
CycleIssue.objects.filter(issue=OuterRef("id"), deleted_at__isnull=True).values("cycle_id")[:1]
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
|
|
@ -147,10 +137,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
ArrayAgg(
|
||||
"labels__id",
|
||||
distinct=True,
|
||||
filter=Q(
|
||||
~Q(labels__id__isnull=True)
|
||||
& Q(label_issue__deleted_at__isnull=True)
|
||||
),
|
||||
filter=Q(~Q(labels__id__isnull=True) & Q(label_issue__deleted_at__isnull=True)),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
),
|
||||
|
|
@ -183,20 +170,14 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def list(self, request, slug, project_id):
|
||||
intake = Intake.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
).first()
|
||||
intake = Intake.objects.filter(workspace__slug=slug, project_id=project_id).first()
|
||||
if not intake:
|
||||
return Response(
|
||||
{"error": "Intake not found"}, status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
return Response({"error": "Intake not found"}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
project = Project.objects.get(pk=project_id)
|
||||
filters = issue_filters(request.GET, "GET", "issue__")
|
||||
intake_issue = (
|
||||
IntakeIssue.objects.filter(
|
||||
intake_id=intake.id, project_id=project_id, **filters
|
||||
)
|
||||
IntakeIssue.objects.filter(intake_id=intake.id, project_id=project_id, **filters)
|
||||
.select_related("issue")
|
||||
.prefetch_related("issue__labels")
|
||||
.annotate(
|
||||
|
|
@ -204,21 +185,14 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
ArrayAgg(
|
||||
"issue__labels__id",
|
||||
distinct=True,
|
||||
filter=Q(
|
||||
~Q(issue__labels__id__isnull=True)
|
||||
& Q(issue__label_issue__deleted_at__isnull=True)
|
||||
),
|
||||
filter=Q(~Q(issue__labels__id__isnull=True) & Q(issue__label_issue__deleted_at__isnull=True)),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
)
|
||||
)
|
||||
).order_by(request.GET.get("order_by", "-issue__created_at"))
|
||||
# Intake status filter
|
||||
intake_status = [
|
||||
item
|
||||
for item in request.GET.get("status", "-2").split(",")
|
||||
if item != "null"
|
||||
]
|
||||
intake_status = [item for item in request.GET.get("status", "-2").split(",") if item != "null"]
|
||||
if intake_status:
|
||||
intake_issue = intake_issue.filter(status__in=intake_status)
|
||||
|
||||
|
|
@ -236,17 +210,13 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
return self.paginate(
|
||||
request=request,
|
||||
queryset=(intake_issue),
|
||||
on_results=lambda intake_issues: IntakeIssueSerializer(
|
||||
intake_issues, many=True
|
||||
).data,
|
||||
on_results=lambda intake_issues: IntakeIssueSerializer(intake_issues, many=True).data,
|
||||
)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def create(self, request, slug, project_id):
|
||||
if not request.data.get("issue", {}).get("name", False):
|
||||
return Response(
|
||||
{"error": "Name is required"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response({"error": "Name is required"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# Check for valid priority
|
||||
if request.data.get("issue", {}).get("priority", "none") not in [
|
||||
|
|
@ -256,9 +226,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
"urgent",
|
||||
"none",
|
||||
]:
|
||||
return Response(
|
||||
{"error": "Invalid priority"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response({"error": "Invalid priority"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# create an issue
|
||||
project = Project.objects.get(pk=project_id)
|
||||
|
|
@ -272,9 +240,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
intake_id = Intake.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
).first()
|
||||
intake_id = Intake.objects.filter(workspace__slug=slug, project_id=project_id).first()
|
||||
# create an intake issue
|
||||
intake_issue = IntakeIssue.objects.create(
|
||||
intake_id=intake_id.id,
|
||||
|
|
@ -311,8 +277,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
"issue__labels__id",
|
||||
distinct=True,
|
||||
filter=Q(
|
||||
~Q(issue__labels__id__isnull=True)
|
||||
& Q(issue__label_issue__deleted_at__isnull=True)
|
||||
~Q(issue__labels__id__isnull=True) & Q(issue__label_issue__deleted_at__isnull=True)
|
||||
),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
|
|
@ -340,9 +305,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN], creator=True, model=Issue)
|
||||
def partial_update(self, request, slug, project_id, pk):
|
||||
intake_id = Intake.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
).first()
|
||||
intake_id = Intake.objects.filter(workspace__slug=slug, project_id=project_id).first()
|
||||
intake_issue = IntakeIssue.objects.get(
|
||||
issue_id=pk,
|
||||
workspace__slug=slug,
|
||||
|
|
@ -371,10 +334,9 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
)
|
||||
|
||||
# Only project members admins and created_by users can access this endpoint
|
||||
if (
|
||||
(project_member and project_member.role <= ROLE.GUEST.value)
|
||||
and not is_workspace_admin
|
||||
) and str(intake_issue.created_by_id) != str(request.user.id):
|
||||
if ((project_member and project_member.role <= ROLE.GUEST.value) and not is_workspace_admin) and str(
|
||||
intake_issue.created_by_id
|
||||
) != str(request.user.id):
|
||||
return Response(
|
||||
{"error": "You cannot edit intake issues"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
|
|
@ -388,10 +350,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
ArrayAgg(
|
||||
"labels__id",
|
||||
distinct=True,
|
||||
filter=Q(
|
||||
~Q(labels__id__isnull=True)
|
||||
& Q(label_issue__deleted_at__isnull=True)
|
||||
),
|
||||
filter=Q(~Q(labels__id__isnull=True) & Q(label_issue__deleted_at__isnull=True)),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
),
|
||||
|
|
@ -399,10 +358,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
ArrayAgg(
|
||||
"assignees__id",
|
||||
distinct=True,
|
||||
filter=Q(
|
||||
~Q(assignees__id__isnull=True)
|
||||
& Q(issue_assignee__deleted_at__isnull=True)
|
||||
),
|
||||
filter=Q(~Q(assignees__id__isnull=True) & Q(issue_assignee__deleted_at__isnull=True)),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
),
|
||||
|
|
@ -411,15 +367,11 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
if project_member and project_member.role <= ROLE.GUEST.value:
|
||||
issue_data = {
|
||||
"name": issue_data.get("name", issue.name),
|
||||
"description_html": issue_data.get(
|
||||
"description_html", issue.description_html
|
||||
),
|
||||
"description_html": issue_data.get("description_html", issue.description_html),
|
||||
"description": issue_data.get("description", issue.description),
|
||||
}
|
||||
|
||||
current_instance = json.dumps(
|
||||
IssueDetailSerializer(issue).data, cls=DjangoJSONEncoder
|
||||
)
|
||||
current_instance = json.dumps(IssueDetailSerializer(issue).data, cls=DjangoJSONEncoder)
|
||||
|
||||
issue_serializer = IssueCreateSerializer(
|
||||
issue, data=issue_data, partial=True, context={"project_id": project_id}
|
||||
|
|
@ -449,20 +401,12 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
)
|
||||
issue_serializer.save()
|
||||
else:
|
||||
return Response(
|
||||
issue_serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response(issue_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# Only project admins can edit intake issue attributes
|
||||
if (
|
||||
project_member and project_member.role > ROLE.MEMBER.value
|
||||
) or is_workspace_admin:
|
||||
serializer = IntakeIssueSerializer(
|
||||
intake_issue, data=request.data, partial=True
|
||||
)
|
||||
current_instance = json.dumps(
|
||||
IntakeIssueSerializer(intake_issue).data, cls=DjangoJSONEncoder
|
||||
)
|
||||
if (project_member and project_member.role > ROLE.MEMBER.value) or is_workspace_admin:
|
||||
serializer = IntakeIssueSerializer(intake_issue, data=request.data, partial=True)
|
||||
current_instance = json.dumps(IntakeIssueSerializer(intake_issue).data, cls=DjangoJSONEncoder)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
# Update the issue state if the issue is rejected or marked as duplicate
|
||||
|
|
@ -472,9 +416,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
)
|
||||
state = State.objects.filter(
|
||||
group="cancelled", workspace__slug=slug, project_id=project_id
|
||||
).first()
|
||||
state = State.objects.filter(group="cancelled", workspace__slug=slug, project_id=project_id).first()
|
||||
if state is not None:
|
||||
issue.state = state
|
||||
issue.save()
|
||||
|
|
@ -490,9 +432,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
# Update the issue state only if it is in triage state
|
||||
if issue.state.is_triage:
|
||||
# Move to default state
|
||||
state = State.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id, default=True
|
||||
).first()
|
||||
state = State.objects.filter(workspace__slug=slug, project_id=project_id, default=True).first()
|
||||
if state is not None:
|
||||
issue.state = state
|
||||
issue.save()
|
||||
|
|
@ -519,8 +459,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
"issue__labels__id",
|
||||
distinct=True,
|
||||
filter=Q(
|
||||
~Q(issue__labels__id__isnull=True)
|
||||
& Q(issue__label_issue__deleted_at__isnull=True)
|
||||
~Q(issue__labels__id__isnull=True) & Q(issue__label_issue__deleted_at__isnull=True)
|
||||
),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
|
|
@ -546,13 +485,9 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
serializer = IntakeIssueDetailSerializer(intake_issue).data
|
||||
return Response(serializer, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], creator=True, model=Issue
|
||||
)
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], creator=True, model=Issue)
|
||||
def retrieve(self, request, slug, project_id, pk):
|
||||
intake_id = Intake.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
).first()
|
||||
intake_id = Intake.objects.filter(workspace__slug=slug, project_id=project_id).first()
|
||||
project = Project.objects.get(pk=project_id)
|
||||
intake_issue = (
|
||||
IntakeIssue.objects.select_related("issue")
|
||||
|
|
@ -562,10 +497,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
ArrayAgg(
|
||||
"issue__labels__id",
|
||||
distinct=True,
|
||||
filter=Q(
|
||||
~Q(issue__labels__id__isnull=True)
|
||||
& Q(issue__label_issue__deleted_at__isnull=True)
|
||||
),
|
||||
filter=Q(~Q(issue__labels__id__isnull=True) & Q(issue__label_issue__deleted_at__isnull=True)),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
),
|
||||
|
|
@ -574,8 +506,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
"issue__assignees__id",
|
||||
distinct=True,
|
||||
filter=Q(
|
||||
~Q(issue__assignees__id__isnull=True)
|
||||
& Q(issue__issue_assignee__deleted_at__isnull=True)
|
||||
~Q(issue__assignees__id__isnull=True) & Q(issue__issue_assignee__deleted_at__isnull=True)
|
||||
),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
|
|
@ -603,9 +534,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN], creator=True, model=Issue)
|
||||
def destroy(self, request, slug, project_id, pk):
|
||||
intake_id = Intake.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
).first()
|
||||
intake_id = Intake.objects.filter(workspace__slug=slug, project_id=project_id).first()
|
||||
intake_issue = IntakeIssue.objects.get(
|
||||
issue_id=pk,
|
||||
workspace__slug=slug,
|
||||
|
|
@ -616,9 +545,7 @@ class IntakeIssueViewSet(BaseViewSet):
|
|||
# Check the issue status
|
||||
if intake_issue.status in [-2, -1, 0, 2]:
|
||||
# Delete the issue also
|
||||
issue = Issue.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id, pk=pk
|
||||
).first()
|
||||
issue = Issue.objects.filter(workspace__slug=slug, project_id=project_id, pk=pk).first()
|
||||
issue.delete()
|
||||
|
||||
intake_issue.delete()
|
||||
|
|
@ -630,18 +557,14 @@ class IntakeWorkItemDescriptionVersionEndpoint(BaseAPIView):
|
|||
paginated_data = results.values(*fields)
|
||||
|
||||
datetime_fields = ["created_at", "updated_at"]
|
||||
paginated_data = user_timezone_converter(
|
||||
paginated_data, datetime_fields, timezone
|
||||
)
|
||||
paginated_data = user_timezone_converter(paginated_data, datetime_fields, timezone)
|
||||
|
||||
return paginated_data
|
||||
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def get(self, request, slug, project_id, work_item_id, pk=None):
|
||||
project = Project.objects.get(pk=project_id)
|
||||
issue = Issue.objects.get(
|
||||
workspace__slug=slug, project_id=project_id, pk=work_item_id
|
||||
)
|
||||
issue = Issue.objects.get(workspace__slug=slug, project_id=project_id, pk=work_item_id)
|
||||
|
||||
if (
|
||||
ProjectMember.objects.filter(
|
||||
|
|
@ -667,9 +590,7 @@ class IntakeWorkItemDescriptionVersionEndpoint(BaseAPIView):
|
|||
pk=pk,
|
||||
)
|
||||
|
||||
serializer = IssueDescriptionVersionDetailSerializer(
|
||||
issue_description_version
|
||||
)
|
||||
serializer = IssueDescriptionVersionDetailSerializer(issue_description_version)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
cursor = request.GET.get("cursor", None)
|
||||
|
|
|
|||
|
|
@ -63,9 +63,7 @@ class IssueActivityEndpoint(BaseAPIView):
|
|||
issue_activities = issue_activities.prefetch_related(
|
||||
Prefetch(
|
||||
"issue__issue_intake",
|
||||
queryset=IntakeIssue.objects.only(
|
||||
"source_email", "source", "extra"
|
||||
),
|
||||
queryset=IntakeIssue.objects.only("source_email", "source", "extra"),
|
||||
to_attr="source_data",
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import json
|
|||
|
||||
# Django imports
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from django.db.models import F, Func, OuterRef, Q, Prefetch, Exists, Subquery, Count
|
||||
from django.db.models import OuterRef, Q, Prefetch, Exists, Subquery, Count
|
||||
from django.utils import timezone
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.gzip import gzip_page
|
||||
|
|
@ -57,9 +57,7 @@ class IssueArchiveViewSet(BaseViewSet):
|
|||
return (
|
||||
issues.annotate(
|
||||
cycle_id=Subquery(
|
||||
CycleIssue.objects.filter(
|
||||
issue=OuterRef("id"), deleted_at__isnull=True
|
||||
).values("cycle_id")[:1]
|
||||
CycleIssue.objects.filter(issue=OuterRef("id"), deleted_at__isnull=True).values("cycle_id")[:1]
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
|
|
@ -110,11 +108,7 @@ class IssueArchiveViewSet(BaseViewSet):
|
|||
|
||||
issue_queryset = self.get_queryset()
|
||||
|
||||
issue_queryset = (
|
||||
issue_queryset
|
||||
if show_sub_issues == "true"
|
||||
else issue_queryset.filter(parent__isnull=True)
|
||||
)
|
||||
issue_queryset = issue_queryset if show_sub_issues == "true" else issue_queryset.filter(parent__isnull=True)
|
||||
# Apply filtering from filterset
|
||||
issue_queryset = self.filter_queryset(issue_queryset)
|
||||
|
||||
|
|
@ -137,18 +131,14 @@ class IssueArchiveViewSet(BaseViewSet):
|
|||
sub_group_by = request.GET.get("sub_group_by", False)
|
||||
|
||||
# issue queryset
|
||||
issue_queryset = issue_queryset_grouper(
|
||||
queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by
|
||||
)
|
||||
issue_queryset = issue_queryset_grouper(queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by)
|
||||
|
||||
if group_by:
|
||||
# Check group and sub group value paginate
|
||||
if sub_group_by:
|
||||
if group_by == sub_group_by:
|
||||
return Response(
|
||||
{
|
||||
"error": "Group by and sub group by cannot have same parameters"
|
||||
},
|
||||
{"error": "Group by and sub group by cannot have same parameters"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
else:
|
||||
|
|
@ -220,9 +210,7 @@ class IssueArchiveViewSet(BaseViewSet):
|
|||
request=request,
|
||||
queryset=issue_queryset,
|
||||
total_count_queryset=total_issue_queryset,
|
||||
on_results=lambda issues: issue_on_results(
|
||||
group_by=group_by, issues=issues, sub_group_by=sub_group_by
|
||||
),
|
||||
on_results=lambda issues: issue_on_results(group_by=group_by, issues=issues, sub_group_by=sub_group_by),
|
||||
)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
|
|
@ -263,9 +251,7 @@ class IssueArchiveViewSet(BaseViewSet):
|
|||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def archive(self, request, slug, project_id, pk=None):
|
||||
issue = Issue.issue_objects.get(
|
||||
workspace__slug=slug, project_id=project_id, pk=pk
|
||||
)
|
||||
issue = Issue.issue_objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
|
||||
if issue.state.group not in ["completed", "cancelled"]:
|
||||
return Response(
|
||||
{"error": "Can only archive completed or cancelled state group issue"},
|
||||
|
|
@ -273,15 +259,11 @@ class IssueArchiveViewSet(BaseViewSet):
|
|||
)
|
||||
issue_activity.delay(
|
||||
type="issue.activity.updated",
|
||||
requested_data=json.dumps(
|
||||
{"archived_at": str(timezone.now().date()), "automation": False}
|
||||
),
|
||||
requested_data=json.dumps({"archived_at": str(timezone.now().date()), "automation": False}),
|
||||
actor_id=str(request.user.id),
|
||||
issue_id=str(issue.id),
|
||||
project_id=str(project_id),
|
||||
current_instance=json.dumps(
|
||||
IssueSerializer(issue).data, cls=DjangoJSONEncoder
|
||||
),
|
||||
current_instance=json.dumps(IssueSerializer(issue).data, cls=DjangoJSONEncoder),
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
notification=True,
|
||||
origin=base_host(request=request, is_app=True),
|
||||
|
|
@ -289,9 +271,7 @@ class IssueArchiveViewSet(BaseViewSet):
|
|||
issue.archived_at = timezone.now().date()
|
||||
issue.save()
|
||||
|
||||
return Response(
|
||||
{"archived_at": str(issue.archived_at)}, status=status.HTTP_200_OK
|
||||
)
|
||||
return Response({"archived_at": str(issue.archived_at)}, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def unarchive(self, request, slug, project_id, pk=None):
|
||||
|
|
@ -307,9 +287,7 @@ class IssueArchiveViewSet(BaseViewSet):
|
|||
actor_id=str(request.user.id),
|
||||
issue_id=str(issue.id),
|
||||
project_id=str(project_id),
|
||||
current_instance=json.dumps(
|
||||
IssueSerializer(issue).data, cls=DjangoJSONEncoder
|
||||
),
|
||||
current_instance=json.dumps(IssueSerializer(issue).data, cls=DjangoJSONEncoder),
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
notification=True,
|
||||
origin=base_host(request=request, is_app=True),
|
||||
|
|
@ -328,13 +306,11 @@ class BulkArchiveIssuesEndpoint(BaseAPIView):
|
|||
issue_ids = request.data.get("issue_ids", [])
|
||||
|
||||
if not len(issue_ids):
|
||||
return Response(
|
||||
{"error": "Issue IDs are required"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response({"error": "Issue IDs are required"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
issues = Issue.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id, pk__in=issue_ids
|
||||
).select_related("state")
|
||||
issues = Issue.objects.filter(workspace__slug=slug, project_id=project_id, pk__in=issue_ids).select_related(
|
||||
"state"
|
||||
)
|
||||
bulk_archive_issues = []
|
||||
for issue in issues:
|
||||
if issue.state.group not in ["completed", "cancelled"]:
|
||||
|
|
@ -347,15 +323,11 @@ class BulkArchiveIssuesEndpoint(BaseAPIView):
|
|||
)
|
||||
issue_activity.delay(
|
||||
type="issue.activity.updated",
|
||||
requested_data=json.dumps(
|
||||
{"archived_at": str(timezone.now().date()), "automation": False}
|
||||
),
|
||||
requested_data=json.dumps({"archived_at": str(timezone.now().date()), "automation": False}),
|
||||
actor_id=str(request.user.id),
|
||||
issue_id=str(issue.id),
|
||||
project_id=str(project_id),
|
||||
current_instance=json.dumps(
|
||||
IssueSerializer(issue).data, cls=DjangoJSONEncoder
|
||||
),
|
||||
current_instance=json.dumps(IssueSerializer(issue).data, cls=DjangoJSONEncoder),
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
notification=True,
|
||||
origin=base_host(request=request, is_app=True),
|
||||
|
|
@ -364,6 +336,4 @@ class BulkArchiveIssuesEndpoint(BaseAPIView):
|
|||
bulk_archive_issues.append(issue)
|
||||
Issue.objects.bulk_update(bulk_archive_issues, ["archived_at"])
|
||||
|
||||
return Response(
|
||||
{"archived_at": str(timezone.now().date())}, status=status.HTTP_200_OK
|
||||
)
|
||||
return Response({"archived_at": str(timezone.now().date())}, status=status.HTTP_200_OK)
|
||||
|
|
|
|||
|
|
@ -75,9 +75,7 @@ class IssueAttachmentEndpoint(BaseAPIView):
|
|||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def get(self, request, slug, project_id, issue_id):
|
||||
issue_attachments = FileAsset.objects.filter(
|
||||
issue_id=issue_id, workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
issue_attachments = FileAsset.objects.filter(issue_id=issue_id, workspace__slug=slug, project_id=project_id)
|
||||
serializer = IssueAttachmentSerializer(issue_attachments, many=True)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
|
|
@ -123,9 +121,7 @@ class IssueAttachmentV2Endpoint(BaseAPIView):
|
|||
storage = S3Storage(request=request)
|
||||
|
||||
# Generate a presigned URL to share an S3 object
|
||||
presigned_url = storage.generate_presigned_post(
|
||||
object_name=asset_key, file_type=type, file_size=size_limit
|
||||
)
|
||||
presigned_url = storage.generate_presigned_post(object_name=asset_key, file_type=type, file_size=size_limit)
|
||||
|
||||
# Return the presigned URL
|
||||
return Response(
|
||||
|
|
@ -140,9 +136,7 @@ class IssueAttachmentV2Endpoint(BaseAPIView):
|
|||
|
||||
@allow_permission([ROLE.ADMIN], creator=True, model=FileAsset)
|
||||
def delete(self, request, slug, project_id, issue_id, pk):
|
||||
issue_attachment = FileAsset.objects.get(
|
||||
pk=pk, workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
issue_attachment = FileAsset.objects.get(pk=pk, workspace__slug=slug, project_id=project_id)
|
||||
issue_attachment.is_deleted = True
|
||||
issue_attachment.deleted_at = timezone.now()
|
||||
issue_attachment.save()
|
||||
|
|
@ -165,9 +159,7 @@ class IssueAttachmentV2Endpoint(BaseAPIView):
|
|||
def get(self, request, slug, project_id, issue_id, pk=None):
|
||||
if pk:
|
||||
# Get the asset
|
||||
asset = FileAsset.objects.get(
|
||||
id=pk, workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
asset = FileAsset.objects.get(id=pk, workspace__slug=slug, project_id=project_id)
|
||||
|
||||
# Check if the asset is uploaded
|
||||
if not asset.is_uploaded:
|
||||
|
|
@ -198,9 +190,7 @@ class IssueAttachmentV2Endpoint(BaseAPIView):
|
|||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def patch(self, request, slug, project_id, issue_id, pk):
|
||||
issue_attachment = FileAsset.objects.get(
|
||||
pk=pk, workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
issue_attachment = FileAsset.objects.get(pk=pk, workspace__slug=slug, project_id=project_id)
|
||||
serializer = IssueAttachmentSerializer(issue_attachment)
|
||||
|
||||
# Send this activity only if the attachment is not uploaded before
|
||||
|
|
|
|||
|
|
@ -82,16 +82,12 @@ class IssueListEndpoint(BaseAPIView):
|
|||
issue_ids = request.GET.get("issues", False)
|
||||
|
||||
if not issue_ids:
|
||||
return Response(
|
||||
{"error": "Issues are required"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response({"error": "Issues are required"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
issue_ids = [issue_id for issue_id in issue_ids.split(",") if issue_id != ""]
|
||||
|
||||
# Base queryset with basic filters
|
||||
queryset = Issue.issue_objects.filter(
|
||||
workspace__slug=slug, project_id=project_id, pk__in=issue_ids
|
||||
)
|
||||
queryset = Issue.issue_objects.filter(workspace__slug=slug, project_id=project_id, pk__in=issue_ids)
|
||||
|
||||
# Apply filtering from filterset
|
||||
queryset = self.filter_queryset(queryset)
|
||||
|
|
@ -102,17 +98,15 @@ class IssueListEndpoint(BaseAPIView):
|
|||
|
||||
# Add select_related, prefetch_related if fields or expand is not None
|
||||
if self.fields or self.expand:
|
||||
issue_queryset = issue_queryset.select_related(
|
||||
"workspace", "project", "state", "parent"
|
||||
).prefetch_related("assignees", "labels", "issue_module__module")
|
||||
issue_queryset = issue_queryset.select_related("workspace", "project", "state", "parent").prefetch_related(
|
||||
"assignees", "labels", "issue_module__module"
|
||||
)
|
||||
|
||||
# Add annotations
|
||||
issue_queryset = (
|
||||
issue_queryset.annotate(
|
||||
cycle_id=Subquery(
|
||||
CycleIssue.objects.filter(
|
||||
issue=OuterRef("id"), deleted_at__isnull=True
|
||||
).values("cycle_id")[:1]
|
||||
CycleIssue.objects.filter(issue=OuterRef("id"), deleted_at__isnull=True).values("cycle_id")[:1]
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
|
|
@ -141,18 +135,14 @@ class IssueListEndpoint(BaseAPIView):
|
|||
|
||||
order_by_param = request.GET.get("order_by", "-created_at")
|
||||
# Issue queryset
|
||||
issue_queryset, _ = order_issue_queryset(
|
||||
issue_queryset=issue_queryset, order_by_param=order_by_param
|
||||
)
|
||||
issue_queryset, _ = order_issue_queryset(issue_queryset=issue_queryset, order_by_param=order_by_param)
|
||||
|
||||
# Group by
|
||||
group_by = request.GET.get("group_by", False)
|
||||
sub_group_by = request.GET.get("sub_group_by", False)
|
||||
|
||||
# issue queryset
|
||||
issue_queryset = issue_queryset_grouper(
|
||||
queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by
|
||||
)
|
||||
issue_queryset = issue_queryset_grouper(queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by)
|
||||
|
||||
recent_visited_task.delay(
|
||||
slug=slug,
|
||||
|
|
@ -163,9 +153,7 @@ class IssueListEndpoint(BaseAPIView):
|
|||
)
|
||||
|
||||
if self.fields or self.expand:
|
||||
issues = IssueSerializer(
|
||||
queryset, many=True, fields=self.fields, expand=self.expand
|
||||
).data
|
||||
issues = IssueSerializer(queryset, many=True, fields=self.fields, expand=self.expand).data
|
||||
else:
|
||||
issues = issue_queryset.values(
|
||||
"id",
|
||||
|
|
@ -196,9 +184,7 @@ class IssueListEndpoint(BaseAPIView):
|
|||
"deleted_at",
|
||||
)
|
||||
datetime_fields = ["created_at", "updated_at"]
|
||||
issues = user_timezone_converter(
|
||||
issues, datetime_fields, request.user.user_timezone
|
||||
)
|
||||
issues = user_timezone_converter(issues, datetime_fields, request.user.user_timezone)
|
||||
return Response(issues, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
|
|
@ -210,11 +196,7 @@ class IssueViewSet(BaseViewSet):
|
|||
filterset_class = IssueFilterSet
|
||||
|
||||
def get_serializer_class(self):
|
||||
return (
|
||||
IssueCreateSerializer
|
||||
if self.action in ["create", "update", "partial_update"]
|
||||
else IssueSerializer
|
||||
)
|
||||
return IssueCreateSerializer if self.action in ["create", "update", "partial_update"] else IssueSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
issues = Issue.issue_objects.filter(
|
||||
|
|
@ -228,9 +210,7 @@ class IssueViewSet(BaseViewSet):
|
|||
issues = (
|
||||
issues.annotate(
|
||||
cycle_id=Subquery(
|
||||
CycleIssue.objects.filter(
|
||||
issue=OuterRef("id"), deleted_at__isnull=True
|
||||
).values("cycle_id")[:1]
|
||||
CycleIssue.objects.filter(issue=OuterRef("id"), deleted_at__isnull=True).values("cycle_id")[:1]
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
|
|
@ -301,9 +281,7 @@ class IssueViewSet(BaseViewSet):
|
|||
sub_group_by = request.GET.get("sub_group_by", False)
|
||||
|
||||
# issue queryset
|
||||
issue_queryset = issue_queryset_grouper(
|
||||
queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by
|
||||
)
|
||||
issue_queryset = issue_queryset_grouper(queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by)
|
||||
|
||||
recent_visited_task.delay(
|
||||
slug=slug,
|
||||
|
|
@ -323,9 +301,7 @@ class IssueViewSet(BaseViewSet):
|
|||
and not project.guest_view_all_features
|
||||
):
|
||||
issue_queryset = issue_queryset.filter(created_by=request.user)
|
||||
filtered_issue_queryset = filtered_issue_queryset.filter(
|
||||
created_by=request.user
|
||||
)
|
||||
filtered_issue_queryset = filtered_issue_queryset.filter(created_by=request.user)
|
||||
|
||||
if group_by:
|
||||
if sub_group_by:
|
||||
|
|
@ -405,9 +381,7 @@ class IssueViewSet(BaseViewSet):
|
|||
request=request,
|
||||
queryset=issue_queryset,
|
||||
total_count_queryset=filtered_issue_queryset,
|
||||
on_results=lambda issues: issue_on_results(
|
||||
group_by=group_by, issues=issues, sub_group_by=sub_group_by
|
||||
),
|
||||
on_results=lambda issues: issue_on_results(group_by=group_by, issues=issues, sub_group_by=sub_group_by),
|
||||
)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
|
|
@ -477,9 +451,7 @@ class IssueViewSet(BaseViewSet):
|
|||
.first()
|
||||
)
|
||||
datetime_fields = ["created_at", "updated_at"]
|
||||
issue = user_timezone_converter(
|
||||
issue, datetime_fields, request.user.user_timezone
|
||||
)
|
||||
issue = user_timezone_converter(issue, datetime_fields, request.user.user_timezone)
|
||||
# Send the model activity
|
||||
model_activity.delay(
|
||||
model_name="issue",
|
||||
|
|
@ -500,9 +472,7 @@ class IssueViewSet(BaseViewSet):
|
|||
return Response(issue, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], creator=True, model=Issue
|
||||
)
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], creator=True, model=Issue)
|
||||
def retrieve(self, request, slug, project_id, pk=None):
|
||||
project = Project.objects.get(pk=project_id, workspace__slug=slug)
|
||||
|
||||
|
|
@ -513,13 +483,7 @@ class IssueViewSet(BaseViewSet):
|
|||
pk=pk,
|
||||
)
|
||||
.select_related("state")
|
||||
.annotate(
|
||||
cycle_id=Subquery(
|
||||
CycleIssue.objects.filter(issue=OuterRef("id")).values("cycle_id")[
|
||||
:1
|
||||
]
|
||||
)
|
||||
)
|
||||
.annotate(cycle_id=Subquery(CycleIssue.objects.filter(issue=OuterRef("id")).values("cycle_id")[:1]))
|
||||
.annotate(
|
||||
link_count=Subquery(
|
||||
IssueLink.objects.filter(issue=OuterRef("id"))
|
||||
|
|
@ -643,9 +607,7 @@ class IssueViewSet(BaseViewSet):
|
|||
serializer = IssueDetailSerializer(issue, expand=self.expand)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], creator=True, model=Issue
|
||||
)
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], creator=True, model=Issue)
|
||||
def partial_update(self, request, slug, project_id, pk=None):
|
||||
queryset = self.get_queryset()
|
||||
queryset = self.apply_annotations(queryset)
|
||||
|
|
@ -655,10 +617,7 @@ class IssueViewSet(BaseViewSet):
|
|||
ArrayAgg(
|
||||
"labels__id",
|
||||
distinct=True,
|
||||
filter=Q(
|
||||
~Q(labels__id__isnull=True)
|
||||
& Q(label_issue__deleted_at__isnull=True)
|
||||
),
|
||||
filter=Q(~Q(labels__id__isnull=True) & Q(label_issue__deleted_at__isnull=True)),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
),
|
||||
|
|
@ -692,18 +651,12 @@ class IssueViewSet(BaseViewSet):
|
|||
)
|
||||
|
||||
if not issue:
|
||||
return Response(
|
||||
{"error": "Issue not found"}, status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
return Response({"error": "Issue not found"}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
current_instance = json.dumps(
|
||||
IssueDetailSerializer(issue).data, cls=DjangoJSONEncoder
|
||||
)
|
||||
current_instance = json.dumps(IssueDetailSerializer(issue).data, cls=DjangoJSONEncoder)
|
||||
|
||||
requested_data = json.dumps(self.request.data, cls=DjangoJSONEncoder)
|
||||
serializer = IssueCreateSerializer(
|
||||
issue, data=request.data, partial=True, context={"project_id": project_id}
|
||||
)
|
||||
serializer = IssueCreateSerializer(issue, data=request.data, partial=True, context={"project_id": project_id})
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
issue_activity.delay(
|
||||
|
|
@ -765,29 +718,19 @@ class IssueViewSet(BaseViewSet):
|
|||
class IssueUserDisplayPropertyEndpoint(BaseAPIView):
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def patch(self, request, slug, project_id):
|
||||
issue_property = IssueUserProperty.objects.get(
|
||||
user=request.user, project_id=project_id
|
||||
)
|
||||
issue_property = IssueUserProperty.objects.get(user=request.user, project_id=project_id)
|
||||
|
||||
issue_property.rich_filters = request.data.get(
|
||||
"rich_filters", issue_property.rich_filters
|
||||
)
|
||||
issue_property.rich_filters = request.data.get("rich_filters", issue_property.rich_filters)
|
||||
issue_property.filters = request.data.get("filters", issue_property.filters)
|
||||
issue_property.display_filters = request.data.get(
|
||||
"display_filters", issue_property.display_filters
|
||||
)
|
||||
issue_property.display_properties = request.data.get(
|
||||
"display_properties", issue_property.display_properties
|
||||
)
|
||||
issue_property.display_filters = request.data.get("display_filters", issue_property.display_filters)
|
||||
issue_property.display_properties = request.data.get("display_properties", issue_property.display_properties)
|
||||
issue_property.save()
|
||||
serializer = IssueUserPropertySerializer(issue_property)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def get(self, request, slug, project_id):
|
||||
issue_property, _ = IssueUserProperty.objects.get_or_create(
|
||||
user=request.user, project_id=project_id
|
||||
)
|
||||
issue_property, _ = IssueUserProperty.objects.get_or_create(user=request.user, project_id=project_id)
|
||||
serializer = IssueUserPropertySerializer(issue_property)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
|
|
@ -798,13 +741,9 @@ class BulkDeleteIssuesEndpoint(BaseAPIView):
|
|||
issue_ids = request.data.get("issue_ids", [])
|
||||
|
||||
if not len(issue_ids):
|
||||
return Response(
|
||||
{"error": "Issue IDs are required"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response({"error": "Issue IDs are required"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
issues = Issue.issue_objects.filter(
|
||||
workspace__slug=slug, project_id=project_id, pk__in=issue_ids
|
||||
)
|
||||
issues = Issue.issue_objects.filter(workspace__slug=slug, project_id=project_id, pk__in=issue_ids)
|
||||
|
||||
total_issues = len(issues)
|
||||
|
||||
|
|
@ -844,19 +783,11 @@ class IssuePaginatedViewSet(BaseViewSet):
|
|||
workspace_slug = self.kwargs.get("slug")
|
||||
project_id = self.kwargs.get("project_id")
|
||||
|
||||
issue_queryset = Issue.issue_objects.filter(
|
||||
workspace__slug=workspace_slug, project_id=project_id
|
||||
)
|
||||
issue_queryset = Issue.issue_objects.filter(workspace__slug=workspace_slug, project_id=project_id)
|
||||
|
||||
return (
|
||||
issue_queryset.select_related("state")
|
||||
.annotate(
|
||||
cycle_id=Subquery(
|
||||
CycleIssue.objects.filter(issue=OuterRef("id")).values("cycle_id")[
|
||||
:1
|
||||
]
|
||||
)
|
||||
)
|
||||
.annotate(cycle_id=Subquery(CycleIssue.objects.filter(issue=OuterRef("id")).values("cycle_id")[:1]))
|
||||
.annotate(
|
||||
link_count=Subquery(
|
||||
IssueLink.objects.filter(issue=OuterRef("id"))
|
||||
|
|
@ -891,9 +822,7 @@ class IssuePaginatedViewSet(BaseViewSet):
|
|||
|
||||
# converting the datetime fields in paginated data
|
||||
datetime_fields = ["created_at", "updated_at"]
|
||||
paginated_data = user_timezone_converter(
|
||||
paginated_data, datetime_fields, timezone
|
||||
)
|
||||
paginated_data = user_timezone_converter(paginated_data, datetime_fields, timezone)
|
||||
|
||||
return paginated_data
|
||||
|
||||
|
|
@ -937,9 +866,7 @@ class IssuePaginatedViewSet(BaseViewSet):
|
|||
required_fields.append("description_html")
|
||||
|
||||
# querying issues
|
||||
base_queryset = Issue.issue_objects.filter(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
base_queryset = Issue.issue_objects.filter(workspace__slug=slug, project_id=project_id)
|
||||
|
||||
base_queryset = base_queryset.order_by("updated_at")
|
||||
queryset = self.get_queryset().order_by("updated_at")
|
||||
|
|
@ -1018,9 +945,7 @@ class IssueDetailEndpoint(BaseAPIView):
|
|||
return (
|
||||
issues.annotate(
|
||||
cycle_id=Subquery(
|
||||
CycleIssue.objects.filter(
|
||||
issue=OuterRef("id"), deleted_at__isnull=True
|
||||
).values("cycle_id")[:1]
|
||||
CycleIssue.objects.filter(issue=OuterRef("id"), deleted_at__isnull=True).values("cycle_id")[:1]
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
|
|
@ -1071,9 +996,7 @@ class IssueDetailEndpoint(BaseAPIView):
|
|||
# check for the project member role, if the role is 5 then check for the guest_view_all_features
|
||||
# if it is true then show all the issues else show only the issues created by the user
|
||||
permission_subquery = (
|
||||
Issue.issue_objects.filter(
|
||||
workspace__slug=slug, project_id=project_id, id=OuterRef("id")
|
||||
)
|
||||
Issue.issue_objects.filter(workspace__slug=slug, project_id=project_id, id=OuterRef("id"))
|
||||
.filter(
|
||||
Q(
|
||||
project__project_projectmember__member=self.request.user,
|
||||
|
|
@ -1097,9 +1020,9 @@ class IssueDetailEndpoint(BaseAPIView):
|
|||
.values("id")
|
||||
)
|
||||
# Main issue query
|
||||
issue = Issue.issue_objects.filter(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
).filter(Exists(permission_subquery))
|
||||
issue = Issue.issue_objects.filter(workspace__slug=slug, project_id=project_id).filter(
|
||||
Exists(permission_subquery)
|
||||
)
|
||||
|
||||
# Add additional prefetch based on expand parameter
|
||||
if self.expand:
|
||||
|
|
@ -1133,9 +1056,7 @@ class IssueDetailEndpoint(BaseAPIView):
|
|||
order_by_param = request.GET.get("order_by", "-created_at")
|
||||
|
||||
# Issue queryset
|
||||
issue, order_by_param = order_issue_queryset(
|
||||
issue_queryset=issue, order_by_param=order_by_param
|
||||
)
|
||||
issue, order_by_param = order_issue_queryset(issue_queryset=issue, order_by_param=order_by_param)
|
||||
return self.paginate(
|
||||
request=request,
|
||||
order_by=order_by_param,
|
||||
|
|
@ -1188,9 +1109,7 @@ class IssueBulkUpdateDateEndpoint(BaseAPIView):
|
|||
|
||||
start_date = update.get("start_date")
|
||||
target_date = update.get("target_date")
|
||||
validate_dates = self.validate_dates(
|
||||
issue.start_date, issue.target_date, start_date, target_date
|
||||
)
|
||||
validate_dates = self.validate_dates(issue.start_date, issue.target_date, start_date, target_date)
|
||||
if not validate_dates:
|
||||
return Response(
|
||||
{"message": "Start date cannot exceed target date"},
|
||||
|
|
@ -1213,12 +1132,8 @@ class IssueBulkUpdateDateEndpoint(BaseAPIView):
|
|||
if target_date:
|
||||
issue_activity.delay(
|
||||
type="issue.activity.updated",
|
||||
requested_data=json.dumps(
|
||||
{"target_date": update.get("target_date")}
|
||||
),
|
||||
current_instance=json.dumps(
|
||||
{"target_date": str(issue.target_date)}
|
||||
),
|
||||
requested_data=json.dumps({"target_date": update.get("target_date")}),
|
||||
current_instance=json.dumps({"target_date": str(issue.target_date)}),
|
||||
issue_id=str(issue_id),
|
||||
actor_id=str(request.user.id),
|
||||
project_id=str(project_id),
|
||||
|
|
@ -1230,9 +1145,7 @@ class IssueBulkUpdateDateEndpoint(BaseAPIView):
|
|||
# Bulk update issues
|
||||
Issue.objects.bulk_update(issues_to_update, ["start_date", "target_date"])
|
||||
|
||||
return Response(
|
||||
{"message": "Issues updated successfully"}, status=status.HTTP_200_OK
|
||||
)
|
||||
return Response({"message": "Issues updated successfully"}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class IssueMetaEndpoint(BaseAPIView):
|
||||
|
|
@ -1267,9 +1180,7 @@ class IssueDetailIdentifierEndpoint(BaseAPIView):
|
|||
)
|
||||
|
||||
# Fetch the project
|
||||
project = Project.objects.get(
|
||||
identifier__iexact=project_identifier, workspace__slug=slug
|
||||
)
|
||||
project = Project.objects.get(identifier__iexact=project_identifier, workspace__slug=slug)
|
||||
|
||||
# Check if the user is a member of the project
|
||||
if not ProjectMember.objects.filter(
|
||||
|
|
@ -1289,13 +1200,7 @@ class IssueDetailIdentifierEndpoint(BaseAPIView):
|
|||
.filter(workspace__slug=slug)
|
||||
.select_related("workspace", "project", "state", "parent")
|
||||
.prefetch_related("assignees", "labels", "issue_module__module")
|
||||
.annotate(
|
||||
cycle_id=Subquery(
|
||||
CycleIssue.objects.filter(issue=OuterRef("id")).values("cycle_id")[
|
||||
:1
|
||||
]
|
||||
)
|
||||
)
|
||||
.annotate(cycle_id=Subquery(CycleIssue.objects.filter(issue=OuterRef("id")).values("cycle_id")[:1]))
|
||||
.annotate(
|
||||
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
|
||||
.order_by()
|
||||
|
|
@ -1323,10 +1228,7 @@ class IssueDetailIdentifierEndpoint(BaseAPIView):
|
|||
ArrayAgg(
|
||||
"labels__id",
|
||||
distinct=True,
|
||||
filter=Q(
|
||||
~Q(labels__id__isnull=True)
|
||||
& Q(label_issue__deleted_at__isnull=True)
|
||||
),
|
||||
filter=Q(~Q(labels__id__isnull=True) & Q(label_issue__deleted_at__isnull=True)),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -77,9 +77,7 @@ class IssueCommentViewSet(BaseViewSet):
|
|||
)
|
||||
serializer = IssueCommentSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save(
|
||||
project_id=project_id, issue_id=issue_id, actor=request.user
|
||||
)
|
||||
serializer.save(project_id=project_id, issue_id=issue_id, actor=request.user)
|
||||
issue_activity.delay(
|
||||
type="comment.activity.created",
|
||||
requested_data=json.dumps(serializer.data, cls=DjangoJSONEncoder),
|
||||
|
|
@ -106,21 +104,12 @@ class IssueCommentViewSet(BaseViewSet):
|
|||
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN], creator=True, model=IssueComment)
|
||||
def partial_update(self, request, slug, project_id, issue_id, pk):
|
||||
issue_comment = IssueComment.objects.get(
|
||||
workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk
|
||||
)
|
||||
issue_comment = IssueComment.objects.get(workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk)
|
||||
requested_data = json.dumps(self.request.data, cls=DjangoJSONEncoder)
|
||||
current_instance = json.dumps(
|
||||
IssueCommentSerializer(issue_comment).data, cls=DjangoJSONEncoder
|
||||
)
|
||||
serializer = IssueCommentSerializer(
|
||||
issue_comment, data=request.data, partial=True
|
||||
)
|
||||
current_instance = json.dumps(IssueCommentSerializer(issue_comment).data, cls=DjangoJSONEncoder)
|
||||
serializer = IssueCommentSerializer(issue_comment, data=request.data, partial=True)
|
||||
if serializer.is_valid():
|
||||
if (
|
||||
"comment_html" in request.data
|
||||
and request.data["comment_html"] != issue_comment.comment_html
|
||||
):
|
||||
if "comment_html" in request.data and request.data["comment_html"] != issue_comment.comment_html:
|
||||
serializer.save(edited_at=timezone.now())
|
||||
else:
|
||||
serializer.save()
|
||||
|
|
@ -150,12 +139,8 @@ class IssueCommentViewSet(BaseViewSet):
|
|||
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN], creator=True, model=IssueComment)
|
||||
def destroy(self, request, slug, project_id, issue_id, pk):
|
||||
issue_comment = IssueComment.objects.get(
|
||||
workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk
|
||||
)
|
||||
current_instance = json.dumps(
|
||||
IssueCommentSerializer(issue_comment).data, cls=DjangoJSONEncoder
|
||||
)
|
||||
issue_comment = IssueComment.objects.get(workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk)
|
||||
current_instance = json.dumps(IssueCommentSerializer(issue_comment).data, cls=DjangoJSONEncoder)
|
||||
issue_comment.delete()
|
||||
issue_activity.delay(
|
||||
type="comment.activity.deleted",
|
||||
|
|
|
|||
|
|
@ -35,9 +35,7 @@ class LabelViewSet(BaseViewSet):
|
|||
.order_by("sort_order")
|
||||
)
|
||||
|
||||
@invalidate_cache(
|
||||
path="/api/workspaces/:slug/labels/", url_params=True, user=False, multiple=True
|
||||
)
|
||||
@invalidate_cache(path="/api/workspaces/:slug/labels/", url_params=True, user=False, multiple=True)
|
||||
@allow_permission([ROLE.ADMIN])
|
||||
def create(self, request, slug, project_id):
|
||||
try:
|
||||
|
|
@ -58,9 +56,7 @@ class LabelViewSet(BaseViewSet):
|
|||
# Check if the label name is unique within the project
|
||||
if (
|
||||
"name" in request.data
|
||||
and Label.objects.filter(
|
||||
project_id=kwargs["project_id"], name=request.data["name"]
|
||||
)
|
||||
and Label.objects.filter(project_id=kwargs["project_id"], name=request.data["name"])
|
||||
.exclude(pk=kwargs["pk"])
|
||||
.exists()
|
||||
):
|
||||
|
|
|
|||
|
|
@ -45,9 +45,7 @@ class IssueLinkViewSet(BaseViewSet):
|
|||
serializer = IssueLinkSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save(project_id=project_id, issue_id=issue_id)
|
||||
crawl_work_item_link_title.delay(
|
||||
serializer.data.get("id"), serializer.data.get("url")
|
||||
)
|
||||
crawl_work_item_link_title.delay(serializer.data.get("id"), serializer.data.get("url"))
|
||||
issue_activity.delay(
|
||||
type="link.activity.created",
|
||||
requested_data=json.dumps(serializer.data, cls=DjangoJSONEncoder),
|
||||
|
|
@ -67,20 +65,14 @@ class IssueLinkViewSet(BaseViewSet):
|
|||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def partial_update(self, request, slug, project_id, issue_id, pk):
|
||||
issue_link = IssueLink.objects.get(
|
||||
workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk
|
||||
)
|
||||
issue_link = IssueLink.objects.get(workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk)
|
||||
requested_data = json.dumps(request.data, cls=DjangoJSONEncoder)
|
||||
current_instance = json.dumps(
|
||||
IssueLinkSerializer(issue_link).data, cls=DjangoJSONEncoder
|
||||
)
|
||||
current_instance = json.dumps(IssueLinkSerializer(issue_link).data, cls=DjangoJSONEncoder)
|
||||
|
||||
serializer = IssueLinkSerializer(issue_link, data=request.data, partial=True)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
crawl_work_item_link_title.delay(
|
||||
serializer.data.get("id"), serializer.data.get("url")
|
||||
)
|
||||
crawl_work_item_link_title.delay(serializer.data.get("id"), serializer.data.get("url"))
|
||||
|
||||
issue_activity.delay(
|
||||
type="link.activity.updated",
|
||||
|
|
@ -100,12 +92,8 @@ class IssueLinkViewSet(BaseViewSet):
|
|||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def destroy(self, request, slug, project_id, issue_id, pk):
|
||||
issue_link = IssueLink.objects.get(
|
||||
workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk
|
||||
)
|
||||
current_instance = json.dumps(
|
||||
IssueLinkSerializer(issue_link).data, cls=DjangoJSONEncoder
|
||||
)
|
||||
issue_link = IssueLink.objects.get(workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk)
|
||||
current_instance = json.dumps(IssueLinkSerializer(issue_link).data, cls=DjangoJSONEncoder)
|
||||
issue_activity.delay(
|
||||
type="link.activity.deleted",
|
||||
requested_data=json.dumps({"link_id": str(pk)}),
|
||||
|
|
|
|||
|
|
@ -42,9 +42,7 @@ class IssueReactionViewSet(BaseViewSet):
|
|||
def create(self, request, slug, project_id, issue_id):
|
||||
serializer = IssueReactionSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save(
|
||||
issue_id=issue_id, project_id=project_id, actor=request.user
|
||||
)
|
||||
serializer.save(issue_id=issue_id, project_id=project_id, actor=request.user)
|
||||
issue_activity.delay(
|
||||
type="issue_reaction.activity.created",
|
||||
requested_data=json.dumps(request.data, cls=DjangoJSONEncoder),
|
||||
|
|
@ -74,9 +72,7 @@ class IssueReactionViewSet(BaseViewSet):
|
|||
actor_id=str(self.request.user.id),
|
||||
issue_id=str(self.kwargs.get("issue_id", None)),
|
||||
project_id=str(self.kwargs.get("project_id", None)),
|
||||
current_instance=json.dumps(
|
||||
{"reaction": str(reaction_code), "identifier": str(issue_reaction.id)}
|
||||
),
|
||||
current_instance=json.dumps({"reaction": str(reaction_code), "identifier": str(issue_reaction.id)}),
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
notification=True,
|
||||
origin=base_host(request=request, is_app=True),
|
||||
|
|
|
|||
|
|
@ -37,9 +37,7 @@ class IssueRelationViewSet(BaseViewSet):
|
|||
|
||||
def list(self, request, slug, project_id, issue_id):
|
||||
issue_relations = (
|
||||
IssueRelation.objects.filter(
|
||||
Q(issue_id=issue_id) | Q(related_issue=issue_id)
|
||||
)
|
||||
IssueRelation.objects.filter(Q(issue_id=issue_id) | Q(related_issue=issue_id))
|
||||
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||
.select_related("project")
|
||||
.select_related("workspace")
|
||||
|
|
@ -48,19 +46,19 @@ class IssueRelationViewSet(BaseViewSet):
|
|||
.distinct()
|
||||
)
|
||||
# get all blocking issues
|
||||
blocking_issues = issue_relations.filter(
|
||||
relation_type="blocked_by", related_issue_id=issue_id
|
||||
).values_list("issue_id", flat=True)
|
||||
blocking_issues = issue_relations.filter(relation_type="blocked_by", related_issue_id=issue_id).values_list(
|
||||
"issue_id", flat=True
|
||||
)
|
||||
|
||||
# get all blocked by issues
|
||||
blocked_by_issues = issue_relations.filter(
|
||||
relation_type="blocked_by", issue_id=issue_id
|
||||
).values_list("related_issue_id", flat=True)
|
||||
blocked_by_issues = issue_relations.filter(relation_type="blocked_by", issue_id=issue_id).values_list(
|
||||
"related_issue_id", flat=True
|
||||
)
|
||||
|
||||
# get all duplicate issues
|
||||
duplicate_issues = issue_relations.filter(
|
||||
issue_id=issue_id, relation_type="duplicate"
|
||||
).values_list("related_issue_id", flat=True)
|
||||
duplicate_issues = issue_relations.filter(issue_id=issue_id, relation_type="duplicate").values_list(
|
||||
"related_issue_id", flat=True
|
||||
)
|
||||
|
||||
# get all relates to issues
|
||||
duplicate_issues_related = issue_relations.filter(
|
||||
|
|
@ -68,9 +66,9 @@ class IssueRelationViewSet(BaseViewSet):
|
|||
).values_list("issue_id", flat=True)
|
||||
|
||||
# get all relates to issues
|
||||
relates_to_issues = issue_relations.filter(
|
||||
issue_id=issue_id, relation_type="relates_to"
|
||||
).values_list("related_issue_id", flat=True)
|
||||
relates_to_issues = issue_relations.filter(issue_id=issue_id, relation_type="relates_to").values_list(
|
||||
"related_issue_id", flat=True
|
||||
)
|
||||
|
||||
# get all relates to issues
|
||||
relates_to_issues_related = issue_relations.filter(
|
||||
|
|
@ -83,9 +81,9 @@ class IssueRelationViewSet(BaseViewSet):
|
|||
).values_list("issue_id", flat=True)
|
||||
|
||||
# get all start_before issues
|
||||
start_before_issues = issue_relations.filter(
|
||||
relation_type="start_before", issue_id=issue_id
|
||||
).values_list("related_issue_id", flat=True)
|
||||
start_before_issues = issue_relations.filter(relation_type="start_before", issue_id=issue_id).values_list(
|
||||
"related_issue_id", flat=True
|
||||
)
|
||||
|
||||
# get all finish after issues
|
||||
finish_after_issues = issue_relations.filter(
|
||||
|
|
@ -93,9 +91,9 @@ class IssueRelationViewSet(BaseViewSet):
|
|||
).values_list("issue_id", flat=True)
|
||||
|
||||
# get all finish before issues
|
||||
finish_before_issues = issue_relations.filter(
|
||||
relation_type="finish_before", issue_id=issue_id
|
||||
).values_list("related_issue_id", flat=True)
|
||||
finish_before_issues = issue_relations.filter(relation_type="finish_before", issue_id=issue_id).values_list(
|
||||
"related_issue_id", flat=True
|
||||
)
|
||||
|
||||
queryset = (
|
||||
Issue.issue_objects.filter(workspace__slug=slug)
|
||||
|
|
@ -103,9 +101,7 @@ class IssueRelationViewSet(BaseViewSet):
|
|||
.prefetch_related("assignees", "labels", "issue_module__module")
|
||||
.annotate(
|
||||
cycle_id=Subquery(
|
||||
CycleIssue.objects.filter(
|
||||
issue=OuterRef("id"), deleted_at__isnull=True
|
||||
).values("cycle_id")[:1]
|
||||
CycleIssue.objects.filter(issue=OuterRef("id"), deleted_at__isnull=True).values("cycle_id")[:1]
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
|
|
@ -134,10 +130,7 @@ class IssueRelationViewSet(BaseViewSet):
|
|||
ArrayAgg(
|
||||
"labels__id",
|
||||
distinct=True,
|
||||
filter=Q(
|
||||
~Q(labels__id__isnull=True)
|
||||
& (Q(label_issue__deleted_at__isnull=True))
|
||||
),
|
||||
filter=Q(~Q(labels__id__isnull=True) & (Q(label_issue__deleted_at__isnull=True))),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
),
|
||||
|
|
@ -223,15 +216,9 @@ class IssueRelationViewSet(BaseViewSet):
|
|||
issue_relation = IssueRelation.objects.bulk_create(
|
||||
[
|
||||
IssueRelation(
|
||||
issue_id=(
|
||||
issue
|
||||
if relation_type in ["blocking", "start_after", "finish_after"]
|
||||
else issue_id
|
||||
),
|
||||
issue_id=(issue if relation_type in ["blocking", "start_after", "finish_after"] else issue_id),
|
||||
related_issue_id=(
|
||||
issue_id
|
||||
if relation_type in ["blocking", "start_after", "finish_after"]
|
||||
else issue
|
||||
issue_id if relation_type in ["blocking", "start_after", "finish_after"] else issue
|
||||
),
|
||||
relation_type=(get_actual_relation(relation_type)),
|
||||
project_id=project_id,
|
||||
|
|
@ -274,13 +261,10 @@ class IssueRelationViewSet(BaseViewSet):
|
|||
issue_relations = IssueRelation.objects.filter(
|
||||
workspace__slug=slug,
|
||||
).filter(
|
||||
Q(issue_id=related_issue, related_issue_id=issue_id)
|
||||
| Q(issue_id=issue_id, related_issue_id=related_issue)
|
||||
Q(issue_id=related_issue, related_issue_id=issue_id) | Q(issue_id=issue_id, related_issue_id=related_issue)
|
||||
)
|
||||
issue_relations = issue_relations.first()
|
||||
current_instance = json.dumps(
|
||||
IssueRelationSerializer(issue_relations).data, cls=DjangoJSONEncoder
|
||||
)
|
||||
current_instance = json.dumps(IssueRelationSerializer(issue_relations).data, cls=DjangoJSONEncoder)
|
||||
issue_relations.delete()
|
||||
issue_activity.delay(
|
||||
type="issue_relation.activity.deleted",
|
||||
|
|
|
|||
|
|
@ -37,9 +37,7 @@ class SubIssuesEndpoint(BaseAPIView):
|
|||
.prefetch_related("assignees", "labels", "issue_module__module")
|
||||
.annotate(
|
||||
cycle_id=Subquery(
|
||||
CycleIssue.objects.filter(
|
||||
issue=OuterRef("id"), deleted_at__isnull=True
|
||||
).values("cycle_id")[:1]
|
||||
CycleIssue.objects.filter(issue=OuterRef("id"), deleted_at__isnull=True).values("cycle_id")[:1]
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
|
|
@ -68,10 +66,7 @@ class SubIssuesEndpoint(BaseAPIView):
|
|||
ArrayAgg(
|
||||
"labels__id",
|
||||
distinct=True,
|
||||
filter=Q(
|
||||
~Q(labels__id__isnull=True)
|
||||
& Q(label_issue__deleted_at__isnull=True)
|
||||
),
|
||||
filter=Q(~Q(labels__id__isnull=True) & Q(label_issue__deleted_at__isnull=True)),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
),
|
||||
|
|
@ -109,9 +104,7 @@ class SubIssuesEndpoint(BaseAPIView):
|
|||
group_by = request.GET.get("group_by", False)
|
||||
|
||||
if order_by_param:
|
||||
sub_issues, order_by_param = order_issue_queryset(
|
||||
sub_issues, order_by_param
|
||||
)
|
||||
sub_issues, order_by_param = order_issue_queryset(sub_issues, order_by_param)
|
||||
|
||||
# create's a dict with state group name with their respective issue id's
|
||||
result = defaultdict(list)
|
||||
|
|
@ -146,9 +139,7 @@ class SubIssuesEndpoint(BaseAPIView):
|
|||
"archived_at",
|
||||
)
|
||||
datetime_fields = ["created_at", "updated_at"]
|
||||
sub_issues = user_timezone_converter(
|
||||
sub_issues, datetime_fields, request.user.user_timezone
|
||||
)
|
||||
sub_issues = user_timezone_converter(sub_issues, datetime_fields, request.user.user_timezone)
|
||||
# Grouping
|
||||
if group_by:
|
||||
result_dict = defaultdict(list)
|
||||
|
|
@ -192,9 +183,7 @@ class SubIssuesEndpoint(BaseAPIView):
|
|||
|
||||
_ = Issue.objects.bulk_update(sub_issues, ["parent"], batch_size=10)
|
||||
|
||||
updated_sub_issues = Issue.issue_objects.filter(id__in=sub_issue_ids).annotate(
|
||||
state_group=F("state__group")
|
||||
)
|
||||
updated_sub_issues = Issue.issue_objects.filter(id__in=sub_issue_ids).annotate(state_group=F("state__group"))
|
||||
|
||||
# Track the issue
|
||||
_ = [
|
||||
|
|
|
|||
|
|
@ -25,9 +25,7 @@ class IssueVersionEndpoint(BaseAPIView):
|
|||
paginated_data = results.values(*fields)
|
||||
|
||||
datetime_fields = ["created_at", "updated_at"]
|
||||
paginated_data = user_timezone_converter(
|
||||
paginated_data, datetime_fields, timezone
|
||||
)
|
||||
paginated_data = user_timezone_converter(paginated_data, datetime_fields, timezone)
|
||||
|
||||
return paginated_data
|
||||
|
||||
|
|
@ -77,18 +75,14 @@ class WorkItemDescriptionVersionEndpoint(BaseAPIView):
|
|||
paginated_data = results.values(*fields)
|
||||
|
||||
datetime_fields = ["created_at", "updated_at"]
|
||||
paginated_data = user_timezone_converter(
|
||||
paginated_data, datetime_fields, timezone
|
||||
)
|
||||
paginated_data = user_timezone_converter(paginated_data, datetime_fields, timezone)
|
||||
|
||||
return paginated_data
|
||||
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def get(self, request, slug, project_id, work_item_id, pk=None):
|
||||
project = Project.objects.get(pk=project_id)
|
||||
issue = Issue.objects.get(
|
||||
workspace__slug=slug, project_id=project_id, pk=work_item_id
|
||||
)
|
||||
issue = Issue.objects.get(workspace__slug=slug, project_id=project_id, pk=work_item_id)
|
||||
|
||||
if (
|
||||
ProjectMember.objects.filter(
|
||||
|
|
@ -114,9 +108,7 @@ class WorkItemDescriptionVersionEndpoint(BaseAPIView):
|
|||
pk=pk,
|
||||
)
|
||||
|
||||
serializer = IssueDescriptionVersionDetailSerializer(
|
||||
issue_description_version
|
||||
)
|
||||
serializer = IssueDescriptionVersionDetailSerializer(issue_description_version)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
cursor = request.GET.get("cursor", None)
|
||||
|
|
|
|||
|
|
@ -113,11 +113,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
issue_module__deleted_at__isnull=True,
|
||||
)
|
||||
.values("issue_module__module_id")
|
||||
.annotate(
|
||||
completed_estimate_points=Sum(
|
||||
Cast("estimate_point__value", FloatField())
|
||||
)
|
||||
)
|
||||
.annotate(completed_estimate_points=Sum(Cast("estimate_point__value", FloatField())))
|
||||
.values("completed_estimate_points")[:1]
|
||||
)
|
||||
|
||||
|
|
@ -128,9 +124,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
issue_module__deleted_at__isnull=True,
|
||||
)
|
||||
.values("issue_module__module_id")
|
||||
.annotate(
|
||||
total_estimate_points=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.annotate(total_estimate_points=Sum(Cast("estimate_point__value", FloatField())))
|
||||
.values("total_estimate_points")[:1]
|
||||
)
|
||||
backlog_estimate_point = (
|
||||
|
|
@ -141,9 +135,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
issue_module__deleted_at__isnull=True,
|
||||
)
|
||||
.values("issue_module__module_id")
|
||||
.annotate(
|
||||
backlog_estimate_point=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.annotate(backlog_estimate_point=Sum(Cast("estimate_point__value", FloatField())))
|
||||
.values("backlog_estimate_point")[:1]
|
||||
)
|
||||
unstarted_estimate_point = (
|
||||
|
|
@ -154,11 +146,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
issue_module__deleted_at__isnull=True,
|
||||
)
|
||||
.values("issue_module__module_id")
|
||||
.annotate(
|
||||
unstarted_estimate_point=Sum(
|
||||
Cast("estimate_point__value", FloatField())
|
||||
)
|
||||
)
|
||||
.annotate(unstarted_estimate_point=Sum(Cast("estimate_point__value", FloatField())))
|
||||
.values("unstarted_estimate_point")[:1]
|
||||
)
|
||||
started_estimate_point = (
|
||||
|
|
@ -169,9 +157,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
issue_module__deleted_at__isnull=True,
|
||||
)
|
||||
.values("issue_module__module_id")
|
||||
.annotate(
|
||||
started_estimate_point=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.annotate(started_estimate_point=Sum(Cast("estimate_point__value", FloatField())))
|
||||
.values("started_estimate_point")[:1]
|
||||
)
|
||||
cancelled_estimate_point = (
|
||||
|
|
@ -182,11 +168,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
issue_module__deleted_at__isnull=True,
|
||||
)
|
||||
.values("issue_module__module_id")
|
||||
.annotate(
|
||||
cancelled_estimate_point=Sum(
|
||||
Cast("estimate_point__value", FloatField())
|
||||
)
|
||||
)
|
||||
.annotate(cancelled_estimate_point=Sum(Cast("estimate_point__value", FloatField())))
|
||||
.values("cancelled_estimate_point")[:1]
|
||||
)
|
||||
return (
|
||||
|
|
@ -214,27 +196,15 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
Value(0, output_field=IntegerField()),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
started_issues=Coalesce(
|
||||
Subquery(started_issues[:1]), Value(0, output_field=IntegerField())
|
||||
)
|
||||
)
|
||||
.annotate(started_issues=Coalesce(Subquery(started_issues[:1]), Value(0, output_field=IntegerField())))
|
||||
.annotate(
|
||||
unstarted_issues=Coalesce(
|
||||
Subquery(unstarted_issues[:1]),
|
||||
Value(0, output_field=IntegerField()),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
backlog_issues=Coalesce(
|
||||
Subquery(backlog_issues[:1]), Value(0, output_field=IntegerField())
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
total_issues=Coalesce(
|
||||
Subquery(total_issues[:1]), Value(0, output_field=IntegerField())
|
||||
)
|
||||
)
|
||||
.annotate(backlog_issues=Coalesce(Subquery(backlog_issues[:1]), Value(0, output_field=IntegerField())))
|
||||
.annotate(total_issues=Coalesce(Subquery(total_issues[:1]), Value(0, output_field=IntegerField())))
|
||||
.annotate(
|
||||
backlog_estimate_points=Coalesce(
|
||||
Subquery(backlog_estimate_point),
|
||||
|
|
@ -266,9 +236,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
)
|
||||
)
|
||||
.annotate(
|
||||
total_estimate_points=Coalesce(
|
||||
Subquery(total_estimate_point), Value(0, output_field=FloatField())
|
||||
)
|
||||
total_estimate_points=Coalesce(Subquery(total_estimate_point), Value(0, output_field=FloatField()))
|
||||
)
|
||||
.annotate(
|
||||
member_ids=Coalesce(
|
||||
|
|
@ -317,9 +285,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
"archived_at",
|
||||
)
|
||||
datetime_fields = ["created_at", "updated_at"]
|
||||
modules = user_timezone_converter(
|
||||
modules, datetime_fields, request.user.user_timezone
|
||||
)
|
||||
modules = user_timezone_converter(modules, datetime_fields, request.user.user_timezone)
|
||||
return Response(modules, status=status.HTTP_200_OK)
|
||||
else:
|
||||
queryset = (
|
||||
|
|
@ -389,9 +355,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
"avatar_url",
|
||||
"display_name",
|
||||
)
|
||||
.annotate(
|
||||
total_estimates=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.annotate(total_estimates=Sum(Cast("estimate_point__value", FloatField())))
|
||||
.annotate(
|
||||
completed_estimates=Sum(
|
||||
Cast("estimate_point__value", FloatField()),
|
||||
|
|
@ -425,9 +389,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
.annotate(color=F("labels__color"))
|
||||
.annotate(label_id=F("labels__id"))
|
||||
.values("label_name", "color", "label_id")
|
||||
.annotate(
|
||||
total_estimates=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.annotate(total_estimates=Sum(Cast("estimate_point__value", FloatField())))
|
||||
.annotate(
|
||||
completed_estimates=Sum(
|
||||
Cast("estimate_point__value", FloatField()),
|
||||
|
|
@ -500,11 +462,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
"avatar_url",
|
||||
"display_name",
|
||||
)
|
||||
.annotate(
|
||||
total_issues=Count(
|
||||
"id", filter=Q(archived_at__isnull=True, is_draft=False)
|
||||
)
|
||||
)
|
||||
.annotate(total_issues=Count("id", filter=Q(archived_at__isnull=True, is_draft=False)))
|
||||
.annotate(
|
||||
completed_issues=Count(
|
||||
"id",
|
||||
|
|
@ -539,11 +497,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
.annotate(color=F("labels__color"))
|
||||
.annotate(label_id=F("labels__id"))
|
||||
.values("label_name", "color", "label_id")
|
||||
.annotate(
|
||||
total_issues=Count(
|
||||
"id", filter=Q(archived_at__isnull=True, is_draft=False)
|
||||
)
|
||||
)
|
||||
.annotate(total_issues=Count("id", filter=Q(archived_at__isnull=True, is_draft=False)))
|
||||
.annotate(
|
||||
completed_issues=Count(
|
||||
"id",
|
||||
|
|
@ -584,9 +538,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
return Response(data, status=status.HTTP_200_OK)
|
||||
|
||||
def post(self, request, slug, project_id, module_id):
|
||||
module = Module.objects.get(
|
||||
pk=module_id, project_id=project_id, workspace__slug=slug
|
||||
)
|
||||
module = Module.objects.get(pk=module_id, project_id=project_id, workspace__slug=slug)
|
||||
if module.status not in ["completed", "cancelled"]:
|
||||
return Response(
|
||||
{"error": "Only completed or cancelled modules can be archived"},
|
||||
|
|
@ -600,14 +552,10 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
project_id=project_id,
|
||||
workspace__slug=slug,
|
||||
).delete()
|
||||
return Response(
|
||||
{"archived_at": str(module.archived_at)}, status=status.HTTP_200_OK
|
||||
)
|
||||
return Response({"archived_at": str(module.archived_at)}, status=status.HTTP_200_OK)
|
||||
|
||||
def delete(self, request, slug, project_id, module_id):
|
||||
module = Module.objects.get(
|
||||
pk=module_id, project_id=project_id, workspace__slug=slug
|
||||
)
|
||||
module = Module.objects.get(pk=module_id, project_id=project_id, workspace__slug=slug)
|
||||
module.archived_at = None
|
||||
module.save()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
|
|
|||
|
|
@ -69,11 +69,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
webhook_event = "module"
|
||||
|
||||
def get_serializer_class(self):
|
||||
return (
|
||||
ModuleWriteSerializer
|
||||
if self.action in ["create", "update", "partial_update"]
|
||||
else ModuleSerializer
|
||||
)
|
||||
return ModuleWriteSerializer if self.action in ["create", "update", "partial_update"] else ModuleSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
favorite_subquery = UserFavorite.objects.filter(
|
||||
|
|
@ -150,11 +146,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
issue_module__deleted_at__isnull=True,
|
||||
)
|
||||
.values("issue_module__module_id")
|
||||
.annotate(
|
||||
completed_estimate_points=Sum(
|
||||
Cast("estimate_point__value", FloatField())
|
||||
)
|
||||
)
|
||||
.annotate(completed_estimate_points=Sum(Cast("estimate_point__value", FloatField())))
|
||||
.values("completed_estimate_points")[:1]
|
||||
)
|
||||
|
||||
|
|
@ -165,9 +157,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
issue_module__deleted_at__isnull=True,
|
||||
)
|
||||
.values("issue_module__module_id")
|
||||
.annotate(
|
||||
total_estimate_points=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.annotate(total_estimate_points=Sum(Cast("estimate_point__value", FloatField())))
|
||||
.values("total_estimate_points")[:1]
|
||||
)
|
||||
backlog_estimate_point = (
|
||||
|
|
@ -178,9 +168,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
issue_module__deleted_at__isnull=True,
|
||||
)
|
||||
.values("issue_module__module_id")
|
||||
.annotate(
|
||||
backlog_estimate_point=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.annotate(backlog_estimate_point=Sum(Cast("estimate_point__value", FloatField())))
|
||||
.values("backlog_estimate_point")[:1]
|
||||
)
|
||||
unstarted_estimate_point = (
|
||||
|
|
@ -191,11 +179,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
issue_module__deleted_at__isnull=True,
|
||||
)
|
||||
.values("issue_module__module_id")
|
||||
.annotate(
|
||||
unstarted_estimate_point=Sum(
|
||||
Cast("estimate_point__value", FloatField())
|
||||
)
|
||||
)
|
||||
.annotate(unstarted_estimate_point=Sum(Cast("estimate_point__value", FloatField())))
|
||||
.values("unstarted_estimate_point")[:1]
|
||||
)
|
||||
started_estimate_point = (
|
||||
|
|
@ -206,9 +190,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
issue_module__deleted_at__isnull=True,
|
||||
)
|
||||
.values("issue_module__module_id")
|
||||
.annotate(
|
||||
started_estimate_point=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.annotate(started_estimate_point=Sum(Cast("estimate_point__value", FloatField())))
|
||||
.values("started_estimate_point")[:1]
|
||||
)
|
||||
cancelled_estimate_point = (
|
||||
|
|
@ -219,11 +201,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
issue_module__deleted_at__isnull=True,
|
||||
)
|
||||
.values("issue_module__module_id")
|
||||
.annotate(
|
||||
cancelled_estimate_point=Sum(
|
||||
Cast("estimate_point__value", FloatField())
|
||||
)
|
||||
)
|
||||
.annotate(cancelled_estimate_point=Sum(Cast("estimate_point__value", FloatField())))
|
||||
.values("cancelled_estimate_point")[:1]
|
||||
)
|
||||
return (
|
||||
|
|
@ -251,27 +229,15 @@ class ModuleViewSet(BaseViewSet):
|
|||
Value(0, output_field=IntegerField()),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
started_issues=Coalesce(
|
||||
Subquery(started_issues[:1]), Value(0, output_field=IntegerField())
|
||||
)
|
||||
)
|
||||
.annotate(started_issues=Coalesce(Subquery(started_issues[:1]), Value(0, output_field=IntegerField())))
|
||||
.annotate(
|
||||
unstarted_issues=Coalesce(
|
||||
Subquery(unstarted_issues[:1]),
|
||||
Value(0, output_field=IntegerField()),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
backlog_issues=Coalesce(
|
||||
Subquery(backlog_issues[:1]), Value(0, output_field=IntegerField())
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
total_issues=Coalesce(
|
||||
Subquery(total_issues[:1]), Value(0, output_field=IntegerField())
|
||||
)
|
||||
)
|
||||
.annotate(backlog_issues=Coalesce(Subquery(backlog_issues[:1]), Value(0, output_field=IntegerField())))
|
||||
.annotate(total_issues=Coalesce(Subquery(total_issues[:1]), Value(0, output_field=IntegerField())))
|
||||
.annotate(
|
||||
backlog_estimate_points=Coalesce(
|
||||
Subquery(backlog_estimate_point),
|
||||
|
|
@ -303,9 +269,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
)
|
||||
)
|
||||
.annotate(
|
||||
total_estimate_points=Coalesce(
|
||||
Subquery(total_estimate_point), Value(0, output_field=FloatField())
|
||||
)
|
||||
total_estimate_points=Coalesce(Subquery(total_estimate_point), Value(0, output_field=FloatField()))
|
||||
)
|
||||
.annotate(
|
||||
member_ids=Coalesce(
|
||||
|
|
@ -326,9 +290,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def create(self, request, slug, project_id):
|
||||
project = Project.objects.get(workspace__slug=slug, pk=project_id)
|
||||
serializer = ModuleWriteSerializer(
|
||||
data=request.data, context={"project": project}
|
||||
)
|
||||
serializer = ModuleWriteSerializer(data=request.data, context={"project": project})
|
||||
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
|
|
@ -380,9 +342,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
origin=base_host(request=request, is_app=True),
|
||||
)
|
||||
datetime_fields = ["created_at", "updated_at"]
|
||||
module = user_timezone_converter(
|
||||
module, datetime_fields, request.user.user_timezone
|
||||
)
|
||||
module = user_timezone_converter(module, datetime_fields, request.user.user_timezone)
|
||||
return Response(module, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
|
@ -425,9 +385,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
"updated_at",
|
||||
)
|
||||
datetime_fields = ["created_at", "updated_at"]
|
||||
modules = user_timezone_converter(
|
||||
modules, datetime_fields, request.user.user_timezone
|
||||
)
|
||||
modules = user_timezone_converter(modules, datetime_fields, request.user.user_timezone)
|
||||
return Response(modules, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
|
|
@ -450,9 +408,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
)
|
||||
|
||||
if not queryset.exists():
|
||||
return Response(
|
||||
{"error": "Module not found"}, status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
return Response({"error": "Module not found"}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
estimate_type = Project.objects.filter(
|
||||
workspace__slug=slug,
|
||||
|
|
@ -505,9 +461,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
"avatar_url",
|
||||
"display_name",
|
||||
)
|
||||
.annotate(
|
||||
total_estimates=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.annotate(total_estimates=Sum(Cast("estimate_point__value", FloatField())))
|
||||
.annotate(
|
||||
completed_estimates=Sum(
|
||||
Cast("estimate_point__value", FloatField()),
|
||||
|
|
@ -542,9 +496,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
.annotate(color=F("labels__color"))
|
||||
.annotate(label_id=F("labels__id"))
|
||||
.values("label_name", "color", "label_id")
|
||||
.annotate(
|
||||
total_estimates=Sum(Cast("estimate_point__value", FloatField()))
|
||||
)
|
||||
.annotate(total_estimates=Sum(Cast("estimate_point__value", FloatField())))
|
||||
.annotate(
|
||||
completed_estimates=Sum(
|
||||
Cast("estimate_point__value", FloatField()),
|
||||
|
|
@ -602,21 +554,13 @@ class ModuleViewSet(BaseViewSet):
|
|||
),
|
||||
),
|
||||
# If `avatar_asset` is None, fall back to using `avatar` field directly
|
||||
When(
|
||||
assignees__avatar_asset__isnull=True, then="assignees__avatar"
|
||||
),
|
||||
When(assignees__avatar_asset__isnull=True, then="assignees__avatar"),
|
||||
default=Value(None),
|
||||
output_field=models.CharField(),
|
||||
)
|
||||
)
|
||||
.values(
|
||||
"first_name", "last_name", "assignee_id", "avatar_url", "display_name"
|
||||
)
|
||||
.annotate(
|
||||
total_issues=Count(
|
||||
"id", filter=Q(archived_at__isnull=True, is_draft=False)
|
||||
)
|
||||
)
|
||||
.values("first_name", "last_name", "assignee_id", "avatar_url", "display_name")
|
||||
.annotate(total_issues=Count("id", filter=Q(archived_at__isnull=True, is_draft=False)))
|
||||
.annotate(
|
||||
completed_issues=Count(
|
||||
"id",
|
||||
|
|
@ -651,11 +595,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
.annotate(color=F("labels__color"))
|
||||
.annotate(label_id=F("labels__id"))
|
||||
.values("label_name", "color", "label_id")
|
||||
.annotate(
|
||||
total_issues=Count(
|
||||
"id", filter=Q(archived_at__isnull=True, is_draft=False)
|
||||
)
|
||||
)
|
||||
.annotate(total_issues=Count("id", filter=Q(archived_at__isnull=True, is_draft=False)))
|
||||
.annotate(
|
||||
completed_issues=Count(
|
||||
"id",
|
||||
|
|
@ -685,12 +625,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
"completion_chart": {},
|
||||
}
|
||||
|
||||
if (
|
||||
modules
|
||||
and modules.start_date
|
||||
and modules.target_date
|
||||
and modules.total_issues > 0
|
||||
):
|
||||
if modules and modules.start_date and modules.target_date and modules.total_issues > 0:
|
||||
data["distribution"]["completion_chart"] = burndown_plot(
|
||||
queryset=modules,
|
||||
slug=slug,
|
||||
|
|
@ -726,12 +661,8 @@ class ModuleViewSet(BaseViewSet):
|
|||
{"error": "Archived module cannot be updated"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
current_instance = json.dumps(
|
||||
ModuleSerializer(current_module).data, cls=DjangoJSONEncoder
|
||||
)
|
||||
serializer = ModuleWriteSerializer(
|
||||
current_module, data=request.data, partial=True
|
||||
)
|
||||
current_instance = json.dumps(ModuleSerializer(current_module).data, cls=DjangoJSONEncoder)
|
||||
serializer = ModuleWriteSerializer(current_module, data=request.data, partial=True)
|
||||
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
|
|
@ -781,9 +712,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
)
|
||||
|
||||
datetime_fields = ["created_at", "updated_at"]
|
||||
module = user_timezone_converter(
|
||||
module, datetime_fields, request.user.user_timezone
|
||||
)
|
||||
module = user_timezone_converter(module, datetime_fields, request.user.user_timezone)
|
||||
return Response(module, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
|
@ -791,9 +720,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
def destroy(self, request, slug, project_id, pk):
|
||||
module = Module.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
|
||||
|
||||
module_issues = list(
|
||||
ModuleIssue.objects.filter(module_id=pk).values_list("issue", flat=True)
|
||||
)
|
||||
module_issues = list(ModuleIssue.objects.filter(module_id=pk).values_list("issue", flat=True))
|
||||
_ = [
|
||||
issue_activity.delay(
|
||||
type="module.activity.deleted",
|
||||
|
|
@ -901,15 +828,9 @@ class ModuleUserPropertiesEndpoint(BaseAPIView):
|
|||
workspace__slug=slug,
|
||||
)
|
||||
|
||||
module_properties.filters = request.data.get(
|
||||
"filters", module_properties.filters
|
||||
)
|
||||
module_properties.rich_filters = request.data.get(
|
||||
"rich_filters", module_properties.rich_filters
|
||||
)
|
||||
module_properties.display_filters = request.data.get(
|
||||
"display_filters", module_properties.display_filters
|
||||
)
|
||||
module_properties.filters = request.data.get("filters", module_properties.filters)
|
||||
module_properties.rich_filters = request.data.get("rich_filters", module_properties.rich_filters)
|
||||
module_properties.display_filters = request.data.get("display_filters", module_properties.display_filters)
|
||||
module_properties.display_properties = request.data.get(
|
||||
"display_properties", module_properties.display_properties
|
||||
)
|
||||
|
|
|
|||
|
|
@ -50,9 +50,7 @@ class ModuleIssueViewSet(BaseViewSet):
|
|||
return (
|
||||
issues.annotate(
|
||||
cycle_id=Subquery(
|
||||
CycleIssue.objects.filter(
|
||||
issue=OuterRef("id"), deleted_at__isnull=True
|
||||
).values("cycle_id")[:1]
|
||||
CycleIssue.objects.filter(issue=OuterRef("id"), deleted_at__isnull=True).values("cycle_id")[:1]
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
|
|
@ -119,18 +117,14 @@ class ModuleIssueViewSet(BaseViewSet):
|
|||
sub_group_by = request.GET.get("sub_group_by", False)
|
||||
|
||||
# issue queryset
|
||||
issue_queryset = issue_queryset_grouper(
|
||||
queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by
|
||||
)
|
||||
issue_queryset = issue_queryset_grouper(queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by)
|
||||
|
||||
if group_by:
|
||||
# Check group and sub group value paginate
|
||||
if sub_group_by:
|
||||
if group_by == sub_group_by:
|
||||
return Response(
|
||||
{
|
||||
"error": "Group by and sub group by cannot have same parameters"
|
||||
},
|
||||
{"error": "Group by and sub group by cannot have same parameters"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
else:
|
||||
|
|
@ -205,9 +199,7 @@ class ModuleIssueViewSet(BaseViewSet):
|
|||
request=request,
|
||||
queryset=issue_queryset,
|
||||
total_count_queryset=total_issue_queryset,
|
||||
on_results=lambda issues: issue_on_results(
|
||||
group_by=group_by, issues=issues, sub_group_by=sub_group_by
|
||||
),
|
||||
on_results=lambda issues: issue_on_results(group_by=group_by, issues=issues, sub_group_by=sub_group_by),
|
||||
)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
|
|
@ -215,9 +207,7 @@ class ModuleIssueViewSet(BaseViewSet):
|
|||
def create_module_issues(self, request, slug, project_id, module_id):
|
||||
issues = request.data.get("issues", [])
|
||||
if not issues:
|
||||
return Response(
|
||||
{"error": "Issues are required"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response({"error": "Issues are required"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
project = Project.objects.get(pk=project_id)
|
||||
_ = ModuleIssue.objects.bulk_create(
|
||||
[
|
||||
|
|
@ -334,9 +324,7 @@ class ModuleIssueViewSet(BaseViewSet):
|
|||
actor_id=str(request.user.id),
|
||||
issue_id=str(issue_id),
|
||||
project_id=str(project_id),
|
||||
current_instance=json.dumps(
|
||||
{"module_name": module_issue.first().module.name}
|
||||
),
|
||||
current_instance=json.dumps({"module_name": module_issue.first().module.name}),
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
notification=True,
|
||||
origin=base_host(request=request, is_app=True),
|
||||
|
|
|
|||
|
|
@ -40,9 +40,7 @@ class NotificationViewSet(BaseViewSet, BasePaginator):
|
|||
.select_related("workspace", "project", "triggered_by", "receiver")
|
||||
)
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
|
||||
)
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def list(self, request, slug):
|
||||
# Get query parameters
|
||||
snoozed = request.GET.get("snoozed", "false")
|
||||
|
|
@ -59,9 +57,7 @@ class NotificationViewSet(BaseViewSet, BasePaginator):
|
|||
)
|
||||
|
||||
notifications = (
|
||||
Notification.objects.filter(
|
||||
workspace__slug=slug, receiver_id=request.user.id
|
||||
)
|
||||
Notification.objects.filter(workspace__slug=slug, receiver_id=request.user.id)
|
||||
.filter(entity_name="issue")
|
||||
.annotate(is_inbox_issue=Exists(intake_issue))
|
||||
.annotate(is_intake_issue=Exists(intake_issue))
|
||||
|
|
@ -106,23 +102,9 @@ class NotificationViewSet(BaseViewSet, BasePaginator):
|
|||
# Subscribed issues
|
||||
if "subscribed" in type:
|
||||
issue_ids = (
|
||||
IssueSubscriber.objects.filter(
|
||||
workspace__slug=slug, subscriber_id=request.user.id
|
||||
)
|
||||
.annotate(
|
||||
created=Exists(
|
||||
Issue.objects.filter(
|
||||
created_by=request.user, pk=OuterRef("issue_id")
|
||||
)
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
assigned=Exists(
|
||||
IssueAssignee.objects.filter(
|
||||
pk=OuterRef("issue_id"), assignee=request.user
|
||||
)
|
||||
)
|
||||
)
|
||||
IssueSubscriber.objects.filter(workspace__slug=slug, subscriber_id=request.user.id)
|
||||
.annotate(created=Exists(Issue.objects.filter(created_by=request.user, pk=OuterRef("issue_id"))))
|
||||
.annotate(assigned=Exists(IssueAssignee.objects.filter(pk=OuterRef("issue_id"), assignee=request.user)))
|
||||
.filter(created=False, assigned=False)
|
||||
.values_list("issue_id", flat=True)
|
||||
)
|
||||
|
|
@ -130,9 +112,9 @@ class NotificationViewSet(BaseViewSet, BasePaginator):
|
|||
|
||||
# Assigned Issues
|
||||
if "assigned" in type:
|
||||
issue_ids = IssueAssignee.objects.filter(
|
||||
workspace__slug=slug, assignee_id=request.user.id
|
||||
).values_list("issue_id", flat=True)
|
||||
issue_ids = IssueAssignee.objects.filter(workspace__slug=slug, assignee_id=request.user.id).values_list(
|
||||
"issue_id", flat=True
|
||||
)
|
||||
q_filters |= Q(entity_identifier__in=issue_ids)
|
||||
|
||||
# Created issues
|
||||
|
|
@ -142,9 +124,9 @@ class NotificationViewSet(BaseViewSet, BasePaginator):
|
|||
).exists():
|
||||
notifications = notifications.none()
|
||||
else:
|
||||
issue_ids = Issue.objects.filter(
|
||||
workspace__slug=slug, created_by=request.user
|
||||
).values_list("pk", flat=True)
|
||||
issue_ids = Issue.objects.filter(workspace__slug=slug, created_by=request.user).values_list(
|
||||
"pk", flat=True
|
||||
)
|
||||
q_filters |= Q(entity_identifier__in=issue_ids)
|
||||
|
||||
# Apply the combined Q object filters
|
||||
|
|
@ -156,75 +138,51 @@ class NotificationViewSet(BaseViewSet, BasePaginator):
|
|||
order_by=request.GET.get("order_by", "-created_at"),
|
||||
request=request,
|
||||
queryset=(notifications),
|
||||
on_results=lambda notifications: NotificationSerializer(
|
||||
notifications, many=True
|
||||
).data,
|
||||
on_results=lambda notifications: NotificationSerializer(notifications, many=True).data,
|
||||
)
|
||||
|
||||
serializer = NotificationSerializer(notifications, many=True)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
|
||||
)
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def partial_update(self, request, slug, pk):
|
||||
notification = Notification.objects.get(
|
||||
workspace__slug=slug, pk=pk, receiver=request.user
|
||||
)
|
||||
notification = Notification.objects.get(workspace__slug=slug, pk=pk, receiver=request.user)
|
||||
# Only read_at and snoozed_till can be updated
|
||||
notification_data = {"snoozed_till": request.data.get("snoozed_till", None)}
|
||||
serializer = NotificationSerializer(
|
||||
notification, data=notification_data, partial=True
|
||||
)
|
||||
serializer = NotificationSerializer(notification, data=notification_data, partial=True)
|
||||
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
|
||||
)
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def mark_read(self, request, slug, pk):
|
||||
notification = Notification.objects.get(
|
||||
receiver=request.user, workspace__slug=slug, pk=pk
|
||||
)
|
||||
notification = Notification.objects.get(receiver=request.user, workspace__slug=slug, pk=pk)
|
||||
notification.read_at = timezone.now()
|
||||
notification.save()
|
||||
serializer = NotificationSerializer(notification)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
|
||||
)
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def mark_unread(self, request, slug, pk):
|
||||
notification = Notification.objects.get(
|
||||
receiver=request.user, workspace__slug=slug, pk=pk
|
||||
)
|
||||
notification = Notification.objects.get(receiver=request.user, workspace__slug=slug, pk=pk)
|
||||
notification.read_at = None
|
||||
notification.save()
|
||||
serializer = NotificationSerializer(notification)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
|
||||
)
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def archive(self, request, slug, pk):
|
||||
notification = Notification.objects.get(
|
||||
receiver=request.user, workspace__slug=slug, pk=pk
|
||||
)
|
||||
notification = Notification.objects.get(receiver=request.user, workspace__slug=slug, pk=pk)
|
||||
notification.archived_at = timezone.now()
|
||||
notification.save()
|
||||
serializer = NotificationSerializer(notification)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
|
||||
)
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def unarchive(self, request, slug, pk):
|
||||
notification = Notification.objects.get(
|
||||
receiver=request.user, workspace__slug=slug, pk=pk
|
||||
)
|
||||
notification = Notification.objects.get(receiver=request.user, workspace__slug=slug, pk=pk)
|
||||
notification.archived_at = None
|
||||
notification.save()
|
||||
serializer = NotificationSerializer(notification)
|
||||
|
|
@ -234,9 +192,7 @@ class NotificationViewSet(BaseViewSet, BasePaginator):
|
|||
class UnreadNotificationEndpoint(BaseAPIView):
|
||||
use_read_replica = True
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
|
||||
)
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def get(self, request, slug):
|
||||
# Watching Issues Count
|
||||
unread_notifications_count = (
|
||||
|
|
@ -270,31 +226,23 @@ class UnreadNotificationEndpoint(BaseAPIView):
|
|||
|
||||
|
||||
class MarkAllReadNotificationViewSet(BaseViewSet):
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
|
||||
)
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def create(self, request, slug):
|
||||
snoozed = request.data.get("snoozed", False)
|
||||
archived = request.data.get("archived", False)
|
||||
type = request.data.get("type", "all")
|
||||
|
||||
notifications = (
|
||||
Notification.objects.filter(
|
||||
workspace__slug=slug, receiver_id=request.user.id, read_at__isnull=True
|
||||
)
|
||||
Notification.objects.filter(workspace__slug=slug, receiver_id=request.user.id, read_at__isnull=True)
|
||||
.select_related("workspace", "project", "triggered_by", "receiver")
|
||||
.order_by("snoozed_till", "-created_at")
|
||||
)
|
||||
|
||||
# Filter for snoozed notifications
|
||||
if snoozed:
|
||||
notifications = notifications.filter(
|
||||
Q(snoozed_till__lt=timezone.now()) | Q(snoozed_till__isnull=False)
|
||||
)
|
||||
notifications = notifications.filter(Q(snoozed_till__lt=timezone.now()) | Q(snoozed_till__isnull=False))
|
||||
else:
|
||||
notifications = notifications.filter(
|
||||
Q(snoozed_till__gte=timezone.now()) | Q(snoozed_till__isnull=True)
|
||||
)
|
||||
notifications = notifications.filter(Q(snoozed_till__gte=timezone.now()) | Q(snoozed_till__isnull=True))
|
||||
|
||||
# Filter for archived or unarchive
|
||||
if archived:
|
||||
|
|
@ -304,16 +252,16 @@ class MarkAllReadNotificationViewSet(BaseViewSet):
|
|||
|
||||
# Subscribed issues
|
||||
if type == "watching":
|
||||
issue_ids = IssueSubscriber.objects.filter(
|
||||
workspace__slug=slug, subscriber_id=request.user.id
|
||||
).values_list("issue_id", flat=True)
|
||||
issue_ids = IssueSubscriber.objects.filter(workspace__slug=slug, subscriber_id=request.user.id).values_list(
|
||||
"issue_id", flat=True
|
||||
)
|
||||
notifications = notifications.filter(entity_identifier__in=issue_ids)
|
||||
|
||||
# Assigned Issues
|
||||
if type == "assigned":
|
||||
issue_ids = IssueAssignee.objects.filter(
|
||||
workspace__slug=slug, assignee_id=request.user.id
|
||||
).values_list("issue_id", flat=True)
|
||||
issue_ids = IssueAssignee.objects.filter(workspace__slug=slug, assignee_id=request.user.id).values_list(
|
||||
"issue_id", flat=True
|
||||
)
|
||||
notifications = notifications.filter(entity_identifier__in=issue_ids)
|
||||
|
||||
# Created issues
|
||||
|
|
@ -323,18 +271,16 @@ class MarkAllReadNotificationViewSet(BaseViewSet):
|
|||
).exists():
|
||||
notifications = Notification.objects.none()
|
||||
else:
|
||||
issue_ids = Issue.objects.filter(
|
||||
workspace__slug=slug, created_by=request.user
|
||||
).values_list("pk", flat=True)
|
||||
issue_ids = Issue.objects.filter(workspace__slug=slug, created_by=request.user).values_list(
|
||||
"pk", flat=True
|
||||
)
|
||||
notifications = notifications.filter(entity_identifier__in=issue_ids)
|
||||
|
||||
updated_notifications = []
|
||||
for notification in notifications:
|
||||
notification.read_at = timezone.now()
|
||||
updated_notifications.append(notification)
|
||||
Notification.objects.bulk_update(
|
||||
updated_notifications, ["read_at"], batch_size=100
|
||||
)
|
||||
Notification.objects.bulk_update(updated_notifications, ["read_at"], batch_size=100)
|
||||
return Response({"message": "Successful"}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
|
|
@ -344,20 +290,14 @@ class UserNotificationPreferenceEndpoint(BaseAPIView):
|
|||
|
||||
# request the object
|
||||
def get(self, request):
|
||||
user_notification_preference = UserNotificationPreference.objects.get(
|
||||
user=request.user
|
||||
)
|
||||
user_notification_preference = UserNotificationPreference.objects.get(user=request.user)
|
||||
serializer = UserNotificationPreferenceSerializer(user_notification_preference)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
# update the object
|
||||
def patch(self, request):
|
||||
user_notification_preference = UserNotificationPreference.objects.get(
|
||||
user=request.user
|
||||
)
|
||||
serializer = UserNotificationPreferenceSerializer(
|
||||
user_notification_preference, data=request.data, partial=True
|
||||
)
|
||||
user_notification_preference = UserNotificationPreference.objects.get(user=request.user)
|
||||
serializer = UserNotificationPreferenceSerializer(user_notification_preference, data=request.data, partial=True)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
|
|
|||
|
|
@ -101,9 +101,7 @@ class PageViewSet(BaseViewSet):
|
|||
.order_by("-is_favorite", "-created_at")
|
||||
.annotate(
|
||||
project=Exists(
|
||||
ProjectPage.objects.filter(
|
||||
page_id=OuterRef("id"), project_id=self.kwargs.get("project_id")
|
||||
)
|
||||
ProjectPage.objects.filter(page_id=OuterRef("id"), project_id=self.kwargs.get("project_id"))
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
|
|
@ -116,9 +114,7 @@ class PageViewSet(BaseViewSet):
|
|||
Value([], output_field=ArrayField(UUIDField())),
|
||||
),
|
||||
project_ids=Coalesce(
|
||||
ArrayAgg(
|
||||
"projects__id", distinct=True, filter=~Q(projects__id=True)
|
||||
),
|
||||
ArrayAgg("projects__id", distinct=True, filter=~Q(projects__id=True)),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
),
|
||||
)
|
||||
|
|
@ -149,30 +145,19 @@ class PageViewSet(BaseViewSet):
|
|||
|
||||
def partial_update(self, request, slug, project_id, page_id):
|
||||
try:
|
||||
page = Page.objects.get(
|
||||
pk=page_id, workspace__slug=slug, projects__id=project_id
|
||||
)
|
||||
page = Page.objects.get(pk=page_id, workspace__slug=slug, projects__id=project_id)
|
||||
|
||||
if page.is_locked:
|
||||
return Response(
|
||||
{"error": "Page is locked"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response({"error": "Page is locked"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
parent = request.data.get("parent", None)
|
||||
if parent:
|
||||
_ = Page.objects.get(
|
||||
pk=parent, workspace__slug=slug, projects__id=project_id
|
||||
)
|
||||
_ = Page.objects.get(pk=parent, workspace__slug=slug, projects__id=project_id)
|
||||
|
||||
# Only update access if the page owner is the requesting user
|
||||
if (
|
||||
page.access != request.data.get("access", page.access)
|
||||
and page.owned_by_id != request.user.id
|
||||
):
|
||||
if page.access != request.data.get("access", page.access) and page.owned_by_id != request.user.id:
|
||||
return Response(
|
||||
{
|
||||
"error": "Access cannot be updated since this page is owned by someone else"
|
||||
},
|
||||
{"error": "Access cannot be updated since this page is owned by someone else"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
|
@ -195,9 +180,7 @@ class PageViewSet(BaseViewSet):
|
|||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
except Page.DoesNotExist:
|
||||
return Response(
|
||||
{
|
||||
"error": "Access cannot be updated since this page is owned by someone else"
|
||||
},
|
||||
{"error": "Access cannot be updated since this page is owned by someone else"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
|
@ -228,13 +211,11 @@ class PageViewSet(BaseViewSet):
|
|||
)
|
||||
|
||||
if page is None:
|
||||
return Response(
|
||||
{"error": "Page not found"}, status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
return Response({"error": "Page not found"}, status=status.HTTP_404_NOT_FOUND)
|
||||
else:
|
||||
issue_ids = PageLog.objects.filter(
|
||||
page_id=page_id, entity_name="issue"
|
||||
).values_list("entity_identifier", flat=True)
|
||||
issue_ids = PageLog.objects.filter(page_id=page_id, entity_name="issue").values_list(
|
||||
"entity_identifier", flat=True
|
||||
)
|
||||
data = PageDetailSerializer(page).data
|
||||
data["issue_ids"] = issue_ids
|
||||
if track_visit:
|
||||
|
|
@ -248,18 +229,14 @@ class PageViewSet(BaseViewSet):
|
|||
return Response(data, status=status.HTTP_200_OK)
|
||||
|
||||
def lock(self, request, slug, project_id, page_id):
|
||||
page = Page.objects.filter(
|
||||
pk=page_id, workspace__slug=slug, projects__id=project_id
|
||||
).first()
|
||||
page = Page.objects.filter(pk=page_id, workspace__slug=slug, projects__id=project_id).first()
|
||||
|
||||
page.is_locked = True
|
||||
page.save()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
def unlock(self, request, slug, project_id, page_id):
|
||||
page = Page.objects.filter(
|
||||
pk=page_id, workspace__slug=slug, projects__id=project_id
|
||||
).first()
|
||||
page = Page.objects.filter(pk=page_id, workspace__slug=slug, projects__id=project_id).first()
|
||||
|
||||
page.is_locked = False
|
||||
page.save()
|
||||
|
|
@ -268,19 +245,12 @@ class PageViewSet(BaseViewSet):
|
|||
|
||||
def access(self, request, slug, project_id, page_id):
|
||||
access = request.data.get("access", 0)
|
||||
page = Page.objects.filter(
|
||||
pk=page_id, workspace__slug=slug, projects__id=project_id
|
||||
).first()
|
||||
page = Page.objects.filter(pk=page_id, workspace__slug=slug, projects__id=project_id).first()
|
||||
|
||||
# Only update access if the page owner is the requesting user
|
||||
if (
|
||||
page.access != request.data.get("access", page.access)
|
||||
and page.owned_by_id != request.user.id
|
||||
):
|
||||
if page.access != request.data.get("access", page.access) and page.owned_by_id != request.user.id:
|
||||
return Response(
|
||||
{
|
||||
"error": "Access cannot be updated since this page is owned by someone else"
|
||||
},
|
||||
{"error": "Access cannot be updated since this page is owned by someone else"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
|
@ -306,9 +276,7 @@ class PageViewSet(BaseViewSet):
|
|||
return Response(pages, status=status.HTTP_200_OK)
|
||||
|
||||
def archive(self, request, slug, project_id, page_id):
|
||||
page = Page.objects.get(
|
||||
pk=page_id, workspace__slug=slug, projects__id=project_id
|
||||
)
|
||||
page = Page.objects.get(pk=page_id, workspace__slug=slug, projects__id=project_id)
|
||||
|
||||
# only the owner or admin can archive the page
|
||||
if (
|
||||
|
|
@ -334,9 +302,7 @@ class PageViewSet(BaseViewSet):
|
|||
return Response({"archived_at": str(datetime.now())}, status=status.HTTP_200_OK)
|
||||
|
||||
def unarchive(self, request, slug, project_id, page_id):
|
||||
page = Page.objects.get(
|
||||
pk=page_id, workspace__slug=slug, projects__id=project_id
|
||||
)
|
||||
page = Page.objects.get(pk=page_id, workspace__slug=slug, projects__id=project_id)
|
||||
|
||||
# only the owner or admin can un archive the page
|
||||
if (
|
||||
|
|
@ -360,9 +326,7 @@ class PageViewSet(BaseViewSet):
|
|||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
def destroy(self, request, slug, project_id, page_id):
|
||||
page = Page.objects.get(
|
||||
pk=page_id, workspace__slug=slug, projects__id=project_id
|
||||
)
|
||||
page = Page.objects.get(pk=page_id, workspace__slug=slug, projects__id=project_id)
|
||||
|
||||
if page.archived_at is None:
|
||||
return Response(
|
||||
|
|
@ -385,9 +349,7 @@ class PageViewSet(BaseViewSet):
|
|||
)
|
||||
|
||||
# remove parent from all the children
|
||||
_ = Page.objects.filter(
|
||||
parent_id=page_id, projects__id=project_id, workspace__slug=slug
|
||||
).update(parent=None)
|
||||
_ = Page.objects.filter(parent_id=page_id, projects__id=project_id, workspace__slug=slug).update(parent=None)
|
||||
|
||||
page.delete()
|
||||
# Delete the user favorite page
|
||||
|
|
@ -418,9 +380,7 @@ class PageViewSet(BaseViewSet):
|
|||
.filter(Q(owned_by=request.user) | Q(access=0))
|
||||
.annotate(
|
||||
project=Exists(
|
||||
ProjectPage.objects.filter(
|
||||
page_id=OuterRef("id"), project_id=self.kwargs.get("project_id")
|
||||
)
|
||||
ProjectPage.objects.filter(page_id=OuterRef("id"), project_id=self.kwargs.get("project_id"))
|
||||
)
|
||||
)
|
||||
.filter(project=True)
|
||||
|
|
@ -453,11 +413,7 @@ class PageViewSet(BaseViewSet):
|
|||
output_field=IntegerField(),
|
||||
)
|
||||
),
|
||||
archived_pages=Count(
|
||||
Case(
|
||||
When(archived_at__isnull=False, then=1), output_field=IntegerField()
|
||||
)
|
||||
),
|
||||
archived_pages=Count(Case(When(archived_at__isnull=False, then=1), output_field=IntegerField())),
|
||||
)
|
||||
|
||||
return Response(stats, status=status.HTTP_200_OK)
|
||||
|
|
@ -494,9 +450,7 @@ class PagesDescriptionViewSet(BaseViewSet):
|
|||
|
||||
def retrieve(self, request, slug, project_id, page_id):
|
||||
page = (
|
||||
Page.objects.filter(
|
||||
pk=page_id, workspace__slug=slug, projects__id=project_id
|
||||
)
|
||||
Page.objects.filter(pk=page_id, workspace__slug=slug, projects__id=project_id)
|
||||
.filter(Q(owned_by=self.request.user) | Q(access=0))
|
||||
.first()
|
||||
)
|
||||
|
|
@ -510,17 +464,13 @@ class PagesDescriptionViewSet(BaseViewSet):
|
|||
else:
|
||||
yield b""
|
||||
|
||||
response = StreamingHttpResponse(
|
||||
stream_data(), content_type="application/octet-stream"
|
||||
)
|
||||
response = StreamingHttpResponse(stream_data(), content_type="application/octet-stream")
|
||||
response["Content-Disposition"] = 'attachment; filename="page_description.bin"'
|
||||
return response
|
||||
|
||||
def partial_update(self, request, slug, project_id, page_id):
|
||||
page = (
|
||||
Page.objects.filter(
|
||||
pk=page_id, workspace__slug=slug, projects__id=project_id
|
||||
)
|
||||
Page.objects.filter(pk=page_id, workspace__slug=slug, projects__id=project_id)
|
||||
.filter(Q(owned_by=self.request.user) | Q(access=0))
|
||||
.first()
|
||||
)
|
||||
|
|
@ -547,18 +497,14 @@ class PagesDescriptionViewSet(BaseViewSet):
|
|||
)
|
||||
|
||||
# Serialize the existing instance
|
||||
existing_instance = json.dumps(
|
||||
{"description_html": page.description_html}, cls=DjangoJSONEncoder
|
||||
)
|
||||
existing_instance = json.dumps({"description_html": page.description_html}, cls=DjangoJSONEncoder)
|
||||
|
||||
# Use serializer for validation and update
|
||||
serializer = PageBinaryUpdateSerializer(page, data=request.data, partial=True)
|
||||
if serializer.is_valid():
|
||||
# Capture the page transaction
|
||||
if request.data.get("description_html"):
|
||||
page_transaction.delay(
|
||||
new_value=request.data, old_value=existing_instance, page_id=page_id
|
||||
)
|
||||
page_transaction.delay(new_value=request.data, old_value=existing_instance, page_id=page_id)
|
||||
|
||||
# Update the page using serializer
|
||||
updated_page = serializer.save()
|
||||
|
|
@ -578,20 +524,14 @@ class PageDuplicateEndpoint(BaseAPIView):
|
|||
permission_classes = [ProjectPagePermission]
|
||||
|
||||
def post(self, request, slug, project_id, page_id):
|
||||
page = Page.objects.filter(
|
||||
pk=page_id, workspace__slug=slug, projects__id=project_id
|
||||
).first()
|
||||
page = Page.objects.filter(pk=page_id, workspace__slug=slug, projects__id=project_id).first()
|
||||
|
||||
# check for permission
|
||||
if page.access == Page.PRIVATE_ACCESS and page.owned_by_id != request.user.id:
|
||||
return Response(
|
||||
{"error": "Permission denied"}, status=status.HTTP_403_FORBIDDEN
|
||||
)
|
||||
return Response({"error": "Permission denied"}, status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
# get all the project ids where page is present
|
||||
project_ids = ProjectPage.objects.filter(page_id=page_id).values_list(
|
||||
"project_id", flat=True
|
||||
)
|
||||
project_ids = ProjectPage.objects.filter(page_id=page_id).values_list("project_id", flat=True)
|
||||
|
||||
page.pk = None
|
||||
page.name = f"{page.name} (Copy)"
|
||||
|
|
@ -610,9 +550,7 @@ class PageDuplicateEndpoint(BaseAPIView):
|
|||
updated_by_id=page.updated_by_id,
|
||||
)
|
||||
|
||||
page_transaction.delay(
|
||||
{"description_html": page.description_html}, None, page.id
|
||||
)
|
||||
page_transaction.delay({"description_html": page.description_html}, None, page.id)
|
||||
|
||||
# Copy the s3 objects uploaded in the page
|
||||
copy_s3_objects_of_description_and_assets.delay(
|
||||
|
|
@ -627,9 +565,7 @@ class PageDuplicateEndpoint(BaseAPIView):
|
|||
Page.objects.filter(pk=page.id)
|
||||
.annotate(
|
||||
project_ids=Coalesce(
|
||||
ArrayAgg(
|
||||
"projects__id", distinct=True, filter=~Q(projects__id=True)
|
||||
),
|
||||
ArrayAgg("projects__id", distinct=True, filter=~Q(projects__id=True)),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,27 +6,22 @@ from rest_framework.response import Response
|
|||
from plane.db.models import PageVersion
|
||||
from ..base import BaseAPIView
|
||||
from plane.app.serializers import PageVersionSerializer, PageVersionDetailSerializer
|
||||
from plane.app.permissions import allow_permission, ROLE
|
||||
from plane.app.permissions import ProjectPagePermission
|
||||
|
||||
class PageVersionEndpoint(BaseAPIView):
|
||||
|
||||
class PageVersionEndpoint(BaseAPIView):
|
||||
permission_classes = [ProjectPagePermission]
|
||||
|
||||
def get(self, request, slug, project_id, page_id, pk=None):
|
||||
# Check if pk is provided
|
||||
if pk:
|
||||
# Return a single page version
|
||||
page_version = PageVersion.objects.get(
|
||||
workspace__slug=slug, page_id=page_id, pk=pk
|
||||
)
|
||||
page_version = PageVersion.objects.get(workspace__slug=slug, page_id=page_id, pk=pk)
|
||||
# Serialize the page version
|
||||
serializer = PageVersionDetailSerializer(page_version)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
# Return all page versions
|
||||
page_versions = PageVersion.objects.filter(
|
||||
workspace__slug=slug, page_id=page_id
|
||||
)
|
||||
page_versions = PageVersion.objects.filter(workspace__slug=slug, page_id=page_id)
|
||||
# Serialize the page versions
|
||||
serializer = PageVersionSerializer(page_versions, many=True)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
|
|
|||
|
|
@ -58,9 +58,7 @@ class ProjectViewSet(BaseViewSet):
|
|||
super()
|
||||
.get_queryset()
|
||||
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||
.select_related(
|
||||
"workspace", "workspace__owner", "default_assignee", "project_lead"
|
||||
)
|
||||
.select_related("workspace", "workspace__owner", "default_assignee", "project_lead")
|
||||
.annotate(
|
||||
is_favorite=Exists(
|
||||
UserFavorite.objects.filter(
|
||||
|
|
@ -98,9 +96,7 @@ class ProjectViewSet(BaseViewSet):
|
|||
.distinct()
|
||||
)
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
|
||||
)
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def list_detail(self, request, slug):
|
||||
fields = [field for field in request.GET.get("fields", "").split(",") if field]
|
||||
projects = self.get_queryset().order_by("sort_order", "name")
|
||||
|
|
@ -134,19 +130,13 @@ class ProjectViewSet(BaseViewSet):
|
|||
order_by=request.GET.get("order_by", "-created_at"),
|
||||
request=request,
|
||||
queryset=(projects),
|
||||
on_results=lambda projects: ProjectListSerializer(
|
||||
projects, many=True
|
||||
).data,
|
||||
on_results=lambda projects: ProjectListSerializer(projects, many=True).data,
|
||||
)
|
||||
|
||||
projects = ProjectListSerializer(
|
||||
projects, many=True, fields=fields if fields else None
|
||||
).data
|
||||
projects = ProjectListSerializer(projects, many=True, fields=fields if fields else None).data
|
||||
return Response(projects, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
|
||||
)
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def list(self, request, slug):
|
||||
sort_order = ProjectMember.objects.filter(
|
||||
member=self.request.user,
|
||||
|
|
@ -157,9 +147,7 @@ class ProjectViewSet(BaseViewSet):
|
|||
|
||||
projects = (
|
||||
Project.objects.filter(workspace__slug=self.kwargs.get("slug"))
|
||||
.select_related(
|
||||
"workspace", "workspace__owner", "default_assignee", "project_lead"
|
||||
)
|
||||
.select_related("workspace", "workspace__owner", "default_assignee", "project_lead")
|
||||
.annotate(
|
||||
member_role=ProjectMember.objects.filter(
|
||||
project_id=OuterRef("pk"),
|
||||
|
|
@ -219,9 +207,7 @@ class ProjectViewSet(BaseViewSet):
|
|||
)
|
||||
return Response(projects, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
|
||||
)
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def retrieve(self, request, slug, pk):
|
||||
project = (
|
||||
self.get_queryset()
|
||||
|
|
@ -234,9 +220,7 @@ class ProjectViewSet(BaseViewSet):
|
|||
).first()
|
||||
|
||||
if project is None:
|
||||
return Response(
|
||||
{"error": "Project does not exist"}, status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
return Response({"error": "Project does not exist"}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
recent_visited_task.delay(
|
||||
slug=slug,
|
||||
|
|
@ -253,9 +237,7 @@ class ProjectViewSet(BaseViewSet):
|
|||
def create(self, request, slug):
|
||||
workspace = Workspace.objects.get(slug=slug)
|
||||
|
||||
serializer = ProjectSerializer(
|
||||
data={**request.data}, context={"workspace_id": workspace.id}
|
||||
)
|
||||
serializer = ProjectSerializer(data={**request.data}, context={"workspace_id": workspace.id})
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
|
||||
|
|
@ -266,13 +248,11 @@ class ProjectViewSet(BaseViewSet):
|
|||
role=ROLE.ADMIN.value,
|
||||
)
|
||||
# Also create the issue property for the user
|
||||
_ = IssueUserProperty.objects.create(
|
||||
project_id=serializer.data["id"], user=request.user
|
||||
)
|
||||
_ = IssueUserProperty.objects.create(project_id=serializer.data["id"], user=request.user)
|
||||
|
||||
if serializer.data["project_lead"] is not None and str(
|
||||
serializer.data["project_lead"]
|
||||
) != str(request.user.id):
|
||||
if serializer.data["project_lead"] is not None and str(serializer.data["project_lead"]) != str(
|
||||
request.user.id
|
||||
):
|
||||
ProjectMember.objects.create(
|
||||
project_id=serializer.data["id"],
|
||||
member_id=serializer.data["project_lead"],
|
||||
|
|
@ -380,9 +360,7 @@ class ProjectViewSet(BaseViewSet):
|
|||
|
||||
project = Project.objects.get(pk=pk)
|
||||
intake_view = request.data.get("inbox_view", project.intake_view)
|
||||
current_instance = json.dumps(
|
||||
ProjectSerializer(project).data, cls=DjangoJSONEncoder
|
||||
)
|
||||
current_instance = json.dumps(ProjectSerializer(project).data, cls=DjangoJSONEncoder)
|
||||
if project.archived_at:
|
||||
return Response(
|
||||
{"error": "Archived projects cannot be updated"},
|
||||
|
|
@ -474,9 +452,7 @@ class ProjectArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
project.archived_at = timezone.now()
|
||||
project.save()
|
||||
UserFavorite.objects.filter(workspace__slug=slug, project=project_id).delete()
|
||||
return Response(
|
||||
{"archived_at": str(project.archived_at)}, status=status.HTTP_200_OK
|
||||
)
|
||||
return Response({"archived_at": str(project.archived_at)}, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def delete(self, request, slug, project_id):
|
||||
|
|
@ -492,26 +468,18 @@ class ProjectIdentifierEndpoint(BaseAPIView):
|
|||
name = request.GET.get("name", "").strip().upper()
|
||||
|
||||
if name == "":
|
||||
return Response(
|
||||
{"error": "Name is required"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response({"error": "Name is required"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
exists = ProjectIdentifier.objects.filter(
|
||||
name=name, workspace__slug=slug
|
||||
).values("id", "name", "project")
|
||||
exists = ProjectIdentifier.objects.filter(name=name, workspace__slug=slug).values("id", "name", "project")
|
||||
|
||||
return Response(
|
||||
{"exists": len(exists), "identifiers": exists}, status=status.HTTP_200_OK
|
||||
)
|
||||
return Response({"exists": len(exists), "identifiers": exists}, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
|
||||
def delete(self, request, slug):
|
||||
name = request.data.get("name", "").strip().upper()
|
||||
|
||||
if name == "":
|
||||
return Response(
|
||||
{"error": "Name is required"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response({"error": "Name is required"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
if Project.objects.filter(identifier=name, workspace__slug=slug).exists():
|
||||
return Response(
|
||||
|
|
@ -528,9 +496,7 @@ class ProjectUserViewsEndpoint(BaseAPIView):
|
|||
def post(self, request, slug, project_id):
|
||||
project = Project.objects.get(pk=project_id, workspace__slug=slug)
|
||||
|
||||
project_member = ProjectMember.objects.filter(
|
||||
member=request.user, project=project, is_active=True
|
||||
).first()
|
||||
project_member = ProjectMember.objects.filter(member=request.user, project=project, is_active=True).first()
|
||||
|
||||
if project_member is None:
|
||||
return Response({"error": "Forbidden"}, status=status.HTTP_403_FORBIDDEN)
|
||||
|
|
@ -559,9 +525,7 @@ class ProjectFavoritesViewSet(BaseViewSet):
|
|||
.get_queryset()
|
||||
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||
.filter(user=self.request.user)
|
||||
.select_related(
|
||||
"project", "project__project_lead", "project__default_assignee"
|
||||
)
|
||||
.select_related("project", "project__project_lead", "project__default_assignee")
|
||||
.select_related("workspace", "workspace__owner")
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -52,9 +52,7 @@ class ProjectInvitationsViewset(BaseViewSet):
|
|||
|
||||
# Check if email is provided
|
||||
if not emails:
|
||||
return Response(
|
||||
{"error": "Emails are required"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response({"error": "Emails are required"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
for email in emails:
|
||||
workspace_role = WorkspaceMember.objects.filter(
|
||||
|
|
@ -62,11 +60,7 @@ class ProjectInvitationsViewset(BaseViewSet):
|
|||
).role
|
||||
|
||||
if workspace_role in [5, 20] and workspace_role != email.get("role", 5):
|
||||
return Response(
|
||||
{
|
||||
"error": "You cannot invite a user with different role than workspace role"
|
||||
}
|
||||
)
|
||||
return Response({"error": "You cannot invite a user with different role than workspace role"})
|
||||
|
||||
workspace = Workspace.objects.get(slug=slug)
|
||||
|
||||
|
|
@ -91,7 +85,7 @@ class ProjectInvitationsViewset(BaseViewSet):
|
|||
except ValidationError:
|
||||
return Response(
|
||||
{
|
||||
"error": f"Invalid email - {email} provided a valid email address is required to send the invite"
|
||||
"error": f"Invalid email - {email} provided a valid email address is required to send the invite" # noqa: E501
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
|
@ -112,9 +106,7 @@ class ProjectInvitationsViewset(BaseViewSet):
|
|||
request.user.email,
|
||||
)
|
||||
|
||||
return Response(
|
||||
{"message": "Email sent successfully"}, status=status.HTTP_200_OK
|
||||
)
|
||||
return Response({"message": "Email sent successfully"}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class UserProjectInvitationsViewset(BaseViewSet):
|
||||
|
|
@ -134,20 +126,13 @@ class UserProjectInvitationsViewset(BaseViewSet):
|
|||
project_ids = request.data.get("project_ids", [])
|
||||
|
||||
# Get the workspace user role
|
||||
workspace_member = WorkspaceMember.objects.get(
|
||||
member=request.user, workspace__slug=slug, is_active=True
|
||||
)
|
||||
workspace_member = WorkspaceMember.objects.get(member=request.user, workspace__slug=slug, is_active=True)
|
||||
|
||||
# Get all the projects
|
||||
projects = Project.objects.filter(
|
||||
id__in=project_ids, workspace__slug=slug
|
||||
).only("id", "network")
|
||||
projects = Project.objects.filter(id__in=project_ids, workspace__slug=slug).only("id", "network")
|
||||
# Check if user has permission to join each project
|
||||
for project in projects:
|
||||
if (
|
||||
project.network == ProjectNetwork.SECRET.value
|
||||
and workspace_member.role != ROLE.ADMIN.value
|
||||
):
|
||||
if project.network == ProjectNetwork.SECRET.value and workspace_member.role != ROLE.ADMIN.value:
|
||||
return Response(
|
||||
{"error": "Only workspace admins can join private project"},
|
||||
status=status.HTTP_403_FORBIDDEN,
|
||||
|
|
@ -157,9 +142,9 @@ class UserProjectInvitationsViewset(BaseViewSet):
|
|||
workspace = workspace_member.workspace
|
||||
|
||||
# If the user was already part of workspace
|
||||
_ = ProjectMember.objects.filter(
|
||||
workspace__slug=slug, project_id__in=project_ids, member=request.user
|
||||
).update(is_active=True)
|
||||
_ = ProjectMember.objects.filter(workspace__slug=slug, project_id__in=project_ids, member=request.user).update(
|
||||
is_active=True
|
||||
)
|
||||
|
||||
ProjectMember.objects.bulk_create(
|
||||
[
|
||||
|
|
@ -188,18 +173,14 @@ class UserProjectInvitationsViewset(BaseViewSet):
|
|||
ignore_conflicts=True,
|
||||
)
|
||||
|
||||
return Response(
|
||||
{"message": "Projects joined successfully"}, status=status.HTTP_201_CREATED
|
||||
)
|
||||
return Response({"message": "Projects joined successfully"}, status=status.HTTP_201_CREATED)
|
||||
|
||||
|
||||
class ProjectJoinEndpoint(BaseAPIView):
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
def post(self, request, slug, project_id, pk):
|
||||
project_invite = ProjectMemberInvite.objects.get(
|
||||
pk=pk, project_id=project_id, workspace__slug=slug
|
||||
)
|
||||
project_invite = ProjectMemberInvite.objects.get(pk=pk, project_id=project_id, workspace__slug=slug)
|
||||
|
||||
email = request.data.get("email", "")
|
||||
|
||||
|
|
@ -219,9 +200,7 @@ class ProjectJoinEndpoint(BaseAPIView):
|
|||
user = User.objects.filter(email=email).first()
|
||||
|
||||
# Check if user is a part of workspace
|
||||
workspace_member = WorkspaceMember.objects.filter(
|
||||
workspace__slug=slug, member=user
|
||||
).first()
|
||||
workspace_member = WorkspaceMember.objects.filter(workspace__slug=slug, member=user).first()
|
||||
# Add him to workspace
|
||||
if workspace_member is None:
|
||||
_ = WorkspaceMember.objects.create(
|
||||
|
|
@ -266,8 +245,6 @@ class ProjectJoinEndpoint(BaseAPIView):
|
|||
)
|
||||
|
||||
def get(self, request, slug, project_id, pk):
|
||||
project_invitation = ProjectMemberInvite.objects.get(
|
||||
workspace__slug=slug, project_id=project_id, pk=pk
|
||||
)
|
||||
project_invitation = ProjectMemberInvite.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
|
||||
serializer = ProjectMemberInviteSerializer(project_invitation)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
|
|
|||
|
|
@ -57,9 +57,7 @@ class ProjectMemberViewSet(BaseViewSet):
|
|||
bulk_issue_props = []
|
||||
|
||||
# Create a dictionary of the member_id and their roles
|
||||
member_roles = {
|
||||
member.get("member_id"): member.get("role") for member in members
|
||||
}
|
||||
member_roles = {member.get("member_id"): member.get("role") for member in members}
|
||||
|
||||
# check the workspace role of the new user
|
||||
for member in member_roles:
|
||||
|
|
@ -68,17 +66,13 @@ class ProjectMemberViewSet(BaseViewSet):
|
|||
).role
|
||||
if workspace_member_role in [20] and member_roles.get(member) in [5, 15]:
|
||||
return Response(
|
||||
{
|
||||
"error": "You cannot add a user with role lower than the workspace role"
|
||||
},
|
||||
{"error": "You cannot add a user with role lower than the workspace role"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
if workspace_member_role in [5] and member_roles.get(member) in [15, 20]:
|
||||
return Response(
|
||||
{
|
||||
"error": "You cannot add a user with role higher than the workspace role"
|
||||
},
|
||||
{"error": "You cannot add a user with role higher than the workspace role"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
|
@ -92,9 +86,7 @@ class ProjectMemberViewSet(BaseViewSet):
|
|||
bulk_project_members.append(project_member)
|
||||
|
||||
# Update the roles of the existing members
|
||||
ProjectMember.objects.bulk_update(
|
||||
bulk_project_members, ["is_active", "role"], batch_size=100
|
||||
)
|
||||
ProjectMember.objects.bulk_update(bulk_project_members, ["is_active", "role"], batch_size=100)
|
||||
|
||||
# Get the list of project members of the requested workspace with the given slug
|
||||
project_members = (
|
||||
|
|
@ -134,13 +126,9 @@ class ProjectMemberViewSet(BaseViewSet):
|
|||
)
|
||||
|
||||
# Bulk create the project members and issue properties
|
||||
project_members = ProjectMember.objects.bulk_create(
|
||||
bulk_project_members, batch_size=10, ignore_conflicts=True
|
||||
)
|
||||
project_members = ProjectMember.objects.bulk_create(bulk_project_members, batch_size=10, ignore_conflicts=True)
|
||||
|
||||
_ = IssueUserProperty.objects.bulk_create(
|
||||
bulk_issue_props, batch_size=10, ignore_conflicts=True
|
||||
)
|
||||
_ = IssueUserProperty.objects.bulk_create(bulk_issue_props, batch_size=10, ignore_conflicts=True)
|
||||
|
||||
project_members = ProjectMember.objects.filter(
|
||||
project_id=project_id,
|
||||
|
|
@ -172,16 +160,12 @@ class ProjectMemberViewSet(BaseViewSet):
|
|||
member__member_workspace__is_active=True,
|
||||
).select_related("project", "member", "workspace")
|
||||
|
||||
serializer = ProjectMemberRoleSerializer(
|
||||
project_members, fields=("id", "member", "role"), many=True
|
||||
)
|
||||
serializer = ProjectMemberRoleSerializer(project_members, fields=("id", "member", "role"), many=True)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def partial_update(self, request, slug, project_id, pk):
|
||||
project_member = ProjectMember.objects.get(
|
||||
pk=pk, workspace__slug=slug, project_id=project_id, is_active=True
|
||||
)
|
||||
project_member = ProjectMember.objects.get(pk=pk, workspace__slug=slug, project_id=project_id, is_active=True)
|
||||
|
||||
# Fetch the workspace role of the project member
|
||||
workspace_role = WorkspaceMember.objects.get(
|
||||
|
|
@ -203,20 +187,15 @@ class ProjectMemberViewSet(BaseViewSet):
|
|||
is_active=True,
|
||||
)
|
||||
|
||||
if workspace_role in [5] and int(
|
||||
request.data.get("role", project_member.role)
|
||||
) in [15, 20]:
|
||||
if workspace_role in [5] and int(request.data.get("role", project_member.role)) in [15, 20]:
|
||||
return Response(
|
||||
{
|
||||
"error": "You cannot add a user with role higher than the workspace role"
|
||||
},
|
||||
{"error": "You cannot add a user with role higher than the workspace role"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
if (
|
||||
"role" in request.data
|
||||
and int(request.data.get("role", project_member.role))
|
||||
> requested_project_member.role
|
||||
and int(request.data.get("role", project_member.role)) > requested_project_member.role
|
||||
and not is_workspace_admin
|
||||
):
|
||||
return Response(
|
||||
|
|
@ -224,9 +203,7 @@ class ProjectMemberViewSet(BaseViewSet):
|
|||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
serializer = ProjectMemberSerializer(
|
||||
project_member, data=request.data, partial=True
|
||||
)
|
||||
serializer = ProjectMemberSerializer(project_member, data=request.data, partial=True)
|
||||
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
|
|
@ -252,9 +229,7 @@ class ProjectMemberViewSet(BaseViewSet):
|
|||
# User cannot remove himself
|
||||
if str(project_member.id) == str(requesting_project_member.id):
|
||||
return Response(
|
||||
{
|
||||
"error": "You cannot remove yourself from the workspace. Please use leave workspace"
|
||||
},
|
||||
{"error": "You cannot remove yourself from the workspace. Please use leave workspace"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
# User cannot deactivate higher role
|
||||
|
|
@ -287,7 +262,7 @@ class ProjectMemberViewSet(BaseViewSet):
|
|||
):
|
||||
return Response(
|
||||
{
|
||||
"error": "You cannot leave the project as your the only admin of the project you will have to either delete the project or create an another admin"
|
||||
"error": "You cannot leave the project as your the only admin of the project you will have to either delete the project or create an another admin" # noqa: E501
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
|
@ -323,7 +298,5 @@ class UserProjectRolesEndpoint(BaseAPIView):
|
|||
member__member_workspace__is_active=True,
|
||||
).values("project_id", "role")
|
||||
|
||||
project_members = {
|
||||
str(member["project_id"]): member["role"] for member in project_members
|
||||
}
|
||||
project_members = {str(member["project_id"]): member["role"] for member in project_members}
|
||||
return Response(project_members, status=status.HTTP_200_OK)
|
||||
|
|
|
|||
|
|
@ -120,9 +120,7 @@ class GlobalSearchEndpoint(BaseAPIView):
|
|||
if workspace_search == "false" and project_id:
|
||||
cycles = cycles.filter(project_id=project_id)
|
||||
|
||||
return cycles.distinct().values(
|
||||
"name", "id", "project_id", "project__identifier", "workspace__slug"
|
||||
)
|
||||
return cycles.distinct().values("name", "id", "project_id", "project__identifier", "workspace__slug")
|
||||
|
||||
def filter_modules(self, query, slug, project_id, workspace_search):
|
||||
fields = ["name"]
|
||||
|
|
@ -141,9 +139,7 @@ class GlobalSearchEndpoint(BaseAPIView):
|
|||
if workspace_search == "false" and project_id:
|
||||
modules = modules.filter(project_id=project_id)
|
||||
|
||||
return modules.distinct().values(
|
||||
"name", "id", "project_id", "project__identifier", "workspace__slug"
|
||||
)
|
||||
return modules.distinct().values("name", "id", "project_id", "project__identifier", "workspace__slug")
|
||||
|
||||
def filter_pages(self, query, slug, project_id, workspace_search):
|
||||
fields = ["name"]
|
||||
|
|
@ -161,9 +157,7 @@ class GlobalSearchEndpoint(BaseAPIView):
|
|||
)
|
||||
.annotate(
|
||||
project_ids=Coalesce(
|
||||
ArrayAgg(
|
||||
"projects__id", distinct=True, filter=~Q(projects__id=True)
|
||||
),
|
||||
ArrayAgg("projects__id", distinct=True, filter=~Q(projects__id=True)),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
)
|
||||
)
|
||||
|
|
@ -180,17 +174,13 @@ class GlobalSearchEndpoint(BaseAPIView):
|
|||
)
|
||||
|
||||
if workspace_search == "false" and project_id:
|
||||
project_subquery = ProjectPage.objects.filter(
|
||||
page_id=OuterRef("id"), project_id=project_id
|
||||
).values_list("project_id", flat=True)[:1]
|
||||
project_subquery = ProjectPage.objects.filter(page_id=OuterRef("id"), project_id=project_id).values_list(
|
||||
"project_id", flat=True
|
||||
)[:1]
|
||||
|
||||
pages = pages.annotate(project_id=Subquery(project_subquery)).filter(
|
||||
project_id=project_id
|
||||
)
|
||||
pages = pages.annotate(project_id=Subquery(project_subquery)).filter(project_id=project_id)
|
||||
|
||||
return pages.distinct().values(
|
||||
"name", "id", "project_ids", "project_identifiers", "workspace__slug"
|
||||
)
|
||||
return pages.distinct().values("name", "id", "project_ids", "project_identifiers", "workspace__slug")
|
||||
|
||||
def filter_views(self, query, slug, project_id, workspace_search):
|
||||
fields = ["name"]
|
||||
|
|
@ -209,9 +199,7 @@ class GlobalSearchEndpoint(BaseAPIView):
|
|||
if workspace_search == "false" and project_id:
|
||||
issue_views = issue_views.filter(project_id=project_id)
|
||||
|
||||
return issue_views.distinct().values(
|
||||
"name", "id", "project_id", "project__identifier", "workspace__slug"
|
||||
)
|
||||
return issue_views.distinct().values("name", "id", "project_id", "project__identifier", "workspace__slug")
|
||||
|
||||
def get(self, request, slug):
|
||||
query = request.query_params.get("search", False)
|
||||
|
|
@ -308,9 +296,7 @@ class SearchEndpoint(BaseAPIView):
|
|||
|
||||
if issue_id:
|
||||
issue_created_by = (
|
||||
Issue.objects.filter(id=issue_id)
|
||||
.values_list("created_by_id", flat=True)
|
||||
.first()
|
||||
Issue.objects.filter(id=issue_id).values_list("created_by_id", flat=True).first()
|
||||
)
|
||||
users = (
|
||||
users.filter(Q(role__gt=10) | Q(member_id=issue_created_by))
|
||||
|
|
@ -344,15 +330,12 @@ class SearchEndpoint(BaseAPIView):
|
|||
projects = (
|
||||
Project.objects.filter(
|
||||
q,
|
||||
Q(project_projectmember__member=self.request.user)
|
||||
| Q(network=2),
|
||||
Q(project_projectmember__member=self.request.user) | Q(network=2),
|
||||
workspace__slug=slug,
|
||||
)
|
||||
.order_by("-created_at")
|
||||
.distinct()
|
||||
.values(
|
||||
"name", "id", "identifier", "logo_props", "workspace__slug"
|
||||
)[:count]
|
||||
.values("name", "id", "identifier", "logo_props", "workspace__slug")[:count]
|
||||
)
|
||||
response_data["project"] = list(projects)
|
||||
|
||||
|
|
@ -411,20 +394,16 @@ class SearchEndpoint(BaseAPIView):
|
|||
.annotate(
|
||||
status=Case(
|
||||
When(
|
||||
Q(start_date__lte=timezone.now())
|
||||
& Q(end_date__gte=timezone.now()),
|
||||
Q(start_date__lte=timezone.now()) & Q(end_date__gte=timezone.now()),
|
||||
then=Value("CURRENT"),
|
||||
),
|
||||
When(
|
||||
start_date__gt=timezone.now(),
|
||||
then=Value("UPCOMING"),
|
||||
),
|
||||
When(end_date__lt=timezone.now(), then=Value("COMPLETED")),
|
||||
When(
|
||||
end_date__lt=timezone.now(), then=Value("COMPLETED")
|
||||
),
|
||||
When(
|
||||
Q(start_date__isnull=True)
|
||||
& Q(end_date__isnull=True),
|
||||
Q(start_date__isnull=True) & Q(end_date__isnull=True),
|
||||
then=Value("DRAFT"),
|
||||
),
|
||||
default=Value("DRAFT"),
|
||||
|
|
@ -542,9 +521,7 @@ class SearchEndpoint(BaseAPIView):
|
|||
)
|
||||
)
|
||||
.order_by("-created_at")
|
||||
.values(
|
||||
"member__avatar_url", "member__display_name", "member__id"
|
||||
)[:count]
|
||||
.values("member__avatar_url", "member__display_name", "member__id")[:count]
|
||||
)
|
||||
response_data["user_mention"] = list(users)
|
||||
|
||||
|
|
@ -558,15 +535,12 @@ class SearchEndpoint(BaseAPIView):
|
|||
projects = (
|
||||
Project.objects.filter(
|
||||
q,
|
||||
Q(project_projectmember__member=self.request.user)
|
||||
| Q(network=2),
|
||||
Q(project_projectmember__member=self.request.user) | Q(network=2),
|
||||
workspace__slug=slug,
|
||||
)
|
||||
.order_by("-created_at")
|
||||
.distinct()
|
||||
.values(
|
||||
"name", "id", "identifier", "logo_props", "workspace__slug"
|
||||
)[:count]
|
||||
.values("name", "id", "identifier", "logo_props", "workspace__slug")[:count]
|
||||
)
|
||||
response_data["project"] = list(projects)
|
||||
|
||||
|
|
@ -623,20 +597,16 @@ class SearchEndpoint(BaseAPIView):
|
|||
.annotate(
|
||||
status=Case(
|
||||
When(
|
||||
Q(start_date__lte=timezone.now())
|
||||
& Q(end_date__gte=timezone.now()),
|
||||
Q(start_date__lte=timezone.now()) & Q(end_date__gte=timezone.now()),
|
||||
then=Value("CURRENT"),
|
||||
),
|
||||
When(
|
||||
start_date__gt=timezone.now(),
|
||||
then=Value("UPCOMING"),
|
||||
),
|
||||
When(end_date__lt=timezone.now(), then=Value("COMPLETED")),
|
||||
When(
|
||||
end_date__lt=timezone.now(), then=Value("COMPLETED")
|
||||
),
|
||||
When(
|
||||
Q(start_date__isnull=True)
|
||||
& Q(end_date__isnull=True),
|
||||
Q(start_date__isnull=True) & Q(end_date__isnull=True),
|
||||
then=Value("DRAFT"),
|
||||
),
|
||||
default=Value("DRAFT"),
|
||||
|
|
|
|||
|
|
@ -30,23 +30,17 @@ class IssueSearchEndpoint(BaseAPIView):
|
|||
|
||||
return issues
|
||||
|
||||
def search_issues_and_excluding_parent(
|
||||
self, issues: QuerySet, issue_id: str
|
||||
) -> QuerySet:
|
||||
def search_issues_and_excluding_parent(self, issues: QuerySet, issue_id: str) -> QuerySet:
|
||||
"""
|
||||
Search issues and epics by query excluding the parent
|
||||
"""
|
||||
|
||||
issue = Issue.issue_objects.filter(pk=issue_id).first()
|
||||
if issue:
|
||||
issues = issues.filter(
|
||||
~Q(pk=issue_id), ~Q(pk=issue.parent_id), ~Q(parent_id=issue_id)
|
||||
)
|
||||
issues = issues.filter(~Q(pk=issue_id), ~Q(pk=issue.parent_id), ~Q(parent_id=issue_id))
|
||||
return issues
|
||||
|
||||
def filter_issues_excluding_related_issues(
|
||||
self, issue_id: str, issues: QuerySet
|
||||
) -> QuerySet:
|
||||
def filter_issues_excluding_related_issues(self, issue_id: str, issues: QuerySet) -> QuerySet:
|
||||
"""
|
||||
Filter issues excluding related issues
|
||||
"""
|
||||
|
|
@ -81,18 +75,14 @@ class IssueSearchEndpoint(BaseAPIView):
|
|||
"""
|
||||
Exclude issues in cycles
|
||||
"""
|
||||
issues = issues.exclude(
|
||||
Q(issue_cycle__isnull=False) & Q(issue_cycle__deleted_at__isnull=True)
|
||||
)
|
||||
issues = issues.exclude(Q(issue_cycle__isnull=False) & Q(issue_cycle__deleted_at__isnull=True))
|
||||
return issues
|
||||
|
||||
def exclude_issues_in_module(self, issues: QuerySet, module: str) -> QuerySet:
|
||||
"""
|
||||
Exclude issues in a module
|
||||
"""
|
||||
issues = issues.exclude(
|
||||
Q(issue_module__module=module) & Q(issue_module__deleted_at__isnull=True)
|
||||
)
|
||||
issues = issues.exclude(Q(issue_module__module=module) & Q(issue_module__deleted_at__isnull=True))
|
||||
return issues
|
||||
|
||||
def filter_issues_without_target_date(self, issues: QuerySet) -> QuerySet:
|
||||
|
|
|
|||
|
|
@ -57,9 +57,7 @@ class StateViewSet(BaseViewSet):
|
|||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def partial_update(self, request, slug, project_id, pk):
|
||||
try:
|
||||
state = State.objects.get(
|
||||
pk=pk, project_id=project_id, workspace__slug=slug
|
||||
)
|
||||
state = State.objects.get(pk=pk, project_id=project_id, workspace__slug=slug)
|
||||
serializer = StateSerializer(state, data=request.data, partial=True)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
|
|
@ -103,20 +101,14 @@ class StateViewSet(BaseViewSet):
|
|||
@allow_permission([ROLE.ADMIN])
|
||||
def mark_as_default(self, request, slug, project_id, pk):
|
||||
# Select all the states which are marked as default
|
||||
_ = State.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id, default=True
|
||||
).update(default=False)
|
||||
_ = State.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id, pk=pk
|
||||
).update(default=True)
|
||||
_ = State.objects.filter(workspace__slug=slug, project_id=project_id, default=True).update(default=False)
|
||||
_ = State.objects.filter(workspace__slug=slug, project_id=project_id, pk=pk).update(default=True)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
@invalidate_cache(path="workspaces/:slug/states/", url_params=True, user=False)
|
||||
@allow_permission([ROLE.ADMIN])
|
||||
def destroy(self, request, slug, project_id, pk):
|
||||
state = State.objects.get(
|
||||
is_triage=False, pk=pk, project_id=project_id, workspace__slug=slug
|
||||
)
|
||||
state = State.objects.get(is_triage=False, pk=pk, project_id=project_id, workspace__slug=slug)
|
||||
|
||||
if state.default:
|
||||
return Response(
|
||||
|
|
|
|||
|
|
@ -187,10 +187,7 @@ class TimezoneEndpoint(APIView):
|
|||
total_seconds = int(current_utc_offset.total_seconds())
|
||||
hours_offset = total_seconds // 3600
|
||||
minutes_offset = abs(total_seconds % 3600) // 60
|
||||
offset = (
|
||||
f"{'+' if hours_offset >= 0 else '-'}"
|
||||
f"{abs(hours_offset):02}:{minutes_offset:02}"
|
||||
)
|
||||
offset = f"{'+' if hours_offset >= 0 else '-'}{abs(hours_offset):02}:{minutes_offset:02}"
|
||||
|
||||
timezone_value = {
|
||||
"offset": int(current_offset),
|
||||
|
|
|
|||
|
|
@ -63,9 +63,7 @@ class UserEndpoint(BaseViewSet):
|
|||
|
||||
def retrieve_instance_admin(self, request):
|
||||
instance = Instance.objects.first()
|
||||
is_admin = InstanceAdmin.objects.filter(
|
||||
instance=instance, user=request.user
|
||||
).exists()
|
||||
is_admin = InstanceAdmin.objects.filter(instance=instance, user=request.user).exists()
|
||||
return Response({"is_instance_admin": is_admin}, status=status.HTTP_200_OK)
|
||||
|
||||
def partial_update(self, request, *args, **kwargs):
|
||||
|
|
@ -78,18 +76,14 @@ class UserEndpoint(BaseViewSet):
|
|||
# Instance admin check
|
||||
if InstanceAdmin.objects.filter(user=user).exists():
|
||||
return Response(
|
||||
{
|
||||
"error": "You cannot deactivate your account since you are an instance admin"
|
||||
},
|
||||
{"error": "You cannot deactivate your account since you are an instance admin"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
projects_to_deactivate = []
|
||||
workspaces_to_deactivate = []
|
||||
|
||||
projects = ProjectMember.objects.filter(
|
||||
member=request.user, is_active=True
|
||||
).annotate(
|
||||
projects = ProjectMember.objects.filter(member=request.user, is_active=True).annotate(
|
||||
other_admin_exists=Count(
|
||||
Case(
|
||||
When(Q(role=20, is_active=True) & ~Q(member=request.user), then=1),
|
||||
|
|
@ -106,15 +100,11 @@ class UserEndpoint(BaseViewSet):
|
|||
projects_to_deactivate.append(project)
|
||||
else:
|
||||
return Response(
|
||||
{
|
||||
"error": "You cannot deactivate account as you are the only admin in some projects."
|
||||
},
|
||||
{"error": "You cannot deactivate account as you are the only admin in some projects."},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
workspaces = WorkspaceMember.objects.filter(
|
||||
member=request.user, is_active=True
|
||||
).annotate(
|
||||
workspaces = WorkspaceMember.objects.filter(member=request.user, is_active=True).annotate(
|
||||
other_admin_exists=Count(
|
||||
Case(
|
||||
When(Q(role=20, is_active=True) & ~Q(member=request.user), then=1),
|
||||
|
|
@ -131,19 +121,13 @@ class UserEndpoint(BaseViewSet):
|
|||
workspaces_to_deactivate.append(workspace)
|
||||
else:
|
||||
return Response(
|
||||
{
|
||||
"error": "You cannot deactivate account as you are the only admin in some workspaces."
|
||||
},
|
||||
{"error": "You cannot deactivate account as you are the only admin in some workspaces."},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
ProjectMember.objects.bulk_update(
|
||||
projects_to_deactivate, ["is_active"], batch_size=100
|
||||
)
|
||||
ProjectMember.objects.bulk_update(projects_to_deactivate, ["is_active"], batch_size=100)
|
||||
|
||||
WorkspaceMember.objects.bulk_update(
|
||||
workspaces_to_deactivate, ["is_active"], batch_size=100
|
||||
)
|
||||
WorkspaceMember.objects.bulk_update(workspaces_to_deactivate, ["is_active"], batch_size=100)
|
||||
|
||||
# Delete all workspace invites
|
||||
WorkspaceMemberInvite.objects.filter(email=user.email).delete()
|
||||
|
|
@ -224,9 +208,7 @@ class UserActivityEndpoint(BaseAPIView, BasePaginator):
|
|||
order_by=request.GET.get("order_by", "-created_at"),
|
||||
request=request,
|
||||
queryset=queryset,
|
||||
on_results=lambda issue_activities: IssueActivitySerializer(
|
||||
issue_activities, many=True
|
||||
).data,
|
||||
on_results=lambda issue_activities: IssueActivitySerializer(issue_activities, many=True).data,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -64,34 +64,22 @@ class WorkspaceViewViewSet(BaseViewSet):
|
|||
.distinct()
|
||||
)
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
|
||||
)
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def list(self, request, slug):
|
||||
queryset = self.get_queryset()
|
||||
fields = [field for field in request.GET.get("fields", "").split(",") if field]
|
||||
if WorkspaceMember.objects.filter(
|
||||
workspace__slug=slug, member=request.user, role=5, is_active=True
|
||||
).exists():
|
||||
if WorkspaceMember.objects.filter(workspace__slug=slug, member=request.user, role=5, is_active=True).exists():
|
||||
queryset = queryset.filter(owned_by=request.user)
|
||||
views = IssueViewSerializer(
|
||||
queryset, many=True, fields=fields if fields else None
|
||||
).data
|
||||
views = IssueViewSerializer(queryset, many=True, fields=fields if fields else None).data
|
||||
return Response(views, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[], level="WORKSPACE", creator=True, model=IssueView
|
||||
)
|
||||
@allow_permission(allowed_roles=[], level="WORKSPACE", creator=True, model=IssueView)
|
||||
def partial_update(self, request, slug, pk):
|
||||
with transaction.atomic():
|
||||
workspace_view = IssueView.objects.select_for_update().get(
|
||||
pk=pk, workspace__slug=slug
|
||||
)
|
||||
workspace_view = IssueView.objects.select_for_update().get(pk=pk, workspace__slug=slug)
|
||||
|
||||
if workspace_view.is_locked:
|
||||
return Response(
|
||||
{"error": "view is locked"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response({"error": "view is locked"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# Only update the view if owner is updating
|
||||
if workspace_view.owned_by_id != request.user.id:
|
||||
|
|
@ -100,9 +88,7 @@ class WorkspaceViewViewSet(BaseViewSet):
|
|||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
serializer = IssueViewSerializer(
|
||||
workspace_view, data=request.data, partial=True
|
||||
)
|
||||
serializer = IssueViewSerializer(workspace_view, data=request.data, partial=True)
|
||||
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
|
|
@ -121,9 +107,7 @@ class WorkspaceViewViewSet(BaseViewSet):
|
|||
)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN], level="WORKSPACE", creator=True, model=IssueView
|
||||
)
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN], level="WORKSPACE", creator=True, model=IssueView)
|
||||
def destroy(self, request, slug, pk):
|
||||
workspace_view = IssueView.objects.get(pk=pk, workspace__slug=slug)
|
||||
|
||||
|
|
@ -177,9 +161,7 @@ class WorkspaceViewIssuesViewSet(BaseViewSet):
|
|||
return (
|
||||
issues.annotate(
|
||||
cycle_id=Subquery(
|
||||
CycleIssue.objects.filter(
|
||||
issue=OuterRef("id"), deleted_at__isnull=True
|
||||
).values("cycle_id")[:1]
|
||||
CycleIssue.objects.filter(issue=OuterRef("id"), deleted_at__isnull=True).values("cycle_id")[:1]
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
|
|
@ -227,9 +209,7 @@ class WorkspaceViewIssuesViewSet(BaseViewSet):
|
|||
return Issue.issue_objects.filter(workspace__slug=self.kwargs.get("slug"))
|
||||
|
||||
@method_decorator(gzip_page)
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
|
||||
)
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def list(self, request, slug):
|
||||
issue_queryset = self.get_queryset()
|
||||
|
||||
|
|
@ -274,9 +254,7 @@ class IssueViewViewSet(BaseViewSet):
|
|||
model = IssueView
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(
|
||||
project_id=self.kwargs.get("project_id"), owned_by=self.request.user
|
||||
)
|
||||
serializer.save(project_id=self.kwargs.get("project_id"), owned_by=self.request.user)
|
||||
|
||||
def get_queryset(self):
|
||||
subquery = UserFavorite.objects.filter(
|
||||
|
|
@ -320,9 +298,7 @@ class IssueViewViewSet(BaseViewSet):
|
|||
):
|
||||
queryset = queryset.filter(owned_by=request.user)
|
||||
fields = [field for field in request.GET.get("fields", "").split(",") if field]
|
||||
views = IssueViewSerializer(
|
||||
queryset, many=True, fields=fields if fields else None
|
||||
).data
|
||||
views = IssueViewSerializer(queryset, many=True, fields=fields if fields else None).data
|
||||
return Response(views, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
|
|
@ -363,14 +339,10 @@ class IssueViewViewSet(BaseViewSet):
|
|||
@allow_permission(allowed_roles=[], creator=True, model=IssueView)
|
||||
def partial_update(self, request, slug, project_id, pk):
|
||||
with transaction.atomic():
|
||||
issue_view = IssueView.objects.select_for_update().get(
|
||||
pk=pk, workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
issue_view = IssueView.objects.select_for_update().get(pk=pk, workspace__slug=slug, project_id=project_id)
|
||||
|
||||
if issue_view.is_locked:
|
||||
return Response(
|
||||
{"error": "view is locked"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response({"error": "view is locked"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# Only update the view if owner is updating
|
||||
if issue_view.owned_by_id != request.user.id:
|
||||
|
|
@ -379,9 +351,7 @@ class IssueViewViewSet(BaseViewSet):
|
|||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
serializer = IssueViewSerializer(
|
||||
issue_view, data=request.data, partial=True
|
||||
)
|
||||
serializer = IssueViewSerializer(issue_view, data=request.data, partial=True)
|
||||
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
|
|
@ -390,9 +360,7 @@ class IssueViewViewSet(BaseViewSet):
|
|||
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN], creator=True, model=IssueView)
|
||||
def destroy(self, request, slug, project_id, pk):
|
||||
project_view = IssueView.objects.get(
|
||||
pk=pk, project_id=project_id, workspace__slug=slug
|
||||
)
|
||||
project_view = IssueView.objects.get(pk=pk, project_id=project_id, workspace__slug=slug)
|
||||
if (
|
||||
ProjectMember.objects.filter(
|
||||
workspace__slug=slug,
|
||||
|
|
|
|||
|
|
@ -18,9 +18,7 @@ class WebhookEndpoint(BaseAPIView):
|
|||
def post(self, request, slug):
|
||||
workspace = Workspace.objects.get(slug=slug)
|
||||
try:
|
||||
serializer = WebhookSerializer(
|
||||
data=request.data, context={"request": request}
|
||||
)
|
||||
serializer = WebhookSerializer(data=request.data, context={"request": request})
|
||||
if serializer.is_valid():
|
||||
serializer.save(workspace_id=workspace.id)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
|
@ -119,8 +117,6 @@ class WebhookSecretRegenerateEndpoint(BaseAPIView):
|
|||
class WebhookLogsEndpoint(BaseAPIView):
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN], level="WORKSPACE")
|
||||
def get(self, request, slug, webhook_id):
|
||||
webhook_logs = WebhookLog.objects.filter(
|
||||
workspace__slug=slug, webhook=webhook_id
|
||||
)
|
||||
webhook_logs = WebhookLog.objects.filter(workspace__slug=slug, webhook=webhook_id)
|
||||
serializer = WebhookLogSerializer(webhook_logs, many=True)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
|
|
|||
|
|
@ -57,9 +57,7 @@ class WorkSpaceViewSet(BaseViewSet):
|
|||
|
||||
def get_queryset(self):
|
||||
member_count = (
|
||||
WorkspaceMember.objects.filter(
|
||||
workspace=OuterRef("id"), member__is_bot=False, is_active=True
|
||||
)
|
||||
WorkspaceMember.objects.filter(workspace=OuterRef("id"), member__is_bot=False, is_active=True)
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
|
|
@ -126,9 +124,7 @@ class WorkSpaceViewSet(BaseViewSet):
|
|||
)
|
||||
|
||||
# Get total members and role
|
||||
total_members = WorkspaceMember.objects.filter(
|
||||
workspace_id=serializer.data["id"]
|
||||
).count()
|
||||
total_members = WorkspaceMember.objects.filter(workspace_id=serializer.data["id"]).count()
|
||||
data = serializer.data
|
||||
data["total_members"] = total_members
|
||||
data["role"] = 20
|
||||
|
|
@ -179,31 +175,25 @@ class UserWorkSpacesEndpoint(BaseAPIView):
|
|||
def get(self, request):
|
||||
fields = [field for field in request.GET.get("fields", "").split(",") if field]
|
||||
member_count = (
|
||||
WorkspaceMember.objects.filter(
|
||||
workspace=OuterRef("id"), member__is_bot=False, is_active=True
|
||||
)
|
||||
WorkspaceMember.objects.filter(workspace=OuterRef("id"), member__is_bot=False, is_active=True)
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
|
||||
role = WorkspaceMember.objects.filter(
|
||||
workspace=OuterRef("id"), member=request.user, is_active=True
|
||||
).values("role")
|
||||
role = WorkspaceMember.objects.filter(workspace=OuterRef("id"), member=request.user, is_active=True).values(
|
||||
"role"
|
||||
)
|
||||
|
||||
workspace = (
|
||||
Workspace.objects.prefetch_related(
|
||||
Prefetch(
|
||||
"workspace_member",
|
||||
queryset=WorkspaceMember.objects.filter(
|
||||
member=request.user, is_active=True
|
||||
),
|
||||
queryset=WorkspaceMember.objects.filter(member=request.user, is_active=True),
|
||||
)
|
||||
)
|
||||
.annotate(role=role, total_members=member_count)
|
||||
.filter(
|
||||
workspace_member__member=request.user, workspace_member__is_active=True
|
||||
)
|
||||
.filter(workspace_member__member=request.user, workspace_member__is_active=True)
|
||||
.distinct()
|
||||
)
|
||||
|
||||
|
|
@ -226,10 +216,7 @@ class WorkSpaceAvailabilityCheckEndpoint(BaseAPIView):
|
|||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
workspace = (
|
||||
Workspace.objects.filter(slug=slug).exists()
|
||||
or slug in RESTRICTED_WORKSPACE_SLUGS
|
||||
)
|
||||
workspace = Workspace.objects.filter(slug=slug).exists() or slug in RESTRICTED_WORKSPACE_SLUGS
|
||||
return Response({"status": not workspace}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
|
|
@ -268,9 +255,7 @@ class UserWorkspaceDashboardEndpoint(BaseAPIView):
|
|||
.order_by("week_in_month")
|
||||
)
|
||||
|
||||
assigned_issues = Issue.issue_objects.filter(
|
||||
workspace__slug=slug, assignees__in=[request.user]
|
||||
).count()
|
||||
assigned_issues = Issue.issue_objects.filter(workspace__slug=slug, assignees__in=[request.user]).count()
|
||||
|
||||
pending_issues_count = Issue.issue_objects.filter(
|
||||
~Q(state__group__in=["completed", "cancelled"]),
|
||||
|
|
@ -283,18 +268,14 @@ class UserWorkspaceDashboardEndpoint(BaseAPIView):
|
|||
).count()
|
||||
|
||||
issues_due_week = (
|
||||
Issue.issue_objects.filter(
|
||||
workspace__slug=slug, assignees__in=[request.user]
|
||||
)
|
||||
Issue.issue_objects.filter(workspace__slug=slug, assignees__in=[request.user])
|
||||
.annotate(target_week=ExtractWeek("target_date"))
|
||||
.filter(target_week=timezone.now().date().isocalendar()[1])
|
||||
.count()
|
||||
)
|
||||
|
||||
state_distribution = (
|
||||
Issue.issue_objects.filter(
|
||||
workspace__slug=slug, assignees__in=[request.user]
|
||||
)
|
||||
Issue.issue_objects.filter(workspace__slug=slug, assignees__in=[request.user])
|
||||
.annotate(state_group=F("state__group"))
|
||||
.values("state_group")
|
||||
.annotate(state_count=Count("state_group"))
|
||||
|
|
@ -363,9 +344,7 @@ class ExportWorkspaceUserActivityEndpoint(BaseAPIView):
|
|||
|
||||
def post(self, request, slug, user_id):
|
||||
if not request.data.get("date"):
|
||||
return Response(
|
||||
{"error": "Date is required"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response({"error": "Date is required"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
user_activities = IssueActivity.objects.filter(
|
||||
~Q(field__in=["comment", "vote", "reaction", "draft"]),
|
||||
|
|
@ -403,7 +382,5 @@ class ExportWorkspaceUserActivityEndpoint(BaseAPIView):
|
|||
]
|
||||
csv_buffer = self.generate_csv_from_rows([header] + rows)
|
||||
response = HttpResponse(csv_buffer.getvalue(), content_type="text/csv")
|
||||
response["Content-Disposition"] = (
|
||||
'attachment; filename="workspace-user-activity.csv"'
|
||||
)
|
||||
response["Content-Disposition"] = 'attachment; filename="workspace-user-activity.csv"'
|
||||
return response
|
||||
|
|
|
|||
|
|
@ -49,9 +49,9 @@ class WorkspaceDraftIssueViewSet(BaseViewSet):
|
|||
.prefetch_related("assignees", "labels", "draft_issue_module__module")
|
||||
.annotate(
|
||||
cycle_id=Subquery(
|
||||
DraftIssueCycle.objects.filter(
|
||||
draft_issue=OuterRef("id"), deleted_at__isnull=True
|
||||
).values("cycle_id")[:1]
|
||||
DraftIssueCycle.objects.filter(draft_issue=OuterRef("id"), deleted_at__isnull=True).values(
|
||||
"cycle_id"
|
||||
)[:1]
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
|
|
@ -59,10 +59,7 @@ class WorkspaceDraftIssueViewSet(BaseViewSet):
|
|||
ArrayAgg(
|
||||
"labels__id",
|
||||
distinct=True,
|
||||
filter=Q(
|
||||
~Q(labels__id__isnull=True)
|
||||
& (Q(draft_label_issue__deleted_at__isnull=True))
|
||||
),
|
||||
filter=Q(~Q(labels__id__isnull=True) & (Q(draft_label_issue__deleted_at__isnull=True))),
|
||||
),
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
),
|
||||
|
|
@ -94,14 +91,10 @@ class WorkspaceDraftIssueViewSet(BaseViewSet):
|
|||
).distinct()
|
||||
|
||||
@method_decorator(gzip_page)
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
|
||||
)
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def list(self, request, slug):
|
||||
filters = issue_filters(request.query_params, "GET")
|
||||
issues = (
|
||||
self.get_queryset().filter(created_by=request.user).order_by("-created_at")
|
||||
)
|
||||
issues = self.get_queryset().filter(created_by=request.user).order_by("-created_at")
|
||||
|
||||
issues = issues.filter(**filters)
|
||||
# List Paginate
|
||||
|
|
@ -111,9 +104,7 @@ class WorkspaceDraftIssueViewSet(BaseViewSet):
|
|||
on_results=lambda issues: DraftIssueSerializer(issues, many=True).data,
|
||||
)
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
|
||||
)
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def create(self, request, slug):
|
||||
workspace = Workspace.objects.get(slug=slug)
|
||||
|
||||
|
|
@ -168,9 +159,7 @@ class WorkspaceDraftIssueViewSet(BaseViewSet):
|
|||
issue = self.get_queryset().filter(pk=pk, created_by=request.user).first()
|
||||
|
||||
if not issue:
|
||||
return Response(
|
||||
{"error": "Issue not found"}, status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
return Response({"error": "Issue not found"}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
project_id = request.data.get("project_id", issue.project_id)
|
||||
|
||||
|
|
@ -190,9 +179,7 @@ class WorkspaceDraftIssueViewSet(BaseViewSet):
|
|||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN], creator=True, model=Issue, level="WORKSPACE"
|
||||
)
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN], creator=True, model=Issue, level="WORKSPACE")
|
||||
def retrieve(self, request, slug, pk=None):
|
||||
issue = self.get_queryset().filter(pk=pk, created_by=request.user).first()
|
||||
|
||||
|
|
@ -205,9 +192,7 @@ class WorkspaceDraftIssueViewSet(BaseViewSet):
|
|||
serializer = DraftIssueDetailSerializer(issue)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN], creator=True, model=DraftIssue, level="WORKSPACE"
|
||||
)
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN], creator=True, model=DraftIssue, level="WORKSPACE")
|
||||
def destroy(self, request, slug, pk=None):
|
||||
draft_issue = DraftIssue.objects.get(workspace__slug=slug, pk=pk)
|
||||
draft_issue.delete()
|
||||
|
|
@ -266,9 +251,7 @@ class WorkspaceDraftIssueViewSet(BaseViewSet):
|
|||
current_instance=json.dumps(
|
||||
{
|
||||
"updated_cycle_issues": None,
|
||||
"created_cycle_issues": serializers.serialize(
|
||||
"json", [created_records]
|
||||
),
|
||||
"created_cycle_issues": serializers.serialize("json", [created_records]),
|
||||
}
|
||||
),
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@ class WorkspaceEstimatesEndpoint(BaseAPIView):
|
|||
|
||||
@cache_response(60 * 60 * 2)
|
||||
def get(self, request, slug):
|
||||
estimate_ids = Project.objects.filter(
|
||||
workspace__slug=slug, estimate__isnull=False
|
||||
).values_list("estimate_id", flat=True)
|
||||
estimate_ids = Project.objects.filter(workspace__slug=slug, estimate__isnull=False).values_list(
|
||||
"estimate_id", flat=True
|
||||
)
|
||||
estimates = (
|
||||
Estimate.objects.filter(pk__in=estimate_ids, workspace__slug=slug)
|
||||
.prefetch_related("points")
|
||||
|
|
|
|||
|
|
@ -19,9 +19,7 @@ class WorkspaceFavoriteEndpoint(BaseAPIView):
|
|||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
|
||||
def get(self, request, slug):
|
||||
# the second filter is to check if the user is a member of the project
|
||||
favorites = UserFavorite.objects.filter(
|
||||
user=request.user, workspace__slug=slug, parent__isnull=True
|
||||
).filter(
|
||||
favorites = UserFavorite.objects.filter(user=request.user, workspace__slug=slug, parent__isnull=True).filter(
|
||||
Q(project__isnull=True) & ~Q(entity_type="page")
|
||||
| (
|
||||
Q(project__isnull=False)
|
||||
|
|
@ -62,15 +60,11 @@ class WorkspaceFavoriteEndpoint(BaseAPIView):
|
|||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
except IntegrityError:
|
||||
return Response(
|
||||
{"error": "Favorite already exists"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response({"error": "Favorite already exists"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
|
||||
def patch(self, request, slug, favorite_id):
|
||||
favorite = UserFavorite.objects.get(
|
||||
user=request.user, workspace__slug=slug, pk=favorite_id
|
||||
)
|
||||
favorite = UserFavorite.objects.get(user=request.user, workspace__slug=slug, pk=favorite_id)
|
||||
serializer = UserFavoriteSerializer(favorite, data=request.data, partial=True)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
|
|
@ -79,9 +73,7 @@ class WorkspaceFavoriteEndpoint(BaseAPIView):
|
|||
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
|
||||
def delete(self, request, slug, favorite_id):
|
||||
favorite = UserFavorite.objects.get(
|
||||
user=request.user, workspace__slug=slug, pk=favorite_id
|
||||
)
|
||||
favorite = UserFavorite.objects.get(user=request.user, workspace__slug=slug, pk=favorite_id)
|
||||
favorite.delete(soft=False)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
|
@ -89,9 +81,7 @@ class WorkspaceFavoriteEndpoint(BaseAPIView):
|
|||
class WorkspaceFavoriteGroupEndpoint(BaseAPIView):
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
|
||||
def get(self, request, slug, favorite_id):
|
||||
favorites = UserFavorite.objects.filter(
|
||||
user=request.user, workspace__slug=slug, parent_id=favorite_id
|
||||
).filter(
|
||||
favorites = UserFavorite.objects.filter(user=request.user, workspace__slug=slug, parent_id=favorite_id).filter(
|
||||
Q(project__isnull=True)
|
||||
| (
|
||||
Q(project__isnull=False)
|
||||
|
|
|
|||
|
|
@ -20,9 +20,7 @@ class WorkspaceHomePreferenceViewSet(BaseAPIView):
|
|||
def get(self, request, slug):
|
||||
workspace = Workspace.objects.get(slug=slug)
|
||||
|
||||
get_preference = WorkspaceHomePreference.objects.filter(
|
||||
user=request.user, workspace_id=workspace.id
|
||||
)
|
||||
get_preference = WorkspaceHomePreference.objects.filter(user=request.user, workspace_id=workspace.id)
|
||||
|
||||
create_preference_keys = []
|
||||
|
||||
|
|
@ -55,9 +53,7 @@ class WorkspaceHomePreferenceViewSet(BaseAPIView):
|
|||
)
|
||||
sort_order_counter += 1
|
||||
|
||||
preference = WorkspaceHomePreference.objects.filter(
|
||||
user=request.user, workspace_id=workspace.id
|
||||
)
|
||||
preference = WorkspaceHomePreference.objects.filter(user=request.user, workspace_id=workspace.id)
|
||||
|
||||
return Response(
|
||||
preference.values("key", "is_enabled", "config", "sort_order"),
|
||||
|
|
@ -66,20 +62,14 @@ class WorkspaceHomePreferenceViewSet(BaseAPIView):
|
|||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def patch(self, request, slug, key):
|
||||
preference = WorkspaceHomePreference.objects.filter(
|
||||
key=key, workspace__slug=slug, user=request.user
|
||||
).first()
|
||||
preference = WorkspaceHomePreference.objects.filter(key=key, workspace__slug=slug, user=request.user).first()
|
||||
|
||||
if preference:
|
||||
serializer = WorkspaceHomePreferenceSerializer(
|
||||
preference, data=request.data, partial=True
|
||||
)
|
||||
serializer = WorkspaceHomePreferenceSerializer(preference, data=request.data, partial=True)
|
||||
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
return Response(
|
||||
{"detail": "Preference not found"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response({"detail": "Preference not found"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
|
|
|||
|
|
@ -50,23 +50,13 @@ class WorkspaceInvitationsViewset(BaseViewSet):
|
|||
emails = request.data.get("emails", [])
|
||||
# Check if email is provided
|
||||
if not emails:
|
||||
return Response(
|
||||
{"error": "Emails are required"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response({"error": "Emails are required"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# check for role level of the requesting user
|
||||
requesting_user = WorkspaceMember.objects.get(
|
||||
workspace__slug=slug, member=request.user, is_active=True
|
||||
)
|
||||
requesting_user = WorkspaceMember.objects.get(workspace__slug=slug, member=request.user, is_active=True)
|
||||
|
||||
# Check if any invited user has an higher role
|
||||
if len(
|
||||
[
|
||||
email
|
||||
for email in emails
|
||||
if int(email.get("role", 5)) > requesting_user.role
|
||||
]
|
||||
):
|
||||
if len([email for email in emails if int(email.get("role", 5)) > requesting_user.role]):
|
||||
return Response(
|
||||
{"error": "You cannot invite a user with higher role"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
|
|
@ -86,9 +76,7 @@ class WorkspaceInvitationsViewset(BaseViewSet):
|
|||
return Response(
|
||||
{
|
||||
"error": "Some users are already member of workspace",
|
||||
"workspace_users": WorkSpaceMemberSerializer(
|
||||
workspace_members, many=True
|
||||
).data,
|
||||
"workspace_users": WorkSpaceMemberSerializer(workspace_members, many=True).data,
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
|
@ -113,7 +101,7 @@ class WorkspaceInvitationsViewset(BaseViewSet):
|
|||
except ValidationError:
|
||||
return Response(
|
||||
{
|
||||
"error": f"Invalid email - {email} provided a valid email address is required to send the invite"
|
||||
"error": f"Invalid email - {email} provided a valid email address is required to send the invite" # noqa: E501
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
|
@ -134,14 +122,10 @@ class WorkspaceInvitationsViewset(BaseViewSet):
|
|||
request.user.email,
|
||||
)
|
||||
|
||||
return Response(
|
||||
{"message": "Emails sent successfully"}, status=status.HTTP_200_OK
|
||||
)
|
||||
return Response({"message": "Emails sent successfully"}, status=status.HTTP_200_OK)
|
||||
|
||||
def destroy(self, request, slug, pk):
|
||||
workspace_member_invite = WorkspaceMemberInvite.objects.get(
|
||||
pk=pk, workspace__slug=slug
|
||||
)
|
||||
workspace_member_invite = WorkspaceMemberInvite.objects.get(pk=pk, workspace__slug=slug)
|
||||
workspace_member_invite.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
|
@ -160,9 +144,7 @@ class WorkspaceJoinEndpoint(BaseAPIView):
|
|||
)
|
||||
@invalidate_cache(path="/api/users/me/settings/", multiple=True)
|
||||
def post(self, request, slug, pk):
|
||||
workspace_invite = WorkspaceMemberInvite.objects.get(
|
||||
pk=pk, workspace__slug=slug
|
||||
)
|
||||
workspace_invite = WorkspaceMemberInvite.objects.get(pk=pk, workspace__slug=slug)
|
||||
|
||||
email = request.data.get("email", "")
|
||||
|
||||
|
|
@ -235,9 +217,7 @@ class WorkspaceJoinEndpoint(BaseAPIView):
|
|||
)
|
||||
|
||||
def get(self, request, slug, pk):
|
||||
workspace_invitation = WorkspaceMemberInvite.objects.get(
|
||||
workspace__slug=slug, pk=pk
|
||||
)
|
||||
workspace_invitation = WorkspaceMemberInvite.objects.get(workspace__slug=slug, pk=pk)
|
||||
serializer = WorkSpaceMemberInviteSerializer(workspace_invitation)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
|
|
@ -248,10 +228,7 @@ class UserWorkspaceInvitationsViewSet(BaseViewSet):
|
|||
|
||||
def get_queryset(self):
|
||||
return self.filter_queryset(
|
||||
super()
|
||||
.get_queryset()
|
||||
.filter(email=self.request.user.email)
|
||||
.select_related("workspace")
|
||||
super().get_queryset().filter(email=self.request.user.email).select_related("workspace")
|
||||
)
|
||||
|
||||
@invalidate_cache(path="/api/workspaces/", user=False)
|
||||
|
|
@ -271,9 +248,9 @@ class UserWorkspaceInvitationsViewSet(BaseViewSet):
|
|||
multiple=True,
|
||||
)
|
||||
# Update the WorkspaceMember for this specific invitation
|
||||
WorkspaceMember.objects.filter(
|
||||
workspace_id=invitation.workspace_id, member=request.user
|
||||
).update(is_active=True, role=invitation.role)
|
||||
WorkspaceMember.objects.filter(workspace_id=invitation.workspace_id, member=request.user).update(
|
||||
is_active=True, role=invitation.role
|
||||
)
|
||||
|
||||
# Bulk create the user for all the workspaces
|
||||
WorkspaceMember.objects.bulk_create(
|
||||
|
|
|
|||
|
|
@ -38,24 +38,16 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
|||
.select_related("member", "member__avatar_asset")
|
||||
)
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
|
||||
)
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def list(self, request, slug):
|
||||
workspace_member = WorkspaceMember.objects.get(
|
||||
member=request.user, workspace__slug=slug, is_active=True
|
||||
)
|
||||
workspace_member = WorkspaceMember.objects.get(member=request.user, workspace__slug=slug, is_active=True)
|
||||
|
||||
# Get all active workspace members
|
||||
workspace_members = self.get_queryset()
|
||||
if workspace_member.role > 5:
|
||||
serializer = WorkspaceMemberAdminSerializer(
|
||||
workspace_members, fields=("id", "member", "role"), many=True
|
||||
)
|
||||
serializer = WorkspaceMemberAdminSerializer(workspace_members, fields=("id", "member", "role"), many=True)
|
||||
else:
|
||||
serializer = WorkSpaceMemberSerializer(
|
||||
workspace_members, fields=("id", "member", "role"), many=True
|
||||
)
|
||||
serializer = WorkSpaceMemberSerializer(workspace_members, fields=("id", "member", "role"), many=True)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN], level="WORKSPACE")
|
||||
|
|
@ -71,13 +63,9 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
|||
|
||||
# If a user is moved to a guest role he can't have any other role in projects
|
||||
if "role" in request.data and int(request.data.get("role")) == 5:
|
||||
ProjectMember.objects.filter(
|
||||
workspace__slug=slug, member_id=workspace_member.member_id
|
||||
).update(role=5)
|
||||
ProjectMember.objects.filter(workspace__slug=slug, member_id=workspace_member.member_id).update(role=5)
|
||||
|
||||
serializer = WorkSpaceMemberSerializer(
|
||||
workspace_member, data=request.data, partial=True
|
||||
)
|
||||
serializer = WorkSpaceMemberSerializer(workspace_member, data=request.data, partial=True)
|
||||
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
|
|
@ -98,9 +86,7 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
|||
|
||||
if str(workspace_member.id) == str(requesting_workspace_member.id):
|
||||
return Response(
|
||||
{
|
||||
"error": "You cannot remove yourself from the workspace. Please use leave workspace"
|
||||
},
|
||||
{"error": "You cannot remove yourself from the workspace. Please use leave workspace"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
|
@ -126,7 +112,7 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
|||
):
|
||||
return Response(
|
||||
{
|
||||
"error": "User is a part of some projects where they are the only admin, they should either leave that project or promote another user to admin."
|
||||
"error": "User is a part of some projects where they are the only admin, they should either leave that project or promote another user to admin." # noqa: E501
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
|
@ -148,25 +134,18 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
|||
)
|
||||
@invalidate_cache(path="/api/users/me/settings/")
|
||||
@invalidate_cache(path="api/users/me/workspaces/", user=False, multiple=True)
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
|
||||
)
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def leave(self, request, slug):
|
||||
workspace_member = WorkspaceMember.objects.get(
|
||||
workspace__slug=slug, member=request.user, is_active=True
|
||||
)
|
||||
workspace_member = WorkspaceMember.objects.get(workspace__slug=slug, member=request.user, is_active=True)
|
||||
|
||||
# Check if the leaving user is the only admin of the workspace
|
||||
if (
|
||||
workspace_member.role == 20
|
||||
and not WorkspaceMember.objects.filter(
|
||||
workspace__slug=slug, role=20, is_active=True
|
||||
).count()
|
||||
> 1
|
||||
and not WorkspaceMember.objects.filter(workspace__slug=slug, role=20, is_active=True).count() > 1
|
||||
):
|
||||
return Response(
|
||||
{
|
||||
"error": "You cannot leave the workspace as you are the only admin of the workspace you will have to either delete the workspace or promote another user to admin."
|
||||
"error": "You cannot leave the workspace as you are the only admin of the workspace you will have to either delete the workspace or promote another user to admin." # noqa: E501
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
|
@ -187,7 +166,7 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
|||
):
|
||||
return Response(
|
||||
{
|
||||
"error": "You are a part of some projects where you are the only admin, you should either leave the project or promote another user to admin."
|
||||
"error": "You are a part of some projects where you are the only admin, you should either leave the project or promote another user to admin." # noqa: E501
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
|
@ -205,9 +184,7 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
|||
|
||||
class WorkspaceMemberUserViewsEndpoint(BaseAPIView):
|
||||
def post(self, request, slug):
|
||||
workspace_member = WorkspaceMember.objects.get(
|
||||
workspace__slug=slug, member=request.user, is_active=True
|
||||
)
|
||||
workspace_member = WorkspaceMember.objects.get(workspace__slug=slug, member=request.user, is_active=True)
|
||||
workspace_member.view_props = request.data.get("view_props", {})
|
||||
workspace_member.save()
|
||||
|
||||
|
|
@ -219,23 +196,15 @@ class WorkspaceMemberUserEndpoint(BaseAPIView):
|
|||
|
||||
def get(self, request, slug):
|
||||
draft_issue_count = (
|
||||
DraftIssue.objects.filter(
|
||||
created_by=request.user, workspace_id=OuterRef("workspace_id")
|
||||
)
|
||||
DraftIssue.objects.filter(created_by=request.user, workspace_id=OuterRef("workspace_id"))
|
||||
.values("workspace_id")
|
||||
.annotate(count=Count("id"))
|
||||
.values("count")
|
||||
)
|
||||
|
||||
workspace_member = (
|
||||
WorkspaceMember.objects.filter(
|
||||
member=request.user, workspace__slug=slug, is_active=True
|
||||
)
|
||||
.annotate(
|
||||
draft_issue_count=Coalesce(
|
||||
Subquery(draft_issue_count, output_field=IntegerField()), 0
|
||||
)
|
||||
)
|
||||
WorkspaceMember.objects.filter(member=request.user, workspace__slug=slug, is_active=True)
|
||||
.annotate(draft_issue_count=Coalesce(Subquery(draft_issue_count, output_field=IntegerField()), 0))
|
||||
.first()
|
||||
)
|
||||
serializer = WorkspaceMemberMeSerializer(workspace_member)
|
||||
|
|
|
|||
|
|
@ -28,48 +28,34 @@ class QuickLinkViewSet(BaseViewSet):
|
|||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def partial_update(self, request, slug, pk):
|
||||
quick_link = WorkspaceUserLink.objects.filter(
|
||||
pk=pk, workspace__slug=slug, owner=request.user
|
||||
).first()
|
||||
quick_link = WorkspaceUserLink.objects.filter(pk=pk, workspace__slug=slug, owner=request.user).first()
|
||||
|
||||
if quick_link:
|
||||
serializer = WorkspaceUserLinkSerializer(
|
||||
quick_link, data=request.data, partial=True
|
||||
)
|
||||
serializer = WorkspaceUserLinkSerializer(quick_link, data=request.data, partial=True)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(
|
||||
{"detail": "Quick link not found."}, status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
return Response({"detail": "Quick link not found."}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def retrieve(self, request, slug, pk):
|
||||
try:
|
||||
quick_link = WorkspaceUserLink.objects.get(
|
||||
pk=pk, workspace__slug=slug, owner=request.user
|
||||
)
|
||||
quick_link = WorkspaceUserLink.objects.get(pk=pk, workspace__slug=slug, owner=request.user)
|
||||
serializer = WorkspaceUserLinkSerializer(quick_link)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
except WorkspaceUserLink.DoesNotExist:
|
||||
return Response(
|
||||
{"error": "Quick link not found."}, status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
return Response({"error": "Quick link not found."}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def destroy(self, request, slug, pk):
|
||||
quick_link = WorkspaceUserLink.objects.get(
|
||||
pk=pk, workspace__slug=slug, owner=request.user
|
||||
)
|
||||
quick_link = WorkspaceUserLink.objects.get(pk=pk, workspace__slug=slug, owner=request.user)
|
||||
quick_link.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def list(self, request, slug):
|
||||
quick_links = WorkspaceUserLink.objects.filter(
|
||||
workspace__slug=slug, owner=request.user
|
||||
)
|
||||
quick_links = WorkspaceUserLink.objects.filter(workspace__slug=slug, owner=request.user)
|
||||
|
||||
serializer = WorkspaceUserLinkSerializer(quick_links, many=True)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
|
|
|||
|
|
@ -19,18 +19,14 @@ class UserRecentVisitViewSet(BaseViewSet):
|
|||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def list(self, request, slug):
|
||||
user_recent_visits = UserRecentVisit.objects.filter(
|
||||
workspace__slug=slug, user=request.user
|
||||
)
|
||||
user_recent_visits = UserRecentVisit.objects.filter(workspace__slug=slug, user=request.user)
|
||||
|
||||
entity_name = request.query_params.get("entity_name")
|
||||
|
||||
if entity_name:
|
||||
user_recent_visits = user_recent_visits.filter(entity_name=entity_name)
|
||||
|
||||
user_recent_visits = user_recent_visits.filter(
|
||||
entity_name__in=["issue", "page", "project"]
|
||||
)
|
||||
user_recent_visits = user_recent_visits.filter(entity_name__in=["issue", "page", "project"])
|
||||
|
||||
serializer = WorkspaceRecentVisitSerializer(user_recent_visits[:20], many=True)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
|
|
|||
|
|
@ -24,9 +24,7 @@ class WorkspaceStickyViewSet(BaseViewSet):
|
|||
.distinct()
|
||||
)
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
|
||||
)
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def create(self, request, slug):
|
||||
workspace = Workspace.objects.get(slug=slug)
|
||||
serializer = StickySerializer(data=request.data)
|
||||
|
|
@ -35,9 +33,7 @@ class WorkspaceStickyViewSet(BaseViewSet):
|
|||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
|
||||
)
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def list(self, request, slug):
|
||||
query = request.query_params.get("query", False)
|
||||
stickies = self.get_queryset().order_by("-sort_order")
|
||||
|
|
|
|||
|
|
@ -101,9 +101,7 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
|
|||
return (
|
||||
issues.annotate(
|
||||
cycle_id=Subquery(
|
||||
CycleIssue.objects.filter(
|
||||
issue=OuterRef("id"), deleted_at__isnull=True
|
||||
).values("cycle_id")[:1]
|
||||
CycleIssue.objects.filter(issue=OuterRef("id"), deleted_at__isnull=True).values("cycle_id")[:1]
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
|
|
@ -136,9 +134,7 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
|
|||
order_by_param = request.GET.get("order_by", "-created_at")
|
||||
issue_queryset = Issue.issue_objects.filter(
|
||||
id__in=Issue.issue_objects.filter(
|
||||
Q(assignees__in=[user_id])
|
||||
| Q(created_by_id=user_id)
|
||||
| Q(issue_subscribers__subscriber_id=user_id),
|
||||
Q(assignees__in=[user_id]) | Q(created_by_id=user_id) | Q(issue_subscribers__subscriber_id=user_id),
|
||||
workspace__slug=slug,
|
||||
).values_list("id", flat=True),
|
||||
workspace__slug=slug,
|
||||
|
|
@ -168,9 +164,7 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
|
|||
sub_group_by = request.GET.get("sub_group_by", False)
|
||||
|
||||
# issue queryset
|
||||
issue_queryset = issue_queryset_grouper(
|
||||
queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by
|
||||
)
|
||||
issue_queryset = issue_queryset_grouper(queryset=issue_queryset, group_by=group_by, sub_group_by=sub_group_by)
|
||||
|
||||
if group_by:
|
||||
if sub_group_by:
|
||||
|
|
@ -247,9 +241,7 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
|
|||
request=request,
|
||||
queryset=issue_queryset,
|
||||
total_count_queryset=total_issue_queryset,
|
||||
on_results=lambda issues: issue_on_results(
|
||||
group_by=group_by, issues=issues, sub_group_by=sub_group_by
|
||||
),
|
||||
on_results=lambda issues: issue_on_results(group_by=group_by, issues=issues, sub_group_by=sub_group_by),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -257,19 +249,11 @@ class WorkspaceUserPropertiesEndpoint(BaseAPIView):
|
|||
permission_classes = [WorkspaceViewerPermission]
|
||||
|
||||
def patch(self, request, slug):
|
||||
workspace_properties = WorkspaceUserProperties.objects.get(
|
||||
user=request.user, workspace__slug=slug
|
||||
)
|
||||
workspace_properties = WorkspaceUserProperties.objects.get(user=request.user, workspace__slug=slug)
|
||||
|
||||
workspace_properties.filters = request.data.get(
|
||||
"filters", workspace_properties.filters
|
||||
)
|
||||
workspace_properties.rich_filters = request.data.get(
|
||||
"rich_filters", workspace_properties.rich_filters
|
||||
)
|
||||
workspace_properties.display_filters = request.data.get(
|
||||
"display_filters", workspace_properties.display_filters
|
||||
)
|
||||
workspace_properties.filters = request.data.get("filters", workspace_properties.filters)
|
||||
workspace_properties.rich_filters = request.data.get("rich_filters", workspace_properties.rich_filters)
|
||||
workspace_properties.display_filters = request.data.get("display_filters", workspace_properties.display_filters)
|
||||
workspace_properties.display_properties = request.data.get(
|
||||
"display_properties", workspace_properties.display_properties
|
||||
)
|
||||
|
|
@ -398,9 +382,7 @@ class WorkspaceUserActivityEndpoint(BaseAPIView):
|
|||
order_by=request.GET.get("order_by", "-created_at"),
|
||||
request=request,
|
||||
queryset=queryset,
|
||||
on_results=lambda issue_activities: IssueActivitySerializer(
|
||||
issue_activities, many=True
|
||||
).data,
|
||||
on_results=lambda issue_activities: IssueActivitySerializer(issue_activities, many=True).data,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -410,10 +392,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView):
|
|||
|
||||
state_distribution = (
|
||||
Issue.issue_objects.filter(
|
||||
(
|
||||
Q(assignees__in=[user_id])
|
||||
& Q(issue_assignee__deleted_at__isnull=True)
|
||||
),
|
||||
(Q(assignees__in=[user_id]) & Q(issue_assignee__deleted_at__isnull=True)),
|
||||
workspace__slug=slug,
|
||||
project__project_projectmember__member=request.user,
|
||||
project__project_projectmember__is_active=True,
|
||||
|
|
@ -429,10 +408,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView):
|
|||
|
||||
priority_distribution = (
|
||||
Issue.issue_objects.filter(
|
||||
(
|
||||
Q(assignees__in=[user_id])
|
||||
& Q(issue_assignee__deleted_at__isnull=True)
|
||||
),
|
||||
(Q(assignees__in=[user_id]) & Q(issue_assignee__deleted_at__isnull=True)),
|
||||
workspace__slug=slug,
|
||||
project__project_projectmember__member=request.user,
|
||||
project__project_projectmember__is_active=True,
|
||||
|
|
@ -443,10 +419,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView):
|
|||
.filter(priority_count__gte=1)
|
||||
.annotate(
|
||||
priority_order=Case(
|
||||
*[
|
||||
When(priority=p, then=Value(i))
|
||||
for i, p in enumerate(priority_order)
|
||||
],
|
||||
*[When(priority=p, then=Value(i)) for i, p in enumerate(priority_order)],
|
||||
default=Value(len(priority_order)),
|
||||
output_field=IntegerField(),
|
||||
)
|
||||
|
|
@ -467,10 +440,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView):
|
|||
|
||||
assigned_issues_count = (
|
||||
Issue.issue_objects.filter(
|
||||
(
|
||||
Q(assignees__in=[user_id])
|
||||
& Q(issue_assignee__deleted_at__isnull=True)
|
||||
),
|
||||
(Q(assignees__in=[user_id]) & Q(issue_assignee__deleted_at__isnull=True)),
|
||||
workspace__slug=slug,
|
||||
project__project_projectmember__member=request.user,
|
||||
project__project_projectmember__is_active=True,
|
||||
|
|
@ -482,10 +452,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView):
|
|||
pending_issues_count = (
|
||||
Issue.issue_objects.filter(
|
||||
~Q(state__group__in=["completed", "cancelled"]),
|
||||
(
|
||||
Q(assignees__in=[user_id])
|
||||
& Q(issue_assignee__deleted_at__isnull=True)
|
||||
),
|
||||
(Q(assignees__in=[user_id]) & Q(issue_assignee__deleted_at__isnull=True)),
|
||||
workspace__slug=slug,
|
||||
project__project_projectmember__member=request.user,
|
||||
project__project_projectmember__is_active=True,
|
||||
|
|
@ -496,10 +463,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView):
|
|||
|
||||
completed_issues_count = (
|
||||
Issue.issue_objects.filter(
|
||||
(
|
||||
Q(assignees__in=[user_id])
|
||||
& Q(issue_assignee__deleted_at__isnull=True)
|
||||
),
|
||||
(Q(assignees__in=[user_id]) & Q(issue_assignee__deleted_at__isnull=True)),
|
||||
workspace__slug=slug,
|
||||
state__group="completed",
|
||||
project__project_projectmember__member=request.user,
|
||||
|
|
|
|||
|
|
@ -22,9 +22,7 @@ class WorkspaceUserPreferenceViewSet(BaseAPIView):
|
|||
def get(self, request, slug):
|
||||
workspace = Workspace.objects.get(slug=slug)
|
||||
|
||||
get_preference = WorkspaceUserPreference.objects.filter(
|
||||
user=request.user, workspace_id=workspace.id
|
||||
)
|
||||
get_preference = WorkspaceUserPreference.objects.filter(user=request.user, workspace_id=workspace.id)
|
||||
|
||||
create_preference_keys = []
|
||||
|
||||
|
|
@ -49,9 +47,7 @@ class WorkspaceUserPreferenceViewSet(BaseAPIView):
|
|||
)
|
||||
|
||||
preferences = (
|
||||
WorkspaceUserPreference.objects.filter(
|
||||
user=request.user, workspace_id=workspace.id
|
||||
)
|
||||
WorkspaceUserPreference.objects.filter(user=request.user, workspace_id=workspace.id)
|
||||
.order_by("sort_order")
|
||||
.values("key", "is_pinned", "sort_order")
|
||||
)
|
||||
|
|
@ -70,20 +66,14 @@ class WorkspaceUserPreferenceViewSet(BaseAPIView):
|
|||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def patch(self, request, slug, key):
|
||||
preference = WorkspaceUserPreference.objects.filter(
|
||||
key=key, workspace__slug=slug, user=request.user
|
||||
).first()
|
||||
preference = WorkspaceUserPreference.objects.filter(key=key, workspace__slug=slug, user=request.user).first()
|
||||
|
||||
if preference:
|
||||
serializer = WorkspaceUserPreferenceSerializer(
|
||||
preference, data=request.data, partial=True
|
||||
)
|
||||
serializer = WorkspaceUserPreferenceSerializer(preference, data=request.data, partial=True)
|
||||
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
return Response(
|
||||
{"detail": "Preference not found"}, status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
return Response({"detail": "Preference not found"}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue