@knime/product-features 2.3.1 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # @knime/product-features
2
2
 
3
+ ## 2.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 103d868: - `useFileUpload`: added `shouldUseFlatManifestPath` option to support upload targets whose manifest endpoint isn't scoped under a repository parent (e.g. environment extension DB driver uploads).
8
+ - `createUploadClient`:
9
+ - added matching `shouldUseFlatManifestPath` option.
10
+ - fixed a malformed upload-part URL (stray trailing slash before the query string).
11
+ - `createHttpClient`: fixed custom fetch options being overridden by defaults instead of the reverse.
12
+
13
+ ### Patch Changes
14
+
15
+ - Updated dependencies [d4a7a53]
16
+ - @knime/kds-styles@7.2.3
17
+ - @knime/kds-components@7.2.3
18
+
19
+ ## 2.4.0
20
+
21
+ ### Minor Changes
22
+
23
+ - fcae9ba: Add optional retry button to UploadProgressPanel footer
24
+
25
+ ### Patch Changes
26
+
27
+ - Updated dependencies [c4826f2]
28
+ - @knime/kds-components@7.2.2
29
+ - @knime/kds-styles@7.2.2
30
+
3
31
  ## 2.3.1
4
32
 
5
33
  ### Patch Changes
@@ -136,11 +136,7 @@ var defaultConfig = {
136
136
  }
137
137
  };
138
138
  var createHttpClient = (customOptions) => {
139
- const { baseURL = API_BASE_URL, ...otherCustomOptions } = customOptions ?? {};
140
- return ofetch.create(merge({
141
- baseURL,
142
- ...otherCustomOptions
143
- }, defaultConfig));
139
+ return ofetch.create(merge(structuredClone(defaultConfig), customOptions ?? {}));
144
140
  };
145
141
  //#endregion
146
142
  //#region src/dataTransfer/upload/core/uploadManager/config.ts
@@ -602,7 +598,8 @@ var createUploadClient = (options = {}) => {
602
598
  itemContentType: getFileMimeType(file),
603
599
  itemContentSize: file.size
604
600
  }])) };
