Merge pull request #1475 from makeplane/sync/11308041808

sync: community changes
This commit is contained in:
sriram veeraghanta
2024-10-13 00:33:26 +05:30
committed by GitHub
8 changed files with 188 additions and 14 deletions

View File

@@ -21,10 +21,12 @@ from plane.db.models import (
)
from plane.settings.storage import S3Storage
from plane.app.permissions import allow_permission, ROLE
from plane.utils.cache import invalidate_cache_directly
from plane.payment.flags.flag_decorator import check_workspace_feature_flag
from plane.payment.flags.flag import FeatureFlag
class UserAssetsV2Endpoint(BaseAPIView):
"""This endpoint is used to upload user profile images."""
@@ -37,7 +39,7 @@ class UserAssetsV2Endpoint(BaseAPIView):
asset.save()
return
def entity_asset_save(self, asset_id, entity_type, asset):
def entity_asset_save(self, asset_id, entity_type, asset, request):
# User Avatar
if entity_type == FileAsset.EntityTypeContext.USER_AVATAR:
user = User.objects.get(id=asset.user_id)
@@ -48,6 +50,18 @@ 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/settings/",
url_params=False,
user=True,
request=request,
)
return
# User Cover
if entity_type == FileAsset.EntityTypeContext.USER_COVER:
@@ -59,21 +73,57 @@ 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/settings/",
url_params=False,
user=True,
request=request,
)
return
return
def entity_asset_delete(self, entity_type, asset):
def entity_asset_delete(self, entity_type, asset, request):
# User Avatar
if entity_type == FileAsset.EntityTypeContext.USER_AVATAR:
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/settings/",
url_params=False,
user=True,
request=request,
)
return
# User Cover
if entity_type == FileAsset.EntityTypeContext.USER_COVER:
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/settings/",
url_params=False,
user=True,
request=request,
)
return
return
@@ -155,7 +205,12 @@ class UserAssetsV2Endpoint(BaseAPIView):
object_name=asset.asset.name
)
# get the entity and save the asset id for the request field
self.entity_asset_save(asset_id, asset.entity_type, asset)
self.entity_asset_save(
asset_id=asset_id,
entity_type=asset.entity_type,
asset=asset,
request=request,
)
# update the attributes
asset.attributes = request.data.get("attributes", asset.attributes)
# save the asset
@@ -167,7 +222,9 @@ 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(asset.entity_type, asset)
self.entity_asset_delete(
entity_type=asset.entity_type, asset=asset, request=request
)
asset.save()
return Response(status=status.HTTP_204_NO_CONTENT)
@@ -230,7 +287,7 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
asset.save()
return
def entity_asset_save(self, asset_id, entity_type, asset):
def entity_asset_save(self, asset_id, entity_type, asset, request):
# Workspace Logo
if entity_type == FileAsset.EntityTypeContext.WORKSPACE_LOGO:
workspace = Workspace.objects.filter(id=asset.workspace_id).first()
@@ -243,6 +300,24 @@ 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/users/me/workspaces/",
url_params=False,
user=True,
request=request,
)
invalidate_cache_directly(
path="/api/instances/",
url_params=False,
user=False,
request=request,
)
return
# Project Cover
@@ -261,7 +336,7 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
else:
return
def entity_asset_delete(self, entity_type, asset):
def entity_asset_delete(self, entity_type, asset, request):
# Workspace Logo
if entity_type == FileAsset.EntityTypeContext.WORKSPACE_LOGO:
workspace = Workspace.objects.get(id=asset.workspace_id)
@@ -269,6 +344,24 @@ 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/users/me/workspaces/",
url_params=False,
user=True,
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:
@@ -342,7 +435,9 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
workspace=workspace,
created_by=request.user,
entity_type=entity_type,
**self.get_entity_id_field(entity_type, entity_identifier),
**self.get_entity_id_field(
entity_type=entity_type, entity_id=entity_identifier
),
)
# Get the presigned URL
@@ -375,7 +470,12 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
object_name=asset.asset.name
)
# get the entity and save the asset id for the request field
self.entity_asset_save(asset_id, asset.entity_type, asset)
self.entity_asset_save(
asset_id=asset_id,
entity_type=asset.entity_type,
asset=asset,
request=request,
)
# update the attributes
asset.attributes = request.data.get("attributes", asset.attributes)
# save the asset
@@ -387,7 +487,9 @@ 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(asset.entity_type, asset)
self.entity_asset_delete(
entity_type=asset.entity_type, asset=asset, request=request
)
asset.save()
return Response(status=status.HTTP_204_NO_CONTENT)
@@ -510,6 +612,11 @@ class ProjectAssetEndpoint(BaseAPIView):
return {
"comment_id": entity_id,
}
if entity_type == FileAsset.EntityTypeContext.DRAFT_ISSUE_DESCRIPTION:
return {
"draft_issue_id": entity_id,
}
return {}
@allow_permission(
@@ -719,4 +826,12 @@ class ProjectBulkAssetEndpoint(BaseAPIView):
page_id=entity_id,
)
if (
asset.entity_type
== FileAsset.EntityTypeContext.DRAFT_ISSUE_DESCRIPTION
):
assets.update(
draft_issue_id=entity_id,
)
return Response(status=status.HTTP_204_NO_CONTENT)

View File

