fix: delete and restore for dynamic urls for minio hosted images fix… (#3452)

* feat: delete and restore for dynamic urls for minio hosted images fixed in spaces

* feat: delete and restore images calls fixed for web
This commit is contained in:
M. Palanikannan 2024-01-24 18:56:19 +05:30 committed by GitHub
parent 6a2be6afc4
commit 8d3ea5bb3e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 143 additions and 36 deletions

View File

@ -14,6 +14,7 @@ import { Comment } from "types/issue";
import { LiteTextEditorWithRef } from "@plane/lite-text-editor";
// service
import fileService from "services/file.service";
import { RootStore } from "store/root";
const defaultValues: Partial<Comment> = {
comment_html: "",
@ -35,6 +36,9 @@ export const AddComment: React.FC<Props> = observer((props) => {
} = useForm<Comment>({ defaultValues });
const router = useRouter();
const { project }: RootStore = useMobxStore();
const workspaceId = project.workspace?.id;
const { workspace_slug, project_slug } = router.query as { workspace_slug: string; project_slug: string };
const { user: userStore, issueDetails: issueDetailStore } = useMobxStore();
@ -78,8 +82,8 @@ export const AddComment: React.FC<Props> = observer((props) => {
}}
cancelUploadImage={fileService.cancelUpload}
uploadFile={fileService.getUploadFileFunction(workspace_slug as string)}
deleteFile={fileService.deleteImage}
restoreFile={fileService.restoreImage}
deleteFile={fileService.getDeleteImageFunction(workspaceId as string)}
restoreFile={fileService.getRestoreImageFunction(workspaceId as string)}
ref={editorRef}
value={
!value || value === "" || (typeof value === "object" && Object.keys(value).length === 0)

View File

@ -17,6 +17,7 @@ import { Comment } from "types/issue";
import fileService from "services/file.service";
import useEditorSuggestions from "hooks/use-editor-suggestions";
import { RootStore } from "store/root";
type Props = {
workspaceSlug: string;
comment: Comment;
@ -24,6 +25,9 @@ type Props = {
export const CommentCard: React.FC<Props> = observer((props) => {
const { comment, workspaceSlug } = props;
const { project }: RootStore = useMobxStore();
const workspaceId = project.workspace?.id;
// store
const { user: userStore, issueDetails: issueDetailStore } = useMobxStore();
// states
@ -105,8 +109,8 @@ export const CommentCard: React.FC<Props> = observer((props) => {
onEnterKeyPress={handleSubmit(handleCommentUpdate)}
cancelUploadImage={fileService.cancelUpload}
uploadFile={fileService.getUploadFileFunction(workspaceSlug)}
deleteFile={fileService.deleteImage}
restoreFile={fileService.restoreImage}
deleteFile={fileService.getDeleteImageFunction(workspaceId as string)}
restoreFile={fileService.getRestoreImageFunction(workspaceId as string)}
ref={editorRef}
value={value}
debouncedUpdatesEnabled={false}

View File

@ -74,6 +74,39 @@ class FileService extends APIService {
};
}
getDeleteImageFunction(workspaceId: string) {
return async (src: string) => {
try {
const assetUrlWithWorkspaceId = `${workspaceId}/${this.extractAssetIdFromUrl(src, workspaceId)}`;
const data = await this.deleteImage(assetUrlWithWorkspaceId);
return data;
} catch (e) {
console.error(e);
}
};
}
getRestoreImageFunction(workspaceId: string) {
return async (src: string) => {
try {
const assetUrlWithWorkspaceId = `${workspaceId}/${this.extractAssetIdFromUrl(src, workspaceId)}`;
const data = await this.restoreImage(assetUrlWithWorkspaceId);
return data;
} catch (e) {
console.error(e);
}
};
}
extractAssetIdFromUrl(src: string, workspaceId: string): string {
const indexWhereAssetIdStarts = src.indexOf(workspaceId) + workspaceId.length + 1;
if (indexWhereAssetIdStarts === -1) {
throw new Error("Workspace ID not found in source string");
}
const assetUrl = src.substring(indexWhereAssetIdStarts);
return assetUrl;
}
async deleteImage(assetUrlWithWorkspaceId: string): Promise<any> {
return this.delete(`/api/workspaces/file-assets/${assetUrlWithWorkspaceId}/`)
.then((response) => response?.status)

View File

@ -54,6 +54,9 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
projectId: string;
inboxId: string;
};
const workspaceStore = useWorkspace();
const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug as string)?.id as string;
// store hooks
const { createIssue } = useInboxIssues();
const {
@ -277,8 +280,8 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
<RichTextEditorWithRef
cancelUploadImage={fileService.cancelUpload}
uploadFile={fileService.getUploadFileFunction(workspaceSlug as string)}
deleteFile={fileService.deleteImage}
restoreFile={fileService.restoreImage}
deleteFile={fileService.getDeleteImageFunction(workspaceId)}
restoreFile={fileService.getRestoreImageFunction(workspaceId)}
ref={editorRef}
debouncedUpdatesEnabled={false}
value={!value || value === "" ? "<p></p>" : value}

View File

@ -2,7 +2,7 @@ import React from "react";
import { useRouter } from "next/router";
import { useForm, Controller } from "react-hook-form";
// hooks
import { useMention } from "hooks/store";
import { useMention, useWorkspace } from "hooks/store";
// services
import { FileService } from "services/file.service";
// components
@ -51,6 +51,9 @@ export const AddComment: React.FC<Props> = ({ disabled = false, onSubmit, showAc
// router
const router = useRouter();
const { workspaceSlug } = router.query;
const workspaceStore = useWorkspace();
const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug as string)?.id as string;
// store hooks
const { mentionHighlights, mentionSuggestions } = useMention();
// form info
@ -86,8 +89,8 @@ export const AddComment: React.FC<Props> = ({ disabled = false, onSubmit, showAc
onEnterKeyPress={handleSubmit(handleAddComment)}
cancelUploadImage={fileService.cancelUpload}
uploadFile={fileService.getUploadFileFunction(workspaceSlug as string)}
deleteFile={fileService.deleteImage}
restoreFile={fileService.restoreImage}
deleteFile={fileService.getDeleteImageFunction(workspaceId)}
restoreFile={fileService.getRestoreImageFunction(workspaceId)}
ref={editorRef}
value={!commentValue || commentValue === "" ? "<p></p>" : commentValue}
customClassName="p-2 h-full"

View File

@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { observer } from "mobx-react-lite";
// hooks
import { useMention, useUser } from "hooks/store";
import { useMention, useUser, useWorkspace } from "hooks/store";
// services
import { FileService } from "services/file.service";
// icons
@ -29,6 +29,9 @@ type Props = {
export const CommentCard: React.FC<Props> = observer((props) => {
const { comment, handleCommentDeletion, onSubmit, showAccessSpecifier = false, workspaceSlug } = props;
const workspaceStore = useWorkspace();
const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug)?.id as string;
// states
const [isEditing, setIsEditing] = useState(false);
// refs
@ -102,8 +105,8 @@ export const CommentCard: React.FC<Props> = observer((props) => {
onEnterKeyPress={handleSubmit(onEnter)}
cancelUploadImage={fileService.cancelUpload}
uploadFile={fileService.getUploadFileFunction(workspaceSlug as string)}
deleteFile={fileService.deleteImage}
restoreFile={fileService.restoreImage}
deleteFile={fileService.getDeleteImageFunction(workspaceId)}
restoreFile={fileService.getRestoreImageFunction(workspaceId)}
ref={editorRef}
value={watch("comment_html") ?? ""}
debouncedUpdatesEnabled={false}

View File

@ -1,4 +1,4 @@
import { ChangeEvent, FC, useCallback, useEffect, useState } from "react";
import { ChangeEvent, FC, useCallback, useContext, useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form";
// hooks
import useReloadConfirmations from "hooks/use-reload-confirmation";
@ -11,7 +11,7 @@ import { TIssue } from "@plane/types";
import { TIssueOperations } from "./issue-detail";
// services
import { FileService } from "services/file.service";
import { useMention } from "hooks/store";
import { useMention, useWorkspace } from "hooks/store";
export interface IssueDescriptionFormValues {
name: string;
@ -38,6 +38,9 @@ const fileService = new FileService();
export const IssueDescriptionForm: FC<IssueDetailsProps> = (props) => {
const { workspaceSlug, projectId, issueId, issue, issueOperations, disabled, isSubmitting, setIsSubmitting } = props;
const workspaceStore = useWorkspace();
const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug)?.id as string;
// states
const [characterLimit, setCharacterLimit] = useState(false);
@ -172,8 +175,8 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = (props) => {
<RichTextEditor
cancelUploadImage={fileService.cancelUpload}
uploadFile={fileService.getUploadFileFunction(workspaceSlug)}
deleteFile={fileService.deleteImage}
restoreFile={fileService.restoreImage}
deleteFile={fileService.getDeleteImageFunction(workspaceId)}
restoreFile={fileService.getRestoreImageFunction(workspaceId)}
value={localIssueDescription.description_html}
rerenderOnPropsChange={localIssueDescription}
setShouldShowAlert={setShowAlert}

View File

@ -4,7 +4,7 @@ import { Controller, useForm } from "react-hook-form";
import { observer } from "mobx-react-lite";
import { Sparkle, X } from "lucide-react";
// hooks
import { useApplication, useEstimate, useMention, useProject } from "hooks/store";
import { useApplication, useEstimate, useMention, useProject, useWorkspace } from "hooks/store";
import useToast from "hooks/use-toast";
import useLocalStorage from "hooks/use-local-storage";
// services
@ -115,6 +115,9 @@ export const DraftIssueForm: FC<IssueFormProps> = observer((props) => {
// router
const router = useRouter();
const { workspaceSlug } = router.query;
const workspaceStore = useWorkspace();
const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug as string)?.id as string;
// store
const {
config: { envConfig },
@ -434,8 +437,8 @@ export const DraftIssueForm: FC<IssueFormProps> = observer((props) => {
<RichTextEditorWithRef
cancelUploadImage={fileService.cancelUpload}
uploadFile={fileService.getUploadFileFunction(workspaceSlug as string)}
deleteFile={fileService.deleteImage}
restoreFile={fileService.restoreImage}
deleteFile={fileService.getDeleteImageFunction(workspaceId)}
restoreFile={fileService.getRestoreImageFunction(workspaceId)}
ref={editorRef}
debouncedUpdatesEnabled={false}
value={

View File

@ -2,7 +2,7 @@ import { FC, useEffect, useRef, useState } from "react";
import { useForm } from "react-hook-form";
import { Check, Globe2, Lock, Pencil, Trash2, X } from "lucide-react";
// hooks
import { useIssueDetail, useMention, useUser } from "hooks/store";
import { useIssueDetail, useMention, useUser, useWorkspace } from "hooks/store";
// components
import { IssueCommentBlock } from "./comment-block";
import { LiteTextEditorWithRef, LiteReadOnlyEditorWithRef } from "@plane/lite-text-editor";
@ -40,6 +40,8 @@ export const IssueCommentCard: FC<TIssueCommentCard> = (props) => {
const [isEditing, setIsEditing] = useState(false);
const comment = getCommentById(commentId);
const workspaceStore = useWorkspace();
const workspaceId = workspaceStore.getWorkspaceBySlug(comment?.workspace_detail?.slug as string)?.id as string;
const {
formState: { isSubmitting },
@ -118,8 +120,8 @@ export const IssueCommentCard: FC<TIssueCommentCard> = (props) => {
onEnterKeyPress={handleSubmit(onEnter)}
cancelUploadImage={fileService.cancelUpload}
uploadFile={fileService.getUploadFileFunction(comment?.workspace_detail?.slug as string)}
deleteFile={fileService.deleteImage}
restoreFile={fileService.restoreImage}
deleteFile={fileService.getDeleteImageFunction(workspaceId)}
restoreFile={fileService.getRestoreImageFunction(workspaceId)}
ref={editorRef}
value={watch("comment_html") ?? ""}
debouncedUpdatesEnabled={false}

View File

@ -10,6 +10,7 @@ import { TActivityOperations } from "../root";
import { TIssueComment } from "@plane/types";
// icons
import { Globe2, Lock } from "lucide-react";
import { useWorkspace } from "hooks/store";
const fileService = new FileService();
@ -40,6 +41,9 @@ const commentAccess: commentAccessType[] = [
export const IssueCommentCreate: FC<TIssueCommentCreate> = (props) => {
const { workspaceSlug, activityOperations, disabled, showAccessSpecifier = false } = props;
const workspaceStore = useWorkspace();
const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug as string)?.id as string;
// refs
const editorRef = useRef<any>(null);
// react hook form
@ -73,8 +77,8 @@ export const IssueCommentCreate: FC<TIssueCommentCreate> = (props) => {
}}
cancelUploadImage={fileService.cancelUpload}
uploadFile={fileService.getUploadFileFunction(workspaceSlug as string)}
deleteFile={fileService.deleteImage}
restoreFile={fileService.restoreImage}
deleteFile={fileService.getDeleteImageFunction(workspaceId)}
restoreFile={fileService.getRestoreImageFunction(workspaceId)}
ref={editorRef}
value={!value ? "<p></p>" : value}
customClassName="p-2"

View File

@ -6,7 +6,7 @@ import { LayoutPanelTop, Sparkle, X } from "lucide-react";
// editor
import { RichTextEditorWithRef } from "@plane/rich-text-editor";
// hooks
import { useApplication, useEstimate, useIssueDetail, useMention, useProject } from "hooks/store";
import { useApplication, useEstimate, useIssueDetail, useMention, useProject, useWorkspace } from "hooks/store";
import useToast from "hooks/use-toast";
// services
import { AIService } from "services/ai.service";
@ -85,6 +85,9 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
// router
const router = useRouter();
const { workspaceSlug } = router.query;
const workspaceStore = useWorkspace();
const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug as string)?.id as string;
// store hooks
const {
config: { envConfig },
@ -384,8 +387,8 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
<RichTextEditorWithRef
cancelUploadImage={fileService.cancelUpload}
uploadFile={fileService.getUploadFileFunction(workspaceSlug as string)}
deleteFile={fileService.deleteImage}
restoreFile={fileService.restoreImage}
deleteFile={fileService.getDeleteImageFunction(workspaceId)}
restoreFile={fileService.getRestoreImageFunction(workspaceId)}
ref={editorRef}
debouncedUpdatesEnabled={false}
value={

View File

@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { Check, Globe2, Lock, MessageSquare, Pencil, Trash2, X } from "lucide-react";
// hooks
import { useMention } from "hooks/store";
import { useMention, useWorkspace } from "hooks/store";
// services
import { FileService } from "services/file.service";
// ui
@ -44,6 +44,9 @@ export const IssueCommentCard: React.FC<IIssueCommentCard> = (props) => {
issueCommentReactionCreate,
issueCommentReactionRemove,
} = props;
const workspaceStore = useWorkspace();
const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug)?.id as string;
// states
const [isEditing, setIsEditing] = useState(false);
// refs
@ -117,8 +120,8 @@ export const IssueCommentCard: React.FC<IIssueCommentCard> = (props) => {
onEnterKeyPress={handleSubmit(formSubmit)}
cancelUploadImage={fileService.cancelUpload}
uploadFile={fileService.getUploadFileFunction(workspaceSlug as string)}
deleteFile={fileService.deleteImage}
restoreFile={fileService.restoreImage}
deleteFile={fileService.getDeleteImageFunction(workspaceId)}
restoreFile={fileService.getRestoreImageFunction(workspaceId)}
ref={editorRef}
value={watch("comment_html") ?? ""}
debouncedUpdatesEnabled={false}

View File

@ -3,7 +3,7 @@ import { useRouter } from "next/router";
import { useForm, Controller } from "react-hook-form";
import { Globe2, Lock } from "lucide-react";
// hooks
import { useMention } from "hooks/store";
import { useMention, useWorkspace } from "hooks/store";
// services
import { FileService } from "services/file.service";
// components
@ -52,6 +52,9 @@ export const IssueCommentEditor: React.FC<IIssueCommentEditor> = (props) => {
// router
const router = useRouter();
const { workspaceSlug } = router.query;
const workspaceStore = useWorkspace();
const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug as string)?.id as string;
// store hooks
const { mentionHighlights, mentionSuggestions } = useMention();
// form info
@ -87,8 +90,8 @@ export const IssueCommentEditor: React.FC<IIssueCommentEditor> = (props) => {
onEnterKeyPress={handleSubmit(handleAddComment)}
cancelUploadImage={fileService.cancelUpload}
uploadFile={fileService.getUploadFileFunction(workspaceSlug as string)}
deleteFile={fileService.deleteImage}
restoreFile={fileService.restoreImage}
deleteFile={fileService.getDeleteImageFunction(workspaceId)}
restoreFile={fileService.getRestoreImageFunction(workspaceId)}
ref={editorRef}
value={!commentValue || commentValue === "" ? "<p></p>" : commentValue}
customClassName="p-2 h-full"

View File

@ -5,7 +5,7 @@ import { useRouter } from "next/router";
import { ReactElement, useEffect, useRef, useState } from "react";
import { Controller, useForm } from "react-hook-form";
// hooks
import { useApplication, useIssues, usePage, useUser } from "hooks/store";
import { useApplication, useIssues, usePage, useUser, useWorkspace } from "hooks/store";
import useReloadConfirmations from "hooks/use-reload-confirmation";
import useToast from "hooks/use-toast";
// services
@ -46,6 +46,9 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId, pageId } = router.query;
const workspaceStore = useWorkspace();
const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug as string)?.id as string;
// store hooks
const {
config: { envConfig },
@ -312,10 +315,10 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
last_updated_by: updated_by,
}}
uploadFile={fileService.getUploadFileFunction(workspaceSlug as string)}
deleteFile={fileService.getDeleteImageFunction(workspaceId)}
restoreFile={fileService.getRestoreImageFunction(workspaceId)}
value={pageDescription}
setShouldShowAlert={setShowAlert}
deleteFile={fileService.deleteImage}
restoreFile={fileService.restoreImage}
cancelUploadImage={fileService.cancelUpload}
ref={editorRef}
debouncedUpdatesEnabled={false}

View File

@ -78,6 +78,39 @@ export class FileService extends APIService {
};
}
getDeleteImageFunction(workspaceId: string) {
return async (src: string) => {
try {
const assetUrlWithWorkspaceId = `${workspaceId}/${this.extractAssetIdFromUrl(src, workspaceId)}`;
const data = await this.deleteImage(assetUrlWithWorkspaceId);
return data;
} catch (e) {
console.error(e);
}
};
}
getRestoreImageFunction(workspaceId: string) {
return async (src: string) => {
try {
const assetUrlWithWorkspaceId = `${workspaceId}/${this.extractAssetIdFromUrl(src, workspaceId)}`;
const data = await this.restoreImage(assetUrlWithWorkspaceId);
return data;
} catch (e) {
console.error(e);
}
};
}
extractAssetIdFromUrl(src: string, workspaceId: string): string {
const indexWhereAssetIdStarts = src.indexOf(workspaceId) + workspaceId.length + 1;
if (indexWhereAssetIdStarts === -1) {
throw new Error("Workspace ID not found in source string");
}
const assetUrl = src.substring(indexWhereAssetIdStarts);
return assetUrl;
}
async deleteImage(assetUrlWithWorkspaceId: string): Promise<any> {
return this.delete(`/api/workspaces/file-assets/${assetUrlWithWorkspaceId}/`)
.then((response) => response?.status)