Feature: Temporary Manager Chunksize and Callback (#2432)

Allow to set a chunk size and batch up uploads

Co-authored-by: Nikolaj Brask-Nielsen <nikolajgive@gmail.com>
This commit is contained in:
Lone Iversen
2024-10-09 14:10:52 +02:00
committed by GitHub
parent fb67d30bfb
commit e5a99d423c
3 changed files with 60 additions and 32 deletions

View File

@@ -1,3 +1,4 @@
export * from './temporary-file.repository.js';
export * from './components/temporary-file-badge.element.js';
export * from './temporary-file-manager.class.js';
export * from './types.js';

View File

@@ -1,21 +1,13 @@
import { UmbTemporaryFileRepository } from './temporary-file.repository.js';
import {
TemporaryFileStatus,
type UmbQueueHandlerCallback,
type UmbTemporaryFileModel,
type UmbUploadOptions,
} from './types.js';
import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api';
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
///export type TemporaryFileStatus = 'success' | 'waiting' | 'error';
export enum TemporaryFileStatus {
SUCCESS = 'success',
WAITING = 'waiting',
ERROR = 'error',
}
export interface UmbTemporaryFileModel {
file: File;
temporaryUnique: string;
status?: TemporaryFileStatus;
}
export class UmbTemporaryFileManager<
UploadableItem extends UmbTemporaryFileModel = UmbTemporaryFileModel,
> extends UmbControllerBase {
@@ -24,7 +16,7 @@ export class UmbTemporaryFileManager<
readonly #queue = new UmbArrayState<UploadableItem>([], (item) => item.temporaryUnique);
public readonly queue = this.#queue.asObservable();
async uploadOne(uploadableItem: UploadableItem): Promise<UploadableItem> {
async uploadOne(uploadableItem: UploadableItem, options?: UmbUploadOptions<UploadableItem>): Promise<UploadableItem> {
this.#queue.setValue([]);
const item: UploadableItem = {
@@ -33,15 +25,18 @@ export class UmbTemporaryFileManager<
};
this.#queue.appendOne(item);
return (await this.#handleQueue())[0];
return (await this.#handleQueue({ ...options, chunkSize: 1 }))[0];
}
async upload(queueItems: Array<UploadableItem>): Promise<Array<UploadableItem>> {
async upload(
queueItems: Array<UploadableItem>,
options?: UmbUploadOptions<UploadableItem>,
): Promise<Array<UploadableItem>> {
this.#queue.setValue([]);
const items = queueItems.map((item): UploadableItem => ({ status: TemporaryFileStatus.WAITING, ...item }));
this.#queue.append(items);
return this.#handleQueue();
return this.#handleQueue({ ...options });
}
removeOne(unique: string) {
@@ -52,30 +47,44 @@ export class UmbTemporaryFileManager<
this.#queue.remove(uniques);
}
async #handleQueue() {
async #handleQueue(options?: UmbUploadOptions<UploadableItem>): Promise<Array<UploadableItem>> {
const filesCompleted: Array<UploadableItem> = [];
const queue = this.#queue.getValue();
if (!queue.length) return filesCompleted;
for (const item of queue) {
if (!item.temporaryUnique) throw new Error(`Unique is missing for item ${item}`);
const handler: UmbQueueHandlerCallback<UploadableItem> = async (item) => {
const completedUpload = await this.#handleUpload(item);
filesCompleted.push(completedUpload);
const { error } = await this.#temporaryFileRepository.upload(item.temporaryUnique, item.file);
//await new Promise((resolve) => setTimeout(resolve, (Math.random() + 0.5) * 1000)); // simulate small delay so that the upload badge is properly shown
if (options?.callback) await options.callback(completedUpload);
};
let status: TemporaryFileStatus;
if (error) {
status = TemporaryFileStatus.ERROR;
this.#queue.updateOne(item.temporaryUnique, { ...item, status });
} else {
status = TemporaryFileStatus.SUCCESS;
this.#queue.updateOne(item.temporaryUnique, { ...item, status });
}
const chunkSize = options?.chunkSize ?? 5;
const chunks = Math.ceil(queue.length / chunkSize);
filesCompleted.push({ ...item, status });
for (let i = 0; i < chunks; i++) {
const chunk = queue.slice(i * chunkSize, i * chunkSize + chunkSize);
await Promise.all(chunk.map(handler));
}
return filesCompleted;
}
async #handleUpload(item: UploadableItem) {
if (!item.temporaryUnique) throw new Error(`Unique is missing for item ${item}`);
const { error } = await this.#temporaryFileRepository.upload(item.temporaryUnique, item.file);
let status: TemporaryFileStatus;
if (error) {
status = TemporaryFileStatus.ERROR;
this.#queue.updateOne(item.temporaryUnique, { ...item, status });
} else {
status = TemporaryFileStatus.SUCCESS;
this.#queue.updateOne(item.temporaryUnique, { ...item, status });
}
return { ...item, status };
}
}

View File

@@ -0,0 +1,18 @@
export enum TemporaryFileStatus {
SUCCESS = 'success',
WAITING = 'waiting',
ERROR = 'error',
}
export interface UmbTemporaryFileModel {
file: File;
temporaryUnique: string;
status?: TemporaryFileStatus;
}
export type UmbQueueHandlerCallback<TItem extends UmbTemporaryFileModel> = (item: TItem) => Promise<void>;
export type UmbUploadOptions<TItem extends UmbTemporaryFileModel> = {
chunkSize?: number;
callback?: UmbQueueHandlerCallback<TItem>;
};