mirror of
https://github.com/makeplane/plane.git
synced 2026-07-13 14:01:45 +02:00
Merge pull request #2252 from makeplane/sync/ce-ee
Sync: Community Changes
This commit is contained in:
@@ -73,6 +73,8 @@ from .issue import (
|
||||
IssueReactionLiteSerializer,
|
||||
IssueAttachmentLiteSerializer,
|
||||
IssueLinkLiteSerializer,
|
||||
IssueVersionDetailSerializer,
|
||||
IssueDescriptionVersionDetailSerializer,
|
||||
)
|
||||
|
||||
from .module import (
|
||||
|
||||
@@ -33,6 +33,8 @@ from plane.db.models import (
|
||||
IssueVote,
|
||||
IssueRelation,
|
||||
State,
|
||||
IssueVersion,
|
||||
IssueDescriptionVersion,
|
||||
IssueType,
|
||||
)
|
||||
|
||||
@@ -687,3 +689,64 @@ class IssueSubscriberSerializer(BaseSerializer):
|
||||
model = IssueSubscriber
|
||||
fields = "__all__"
|
||||
read_only_fields = ["workspace", "project", "issue"]
|
||||
|
||||
|
||||
class IssueVersionDetailSerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = IssueVersion
|
||||
fields = [
|
||||
"id",
|
||||
"workspace",
|
||||
"project",
|
||||
"issue",
|
||||
"parent",
|
||||
"state",
|
||||
"estimate_point",
|
||||
"name",
|
||||
"priority",
|
||||
"start_date",
|
||||
"target_date",
|
||||
"assignees",
|
||||
"sequence_id",
|
||||
"labels",
|
||||
"sort_order",
|
||||
"completed_at",
|
||||
"archived_at",
|
||||
"is_draft",
|
||||
"external_source",
|
||||
"external_id",
|
||||
"type",
|
||||
"cycle",
|
||||
"modules",
|
||||
"meta",
|
||||
"name",
|
||||
"last_saved_at",
|
||||
"owned_by",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"created_by",
|
||||
"updated_by",
|
||||
]
|
||||
read_only_fields = ["workspace", "project", "issue"]
|
||||
|
||||
|
||||
class IssueDescriptionVersionDetailSerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = IssueDescriptionVersion
|
||||
fields = [
|
||||
"id",
|
||||
"workspace",
|
||||
"project",
|
||||
"issue",
|
||||
"description_binary",
|
||||
"description_html",
|
||||
"description_stripped",
|
||||
"description_json",
|
||||
"last_saved_at",
|
||||
"owned_by",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"created_by",
|
||||
"updated_by",
|
||||
]
|
||||
read_only_fields = ["workspace", "project", "issue"]
|
||||
|
||||
@@ -23,6 +23,8 @@ from plane.app.views import (
|
||||
IssueDetailEndpoint,
|
||||
IssueAttachmentV2Endpoint,
|
||||
IssueBulkUpdateDateEndpoint,
|
||||
IssueVersionEndpoint,
|
||||
IssueDescriptionVersionEndpoint,
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
@@ -250,4 +252,24 @@ urlpatterns = [
|
||||
IssueBulkUpdateDateEndpoint.as_view(),
|
||||
name="project-issue-dates",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/versions/",
|
||||
IssueVersionEndpoint.as_view(),
|
||||
name="page-versions",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/versions/<uuid:pk>/",
|
||||
IssueVersionEndpoint.as_view(),
|
||||
name="page-versions",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/description-versions/",
|
||||
IssueDescriptionVersionEndpoint.as_view(),
|
||||
name="page-versions",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/description-versions/<uuid:pk>/",
|
||||
IssueDescriptionVersionEndpoint.as_view(),
|
||||
name="page-versions",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -141,6 +141,8 @@ from .issue.sub_issue import SubIssuesEndpoint
|
||||
|
||||
from .issue.subscriber import IssueSubscriberViewSet
|
||||
|
||||
from .issue.version import IssueVersionEndpoint, IssueDescriptionVersionEndpoint
|
||||
|
||||
from .module.base import (
|
||||
ModuleViewSet,
|
||||
ModuleLinkViewSet,
|
||||
|
||||
118
apiserver/plane/app/views/issue/version.py
Normal file
118
apiserver/plane/app/views/issue/version.py
Normal file
@@ -0,0 +1,118 @@
|
||||
# Third party imports
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
|
||||
# Module imports
|
||||
from plane.db.models import IssueVersion, IssueDescriptionVersion
|
||||
from ..base import BaseAPIView
|
||||
from plane.app.serializers import (
|
||||
IssueVersionDetailSerializer,
|
||||
IssueDescriptionVersionDetailSerializer,
|
||||
)
|
||||
from plane.app.permissions import allow_permission, ROLE
|
||||
from plane.utils.global_paginator import paginate
|
||||
from plane.utils.timezone_converter import user_timezone_converter
|
||||
|
||||
|
||||
class IssueVersionEndpoint(BaseAPIView):
|
||||
def process_paginated_result(self, fields, results, timezone):
|
||||
paginated_data = results.values(*fields)
|
||||
|
||||
datetime_fields = ["created_at", "updated_at"]
|
||||
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, issue_id, pk=None):
|
||||
if pk:
|
||||
issue_version = IssueVersion.objects.get(
|
||||
workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk
|
||||
)
|
||||
|
||||
serializer = IssueVersionDetailSerializer(issue_version)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
cursor = request.GET.get("cursor", None)
|
||||
|
||||
required_fields = [
|
||||
"id",
|
||||
"workspace",
|
||||
"project",
|
||||
"issue",
|
||||
"last_saved_at",
|
||||
"owned_by",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"created_by",
|
||||
"updated_by",
|
||||
]
|
||||
|
||||
issue_versions_queryset = IssueVersion.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id, issue_id=issue_id
|
||||
)
|
||||
|
||||
paginated_data = paginate(
|
||||
base_queryset=issue_versions_queryset,
|
||||
queryset=issue_versions_queryset,
|
||||
cursor=cursor,
|
||||
on_result=lambda results: self.process_paginated_result(
|
||||
required_fields, results, request.user.user_timezone
|
||||
),
|
||||
)
|
||||
|
||||
return Response(paginated_data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class IssueDescriptionVersionEndpoint(BaseAPIView):
|
||||
def process_paginated_result(self, fields, results, timezone):
|
||||
paginated_data = results.values(*fields)
|
||||
|
||||
datetime_fields = ["created_at", "updated_at"]
|
||||
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, issue_id, pk=None):
|
||||
if pk:
|
||||
issue_description_version = IssueDescriptionVersion.objects.get(
|
||||
workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk
|
||||
)
|
||||
|
||||
serializer = IssueDescriptionVersionDetailSerializer(
|
||||
issue_description_version
|
||||
)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
cursor = request.GET.get("cursor", None)
|
||||
|
||||
required_fields = [
|
||||
"id",
|
||||
"workspace",
|
||||
"project",
|
||||
"issue",
|
||||
"last_saved_at",
|
||||
"owned_by",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"created_by",
|
||||
"updated_by",
|
||||
]
|
||||
|
||||
issue_description_versions_queryset = IssueDescriptionVersion.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id, issue_id=issue_id
|
||||
)
|
||||
paginated_data = paginate(
|
||||
base_queryset=issue_description_versions_queryset,
|
||||
queryset=issue_description_versions_queryset,
|
||||
cursor=cursor,
|
||||
on_result=lambda results: self.process_paginated_result(
|
||||
required_fields, results, request.user.user_timezone
|
||||
),
|
||||
)
|
||||
return Response(paginated_data, status=status.HTTP_200_OK)
|
||||
@@ -8,7 +8,7 @@ export const STICKY_COLORS_LIST: {
|
||||
{
|
||||
key: "gray",
|
||||
label: "Gray",
|
||||
backgroundColor: "var(--editor-colors-gray-background)",
|
||||
backgroundColor: "rgba(var(--color-background-90))",
|
||||
},
|
||||
{
|
||||
key: "peach",
|
||||
|
||||
@@ -43,7 +43,6 @@ export const StickyEditor = React.forwardRef<EditorRefApi, StickyEditorWrapperPr
|
||||
showToolbarInitially = true,
|
||||
showToolbar = true,
|
||||
parentClassName = "",
|
||||
placeholder = "Add comment...",
|
||||
uploadFile,
|
||||
...rest
|
||||
} = props;
|
||||
@@ -78,7 +77,6 @@ export const StickyEditor = React.forwardRef<EditorRefApi, StickyEditorWrapperPr
|
||||
mentionHandler={{
|
||||
renderComponent: () => <></>,
|
||||
}}
|
||||
placeholder={placeholder}
|
||||
containerClassName={cn(containerClassName, "relative")}
|
||||
{...rest}
|
||||
/>
|
||||
|
||||
@@ -57,9 +57,11 @@ export const IssueCommentBlock: FC<TIssueCommentBlock> = observer((props) => {
|
||||
>
|
||||
{comment.actor_detail.avatar_url && comment.actor_detail.avatar_url !== "" ? (
|
||||
<img
|
||||
src={getFileURL(comment.actor_detail.avatar_url)}
|
||||
src={getFileURL(comment.actor_detail?.avatar_url)}
|
||||
alt={
|
||||
comment.actor_detail.is_bot ? comment.actor_detail.first_name + " Bot" : comment.actor_detail.display_name
|
||||
comment.actor_detail?.is_bot
|
||||
? comment.actor_detail?.first_name + " Bot"
|
||||
: comment.actor_detail?.display_name
|
||||
}
|
||||
height={30}
|
||||
width={30}
|
||||
@@ -67,9 +69,9 @@ export const IssueCommentBlock: FC<TIssueCommentBlock> = observer((props) => {
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{comment.actor_detail.is_bot
|
||||
? comment.actor_detail.first_name.charAt(0)
|
||||
: comment.actor_detail.display_name.charAt(0)}
|
||||
{comment.actor_detail?.is_bot
|
||||
? comment.actor_detail?.first_name.charAt(0)
|
||||
: comment.actor_detail?.display_name.charAt(0)}
|
||||
</>
|
||||
)}
|
||||
<div
|
||||
@@ -93,9 +95,9 @@ export const IssueCommentBlock: FC<TIssueCommentBlock> = observer((props) => {
|
||||
<div className="w-full truncate space-y-1">
|
||||
<div>
|
||||
<div className="text-xs capitalize">
|
||||
{comment.actor_detail.is_bot
|
||||
? comment.actor_detail.first_name + " Bot"
|
||||
: comment.actor_detail.display_name}
|
||||
{comment.actor_detail?.is_bot
|
||||
? comment.actor_detail?.first_name + " Bot"
|
||||
: comment.actor_detail?.display_name}
|
||||
</div>
|
||||
<div className="text-xs text-custom-text-200">commented {calculateTimeAgo(comment.created_at)}</div>
|
||||
</div>
|
||||
|
||||
@@ -4,13 +4,15 @@ import { Controller, useForm } from "react-hook-form";
|
||||
import { EditorRefApi } from "@plane/editor";
|
||||
// plane types
|
||||
import { TSticky } from "@plane/types";
|
||||
// plane utils
|
||||
import { isCommentEmpty } from "@plane/utils";
|
||||
// hooks
|
||||
import { useWorkspace } from "@/hooks/store";
|
||||
// components
|
||||
import { StickyEditor } from "../../editor";
|
||||
|
||||
type TProps = {
|
||||
stickyData: TSticky | undefined;
|
||||
stickyData: Partial<TSticky> | undefined;
|
||||
workspaceSlug: string;
|
||||
handleUpdate: (payload: Partial<TSticky>) => void;
|
||||
stickyId: string | undefined;
|
||||
@@ -67,7 +69,11 @@ export const StickyInput = (props: TProps) => {
|
||||
onChange(description_html);
|
||||
handleSubmit(handleFormSubmit)();
|
||||
}}
|
||||
placeholder="Click to type here"
|
||||
placeholder={(_, value) => {
|
||||
const isContentEmpty = isCommentEmpty(value);
|
||||
if (!isContentEmpty) return "";
|
||||
return "Click to type here";
|
||||
}}
|
||||
containerClassName="px-0 text-base min-h-[250px] w-full"
|
||||
uploadFile={async () => ""}
|
||||
showToolbar={showToolbar}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useSticky } from "@/hooks/use-stickies";
|
||||
import { STICKY_COLORS_LIST } from "../../editor/sticky-editor/color-palette";
|
||||
import { StickyDeleteModal } from "../delete-modal";
|
||||
import { StickyInput } from "./inputs";
|
||||
import { useStickyOperations } from "./use-operations";
|
||||
import { getRandomStickyColor, useStickyOperations } from "./use-operations";
|
||||
|
||||
type TProps = {
|
||||
onClose?: () => void;
|
||||
@@ -33,7 +33,7 @@ export const StickyNote = observer((props: TProps) => {
|
||||
// sticky operations
|
||||
const { stickyOperations } = useStickyOperations({ workspaceSlug });
|
||||
// derived values
|
||||
const stickyData = stickyId ? stickies[stickyId] : undefined;
|
||||
const stickyData: Partial<TSticky> = stickyId ? stickies[stickyId] : { background_color: getRandomStickyColor() };
|
||||
// const isStickiesPage = pathName?.includes("stickies");
|
||||
const backgroundColor =
|
||||
STICKY_COLORS_LIST.find((c) => c.key === stickyData?.background_color)?.backgroundColor ||
|
||||
@@ -45,6 +45,7 @@ export const StickyNote = observer((props: TProps) => {
|
||||
await stickyOperations.update(stickyId, payload);
|
||||
} else {
|
||||
await stickyOperations.create({
|
||||
...stickyData,
|
||||
...payload,
|
||||
});
|
||||
}
|
||||
@@ -73,7 +74,7 @@ export const StickyNote = observer((props: TProps) => {
|
||||
handleClose={() => setIsDeleteModalOpen(false)}
|
||||
/>
|
||||
<div
|
||||
className={cn("w-full flex flex-col h-fit rounded p-4 group/sticky", className)}
|
||||
className={cn("w-full flex flex-col h-fit rounded p-4 group/sticky max-h-[650px] overflow-y-scroll", className)}
|
||||
style={{
|
||||
backgroundColor,
|
||||
}}
|
||||
|
||||
@@ -21,12 +21,13 @@ export class StickyService extends APIService {
|
||||
async getStickies(
|
||||
workspaceSlug: string,
|
||||
cursor: string,
|
||||
query?: string
|
||||
query?: string,
|
||||
per_page?: number
|
||||
): Promise<{ results: TSticky[]; total_pages: number }> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/stickies/`, {
|
||||
params: {
|
||||
cursor,
|
||||
per_page: STICKIES_PER_PAGE,
|
||||
per_page: per_page || STICKIES_PER_PAGE,
|
||||
query,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -96,7 +96,7 @@ export class StickyStore implements IStickyStore {
|
||||
};
|
||||
|
||||
fetchRecentSticky = async (workspaceSlug: string) => {
|
||||
const response = await this.stickyService.getStickies(workspaceSlug, "1:0:0");
|
||||
const response = await this.stickyService.getStickies(workspaceSlug, "1:0:0", undefined, 1);
|
||||
runInAction(() => {
|
||||
this.recentStickyId = response.results[0]?.id;
|
||||
this.stickies[response.results[0]?.id] = response.results[0];
|
||||
|
||||
Reference in New Issue
Block a user