[WEB-2717] chore: implemented issue attachment upload progress (#5901)

* chore: added attachment upload progress

* chore: add debounce while updating the upload status

* chore: update percentage calc logic

* chore: update debounce interval
This commit is contained in:
Aaryan Khandelwal 2024-10-29 19:22:29 +05:30 committed by GitHub
parent 538e78f135
commit c423d7d9df
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 314 additions and 194 deletions

View file

@ -1,9 +1,12 @@
import concat from "lodash/concat";
import debounce from "lodash/debounce";
import pull from "lodash/pull";
import set from "lodash/set";
import uniq from "lodash/uniq";
import update from "lodash/update";
import { action, computed, makeObservable, observable, runInAction } from "mobx";
import { computedFn } from "mobx-utils";
import { v4 as uuidv4 } from "uuid";
// types
import { TIssueAttachment, TIssueAttachmentMap, TIssueAttachmentIdMap } from "@plane/types";
// services
@ -11,7 +14,16 @@ import { IssueAttachmentService } from "@/services/issue";
import { IIssueRootStore } from "../root.store";
import { IIssueDetail } from "./root.store";
export type TAttachmentUploadStatus = {
id: string;
name: string;
progress: number;
size: number;
type: string;
};
export interface IIssueAttachmentStoreActions {
// actions
addAttachments: (issueId: string, attachments: TIssueAttachment[]) => void;
fetchAttachments: (workspaceSlug: string, projectId: string, issueId: string) => Promise<TIssueAttachment[]>;
createAttachment: (
@ -32,9 +44,11 @@ export interface IIssueAttachmentStore extends IIssueAttachmentStoreActions {
// observables
attachments: TIssueAttachmentIdMap;
attachmentMap: TIssueAttachmentMap;
attachmentsUploadStatusMap: Record<string, Record<string, TAttachmentUploadStatus>>;
// computed
issueAttachments: string[] | undefined;
// helper methods
getAttachmentsUploadStatusByIssueId: (issueId: string) => TAttachmentUploadStatus[] | undefined;
getAttachmentsByIssueId: (issueId: string) => string[] | undefined;
getAttachmentById: (attachmentId: string) => TIssueAttachment | undefined;
}
@ -43,6 +57,7 @@ export class IssueAttachmentStore implements IIssueAttachmentStore {
// observables
attachments: TIssueAttachmentIdMap = {};
attachmentMap: TIssueAttachmentMap = {};
attachmentsUploadStatusMap: Record<string, Record<string, TAttachmentUploadStatus>> = {};
// root store
rootIssueStore: IIssueRootStore;
rootIssueDetailStore: IIssueDetail;
@ -54,6 +69,7 @@ export class IssueAttachmentStore implements IIssueAttachmentStore {
// observables
attachments: observable,
attachmentMap: observable,
attachmentsUploadStatusMap: observable,
// computed
issueAttachments: computed,
// actions
@ -77,6 +93,12 @@ export class IssueAttachmentStore implements IIssueAttachmentStore {
}
// helper methods
getAttachmentsUploadStatusByIssueId = computedFn((issueId: string) => {
if (!issueId) return undefined;
const attachmentsUploadStatus = Object.values(this.attachmentsUploadStatusMap[issueId] ?? {});
return attachmentsUploadStatus ?? undefined;
});
getAttachmentsByIssueId = (issueId: string) => {
if (!issueId) return undefined;
return this.attachments[issueId] ?? undefined;
@ -104,21 +126,56 @@ export class IssueAttachmentStore implements IIssueAttachmentStore {
return response;
};
createAttachment = async (workspaceSlug: string, projectId: string, issueId: string, file: File) => {
const response = await this.issueAttachmentService.uploadIssueAttachment(workspaceSlug, projectId, issueId, file);
const issueAttachmentsCount = this.getAttachmentsByIssueId(issueId)?.length ?? 0;
debouncedUpdateProgress = debounce((issueId: string, tempId: string, progress: number) => {
runInAction(() => {
set(this.attachmentsUploadStatusMap, [issueId, tempId, "progress"], progress);
});
}, 16);
if (response && response.id) {
createAttachment = async (workspaceSlug: string, projectId: string, issueId: string, file: File) => {
const tempId = uuidv4();
try {
// update attachment upload status
runInAction(() => {
update(this.attachments, [issueId], (attachmentIds = []) => uniq(concat(attachmentIds, [response.id])));
set(this.attachmentMap, response.id, response);
this.rootIssueStore.issues.updateIssue(issueId, {
attachment_count: issueAttachmentsCount + 1, // increment attachment count
set(this.attachmentsUploadStatusMap, [issueId, tempId], {
id: tempId,
name: file.name,
progress: 0,
size: file.size,
type: file.type,
});
});
}
const response = await this.issueAttachmentService.uploadIssueAttachment(
workspaceSlug,
projectId,
issueId,
file,
(progressEvent) => {
const progressPercentage = Math.round((progressEvent.progress ?? 0) * 100);
this.debouncedUpdateProgress(issueId, tempId, progressPercentage);
}
);
const issueAttachmentsCount = this.getAttachmentsByIssueId(issueId)?.length ?? 0;
return response;
if (response && response.id) {
runInAction(() => {
update(this.attachments, [issueId], (attachmentIds = []) => uniq(concat(attachmentIds, [response.id])));
set(this.attachmentMap, response.id, response);
this.rootIssueStore.issues.updateIssue(issueId, {
attachment_count: issueAttachmentsCount + 1, // increment attachment count
});
});
}
return response;
} catch (error) {
console.error("Error in uploading issue attachment:", error);
throw error;
} finally {
runInAction(() => {
delete this.attachmentsUploadStatusMap[issueId][tempId];
});
}
};
removeAttachment = async (workspaceSlug: string, projectId: string, issueId: string, attachmentId: string) => {