@@ -129,7 +129,7 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
)
.annotate(
attachment_count=FileAsset.objects.filter(
entity_identifier=OuterRef("id"),
issue_id=OuterRef("id"),
entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT,
)
.order_by()

View File

@@ -0,0 +1,45 @@
# Generated by Django 4.2.15 on 2024-10-12 18:45
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("db", "0079_auto_20241009_0619"),
]
operations = [
migrations.AddField(
model_name="fileasset",
name="draft_issue",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="assets",
to="db.draftissue",
),
),
migrations.AlterField(
model_name="fileasset",
name="entity_type",
field=models.CharField(
blank=True,
choices=[
("ISSUE_ATTACHMENT", "Issue Attachment"),
("ISSUE_DESCRIPTION", "Issue Description"),
("COMMENT_DESCRIPTION", "Comment Description"),
("PAGE_DESCRIPTION", "Page Description"),
("USER_COVER", "User Cover"),
("USER_AVATAR", "User Avatar"),
("WORKSPACE_LOGO", "Workspace Logo"),
("PROJECT_COVER", "Project Cover"),
("DRAFT_ISSUE_ATTACHMENT", "Draft Issue Attachment"),
("DRAFT_ISSUE_DESCRIPTION", "Draft Issue Description"),
],
max_length=255,
null=True,
),
),
]

View File

@@ -36,6 +36,8 @@ class FileAsset(BaseModel):
USER_AVATAR = "USER_AVATAR"
WORKSPACE_LOGO = "WORKSPACE_LOGO"
PROJECT_COVER = "PROJECT_COVER"
DRAFT_ISSUE_ATTACHMENT = "DRAFT_ISSUE_ATTACHMENT"
DRAFT_ISSUE_DESCRIPTION = "DRAFT_ISSUE_DESCRIPTION"
attributes = models.JSONField(default=dict)
asset = models.FileField(
@@ -54,6 +56,12 @@ class FileAsset(BaseModel):
null=True,
related_name="assets",
)
draft_issue = models.ForeignKey(
"db.DraftIssue",
on_delete=models.CASCADE,
null=True,
related_name="assets",
)
project = models.ForeignKey(
"db.Project",
on_delete=models.CASCADE,
@@ -118,6 +126,7 @@ class FileAsset(BaseModel):
self.EntityTypeContext.ISSUE_DESCRIPTION,
self.EntityTypeContext.COMMENT_DESCRIPTION,
self.EntityTypeContext.PAGE_DESCRIPTION,
self.EntityTypeContext.DRAFT_ISSUE_DESCRIPTION,
]:
return f"/api/assets/v2/workspaces/{self.workspace.slug}/projects/{self.project_id}/{self.id}/"

View File

@@ -53,9 +53,10 @@ export enum EFileAssetType {
COMMENT_DESCRIPTION = "COMMENT_DESCRIPTION",
ISSUE_ATTACHMENT = "ISSUE_ATTACHMENT",
ISSUE_DESCRIPTION = "ISSUE_DESCRIPTION",
DRAFT_ISSUE_DESCRIPTION = "DRAFT_ISSUE_DESCRIPTION",
PAGE_DESCRIPTION = "PAGE_DESCRIPTION",
PROJECT_COVER = "PROJECT_COVER",
USER_AVATAR = "USER_AVATAR",
USER_COVER = "USER_COVER",
WORKSPACE_LOGO = "WORKSPACE_LOGO",
}
}

View File

@@ -16,7 +16,6 @@ import { useIssueModal } from "@/hooks/context/use-issue-modal";
import { useEventTracker, useCycle, useIssues, useModule, useIssueDetail, useUser } from "@/hooks/store";
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
import { useIssuesActions } from "@/hooks/use-issues-actions";
import useLocalStorage from "@/hooks/use-local-storage";
// services
import { FileService } from "@/services/file.service";
const fileService = new FileService();
@@ -168,7 +167,7 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
if (uploadedAssetIds.length > 0) {
await fileService.updateBulkProjectAssetsUploadStatus(
workspaceSlug?.toString() ?? "",
projectId,
activeProjectId ?? "",
response?.id ?? "",
{
asset_ids: uploadedAssetIds,

View File

@@ -29,6 +29,7 @@ import { FileService } from "@/services/file.service";
type TIssueDescriptionEditorProps = {
control: Control<TIssue>;
isDraft: boolean;
issueName: string;
issueId: string | undefined;
descriptionHtmlData: string | undefined;
@@ -52,6 +53,7 @@ const fileService = new FileService();
export const IssueDescriptionEditor: React.FC<TIssueDescriptionEditorProps> = observer((props) => {
const {
control,
isDraft,
issueName,
issueId,
descriptionHtmlData,
@@ -194,7 +196,9 @@ export const IssueDescriptionEditor: React.FC<TIssueDescriptionEditorProps> = ob
projectId,
{
entity_identifier: issueId ?? "",
entity_type: EFileAssetType.ISSUE_DESCRIPTION,
entity_type: isDraft
? EFileAssetType.DRAFT_ISSUE_DESCRIPTION
: EFileAssetType.ISSUE_DESCRIPTION,
},
file
);

View File

@@ -320,6 +320,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
<div className="px-5">
<IssueDescriptionEditor
control={control}
isDraft={isDraft}
issueName={watch("name")}
issueId={data?.id}
descriptionHtmlData={data?.description_html}