605
- return $ofetch(`/repository/${parentId}/manifest`, {
601
+ const manifestPath = options.shouldUseFlatManifestPath ? "/manifest" : `/repository/${parentId}/manifest`;
602
+ return $ofetch(manifestPath, {
606
603
  method: "POST",
607
604
  body
608
605
  }).then(({ items: responseItems }) => Object.keys(responseItems).map((name) => ({
@@ -612,7 +609,7 @@ var createUploadClient = (options = {}) => {
612
609
  })));
613
610
  };
614
611
  const resolveFilePartUploadURL = (uploadId, partNumber) => {
615
- return $ofetch(`/uploads/${uploadId}/parts/?partNumber=${partNumber}`, { method: "POST" });
612
+ return $ofetch(`/uploads/${uploadId}/parts?partNumber=${partNumber}`, { method: "POST" });
616
613
  };
617
614
  const completeUpload = (uploadId, partIds) => {
618
615
  return $ofetch(`/uploads/${uploadId}`, {
@@ -736,4 +733,4 @@ function createUploadStatusPoller(uploadClient, options) {
736
733
  //#endregion
737
734
  export { createUploadManager as a, API_BASE_URL as c, createStreamingUploadManager as i, createUploadClient as n, createHttpClient as o, UploadStates as r, defaultConfig as s, createUploadStatusPoller as t };
738
735
 
739
- //# sourceMappingURL=core-CVWznjtP.js.map
736
+ //# sourceMappingURL=core-DznNUqnG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core-DznNUqnG.js","names":["#length","#chunks"],"sources":["../../../node_modules/.pnpm/es-toolkit@1.49.0/node_modules/es-toolkit/dist/predicate/isPlainObject.mjs","../../../node_modules/.pnpm/es-toolkit@1.49.0/node_modules/es-toolkit/dist/_internal/isUnsafeProperty.mjs","../../../node_modules/.pnpm/es-toolkit@1.49.0/node_modules/es-toolkit/dist/object/merge.mjs","../src/dataTransfer/shared/httpClient/constants.ts","../src/dataTransfer/shared/httpClient/createHttpClient.ts","../src/dataTransfer/upload/core/uploadManager/config.ts","../src/dataTransfer/upload/core/uploadManager/state.ts","../src/dataTransfer/upload/core/uploadManager/uploadPartBytes.ts","../src/dataTransfer/upload/core/uploadManager/chunkedUploadManager.ts","../src/dataTransfer/upload/core/uploadManager/streamingUploadManager.ts","../src/dataTransfer/upload/core/uploadManager/types.ts","../src/dataTransfer/upload/core/uploadClient/uploadClient.ts","../../../node_modules/.pnpm/es-toolkit@1.49.0/node_modules/es-toolkit/dist/array/chunk.mjs","../src/dataTransfer/upload/core/uploadClient/createUploadStatusPoller.ts"],"sourcesContent":["//#region src/predicate/isPlainObject.ts\n/**\n* Checks if a given value is a plain object.\n*\n* @param value - The value to check.\n* @returns True if the value is a plain object, otherwise false.\n*\n* @example\n* ```typescript\n* // ✅👇 True\n*\n* isPlainObject({ }); // ✅\n* isPlainObject({ key: 'value' }); // ✅\n* isPlainObject({ key: new Date() }); // ✅\n* isPlainObject(new Object()); // ✅\n* isPlainObject(Object.create(null)); // ✅\n* isPlainObject({ nested: { key: true} }); // ✅\n* isPlainObject(new Proxy({}, {})); // ✅\n* isPlainObject({ [Symbol('tag')]: 'A' }); // ✅\n*\n* // ✅👇 (cross-realms, node context, workers, ...)\n* const runInNewContext = await import('node:vm').then(\n* (mod) => mod.runInNewContext\n* );\n* isPlainObject(runInNewContext('({})')); // ✅\n*\n* // ❌👇 False\n*\n* class Test { };\n* isPlainObject(new Test()) // ❌\n* isPlainObject(10); // ❌\n* isPlainObject(null); // ❌\n* isPlainObject('hello'); // ❌\n* isPlainObject([]); // ❌\n* isPlainObject(new Date()); // ❌\n* isPlainObject(new Uint8Array([1])); // ❌\n* isPlainObject(Buffer.from('ABC')); // ❌\n* isPlainObject(Promise.resolve({})); // ❌\n* isPlainObject(Object.create({})); // ❌\n* isPlainObject(new (class Cls {})); // ❌\n* isPlainObject(globalThis); // ❌,\n* ```\n*/\nfunction isPlainObject(value) {\n\tif (!value || typeof value !== \"object\") return false;\n\tconst proto = Object.getPrototypeOf(value);\n\tif (!(proto === null || proto === Object.prototype || Object.getPrototypeOf(proto) === null)) return false;\n\treturn Object.prototype.toString.call(value) === \"[object Object]\";\n}\n//#endregion\nexport { isPlainObject };\n","//#region src/_internal/isUnsafeProperty.ts\n/**\n* Checks if a property key is unsafe to modify directly.\n*\n* This function is used in functions like `merge` to prevent prototype pollution attacks\n* by identifying property keys that could modify the object's prototype chain or constructor.\n*\n* @param key - The property key to check\n* @returns `true` if the property is unsafe to modify directly, `false` otherwise\n* @internal\n*/\nfunction isUnsafeProperty(key) {\n\treturn key === \"__proto__\";\n}\n//#endregion\nexport { isUnsafeProperty };\n","import { isPlainObject } from \"../predicate/isPlainObject.mjs\";\nimport { isUnsafeProperty } from \"../_internal/isUnsafeProperty.mjs\";\n//#region src/object/merge.ts\n/**\n* Merges the properties of the source object into the target object.\n*\n* This function performs a deep merge, meaning nested objects and arrays are merged recursively.\n* If a property in the source object is an array or an object and the corresponding property in the target object is also an array or object, they will be merged.\n* If a property in the source object is undefined, it will not overwrite a defined property in the target object.\n*\n* Note that this function mutates the target object.\n*\n* @param target - The target object into which the source object properties will be merged. This object is modified in place.\n* @param source - The source object whose properties will be merged into the target object.\n* @returns The updated target object with properties from the source object merged in.\n*\n* @template T - Type of the target object.\n* @template S - Type of the source object.\n*\n* @example\n* const target = { a: 1, b: { x: 1, y: 2 } };\n* const source = { b: { y: 3, z: 4 }, c: 5 };\n*\n* const result = merge(target, source);\n* console.log(result);\n* // Output: { a: 1, b: { x: 1, y: 3, z: 4 }, c: 5 }\n*\n* @example\n* const target = { a: [1, 2], b: { x: 1 } };\n* const source = { a: [3], b: { y: 2 } };\n*\n* const result = merge(target, source);\n* console.log(result);\n* // Output: { a: [3, 2], b: { x: 1, y: 2 } }\n*\n* @example\n* const target = { a: null };\n* const source = { a: [1, 2, 3] };\n*\n* const result = merge(target, source);\n* console.log(result);\n* // Output: { a: [1, 2, 3] }\n*/\nfunction merge(target, source) {\n\tconst sourceKeys = Object.keys(source);\n\tfor (let i = 0; i < sourceKeys.length; i++) {\n\t\tconst key = sourceKeys[i];\n\t\tif (isUnsafeProperty(key)) continue;\n\t\tconst sourceValue = source[key];\n\t\tconst targetValue = target[key];\n\t\tif (isMergeableValue(sourceValue) && isMergeableValue(targetValue)) target[key] = merge(targetValue, sourceValue);\n\t\telse if (Array.isArray(sourceValue)) target[key] = merge([], sourceValue);\n\t\telse if (isPlainObject(sourceValue)) target[key] = merge({}, sourceValue);\n\t\telse if (targetValue === void 0 || sourceValue !== void 0) target[key] = sourceValue;\n\t}\n\treturn target;\n}\nfunction isMergeableValue(value) {\n\treturn isPlainObject(value) || Array.isArray(value);\n}\n//#endregion\nexport { merge };\n","export const API_BASE_URL = \"/_/api\";\n","import { merge } from \"es-toolkit/object\";\nimport { type FetchOptions, ofetch } from \"ofetch\";\n\nimport { API_BASE_URL } from \"./constants\";\n\nconst defaultConfig: FetchOptions = {\n baseURL: API_BASE_URL,\n headers: {\n \"Content-Type\": \"application/json\",\n\n // see: https://knime-com.atlassian.net/wiki/spaces/SPECS/pages/4126769212/API+Errors#Client-Request-Headers\n Accept: \"application/json; application/problem+json\",\n },\n} as const;\n\nconst createHttpClient = (customOptions?: FetchOptions) => {\n return ofetch.create(\n merge(structuredClone(defaultConfig), customOptions ?? {}),\n );\n};\n\nexport { type FetchOptions, defaultConfig, createHttpClient };\n","/**\n * Configuration constants shared by the upload managers\n */\nexport const CONFIG = Object.freeze({\n /** Size, in bytes, of each uploaded file part. */\n CHUNK_SIZE: 50 * 1024 * 1024, // 50 MB per chunk\n /** Max number of retries for a failed request (part-url resolution or the chunk upload itself). */\n DEFAULT_RETRY_COUNT: 5,\n /** Delay, in ms, between retries. */\n DEFAULT_RETRY_DELAY_MS: 50,\n});\n","import { promise as promiseUtils } from \"@knime/utils\";\n\nimport type { UploadStateName } from \"./types\";\n\ntype UploadState = {\n uploadId: string;\n fileDescriptor?: { name: string; type: string };\n completedChunkCount: number;\n uploadedByteCount: number;\n state: UploadStateName;\n abortController: AbortController;\n};\n\nexport const createStateManager = () => {\n const uploadStateMap = new Map<string, UploadState>();\n\n const completedUploads = new Set<string>();\n const cancelledUploads = new Set<string>();\n const failedUploads = new Set<string>();\n\n const assertState = (uploadId: string, state: UploadStateName) =>\n uploadStateMap.has(uploadId) &&\n uploadStateMap.get(uploadId)!.state === state;\n\n const getUploadState = (uploadId: string) => uploadStateMap.get(uploadId);\n const setUploadState = (uploadId: string, state: Partial<UploadState>) => {\n uploadStateMap.set(uploadId, {\n ...uploadStateMap.get(uploadId)!,\n ...state,\n });\n };\n\n const isFailed = (uploadId: string) => assertState(uploadId, \"failed\");\n const isCancelled = (uploadId: string) => assertState(uploadId, \"cancelled\");\n\n const abortInFlightUpload = (uploadId: string, reason: string) => {\n const currentState = uploadStateMap.get(uploadId);\n\n if (!currentState) {\n return;\n }\n\n const message = currentState.fileDescriptor\n ? `Upload (${currentState.uploadId}) for file \"${currentState.fileDescriptor.name}\" aborted. Reason: \"${reason}\"`\n : `Upload (${currentState.uploadId}) aborted. Reason: \"${reason}\"`;\n\n currentState.abortController.abort(new promiseUtils.AbortError(message));\n };\n\n const cancel = (uploadId: string) => {\n if (!uploadStateMap.has(uploadId)) {\n consola.error(\"cancel:: Invalid upload id\");\n return;\n }\n\n setUploadState(uploadId, { state: \"cancelled\" });\n\n abortInFlightUpload(uploadId, \"upload cancelled\");\n cancelledUploads.add(uploadId);\n };\n\n const cancelAll = () => {\n for (const [id, upload] of uploadStateMap.entries()) {\n if (upload.state === \"inprogress\") {\n cancel(id);\n }\n }\n };\n\n const setCompleted = (uploadId: string, completedChunkCount: number) => {\n setUploadState(uploadId, {\n completedChunkCount,\n state: \"complete\",\n });\n completedUploads.add(uploadId);\n };\n\n const setFailed = (uploadId: string) => {\n if (!uploadStateMap.has(uploadId)) {\n consola.error(\"failed:: Invalid upload id\");\n return;\n }\n\n setUploadState(uploadId, { state: \"failed\" });\n failedUploads.add(uploadId);\n\n if (completedUploads.has(uploadId)) {\n completedUploads.delete(uploadId);\n }\n };\n\n const flush = () => {\n // state cleanup\n const completed = [...completedUploads.values()];\n const cancelled = [...cancelledUploads.values()];\n const failed = [...failedUploads.values()];\n\n [...completed, ...cancelled, ...failed].forEach((uploadId) =>\n uploadStateMap.delete(uploadId),\n );\n\n completedUploads.clear();\n cancelledUploads.clear();\n failedUploads.clear();\n\n return {\n completed,\n cancelled,\n failed,\n };\n };\n\n return {\n get: getUploadState,\n set: setUploadState,\n isFailed,\n isCancelled,\n cancel,\n cancelAll,\n setCompleted,\n setFailed,\n abortInFlightUpload,\n flush,\n };\n};\n","export type PartUploadResponse = {\n partId: string;\n};\n\n/**\n * Uploads a single file part via XHR, reporting upload progress and\n * resolving with the part's identifying ETag (or an empty string if the\n * backend doesn't return one).\n */\nexport const uploadPartBytesWithProgress = (params: {\n method: string;\n url: string;\n partBytes: Blob;\n partIndex: number;\n onProgress: (progress: number) => void;\n abortSignal: AbortSignal;\n}): Promise<PartUploadResponse> => {\n const { method, url, partBytes, partIndex, onProgress, abortSignal } = params;\n\n const OK = 200;\n const CREATED = 201;\n\n return new Promise<PartUploadResponse>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n\n xhr.open(method, url, true);\n\n abortSignal.addEventListener(\"abort\", () => xhr.abort());\n\n // Track the upload progress of the current part\n xhr.upload.onprogress = (event) => {\n if (event.lengthComputable) {\n onProgress(event.loaded);\n }\n };\n\n xhr.onload = () => {\n if (xhr.status === OK || xhr.status === CREATED) {\n const etag = xhr.getResponseHeader(\"ETag\");\n\n // 1. note that some backends don't return the etag header, then we just set an empty string\n // 2. remove possible doublequotes (\") around the etag and/or weak identifier (if either is present)\n // https://datatracker.ietf.org/doc/html/rfc7232#section-2.3\n resolve({ partId: etag?.replace(/^(?:W\\/)?\"|\"$/g, \"\") ?? \"\" });\n } else {\n consola.error(\"Failed or unexpected XHR response\", {\n xhrStatus: xhr.status,\n xhrResponse: xhr.response,\n partIndex,\n });\n reject(new Error(\"Failed to upload file\"));\n }\n };\n\n xhr.onerror = (event) => {\n consola.error(\"Failed to upload part\", {\n xhrErrorEvent: event,\n partIndex,\n });\n reject(new Error(\"Failed to upload file\"));\n };\n\n xhr.send(partBytes);\n });\n};\n","import { promise as promiseUtils } from \"@knime/utils\";\n\nimport { CONFIG } from \"./config\";\nimport { createStateManager } from \"./state\";\nimport type { BaseUploadManagerConfig } from \"./types\";\nimport { uploadPartBytesWithProgress } from \"./uploadPartBytes\";\n\nexport type ChunkUploadManagerConfig = BaseUploadManagerConfig & {\n /**\n * Callback that gets triggered when the progress of a file part upload\n * for a specific upload id is updated\n * @param uploadId\n * @param progress\n * @returns\n */\n onProgress?: (uploadId: string, progress: number) => void;\n};\n\n/**\n * Creates and returns an `uploadManager`, a comprehensive multi-file, multi-part upload handler.\n *\n * The `uploadManager` offers functionality for handling file uploads in parts and provides\n * several built-in mechanisms to ensure the process is reliable and adaptable.\n *\n * Key features include:\n *\n * - **Chunked File Uploads**: Splits provided files into manageable chunks and initiates the upload\n * for each part using the `resolveFilePartUploadURL` function provided in the configuration.\n *\n * - **Retry Mechanism**: Automatically retries failed upload attempts up to a maximum of 5 times\n * per failed request.\n *\n * - **Upload Cancellation**: Allows cancellation of ongoing uploads, giving users control over the process.\n *\n * - **Progress Tracking**: Monitors the progress of each file part upload and triggers the `onProgress`\n * callback to provide updates.\n *\n * - **Error Handling**: The returned `uploadFiles` function does not throw errors directly. Instead,\n * it manages retries and stops any failed uploads after the retry limit is reached, marking them as failed.\n * This ensures that the promise it returns completes gracefully without uncaught exceptions.\n *\n * This is the \"slice-based\" manager: it needs each file's complete bytes to already be\n * available (as a real `File`/`Blob` with a known `.size`), since it produces each part via\n * `file.slice()`. For uploading from a source whose total size isn't known upfront (e.g. a\n * stream of compressed output), see `createStreamingUploadManager` instead.\n */\nexport const createUploadManager = (config: ChunkUploadManagerConfig) => {\n const {\n resolveFilePartUploadURL: getUploadFilePartUrl,\n onFileUploadComplete,\n onFileUploadFailed,\n } = config;\n const state = createStateManager();\n\n const getTotalFileParts = (file: File) =>\n Math.ceil(file.size / CONFIG.CHUNK_SIZE);\n\n const uploadFileFromChunkIndex = async (uploadId: string, file: File) => {\n const totalChunks = getTotalFileParts(file);\n const totalBytes = file.size;\n const completedPartIds: Record<number, string> = {};\n\n const { abortController, runAbortablePromise } =\n promiseUtils.createAbortablePromise();\n\n state.set(uploadId, {\n uploadId,\n fileDescriptor: { name: file.name, type: file.type },\n state: \"inprogress\",\n uploadedByteCount: state.get(uploadId)?.uploadedByteCount ?? 0,\n completedChunkCount: state.get(uploadId)?.completedChunkCount ?? 0,\n abortController,\n });\n\n let chunkIndex = 0;\n let uploadedBytes = state.get(uploadId)?.uploadedByteCount;\n\n while (chunkIndex < totalChunks) {\n if (state.isCancelled(uploadId) || state.isFailed(uploadId)) {\n break;\n }\n\n const start = chunkIndex * CONFIG.CHUNK_SIZE;\n const end = Math.min(start + CONFIG.CHUNK_SIZE, file.size);\n\n const chunk = file.slice(start, end);\n\n try {\n const { method, url } = await promiseUtils.retryPromise({\n fn: () => getUploadFilePartUrl(uploadId, chunkIndex + 1),\n retryCount: CONFIG.DEFAULT_RETRY_COUNT,\n retryDelayMS: CONFIG.DEFAULT_RETRY_DELAY_MS,\n });\n\n consola.trace(\"File part upload url resolved\", { method, url });\n\n const onChunkUploadProgress = (uploadedChunkBytes: number) => {\n uploadedBytes = uploadedChunkBytes + start;\n state.set(uploadId, { uploadedByteCount: uploadedBytes });\n\n const progress = Math.round((uploadedBytes / totalBytes) * 100);\n\n config.onProgress?.(uploadId, progress);\n };\n\n const filePartResponse = await runAbortablePromise(() =>\n promiseUtils.retryPromise({\n fn: () =>\n uploadPartBytesWithProgress({\n method,\n url,\n partBytes: chunk,\n partIndex: chunkIndex,\n onProgress: onChunkUploadProgress,\n abortSignal: abortController.signal,\n }),\n retryCount: CONFIG.DEFAULT_RETRY_COUNT,\n retryDelayMS: CONFIG.DEFAULT_RETRY_DELAY_MS,\n }),\n );\n\n consola.trace(\n `${file.name}: Chunk ${chunkIndex + 1} of ${totalChunks} uploaded successfully`,\n );\n\n completedPartIds[chunkIndex + 1] = filePartResponse.partId;\n\n state.set(uploadId, {\n completedChunkCount: state.get(uploadId)!.completedChunkCount + 1,\n });\n } catch (_error) {\n if (_error instanceof promiseUtils.AbortError) {\n consola.info(\"Aborted::\", _error);\n return;\n }\n\n const error =\n _error instanceof Error ? _error : new Error(_error as string);\n\n state.setFailed(uploadId);\n\n onFileUploadFailed?.({ uploadId, error });\n return;\n }\n\n chunkIndex++;\n }\n\n if (state.isCancelled(uploadId) || state.isFailed(uploadId)) {\n return;\n }\n\n state.setCompleted(uploadId, totalChunks);\n onFileUploadComplete?.({ uploadId, filePartIds: completedPartIds });\n };\n\n const uploadFiles = (files: Array<{ uploadId: string; file: File }>) => {\n const result: Array<Promise<unknown>> = [];\n\n for (const { uploadId, file } of files) {\n result.push(uploadFileFromChunkIndex(uploadId, file));\n }\n\n return Promise.allSettled(result).then(() => state.flush());\n };\n\n return {\n uploadFiles,\n\n getUploadState: state.get,\n isFailed: state.isFailed,\n isCancelled: state.isCancelled,\n cancel: state.cancel,\n cancelAll: state.cancelAll,\n setFailed: state.setFailed,\n };\n};\n","import { promise as promiseUtils } from \"@knime/utils\";\n\nimport { CONFIG } from \"./config\";\nimport { createStateManager } from \"./state\";\nimport type { BaseUploadManagerConfig } from \"./types\";\nimport { uploadPartBytesWithProgress } from \"./uploadPartBytes\";\n\nexport type StreamingUploadManagerConfig = BaseUploadManagerConfig & {\n /**\n * Callback triggered as bytes are uploaded. This reports an absolute byte count rather\n * than a percentage: the total size isn't known upfront for a stream.\n */\n onProgress?: (uploadId: string, uploadedBytes: number) => void;\n};\n\n/**\n * A small FIFO byte buffer. Pushed chunks are stored by reference (no\n * copying on push).\n */\nclass ByteQueue {\n #chunks: Uint8Array[] = [];\n #length = 0;\n\n get length() {\n return this.#length;\n }\n\n push(chunk: Uint8Array) {\n if (chunk.length === 0) {\n return;\n }\n this.#chunks.push(chunk);\n this.#length += chunk.length;\n }\n\n /**\n * FIFO exactly `n` bytes from the buffer. If the first chunk has more bytes than needed,\n * it will be split and only the requested amount returned. This ensures each byte is copied\n * at most once, regardless of how many small chunks it took to accumulate them.\n * @param n\n * @returns\n */\n take(n: number): Uint8Array<ArrayBuffer> {\n const size = Math.min(n, this.#length);\n const result = new Uint8Array(size);\n let offset = 0;\n\n while (offset < size) {\n const chunk = this.#chunks[0];\n const needed = size - offset;\n\n if (chunk.length <= needed) {\n result.set(chunk, offset);\n offset += chunk.length;\n this.#chunks.shift();\n } else {\n result.set(chunk.subarray(0, needed), offset);\n this.#chunks[0] = chunk.subarray(needed);\n offset += needed;\n }\n }\n\n this.#length -= size;\n return result;\n }\n\n takeAll(): Uint8Array<ArrayBuffer> {\n return this.take(this.#length);\n }\n}\n\nconst readStreamAndUpload = async (\n state: ReturnType<typeof createStateManager>,\n uploadId: string,\n stream: ReadableStream<Uint8Array>,\n uploadPart: (bytes: Uint8Array<ArrayBuffer>) => Promise<void>,\n config: StreamingUploadManagerConfig,\n // eslint-disable-next-line max-params\n) => {\n const buffer = new ByteQueue();\n const reader = stream.getReader();\n\n const closeReader = async () => {\n try {\n await reader.cancel();\n } catch {\n // stream already closed/errored - nothing left to cancel\n } finally {\n reader.releaseLock();\n }\n };\n\n try {\n while (true) {\n if (state.isCancelled(uploadId) || state.isFailed(uploadId)) {\n return;\n }\n\n const { value, done } = await reader.read();\n\n if (value) {\n buffer.push(value);\n }\n\n while (buffer.length >= CONFIG.CHUNK_SIZE) {\n if (state.isCancelled(uploadId) || state.isFailed(uploadId)) {\n return;\n }\n\n await uploadPart(buffer.take(CONFIG.CHUNK_SIZE));\n }\n\n if (done) {\n // when stream is done, if there's still any content in the buffer\n // then take all remaining bytes and upload them\n if (\n buffer.length > 0 &&\n !state.isCancelled(uploadId) &&\n !state.isFailed(uploadId)\n ) {\n await uploadPart(buffer.takeAll());\n }\n break;\n }\n }\n } catch (_error) {\n if (_error instanceof promiseUtils.AbortError) {\n consola.info(\"Aborted::\", _error);\n return;\n }\n\n state.setFailed(uploadId);\n const error =\n _error instanceof Error ? _error : new Error(_error as string);\n\n config.onFileUploadFailed?.({ uploadId, error });\n } finally {\n await closeReader();\n }\n};\n\n/**\n * Creates and returns a `streamingUploadManager` - a multi-part upload handler\n * driven by a `ReadableStream<Uint8Array>` rather than a `File`/`Blob`.\n *\n * Unlike the chunkedUploadManager, this doesn't need to know the total size of the upload upfront.\n * Instead, it accumulates stream chunks into a buffer and uploadsa part every time\n * the buffer crosses the configured threshold for upload chunks, flushing whatever remains\n * as a final (possibly smaller) part once the stream ends.\n *\n * This bounds memory to roughly one chunk's worth of buffered bytes, plus\n * whatever the stream source itself buffers, regardless of the eventual\n * total size - useful for sources whose size can't be known in advance,\n * such as compressed output from a streaming zip encoder.\n *\n * Shares the same part-upload mechanics (retries, progress, abort) as\n * the chunkedUploadManager\n */\nexport const createStreamingUploadManager = (\n config: StreamingUploadManagerConfig,\n) => {\n const {\n resolveFilePartUploadURL: getUploadFilePartUrl,\n onFileUploadComplete,\n } = config;\n\n const state = createStateManager();\n\n const uploadStreamFromPartIndex = async (\n uploadId: string,\n stream: ReadableStream<Uint8Array>,\n ) => {\n const completedPartIds: Record<number, string> = {};\n\n const { abortController, runAbortablePromise } =\n promiseUtils.createAbortablePromise();\n\n state.set(uploadId, {\n uploadId,\n state: \"inprogress\",\n uploadedByteCount: state.get(uploadId)?.uploadedByteCount ?? 0,\n completedChunkCount: state.get(uploadId)?.completedChunkCount ?? 0,\n abortController,\n });\n\n let partIndex = 0;\n let uploadedBytes = state.get(uploadId)?.uploadedByteCount ?? 0;\n\n const uploadPart = async (bytes: Uint8Array<ArrayBuffer>) => {\n const chunk = new Blob([bytes]);\n\n const { method, url } = await promiseUtils.retryPromise({\n fn: () => getUploadFilePartUrl(uploadId, partIndex + 1),\n retryCount: CONFIG.DEFAULT_RETRY_COUNT,\n retryDelayMS: CONFIG.DEFAULT_RETRY_DELAY_MS,\n });\n\n const uploadedBeforeThisPart = uploadedBytes;\n const onChunkUploadProgress = (uploadedChunkBytes: number) => {\n uploadedBytes = uploadedBeforeThisPart + uploadedChunkBytes;\n state.set(uploadId, { uploadedByteCount: uploadedBytes });\n config.onProgress?.(uploadId, uploadedBytes);\n };\n\n const filePartResponse = await runAbortablePromise(() =>\n promiseUtils.retryPromise({\n fn: () =>\n uploadPartBytesWithProgress({\n method,\n url,\n partBytes: chunk,\n partIndex,\n onProgress: onChunkUploadProgress,\n abortSignal: abortController.signal,\n }),\n retryCount: CONFIG.DEFAULT_RETRY_COUNT,\n retryDelayMS: CONFIG.DEFAULT_RETRY_DELAY_MS,\n }),\n );\n\n completedPartIds[partIndex + 1] = filePartResponse.partId;\n uploadedBytes = uploadedBeforeThisPart + bytes.length;\n partIndex++;\n\n state.set(uploadId, {\n completedChunkCount: partIndex,\n uploadedByteCount: uploadedBytes,\n });\n };\n\n await readStreamAndUpload(state, uploadId, stream, uploadPart, config);\n\n if (state.isCancelled(uploadId) || state.isFailed(uploadId)) {\n return;\n }\n\n state.setCompleted(uploadId, state.get(uploadId)!.completedChunkCount);\n onFileUploadComplete?.({ uploadId, filePartIds: completedPartIds });\n };\n\n const uploadStreams = (\n streams: Array<{ uploadId: string; stream: ReadableStream<Uint8Array> }>,\n ) => {\n const result: Array<Promise<unknown>> = [];\n\n for (const { uploadId, stream } of streams) {\n result.push(uploadStreamFromPartIndex(uploadId, stream));\n }\n\n return Promise.allSettled(result).then(() => state.flush());\n };\n\n return {\n uploadStreams,\n\n getUploadState: state.get,\n isFailed: state.isFailed,\n isCancelled: state.isCancelled,\n cancel: state.cancel,\n cancelAll: state.cancelAll,\n setFailed: state.setFailed,\n };\n};\n","export type BaseUploadManagerConfig = {\n /**\n * Function to resolve the metadata for a request to upload a file part\n * @param uploadId\n * @param partNumber\n * @returns\n */\n resolveFilePartUploadURL: (\n uploadId: string,\n partNumber: number,\n ) => Promise<{ method: string; url: string }>;\n\n /**\n * Callback that gets triggered when the whole file (or stream in streaming mode) is uploaded.\n * @param\n * @returns\n */\n onFileUploadComplete?: (payload: {\n /**\n * Identifier of the upload for this file (must be unique)\n */\n uploadId: string;\n /**\n * Ids of all the uploaded file parts\n */\n filePartIds: Record<number, string>;\n }) => void;\n\n /**\n * Callback that gets triggered when an upload has failed.\n * @param uploadId\n * @param error the error that cause the failure if it exists\n * @returns\n */\n onFileUploadFailed?: (payload: { uploadId: string; error: Error }) => void;\n};\n\nexport const UploadStates = {\n IDLE: \"idle\",\n IN_PROGRESS: \"inprogress\",\n CANCELLED: \"cancelled\",\n FAILED: \"failed\",\n COMPLETE: \"complete\",\n} as const;\n\nexport type UploadStateName = (typeof UploadStates)[keyof typeof UploadStates];\n","import type { FetchOptions } from \"ofetch\";\n\nimport { getFileMimeType } from \"@knime/utils\";\n\nimport { createHttpClient } from \"../../../shared/httpClient\";\n\n/**\n * What `prepareUpload` actually needs to build a\n * correct manifest request. Kept structural (rather than requiring a real\n * `File`) so callers that don't have a materialized file - e.g. a streaming\n * upload whose final size isn't known yet - can pass a plain metadata object\n * instead.\n */\ntype UploadCandidate = { name: string; size: number; type: string };\n\ntype PrepareUploadItem<T extends UploadCandidate> = {\n /**\n * The manifest key: the path (relative to `parentId`) this item should be\n * created at.\n */\n path: string;\n file: T;\n};\n\ntype CreateUploadClientOptions = {\n /**\n * Options to customize the fetch client used internally to make http requests.\n * e.g: headers, baseUrl, etc\n */\n customFetchClientOptions?: FetchOptions;\n\n /**\n * Whether the manifest endpoint is flat (`/manifest`) rather than nested\n * under a repository item (`/repository/{parentId}/manifest`). Used for\n * targets whose upload endpoint isn't scoped by a repository parent,\n * e.g. environment extension DB driver uploads.\n */\n shouldUseFlatManifestPath?: boolean;\n};\n\nexport type UploadClient = ReturnType<typeof createUploadClient>;\n\n/**\n * Creates the raw upload lifecycle client:\n *\n * (1) - prepare a batch of items for upload\n * (2) - resolve a presigned URL for a given file part\n * (3) - complete an upload once all its parts are uploaded, or cancel one in progress.\n */\nexport const createUploadClient = (options: CreateUploadClientOptions = {}) => {\n const $ofetch = createHttpClient(options.customFetchClientOptions);\n\n const prepareUpload = <T extends UploadCandidate>(\n parentId: string,\n items: Array<PrepareUploadItem<T>>,\n ): Promise<Array<{ uploadId: string; file: T; path: string }>> => {\n const fileByName = new Map(\n items.map(({ path: name, file }) => [name, file]),\n );\n\n const body: {\n items: Record<\n string,\n { itemContentType: string; itemContentSize: number }\n >;\n } = {\n items: Object.fromEntries(\n items.map(({ path: name, file }) => [\n name,\n {\n itemContentType: getFileMimeType(file),\n itemContentSize: file.size,\n },\n ]),\n ),\n };\n\n type PrepareUploadResponse = {\n items: Record<string, { uploadId: string }>;\n };\n\n const manifestPath = options.shouldUseFlatManifestPath\n ? \"/manifest\"\n : `/repository/${parentId}/manifest`;\n\n return $ofetch<PrepareUploadResponse>(manifestPath, {\n method: \"POST\",\n body,\n }).then(({ items: responseItems }) =>\n Object.keys(responseItems).map((name) => ({\n uploadId: responseItems[name].uploadId,\n file: fileByName.get(name)!,\n path: name,\n })),\n );\n };\n\n const resolveFilePartUploadURL = (uploadId: string, partNumber: number) => {\n type UploadURLResponse = {\n method: string;\n url: string;\n header: { Host: string };\n };\n\n return $ofetch<UploadURLResponse>(\n `/uploads/${uploadId}/parts?partNumber=${partNumber}`,\n { method: \"POST\" },\n );\n };\n\n const completeUpload = (\n uploadId: string,\n partIds: Record<number, string>,\n ) => {\n return $ofetch<void>(`/uploads/${uploadId}`, {\n method: \"POST\",\n body: partIds,\n });\n };\n\n const cancelUpload = (uploadId: string) => {\n return $ofetch(`/uploads/${uploadId}`, {\n method: \"DELETE\",\n });\n };\n\n const getUploadStatus = (uploadIds: string[]) => {\n type UploadStatusResponse = Array<{\n uploadId: string;\n status:\n \"PREPARED\" | \"ANALYSIS_PENDING\" | \"COMPLETED\" | \"ABORTED\" | \"FAILED\";\n statusMessage: string;\n }>;\n\n return $ofetch<UploadStatusResponse>(\"/uploads/status\", {\n method: \"GET\",\n query: {\n uploadIds: uploadIds.join(\",\"),\n },\n });\n };\n\n return {\n prepareUpload,\n resolveFilePartUploadURL,\n completeUpload,\n cancelUpload,\n getUploadStatus,\n };\n};\n","//#region src/array/chunk.ts\n/**\n* Splits an array into smaller arrays of a specified length.\n*\n* This function takes an input array and divides it into multiple smaller arrays,\n* each of a specified length. If the input array cannot be evenly divided,\n* the final sub-array will contain the remaining elements.\n*\n* @template T The type of elements in the array.\n* @param arr - The array to be chunked into smaller arrays.\n* @param size - The size of each smaller array. Must be a positive integer.\n* @returns A two-dimensional array where each sub-array has a maximum length of `size`.\n* @throws {Error} Throws an error if `size` is not a positive integer.\n*\n* @example\n* // Splits an array of numbers into sub-arrays of length 2\n* chunk([1, 2, 3, 4, 5], 2);\n* // Returns: [[1, 2], [3, 4], [5]]\n*\n* @example\n* // Splits an array of strings into sub-arrays of length 3\n* chunk(['a', 'b', 'c', 'd', 'e', 'f', 'g'], 3);\n* // Returns: [['a', 'b', 'c'], ['d', 'e', 'f'], ['g']]\n*/\nfunction chunk(arr, size) {\n\tif (!Number.isInteger(size) || size <= 0) throw new Error(\"Size must be an integer greater than zero.\");\n\tconst chunkLength = Math.ceil(arr.length / size);\n\tconst result = Array(chunkLength);\n\tfor (let index = 0; index < chunkLength; index++) {\n\t\tconst start = index * size;\n\t\tconst end = start + size;\n\t\tresult[index] = arr.slice(start, end);\n\t}\n\treturn result;\n}\n//#endregion\nexport { chunk };\n","import { chunk } from \"es-toolkit/array\";\n\nimport { sleep } from \"@knime/utils\";\n\nimport type { UploadClient } from \"./uploadClient\";\n\nconst DEFAULT_POLL_INTERVAL_MS = 1500;\nconst DEFAULT_POLL_TIMEOUT = 5 * 60 * 1000; // 5 mins\nconst MAX_IDS_PER_REQUEST = 100;\n\nconst logger = () => consola.withTag(\"Upload status poller\");\n\ntype Options = {\n /** Called once a tracked upload's processing reaches a terminal, successful state. */\n onComplete: (uploadId: string) => void;\n /** Called once a tracked upload's processing reaches a terminal, failed/aborted state. */\n onFail: (uploadId: string, statusMessage: string) => void;\n /** Delay between polling requests. Defaults to 1500ms. */\n pollIntervalMs?: number;\n pollTimeout?: number;\n};\n\nexport function createUploadStatusPoller(\n uploadClient: UploadClient,\n options: Options,\n) {\n const pending = new Set<string>();\n const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n const pollTimeout = options.pollTimeout ?? DEFAULT_POLL_TIMEOUT;\n\n // Caches the currently-running poll session's promise, so a `track()` call\n // that joins an already-running session awaits the same promise as the\n // one that started it, rather than resolving as soon as it's called.\n let inFlight: Promise<void> | null = null;\n\n const poll = (): Promise<void> => {\n if (inFlight) {\n return inFlight;\n }\n\n inFlight = (async () => {\n const deadline = Date.now() + pollTimeout;\n\n while (pending.size > 0 && Date.now() < deadline) {\n try {\n const batches = chunk([...pending.values()], MAX_IDS_PER_REQUEST);\n const promises = batches.map((batch) =>\n uploadClient.getUploadStatus(batch),\n );\n\n const statuses = (await Promise.all(promises)).flat();\n\n for (const { uploadId, status, statusMessage } of statuses) {\n if (status === \"COMPLETED\") {\n pending.delete(uploadId);\n options.onComplete(uploadId);\n } else if (status === \"FAILED\" || status === \"ABORTED\") {\n pending.delete(uploadId);\n options.onFail(uploadId, statusMessage);\n }\n // PREPARED / ANALYSIS_PENDING: not yet terminal, keep polling.\n }\n\n if (pending.size === 0) {\n break;\n }\n\n await sleep(pollIntervalMs);\n } catch (error) {\n logger().error(\"Failed while polling for upload batch status\", {\n error,\n });\n\n await sleep(pollIntervalMs);\n continue;\n }\n }\n\n for (const uploadId of pending) {\n pending.delete(uploadId);\n options.onFail(\n uploadId,\n \"Timed out waiting for processing to complete\",\n );\n }\n })().finally(() => {\n inFlight = null;\n });\n\n return inFlight;\n };\n\n return {\n /**\n * Tracks an id for polling. Will start the polling requests immediately unless indicated otherwise\n * by the `startPolling` parameter.\n *\n * Adding newer ids to an already-running poller is not a problem, since new ids just\n * increase the poll batch size upon the next execution of the polling request\n *\n * @param uploadId\n * @param startPolling\n */\n track: (uploadId: string, startPolling = true) => {\n pending.add(uploadId);\n\n if (startPolling) {\n return poll();\n }\n\n return Promise.resolve();\n },\n\n stop: () => pending.clear(),\n };\n}\n"],"x_google_ignoreList":[0,1,2,12],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import { a as createUploadManager, i as createStreamingUploadManager, n as createUploadClient, r as UploadStates, t as createUploadStatusPoller } from "../core-CVWznjtP.js";
1
+ import { a as createUploadManager, i as createStreamingUploadManager, n as createUploadClient, r as UploadStates, t as createUploadStatusPoller } from "../core-DznNUqnG.js";
2
2
  export { UploadStates, createStreamingUploadManager, createUploadClient, createUploadManager, createUploadStatusPoller };
@@ -136,15 +136,22 @@
136
136
  max-height: var(--kds-dimension-component-height-20x);
137
137
  }
138
138
 
139
- .upload-panel[data-v-5b1b5ab8] {
139
+ .upload-panel[data-v-6d4ab287] {
140
140
  width: var(--kds-dimension-component-width-20x);
141
141
  max-width: var(--kds-dimension-component-width-20x);
142
142
  }
143
- .upload-panel-content[data-v-5b1b5ab8] {
144
- max-height: var(--kds-dimension-component-height-20x);
143
+ .upload-panel-content[data-v-6d4ab287] {
144
+ display: flex;
145
+ flex-direction: column;
146
+ }
147
+ .retry-bar[data-v-6d4ab287] {
148
+ display: flex;
149
+ justify-content: flex-end;
150
+ padding: var(--kds-spacing-container-0-5x) var(--kds-spacing-container-0-75x);
151
+ border-top: var(--kds-border-base-subtle);
145
152
  }
146
153
  .skeleton-container {
147
- &[data-v-5b1b5ab8] {
154
+ &[data-v-6d4ab287] {
148
155
  display: grid;
149
156
  grid-template-columns: min-content 1fr;
150
157
  gap: var(--kds-spacing-container-0-25x);
@@ -153,7 +160,7 @@
153
160
  padding: 0 var(--kds-spacing-container-1x);
154
161
  border-top: var(--kds-border-base-subtle);
155
162
  }
156
- & .skeleton-text-content[data-v-5b1b5ab8] {
163
+ & .skeleton-text-content[data-v-6d4ab287] {
157
164
  display: flex;
158
165
  flex-direction: column;
159
166
  gap: var(--kds-spacing-container-0-25x);
@@ -1,7 +1,7 @@
1
1
  import { t as _plugin_vue_export_helper_default } from "./_plugin-vue_export-helper-BOaGB7Aw.js";
2
2
  import { a as getFileExtension, c as promise_exports, l as sleep, r as formatFileSize, s as knimeFileFormats } from "./dist-DvDWkkNg.js";
3
3
  import { t as rfcErrors } from "./rfcErrors-B2gYd5aG.js";
4
- import { a as createUploadManager, c as API_BASE_URL, n as createUploadClient, o as createHttpClient, s as defaultConfig, t as createUploadStatusPoller } from "./core-CVWznjtP.js";
4
+ import { a as createUploadManager, c as API_BASE_URL, n as createUploadClient, o as createHttpClient, s as defaultConfig, t as createUploadStatusPoller } from "./core-DznNUqnG.js";
5
5
  import { Fragment, Transition, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, mergeModels, normalizeClass, normalizeStyle, openBlock, ref, renderList, renderSlot, toDisplayString, unref, useId, useModel, vShow, watch, withCtx, withDirectives } from "vue";
6
6
  import { KdsBadge, KdsButton, KdsIcon, KdsLoadingSkeleton, KdsProgressBar, vKdsTooltip } from "@knime/kds-components";
7
7
  import { FetchError } from "ofetch";
@@ -24,7 +24,7 @@ var useIconFlipper = (options) => {
24
24
  //#region src/dataTransfer/shared/ui/CollapsiblePanel/CollapsiblePanel.vue?vue&type=script&setup=true&lang.ts
25
25
  var _hoisted_1$4 = { class: "title-bar" };
26
26
  var _hoisted_2$2 = { class: "actions" };
27
- var _hoisted_3$1 = ["id"];
27
+ var _hoisted_3$2 = ["id"];
28
28
  //#endregion
29
29
  //#region src/dataTransfer/shared/ui/CollapsiblePanel/CollapsiblePanel.vue
30
30
  var CollapsiblePanel_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
@@ -77,7 +77,7 @@ var CollapsiblePanel_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/
77
77
  default: withCtx(() => [withDirectives(createElementVNode("div", {
78
78
  id: unref(panelContentId),
79
79
  class: "panel-content"
80
- }, [renderSlot(_ctx.$slots, "default", {}, void 0, true)], 8, _hoisted_3$1), [[vShow, modelValue.value]])]),
80
+ }, [renderSlot(_ctx.$slots, "default", {}, void 0, true)], 8, _hoisted_3$2), [[vShow, modelValue.value]])]),
81
81
  _: 3
82
82
  })], 2);
83
83
  };
@@ -87,7 +87,7 @@ var CollapsiblePanel_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/
87
87
  //#region src/dataTransfer/shared/ui/ProgressItem/ProgressItem.vue?vue&type=script&setup=true&lang.ts
88
88
  var _hoisted_1$3 = { class: "progress-wrapper" };
89
89
  var _hoisted_2$1 = { class: "item-info" };
90
- var _hoisted_3 = { class: "item-name" };
90
+ var _hoisted_3$1 = { class: "item-name" };
91
91
  var _hoisted_4 = { class: "title" };
92
92
  var _hoisted_5 = {
93
93
  key: 0,
@@ -113,7 +113,7 @@ var ProgressItem_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @_
113
113
  },
114
114
  setup(__props) {
115
115
  return (_ctx, _cache) => {
116
- return openBlock(), createElementBlock("div", _hoisted_1$3, [createElementVNode("div", { class: normalizeClass(["progress-item", { padded: __props.progress === void 0 }]) }, [createElementVNode("div", _hoisted_2$1, [createVNode(unref(KdsIcon), { name: __props.icon }, null, 8, ["name"]), createElementVNode("div", _hoisted_3, [withDirectives((openBlock(), createElementBlock("div", _hoisted_4, [createTextVNode(toDisplayString(__props.headline), 1)])), [[
116
+ return openBlock(), createElementBlock("div", _hoisted_1$3, [createElementVNode("div", { class: normalizeClass(["progress-item", { padded: __props.progress === void 0 }]) }, [createElementVNode("div", _hoisted_2$1, [createVNode(unref(KdsIcon), { name: __props.icon }, null, 8, ["name"]), createElementVNode("div", _hoisted_3$1, [withDirectives((openBlock(), createElementBlock("div", _hoisted_4, [createTextVNode(toDisplayString(__props.headline), 1)])), [[
117
117
  unref(vKdsTooltip),
118
118
  __props.headline,
119
119
  "truncated"
@@ -605,13 +605,21 @@ var UploadProgressPanelItem_default = /* @__PURE__ */ defineComponent({
605
605
  //#region src/dataTransfer/upload/UploadProgressPanel.vue?vue&type=script&setup=true&lang.ts
606
606
  var _hoisted_1 = { class: "upload-panel-content" };
607
607
  var _hoisted_2 = { class: "skeleton-text-content" };
608
+ var _hoisted_3 = {
609
+ key: 1,
610
+ class: "retry-bar"
611
+ };
608
612
  //#endregion
609
613
  //#region src/dataTransfer/upload/UploadProgressPanel.vue
610
614
  var UploadProgressPanel_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
611
615
  __name: "UploadProgressPanel",
612
616
  props: /*@__PURE__*/ mergeModels({
613
617
  items: {},
614
- placeholderItems: { default: 0 }
618
+ placeholderItems: { default: 0 },
619
+ canRetry: {
620
+ type: Boolean,
621
+ default: false
622
+ }
615
623
  }, {
616
624
  "expanded": {
617
625
  type: Boolean,
@@ -622,6 +630,7 @@ var UploadProgressPanel_default = /*#__PURE__*/ _plugin_vue_export_helper_defaul
622
630
  emits: /*@__PURE__*/ mergeModels([
623
631
  "cancel",
624
632
  "viewDetails",
633
+ "retry",
625
634
  "close"
626
635
  ], ["update:expanded"]),
627
636
  setup(__props, { emit: __emit }) {
@@ -637,32 +646,42 @@ var UploadProgressPanel_default = /*#__PURE__*/ _plugin_vue_export_helper_defaul
637
646
  return (_ctx, _cache) => {
638
647
  return openBlock(), createBlock(unref(CollapsiblePanel_default), {
639
648
  modelValue: expanded.value,
640
- "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => expanded.value = $event),
649
+ "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => expanded.value = $event),
641
650
  headline: headline.value,
642
651
  class: "upload-panel",
643
652
  "can-be-dismissed": !hasSomeItemInProgress.value,
644
- onClose: _cache[1] || (_cache[1] = ($event) => emit("close"))
653
+ onClose: _cache[2] || (_cache[2] = ($event) => emit("close"))
645
654
  }, {
646
- default: withCtx(() => [createElementVNode("div", _hoisted_1, [createVNode(unref(ProgressList_default), null, {
647
- default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.items, (item) => {
648
- return openBlock(), createBlock(UploadProgressPanelItem_default, {
649
- key: item.id,
650
- item,
651
- onCancel: ($event) => emit("cancel", item),
652
- onViewDetails: ($event) => emit("viewDetails", item)
653
- }, null, 8, [
654
- "item",
655
- "onCancel",
656
- "onViewDetails"
657
- ]);
658
- }), 128))]),
659
- _: 1
660
- }), __props.placeholderItems > 0 ? (openBlock(true), createElementBlock(Fragment, { key: 0 }, renderList(__props.placeholderItems, (skeleton) => {
661
- return openBlock(), createElementBlock("div", {
662
- key: skeleton,
663
- class: "skeleton-container"
664
- }, [createVNode(unref(KdsLoadingSkeleton), { variant: "icon-large" }), createElementVNode("div", _hoisted_2, [createVNode(unref(KdsLoadingSkeleton), { variant: "label" }), createVNode(unref(KdsLoadingSkeleton), { variant: "text" })])]);
665
- }), 128)) : createCommentVNode("", true)])]),
655
+ default: withCtx(() => [createElementVNode("div", _hoisted_1, [
656
+ createVNode(unref(ProgressList_default), null, {
657
+ default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.items, (item) => {
658
+ return openBlock(), createBlock(UploadProgressPanelItem_default, {
659
+ key: item.id,
660
+ item,
661
+ onCancel: ($event) => emit("cancel", item),
662
+ onViewDetails: ($event) => emit("viewDetails", item)
663
+ }, null, 8, [
664
+ "item",
665
+ "onCancel",
666
+ "onViewDetails"
667
+ ]);
668
+ }), 128))]),
669
+ _: 1
670
+ }),
671
+ __props.placeholderItems > 0 ? (openBlock(true), createElementBlock(Fragment, { key: 0 }, renderList(__props.placeholderItems, (skeleton) => {
672
+ return openBlock(), createElementBlock("div", {
673
+ key: skeleton,
674
+ class: "skeleton-container"
675
+ }, [createVNode(unref(KdsLoadingSkeleton), { variant: "icon-large" }), createElementVNode("div", _hoisted_2, [createVNode(unref(KdsLoadingSkeleton), { variant: "label" }), createVNode(unref(KdsLoadingSkeleton), { variant: "text" })])]);
676
+ }), 128)) : createCommentVNode("", true),
677
+ __props.canRetry && !hasSomeItemInProgress.value ? (openBlock(), createElementBlock("div", _hoisted_3, [createVNode(unref(KdsButton), {
678
+ size: "small",
679
+ variant: "outlined",
680
+ label: "Retry failed items",
681
+ "leading-icon": "reload",
682
+ onClick: _cache[0] || (_cache[0] = ($event) => emit("retry"))
683
+ })])) : createCommentVNode("", true)
684
+ ])]),
666
685
  _: 1
667
686
  }, 8, [
668
687
  "modelValue",
@@ -671,7 +690,7 @@ var UploadProgressPanel_default = /*#__PURE__*/ _plugin_vue_export_helper_defaul
671
690
  ]);
672
691
  };
673
692
  }
674
- }), [["__scopeId", "data-v-5b1b5ab8"]]);
693
+ }), [["__scopeId", "data-v-6d4ab287"]]);
675
694
  //#endregion
676
695
  //#region src/dataTransfer/upload/useUploadManager.ts
677
696
  /**
@@ -781,7 +800,9 @@ var getUploadSizeLimitBytes = () => DEFAULT_UPLOAD_SIZE_LIMIT_BYTES;
781
800
  *
782
801
  * Step 1:
783
802
  * First we need to prepare an upload. The items will be uploaded to a `parent` container
784
- * identified by its id. Then, for each file we want to upload, we must provide paths relative
803
+ * identified by its id. (When `shouldUseFlatManifestPath` is set, the manifest endpoint isn't
804
+ * scoped by a parent, so the `parentId` passed to `start` is unused for that request.)
805
+ * Then, for each file we want to upload, we must provide paths relative
785
806
  * to the parent that identify each file. Example:
786
807
  * ```
787
808
  * {
@@ -829,7 +850,10 @@ var getUploadSizeLimitBytes = () => DEFAULT_UPLOAD_SIZE_LIMIT_BYTES;
829
850
  * For a usage example, refer to the Docs page in storybook.
830
851
  */
831
852
  var useFileUpload = (options = {}) => {
832
- const uploadClient = createUploadClient({ customFetchClientOptions: options.customFetchClientOptions });
853
+ const uploadClient = createUploadClient({
854
+ customFetchClientOptions: options.customFetchClientOptions,
855
+ shouldUseFlatManifestPath: options.shouldUseFlatManifestPath
856
+ });
833
857
  let useUploadManagerResult = null;
834
858
  const getItemFromQueue = (uploadId) => {
835
859
  const { uploadState } = useUploadManagerResult;
@@ -1 +1 @@
1
- {"version":3,"file":"dataTransfer.js","names":[],"sources":["../src/dataTransfer/shared/ui/CollapsiblePanel/useIconFlipper.ts","../src/dataTransfer/shared/ui/CollapsiblePanel/CollapsiblePanel.vue","../src/dataTransfer/shared/ui/CollapsiblePanel/CollapsiblePanel.vue","../src/dataTransfer/shared/ui/ProgressItem/ProgressItem.vue","../src/dataTransfer/shared/ui/ProgressItem/ProgressItem.vue","../src/dataTransfer/shared/ui/ProgressList/ProgressList.vue","../src/dataTransfer/download/ui/DownloadProgressPanelItem.vue","../src/dataTransfer/download/ui/DownloadProgressPanelItem.vue","../src/dataTransfer/download/ui/DownloadProgressPanel.vue","../src/dataTransfer/download/ui/DownloadProgressPanel.vue","../src/dataTransfer/download/useDownloadArtifact.ts","../src/dataTransfer/upload/UploadProgressPanelItem.vue","../src/dataTransfer/upload/UploadProgressPanelItem.vue","../src/dataTransfer/upload/UploadProgressPanel.vue","../src/dataTransfer/upload/UploadProgressPanel.vue","../src/dataTransfer/upload/useUploadManager.ts","../src/dataTransfer/upload/useFileUpload.ts"],"sourcesContent":["import { type CSSProperties, type ComputedRef, computed } from \"vue\";\n\ntype UseIconFlipperOptions = {\n active: ComputedRef<boolean>;\n initialRotation?: `${string}deg`;\n};\n\nexport const useIconFlipper = (options: UseIconFlipperOptions) => {\n const iconFlipStyles = computed<CSSProperties>(() => {\n const declaration: Partial<CSSProperties> = {\n transition: \"transform 0.2s linear\",\n transform: \"scaleY(1)\",\n };\n\n if (options.initialRotation) {\n declaration.rotate = `${options.initialRotation}`;\n }\n\n const transform = options.active.value ? \"scaleY(-1)\" : undefined;\n\n return { ...declaration, transform };\n });\n\n return { iconFlipStyles };\n};\n","<script setup lang=\"ts\">\nimport { computed, useId } from \"vue\";\n\nimport { KdsButton } from \"@knime/kds-components\";\n\nimport type { CollapsiblePanelProps } from \"./types\";\nimport { useIconFlipper } from \"./useIconFlipper\";\n\nconst { headline, canBeDismissed = false } =\n defineProps<CollapsiblePanelProps>();\n\nconst emit = defineEmits<{\n close: [];\n}>();\n\nconst modelValue = defineModel<boolean>({ default: true });\n\nconst { iconFlipStyles } = useIconFlipper({\n active: computed(() => modelValue.value),\n initialRotation: \"180deg\",\n});\nconst panelContentId = useId();\n</script>\n\n<template>\n <div class=\"collapsible-panel\" :class=\"{ collapsed: !modelValue }\">\n <div class=\"title-bar\">\n {{ headline }}\n <div class=\"actions\">\n <KdsButton\n size=\"small\"\n variant=\"transparent\"\n leading-icon=\"chevron-up\"\n :aria-expanded=\"modelValue\"\n :aria-controls=\"panelContentId\"\n :ariaLabel=\"modelValue ? 'Collapse' : 'Expand'\"\n :style=\"iconFlipStyles\"\n @click=\"modelValue = !modelValue\"\n />\n\n <KdsButton\n v-if=\"canBeDismissed\"\n size=\"small\"\n variant=\"transparent\"\n ariaLabel=\"Close\"\n leading-icon=\"x-close\"\n @click=\"emit('close')\"\n />\n </div>\n </div>\n\n <Transition name=\"expand\">\n <div v-show=\"modelValue\" :id=\"panelContentId\" class=\"panel-content\">\n <slot />\n </div>\n </Transition>\n </div>\n</template>\n\n<style scoped>\n.collapsible-panel {\n background: var(--kds-color-surface-default);\n border-radius: var(--kds-spacing-container-0-25x);\n box-shadow: var(--kds-elevation-level-3);\n\n & .title-bar {\n display: flex;\n align-items: center;\n height: var(--kds-dimension-component-height-2-5x);\n padding: var(--kds-spacing-container-0-5x)\n var(--kds-spacing-container-0-75x);\n font: var(--kds-font-base-title-small-strong);\n color: var(--kds-color-text-and-icon-neutral);\n\n & .actions {\n display: flex;\n gap: var(--kds-spacing-container-0-25x);\n align-items: center;\n margin-left: auto;\n }\n }\n\n &:not(.collapsed) {\n & .title-bar {\n border-bottom: var(--kds-border-base-subtle);\n }\n }\n}\n\n.panel-content {\n overflow: hidden;\n}\n\n.expand-enter-active,\n.expand-leave-active {\n display: grid;\n overflow: hidden;\n transition:\n grid-template-rows 0.2s ease-in-out,\n opacity 0.2s ease-in-out;\n}\n\n.expand-enter-from,\n.expand-leave-to {\n grid-template-rows: 0fr;\n opacity: 0;\n}\n\n.expand-enter-to,\n.expand-leave-from {\n grid-template-rows: 1fr;\n opacity: 1;\n}\n\n.panel-content > :deep(*) {\n min-height: 0;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, useId } from \"vue\";\n\nimport { KdsButton } from \"@knime/kds-components\";\n\nimport type { CollapsiblePanelProps } from \"./types\";\nimport { useIconFlipper } from \"./useIconFlipper\";\n\nconst { headline, canBeDismissed = false } =\n defineProps<CollapsiblePanelProps>();\n\nconst emit = defineEmits<{\n close: [];\n}>();\n\nconst modelValue = defineModel<boolean>({ default: true });\n\nconst { iconFlipStyles } = useIconFlipper({\n active: computed(() => modelValue.value),\n initialRotation: \"180deg\",\n});\nconst panelContentId = useId();\n</script>\n\n<template>\n <div class=\"collapsible-panel\" :class=\"{ collapsed: !modelValue }\">\n <div class=\"title-bar\">\n {{ headline }}\n <div class=\"actions\">\n <KdsButton\n size=\"small\"\n variant=\"transparent\"\n leading-icon=\"chevron-up\"\n :aria-expanded=\"modelValue\"\n :aria-controls=\"panelContentId\"\n :ariaLabel=\"modelValue ? 'Collapse' : 'Expand'\"\n :style=\"iconFlipStyles\"\n @click=\"modelValue = !modelValue\"\n />\n\n <KdsButton\n v-if=\"canBeDismissed\"\n size=\"small\"\n variant=\"transparent\"\n ariaLabel=\"Close\"\n leading-icon=\"x-close\"\n @click=\"emit('close')\"\n />\n </div>\n </div>\n\n <Transition name=\"expand\">\n <div v-show=\"modelValue\" :id=\"panelContentId\" class=\"panel-content\">\n <slot />\n </div>\n </Transition>\n </div>\n</template>\n\n<style scoped>\n.collapsible-panel {\n background: var(--kds-color-surface-default);\n border-radius: var(--kds-spacing-container-0-25x);\n box-shadow: var(--kds-elevation-level-3);\n\n & .title-bar {\n display: flex;\n align-items: center;\n height: var(--kds-dimension-component-height-2-5x);\n padding: var(--kds-spacing-container-0-5x)\n var(--kds-spacing-container-0-75x);\n font: var(--kds-font-base-title-small-strong);\n color: var(--kds-color-text-and-icon-neutral);\n\n & .actions {\n display: flex;\n gap: var(--kds-spacing-container-0-25x);\n align-items: center;\n margin-left: auto;\n }\n }\n\n &:not(.collapsed) {\n & .title-bar {\n border-bottom: var(--kds-border-base-subtle);\n }\n }\n}\n\n.panel-content {\n overflow: hidden;\n}\n\n.expand-enter-active,\n.expand-leave-active {\n display: grid;\n overflow: hidden;\n transition:\n grid-template-rows 0.2s ease-in-out,\n opacity 0.2s ease-in-out;\n}\n\n.expand-enter-from,\n.expand-leave-to {\n grid-template-rows: 0fr;\n opacity: 0;\n}\n\n.expand-enter-to,\n.expand-leave-from {\n grid-template-rows: 1fr;\n opacity: 1;\n}\n\n.panel-content > :deep(*) {\n min-height: 0;\n}\n</style>\n","<script lang=\"ts\" setup>\nimport {\n KdsBadge,\n KdsButton,\n KdsIcon,\n KdsProgressBar,\n vKdsTooltip,\n} from \"@knime/kds-components\";\n\nimport { type ProgressItemProps } from \"./types\";\n\ndefineProps<ProgressItemProps>();\n</script>\n\n<template>\n <div class=\"progress-wrapper\">\n <div :class=\"['progress-item', { padded: progress === undefined }]\">\n <div class=\"item-info\">\n <KdsIcon :name=\"icon\" />\n\n <div class=\"item-name\">\n <div v-kds-tooltip:truncated=\"headline\" class=\"title\">\n {{ headline }}\n </div>\n <span v-if=\"subtitle\" class=\"subtitle\">\n {{ subtitle }}\n </span>\n </div>\n </div>\n\n <div class=\"item-action\">\n <KdsBadge\n v-if=\"badge\"\n :variant=\"badge.variant\"\n :label=\"badge.text\"\n :leading-icon=\"badge.icon\"\n size=\"xxsmall\"\n />\n\n <template v-for=\"action in actions.slice(0, 2)\" :key=\"action.id\">\n <KdsButton\n :leading-icon=\"action.icon\"\n :ariaLabel=\"action.label\"\n variant=\"transparent\"\n @click=\"action.handler\"\n />\n </template>\n </div>\n </div>\n\n <div v-if=\"typeof progress === 'number'\" class=\"progress\">\n <KdsProgressBar :percentage=\"progress\" size=\"medium\" />\n </div>\n </div>\n</template>\n\n<style scoped>\n.progress-wrapper {\n --base-padding: var(--kds-spacing-container-1x);\n --progress-bar-height: var(--kds-dimension-component-height-0-25x);\n --progress-bar-distance: var(--kds-spacing-container-0-25x);\n\n display: flex;\n flex-direction: column;\n gap: var(--progress-bar-distance);\n height: var(--kds-dimension-component-height-4x);\n padding: var(--base-padding);\n}\n\n.progress-item {\n display: flex;\n align-items: center;\n width: 100%;\n height: 100%;\n\n /* add extra margin to prevent content jump when a progress bars appears/disappears */\n &.padded {\n margin-bottom: calc(\n var(--progress-bar-height) + var(--progress-bar-distance)\n );\n }\n}\n\n.progress {\n min-height: var(--progress-bar-height);\n}\n\n.item-info {\n display: flex;\n flex-grow: 1;\n gap: var(--kds-spacing-container-0-25x);\n overflow: hidden;\n\n & .item-name {\n display: flex;\n flex-grow: 1;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-25x);\n justify-content: center;\n overflow: hidden;\n color: var(--kds-color-text-and-icon-neutral);\n\n & .title {\n max-width: 100%;\n overflow: hidden;\n text-overflow: ellipsis;\n font: var(--kds-font-base-interactive-small);\n white-space: nowrap;\n }\n\n & .subtitle {\n font: var(--kds-font-base-subtext-small);\n }\n }\n}\n\n.item-action {\n display: flex;\n flex-shrink: 0;\n gap: var(--kds-spacing-container-0-25x);\n align-items: center;\n}\n</style>\n","<script lang=\"ts\" setup>\nimport {\n KdsBadge,\n KdsButton,\n KdsIcon,\n KdsProgressBar,\n vKdsTooltip,\n} from \"@knime/kds-components\";\n\nimport { type ProgressItemProps } from \"./types\";\n\ndefineProps<ProgressItemProps>();\n</script>\n\n<template>\n <div class=\"progress-wrapper\">\n <div :class=\"['progress-item', { padded: progress === undefined }]\">\n <div class=\"item-info\">\n <KdsIcon :name=\"icon\" />\n\n <div class=\"item-name\">\n <div v-kds-tooltip:truncated=\"headline\" class=\"title\">\n {{ headline }}\n </div>\n <span v-if=\"subtitle\" class=\"subtitle\">\n {{ subtitle }}\n </span>\n </div>\n </div>\n\n <div class=\"item-action\">\n <KdsBadge\n v-if=\"badge\"\n :variant=\"badge.variant\"\n :label=\"badge.text\"\n :leading-icon=\"badge.icon\"\n size=\"xxsmall\"\n />\n\n <template v-for=\"action in actions.slice(0, 2)\" :key=\"action.id\">\n <KdsButton\n :leading-icon=\"action.icon\"\n :ariaLabel=\"action.label\"\n variant=\"transparent\"\n @click=\"action.handler\"\n />\n </template>\n </div>\n </div>\n\n <div v-if=\"typeof progress === 'number'\" class=\"progress\">\n <KdsProgressBar :percentage=\"progress\" size=\"medium\" />\n </div>\n </div>\n</template>\n\n<style scoped>\n.progress-wrapper {\n --base-padding: var(--kds-spacing-container-1x);\n --progress-bar-height: var(--kds-dimension-component-height-0-25x);\n --progress-bar-distance: var(--kds-spacing-container-0-25x);\n\n display: flex;\n flex-direction: column;\n gap: var(--progress-bar-distance);\n height: var(--kds-dimension-component-height-4x);\n padding: var(--base-padding);\n}\n\n.progress-item {\n display: flex;\n align-items: center;\n width: 100%;\n height: 100%;\n\n /* add extra margin to prevent content jump when a progress bars appears/disappears */\n &.padded {\n margin-bottom: calc(\n var(--progress-bar-height) + var(--progress-bar-distance)\n );\n }\n}\n\n.progress {\n min-height: var(--progress-bar-height);\n}\n\n.item-info {\n display: flex;\n flex-grow: 1;\n gap: var(--kds-spacing-container-0-25x);\n overflow: hidden;\n\n & .item-name {\n display: flex;\n flex-grow: 1;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-25x);\n justify-content: center;\n overflow: hidden;\n color: var(--kds-color-text-and-icon-neutral);\n\n & .title {\n max-width: 100%;\n overflow: hidden;\n text-overflow: ellipsis;\n font: var(--kds-font-base-interactive-small);\n white-space: nowrap;\n }\n\n & .subtitle {\n font: var(--kds-font-base-subtext-small);\n }\n }\n}\n\n.item-action {\n display: flex;\n flex-shrink: 0;\n gap: var(--kds-spacing-container-0-25x);\n align-items: center;\n}\n</style>\n","<template>\n <div class=\"progress-list\" tabindex=\"0\">\n <slot />\n </div>\n</template>\n\n<style scoped>\n.progress-list {\n max-height: var(--kds-dimension-component-height-20x);\n overflow-y: auto;\n\n &:deep(.progress-wrapper):not(:first-child) {\n border-top: var(--kds-border-base-subtle);\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nimport type { KdsBadgeVariant, KdsIconName } from \"@knime/kds-components\";\n\nimport {\n ProgressItem,\n type ProgressItemAction,\n type ProgressItemBadge,\n} from \"../../shared/ui\";\n\nimport type { DownloadItem } from \"./types\";\n\ntype Props = {\n item: DownloadItem;\n};\n\nconst props = defineProps<Props>();\n\nconst emit = defineEmits<{\n cancel: [];\n download: [];\n}>();\n\nconst statusMapper = {\n IN_PROGRESS: [\"Zipping\", \"info\", \"reload\"],\n READY: [\"Ready\", \"success\", \"circle-info\"],\n CANCELLED: [\"Cancelled\", \"error\", \"circle-error\"],\n FAILED: [\"Failed\", \"error\", \"circle-error\"],\n} satisfies Record<\n DownloadItem[\"status\"],\n [string, KdsBadgeVariant, KdsIconName]\n>;\n\nconst badge = computed<ProgressItemBadge>(() => {\n const [text, variant, icon] = statusMapper[props.item.status];\n\n return {\n text,\n variant,\n icon,\n } satisfies ProgressItemBadge;\n});\n\nconst isCancellable = computed(() => props.item.status === \"IN_PROGRESS\");\nconst isReady = computed(() => props.item.status === \"READY\");\n\nconst actions = computed<Array<ProgressItemAction>>(() => {\n const actions: ProgressItemAction[] = [];\n\n if (isCancellable.value) {\n actions.push({\n id: \"cancel\",\n icon: \"x-close\",\n label: \"Cancel\",\n handler: () => emit(\"cancel\"),\n });\n }\n\n if (isReady.value) {\n actions.push({\n id: \"download\",\n icon: \"cloud-download\",\n label: \"Download again\",\n handler: () => emit(\"download\"),\n });\n }\n\n return actions;\n});\n</script>\n\n<template>\n <ProgressItem\n :id=\"item.downloadId\"\n icon=\"file\"\n :headline=\"item.name\"\n :badge=\"badge\"\n :actions=\"actions\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nimport type { KdsBadgeVariant, KdsIconName } from \"@knime/kds-components\";\n\nimport {\n ProgressItem,\n type ProgressItemAction,\n type ProgressItemBadge,\n} from \"../../shared/ui\";\n\nimport type { DownloadItem } from \"./types\";\n\ntype Props = {\n item: DownloadItem;\n};\n\nconst props = defineProps<Props>();\n\nconst emit = defineEmits<{\n cancel: [];\n download: [];\n}>();\n\nconst statusMapper = {\n IN_PROGRESS: [\"Zipping\", \"info\", \"reload\"],\n READY: [\"Ready\", \"success\", \"circle-info\"],\n CANCELLED: [\"Cancelled\", \"error\", \"circle-error\"],\n FAILED: [\"Failed\", \"error\", \"circle-error\"],\n} satisfies Record<\n DownloadItem[\"status\"],\n [string, KdsBadgeVariant, KdsIconName]\n>;\n\nconst badge = computed<ProgressItemBadge>(() => {\n const [text, variant, icon] = statusMapper[props.item.status];\n\n return {\n text,\n variant,\n icon,\n } satisfies ProgressItemBadge;\n});\n\nconst isCancellable = computed(() => props.item.status === \"IN_PROGRESS\");\nconst isReady = computed(() => props.item.status === \"READY\");\n\nconst actions = computed<Array<ProgressItemAction>>(() => {\n const actions: ProgressItemAction[] = [];\n\n if (isCancellable.value) {\n actions.push({\n id: \"cancel\",\n icon: \"x-close\",\n label: \"Cancel\",\n handler: () => emit(\"cancel\"),\n });\n }\n\n if (isReady.value) {\n actions.push({\n id: \"download\",\n icon: \"cloud-download\",\n label: \"Download again\",\n handler: () => emit(\"download\"),\n });\n }\n\n return actions;\n});\n</script>\n\n<template>\n <ProgressItem\n :id=\"item.downloadId\"\n icon=\"file\"\n :headline=\"item.name\"\n :badge=\"badge\"\n :actions=\"actions\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport { computed, watch } from \"vue\";\n\nimport { CollapsiblePanel, ProgressList } from \"../../shared/ui\";\n\nimport DownloadProgressPanelItem from \"./DownloadProgressPanelItem.vue\";\nimport type { DownloadItem } from \"./types\";\n\ntype Props = {\n /**\n * List of current download items that should be displayed\n */\n items: DownloadItem[];\n};\nconst props = defineProps<Props>();\n\nconst emit = defineEmits<{\n cancel: [item: DownloadItem];\n download: [item: DownloadItem];\n close: [];\n}>();\n\nconst expanded = defineModel<boolean>(\"expanded\", { default: true });\n\nconst totalItems = computed(() => props.items.length);\nconst totalDownloadedItems = computed(\n () => props.items.filter(({ status }) => status === \"READY\").length,\n);\n\nconst headline = computed(() => {\n const suffix = `file${totalItems.value > 1 ? \"s\" : \"\"}`;\n return `Downloaded ${totalDownloadedItems.value} of ${totalItems.value} ${suffix}`;\n});\n\nconst hasItemsInProgress = computed(() =>\n props.items.some(({ status }) => status === \"IN_PROGRESS\"),\n);\n\nwatch(\n () => props.items?.length,\n (newValue, oldValue) => {\n if (oldValue > 0 && !newValue) {\n emit(\"close\");\n }\n },\n);\n</script>\n\n<template>\n <CollapsiblePanel\n v-model=\"expanded\"\n :headline=\"headline\"\n class=\"download-panel\"\n :can-be-dismissed=\"!hasItemsInProgress\"\n @close=\"emit('close')\"\n >\n <div class=\"download-panel-content\">\n <ProgressList>\n <DownloadProgressPanelItem\n v-for=\"item in items\"\n :key=\"item.downloadId\"\n :item=\"item\"\n @cancel=\"emit('cancel', item)\"\n @download=\"emit('download', item)\"\n />\n </ProgressList>\n </div>\n </CollapsiblePanel>\n</template>\n\n<style scoped>\n.download-panel {\n width: var(--kds-dimension-component-width-20x);\n max-width: var(--kds-dimension-component-width-20x);\n}\n\n.download-panel-content {\n max-height: var(--kds-dimension-component-height-20x);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, watch } from \"vue\";\n\nimport { CollapsiblePanel, ProgressList } from \"../../shared/ui\";\n\nimport DownloadProgressPanelItem from \"./DownloadProgressPanelItem.vue\";\nimport type { DownloadItem } from \"./types\";\n\ntype Props = {\n /**\n * List of current download items that should be displayed\n */\n items: DownloadItem[];\n};\nconst props = defineProps<Props>();\n\nconst emit = defineEmits<{\n cancel: [item: DownloadItem];\n download: [item: DownloadItem];\n close: [];\n}>();\n\nconst expanded = defineModel<boolean>(\"expanded\", { default: true });\n\nconst totalItems = computed(() => props.items.length);\nconst totalDownloadedItems = computed(\n () => props.items.filter(({ status }) => status === \"READY\").length,\n);\n\nconst headline = computed(() => {\n const suffix = `file${totalItems.value > 1 ? \"s\" : \"\"}`;\n return `Downloaded ${totalDownloadedItems.value} of ${totalItems.value} ${suffix}`;\n});\n\nconst hasItemsInProgress = computed(() =>\n props.items.some(({ status }) => status === \"IN_PROGRESS\"),\n);\n\nwatch(\n () => props.items?.length,\n (newValue, oldValue) => {\n if (oldValue > 0 && !newValue) {\n emit(\"close\");\n }\n },\n);\n</script>\n\n<template>\n <CollapsiblePanel\n v-model=\"expanded\"\n :headline=\"headline\"\n class=\"download-panel\"\n :can-be-dismissed=\"!hasItemsInProgress\"\n @close=\"emit('close')\"\n >\n <div class=\"download-panel-content\">\n <ProgressList>\n <DownloadProgressPanelItem\n v-for=\"item in items\"\n :key=\"item.downloadId\"\n :item=\"item\"\n @cancel=\"emit('cancel', item)\"\n @download=\"emit('download', item)\"\n />\n </ProgressList>\n </div>\n </CollapsiblePanel>\n</template>\n\n<style scoped>\n.download-panel {\n width: var(--kds-dimension-component-width-20x);\n max-width: var(--kds-dimension-component-width-20x);\n}\n\n.download-panel-content {\n max-height: var(--kds-dimension-component-height-20x);\n}\n</style>\n","import { computed, ref } from \"vue\";\nimport { FetchError, type FetchOptions } from \"ofetch\";\n\nimport { sleep } from \"@knime/utils\";\n\nimport { rfcErrors } from \"../../rfcErrors\";\nimport { createHttpClient } from \"../shared/httpClient\";\n\nimport type { DownloadItem as DownloadItemExternal } from \"./ui/types\";\n\nconst DEFAULT_MAX_RETRIES = null;\nconst DEFAULT_POLLING_INTERVAL = 2000;\n\ntype ArtifactStatusResponse = {\n downloadId?: string;\n status?: string;\n statusMessage?: string;\n lastUpdated?: string;\n downloadUrl?: string;\n};\n\n// Initial request can have either download url (if it can be downloaded immediately) or download id (to poll for the status)\ntype RequestDownloadResponse =\n | {\n downloadId: string;\n }\n | {\n downloadUrl: string;\n };\n\n// type guard\nconst hasDownloadUrl = (\n response: RequestDownloadResponse,\n): response is { downloadUrl: string } => {\n return \"downloadUrl\" in response;\n};\n\n/**\n * The version of the artifact to download.\n */\ntype ItemVersion = number | \"current-state\" | \"most-recent\";\n\n/**\n * Tracks the state of each concurrent download.\n */\ntype DownloadItem = DownloadItemExternal & {\n itemId: string;\n version?: ItemVersion;\n};\n\nexport type UseDownloadArtifactOptions = {\n /**\n * The maximum number of times to poll for the download URL. Default is `null` (no limit).\n */\n maxRetries?: number;\n /**\n * The interval in milliseconds between each poll. Default is `2000` ms.\n */\n pollingInterval?: number;\n /**\n * Options to customize the fetch client used internally to make http requests.\n * e.g: headers, baseUrl, etc\n */\n customFetchClientOptions?: FetchOptions;\n};\n\nconst isAbortError = (error: unknown) =>\n error instanceof DOMException && error.name === \"AbortError\";\n\n/**\n * This composable supports multiple concurrent downloads. Each time you call ```start```, the following behavior applies:\n *\n * 1. **Immediate-ready artifact**: If the first status check for the artifact returns `READY`, then there's no polling and no need\n * to add it to `downloadItems`. The download URL is opened in the same tab immediately.\n *\n * 2. **Not-ready artifact**: If the artifact is not yet ready (e.g. needs zipping), then we add a record to `downloadItems` with status `IN_PROGRESS`\n * and repeatedly poll until it becomes `READY`, the polling times out, or the user aborts.\n *\n * **Usage**:\n * ```\n * const { start, cancel, downloadItems } = useDownloadArtifact({ ...options });\n *\n * // start a download\n * await start({ itemId: \"1234\", version: 1 });\n *\n * // if needed, cancel it:\n * cancel(\"1234\");\n *\n * // downloadItems (as a computed array) will reflect the current statuses.\n * ```\n */\nexport const useDownloadArtifact = (\n options: UseDownloadArtifactOptions = {},\n) => {\n const $ofetch = createHttpClient(options.customFetchClientOptions);\n\n const downloadItems = ref<Record<string, DownloadItem>>({});\n const abortControllers: Record<string, AbortController> = {};\n\n // Use fallback values if none are provided\n const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;\n const pollingInterval = options.pollingInterval ?? DEFAULT_POLLING_INTERVAL;\n\n const cancel = (downloadId: string) => {\n const itemController = abortControllers[downloadId];\n if (itemController) {\n itemController.abort();\n delete abortControllers[downloadId];\n }\n };\n\n const removeItem = (downloadId: string) => {\n cancel(downloadId);\n delete downloadItems.value[downloadId];\n };\n\n const fetchDownloadStatus = ({\n downloadId,\n signal,\n }: {\n downloadId: string;\n signal?: AbortSignal;\n }) => {\n return $ofetch<ArtifactStatusResponse>(`/downloads/${downloadId}/status`, {\n method: \"GET\",\n signal,\n });\n };\n\n const openDownload = (downloadUrl: string) => {\n const a = document.createElement(\"a\");\n a.target = \"_blank\";\n a.href = downloadUrl;\n a.download = \"\";\n a.style.display = \"none\";\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n };\n\n /**\n * Makes a request to the download status endpoint for the given download item.\n * Returns a boolean that is true if a terminal state has been reached (download is ready,\n * download failed, or user aborted download)\n * @param downloadId the `downloadId`\n * @param signal the abort signal passed to the request to be able to abort it\n * @returns a boolean indicating whether a terminal state has been reached\n */\n const handlePollingResult = async ({\n downloadId,\n signal,\n }: {\n downloadId: string;\n signal: AbortSignal;\n }) => {\n try {\n const statusResponse = await fetchDownloadStatus({\n downloadId,\n signal,\n });\n\n if (statusResponse?.status === \"READY\" && statusResponse?.downloadUrl) {\n downloadItems.value[downloadId].status = \"READY\";\n downloadItems.value[downloadId].downloadUrl =\n statusResponse.downloadUrl;\n // open download in a new tab\n openDownload(statusResponse.downloadUrl);\n return true;\n } else if (statusResponse?.status === \"FAILED\") {\n let details = \"Download failed\";\n if (statusResponse?.statusMessage) {\n details += `: ${statusResponse.statusMessage}`;\n }\n throw new Error(details);\n }\n } catch (error: unknown) {\n consola.error(\"Error fetching status:\", error);\n // https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort\n const isAborted =\n isAbortError(error) ||\n (error instanceof FetchError && isAbortError(error.cause));\n\n if (!isAborted) {\n downloadItems.value[downloadId].status = \"FAILED\";\n downloadItems.value[downloadId].failureDetails =\n error instanceof Error ? error.message : (error as string);\n }\n return true;\n }\n return false;\n };\n\n const pollDownloadItem = async ({\n itemId,\n downloadId,\n version,\n name,\n }: {\n itemId: string;\n downloadId: string;\n version?: ItemVersion;\n name: string;\n }) => {\n const controller = new AbortController();\n abortControllers[downloadId] = controller;\n\n const signal = controller.signal;\n\n let itemName = `${name}`;\n if (version) {\n itemName += ` (${version})`;\n }\n\n downloadItems.value[downloadId] = {\n itemId,\n ...(version && { version }),\n downloadId,\n name: itemName,\n status: \"IN_PROGRESS\",\n };\n\n const abortListener = () => {\n controller.signal.removeEventListener(\"abort\", abortListener);\n if (!downloadItems.value[downloadId]) {\n return;\n }\n downloadItems.value[downloadId].status = \"CANCELLED\";\n downloadItems.value[downloadId].failureDetails =\n \"Download was cancelled manually\";\n };\n controller.signal.addEventListener(\"abort\", abortListener);\n\n let tries = 0;\n // eslint-disable-next-line no-unmodified-loop-condition\n while (maxRetries === null || tries < maxRetries) {\n tries++;\n await sleep(pollingInterval);\n\n const isTerminalState = await handlePollingResult({ downloadId, signal });\n\n if (isTerminalState) {\n break;\n }\n }\n\n delete abortControllers[downloadId];\n\n if (maxRetries !== null && tries >= maxRetries) {\n downloadItems.value[downloadId].status = \"FAILED\";\n downloadItems.value[downloadId].failureDetails = \"Download timed out\";\n }\n };\n\n const start = async ({\n itemId,\n version,\n name,\n }: {\n itemId: string;\n name: string;\n version?: ItemVersion;\n }) => {\n try {\n const response = await $ofetch<RequestDownloadResponse>(\n `/repository/${itemId}/artifact`,\n {\n method: \"GET\",\n query: { version },\n },\n );\n\n // if download is ready, open url immediately\n if (hasDownloadUrl(response)) {\n openDownload(response.downloadUrl);\n return;\n }\n\n // otherwise start polling download status\n await pollDownloadItem({\n itemId,\n version,\n downloadId: response.downloadId,\n name,\n });\n } catch (error: unknown) {\n // here only errors thrown by the initial requests are caught;\n // errors occurring while polling will be handled in pollDownloadItem\n throw rfcErrors.tryParse(error) ?? error;\n }\n };\n\n const totalItemsBeingZipped = computed(\n () =>\n Object.values(downloadItems.value).filter(\n ({ status }) => status === \"IN_PROGRESS\",\n ).length,\n );\n\n const resetState = () => {\n for (const [downloadId, controller] of Object.entries(abortControllers)) {\n controller?.abort();\n delete abortControllers[downloadId];\n }\n downloadItems.value = {};\n };\n\n const downloadItemsArray = computed(() => Object.values(downloadItems.value));\n\n return {\n /**\n * Initiates a download of the given itemId and version.\n */\n start,\n\n /**\n * Cancels the ongoing download for a given itemId (if any).\n * Sets the status in `downloadItems` to `CANCELLED`.\n */\n cancel,\n\n /**\n * Removes item from `downloadItems`.\n */\n removeItem,\n\n /**\n * A computed array of all current items that are being zipped and prepared for download.\n */\n downloadItems: downloadItemsArray,\n\n /**\n * The total number of zipping items in progress.\n */\n totalItemsBeingZipped,\n\n /**\n * Resets the state of the composable, clearing all downloads and controllers.\n */\n resetState,\n\n /**\n * Opens a download from the given url\n */\n openDownload,\n };\n};\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nimport type { KdsBadgeVariant, KdsIconName } from \"@knime/kds-components\";\nimport {\n formatFileSize,\n getFileExtension,\n knimeFileFormats,\n} from \"@knime/utils\";\n\nimport {\n ProgressItem,\n type ProgressItemAction,\n type ProgressItemBadge,\n} from \"../shared/ui\";\n\nimport type { UploadItem, UploadItemStatus } from \"./types\";\n\ntype Props = {\n item: UploadItem;\n};\n\nconst props = defineProps<Props>();\n\nconst emit = defineEmits<{\n cancel: [];\n viewDetails: [];\n}>();\n\nconst subtitle = computed(() => {\n if (props.item.type === \"file\") {\n const progressed = formatFileSize(\n (props.item.size / 100) * (props.item.progress ?? 0),\n );\n\n const total = formatFileSize(props.item.size);\n return `${progressed} of ${total}`;\n }\n\n if (props.item.type === \"directory\") {\n const total = props.item.totalEntries;\n const progressed = props.item.uploadedEntries;\n\n return `${progressed} of ${total} file${total > 1 ? \"s\" : \"\"}`;\n }\n\n return \"\";\n});\n\nconst progressPercentageValue = computed(() => {\n if (props.item.type === \"file\") {\n return props.item.progress;\n }\n\n if (props.item.type === \"directory\") {\n // The KdsProgressBar only accepts percentages, but for directory items\n // the consumer supplies different values so we must derive the percentage\n const progressPercentage =\n (props.item.uploadedEntries * 100) / props.item.totalEntries;\n\n return progressPercentage;\n }\n\n return undefined;\n});\n\nconst statusMapper = {\n inprogress: [\"Uploading\", \"info\", \"reload\"],\n failed: [\"Failed\", \"error\", \"circle-error\"],\n complete: [\"Uploaded\", \"success\", \"circle-success\"],\n cancelled: [\"Cancelled\", \"error\", \"circle-error\"],\n processing: [\"Processing\", \"info\", \"reload\"],\n \"partial-success\": [\"Partial success\", \"warning\", \"warning\"],\n} satisfies Record<UploadItemStatus, [string, KdsBadgeVariant, KdsIconName]>;\n\nconst itemIcon = computed<KdsIconName>(() => {\n if (props.item.type === \"directory\") {\n return \"folder\";\n }\n\n const extension = getFileExtension(props.item.name);\n return extension === knimeFileFormats.KNWF.extension ? \"workflow\" : \"file\";\n});\n\nconst badge = computed<ProgressItemBadge | undefined>(() => {\n if (!props.item.status) {\n return undefined;\n }\n\n const [text, variant, icon] = statusMapper[props.item.status];\n\n return { text, variant, icon } satisfies ProgressItemBadge;\n});\n\nconst isCancellable = computed(() => {\n if (\n props.item.actions === undefined ||\n props.item.actions.cancellable === undefined\n ) {\n return true;\n }\n\n return props.item.actions.cancellable;\n});\n\nconst shouldShowCancelAction = computed(\n () => props.item.status === \"inprogress\" && isCancellable.value,\n);\n\nconst isInspectable = computed(() => {\n if (\n props.item.actions === undefined ||\n props.item.actions.inspectable === undefined\n ) {\n return false;\n }\n\n return props.item.actions.inspectable;\n});\n\nconst shouldShowViewDetailsAction = computed(\n () => props.item.status === \"partial-success\" && isInspectable.value,\n);\n\nconst actions = computed<Array<ProgressItemAction>>(() => {\n const result: ProgressItemAction[] = [];\n\n if (shouldShowCancelAction.value) {\n result.push({\n id: \"cancel\",\n icon: \"x-close\",\n label: \"Cancel\",\n handler: () => emit(\"cancel\"),\n });\n }\n\n if (shouldShowViewDetailsAction.value) {\n result.push({\n id: \"view-details\",\n icon: \"circle-info\",\n label: \"View details\",\n handler: () => emit(\"viewDetails\"),\n });\n }\n\n return result;\n});\n</script>\n\n<template>\n <ProgressItem\n :id=\"item.id\"\n :headline=\"item.name\"\n :subtitle=\"subtitle\"\n :icon=\"itemIcon\"\n :progress=\"progressPercentageValue\"\n :badge=\"badge\"\n :actions=\"actions\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nimport type { KdsBadgeVariant, KdsIconName } from \"@knime/kds-components\";\nimport {\n formatFileSize,\n getFileExtension,\n knimeFileFormats,\n} from \"@knime/utils\";\n\nimport {\n ProgressItem,\n type ProgressItemAction,\n type ProgressItemBadge,\n} from \"../shared/ui\";\n\nimport type { UploadItem, UploadItemStatus } from \"./types\";\n\ntype Props = {\n item: UploadItem;\n};\n\nconst props = defineProps<Props>();\n\nconst emit = defineEmits<{\n cancel: [];\n viewDetails: [];\n}>();\n\nconst subtitle = computed(() => {\n if (props.item.type === \"file\") {\n const progressed = formatFileSize(\n (props.item.size / 100) * (props.item.progress ?? 0),\n );\n\n const total = formatFileSize(props.item.size);\n return `${progressed} of ${total}`;\n }\n\n if (props.item.type === \"directory\") {\n const total = props.item.totalEntries;\n const progressed = props.item.uploadedEntries;\n\n return `${progressed} of ${total} file${total > 1 ? \"s\" : \"\"}`;\n }\n\n return \"\";\n});\n\nconst progressPercentageValue = computed(() => {\n if (props.item.type === \"file\") {\n return props.item.progress;\n }\n\n if (props.item.type === \"directory\") {\n // The KdsProgressBar only accepts percentages, but for directory items\n // the consumer supplies different values so we must derive the percentage\n const progressPercentage =\n (props.item.uploadedEntries * 100) / props.item.totalEntries;\n\n return progressPercentage;\n }\n\n return undefined;\n});\n\nconst statusMapper = {\n inprogress: [\"Uploading\", \"info\", \"reload\"],\n failed: [\"Failed\", \"error\", \"circle-error\"],\n complete: [\"Uploaded\", \"success\", \"circle-success\"],\n cancelled: [\"Cancelled\", \"error\", \"circle-error\"],\n processing: [\"Processing\", \"info\", \"reload\"],\n \"partial-success\": [\"Partial success\", \"warning\", \"warning\"],\n} satisfies Record<UploadItemStatus, [string, KdsBadgeVariant, KdsIconName]>;\n\nconst itemIcon = computed<KdsIconName>(() => {\n if (props.item.type === \"directory\") {\n return \"folder\";\n }\n\n const extension = getFileExtension(props.item.name);\n return extension === knimeFileFormats.KNWF.extension ? \"workflow\" : \"file\";\n});\n\nconst badge = computed<ProgressItemBadge | undefined>(() => {\n if (!props.item.status) {\n return undefined;\n }\n\n const [text, variant, icon] = statusMapper[props.item.status];\n\n return { text, variant, icon } satisfies ProgressItemBadge;\n});\n\nconst isCancellable = computed(() => {\n if (\n props.item.actions === undefined ||\n props.item.actions.cancellable === undefined\n ) {\n return true;\n }\n\n return props.item.actions.cancellable;\n});\n\nconst shouldShowCancelAction = computed(\n () => props.item.status === \"inprogress\" && isCancellable.value,\n);\n\nconst isInspectable = computed(() => {\n if (\n props.item.actions === undefined ||\n props.item.actions.inspectable === undefined\n ) {\n return false;\n }\n\n return props.item.actions.inspectable;\n});\n\nconst shouldShowViewDetailsAction = computed(\n () => props.item.status === \"partial-success\" && isInspectable.value,\n);\n\nconst actions = computed<Array<ProgressItemAction>>(() => {\n const result: ProgressItemAction[] = [];\n\n if (shouldShowCancelAction.value) {\n result.push({\n id: \"cancel\",\n icon: \"x-close\",\n label: \"Cancel\",\n handler: () => emit(\"cancel\"),\n });\n }\n\n if (shouldShowViewDetailsAction.value) {\n result.push({\n id: \"view-details\",\n icon: \"circle-info\",\n label: \"View details\",\n handler: () => emit(\"viewDetails\"),\n });\n }\n\n return result;\n});\n</script>\n\n<template>\n <ProgressItem\n :id=\"item.id\"\n :headline=\"item.name\"\n :subtitle=\"subtitle\"\n :icon=\"itemIcon\"\n :progress=\"progressPercentageValue\"\n :badge=\"badge\"\n :actions=\"actions\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nimport { KdsLoadingSkeleton } from \"@knime/kds-components\";\n\nimport { CollapsiblePanel, ProgressList } from \"../shared/ui\";\n\nimport UploadProgressPanelItem from \"./UploadProgressPanelItem.vue\";\nimport type { UploadItem, UploadProgressPanelProps } from \"./types\";\n\nconst { items, placeholderItems = 0 } = defineProps<UploadProgressPanelProps>();\n\nconst emit = defineEmits<{\n cancel: [item: UploadItem];\n viewDetails: [item: UploadItem];\n close: [];\n}>();\n\nconst expanded = defineModel<boolean>(\"expanded\", { default: true });\n\nconst totalItems = computed(() => items.length);\nconst uploadedItems = computed(\n () => items.filter(({ status }) => status === \"complete\").length,\n);\n\nconst headline = computed(() => {\n const suffix = `item${totalItems.value > 1 ? \"s\" : \"\"}`;\n return `Uploaded ${uploadedItems.value} of ${totalItems.value} ${suffix}`;\n});\n\nconst hasSomeItemInProgress = computed(\n () =>\n items.some(\n ({ status }) => status === \"inprogress\" || status === \"processing\",\n ) || placeholderItems > 0,\n);\n</script>\n\n<template>\n <CollapsiblePanel\n v-model=\"expanded\"\n :headline=\"headline\"\n class=\"upload-panel\"\n :can-be-dismissed=\"!hasSomeItemInProgress\"\n @close=\"emit('close')\"\n >\n <div class=\"upload-panel-content\">\n <ProgressList>\n <UploadProgressPanelItem\n v-for=\"item in items\"\n :key=\"item.id\"\n :item=\"item\"\n @cancel=\"emit('cancel', item)\"\n @view-details=\"emit('viewDetails', item)\"\n />\n </ProgressList>\n\n <template v-if=\"placeholderItems > 0\">\n <div\n v-for=\"skeleton in placeholderItems\"\n :key=\"skeleton\"\n class=\"skeleton-container\"\n >\n <KdsLoadingSkeleton variant=\"icon-large\" />\n <div class=\"skeleton-text-content\">\n <KdsLoadingSkeleton variant=\"label\" />\n <KdsLoadingSkeleton variant=\"text\" />\n </div>\n </div>\n </template>\n </div>\n </CollapsiblePanel>\n</template>\n\n<style scoped>\n.upload-panel {\n width: var(--kds-dimension-component-width-20x);\n max-width: var(--kds-dimension-component-width-20x);\n}\n\n.upload-panel-content {\n max-height: var(--kds-dimension-component-height-20x);\n}\n\n.skeleton-container {\n display: grid;\n grid-template-columns: min-content 1fr;\n gap: var(--kds-spacing-container-0-25x);\n align-items: center;\n min-height: var(--kds-dimension-component-height-4x);\n padding: 0 var(--kds-spacing-container-1x);\n border-top: var(--kds-border-base-subtle);\n\n & .skeleton-text-content {\n display: flex;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-25x);\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nimport { KdsLoadingSkeleton } from \"@knime/kds-components\";\n\nimport { CollapsiblePanel, ProgressList } from \"../shared/ui\";\n\nimport UploadProgressPanelItem from \"./UploadProgressPanelItem.vue\";\nimport type { UploadItem, UploadProgressPanelProps } from \"./types\";\n\nconst { items, placeholderItems = 0 } = defineProps<UploadProgressPanelProps>();\n\nconst emit = defineEmits<{\n cancel: [item: UploadItem];\n viewDetails: [item: UploadItem];\n close: [];\n}>();\n\nconst expanded = defineModel<boolean>(\"expanded\", { default: true });\n\nconst totalItems = computed(() => items.length);\nconst uploadedItems = computed(\n () => items.filter(({ status }) => status === \"complete\").length,\n);\n\nconst headline = computed(() => {\n const suffix = `item${totalItems.value > 1 ? \"s\" : \"\"}`;\n return `Uploaded ${uploadedItems.value} of ${totalItems.value} ${suffix}`;\n});\n\nconst hasSomeItemInProgress = computed(\n () =>\n items.some(\n ({ status }) => status === \"inprogress\" || status === \"processing\",\n ) || placeholderItems > 0,\n);\n</script>\n\n<template>\n <CollapsiblePanel\n v-model=\"expanded\"\n :headline=\"headline\"\n class=\"upload-panel\"\n :can-be-dismissed=\"!hasSomeItemInProgress\"\n @close=\"emit('close')\"\n >\n <div class=\"upload-panel-content\">\n <ProgressList>\n <UploadProgressPanelItem\n v-for=\"item in items\"\n :key=\"item.id\"\n :item=\"item\"\n @cancel=\"emit('cancel', item)\"\n @view-details=\"emit('viewDetails', item)\"\n />\n </ProgressList>\n\n <template v-if=\"placeholderItems > 0\">\n <div\n v-for=\"skeleton in placeholderItems\"\n :key=\"skeleton\"\n class=\"skeleton-container\"\n >\n <KdsLoadingSkeleton variant=\"icon-large\" />\n <div class=\"skeleton-text-content\">\n <KdsLoadingSkeleton variant=\"label\" />\n <KdsLoadingSkeleton variant=\"text\" />\n </div>\n </div>\n </template>\n </div>\n </CollapsiblePanel>\n</template>\n\n<style scoped>\n.upload-panel {\n width: var(--kds-dimension-component-width-20x);\n max-width: var(--kds-dimension-component-width-20x);\n}\n\n.upload-panel-content {\n max-height: var(--kds-dimension-component-height-20x);\n}\n\n.skeleton-container {\n display: grid;\n grid-template-columns: min-content 1fr;\n gap: var(--kds-spacing-container-0-25x);\n align-items: center;\n min-height: var(--kds-dimension-component-height-4x);\n padding: 0 var(--kds-spacing-container-1x);\n border-top: var(--kds-border-base-subtle);\n\n & .skeleton-text-content {\n display: flex;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-25x);\n }\n}\n</style>\n","import { computed, ref } from \"vue\";\n\nimport { type ChunkUploadManagerConfig, createUploadManager } from \"../core\";\n\nimport type { FileUploadItem, UploadItem } from \"./types\";\n\ntype OnCompletePayload = Parameters<\n NonNullable<ChunkUploadManagerConfig[\"onFileUploadComplete\"]>\n>[0] & { parentId: string; name: string };\n\ntype OnFailedPayload = Parameters<\n NonNullable<ChunkUploadManagerConfig[\"onFileUploadFailed\"]>\n>[0] & { parentId: string };\n\ntype UseUploadManagerOptions = Omit<\n ChunkUploadManagerConfig,\n \"onProgress\" | \"onFileUploadComplete\" | \"onFileUploadFailed\"\n> & {\n onFileUploadComplete?: (payload: OnCompletePayload) => void;\n onFileUploadFailed?: (payload: OnFailedPayload) => void;\n /**\n * Determines the status to assign a file once its raw upload finishes,\n * before calling `onFileUploadComplete`. This helps for files that need further\n * server-side processing\n *\n * Defaults to `complete`\n */\n resolveCompleteStatus?: (\n payload: OnCompletePayload,\n ) => \"complete\" | \"processing\";\n};\n\n/**\n * This composable wraps the `uploadManager` utility\n * in order to provide reactive state which will work tightly with the `UploadProgressPanel` component\n *\n * The reactive state returned (`uploadItems`) will update whenever the progress\n * or status of each item being uploaded changes\n */\nexport const useUploadManager = (options: UseUploadManagerOptions) => {\n const { resolveCompleteStatus, ...restOptions } = options;\n const uploadState = ref<Record<string, UploadItem>>({});\n\n const { uploadFiles, cancel, setFailed } = createUploadManager({\n ...restOptions,\n\n onFileUploadComplete: (params) => {\n const { parentId, name } = uploadState.value[params.uploadId];\n const payload = { ...params, parentId, name };\n uploadState.value[params.uploadId].status =\n resolveCompleteStatus?.(payload) ?? \"complete\";\n options.onFileUploadComplete?.(payload);\n },\n\n onFileUploadFailed: ({ uploadId, error }) => {\n const { parentId } = uploadState.value[uploadId];\n uploadState.value[uploadId] = {\n ...uploadState.value[uploadId],\n status: \"failed\",\n failureDetails: error.message,\n };\n options.onFileUploadFailed?.({ uploadId, error, parentId });\n },\n\n onProgress: (uploadId, progress) => {\n if (uploadState.value[uploadId].status === \"inprogress\") {\n (uploadState.value[uploadId] as FileUploadItem).progress = progress;\n }\n },\n });\n\n const start = async (\n parentId: string,\n uploadPayload: Array<{ uploadId: string; file: File }>,\n ) => {\n try {\n uploadPayload.forEach(({ uploadId, file }) => {\n uploadState.value[uploadId] = {\n id: uploadId,\n name: file.name,\n size: file.size,\n progress: 0,\n type: \"file\",\n status: \"inprogress\",\n parentId,\n };\n });\n\n return await uploadFiles(uploadPayload);\n } catch (error) {\n consola.error(\"Error uploading files\", error);\n return [];\n }\n };\n\n const canCancel = (uploadId: string) =>\n uploadState.value[uploadId]?.status === \"inprogress\" ||\n uploadState.value[uploadId]?.status === \"failed\";\n\n const removeItem = (uploadId: string) => {\n if (canCancel(uploadId)) {\n cancel(uploadId);\n }\n\n delete uploadState.value[uploadId];\n };\n\n const resetState = () => {\n uploadState.value = {};\n };\n\n const uploadItems = computed(() => Object.values(uploadState.value));\n\n const hasPendingUploads = computed(() =>\n uploadItems.value.some(({ status }) => status === \"inprogress\"),\n );\n\n const totalPendingUploads = computed(\n () =>\n uploadItems.value.filter(({ status }) => status === \"inprogress\").length,\n );\n\n return {\n uploadState,\n hasPendingUploads,\n totalPendingUploads,\n start,\n cancel: (uploadId: string) => {\n cancel(uploadId);\n uploadState.value[uploadId].status = \"cancelled\";\n },\n canCancel,\n removeItem,\n resetState,\n setFailed: (uploadId: string, error: Error) => {\n uploadState.value[uploadId] = {\n ...uploadState.value[uploadId],\n status: \"failed\",\n failureDetails: error.message,\n };\n setFailed(uploadId);\n const { parentId } = uploadState.value[uploadId];\n options.onFileUploadFailed?.({ parentId, uploadId, error });\n },\n };\n};\n","import { computed, ref } from \"vue\";\nimport type { FetchError, FetchOptions } from \"ofetch\";\n\nimport { formatFileSize, knimeFileFormats, promise } from \"@knime/utils\";\n\nimport { rfcErrors } from \"../../rfcErrors\";\nimport { createUploadClient, createUploadStatusPoller } from \"../core\";\n\nimport type { UploadItem } from \"./types\";\nimport { useUploadManager } from \"./useUploadManager\";\n\nconst DEFAULT_MAX_UPLOAD_QUEUE_SIZE = 10;\n// eslint-disable-next-line no-magic-numbers\nconst DEFAULT_UPLOAD_SIZE_LIMIT_BYTES = 5 * 1024 * 1024 * 1024;\n\nconst DEFAULT_RETRY_DELAY_MS = 50;\n\n// Catalog & artifacts services currently have a limit for 5 GiB for\n// upload and copy/move operations but it is not communicated to clients.\n// Repeat the constant here to be able to still make a pre-check. See NXT-4510.\nconst getUploadSizeLimitBytes = () => DEFAULT_UPLOAD_SIZE_LIMIT_BYTES;\n\ntype UseFileUploadOptions = {\n /**\n * Max number of concurrent uploads allowed in the upload queue.\n * When an upload is started with a number of files that exceed the pending\n * uploads then only files up-to the max queue size will be processed and the rest\n * will be ignored. This will also call the `onUploadQueueSizeExceeded` callback\n */\n maxUploadQueueSize?: number;\n /**\n * Callback that triggers every time any file in the queue completes its upload\n * @returns\n */\n onFileUploadComplete?: (payload: {\n uploadId: string;\n filePartIds: Record<number, string>;\n parentId: string;\n name: string;\n }) => void;\n /**\n * Callback that triggers every time any file in the queue fails its upload\n * @returns\n */\n onFileUploadFailed?: (payload: {\n uploadId: string;\n error: Error;\n parentId: string;\n }) => void;\n /**\n * Callback that triggers when a upload batch is started (via the `start` function) but the amount\n * of items to upload exceed the max configured size of the queue.\n * @returns\n */\n onUploadQueueSizeExceeded?: (maxQueueSize: number) => void;\n /**\n * Options to customize the fetch client used internally to make http requests.\n * e.g: headers, baseUrl, etc\n */\n customFetchClientOptions?: FetchOptions;\n\n isFileWithProcessing?: (fileName: string) => boolean;\n};\n\n/**\n * This composable handles uploads of data to a hub repository item (space or workflow group).\n * It is a multi-step process which is described as follows:\n *\n * Step 1:\n * First we need to prepare an upload. The items will be uploaded to a `parent` container\n * identified by its id. Then, for each file we want to upload, we must provide paths relative\n * to the parent that identify each file. Example:\n * ```\n * {\n * \"items\": {\n * \"my-file.txt\": { itemContentType: \"text/plain\", itemContentSize: 5000 },\n * \"folder/my-file.zip\": { itemContentType: \"application/zip\", itemContentSize: 5000 },\n * }\n * }\n * ```\n * This describes 2 items, one placed in the parent's root and another inside a folder.\n * The response for this would look like:\n * ```\n * {\n * \"items\": {\n * \"my-file.txt\": { uploadId: \"id1\" },\n * \"folder/my-file.zip\": { uploadId: \"id2\" },\n * }\n * }\n * ```\n * Now, each corresponding `uploadId` uniquely identifies the upload process for\n * their respective files.\n *\n * Step 2:\n * Next, we need to start uploading the file parts. Technically, you _can_ just upload\n * each file as one big file \"part\", but practically that is not recommended because a hiccup\n * in the connection means you would have to start the upload from scratch; instead, we split\n * the files in smaller parts.\n * The upload manager helper will take care of splitting the file. However, the responsibility\n * of this composable is to supply a function to resolve the upload URL. This upload URL is obtained\n * by POSTing to an endpoint with the `uploadId` and the `partNumber` and this will return\n * the data required to make an upload to a presigned URL; this will be called for each file part.\n *\n * Step 3:\n * Completion:\n * Once the upload of a single file is complete, the upload manager will trigger the `onFileUploadComplete`\n * callback, which then will supply the ids of all the different parts that got uploaded for a given `uploadId`\n * Then we need to \"complete\" the upload by sending a request which provides the `uploadId` and a list of\n * the ids of all the parts. Note that some backends don't return the ids of the file parts after a successful\n * upload of a part, in this case empty strings or null can be used instead of the part id.\n *\n * Cancellation:\n * Alternative to completion, you can also cancel an upload. For this, the composable makes a DELETE request\n * providing the corresponding `uploadId` which will effectively cancel it.\n *\n * For a usage example, refer to the Docs page in storybook.\n */\nexport const useFileUpload = (options: UseFileUploadOptions = {}) => {\n const uploadClient = createUploadClient({\n customFetchClientOptions: options.customFetchClientOptions,\n });\n\n let useUploadManagerResult: ReturnType<typeof useUploadManager> | null = null;\n\n const getItemFromQueue = (uploadId: string) => {\n const { uploadState } = useUploadManagerResult!;\n\n if (!uploadState.value[uploadId]) {\n consola.warn(\n `Cannot get item in queue. Upload id \"${uploadId}\" doesn't exist`,\n );\n return undefined;\n }\n\n return uploadState.value[uploadId];\n };\n\n const updateItemInQueue = (uploadId: string, data: Partial<UploadItem>) => {\n const { uploadState } = useUploadManagerResult!;\n\n if (!uploadState.value[uploadId]) {\n consola.warn(\n `Cannot update item in queue. Upload id \"${uploadId}\" doesn't exist`,\n );\n return;\n }\n\n uploadState.value = {\n ...uploadState.value,\n [uploadId]: {\n ...uploadState.value[uploadId],\n ...data,\n } as UploadItem,\n };\n };\n\n const statusPoller = createUploadStatusPoller(uploadClient, {\n onComplete: (uploadId) =>\n updateItemInQueue(uploadId, { status: \"complete\" }),\n\n onFail: (uploadId, statusMessage) =>\n updateItemInQueue(uploadId, {\n status: \"failed\",\n failureDetails: statusMessage,\n }),\n });\n\n useUploadManagerResult = useUploadManager({\n resolveFilePartUploadURL: uploadClient.resolveFilePartUploadURL,\n\n resolveCompleteStatus: ({ name }) =>\n options.isFileWithProcessing?.(name) ? \"processing\" : \"complete\",\n\n onFileUploadComplete: ({ uploadId, filePartIds, name, parentId }) => {\n promise\n .retryPromise({\n fn: () => uploadClient.completeUpload(uploadId, filePartIds),\n })\n .then(() => {\n if (options.isFileWithProcessing?.(name)) {\n statusPoller.track(uploadId);\n }\n\n options.onFileUploadComplete?.({\n uploadId,\n filePartIds,\n name,\n parentId,\n });\n })\n .catch((error) => {\n consola.error(\"Error attempting to complete upload\", { error });\n useUploadManagerResult!.setFailed(uploadId, error);\n });\n },\n\n onFileUploadFailed: ({ uploadId, error, parentId }) => {\n options?.onFileUploadFailed?.({ uploadId, error, parentId });\n },\n });\n\n const getEnqueueableFiles = (files: File[]): File[] => {\n const { totalPendingUploads } = useUploadManagerResult;\n\n const { maxUploadQueueSize = DEFAULT_MAX_UPLOAD_QUEUE_SIZE } = options;\n\n if (files.length + totalPendingUploads.value > maxUploadQueueSize) {\n options.onUploadQueueSizeExceeded?.(maxUploadQueueSize);\n }\n\n // only keep as many files as the queue allows\n const effectiveQueueSize = Math.max(\n 0,\n maxUploadQueueSize - totalPendingUploads.value,\n );\n return files.slice(0, effectiveQueueSize);\n };\n\n const { uploadState, ...uploadManagerResultRest } = useUploadManagerResult;\n\n const prepareQueueSize = ref(0);\n\n const uploadQueue = computed<UploadItem[]>(() =>\n Object.values(uploadState.value),\n );\n\n return {\n ...uploadManagerResultRest,\n\n uploadQueue,\n getItemFromQueue,\n updateItemInQueue,\n\n isPreparingUpload: computed(() => prepareQueueSize.value > 0),\n totalFilesBeingPrepared: computed(() => prepareQueueSize.value),\n\n start: async (parentId: string, files: File[]) => {\n const enqueueableFiles = getEnqueueableFiles(files);\n const uploadSizeLimitBytes = getUploadSizeLimitBytes();\n const oversizedFiles = enqueueableFiles.filter(\n (file) => file.size > uploadSizeLimitBytes,\n );\n\n if (oversizedFiles.length > 0) {\n throw new rfcErrors.RFCError({\n title: \"Upload size limit exceeded\",\n status: 413,\n details: oversizedFiles.map(\n (file) =>\n `${file.name} (${formatFileSize(file.size)}) exceeds the upload limit of ${formatFileSize(uploadSizeLimitBytes)}.`,\n ),\n });\n }\n\n try {\n if (enqueueableFiles.length === 0) {\n return [];\n }\n\n prepareQueueSize.value += enqueueableFiles.length;\n\n const items = enqueueableFiles.map((file) => ({\n // strip extension for workflows. otherwise, the extension gets baked into the name\n // of the uploaded file and the backend appends an extra extension.\n // unnecessary for other file types\n path: knimeFileFormats.KNWF.getNameOrDefault(file, file.name),\n file,\n }));\n\n const uploadPayload = await promise.retryPromise({\n fn: () => uploadClient.prepareUpload(parentId, items),\n excludeError: (error: FetchError) =>\n // eslint-disable-next-line no-magic-numbers\n Boolean(error.statusCode && error.statusCode < 500),\n retryDelayMS: DEFAULT_RETRY_DELAY_MS,\n });\n\n const uploadIds = uploadPayload.map((upload) => upload.uploadId);\n\n // accept floating promise, errors are handled inside the function\n useUploadManagerResult.start(parentId, uploadPayload);\n\n return uploadIds;\n } catch (error) {\n throw rfcErrors.tryParse(error) ?? error;\n } finally {\n // errors can only be thrown in the prepareUpload call, useUploadManagerResult.start does its own error handling\n prepareQueueSize.value -= enqueueableFiles.length;\n }\n },\n\n cancel: (uploadId: string) => {\n useUploadManagerResult.cancel(uploadId);\n uploadClient.cancelUpload(uploadId).catch((error: unknown) => {\n consola.error(\"There was a problem cancelling the upload\", { error });\n });\n },\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"dataTransfer.js","names":[],"sources":["../src/dataTransfer/shared/ui/CollapsiblePanel/useIconFlipper.ts","../src/dataTransfer/shared/ui/CollapsiblePanel/CollapsiblePanel.vue","../src/dataTransfer/shared/ui/CollapsiblePanel/CollapsiblePanel.vue","../src/dataTransfer/shared/ui/ProgressItem/ProgressItem.vue","../src/dataTransfer/shared/ui/ProgressItem/ProgressItem.vue","../src/dataTransfer/shared/ui/ProgressList/ProgressList.vue","../src/dataTransfer/download/ui/DownloadProgressPanelItem.vue","../src/dataTransfer/download/ui/DownloadProgressPanelItem.vue","../src/dataTransfer/download/ui/DownloadProgressPanel.vue","../src/dataTransfer/download/ui/DownloadProgressPanel.vue","../src/dataTransfer/download/useDownloadArtifact.ts","../src/dataTransfer/upload/UploadProgressPanelItem.vue","../src/dataTransfer/upload/UploadProgressPanelItem.vue","../src/dataTransfer/upload/UploadProgressPanel.vue","../src/dataTransfer/upload/UploadProgressPanel.vue","../src/dataTransfer/upload/useUploadManager.ts","../src/dataTransfer/upload/useFileUpload.ts"],"sourcesContent":["import { type CSSProperties, type ComputedRef, computed } from \"vue\";\n\ntype UseIconFlipperOptions = {\n active: ComputedRef<boolean>;\n initialRotation?: `${string}deg`;\n};\n\nexport const useIconFlipper = (options: UseIconFlipperOptions) => {\n const iconFlipStyles = computed<CSSProperties>(() => {\n const declaration: Partial<CSSProperties> = {\n transition: \"transform 0.2s linear\",\n transform: \"scaleY(1)\",\n };\n\n if (options.initialRotation) {\n declaration.rotate = `${options.initialRotation}`;\n }\n\n const transform = options.active.value ? \"scaleY(-1)\" : undefined;\n\n return { ...declaration, transform };\n });\n\n return { iconFlipStyles };\n};\n","<script setup lang=\"ts\">\nimport { computed, useId } from \"vue\";\n\nimport { KdsButton } from \"@knime/kds-components\";\n\nimport type { CollapsiblePanelProps } from \"./types\";\nimport { useIconFlipper } from \"./useIconFlipper\";\n\nconst { headline, canBeDismissed = false } =\n defineProps<CollapsiblePanelProps>();\n\nconst emit = defineEmits<{\n close: [];\n}>();\n\nconst modelValue = defineModel<boolean>({ default: true });\n\nconst { iconFlipStyles } = useIconFlipper({\n active: computed(() => modelValue.value),\n initialRotation: \"180deg\",\n});\nconst panelContentId = useId();\n</script>\n\n<template>\n <div class=\"collapsible-panel\" :class=\"{ collapsed: !modelValue }\">\n <div class=\"title-bar\">\n {{ headline }}\n <div class=\"actions\">\n <KdsButton\n size=\"small\"\n variant=\"transparent\"\n leading-icon=\"chevron-up\"\n :aria-expanded=\"modelValue\"\n :aria-controls=\"panelContentId\"\n :ariaLabel=\"modelValue ? 'Collapse' : 'Expand'\"\n :style=\"iconFlipStyles\"\n @click=\"modelValue = !modelValue\"\n />\n\n <KdsButton\n v-if=\"canBeDismissed\"\n size=\"small\"\n variant=\"transparent\"\n ariaLabel=\"Close\"\n leading-icon=\"x-close\"\n @click=\"emit('close')\"\n />\n </div>\n </div>\n\n <Transition name=\"expand\">\n <div v-show=\"modelValue\" :id=\"panelContentId\" class=\"panel-content\">\n <slot />\n </div>\n </Transition>\n </div>\n</template>\n\n<style scoped>\n.collapsible-panel {\n background: var(--kds-color-surface-default);\n border-radius: var(--kds-spacing-container-0-25x);\n box-shadow: var(--kds-elevation-level-3);\n\n & .title-bar {\n display: flex;\n align-items: center;\n height: var(--kds-dimension-component-height-2-5x);\n padding: var(--kds-spacing-container-0-5x)\n var(--kds-spacing-container-0-75x);\n font: var(--kds-font-base-title-small-strong);\n color: var(--kds-color-text-and-icon-neutral);\n\n & .actions {\n display: flex;\n gap: var(--kds-spacing-container-0-25x);\n align-items: center;\n margin-left: auto;\n }\n }\n\n &:not(.collapsed) {\n & .title-bar {\n border-bottom: var(--kds-border-base-subtle);\n }\n }\n}\n\n.panel-content {\n overflow: hidden;\n}\n\n.expand-enter-active,\n.expand-leave-active {\n display: grid;\n overflow: hidden;\n transition:\n grid-template-rows 0.2s ease-in-out,\n opacity 0.2s ease-in-out;\n}\n\n.expand-enter-from,\n.expand-leave-to {\n grid-template-rows: 0fr;\n opacity: 0;\n}\n\n.expand-enter-to,\n.expand-leave-from {\n grid-template-rows: 1fr;\n opacity: 1;\n}\n\n.panel-content > :deep(*) {\n min-height: 0;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, useId } from \"vue\";\n\nimport { KdsButton } from \"@knime/kds-components\";\n\nimport type { CollapsiblePanelProps } from \"./types\";\nimport { useIconFlipper } from \"./useIconFlipper\";\n\nconst { headline, canBeDismissed = false } =\n defineProps<CollapsiblePanelProps>();\n\nconst emit = defineEmits<{\n close: [];\n}>();\n\nconst modelValue = defineModel<boolean>({ default: true });\n\nconst { iconFlipStyles } = useIconFlipper({\n active: computed(() => modelValue.value),\n initialRotation: \"180deg\",\n});\nconst panelContentId = useId();\n</script>\n\n<template>\n <div class=\"collapsible-panel\" :class=\"{ collapsed: !modelValue }\">\n <div class=\"title-bar\">\n {{ headline }}\n <div class=\"actions\">\n <KdsButton\n size=\"small\"\n variant=\"transparent\"\n leading-icon=\"chevron-up\"\n :aria-expanded=\"modelValue\"\n :aria-controls=\"panelContentId\"\n :ariaLabel=\"modelValue ? 'Collapse' : 'Expand'\"\n :style=\"iconFlipStyles\"\n @click=\"modelValue = !modelValue\"\n />\n\n <KdsButton\n v-if=\"canBeDismissed\"\n size=\"small\"\n variant=\"transparent\"\n ariaLabel=\"Close\"\n leading-icon=\"x-close\"\n @click=\"emit('close')\"\n />\n </div>\n </div>\n\n <Transition name=\"expand\">\n <div v-show=\"modelValue\" :id=\"panelContentId\" class=\"panel-content\">\n <slot />\n </div>\n </Transition>\n </div>\n</template>\n\n<style scoped>\n.collapsible-panel {\n background: var(--kds-color-surface-default);\n border-radius: var(--kds-spacing-container-0-25x);\n box-shadow: var(--kds-elevation-level-3);\n\n & .title-bar {\n display: flex;\n align-items: center;\n height: var(--kds-dimension-component-height-2-5x);\n padding: var(--kds-spacing-container-0-5x)\n var(--kds-spacing-container-0-75x);\n font: var(--kds-font-base-title-small-strong);\n color: var(--kds-color-text-and-icon-neutral);\n\n & .actions {\n display: flex;\n gap: var(--kds-spacing-container-0-25x);\n align-items: center;\n margin-left: auto;\n }\n }\n\n &:not(.collapsed) {\n & .title-bar {\n border-bottom: var(--kds-border-base-subtle);\n }\n }\n}\n\n.panel-content {\n overflow: hidden;\n}\n\n.expand-enter-active,\n.expand-leave-active {\n display: grid;\n overflow: hidden;\n transition:\n grid-template-rows 0.2s ease-in-out,\n opacity 0.2s ease-in-out;\n}\n\n.expand-enter-from,\n.expand-leave-to {\n grid-template-rows: 0fr;\n opacity: 0;\n}\n\n.expand-enter-to,\n.expand-leave-from {\n grid-template-rows: 1fr;\n opacity: 1;\n}\n\n.panel-content > :deep(*) {\n min-height: 0;\n}\n</style>\n","<script lang=\"ts\" setup>\nimport {\n KdsBadge,\n KdsButton,\n KdsIcon,\n KdsProgressBar,\n vKdsTooltip,\n} from \"@knime/kds-components\";\n\nimport { type ProgressItemProps } from \"./types\";\n\ndefineProps<ProgressItemProps>();\n</script>\n\n<template>\n <div class=\"progress-wrapper\">\n <div :class=\"['progress-item', { padded: progress === undefined }]\">\n <div class=\"item-info\">\n <KdsIcon :name=\"icon\" />\n\n <div class=\"item-name\">\n <div v-kds-tooltip:truncated=\"headline\" class=\"title\">\n {{ headline }}\n </div>\n <span v-if=\"subtitle\" class=\"subtitle\">\n {{ subtitle }}\n </span>\n </div>\n </div>\n\n <div class=\"item-action\">\n <KdsBadge\n v-if=\"badge\"\n :variant=\"badge.variant\"\n :label=\"badge.text\"\n :leading-icon=\"badge.icon\"\n size=\"xxsmall\"\n />\n\n <template v-for=\"action in actions.slice(0, 2)\" :key=\"action.id\">\n <KdsButton\n :leading-icon=\"action.icon\"\n :ariaLabel=\"action.label\"\n variant=\"transparent\"\n @click=\"action.handler\"\n />\n </template>\n </div>\n </div>\n\n <div v-if=\"typeof progress === 'number'\" class=\"progress\">\n <KdsProgressBar :percentage=\"progress\" size=\"medium\" />\n </div>\n </div>\n</template>\n\n<style scoped>\n.progress-wrapper {\n --base-padding: var(--kds-spacing-container-1x);\n --progress-bar-height: var(--kds-dimension-component-height-0-25x);\n --progress-bar-distance: var(--kds-spacing-container-0-25x);\n\n display: flex;\n flex-direction: column;\n gap: var(--progress-bar-distance);\n height: var(--kds-dimension-component-height-4x);\n padding: var(--base-padding);\n}\n\n.progress-item {\n display: flex;\n align-items: center;\n width: 100%;\n height: 100%;\n\n /* add extra margin to prevent content jump when a progress bars appears/disappears */\n &.padded {\n margin-bottom: calc(\n var(--progress-bar-height) + var(--progress-bar-distance)\n );\n }\n}\n\n.progress {\n min-height: var(--progress-bar-height);\n}\n\n.item-info {\n display: flex;\n flex-grow: 1;\n gap: var(--kds-spacing-container-0-25x);\n overflow: hidden;\n\n & .item-name {\n display: flex;\n flex-grow: 1;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-25x);\n justify-content: center;\n overflow: hidden;\n color: var(--kds-color-text-and-icon-neutral);\n\n & .title {\n max-width: 100%;\n overflow: hidden;\n text-overflow: ellipsis;\n font: var(--kds-font-base-interactive-small);\n white-space: nowrap;\n }\n\n & .subtitle {\n font: var(--kds-font-base-subtext-small);\n }\n }\n}\n\n.item-action {\n display: flex;\n flex-shrink: 0;\n gap: var(--kds-spacing-container-0-25x);\n align-items: center;\n}\n</style>\n","<script lang=\"ts\" setup>\nimport {\n KdsBadge,\n KdsButton,\n KdsIcon,\n KdsProgressBar,\n vKdsTooltip,\n} from \"@knime/kds-components\";\n\nimport { type ProgressItemProps } from \"./types\";\n\ndefineProps<ProgressItemProps>();\n</script>\n\n<template>\n <div class=\"progress-wrapper\">\n <div :class=\"['progress-item', { padded: progress === undefined }]\">\n <div class=\"item-info\">\n <KdsIcon :name=\"icon\" />\n\n <div class=\"item-name\">\n <div v-kds-tooltip:truncated=\"headline\" class=\"title\">\n {{ headline }}\n </div>\n <span v-if=\"subtitle\" class=\"subtitle\">\n {{ subtitle }}\n </span>\n </div>\n </div>\n\n <div class=\"item-action\">\n <KdsBadge\n v-if=\"badge\"\n :variant=\"badge.variant\"\n :label=\"badge.text\"\n :leading-icon=\"badge.icon\"\n size=\"xxsmall\"\n />\n\n <template v-for=\"action in actions.slice(0, 2)\" :key=\"action.id\">\n <KdsButton\n :leading-icon=\"action.icon\"\n :ariaLabel=\"action.label\"\n variant=\"transparent\"\n @click=\"action.handler\"\n />\n </template>\n </div>\n </div>\n\n <div v-if=\"typeof progress === 'number'\" class=\"progress\">\n <KdsProgressBar :percentage=\"progress\" size=\"medium\" />\n </div>\n </div>\n</template>\n\n<style scoped>\n.progress-wrapper {\n --base-padding: var(--kds-spacing-container-1x);\n --progress-bar-height: var(--kds-dimension-component-height-0-25x);\n --progress-bar-distance: var(--kds-spacing-container-0-25x);\n\n display: flex;\n flex-direction: column;\n gap: var(--progress-bar-distance);\n height: var(--kds-dimension-component-height-4x);\n padding: var(--base-padding);\n}\n\n.progress-item {\n display: flex;\n align-items: center;\n width: 100%;\n height: 100%;\n\n /* add extra margin to prevent content jump when a progress bars appears/disappears */\n &.padded {\n margin-bottom: calc(\n var(--progress-bar-height) + var(--progress-bar-distance)\n );\n }\n}\n\n.progress {\n min-height: var(--progress-bar-height);\n}\n\n.item-info {\n display: flex;\n flex-grow: 1;\n gap: var(--kds-spacing-container-0-25x);\n overflow: hidden;\n\n & .item-name {\n display: flex;\n flex-grow: 1;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-25x);\n justify-content: center;\n overflow: hidden;\n color: var(--kds-color-text-and-icon-neutral);\n\n & .title {\n max-width: 100%;\n overflow: hidden;\n text-overflow: ellipsis;\n font: var(--kds-font-base-interactive-small);\n white-space: nowrap;\n }\n\n & .subtitle {\n font: var(--kds-font-base-subtext-small);\n }\n }\n}\n\n.item-action {\n display: flex;\n flex-shrink: 0;\n gap: var(--kds-spacing-container-0-25x);\n align-items: center;\n}\n</style>\n","<template>\n <div class=\"progress-list\" tabindex=\"0\">\n <slot />\n </div>\n</template>\n\n<style scoped>\n.progress-list {\n max-height: var(--kds-dimension-component-height-20x);\n overflow-y: auto;\n\n &:deep(.progress-wrapper):not(:first-child) {\n border-top: var(--kds-border-base-subtle);\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nimport type { KdsBadgeVariant, KdsIconName } from \"@knime/kds-components\";\n\nimport {\n ProgressItem,\n type ProgressItemAction,\n type ProgressItemBadge,\n} from \"../../shared/ui\";\n\nimport type { DownloadItem } from \"./types\";\n\ntype Props = {\n item: DownloadItem;\n};\n\nconst props = defineProps<Props>();\n\nconst emit = defineEmits<{\n cancel: [];\n download: [];\n}>();\n\nconst statusMapper = {\n IN_PROGRESS: [\"Zipping\", \"info\", \"reload\"],\n READY: [\"Ready\", \"success\", \"circle-info\"],\n CANCELLED: [\"Cancelled\", \"error\", \"circle-error\"],\n FAILED: [\"Failed\", \"error\", \"circle-error\"],\n} satisfies Record<\n DownloadItem[\"status\"],\n [string, KdsBadgeVariant, KdsIconName]\n>;\n\nconst badge = computed<ProgressItemBadge>(() => {\n const [text, variant, icon] = statusMapper[props.item.status];\n\n return {\n text,\n variant,\n icon,\n } satisfies ProgressItemBadge;\n});\n\nconst isCancellable = computed(() => props.item.status === \"IN_PROGRESS\");\nconst isReady = computed(() => props.item.status === \"READY\");\n\nconst actions = computed<Array<ProgressItemAction>>(() => {\n const actions: ProgressItemAction[] = [];\n\n if (isCancellable.value) {\n actions.push({\n id: \"cancel\",\n icon: \"x-close\",\n label: \"Cancel\",\n handler: () => emit(\"cancel\"),\n });\n }\n\n if (isReady.value) {\n actions.push({\n id: \"download\",\n icon: \"cloud-download\",\n label: \"Download again\",\n handler: () => emit(\"download\"),\n });\n }\n\n return actions;\n});\n</script>\n\n<template>\n <ProgressItem\n :id=\"item.downloadId\"\n icon=\"file\"\n :headline=\"item.name\"\n :badge=\"badge\"\n :actions=\"actions\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nimport type { KdsBadgeVariant, KdsIconName } from \"@knime/kds-components\";\n\nimport {\n ProgressItem,\n type ProgressItemAction,\n type ProgressItemBadge,\n} from \"../../shared/ui\";\n\nimport type { DownloadItem } from \"./types\";\n\ntype Props = {\n item: DownloadItem;\n};\n\nconst props = defineProps<Props>();\n\nconst emit = defineEmits<{\n cancel: [];\n download: [];\n}>();\n\nconst statusMapper = {\n IN_PROGRESS: [\"Zipping\", \"info\", \"reload\"],\n READY: [\"Ready\", \"success\", \"circle-info\"],\n CANCELLED: [\"Cancelled\", \"error\", \"circle-error\"],\n FAILED: [\"Failed\", \"error\", \"circle-error\"],\n} satisfies Record<\n DownloadItem[\"status\"],\n [string, KdsBadgeVariant, KdsIconName]\n>;\n\nconst badge = computed<ProgressItemBadge>(() => {\n const [text, variant, icon] = statusMapper[props.item.status];\n\n return {\n text,\n variant,\n icon,\n } satisfies ProgressItemBadge;\n});\n\nconst isCancellable = computed(() => props.item.status === \"IN_PROGRESS\");\nconst isReady = computed(() => props.item.status === \"READY\");\n\nconst actions = computed<Array<ProgressItemAction>>(() => {\n const actions: ProgressItemAction[] = [];\n\n if (isCancellable.value) {\n actions.push({\n id: \"cancel\",\n icon: \"x-close\",\n label: \"Cancel\",\n handler: () => emit(\"cancel\"),\n });\n }\n\n if (isReady.value) {\n actions.push({\n id: \"download\",\n icon: \"cloud-download\",\n label: \"Download again\",\n handler: () => emit(\"download\"),\n });\n }\n\n return actions;\n});\n</script>\n\n<template>\n <ProgressItem\n :id=\"item.downloadId\"\n icon=\"file\"\n :headline=\"item.name\"\n :badge=\"badge\"\n :actions=\"actions\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport { computed, watch } from \"vue\";\n\nimport { CollapsiblePanel, ProgressList } from \"../../shared/ui\";\n\nimport DownloadProgressPanelItem from \"./DownloadProgressPanelItem.vue\";\nimport type { DownloadItem } from \"./types\";\n\ntype Props = {\n /**\n * List of current download items that should be displayed\n */\n items: DownloadItem[];\n};\nconst props = defineProps<Props>();\n\nconst emit = defineEmits<{\n cancel: [item: DownloadItem];\n download: [item: DownloadItem];\n close: [];\n}>();\n\nconst expanded = defineModel<boolean>(\"expanded\", { default: true });\n\nconst totalItems = computed(() => props.items.length);\nconst totalDownloadedItems = computed(\n () => props.items.filter(({ status }) => status === \"READY\").length,\n);\n\nconst headline = computed(() => {\n const suffix = `file${totalItems.value > 1 ? \"s\" : \"\"}`;\n return `Downloaded ${totalDownloadedItems.value} of ${totalItems.value} ${suffix}`;\n});\n\nconst hasItemsInProgress = computed(() =>\n props.items.some(({ status }) => status === \"IN_PROGRESS\"),\n);\n\nwatch(\n () => props.items?.length,\n (newValue, oldValue) => {\n if (oldValue > 0 && !newValue) {\n emit(\"close\");\n }\n },\n);\n</script>\n\n<template>\n <CollapsiblePanel\n v-model=\"expanded\"\n :headline=\"headline\"\n class=\"download-panel\"\n :can-be-dismissed=\"!hasItemsInProgress\"\n @close=\"emit('close')\"\n >\n <div class=\"download-panel-content\">\n <ProgressList>\n <DownloadProgressPanelItem\n v-for=\"item in items\"\n :key=\"item.downloadId\"\n :item=\"item\"\n @cancel=\"emit('cancel', item)\"\n @download=\"emit('download', item)\"\n />\n </ProgressList>\n </div>\n </CollapsiblePanel>\n</template>\n\n<style scoped>\n.download-panel {\n width: var(--kds-dimension-component-width-20x);\n max-width: var(--kds-dimension-component-width-20x);\n}\n\n.download-panel-content {\n max-height: var(--kds-dimension-component-height-20x);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, watch } from \"vue\";\n\nimport { CollapsiblePanel, ProgressList } from \"../../shared/ui\";\n\nimport DownloadProgressPanelItem from \"./DownloadProgressPanelItem.vue\";\nimport type { DownloadItem } from \"./types\";\n\ntype Props = {\n /**\n * List of current download items that should be displayed\n */\n items: DownloadItem[];\n};\nconst props = defineProps<Props>();\n\nconst emit = defineEmits<{\n cancel: [item: DownloadItem];\n download: [item: DownloadItem];\n close: [];\n}>();\n\nconst expanded = defineModel<boolean>(\"expanded\", { default: true });\n\nconst totalItems = computed(() => props.items.length);\nconst totalDownloadedItems = computed(\n () => props.items.filter(({ status }) => status === \"READY\").length,\n);\n\nconst headline = computed(() => {\n const suffix = `file${totalItems.value > 1 ? \"s\" : \"\"}`;\n return `Downloaded ${totalDownloadedItems.value} of ${totalItems.value} ${suffix}`;\n});\n\nconst hasItemsInProgress = computed(() =>\n props.items.some(({ status }) => status === \"IN_PROGRESS\"),\n);\n\nwatch(\n () => props.items?.length,\n (newValue, oldValue) => {\n if (oldValue > 0 && !newValue) {\n emit(\"close\");\n }\n },\n);\n</script>\n\n<template>\n <CollapsiblePanel\n v-model=\"expanded\"\n :headline=\"headline\"\n class=\"download-panel\"\n :can-be-dismissed=\"!hasItemsInProgress\"\n @close=\"emit('close')\"\n >\n <div class=\"download-panel-content\">\n <ProgressList>\n <DownloadProgressPanelItem\n v-for=\"item in items\"\n :key=\"item.downloadId\"\n :item=\"item\"\n @cancel=\"emit('cancel', item)\"\n @download=\"emit('download', item)\"\n />\n </ProgressList>\n </div>\n </CollapsiblePanel>\n</template>\n\n<style scoped>\n.download-panel {\n width: var(--kds-dimension-component-width-20x);\n max-width: var(--kds-dimension-component-width-20x);\n}\n\n.download-panel-content {\n max-height: var(--kds-dimension-component-height-20x);\n}\n</style>\n","import { computed, ref } from \"vue\";\nimport { FetchError, type FetchOptions } from \"ofetch\";\n\nimport { sleep } from \"@knime/utils\";\n\nimport { rfcErrors } from \"../../rfcErrors\";\nimport { createHttpClient } from \"../shared/httpClient\";\n\nimport type { DownloadItem as DownloadItemExternal } from \"./ui/types\";\n\nconst DEFAULT_MAX_RETRIES = null;\nconst DEFAULT_POLLING_INTERVAL = 2000;\n\ntype ArtifactStatusResponse = {\n downloadId?: string;\n status?: string;\n statusMessage?: string;\n lastUpdated?: string;\n downloadUrl?: string;\n};\n\n// Initial request can have either download url (if it can be downloaded immediately) or download id (to poll for the status)\ntype RequestDownloadResponse =\n | {\n downloadId: string;\n }\n | {\n downloadUrl: string;\n };\n\n// type guard\nconst hasDownloadUrl = (\n response: RequestDownloadResponse,\n): response is { downloadUrl: string } => {\n return \"downloadUrl\" in response;\n};\n\n/**\n * The version of the artifact to download.\n */\ntype ItemVersion = number | \"current-state\" | \"most-recent\";\n\n/**\n * Tracks the state of each concurrent download.\n */\ntype DownloadItem = DownloadItemExternal & {\n itemId: string;\n version?: ItemVersion;\n};\n\nexport type UseDownloadArtifactOptions = {\n /**\n * The maximum number of times to poll for the download URL. Default is `null` (no limit).\n */\n maxRetries?: number;\n /**\n * The interval in milliseconds between each poll. Default is `2000` ms.\n */\n pollingInterval?: number;\n /**\n * Options to customize the fetch client used internally to make http requests.\n * e.g: headers, baseUrl, etc\n */\n customFetchClientOptions?: FetchOptions;\n};\n\nconst isAbortError = (error: unknown) =>\n error instanceof DOMException && error.name === \"AbortError\";\n\n/**\n * This composable supports multiple concurrent downloads. Each time you call ```start```, the following behavior applies:\n *\n * 1. **Immediate-ready artifact**: If the first status check for the artifact returns `READY`, then there's no polling and no need\n * to add it to `downloadItems`. The download URL is opened in the same tab immediately.\n *\n * 2. **Not-ready artifact**: If the artifact is not yet ready (e.g. needs zipping), then we add a record to `downloadItems` with status `IN_PROGRESS`\n * and repeatedly poll until it becomes `READY`, the polling times out, or the user aborts.\n *\n * **Usage**:\n * ```\n * const { start, cancel, downloadItems } = useDownloadArtifact({ ...options });\n *\n * // start a download\n * await start({ itemId: \"1234\", version: 1 });\n *\n * // if needed, cancel it:\n * cancel(\"1234\");\n *\n * // downloadItems (as a computed array) will reflect the current statuses.\n * ```\n */\nexport const useDownloadArtifact = (\n options: UseDownloadArtifactOptions = {},\n) => {\n const $ofetch = createHttpClient(options.customFetchClientOptions);\n\n const downloadItems = ref<Record<string, DownloadItem>>({});\n const abortControllers: Record<string, AbortController> = {};\n\n // Use fallback values if none are provided\n const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;\n const pollingInterval = options.pollingInterval ?? DEFAULT_POLLING_INTERVAL;\n\n const cancel = (downloadId: string) => {\n const itemController = abortControllers[downloadId];\n if (itemController) {\n itemController.abort();\n delete abortControllers[downloadId];\n }\n };\n\n const removeItem = (downloadId: string) => {\n cancel(downloadId);\n delete downloadItems.value[downloadId];\n };\n\n const fetchDownloadStatus = ({\n downloadId,\n signal,\n }: {\n downloadId: string;\n signal?: AbortSignal;\n }) => {\n return $ofetch<ArtifactStatusResponse>(`/downloads/${downloadId}/status`, {\n method: \"GET\",\n signal,\n });\n };\n\n const openDownload = (downloadUrl: string) => {\n const a = document.createElement(\"a\");\n a.target = \"_blank\";\n a.href = downloadUrl;\n a.download = \"\";\n a.style.display = \"none\";\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n };\n\n /**\n * Makes a request to the download status endpoint for the given download item.\n * Returns a boolean that is true if a terminal state has been reached (download is ready,\n * download failed, or user aborted download)\n * @param downloadId the `downloadId`\n * @param signal the abort signal passed to the request to be able to abort it\n * @returns a boolean indicating whether a terminal state has been reached\n */\n const handlePollingResult = async ({\n downloadId,\n signal,\n }: {\n downloadId: string;\n signal: AbortSignal;\n }) => {\n try {\n const statusResponse = await fetchDownloadStatus({\n downloadId,\n signal,\n });\n\n if (statusResponse?.status === \"READY\" && statusResponse?.downloadUrl) {\n downloadItems.value[downloadId].status = \"READY\";\n downloadItems.value[downloadId].downloadUrl =\n statusResponse.downloadUrl;\n // open download in a new tab\n openDownload(statusResponse.downloadUrl);\n return true;\n } else if (statusResponse?.status === \"FAILED\") {\n let details = \"Download failed\";\n if (statusResponse?.statusMessage) {\n details += `: ${statusResponse.statusMessage}`;\n }\n throw new Error(details);\n }\n } catch (error: unknown) {\n consola.error(\"Error fetching status:\", error);\n // https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort\n const isAborted =\n isAbortError(error) ||\n (error instanceof FetchError && isAbortError(error.cause));\n\n if (!isAborted) {\n downloadItems.value[downloadId].status = \"FAILED\";\n downloadItems.value[downloadId].failureDetails =\n error instanceof Error ? error.message : (error as string);\n }\n return true;\n }\n return false;\n };\n\n const pollDownloadItem = async ({\n itemId,\n downloadId,\n version,\n name,\n }: {\n itemId: string;\n downloadId: string;\n version?: ItemVersion;\n name: string;\n }) => {\n const controller = new AbortController();\n abortControllers[downloadId] = controller;\n\n const signal = controller.signal;\n\n let itemName = `${name}`;\n if (version) {\n itemName += ` (${version})`;\n }\n\n downloadItems.value[downloadId] = {\n itemId,\n ...(version && { version }),\n downloadId,\n name: itemName,\n status: \"IN_PROGRESS\",\n };\n\n const abortListener = () => {\n controller.signal.removeEventListener(\"abort\", abortListener);\n if (!downloadItems.value[downloadId]) {\n return;\n }\n downloadItems.value[downloadId].status = \"CANCELLED\";\n downloadItems.value[downloadId].failureDetails =\n \"Download was cancelled manually\";\n };\n controller.signal.addEventListener(\"abort\", abortListener);\n\n let tries = 0;\n // eslint-disable-next-line no-unmodified-loop-condition\n while (maxRetries === null || tries < maxRetries) {\n tries++;\n await sleep(pollingInterval);\n\n const isTerminalState = await handlePollingResult({ downloadId, signal });\n\n if (isTerminalState) {\n break;\n }\n }\n\n delete abortControllers[downloadId];\n\n if (maxRetries !== null && tries >= maxRetries) {\n downloadItems.value[downloadId].status = \"FAILED\";\n downloadItems.value[downloadId].failureDetails = \"Download timed out\";\n }\n };\n\n const start = async ({\n itemId,\n version,\n name,\n }: {\n itemId: string;\n name: string;\n version?: ItemVersion;\n }) => {\n try {\n const response = await $ofetch<RequestDownloadResponse>(\n `/repository/${itemId}/artifact`,\n {\n method: \"GET\",\n query: { version },\n },\n );\n\n // if download is ready, open url immediately\n if (hasDownloadUrl(response)) {\n openDownload(response.downloadUrl);\n return;\n }\n\n // otherwise start polling download status\n await pollDownloadItem({\n itemId,\n version,\n downloadId: response.downloadId,\n name,\n });\n } catch (error: unknown) {\n // here only errors thrown by the initial requests are caught;\n // errors occurring while polling will be handled in pollDownloadItem\n throw rfcErrors.tryParse(error) ?? error;\n }\n };\n\n const totalItemsBeingZipped = computed(\n () =>\n Object.values(downloadItems.value).filter(\n ({ status }) => status === \"IN_PROGRESS\",\n ).length,\n );\n\n const resetState = () => {\n for (const [downloadId, controller] of Object.entries(abortControllers)) {\n controller?.abort();\n delete abortControllers[downloadId];\n }\n downloadItems.value = {};\n };\n\n const downloadItemsArray = computed(() => Object.values(downloadItems.value));\n\n return {\n /**\n * Initiates a download of the given itemId and version.\n */\n start,\n\n /**\n * Cancels the ongoing download for a given itemId (if any).\n * Sets the status in `downloadItems` to `CANCELLED`.\n */\n cancel,\n\n /**\n * Removes item from `downloadItems`.\n */\n removeItem,\n\n /**\n * A computed array of all current items that are being zipped and prepared for download.\n */\n downloadItems: downloadItemsArray,\n\n /**\n * The total number of zipping items in progress.\n */\n totalItemsBeingZipped,\n\n /**\n * Resets the state of the composable, clearing all downloads and controllers.\n */\n resetState,\n\n /**\n * Opens a download from the given url\n */\n openDownload,\n };\n};\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nimport type { KdsBadgeVariant, KdsIconName } from \"@knime/kds-components\";\nimport {\n formatFileSize,\n getFileExtension,\n knimeFileFormats,\n} from \"@knime/utils\";\n\nimport {\n ProgressItem,\n type ProgressItemAction,\n type ProgressItemBadge,\n} from \"../shared/ui\";\n\nimport type { UploadItem, UploadItemStatus } from \"./types\";\n\ntype Props = {\n item: UploadItem;\n};\n\nconst props = defineProps<Props>();\n\nconst emit = defineEmits<{\n cancel: [];\n viewDetails: [];\n}>();\n\nconst subtitle = computed(() => {\n if (props.item.type === \"file\") {\n const progressed = formatFileSize(\n (props.item.size / 100) * (props.item.progress ?? 0),\n );\n\n const total = formatFileSize(props.item.size);\n return `${progressed} of ${total}`;\n }\n\n if (props.item.type === \"directory\") {\n const total = props.item.totalEntries;\n const progressed = props.item.uploadedEntries;\n\n return `${progressed} of ${total} file${total > 1 ? \"s\" : \"\"}`;\n }\n\n return \"\";\n});\n\nconst progressPercentageValue = computed(() => {\n if (props.item.type === \"file\") {\n return props.item.progress;\n }\n\n if (props.item.type === \"directory\") {\n // The KdsProgressBar only accepts percentages, but for directory items\n // the consumer supplies different values so we must derive the percentage\n const progressPercentage =\n (props.item.uploadedEntries * 100) / props.item.totalEntries;\n\n return progressPercentage;\n }\n\n return undefined;\n});\n\nconst statusMapper = {\n inprogress: [\"Uploading\", \"info\", \"reload\"],\n failed: [\"Failed\", \"error\", \"circle-error\"],\n complete: [\"Uploaded\", \"success\", \"circle-success\"],\n cancelled: [\"Cancelled\", \"error\", \"circle-error\"],\n processing: [\"Processing\", \"info\", \"reload\"],\n \"partial-success\": [\"Partial success\", \"warning\", \"warning\"],\n} satisfies Record<UploadItemStatus, [string, KdsBadgeVariant, KdsIconName]>;\n\nconst itemIcon = computed<KdsIconName>(() => {\n if (props.item.type === \"directory\") {\n return \"folder\";\n }\n\n const extension = getFileExtension(props.item.name);\n return extension === knimeFileFormats.KNWF.extension ? \"workflow\" : \"file\";\n});\n\nconst badge = computed<ProgressItemBadge | undefined>(() => {\n if (!props.item.status) {\n return undefined;\n }\n\n const [text, variant, icon] = statusMapper[props.item.status];\n\n return { text, variant, icon } satisfies ProgressItemBadge;\n});\n\nconst isCancellable = computed(() => {\n if (\n props.item.actions === undefined ||\n props.item.actions.cancellable === undefined\n ) {\n return true;\n }\n\n return props.item.actions.cancellable;\n});\n\nconst shouldShowCancelAction = computed(\n () => props.item.status === \"inprogress\" && isCancellable.value,\n);\n\nconst isInspectable = computed(() => {\n if (\n props.item.actions === undefined ||\n props.item.actions.inspectable === undefined\n ) {\n return false;\n }\n\n return props.item.actions.inspectable;\n});\n\nconst shouldShowViewDetailsAction = computed(\n () => props.item.status === \"partial-success\" && isInspectable.value,\n);\n\nconst actions = computed<Array<ProgressItemAction>>(() => {\n const result: ProgressItemAction[] = [];\n\n if (shouldShowCancelAction.value) {\n result.push({\n id: \"cancel\",\n icon: \"x-close\",\n label: \"Cancel\",\n handler: () => emit(\"cancel\"),\n });\n }\n\n if (shouldShowViewDetailsAction.value) {\n result.push({\n id: \"view-details\",\n icon: \"circle-info\",\n label: \"View details\",\n handler: () => emit(\"viewDetails\"),\n });\n }\n\n return result;\n});\n</script>\n\n<template>\n <ProgressItem\n :id=\"item.id\"\n :headline=\"item.name\"\n :subtitle=\"subtitle\"\n :icon=\"itemIcon\"\n :progress=\"progressPercentageValue\"\n :badge=\"badge\"\n :actions=\"actions\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nimport type { KdsBadgeVariant, KdsIconName } from \"@knime/kds-components\";\nimport {\n formatFileSize,\n getFileExtension,\n knimeFileFormats,\n} from \"@knime/utils\";\n\nimport {\n ProgressItem,\n type ProgressItemAction,\n type ProgressItemBadge,\n} from \"../shared/ui\";\n\nimport type { UploadItem, UploadItemStatus } from \"./types\";\n\ntype Props = {\n item: UploadItem;\n};\n\nconst props = defineProps<Props>();\n\nconst emit = defineEmits<{\n cancel: [];\n viewDetails: [];\n}>();\n\nconst subtitle = computed(() => {\n if (props.item.type === \"file\") {\n const progressed = formatFileSize(\n (props.item.size / 100) * (props.item.progress ?? 0),\n );\n\n const total = formatFileSize(props.item.size);\n return `${progressed} of ${total}`;\n }\n\n if (props.item.type === \"directory\") {\n const total = props.item.totalEntries;\n const progressed = props.item.uploadedEntries;\n\n return `${progressed} of ${total} file${total > 1 ? \"s\" : \"\"}`;\n }\n\n return \"\";\n});\n\nconst progressPercentageValue = computed(() => {\n if (props.item.type === \"file\") {\n return props.item.progress;\n }\n\n if (props.item.type === \"directory\") {\n // The KdsProgressBar only accepts percentages, but for directory items\n // the consumer supplies different values so we must derive the percentage\n const progressPercentage =\n (props.item.uploadedEntries * 100) / props.item.totalEntries;\n\n return progressPercentage;\n }\n\n return undefined;\n});\n\nconst statusMapper = {\n inprogress: [\"Uploading\", \"info\", \"reload\"],\n failed: [\"Failed\", \"error\", \"circle-error\"],\n complete: [\"Uploaded\", \"success\", \"circle-success\"],\n cancelled: [\"Cancelled\", \"error\", \"circle-error\"],\n processing: [\"Processing\", \"info\", \"reload\"],\n \"partial-success\": [\"Partial success\", \"warning\", \"warning\"],\n} satisfies Record<UploadItemStatus, [string, KdsBadgeVariant, KdsIconName]>;\n\nconst itemIcon = computed<KdsIconName>(() => {\n if (props.item.type === \"directory\") {\n return \"folder\";\n }\n\n const extension = getFileExtension(props.item.name);\n return extension === knimeFileFormats.KNWF.extension ? \"workflow\" : \"file\";\n});\n\nconst badge = computed<ProgressItemBadge | undefined>(() => {\n if (!props.item.status) {\n return undefined;\n }\n\n const [text, variant, icon] = statusMapper[props.item.status];\n\n return { text, variant, icon } satisfies ProgressItemBadge;\n});\n\nconst isCancellable = computed(() => {\n if (\n props.item.actions === undefined ||\n props.item.actions.cancellable === undefined\n ) {\n return true;\n }\n\n return props.item.actions.cancellable;\n});\n\nconst shouldShowCancelAction = computed(\n () => props.item.status === \"inprogress\" && isCancellable.value,\n);\n\nconst isInspectable = computed(() => {\n if (\n props.item.actions === undefined ||\n props.item.actions.inspectable === undefined\n ) {\n return false;\n }\n\n return props.item.actions.inspectable;\n});\n\nconst shouldShowViewDetailsAction = computed(\n () => props.item.status === \"partial-success\" && isInspectable.value,\n);\n\nconst actions = computed<Array<ProgressItemAction>>(() => {\n const result: ProgressItemAction[] = [];\n\n if (shouldShowCancelAction.value) {\n result.push({\n id: \"cancel\",\n icon: \"x-close\",\n label: \"Cancel\",\n handler: () => emit(\"cancel\"),\n });\n }\n\n if (shouldShowViewDetailsAction.value) {\n result.push({\n id: \"view-details\",\n icon: \"circle-info\",\n label: \"View details\",\n handler: () => emit(\"viewDetails\"),\n });\n }\n\n return result;\n});\n</script>\n\n<template>\n <ProgressItem\n :id=\"item.id\"\n :headline=\"item.name\"\n :subtitle=\"subtitle\"\n :icon=\"itemIcon\"\n :progress=\"progressPercentageValue\"\n :badge=\"badge\"\n :actions=\"actions\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nimport { KdsButton, KdsLoadingSkeleton } from \"@knime/kds-components\";\n\nimport { CollapsiblePanel, ProgressList } from \"../shared/ui\";\n\nimport UploadProgressPanelItem from \"./UploadProgressPanelItem.vue\";\nimport type { UploadItem, UploadProgressPanelProps } from \"./types\";\n\nconst {\n items,\n placeholderItems = 0,\n canRetry = false,\n} = defineProps<UploadProgressPanelProps>();\n\nconst emit = defineEmits<{\n cancel: [item: UploadItem];\n viewDetails: [item: UploadItem];\n retry: [];\n close: [];\n}>();\n\nconst expanded = defineModel<boolean>(\"expanded\", { default: true });\n\nconst totalItems = computed(() => items.length);\nconst uploadedItems = computed(\n () => items.filter(({ status }) => status === \"complete\").length,\n);\n\nconst headline = computed(() => {\n const suffix = `item${totalItems.value > 1 ? \"s\" : \"\"}`;\n return `Uploaded ${uploadedItems.value} of ${totalItems.value} ${suffix}`;\n});\n\nconst hasSomeItemInProgress = computed(\n () =>\n items.some(\n ({ status }) => status === \"inprogress\" || status === \"processing\",\n ) || placeholderItems > 0,\n);\n</script>\n\n<template>\n <CollapsiblePanel\n v-model=\"expanded\"\n :headline=\"headline\"\n class=\"upload-panel\"\n :can-be-dismissed=\"!hasSomeItemInProgress\"\n @close=\"emit('close')\"\n >\n <div class=\"upload-panel-content\">\n <ProgressList>\n <UploadProgressPanelItem\n v-for=\"item in items\"\n :key=\"item.id\"\n :item=\"item\"\n @cancel=\"emit('cancel', item)\"\n @view-details=\"emit('viewDetails', item)\"\n />\n </ProgressList>\n\n <template v-if=\"placeholderItems > 0\">\n <div\n v-for=\"skeleton in placeholderItems\"\n :key=\"skeleton\"\n class=\"skeleton-container\"\n >\n <KdsLoadingSkeleton variant=\"icon-large\" />\n <div class=\"skeleton-text-content\">\n <KdsLoadingSkeleton variant=\"label\" />\n <KdsLoadingSkeleton variant=\"text\" />\n </div>\n </div>\n </template>\n\n <div v-if=\"canRetry && !hasSomeItemInProgress\" class=\"retry-bar\">\n <KdsButton\n size=\"small\"\n variant=\"outlined\"\n label=\"Retry failed items\"\n leading-icon=\"reload\"\n @click=\"emit('retry')\"\n />\n </div>\n </div>\n </CollapsiblePanel>\n</template>\n\n<style scoped>\n.upload-panel {\n width: var(--kds-dimension-component-width-20x);\n max-width: var(--kds-dimension-component-width-20x);\n}\n\n.upload-panel-content {\n display: flex;\n flex-direction: column;\n}\n\n.retry-bar {\n display: flex;\n justify-content: flex-end;\n padding: var(--kds-spacing-container-0-5x) var(--kds-spacing-container-0-75x);\n border-top: var(--kds-border-base-subtle);\n}\n\n.skeleton-container {\n display: grid;\n grid-template-columns: min-content 1fr;\n gap: var(--kds-spacing-container-0-25x);\n align-items: center;\n min-height: var(--kds-dimension-component-height-4x);\n padding: 0 var(--kds-spacing-container-1x);\n border-top: var(--kds-border-base-subtle);\n\n & .skeleton-text-content {\n display: flex;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-25x);\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nimport { KdsButton, KdsLoadingSkeleton } from \"@knime/kds-components\";\n\nimport { CollapsiblePanel, ProgressList } from \"../shared/ui\";\n\nimport UploadProgressPanelItem from \"./UploadProgressPanelItem.vue\";\nimport type { UploadItem, UploadProgressPanelProps } from \"./types\";\n\nconst {\n items,\n placeholderItems = 0,\n canRetry = false,\n} = defineProps<UploadProgressPanelProps>();\n\nconst emit = defineEmits<{\n cancel: [item: UploadItem];\n viewDetails: [item: UploadItem];\n retry: [];\n close: [];\n}>();\n\nconst expanded = defineModel<boolean>(\"expanded\", { default: true });\n\nconst totalItems = computed(() => items.length);\nconst uploadedItems = computed(\n () => items.filter(({ status }) => status === \"complete\").length,\n);\n\nconst headline = computed(() => {\n const suffix = `item${totalItems.value > 1 ? \"s\" : \"\"}`;\n return `Uploaded ${uploadedItems.value} of ${totalItems.value} ${suffix}`;\n});\n\nconst hasSomeItemInProgress = computed(\n () =>\n items.some(\n ({ status }) => status === \"inprogress\" || status === \"processing\",\n ) || placeholderItems > 0,\n);\n</script>\n\n<template>\n <CollapsiblePanel\n v-model=\"expanded\"\n :headline=\"headline\"\n class=\"upload-panel\"\n :can-be-dismissed=\"!hasSomeItemInProgress\"\n @close=\"emit('close')\"\n >\n <div class=\"upload-panel-content\">\n <ProgressList>\n <UploadProgressPanelItem\n v-for=\"item in items\"\n :key=\"item.id\"\n :item=\"item\"\n @cancel=\"emit('cancel', item)\"\n @view-details=\"emit('viewDetails', item)\"\n />\n </ProgressList>\n\n <template v-if=\"placeholderItems > 0\">\n <div\n v-for=\"skeleton in placeholderItems\"\n :key=\"skeleton\"\n class=\"skeleton-container\"\n >\n <KdsLoadingSkeleton variant=\"icon-large\" />\n <div class=\"skeleton-text-content\">\n <KdsLoadingSkeleton variant=\"label\" />\n <KdsLoadingSkeleton variant=\"text\" />\n </div>\n </div>\n </template>\n\n <div v-if=\"canRetry && !hasSomeItemInProgress\" class=\"retry-bar\">\n <KdsButton\n size=\"small\"\n variant=\"outlined\"\n label=\"Retry failed items\"\n leading-icon=\"reload\"\n @click=\"emit('retry')\"\n />\n </div>\n </div>\n </CollapsiblePanel>\n</template>\n\n<style scoped>\n.upload-panel {\n width: var(--kds-dimension-component-width-20x);\n max-width: var(--kds-dimension-component-width-20x);\n}\n\n.upload-panel-content {\n display: flex;\n flex-direction: column;\n}\n\n.retry-bar {\n display: flex;\n justify-content: flex-end;\n padding: var(--kds-spacing-container-0-5x) var(--kds-spacing-container-0-75x);\n border-top: var(--kds-border-base-subtle);\n}\n\n.skeleton-container {\n display: grid;\n grid-template-columns: min-content 1fr;\n gap: var(--kds-spacing-container-0-25x);\n align-items: center;\n min-height: var(--kds-dimension-component-height-4x);\n padding: 0 var(--kds-spacing-container-1x);\n border-top: var(--kds-border-base-subtle);\n\n & .skeleton-text-content {\n display: flex;\n flex-direction: column;\n gap: var(--kds-spacing-container-0-25x);\n }\n}\n</style>\n","import { computed, ref } from \"vue\";\n\nimport { type ChunkUploadManagerConfig, createUploadManager } from \"../core\";\n\nimport type { FileUploadItem, UploadItem } from \"./types\";\n\ntype OnCompletePayload = Parameters<\n NonNullable<ChunkUploadManagerConfig[\"onFileUploadComplete\"]>\n>[0] & { parentId: string; name: string };\n\ntype OnFailedPayload = Parameters<\n NonNullable<ChunkUploadManagerConfig[\"onFileUploadFailed\"]>\n>[0] & { parentId: string };\n\ntype UseUploadManagerOptions = Omit<\n ChunkUploadManagerConfig,\n \"onProgress\" | \"onFileUploadComplete\" | \"onFileUploadFailed\"\n> & {\n onFileUploadComplete?: (payload: OnCompletePayload) => void;\n onFileUploadFailed?: (payload: OnFailedPayload) => void;\n /**\n * Determines the status to assign a file once its raw upload finishes,\n * before calling `onFileUploadComplete`. This helps for files that need further\n * server-side processing\n *\n * Defaults to `complete`\n */\n resolveCompleteStatus?: (\n payload: OnCompletePayload,\n ) => \"complete\" | \"processing\";\n};\n\n/**\n * This composable wraps the `uploadManager` utility\n * in order to provide reactive state which will work tightly with the `UploadProgressPanel` component\n *\n * The reactive state returned (`uploadItems`) will update whenever the progress\n * or status of each item being uploaded changes\n */\nexport const useUploadManager = (options: UseUploadManagerOptions) => {\n const { resolveCompleteStatus, ...restOptions } = options;\n const uploadState = ref<Record<string, UploadItem>>({});\n\n const { uploadFiles, cancel, setFailed } = createUploadManager({\n ...restOptions,\n\n onFileUploadComplete: (params) => {\n const { parentId, name } = uploadState.value[params.uploadId];\n const payload = { ...params, parentId, name };\n uploadState.value[params.uploadId].status =\n resolveCompleteStatus?.(payload) ?? \"complete\";\n options.onFileUploadComplete?.(payload);\n },\n\n onFileUploadFailed: ({ uploadId, error }) => {\n const { parentId } = uploadState.value[uploadId];\n uploadState.value[uploadId] = {\n ...uploadState.value[uploadId],\n status: \"failed\",\n failureDetails: error.message,\n };\n options.onFileUploadFailed?.({ uploadId, error, parentId });\n },\n\n onProgress: (uploadId, progress) => {\n if (uploadState.value[uploadId].status === \"inprogress\") {\n (uploadState.value[uploadId] as FileUploadItem).progress = progress;\n }\n },\n });\n\n const start = async (\n parentId: string,\n uploadPayload: Array<{ uploadId: string; file: File }>,\n ) => {\n try {\n uploadPayload.forEach(({ uploadId, file }) => {\n uploadState.value[uploadId] = {\n id: uploadId,\n name: file.name,\n size: file.size,\n progress: 0,\n type: \"file\",\n status: \"inprogress\",\n parentId,\n };\n });\n\n return await uploadFiles(uploadPayload);\n } catch (error) {\n consola.error(\"Error uploading files\", error);\n return [];\n }\n };\n\n const canCancel = (uploadId: string) =>\n uploadState.value[uploadId]?.status === \"inprogress\" ||\n uploadState.value[uploadId]?.status === \"failed\";\n\n const removeItem = (uploadId: string) => {\n if (canCancel(uploadId)) {\n cancel(uploadId);\n }\n\n delete uploadState.value[uploadId];\n };\n\n const resetState = () => {\n uploadState.value = {};\n };\n\n const uploadItems = computed(() => Object.values(uploadState.value));\n\n const hasPendingUploads = computed(() =>\n uploadItems.value.some(({ status }) => status === \"inprogress\"),\n );\n\n const totalPendingUploads = computed(\n () =>\n uploadItems.value.filter(({ status }) => status === \"inprogress\").length,\n );\n\n return {\n uploadState,\n hasPendingUploads,\n totalPendingUploads,\n start,\n cancel: (uploadId: string) => {\n cancel(uploadId);\n uploadState.value[uploadId].status = \"cancelled\";\n },\n canCancel,\n removeItem,\n resetState,\n setFailed: (uploadId: string, error: Error) => {\n uploadState.value[uploadId] = {\n ...uploadState.value[uploadId],\n status: \"failed\",\n failureDetails: error.message,\n };\n setFailed(uploadId);\n const { parentId } = uploadState.value[uploadId];\n options.onFileUploadFailed?.({ parentId, uploadId, error });\n },\n };\n};\n","import { computed, ref } from \"vue\";\nimport type { FetchError, FetchOptions } from \"ofetch\";\n\nimport { formatFileSize, knimeFileFormats, promise } from \"@knime/utils\";\n\nimport { rfcErrors } from \"../../rfcErrors\";\nimport { createUploadClient, createUploadStatusPoller } from \"../core\";\n\nimport type { UploadItem } from \"./types\";\nimport { useUploadManager } from \"./useUploadManager\";\n\nconst DEFAULT_MAX_UPLOAD_QUEUE_SIZE = 10;\n// eslint-disable-next-line no-magic-numbers\nconst DEFAULT_UPLOAD_SIZE_LIMIT_BYTES = 5 * 1024 * 1024 * 1024;\n\nconst DEFAULT_RETRY_DELAY_MS = 50;\n\n// Catalog & artifacts services currently have a limit for 5 GiB for\n// upload and copy/move operations but it is not communicated to clients.\n// Repeat the constant here to be able to still make a pre-check. See NXT-4510.\nconst getUploadSizeLimitBytes = () => DEFAULT_UPLOAD_SIZE_LIMIT_BYTES;\n\ntype UseFileUploadOptions = {\n /**\n * Max number of concurrent uploads allowed in the upload queue.\n * When an upload is started with a number of files that exceed the pending\n * uploads then only files up-to the max queue size will be processed and the rest\n * will be ignored. This will also call the `onUploadQueueSizeExceeded` callback\n */\n maxUploadQueueSize?: number;\n /**\n * Callback that triggers every time any file in the queue completes its upload\n * @returns\n */\n onFileUploadComplete?: (payload: {\n uploadId: string;\n filePartIds: Record<number, string>;\n parentId: string;\n name: string;\n }) => void;\n /**\n * Callback that triggers every time any file in the queue fails its upload\n * @returns\n */\n onFileUploadFailed?: (payload: {\n uploadId: string;\n error: Error;\n parentId: string;\n }) => void;\n /**\n * Callback that triggers when a upload batch is started (via the `start` function) but the amount\n * of items to upload exceed the max configured size of the queue.\n * @returns\n */\n onUploadQueueSizeExceeded?: (maxQueueSize: number) => void;\n /**\n * Options to customize the fetch client used internally to make http requests.\n * e.g: headers, baseUrl, etc\n */\n customFetchClientOptions?: FetchOptions;\n\n /**\n * Whether the manifest endpoint is flat (`/manifest`) rather than nested\n * under a repository item (`/repository/{parentId}/manifest`). Used for\n * targets whose upload endpoint isn't scoped by a repository parent,\n * e.g. environment extension DB driver uploads.\n */\n shouldUseFlatManifestPath?: boolean;\n\n isFileWithProcessing?: (fileName: string) => boolean;\n};\n\n/**\n * This composable handles uploads of data to a hub repository item (space or workflow group).\n * It is a multi-step process which is described as follows:\n *\n * Step 1:\n * First we need to prepare an upload. The items will be uploaded to a `parent` container\n * identified by its id. (When `shouldUseFlatManifestPath` is set, the manifest endpoint isn't\n * scoped by a parent, so the `parentId` passed to `start` is unused for that request.)\n * Then, for each file we want to upload, we must provide paths relative\n * to the parent that identify each file. Example:\n * ```\n * {\n * \"items\": {\n * \"my-file.txt\": { itemContentType: \"text/plain\", itemContentSize: 5000 },\n * \"folder/my-file.zip\": { itemContentType: \"application/zip\", itemContentSize: 5000 },\n * }\n * }\n * ```\n * This describes 2 items, one placed in the parent's root and another inside a folder.\n * The response for this would look like:\n * ```\n * {\n * \"items\": {\n * \"my-file.txt\": { uploadId: \"id1\" },\n * \"folder/my-file.zip\": { uploadId: \"id2\" },\n * }\n * }\n * ```\n * Now, each corresponding `uploadId` uniquely identifies the upload process for\n * their respective files.\n *\n * Step 2:\n * Next, we need to start uploading the file parts. Technically, you _can_ just upload\n * each file as one big file \"part\", but practically that is not recommended because a hiccup\n * in the connection means you would have to start the upload from scratch; instead, we split\n * the files in smaller parts.\n * The upload manager helper will take care of splitting the file. However, the responsibility\n * of this composable is to supply a function to resolve the upload URL. This upload URL is obtained\n * by POSTing to an endpoint with the `uploadId` and the `partNumber` and this will return\n * the data required to make an upload to a presigned URL; this will be called for each file part.\n *\n * Step 3:\n * Completion:\n * Once the upload of a single file is complete, the upload manager will trigger the `onFileUploadComplete`\n * callback, which then will supply the ids of all the different parts that got uploaded for a given `uploadId`\n * Then we need to \"complete\" the upload by sending a request which provides the `uploadId` and a list of\n * the ids of all the parts. Note that some backends don't return the ids of the file parts after a successful\n * upload of a part, in this case empty strings or null can be used instead of the part id.\n *\n * Cancellation:\n * Alternative to completion, you can also cancel an upload. For this, the composable makes a DELETE request\n * providing the corresponding `uploadId` which will effectively cancel it.\n *\n * For a usage example, refer to the Docs page in storybook.\n */\nexport const useFileUpload = (options: UseFileUploadOptions = {}) => {\n const uploadClient = createUploadClient({\n customFetchClientOptions: options.customFetchClientOptions,\n shouldUseFlatManifestPath: options.shouldUseFlatManifestPath,\n });\n\n let useUploadManagerResult: ReturnType<typeof useUploadManager> | null = null;\n\n const getItemFromQueue = (uploadId: string) => {\n const { uploadState } = useUploadManagerResult!;\n\n if (!uploadState.value[uploadId]) {\n consola.warn(\n `Cannot get item in queue. Upload id \"${uploadId}\" doesn't exist`,\n );\n return undefined;\n }\n\n return uploadState.value[uploadId];\n };\n\n const updateItemInQueue = (uploadId: string, data: Partial<UploadItem>) => {\n const { uploadState } = useUploadManagerResult!;\n\n if (!uploadState.value[uploadId]) {\n consola.warn(\n `Cannot update item in queue. Upload id \"${uploadId}\" doesn't exist`,\n );\n return;\n }\n\n uploadState.value = {\n ...uploadState.value,\n [uploadId]: {\n ...uploadState.value[uploadId],\n ...data,\n } as UploadItem,\n };\n };\n\n const statusPoller = createUploadStatusPoller(uploadClient, {\n onComplete: (uploadId) =>\n updateItemInQueue(uploadId, { status: \"complete\" }),\n\n onFail: (uploadId, statusMessage) =>\n updateItemInQueue(uploadId, {\n status: \"failed\",\n failureDetails: statusMessage,\n }),\n });\n\n useUploadManagerResult = useUploadManager({\n resolveFilePartUploadURL: uploadClient.resolveFilePartUploadURL,\n\n resolveCompleteStatus: ({ name }) =>\n options.isFileWithProcessing?.(name) ? \"processing\" : \"complete\",\n\n onFileUploadComplete: ({ uploadId, filePartIds, name, parentId }) => {\n promise\n .retryPromise({\n fn: () => uploadClient.completeUpload(uploadId, filePartIds),\n })\n .then(() => {\n if (options.isFileWithProcessing?.(name)) {\n statusPoller.track(uploadId);\n }\n\n options.onFileUploadComplete?.({\n uploadId,\n filePartIds,\n name,\n parentId,\n });\n })\n .catch((error) => {\n consola.error(\"Error attempting to complete upload\", { error });\n useUploadManagerResult!.setFailed(uploadId, error);\n });\n },\n\n onFileUploadFailed: ({ uploadId, error, parentId }) => {\n options?.onFileUploadFailed?.({ uploadId, error, parentId });\n },\n });\n\n const getEnqueueableFiles = (files: File[]): File[] => {\n const { totalPendingUploads } = useUploadManagerResult;\n\n const { maxUploadQueueSize = DEFAULT_MAX_UPLOAD_QUEUE_SIZE } = options;\n\n if (files.length + totalPendingUploads.value > maxUploadQueueSize) {\n options.onUploadQueueSizeExceeded?.(maxUploadQueueSize);\n }\n\n // only keep as many files as the queue allows\n const effectiveQueueSize = Math.max(\n 0,\n maxUploadQueueSize - totalPendingUploads.value,\n );\n return files.slice(0, effectiveQueueSize);\n };\n\n const { uploadState, ...uploadManagerResultRest } = useUploadManagerResult;\n\n const prepareQueueSize = ref(0);\n\n const uploadQueue = computed<UploadItem[]>(() =>\n Object.values(uploadState.value),\n );\n\n return {\n ...uploadManagerResultRest,\n\n uploadQueue,\n getItemFromQueue,\n updateItemInQueue,\n\n isPreparingUpload: computed(() => prepareQueueSize.value > 0),\n totalFilesBeingPrepared: computed(() => prepareQueueSize.value),\n\n start: async (parentId: string, files: File[]) => {\n const enqueueableFiles = getEnqueueableFiles(files);\n const uploadSizeLimitBytes = getUploadSizeLimitBytes();\n const oversizedFiles = enqueueableFiles.filter(\n (file) => file.size > uploadSizeLimitBytes,\n );\n\n if (oversizedFiles.length > 0) {\n throw new rfcErrors.RFCError({\n title: \"Upload size limit exceeded\",\n status: 413,\n details: oversizedFiles.map(\n (file) =>\n `${file.name} (${formatFileSize(file.size)}) exceeds the upload limit of ${formatFileSize(uploadSizeLimitBytes)}.`,\n ),\n });\n }\n\n try {\n if (enqueueableFiles.length === 0) {\n return [];\n }\n\n prepareQueueSize.value += enqueueableFiles.length;\n\n const items = enqueueableFiles.map((file) => ({\n // strip extension for workflows. otherwise, the extension gets baked into the name\n // of the uploaded file and the backend appends an extra extension.\n // unnecessary for other file types\n path: knimeFileFormats.KNWF.getNameOrDefault(file, file.name),\n file,\n }));\n\n const uploadPayload = await promise.retryPromise({\n fn: () => uploadClient.prepareUpload(parentId, items),\n excludeError: (error: FetchError) =>\n // eslint-disable-next-line no-magic-numbers\n Boolean(error.statusCode && error.statusCode < 500),\n retryDelayMS: DEFAULT_RETRY_DELAY_MS,\n });\n\n const uploadIds = uploadPayload.map((upload) => upload.uploadId);\n\n // accept floating promise, errors are handled inside the function\n useUploadManagerResult.start(parentId, uploadPayload);\n\n return uploadIds;\n } catch (error) {\n throw rfcErrors.tryParse(error) ?? error;\n } finally {\n // errors can only be thrown in the prepareUpload call, useUploadManagerResult.start does its own error handling\n prepareQueueSize.value -= enqueueableFiles.length;\n }\n },\n\n cancel: (uploadId: string) => {\n useUploadManagerResult.cancel(uploadId);\n uploadClient.cancelUpload(uploadId).catch((error: unknown) => {\n consola.error(\"There was a problem cancelling the upload\", { error });\n });\n },\n };\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"createHttpClient.d.ts","sourceRoot":"","sources":["../../../../../src/dataTransfer/shared/httpClient/createHttpClient.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,YAAY,EAAU,MAAM,QAAQ,CAAC;AAInD,QAAA,MAAM,aAAa,EAAE,YAQX,CAAC;AAEX,QAAA,MAAM,gBAAgB,GAAI,gBAAgB,YAAY,4BAMrD,CAAC;AAEF,OAAO,EAAE,KAAK,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,CAAC"}
1
+ {"version":3,"file":"createHttpClient.d.ts","sourceRoot":"","sources":["../../../../../src/dataTransfer/shared/httpClient/createHttpClient.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,YAAY,EAAU,MAAM,QAAQ,CAAC;AAInD,QAAA,MAAM,aAAa,EAAE,YAQX,CAAC;AAEX,QAAA,MAAM,gBAAgB,GAAI,gBAAgB,YAAY,4BAIrD,CAAC;AAEF,OAAO,EAAE,KAAK,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,CAAC"}
@@ -9,11 +9,13 @@ declare const __VLS_export: import('vue').DefineComponent<__VLS_PublicProps, {},
9
9
  cancel: (item: UploadItem) => any;
10
10
  "update:expanded": (value: boolean) => any;
11
11
  viewDetails: (item: UploadItem) => any;
12
+ retry: () => any;
12
13
  }, string, import('vue').PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
13
14
  onClose?: (() => any) | undefined;
14
15
  onCancel?: ((item: UploadItem) => any) | undefined;
15
16
  "onUpdate:expanded"?: ((value: boolean) => any) | undefined;
16
17
  onViewDetails?: ((item: UploadItem) => any) | undefined;
18
+ onRetry?: (() => any) | undefined;
17
19
  }>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
18
20
  declare const _default: typeof __VLS_export;
19
21
  export default _default;
@@ -1 +1 @@
1
- {"version":3,"file":"UploadProgressPanel.vue.d.ts","sourceRoot":"","sources":["../../../../src/dataTransfer/upload/UploadProgressPanel.vue"],"names":[],"mappings":"AA8GA,OAAO,KAAK,EAAE,UAAU,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAC;AAEpE,KAAK,WAAW,GAAG,wBAAwB,CAAC;AAkC5C,KAAK,gBAAgB,GAAG;IACxB,UAAU,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAKF,KAAK,iBAAiB,GAAG,WAAW,GAAG,gBAAgB,CAAC;AAqKxD,QAAA,MAAM,YAAY;;;;;;;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
1
+ {"version":3,"file":"UploadProgressPanel.vue.d.ts","sourceRoot":"","sources":["../../../../src/dataTransfer/upload/UploadProgressPanel.vue"],"names":[],"mappings":"AAqIA,OAAO,KAAK,EAAE,UAAU,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAC;AAEpE,KAAK,WAAW,GAAG,wBAAwB,CAAC;AAuC5C,KAAK,gBAAgB,GAAG;IACxB,UAAU,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAKF,KAAK,iBAAiB,GAAG,WAAW,GAAG,gBAAgB,CAAC;AA2MxD,QAAA,MAAM,YAAY;;;;;;;;;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
@@ -25,6 +25,13 @@ type CreateUploadClientOptions = {
25
25
  * e.g: headers, baseUrl, etc
26
26
  */
27
27
  customFetchClientOptions?: FetchOptions;
28
+ /**
29
+ * Whether the manifest endpoint is flat (`/manifest`) rather than nested
30
+ * under a repository item (`/repository/{parentId}/manifest`). Used for
31
+ * targets whose upload endpoint isn't scoped by a repository parent,
32
+ * e.g. environment extension DB driver uploads.
33
+ */
34
+ shouldUseFlatManifestPath?: boolean;
28
35
  };
29
36
  export type UploadClient = ReturnType<typeof createUploadClient>;
30
37
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"uploadClient.d.ts","sourceRoot":"","sources":["../../../../../../src/dataTransfer/upload/core/uploadClient/uploadClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAM3C;;;;;;GAMG;AACH,KAAK,eAAe,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpE,KAAK,iBAAiB,CAAC,CAAC,SAAS,eAAe,IAAI;IAClD;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,CAAC,CAAC;CACT,CAAC;AAEF,KAAK,yBAAyB,GAAG;IAC/B;;;OAGG;IACH,wBAAwB,CAAC,EAAE,YAAY,CAAC;CACzC,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEjE;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,GAAI,UAAS,yBAA8B;oBAGjD,CAAC,SAAS,eAAe,YACpC,MAAM,SACT,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,KACjC,OAAO,CAAC,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,CAAC,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;yCAsClB,MAAM,cAAc,MAAM;gBAE1D,MAAM;aACT,MAAM;gBACH;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE;;+BAUhB,MAAM,WACP,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;6BAQD,MAAM;iCAMF,MAAM,EAAE;kBAE9B,MAAM;gBAEd,UAAU,GAAG,kBAAkB,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ;uBACvD,MAAM;;CAkB1B,CAAC"}
1
+ {"version":3,"file":"uploadClient.d.ts","sourceRoot":"","sources":["../../../../../../src/dataTransfer/upload/core/uploadClient/uploadClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAM3C;;;;;;GAMG;AACH,KAAK,eAAe,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpE,KAAK,iBAAiB,CAAC,CAAC,SAAS,eAAe,IAAI;IAClD;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,CAAC,CAAC;CACT,CAAC;AAEF,KAAK,yBAAyB,GAAG;IAC/B;;;OAGG;IACH,wBAAwB,CAAC,EAAE,YAAY,CAAC;IAExC;;;;;OAKG;IACH,yBAAyB,CAAC,EAAE,OAAO,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEjE;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,GAAI,UAAS,yBAA8B;oBAGjD,CAAC,SAAS,eAAe,YACpC,MAAM,SACT,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,KACjC,OAAO,CAAC,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,CAAC,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;yCA0ClB,MAAM,cAAc,MAAM;gBAE1D,MAAM;aACT,MAAM;gBACH;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE;;+BAUhB,MAAM,WACP,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;6BAQD,MAAM;iCAMF,MAAM,EAAE;kBAE9B,MAAM;gBAEd,UAAU,GAAG,kBAAkB,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ;uBACvD,MAAM;;CAkB1B,CAAC"}
@@ -75,6 +75,11 @@ export type UploadProgressPanelProps = {
75
75
  * Indicates the number of items to show as placeholder skeletons
76
76
  */
77
77
  placeholderItems?: number;
78
+ /**
79
+ * Whether to offer the retry action. The consumer decides which of its items
80
+ * can be retried; the panel only renders the action and emits `retry`.
81
+ */
82
+ canRetry?: boolean;
78
83
  };
79
84
  export {};
80
85
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/dataTransfer/upload/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,eAAe,EAAgB,MAAM,SAAS,CAAC;AAE7D,MAAM,MAAM,gBAAgB,GAC1B,OAAO,CAAC,eAAe,EAAE,MAAM,CAAC,GAAG,YAAY,GAAG,iBAAiB,CAAC;AAItE,QAAA,MAAM,gBAAgB;;IAGpB,2EAA2E;;;;;;CAE5E,CAAC;AAEF,KAAK,WAAW,GAAG;IACjB,8CAA8C;IAC9C,EAAE,EAAE,MAAM,CAAC;IACX,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAC;IAEjB,OAAO,CAAC,EAAE;QACR,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,WAAW,CAAC,EAAE,OAAO,CAAC;KACvB,CAAC;CACH,CAAC;AAEF,KAAK,cAAc,GAAG;IACpB,MAAM,EAAE,OAAO,gBAAgB,CAAC,WAAW,CAAC;CAC7C,CAAC;AAEF,KAAK,YAAY,GAAG;IAClB,MAAM,EAAE,OAAO,gBAAgB,CAAC,QAAQ,CAAC;CAC1C,CAAC;AAEF,KAAK,aAAa,GAAG;IACnB,8DAA8D;IAC9D,MAAM,EAAE,OAAO,gBAAgB,CAAC,SAAS,CAAC;CAC3C,CAAC;AAEF,KAAK,WAAW,GAAG;IACjB,MAAM,EAAE,OAAO,gBAAgB,CAAC,MAAM,CAAC;IACvC,2DAA2D;IAC3D,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,KAAK,cAAc,GAAG;IACpB,0EAA0E;IAC1E,MAAM,EAAE,OAAO,gBAAgB,CAAC,UAAU,CAAC;CAC5C,CAAC;AAEF,KAAK,kBAAkB,GAAG;IACxB;;;OAGG;IACH,MAAM,EAAE,OAAO,gBAAgB,CAAC,eAAe,CAAC;CACjD,CAAC;AAEF,KAAK,qBAAqB,GAAG;IAAE,eAAe,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/E,MAAM,MAAM,mBAAmB,GAAG,WAAW,GAAG;IAC9C,IAAI,EAAE,WAAW,CAAC;CACnB,GAAG,qBAAqB,GACvB,CACI,cAAc,GACd,YAAY,GACZ,WAAW,GACX,aAAa,GACb,kBAAkB,CACrB,CAAC;AAEJ,KAAK,gBAAgB,GAAG;IACtB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,gBAAgB,GAC5E,CACE,cAAc,GAAG,YAAY,GAAG,cAAc,GAAG,WAAW,GAAG,aAAa,CAC7E,CAAC;AAEJ,MAAM,MAAM,UAAU,GAAG,mBAAmB,GAAG,cAAc,CAAC;AAE9D,MAAM,MAAM,wBAAwB,GAAG;IACrC;;OAEG;IACH,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/dataTransfer/upload/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,eAAe,EAAgB,MAAM,SAAS,CAAC;AAE7D,MAAM,MAAM,gBAAgB,GAC1B,OAAO,CAAC,eAAe,EAAE,MAAM,CAAC,GAAG,YAAY,GAAG,iBAAiB,CAAC;AAItE,QAAA,MAAM,gBAAgB;;IAGpB,2EAA2E;;;;;;CAE5E,CAAC;AAEF,KAAK,WAAW,GAAG;IACjB,8CAA8C;IAC9C,EAAE,EAAE,MAAM,CAAC;IACX,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAC;IAEjB,OAAO,CAAC,EAAE;QACR,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,WAAW,CAAC,EAAE,OAAO,CAAC;KACvB,CAAC;CACH,CAAC;AAEF,KAAK,cAAc,GAAG;IACpB,MAAM,EAAE,OAAO,gBAAgB,CAAC,WAAW,CAAC;CAC7C,CAAC;AAEF,KAAK,YAAY,GAAG;IAClB,MAAM,EAAE,OAAO,gBAAgB,CAAC,QAAQ,CAAC;CAC1C,CAAC;AAEF,KAAK,aAAa,GAAG;IACnB,8DAA8D;IAC9D,MAAM,EAAE,OAAO,gBAAgB,CAAC,SAAS,CAAC;CAC3C,CAAC;AAEF,KAAK,WAAW,GAAG;IACjB,MAAM,EAAE,OAAO,gBAAgB,CAAC,MAAM,CAAC;IACvC,2DAA2D;IAC3D,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,KAAK,cAAc,GAAG;IACpB,0EAA0E;IAC1E,MAAM,EAAE,OAAO,gBAAgB,CAAC,UAAU,CAAC;CAC5C,CAAC;AAEF,KAAK,kBAAkB,GAAG;IACxB;;;OAGG;IACH,MAAM,EAAE,OAAO,gBAAgB,CAAC,eAAe,CAAC;CACjD,CAAC;AAEF,KAAK,qBAAqB,GAAG;IAAE,eAAe,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/E,MAAM,MAAM,mBAAmB,GAAG,WAAW,GAAG;IAC9C,IAAI,EAAE,WAAW,CAAC;CACnB,GAAG,qBAAqB,GACvB,CACI,cAAc,GACd,YAAY,GACZ,WAAW,GACX,aAAa,GACb,kBAAkB,CACrB,CAAC;AAEJ,KAAK,gBAAgB,GAAG;IACtB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,gBAAgB,GAC5E,CACE,cAAc,GAAG,YAAY,GAAG,cAAc,GAAG,WAAW,GAAG,aAAa,CAC7E,CAAC;AAEJ,MAAM,MAAM,UAAU,GAAG,mBAAmB,GAAG,cAAc,CAAC;AAE9D,MAAM,MAAM,wBAAwB,GAAG;IACrC;;OAEG;IACH,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC"}
@@ -38,6 +38,13 @@ type UseFileUploadOptions = {
38
38
  * e.g: headers, baseUrl, etc
39
39
  */
40
40
  customFetchClientOptions?: FetchOptions;
41
+ /**
42
+ * Whether the manifest endpoint is flat (`/manifest`) rather than nested
43
+ * under a repository item (`/repository/{parentId}/manifest`). Used for
44
+ * targets whose upload endpoint isn't scoped by a repository parent,
45
+ * e.g. environment extension DB driver uploads.
46
+ */
47
+ shouldUseFlatManifestPath?: boolean;
41
48
  isFileWithProcessing?: (fileName: string) => boolean;
42
49
  };
43
50
  /**
@@ -46,7 +53,9 @@ type UseFileUploadOptions = {
46
53
  *
47
54
  * Step 1:
48
55
  * First we need to prepare an upload. The items will be uploaded to a `parent` container
49
- * identified by its id. Then, for each file we want to upload, we must provide paths relative
56
+ * identified by its id. (When `shouldUseFlatManifestPath` is set, the manifest endpoint isn't
57
+ * scoped by a parent, so the `parentId` passed to `start` is unused for that request.)
58
+ * Then, for each file we want to upload, we must provide paths relative
50
59
  * to the parent that identify each file. Example:
51
60
  * ```
52
61
  * {
@@ -1 +1 @@
1
- {"version":3,"file":"useFileUpload.d.ts","sourceRoot":"","sources":["../../../../src/dataTransfer/upload/useFileUpload.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAc,YAAY,EAAE,MAAM,QAAQ,CAAC;AAOvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAc1C,KAAK,oBAAoB,GAAG;IAC1B;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,CAAC,OAAO,EAAE;QAC/B,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACpC,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;KACd,KAAK,IAAI,CAAC;IACX;;;OAGG;IACH,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE;QAC7B,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,KAAK,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;KAClB,KAAK,IAAI,CAAC;IACX;;;;OAIG;IACH,yBAAyB,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3D;;;OAGG;IACH,wBAAwB,CAAC,EAAE,YAAY,CAAC;IAExC,oBAAoB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;CACtD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AACH,eAAO,MAAM,aAAa,GAAI,UAAS,oBAAyB;;iCAO1B,MAAM;kCAaL,MAAM,QAAQ,OAAO,CAAC,UAAU,CAAC;;;sBAmG5C,MAAM,SAAS,IAAI,EAAE;uBAuD1B,MAAM;;;;;;;CAO5B,CAAC"}
1
+ {"version":3,"file":"useFileUpload.d.ts","sourceRoot":"","sources":["../../../../src/dataTransfer/upload/useFileUpload.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAc,YAAY,EAAE,MAAM,QAAQ,CAAC;AAOvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAc1C,KAAK,oBAAoB,GAAG;IAC1B;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,CAAC,OAAO,EAAE;QAC/B,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACpC,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;KACd,KAAK,IAAI,CAAC;IACX;;;OAGG;IACH,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE;QAC7B,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,KAAK,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;KAClB,KAAK,IAAI,CAAC;IACX;;;;OAIG;IACH,yBAAyB,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3D;;;OAGG;IACH,wBAAwB,CAAC,EAAE,YAAY,CAAC;IAExC;;;;;OAKG;IACH,yBAAyB,CAAC,EAAE,OAAO,CAAC;IAEpC,oBAAoB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;CACtD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,eAAO,MAAM,aAAa,GAAI,UAAS,oBAAyB;;iCAQ1B,MAAM;kCAaL,MAAM,QAAQ,OAAO,CAAC,UAAU,CAAC;;;sBAmG5C,MAAM,SAAS,IAAI,EAAE;uBAuD1B,MAAM;;;;;;;CAO5B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knime/product-features",
3
- "version": "2.3.1",
3
+ "version": "2.5.0",
4
4
  "description": "Package containing composition patterns of KDS Components and KNIME business logic",
5
5
  "repository": {
6
6
  "type": "git",
@@ -58,8 +58,8 @@
58
58
  },
59
59
  "dependencies": {
60
60
  "consola": "^3.4.2",
61
- "@knime/kds-components": "^7.0.0",
62
- "@knime/kds-styles": "^7.0.0",
61
+ "@knime/kds-components": "^7.2.3",
62
+ "@knime/kds-styles": "^7.2.3",
63
63
  "@knime/utils": "^2.0.2"
64
64
  },
65
65
  "devDependencies": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"core-CVWznjtP.js","names":["#length","#chunks"],"sources":["../../../node_modules/.pnpm/es-toolkit@1.49.0/node_modules/es-toolkit/dist/predicate/isPlainObject.mjs","../../../node_modules/.pnpm/es-toolkit@1.49.0/node_modules/es-toolkit/dist/_internal/isUnsafeProperty.mjs","../../../node_modules/.pnpm/es-toolkit@1.49.0/node_modules/es-toolkit/dist/object/merge.mjs","../src/dataTransfer/shared/httpClient/constants.ts","../src/dataTransfer/shared/httpClient/createHttpClient.ts","../src/dataTransfer/upload/core/uploadManager/config.ts","../src/dataTransfer/upload/core/uploadManager/state.ts","../src/dataTransfer/upload/core/uploadManager/uploadPartBytes.ts","../src/dataTransfer/upload/core/uploadManager/chunkedUploadManager.ts","../src/dataTransfer/upload/core/uploadManager/streamingUploadManager.ts","../src/dataTransfer/upload/core/uploadManager/types.ts","../src/dataTransfer/upload/core/uploadClient/uploadClient.ts","../../../node_modules/.pnpm/es-toolkit@1.49.0/node_modules/es-toolkit/dist/array/chunk.mjs","../src/dataTransfer/upload/core/uploadClient/createUploadStatusPoller.ts"],"sourcesContent":["//#region src/predicate/isPlainObject.ts\n/**\n* Checks if a given value is a plain object.\n*\n* @param value - The value to check.\n* @returns True if the value is a plain object, otherwise false.\n*\n* @example\n* ```typescript\n* // ✅👇 True\n*\n* isPlainObject({ }); // ✅\n* isPlainObject({ key: 'value' }); // ✅\n* isPlainObject({ key: new Date() }); // ✅\n* isPlainObject(new Object()); // ✅\n* isPlainObject(Object.create(null)); // ✅\n* isPlainObject({ nested: { key: true} }); // ✅\n* isPlainObject(new Proxy({}, {})); // ✅\n* isPlainObject({ [Symbol('tag')]: 'A' }); // ✅\n*\n* // ✅👇 (cross-realms, node context, workers, ...)\n* const runInNewContext = await import('node:vm').then(\n* (mod) => mod.runInNewContext\n* );\n* isPlainObject(runInNewContext('({})')); // ✅\n*\n* // ❌👇 False\n*\n* class Test { };\n* isPlainObject(new Test()) // ❌\n* isPlainObject(10); // ❌\n* isPlainObject(null); // ❌\n* isPlainObject('hello'); // ❌\n* isPlainObject([]); // ❌\n* isPlainObject(new Date()); // ❌\n* isPlainObject(new Uint8Array([1])); // ❌\n* isPlainObject(Buffer.from('ABC')); // ❌\n* isPlainObject(Promise.resolve({})); // ❌\n* isPlainObject(Object.create({})); // ❌\n* isPlainObject(new (class Cls {})); // ❌\n* isPlainObject(globalThis); // ❌,\n* ```\n*/\nfunction isPlainObject(value) {\n\tif (!value || typeof value !== \"object\") return false;\n\tconst proto = Object.getPrototypeOf(value);\n\tif (!(proto === null || proto === Object.prototype || Object.getPrototypeOf(proto) === null)) return false;\n\treturn Object.prototype.toString.call(value) === \"[object Object]\";\n}\n//#endregion\nexport { isPlainObject };\n","//#region src/_internal/isUnsafeProperty.ts\n/**\n* Checks if a property key is unsafe to modify directly.\n*\n* This function is used in functions like `merge` to prevent prototype pollution attacks\n* by identifying property keys that could modify the object's prototype chain or constructor.\n*\n* @param key - The property key to check\n* @returns `true` if the property is unsafe to modify directly, `false` otherwise\n* @internal\n*/\nfunction isUnsafeProperty(key) {\n\treturn key === \"__proto__\";\n}\n//#endregion\nexport { isUnsafeProperty };\n","import { isPlainObject } from \"../predicate/isPlainObject.mjs\";\nimport { isUnsafeProperty } from \"../_internal/isUnsafeProperty.mjs\";\n//#region src/object/merge.ts\n/**\n* Merges the properties of the source object into the target object.\n*\n* This function performs a deep merge, meaning nested objects and arrays are merged recursively.\n* If a property in the source object is an array or an object and the corresponding property in the target object is also an array or object, they will be merged.\n* If a property in the source object is undefined, it will not overwrite a defined property in the target object.\n*\n* Note that this function mutates the target object.\n*\n* @param target - The target object into which the source object properties will be merged. This object is modified in place.\n* @param source - The source object whose properties will be merged into the target object.\n* @returns The updated target object with properties from the source object merged in.\n*\n* @template T - Type of the target object.\n* @template S - Type of the source object.\n*\n* @example\n* const target = { a: 1, b: { x: 1, y: 2 } };\n* const source = { b: { y: 3, z: 4 }, c: 5 };\n*\n* const result = merge(target, source);\n* console.log(result);\n* // Output: { a: 1, b: { x: 1, y: 3, z: 4 }, c: 5 }\n*\n* @example\n* const target = { a: [1, 2], b: { x: 1 } };\n* const source = { a: [3], b: { y: 2 } };\n*\n* const result = merge(target, source);\n* console.log(result);\n* // Output: { a: [3, 2], b: { x: 1, y: 2 } }\n*\n* @example\n* const target = { a: null };\n* const source = { a: [1, 2, 3] };\n*\n* const result = merge(target, source);\n* console.log(result);\n* // Output: { a: [1, 2, 3] }\n*/\nfunction merge(target, source) {\n\tconst sourceKeys = Object.keys(source);\n\tfor (let i = 0; i < sourceKeys.length; i++) {\n\t\tconst key = sourceKeys[i];\n\t\tif (isUnsafeProperty(key)) continue;\n\t\tconst sourceValue = source[key];\n\t\tconst targetValue = target[key];\n\t\tif (isMergeableValue(sourceValue) && isMergeableValue(targetValue)) target[key] = merge(targetValue, sourceValue);\n\t\telse if (Array.isArray(sourceValue)) target[key] = merge([], sourceValue);\n\t\telse if (isPlainObject(sourceValue)) target[key] = merge({}, sourceValue);\n\t\telse if (targetValue === void 0 || sourceValue !== void 0) target[key] = sourceValue;\n\t}\n\treturn target;\n}\nfunction isMergeableValue(value) {\n\treturn isPlainObject(value) || Array.isArray(value);\n}\n//#endregion\nexport { merge };\n","export const API_BASE_URL = \"/_/api\";\n","import { merge } from \"es-toolkit/object\";\nimport { type FetchOptions, ofetch } from \"ofetch\";\n\nimport { API_BASE_URL } from \"./constants\";\n\nconst defaultConfig: FetchOptions = {\n baseURL: API_BASE_URL,\n headers: {\n \"Content-Type\": \"application/json\",\n\n // see: https://knime-com.atlassian.net/wiki/spaces/SPECS/pages/4126769212/API+Errors#Client-Request-Headers\n Accept: \"application/json; application/problem+json\",\n },\n} as const;\n\nconst createHttpClient = (customOptions?: FetchOptions) => {\n const { baseURL = API_BASE_URL, ...otherCustomOptions } = customOptions ?? {};\n\n return ofetch.create(\n merge({ baseURL, ...otherCustomOptions }, defaultConfig),\n );\n};\n\nexport { type FetchOptions, defaultConfig, createHttpClient };\n","/**\n * Configuration constants shared by the upload managers\n */\nexport const CONFIG = Object.freeze({\n /** Size, in bytes, of each uploaded file part. */\n CHUNK_SIZE: 50 * 1024 * 1024, // 50 MB per chunk\n /** Max number of retries for a failed request (part-url resolution or the chunk upload itself). */\n DEFAULT_RETRY_COUNT: 5,\n /** Delay, in ms, between retries. */\n DEFAULT_RETRY_DELAY_MS: 50,\n});\n","import { promise as promiseUtils } from \"@knime/utils\";\n\nimport type { UploadStateName } from \"./types\";\n\ntype UploadState = {\n uploadId: string;\n fileDescriptor?: { name: string; type: string };\n completedChunkCount: number;\n uploadedByteCount: number;\n state: UploadStateName;\n abortController: AbortController;\n};\n\nexport const createStateManager = () => {\n const uploadStateMap = new Map<string, UploadState>();\n\n const completedUploads = new Set<string>();\n const cancelledUploads = new Set<string>();\n const failedUploads = new Set<string>();\n\n const assertState = (uploadId: string, state: UploadStateName) =>\n uploadStateMap.has(uploadId) &&\n uploadStateMap.get(uploadId)!.state === state;\n\n const getUploadState = (uploadId: string) => uploadStateMap.get(uploadId);\n const setUploadState = (uploadId: string, state: Partial<UploadState>) => {\n uploadStateMap.set(uploadId, {\n ...uploadStateMap.get(uploadId)!,\n ...state,\n });\n };\n\n const isFailed = (uploadId: string) => assertState(uploadId, \"failed\");\n const isCancelled = (uploadId: string) => assertState(uploadId, \"cancelled\");\n\n const abortInFlightUpload = (uploadId: string, reason: string) => {\n const currentState = uploadStateMap.get(uploadId);\n\n if (!currentState) {\n return;\n }\n\n const message = currentState.fileDescriptor\n ? `Upload (${currentState.uploadId}) for file \"${currentState.fileDescriptor.name}\" aborted. Reason: \"${reason}\"`\n : `Upload (${currentState.uploadId}) aborted. Reason: \"${reason}\"`;\n\n currentState.abortController.abort(new promiseUtils.AbortError(message));\n };\n\n const cancel = (uploadId: string) => {\n if (!uploadStateMap.has(uploadId)) {\n consola.error(\"cancel:: Invalid upload id\");\n return;\n }\n\n setUploadState(uploadId, { state: \"cancelled\" });\n\n abortInFlightUpload(uploadId, \"upload cancelled\");\n cancelledUploads.add(uploadId);\n };\n\n const cancelAll = () => {\n for (const [id, upload] of uploadStateMap.entries()) {\n if (upload.state === \"inprogress\") {\n cancel(id);\n }\n }\n };\n\n const setCompleted = (uploadId: string, completedChunkCount: number) => {\n setUploadState(uploadId, {\n completedChunkCount,\n state: \"complete\",\n });\n completedUploads.add(uploadId);\n };\n\n const setFailed = (uploadId: string) => {\n if (!uploadStateMap.has(uploadId)) {\n consola.error(\"failed:: Invalid upload id\");\n return;\n }\n\n setUploadState(uploadId, { state: \"failed\" });\n failedUploads.add(uploadId);\n\n if (completedUploads.has(uploadId)) {\n completedUploads.delete(uploadId);\n }\n };\n\n const flush = () => {\n // state cleanup\n const completed = [...completedUploads.values()];\n const cancelled = [...cancelledUploads.values()];\n const failed = [...failedUploads.values()];\n\n [...completed, ...cancelled, ...failed].forEach((uploadId) =>\n uploadStateMap.delete(uploadId),\n );\n\n completedUploads.clear();\n cancelledUploads.clear();\n failedUploads.clear();\n\n return {\n completed,\n cancelled,\n failed,\n };\n };\n\n return {\n get: getUploadState,\n set: setUploadState,\n isFailed,\n isCancelled,\n cancel,\n cancelAll,\n setCompleted,\n setFailed,\n abortInFlightUpload,\n flush,\n };\n};\n","export type PartUploadResponse = {\n partId: string;\n};\n\n/**\n * Uploads a single file part via XHR, reporting upload progress and\n * resolving with the part's identifying ETag (or an empty string if the\n * backend doesn't return one).\n */\nexport const uploadPartBytesWithProgress = (params: {\n method: string;\n url: string;\n partBytes: Blob;\n partIndex: number;\n onProgress: (progress: number) => void;\n abortSignal: AbortSignal;\n}): Promise<PartUploadResponse> => {\n const { method, url, partBytes, partIndex, onProgress, abortSignal } = params;\n\n const OK = 200;\n const CREATED = 201;\n\n return new Promise<PartUploadResponse>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n\n xhr.open(method, url, true);\n\n abortSignal.addEventListener(\"abort\", () => xhr.abort());\n\n // Track the upload progress of the current part\n xhr.upload.onprogress = (event) => {\n if (event.lengthComputable) {\n onProgress(event.loaded);\n }\n };\n\n xhr.onload = () => {\n if (xhr.status === OK || xhr.status === CREATED) {\n const etag = xhr.getResponseHeader(\"ETag\");\n\n // 1. note that some backends don't return the etag header, then we just set an empty string\n // 2. remove possible doublequotes (\") around the etag and/or weak identifier (if either is present)\n // https://datatracker.ietf.org/doc/html/rfc7232#section-2.3\n resolve({ partId: etag?.replace(/^(?:W\\/)?\"|\"$/g, \"\") ?? \"\" });\n } else {\n consola.error(\"Failed or unexpected XHR response\", {\n xhrStatus: xhr.status,\n xhrResponse: xhr.response,\n partIndex,\n });\n reject(new Error(\"Failed to upload file\"));\n }\n };\n\n xhr.onerror = (event) => {\n consola.error(\"Failed to upload part\", {\n xhrErrorEvent: event,\n partIndex,\n });\n reject(new Error(\"Failed to upload file\"));\n };\n\n xhr.send(partBytes);\n });\n};\n","import { promise as promiseUtils } from \"@knime/utils\";\n\nimport { CONFIG } from \"./config\";\nimport { createStateManager } from \"./state\";\nimport type { BaseUploadManagerConfig } from \"./types\";\nimport { uploadPartBytesWithProgress } from \"./uploadPartBytes\";\n\nexport type ChunkUploadManagerConfig = BaseUploadManagerConfig & {\n /**\n * Callback that gets triggered when the progress of a file part upload\n * for a specific upload id is updated\n * @param uploadId\n * @param progress\n * @returns\n */\n onProgress?: (uploadId: string, progress: number) => void;\n};\n\n/**\n * Creates and returns an `uploadManager`, a comprehensive multi-file, multi-part upload handler.\n *\n * The `uploadManager` offers functionality for handling file uploads in parts and provides\n * several built-in mechanisms to ensure the process is reliable and adaptable.\n *\n * Key features include:\n *\n * - **Chunked File Uploads**: Splits provided files into manageable chunks and initiates the upload\n * for each part using the `resolveFilePartUploadURL` function provided in the configuration.\n *\n * - **Retry Mechanism**: Automatically retries failed upload attempts up to a maximum of 5 times\n * per failed request.\n *\n * - **Upload Cancellation**: Allows cancellation of ongoing uploads, giving users control over the process.\n *\n * - **Progress Tracking**: Monitors the progress of each file part upload and triggers the `onProgress`\n * callback to provide updates.\n *\n * - **Error Handling**: The returned `uploadFiles` function does not throw errors directly. Instead,\n * it manages retries and stops any failed uploads after the retry limit is reached, marking them as failed.\n * This ensures that the promise it returns completes gracefully without uncaught exceptions.\n *\n * This is the \"slice-based\" manager: it needs each file's complete bytes to already be\n * available (as a real `File`/`Blob` with a known `.size`), since it produces each part via\n * `file.slice()`. For uploading from a source whose total size isn't known upfront (e.g. a\n * stream of compressed output), see `createStreamingUploadManager` instead.\n */\nexport const createUploadManager = (config: ChunkUploadManagerConfig) => {\n const {\n resolveFilePartUploadURL: getUploadFilePartUrl,\n onFileUploadComplete,\n onFileUploadFailed,\n } = config;\n const state = createStateManager();\n\n const getTotalFileParts = (file: File) =>\n Math.ceil(file.size / CONFIG.CHUNK_SIZE);\n\n const uploadFileFromChunkIndex = async (uploadId: string, file: File) => {\n const totalChunks = getTotalFileParts(file);\n const totalBytes = file.size;\n const completedPartIds: Record<number, string> = {};\n\n const { abortController, runAbortablePromise } =\n promiseUtils.createAbortablePromise();\n\n state.set(uploadId, {\n uploadId,\n fileDescriptor: { name: file.name, type: file.type },\n state: \"inprogress\",\n uploadedByteCount: state.get(uploadId)?.uploadedByteCount ?? 0,\n completedChunkCount: state.get(uploadId)?.completedChunkCount ?? 0,\n abortController,\n });\n\n let chunkIndex = 0;\n let uploadedBytes = state.get(uploadId)?.uploadedByteCount;\n\n while (chunkIndex < totalChunks) {\n if (state.isCancelled(uploadId) || state.isFailed(uploadId)) {\n break;\n }\n\n const start = chunkIndex * CONFIG.CHUNK_SIZE;\n const end = Math.min(start + CONFIG.CHUNK_SIZE, file.size);\n\n const chunk = file.slice(start, end);\n\n try {\n const { method, url } = await promiseUtils.retryPromise({\n fn: () => getUploadFilePartUrl(uploadId, chunkIndex + 1),\n retryCount: CONFIG.DEFAULT_RETRY_COUNT,\n retryDelayMS: CONFIG.DEFAULT_RETRY_DELAY_MS,\n });\n\n consola.trace(\"File part upload url resolved\", { method, url });\n\n const onChunkUploadProgress = (uploadedChunkBytes: number) => {\n uploadedBytes = uploadedChunkBytes + start;\n state.set(uploadId, { uploadedByteCount: uploadedBytes });\n\n const progress = Math.round((uploadedBytes / totalBytes) * 100);\n\n config.onProgress?.(uploadId, progress);\n };\n\n const filePartResponse = await runAbortablePromise(() =>\n promiseUtils.retryPromise({\n fn: () =>\n uploadPartBytesWithProgress({\n method,\n url,\n partBytes: chunk,\n partIndex: chunkIndex,\n onProgress: onChunkUploadProgress,\n abortSignal: abortController.signal,\n }),\n retryCount: CONFIG.DEFAULT_RETRY_COUNT,\n retryDelayMS: CONFIG.DEFAULT_RETRY_DELAY_MS,\n }),\n );\n\n consola.trace(\n `${file.name}: Chunk ${chunkIndex + 1} of ${totalChunks} uploaded successfully`,\n );\n\n completedPartIds[chunkIndex + 1] = filePartResponse.partId;\n\n state.set(uploadId, {\n completedChunkCount: state.get(uploadId)!.completedChunkCount + 1,\n });\n } catch (_error) {\n if (_error instanceof promiseUtils.AbortError) {\n consola.info(\"Aborted::\", _error);\n return;\n }\n\n const error =\n _error instanceof Error ? _error : new Error(_error as string);\n\n state.setFailed(uploadId);\n\n onFileUploadFailed?.({ uploadId, error });\n return;\n }\n\n chunkIndex++;\n }\n\n if (state.isCancelled(uploadId) || state.isFailed(uploadId)) {\n return;\n }\n\n state.setCompleted(uploadId, totalChunks);\n onFileUploadComplete?.({ uploadId, filePartIds: completedPartIds });\n };\n\n const uploadFiles = (files: Array<{ uploadId: string; file: File }>) => {\n const result: Array<Promise<unknown>> = [];\n\n for (const { uploadId, file } of files) {\n result.push(uploadFileFromChunkIndex(uploadId, file));\n }\n\n return Promise.allSettled(result).then(() => state.flush());\n };\n\n return {\n uploadFiles,\n\n getUploadState: state.get,\n isFailed: state.isFailed,\n isCancelled: state.isCancelled,\n cancel: state.cancel,\n cancelAll: state.cancelAll,\n setFailed: state.setFailed,\n };\n};\n","import { promise as promiseUtils } from \"@knime/utils\";\n\nimport { CONFIG } from \"./config\";\nimport { createStateManager } from \"./state\";\nimport type { BaseUploadManagerConfig } from \"./types\";\nimport { uploadPartBytesWithProgress } from \"./uploadPartBytes\";\n\nexport type StreamingUploadManagerConfig = BaseUploadManagerConfig & {\n /**\n * Callback triggered as bytes are uploaded. This reports an absolute byte count rather\n * than a percentage: the total size isn't known upfront for a stream.\n */\n onProgress?: (uploadId: string, uploadedBytes: number) => void;\n};\n\n/**\n * A small FIFO byte buffer. Pushed chunks are stored by reference (no\n * copying on push).\n */\nclass ByteQueue {\n #chunks: Uint8Array[] = [];\n #length = 0;\n\n get length() {\n return this.#length;\n }\n\n push(chunk: Uint8Array) {\n if (chunk.length === 0) {\n return;\n }\n this.#chunks.push(chunk);\n this.#length += chunk.length;\n }\n\n /**\n * FIFO exactly `n` bytes from the buffer. If the first chunk has more bytes than needed,\n * it will be split and only the requested amount returned. This ensures each byte is copied\n * at most once, regardless of how many small chunks it took to accumulate them.\n * @param n\n * @returns\n */\n take(n: number): Uint8Array<ArrayBuffer> {\n const size = Math.min(n, this.#length);\n const result = new Uint8Array(size);\n let offset = 0;\n\n while (offset < size) {\n const chunk = this.#chunks[0];\n const needed = size - offset;\n\n if (chunk.length <= needed) {\n result.set(chunk, offset);\n offset += chunk.length;\n this.#chunks.shift();\n } else {\n result.set(chunk.subarray(0, needed), offset);\n this.#chunks[0] = chunk.subarray(needed);\n offset += needed;\n }\n }\n\n this.#length -= size;\n return result;\n }\n\n takeAll(): Uint8Array<ArrayBuffer> {\n return this.take(this.#length);\n }\n}\n\nconst readStreamAndUpload = async (\n state: ReturnType<typeof createStateManager>,\n uploadId: string,\n stream: ReadableStream<Uint8Array>,\n uploadPart: (bytes: Uint8Array<ArrayBuffer>) => Promise<void>,\n config: StreamingUploadManagerConfig,\n // eslint-disable-next-line max-params\n) => {\n const buffer = new ByteQueue();\n const reader = stream.getReader();\n\n const closeReader = async () => {\n try {\n await reader.cancel();\n } catch {\n // stream already closed/errored - nothing left to cancel\n } finally {\n reader.releaseLock();\n }\n };\n\n try {\n while (true) {\n if (state.isCancelled(uploadId) || state.isFailed(uploadId)) {\n return;\n }\n\n const { value, done } = await reader.read();\n\n if (value) {\n buffer.push(value);\n }\n\n while (buffer.length >= CONFIG.CHUNK_SIZE) {\n if (state.isCancelled(uploadId) || state.isFailed(uploadId)) {\n return;\n }\n\n await uploadPart(buffer.take(CONFIG.CHUNK_SIZE));\n }\n\n if (done) {\n // when stream is done, if there's still any content in the buffer\n // then take all remaining bytes and upload them\n if (\n buffer.length > 0 &&\n !state.isCancelled(uploadId) &&\n !state.isFailed(uploadId)\n ) {\n await uploadPart(buffer.takeAll());\n }\n break;\n }\n }\n } catch (_error) {\n if (_error instanceof promiseUtils.AbortError) {\n consola.info(\"Aborted::\", _error);\n return;\n }\n\n state.setFailed(uploadId);\n const error =\n _error instanceof Error ? _error : new Error(_error as string);\n\n config.onFileUploadFailed?.({ uploadId, error });\n } finally {\n await closeReader();\n }\n};\n\n/**\n * Creates and returns a `streamingUploadManager` - a multi-part upload handler\n * driven by a `ReadableStream<Uint8Array>` rather than a `File`/`Blob`.\n *\n * Unlike the chunkedUploadManager, this doesn't need to know the total size of the upload upfront.\n * Instead, it accumulates stream chunks into a buffer and uploadsa part every time\n * the buffer crosses the configured threshold for upload chunks, flushing whatever remains\n * as a final (possibly smaller) part once the stream ends.\n *\n * This bounds memory to roughly one chunk's worth of buffered bytes, plus\n * whatever the stream source itself buffers, regardless of the eventual\n * total size - useful for sources whose size can't be known in advance,\n * such as compressed output from a streaming zip encoder.\n *\n * Shares the same part-upload mechanics (retries, progress, abort) as\n * the chunkedUploadManager\n */\nexport const createStreamingUploadManager = (\n config: StreamingUploadManagerConfig,\n) => {\n const {\n resolveFilePartUploadURL: getUploadFilePartUrl,\n onFileUploadComplete,\n } = config;\n\n const state = createStateManager();\n\n const uploadStreamFromPartIndex = async (\n uploadId: string,\n stream: ReadableStream<Uint8Array>,\n ) => {\n const completedPartIds: Record<number, string> = {};\n\n const { abortController, runAbortablePromise } =\n promiseUtils.createAbortablePromise();\n\n state.set(uploadId, {\n uploadId,\n state: \"inprogress\",\n uploadedByteCount: state.get(uploadId)?.uploadedByteCount ?? 0,\n completedChunkCount: state.get(uploadId)?.completedChunkCount ?? 0,\n abortController,\n });\n\n let partIndex = 0;\n let uploadedBytes = state.get(uploadId)?.uploadedByteCount ?? 0;\n\n const uploadPart = async (bytes: Uint8Array<ArrayBuffer>) => {\n const chunk = new Blob([bytes]);\n\n const { method, url } = await promiseUtils.retryPromise({\n fn: () => getUploadFilePartUrl(uploadId, partIndex + 1),\n retryCount: CONFIG.DEFAULT_RETRY_COUNT,\n retryDelayMS: CONFIG.DEFAULT_RETRY_DELAY_MS,\n });\n\n const uploadedBeforeThisPart = uploadedBytes;\n const onChunkUploadProgress = (uploadedChunkBytes: number) => {\n uploadedBytes = uploadedBeforeThisPart + uploadedChunkBytes;\n state.set(uploadId, { uploadedByteCount: uploadedBytes });\n config.onProgress?.(uploadId, uploadedBytes);\n };\n\n const filePartResponse = await runAbortablePromise(() =>\n promiseUtils.retryPromise({\n fn: () =>\n uploadPartBytesWithProgress({\n method,\n url,\n partBytes: chunk,\n partIndex,\n onProgress: onChunkUploadProgress,\n abortSignal: abortController.signal,\n }),\n retryCount: CONFIG.DEFAULT_RETRY_COUNT,\n retryDelayMS: CONFIG.DEFAULT_RETRY_DELAY_MS,\n }),\n );\n\n completedPartIds[partIndex + 1] = filePartResponse.partId;\n uploadedBytes = uploadedBeforeThisPart + bytes.length;\n partIndex++;\n\n state.set(uploadId, {\n completedChunkCount: partIndex,\n uploadedByteCount: uploadedBytes,\n });\n };\n\n await readStreamAndUpload(state, uploadId, stream, uploadPart, config);\n\n if (state.isCancelled(uploadId) || state.isFailed(uploadId)) {\n return;\n }\n\n state.setCompleted(uploadId, state.get(uploadId)!.completedChunkCount);\n onFileUploadComplete?.({ uploadId, filePartIds: completedPartIds });\n };\n\n const uploadStreams = (\n streams: Array<{ uploadId: string; stream: ReadableStream<Uint8Array> }>,\n ) => {\n const result: Array<Promise<unknown>> = [];\n\n for (const { uploadId, stream } of streams) {\n result.push(uploadStreamFromPartIndex(uploadId, stream));\n }\n\n return Promise.allSettled(result).then(() => state.flush());\n };\n\n return {\n uploadStreams,\n\n getUploadState: state.get,\n isFailed: state.isFailed,\n isCancelled: state.isCancelled,\n cancel: state.cancel,\n cancelAll: state.cancelAll,\n setFailed: state.setFailed,\n };\n};\n","export type BaseUploadManagerConfig = {\n /**\n * Function to resolve the metadata for a request to upload a file part\n * @param uploadId\n * @param partNumber\n * @returns\n */\n resolveFilePartUploadURL: (\n uploadId: string,\n partNumber: number,\n ) => Promise<{ method: string; url: string }>;\n\n /**\n * Callback that gets triggered when the whole file (or stream in streaming mode) is uploaded.\n * @param\n * @returns\n */\n onFileUploadComplete?: (payload: {\n /**\n * Identifier of the upload for this file (must be unique)\n */\n uploadId: string;\n /**\n * Ids of all the uploaded file parts\n */\n filePartIds: Record<number, string>;\n }) => void;\n\n /**\n * Callback that gets triggered when an upload has failed.\n * @param uploadId\n * @param error the error that cause the failure if it exists\n * @returns\n */\n onFileUploadFailed?: (payload: { uploadId: string; error: Error }) => void;\n};\n\nexport const UploadStates = {\n IDLE: \"idle\",\n IN_PROGRESS: \"inprogress\",\n CANCELLED: \"cancelled\",\n FAILED: \"failed\",\n COMPLETE: \"complete\",\n} as const;\n\nexport type UploadStateName = (typeof UploadStates)[keyof typeof UploadStates];\n","import type { FetchOptions } from \"ofetch\";\n\nimport { getFileMimeType } from \"@knime/utils\";\n\nimport { createHttpClient } from \"../../../shared/httpClient\";\n\n/**\n * What `prepareUpload` actually needs to build a\n * correct manifest request. Kept structural (rather than requiring a real\n * `File`) so callers that don't have a materialized file - e.g. a streaming\n * upload whose final size isn't known yet - can pass a plain metadata object\n * instead.\n */\ntype UploadCandidate = { name: string; size: number; type: string };\n\ntype PrepareUploadItem<T extends UploadCandidate> = {\n /**\n * The manifest key: the path (relative to `parentId`) this item should be\n * created at.\n */\n path: string;\n file: T;\n};\n\ntype CreateUploadClientOptions = {\n /**\n * Options to customize the fetch client used internally to make http requests.\n * e.g: headers, baseUrl, etc\n */\n customFetchClientOptions?: FetchOptions;\n};\n\nexport type UploadClient = ReturnType<typeof createUploadClient>;\n\n/**\n * Creates the raw upload lifecycle client:\n *\n * (1) - prepare a batch of items for upload\n * (2) - resolve a presigned URL for a given file part\n * (3) - complete an upload once all its parts are uploaded, or cancel one in progress.\n */\nexport const createUploadClient = (options: CreateUploadClientOptions = {}) => {\n const $ofetch = createHttpClient(options.customFetchClientOptions);\n\n const prepareUpload = <T extends UploadCandidate>(\n parentId: string,\n items: Array<PrepareUploadItem<T>>,\n ): Promise<Array<{ uploadId: string; file: T; path: string }>> => {\n const fileByName = new Map(\n items.map(({ path: name, file }) => [name, file]),\n );\n\n const body: {\n items: Record<\n string,\n { itemContentType: string; itemContentSize: number }\n >;\n } = {\n items: Object.fromEntries(\n items.map(({ path: name, file }) => [\n name,\n {\n itemContentType: getFileMimeType(file),\n itemContentSize: file.size,\n },\n ]),\n ),\n };\n\n type PrepareUploadResponse = {\n items: Record<string, { uploadId: string }>;\n };\n\n return $ofetch<PrepareUploadResponse>(`/repository/${parentId}/manifest`, {\n method: \"POST\",\n body,\n }).then(({ items: responseItems }) =>\n Object.keys(responseItems).map((name) => ({\n uploadId: responseItems[name].uploadId,\n file: fileByName.get(name)!,\n path: name,\n })),\n );\n };\n\n const resolveFilePartUploadURL = (uploadId: string, partNumber: number) => {\n type UploadURLResponse = {\n method: string;\n url: string;\n header: { Host: string };\n };\n\n return $ofetch<UploadURLResponse>(\n `/uploads/${uploadId}/parts/?partNumber=${partNumber}`,\n { method: \"POST\" },\n );\n };\n\n const completeUpload = (\n uploadId: string,\n partIds: Record<number, string>,\n ) => {\n return $ofetch<void>(`/uploads/${uploadId}`, {\n method: \"POST\",\n body: partIds,\n });\n };\n\n const cancelUpload = (uploadId: string) => {\n return $ofetch(`/uploads/${uploadId}`, {\n method: \"DELETE\",\n });\n };\n\n const getUploadStatus = (uploadIds: string[]) => {\n type UploadStatusResponse = Array<{\n uploadId: string;\n status:\n \"PREPARED\" | \"ANALYSIS_PENDING\" | \"COMPLETED\" | \"ABORTED\" | \"FAILED\";\n statusMessage: string;\n }>;\n\n return $ofetch<UploadStatusResponse>(\"/uploads/status\", {\n method: \"GET\",\n query: {\n uploadIds: uploadIds.join(\",\"),\n },\n });\n };\n\n return {\n prepareUpload,\n resolveFilePartUploadURL,\n completeUpload,\n cancelUpload,\n getUploadStatus,\n };\n};\n","//#region src/array/chunk.ts\n/**\n* Splits an array into smaller arrays of a specified length.\n*\n* This function takes an input array and divides it into multiple smaller arrays,\n* each of a specified length. If the input array cannot be evenly divided,\n* the final sub-array will contain the remaining elements.\n*\n* @template T The type of elements in the array.\n* @param arr - The array to be chunked into smaller arrays.\n* @param size - The size of each smaller array. Must be a positive integer.\n* @returns A two-dimensional array where each sub-array has a maximum length of `size`.\n* @throws {Error} Throws an error if `size` is not a positive integer.\n*\n* @example\n* // Splits an array of numbers into sub-arrays of length 2\n* chunk([1, 2, 3, 4, 5], 2);\n* // Returns: [[1, 2], [3, 4], [5]]\n*\n* @example\n* // Splits an array of strings into sub-arrays of length 3\n* chunk(['a', 'b', 'c', 'd', 'e', 'f', 'g'], 3);\n* // Returns: [['a', 'b', 'c'], ['d', 'e', 'f'], ['g']]\n*/\nfunction chunk(arr, size) {\n\tif (!Number.isInteger(size) || size <= 0) throw new Error(\"Size must be an integer greater than zero.\");\n\tconst chunkLength = Math.ceil(arr.length / size);\n\tconst result = Array(chunkLength);\n\tfor (let index = 0; index < chunkLength; index++) {\n\t\tconst start = index * size;\n\t\tconst end = start + size;\n\t\tresult[index] = arr.slice(start, end);\n\t}\n\treturn result;\n}\n//#endregion\nexport { chunk };\n","import { chunk } from \"es-toolkit/array\";\n\nimport { sleep } from \"@knime/utils\";\n\nimport type { UploadClient } from \"./uploadClient\";\n\nconst DEFAULT_POLL_INTERVAL_MS = 1500;\nconst DEFAULT_POLL_TIMEOUT = 5 * 60 * 1000; // 5 mins\nconst MAX_IDS_PER_REQUEST = 100;\n\nconst logger = () => consola.withTag(\"Upload status poller\");\n\ntype Options = {\n /** Called once a tracked upload's processing reaches a terminal, successful state. */\n onComplete: (uploadId: string) => void;\n /** Called once a tracked upload's processing reaches a terminal, failed/aborted state. */\n onFail: (uploadId: string, statusMessage: string) => void;\n /** Delay between polling requests. Defaults to 1500ms. */\n pollIntervalMs?: number;\n pollTimeout?: number;\n};\n\nexport function createUploadStatusPoller(\n uploadClient: UploadClient,\n options: Options,\n) {\n const pending = new Set<string>();\n const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n const pollTimeout = options.pollTimeout ?? DEFAULT_POLL_TIMEOUT;\n\n // Caches the currently-running poll session's promise, so a `track()` call\n // that joins an already-running session awaits the same promise as the\n // one that started it, rather than resolving as soon as it's called.\n let inFlight: Promise<void> | null = null;\n\n const poll = (): Promise<void> => {\n if (inFlight) {\n return inFlight;\n }\n\n inFlight = (async () => {\n const deadline = Date.now() + pollTimeout;\n\n while (pending.size > 0 && Date.now() < deadline) {\n try {\n const batches = chunk([...pending.values()], MAX_IDS_PER_REQUEST);\n const promises = batches.map((batch) =>\n uploadClient.getUploadStatus(batch),\n );\n\n const statuses = (await Promise.all(promises)).flat();\n\n for (const { uploadId, status, statusMessage } of statuses) {\n if (status === \"COMPLETED\") {\n pending.delete(uploadId);\n options.onComplete(uploadId);\n } else if (status === \"FAILED\" || status === \"ABORTED\") {\n pending.delete(uploadId);\n options.onFail(uploadId, statusMessage);\n }\n // PREPARED / ANALYSIS_PENDING: not yet terminal, keep polling.\n }\n\n if (pending.size === 0) {\n break;\n }\n\n await sleep(pollIntervalMs);\n } catch (error) {\n logger().error(\"Failed while polling for upload batch status\", {\n error,\n });\n\n await sleep(pollIntervalMs);\n continue;\n }\n }\n\n for (const uploadId of pending) {\n pending.delete(uploadId);\n options.onFail(\n uploadId,\n \"Timed out waiting for processing to complete\",\n );\n }\n })().finally(() => {\n inFlight = null;\n });\n\n return inFlight;\n };\n\n return {\n /**\n * Tracks an id for polling. Will start the polling requests immediately unless indicated otherwise\n * by the `startPolling` parameter.\n *\n * Adding newer ids to an already-running poller is not a problem, since new ids just\n * increase the poll batch size upon the next execution of the polling request\n *\n * @param uploadId\n * @param startPolling\n */\n track: (uploadId: string, startPolling = true) => {\n pending.add(uploadId);\n\n if (startPolling) {\n return poll();\n }\n\n return Promise.resolve();\n },\n\n stop: () => pending.clear(),\n };\n}\n"],"x_google_ignoreList":[0,1,2,12],"mappings":"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"}