@allurereport/service 3.14.0 → 3.14.2
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/dist/model.d.ts +20 -11
- package/dist/service.d.ts +8 -10
- package/dist/service.js +31 -26
- package/dist/testops.d.ts +22 -0
- package/dist/testops.js +43 -23
- package/dist/utils/http.d.ts +6 -8
- package/dist/utils/http.js +169 -35
- package/dist/utils/upload.d.ts +10 -0
- package/dist/utils/upload.js +27 -0
- package/package.json +3 -3
package/dist/model.d.ts
CHANGED
|
@@ -13,8 +13,21 @@ export type AllureServiceApiClientConfig = UploadReportConfig & {
|
|
|
13
13
|
export type UploadReportPayload = {
|
|
14
14
|
reportUuid: string;
|
|
15
15
|
pluginId?: string;
|
|
16
|
-
files: Record<string, string
|
|
17
|
-
onProgress?: () => void;
|
|
16
|
+
files: Record<string, string> | Record<string, string>[];
|
|
17
|
+
onProgress?: (uploadedFilesDelta?: number) => void;
|
|
18
|
+
uploadBatchMaxBytes?: number;
|
|
19
|
+
};
|
|
20
|
+
export type UploadReportFilePayload = {
|
|
21
|
+
filename: string;
|
|
22
|
+
file?: Buffer;
|
|
23
|
+
filepath?: string;
|
|
24
|
+
signal?: AbortSignal;
|
|
25
|
+
};
|
|
26
|
+
export type UploadReportFilesPayload = {
|
|
27
|
+
reportUuid: string;
|
|
28
|
+
pluginId?: string;
|
|
29
|
+
files: UploadReportFilePayload[];
|
|
30
|
+
signal?: AbortSignal;
|
|
18
31
|
};
|
|
19
32
|
export type UploadReportResult = {
|
|
20
33
|
indexHref?: string;
|
|
@@ -40,19 +53,15 @@ export interface AllureServiceApiClient {
|
|
|
40
53
|
reportUuid: string;
|
|
41
54
|
pluginId?: string;
|
|
42
55
|
}): Promise<unknown>;
|
|
43
|
-
addReportAsset(payload:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
filepath?: string;
|
|
56
|
+
addReportAsset(payload: UploadReportFilePayload): Promise<unknown>;
|
|
57
|
+
addReportAssets?(payload: {
|
|
58
|
+
files: UploadReportFilePayload[];
|
|
47
59
|
signal?: AbortSignal;
|
|
48
60
|
}): Promise<unknown>;
|
|
49
|
-
addReportFile(payload: {
|
|
61
|
+
addReportFile(payload: UploadReportFilePayload & {
|
|
50
62
|
reportUuid: string;
|
|
51
63
|
pluginId?: string;
|
|
52
|
-
filename: string;
|
|
53
|
-
file?: Buffer;
|
|
54
|
-
filepath?: string;
|
|
55
|
-
signal?: AbortSignal;
|
|
56
64
|
}): Promise<string>;
|
|
65
|
+
addReportFiles?(payload: UploadReportFilesPayload): Promise<Record<string, string>>;
|
|
57
66
|
uploadReport(payload: UploadReportPayload): Promise<UploadReportResult>;
|
|
58
67
|
}
|
package/dist/service.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type HistoryDataPoint } from "@allurereport/core-api";
|
|
2
|
-
import { type AllureServiceApiClient, type AllureServiceApiClientConfig, type UploadReportPayload } from "./model.js";
|
|
2
|
+
import { type AllureServiceApiClient, type AllureServiceApiClientConfig, type UploadReportFilePayload, type UploadReportFilesPayload, type UploadReportPayload } from "./model.js";
|
|
3
3
|
export declare class AllureServiceClient implements AllureServiceApiClient {
|
|
4
4
|
#private;
|
|
5
5
|
readonly config: AllureServiceApiClientConfig;
|
|
@@ -23,19 +23,17 @@ export declare class AllureServiceClient implements AllureServiceApiClient {
|
|
|
23
23
|
reportUuid: string;
|
|
24
24
|
pluginId?: string;
|
|
25
25
|
}): Promise<unknown>;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
file?: Buffer;
|
|
29
|
-
filepath?: string;
|
|
26
|
+
addReportAssets(payload: {
|
|
27
|
+
files: UploadReportFilePayload[];
|
|
30
28
|
signal?: AbortSignal;
|
|
31
29
|
}): Promise<unknown>;
|
|
32
|
-
|
|
30
|
+
addReportAsset(payload: UploadReportFilePayload): Promise<unknown>;
|
|
31
|
+
addReportFiles(payload: UploadReportFilesPayload): Promise<{
|
|
32
|
+
[k: string]: string;
|
|
33
|
+
}>;
|
|
34
|
+
addReportFile(payload: UploadReportFilePayload & {
|
|
33
35
|
reportUuid: string;
|
|
34
36
|
pluginId?: string;
|
|
35
|
-
filename: string;
|
|
36
|
-
file?: Buffer;
|
|
37
|
-
filepath?: string;
|
|
38
|
-
signal?: AbortSignal;
|
|
39
37
|
}): Promise<string>;
|
|
40
38
|
uploadReport(payload: UploadReportPayload): Promise<import("./model.js").UploadReportResult>;
|
|
41
39
|
}
|
package/dist/service.js
CHANGED
|
@@ -10,11 +10,11 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
10
10
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
11
|
};
|
|
12
12
|
var _AllureServiceClient_client, _AllureServiceClient_url;
|
|
13
|
-
import { readFile } from "node:fs/promises";
|
|
14
13
|
import { join as joinPosix } from "node:path/posix";
|
|
15
14
|
import { ALLURE_SERVICE_STORAGE_PREFIX, } from "./model.js";
|
|
16
15
|
import { createServiceHttpClient, uploadReport } from "./utils/http.js";
|
|
17
16
|
import { parseServiceToken } from "./utils/token.js";
|
|
17
|
+
import { createUploadForm } from "./utils/upload.js";
|
|
18
18
|
const UPLOAD_CONTENT_TYPE = "application/octet-stream";
|
|
19
19
|
const createUploadBlob = (content) => new Blob([content], { type: UPLOAD_CONTENT_TYPE });
|
|
20
20
|
const createReportUrl = (baseUrl, reportUuid) => `${baseUrl}/${reportUuid}`;
|
|
@@ -79,18 +79,12 @@ export class AllureServiceClient {
|
|
|
79
79
|
},
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
|
-
async
|
|
83
|
-
const {
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
let content = file;
|
|
88
|
-
if (!content) {
|
|
89
|
-
content = signal ? await readFile(filepath, { signal }) : await readFile(filepath);
|
|
82
|
+
async addReportAssets(payload) {
|
|
83
|
+
const { files, signal } = payload;
|
|
84
|
+
if (files.length === 0) {
|
|
85
|
+
return undefined;
|
|
90
86
|
}
|
|
91
|
-
const form =
|
|
92
|
-
form.set("filename", filename);
|
|
93
|
-
form.set("file", createUploadBlob(content), filename);
|
|
87
|
+
const { form } = await createUploadForm(files, createUploadBlob, signal);
|
|
94
88
|
return __classPrivateFieldGet(this, _AllureServiceClient_client, "f").post("/api/assets/upload", {
|
|
95
89
|
body: form,
|
|
96
90
|
headers: {
|
|
@@ -99,19 +93,15 @@ export class AllureServiceClient {
|
|
|
99
93
|
...(signal ? { signal } : {}),
|
|
100
94
|
});
|
|
101
95
|
}
|
|
102
|
-
async
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (!content) {
|
|
110
|
-
content = signal ? await readFile(filepath, { signal }) : await readFile(filepath);
|
|
96
|
+
async addReportAsset(payload) {
|
|
97
|
+
return this.addReportAssets({ files: [payload], ...(payload.signal ? { signal: payload.signal } : {}) });
|
|
98
|
+
}
|
|
99
|
+
async addReportFiles(payload) {
|
|
100
|
+
const { reportUuid, pluginId, files, signal } = payload;
|
|
101
|
+
if (files.length === 0) {
|
|
102
|
+
return {};
|
|
111
103
|
}
|
|
112
|
-
const form =
|
|
113
|
-
form.set("filename", reportFilename);
|
|
114
|
-
form.set("file", createUploadBlob(content), reportFilename);
|
|
104
|
+
const { entries, form } = await createUploadForm(files, createUploadBlob, signal, (filename) => pluginId ? joinPosix(pluginId, filename) : filename);
|
|
115
105
|
await __classPrivateFieldGet(this, _AllureServiceClient_client, "f").post(`/api/reports/${reportUuid}/upload`, {
|
|
116
106
|
body: form,
|
|
117
107
|
headers: {
|
|
@@ -119,16 +109,31 @@ export class AllureServiceClient {
|
|
|
119
109
|
},
|
|
120
110
|
...(signal ? { signal } : {}),
|
|
121
111
|
});
|
|
122
|
-
return
|
|
112
|
+
return Object.fromEntries(entries.map(({ filename, reportFilename }) => [
|
|
113
|
+
filename,
|
|
114
|
+
createReportFileUrl(__classPrivateFieldGet(this, _AllureServiceClient_url, "f"), reportUuid, reportFilename),
|
|
115
|
+
]));
|
|
116
|
+
}
|
|
117
|
+
async addReportFile(payload) {
|
|
118
|
+
const result = await this.addReportFiles({
|
|
119
|
+
reportUuid: payload.reportUuid,
|
|
120
|
+
pluginId: payload.pluginId,
|
|
121
|
+
files: [payload],
|
|
122
|
+
...(payload.signal ? { signal: payload.signal } : {}),
|
|
123
|
+
});
|
|
124
|
+
return result[payload.filename];
|
|
123
125
|
}
|
|
124
126
|
async uploadReport(payload) {
|
|
127
|
+
const { uploadBatchMaxBytes: _uploadBatchMaxBytes, ...uploadPayload } = payload;
|
|
125
128
|
return uploadReport({
|
|
126
|
-
...
|
|
129
|
+
...uploadPayload,
|
|
127
130
|
uploadConcurrency: this.config.uploadConcurrency,
|
|
128
131
|
uploadMaxAttempts: this.config.uploadMaxAttempts,
|
|
129
132
|
uploadMaxSimultaneousFailures: this.config.uploadMaxSimultaneousFailures,
|
|
130
133
|
addReportAsset: this.addReportAsset.bind(this),
|
|
134
|
+
addReportAssets: this.addReportAssets.bind(this),
|
|
131
135
|
addReportFile: this.addReportFile.bind(this),
|
|
136
|
+
addReportFiles: this.addReportFiles.bind(this),
|
|
132
137
|
});
|
|
133
138
|
}
|
|
134
139
|
}
|
package/dist/testops.d.ts
CHANGED
|
@@ -25,6 +25,15 @@ export declare class AllureTestOpsClient implements AllureServiceApiClient {
|
|
|
25
25
|
filepath?: string;
|
|
26
26
|
signal?: AbortSignal;
|
|
27
27
|
}): Promise<unknown>;
|
|
28
|
+
addReportAssets(payload: {
|
|
29
|
+
files: {
|
|
30
|
+
filename: string;
|
|
31
|
+
file?: Buffer;
|
|
32
|
+
filepath?: string;
|
|
33
|
+
signal?: AbortSignal;
|
|
34
|
+
}[];
|
|
35
|
+
signal?: AbortSignal;
|
|
36
|
+
}): Promise<unknown>;
|
|
28
37
|
addReportFile(payload: {
|
|
29
38
|
reportUuid: string;
|
|
30
39
|
pluginId?: string;
|
|
@@ -33,5 +42,18 @@ export declare class AllureTestOpsClient implements AllureServiceApiClient {
|
|
|
33
42
|
filepath?: string;
|
|
34
43
|
signal?: AbortSignal;
|
|
35
44
|
}): Promise<string>;
|
|
45
|
+
addReportFiles(payload: {
|
|
46
|
+
reportUuid: string;
|
|
47
|
+
pluginId?: string;
|
|
48
|
+
files: {
|
|
49
|
+
filename: string;
|
|
50
|
+
file?: Buffer;
|
|
51
|
+
filepath?: string;
|
|
52
|
+
signal?: AbortSignal;
|
|
53
|
+
}[];
|
|
54
|
+
signal?: AbortSignal;
|
|
55
|
+
}): Promise<{
|
|
56
|
+
[k: string]: string;
|
|
57
|
+
}>;
|
|
36
58
|
uploadReport(payload: UploadReportPayload): Promise<import("./model.js").UploadReportResult>;
|
|
37
59
|
}
|
package/dist/testops.js
CHANGED
|
@@ -10,12 +10,13 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
10
10
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
11
|
};
|
|
12
12
|
var _AllureTestOpsClient_instances, _AllureTestOpsClient_url, _AllureTestOpsClient_accessToken, _AllureTestOpsClient_projectId, _AllureTestOpsClient_client, _AllureTestOpsClient_authorizedClient;
|
|
13
|
-
import { readFile } from "node:fs/promises";
|
|
14
13
|
import { extname, join as joinPosix } from "node:path/posix";
|
|
15
14
|
import { ALLURE_SERVICE_TESTOPS_PREFIX, } from "./model.js";
|
|
16
15
|
import { createServiceHttpClient, uploadReport } from "./utils/http.js";
|
|
17
16
|
import { parseServiceToken } from "./utils/token.js";
|
|
17
|
+
import { createUploadForm } from "./utils/upload.js";
|
|
18
18
|
const DEFAULT_UPLOAD_CONTENT_TYPE = "application/octet-stream";
|
|
19
|
+
const UPLOAD_BATCH_MAX_BYTES = 8 * 1024 * 1024;
|
|
19
20
|
const CONTENT_TYPES = {
|
|
20
21
|
".css": "text/css",
|
|
21
22
|
".gif": "image/gif",
|
|
@@ -90,17 +91,8 @@ export class AllureTestOpsClient {
|
|
|
90
91
|
return client.delete(`/api/test-report/${reportUuid}`);
|
|
91
92
|
}
|
|
92
93
|
async addReportAsset(payload) {
|
|
93
|
-
const {
|
|
94
|
-
|
|
95
|
-
throw new Error("File or filepath is required");
|
|
96
|
-
}
|
|
97
|
-
let content = file;
|
|
98
|
-
if (!content) {
|
|
99
|
-
content = signal ? await readFile(filepath, { signal }) : await readFile(filepath);
|
|
100
|
-
}
|
|
101
|
-
const form = new FormData();
|
|
102
|
-
form.set("filename", filename);
|
|
103
|
-
form.set("file", createUploadBlob(content, filename), filename);
|
|
94
|
+
const { file, filepath, filename, signal } = payload;
|
|
95
|
+
const { form } = await createUploadForm([{ filename, file, filepath, signal }], createUploadBlob, signal);
|
|
104
96
|
const client = await __classPrivateFieldGet(this, _AllureTestOpsClient_instances, "m", _AllureTestOpsClient_authorizedClient).call(this);
|
|
105
97
|
return client.post("/api/test-report/upload", {
|
|
106
98
|
body: form,
|
|
@@ -110,19 +102,25 @@ export class AllureTestOpsClient {
|
|
|
110
102
|
...(signal ? { signal } : {}),
|
|
111
103
|
});
|
|
112
104
|
}
|
|
105
|
+
async addReportAssets(payload) {
|
|
106
|
+
const { files, signal } = payload;
|
|
107
|
+
if (files.length === 0) {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
const { form } = await createUploadForm(files, createUploadBlob, signal);
|
|
111
|
+
const client = await __classPrivateFieldGet(this, _AllureTestOpsClient_instances, "m", _AllureTestOpsClient_authorizedClient).call(this);
|
|
112
|
+
return client.post("/api/test-report/upload/batch", {
|
|
113
|
+
body: form,
|
|
114
|
+
headers: {
|
|
115
|
+
"Content-Type": "multipart/form-data",
|
|
116
|
+
},
|
|
117
|
+
...(signal ? { signal } : {}),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
113
120
|
async addReportFile(payload) {
|
|
114
|
-
const { reportUuid, filename, file, filepath,
|
|
121
|
+
const { reportUuid, pluginId, filename, file, filepath, signal } = payload;
|
|
115
122
|
const reportFilename = pluginId ? joinPosix(pluginId, filename) : filename;
|
|
116
|
-
|
|
117
|
-
throw new Error("File or filepath is required");
|
|
118
|
-
}
|
|
119
|
-
let content = file;
|
|
120
|
-
if (!content) {
|
|
121
|
-
content = signal ? await readFile(filepath, { signal }) : await readFile(filepath);
|
|
122
|
-
}
|
|
123
|
-
const form = new FormData();
|
|
124
|
-
form.set("filename", reportFilename);
|
|
125
|
-
form.set("file", createUploadBlob(content, reportFilename), reportFilename);
|
|
123
|
+
const { form } = await createUploadForm([{ filename, file, filepath, signal }], createUploadBlob, signal, () => reportFilename);
|
|
126
124
|
const client = await __classPrivateFieldGet(this, _AllureTestOpsClient_instances, "m", _AllureTestOpsClient_authorizedClient).call(this);
|
|
127
125
|
await client.post(`/api/test-report/${reportUuid}/upload`, {
|
|
128
126
|
body: form,
|
|
@@ -133,14 +131,36 @@ export class AllureTestOpsClient {
|
|
|
133
131
|
});
|
|
134
132
|
return createReportFileUrl(__classPrivateFieldGet(this, _AllureTestOpsClient_url, "f"), reportUuid, reportFilename);
|
|
135
133
|
}
|
|
134
|
+
async addReportFiles(payload) {
|
|
135
|
+
const { reportUuid, pluginId, files, signal } = payload;
|
|
136
|
+
if (files.length === 0) {
|
|
137
|
+
return {};
|
|
138
|
+
}
|
|
139
|
+
const { entries, form } = await createUploadForm(files, createUploadBlob, signal, (filename) => pluginId ? joinPosix(pluginId, filename) : filename);
|
|
140
|
+
const client = await __classPrivateFieldGet(this, _AllureTestOpsClient_instances, "m", _AllureTestOpsClient_authorizedClient).call(this);
|
|
141
|
+
await client.post(`/api/test-report/${reportUuid}/upload/batch`, {
|
|
142
|
+
body: form,
|
|
143
|
+
headers: {
|
|
144
|
+
"Content-Type": "multipart/form-data",
|
|
145
|
+
},
|
|
146
|
+
...(signal ? { signal } : {}),
|
|
147
|
+
});
|
|
148
|
+
return Object.fromEntries(entries.map(({ filename, reportFilename }) => [
|
|
149
|
+
filename,
|
|
150
|
+
createReportFileUrl(__classPrivateFieldGet(this, _AllureTestOpsClient_url, "f"), reportUuid, reportFilename),
|
|
151
|
+
]));
|
|
152
|
+
}
|
|
136
153
|
async uploadReport(payload) {
|
|
137
154
|
return uploadReport({
|
|
138
155
|
...payload,
|
|
139
156
|
uploadConcurrency: this.config.uploadConcurrency,
|
|
140
157
|
uploadMaxAttempts: this.config.uploadMaxAttempts,
|
|
141
158
|
uploadMaxSimultaneousFailures: this.config.uploadMaxSimultaneousFailures,
|
|
159
|
+
uploadBatchMaxBytes: UPLOAD_BATCH_MAX_BYTES,
|
|
142
160
|
addReportAsset: this.addReportAsset.bind(this),
|
|
161
|
+
addReportAssets: this.addReportAssets.bind(this),
|
|
143
162
|
addReportFile: this.addReportFile.bind(this),
|
|
163
|
+
addReportFiles: this.addReportFiles.bind(this),
|
|
144
164
|
});
|
|
145
165
|
}
|
|
146
166
|
}
|
package/dist/utils/http.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type AxiosRequestConfig } from "axios";
|
|
2
|
-
import type { UploadReportConfig, UploadReportPayload, UploadReportResult } from "../model.js";
|
|
2
|
+
import type { UploadReportConfig, UploadReportFilePayload, UploadReportFilesPayload, UploadReportPayload, UploadReportResult } from "../model.js";
|
|
3
3
|
export declare class KnownError extends Error {
|
|
4
4
|
status?: number;
|
|
5
5
|
constructor(message: string, status?: number);
|
|
@@ -40,16 +40,14 @@ export declare const createServiceHttpClient: (serviceUrl: string, params?: {
|
|
|
40
40
|
};
|
|
41
41
|
export type HttpClient = ReturnType<typeof createServiceHttpClient>;
|
|
42
42
|
export declare const uploadReport: (payload: UploadReportPayload & UploadReportConfig & {
|
|
43
|
-
addReportAsset: (payload:
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
addReportAsset: (payload: UploadReportFilePayload) => Promise<unknown>;
|
|
44
|
+
addReportAssets?: (payload: {
|
|
45
|
+
files: UploadReportFilePayload[];
|
|
46
46
|
signal?: AbortSignal;
|
|
47
47
|
}) => Promise<unknown>;
|
|
48
|
-
addReportFile: (payload: {
|
|
48
|
+
addReportFile: (payload: UploadReportFilePayload & {
|
|
49
49
|
reportUuid: string;
|
|
50
50
|
pluginId?: string;
|
|
51
|
-
filename: string;
|
|
52
|
-
filepath: string;
|
|
53
|
-
signal?: AbortSignal;
|
|
54
51
|
}) => Promise<string>;
|
|
52
|
+
addReportFiles?: (payload: UploadReportFilesPayload) => Promise<Record<string, string>>;
|
|
55
53
|
}) => Promise<UploadReportResult>;
|
package/dist/utils/http.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { stat } from "node:fs/promises";
|
|
1
2
|
import axios, { isAxiosError } from "axios";
|
|
2
3
|
import { isReportDataFile } from "./files.js";
|
|
3
4
|
export class KnownError extends Error {
|
|
@@ -24,6 +25,57 @@ const stringifyErrorObject = (value) => {
|
|
|
24
25
|
return entries.length > 0 ? `{ ${entries.join(", ")} }` : undefined;
|
|
25
26
|
}
|
|
26
27
|
};
|
|
28
|
+
const MAX_UPLOAD_BATCH_FILES = 32;
|
|
29
|
+
const createUploadFileBatches = (files) => {
|
|
30
|
+
const entries = Object.entries(files);
|
|
31
|
+
const batches = [];
|
|
32
|
+
let batch = {};
|
|
33
|
+
let batchFiles = 0;
|
|
34
|
+
const flushBatch = () => {
|
|
35
|
+
if (batchFiles === 0) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
batches.push(batch);
|
|
39
|
+
batch = {};
|
|
40
|
+
batchFiles = 0;
|
|
41
|
+
};
|
|
42
|
+
for (const [filename, filepath] of entries) {
|
|
43
|
+
if (batchFiles >= MAX_UPLOAD_BATCH_FILES) {
|
|
44
|
+
flushBatch();
|
|
45
|
+
}
|
|
46
|
+
batch[filename] = filepath;
|
|
47
|
+
batchFiles += 1;
|
|
48
|
+
}
|
|
49
|
+
flushBatch();
|
|
50
|
+
return batches;
|
|
51
|
+
};
|
|
52
|
+
const createUploadFileBatchesBySize = async (files, maxBytes) => {
|
|
53
|
+
const entries = Object.entries(files);
|
|
54
|
+
const batches = [];
|
|
55
|
+
let batch = {};
|
|
56
|
+
let batchFiles = 0;
|
|
57
|
+
let batchBytes = 0;
|
|
58
|
+
const flushBatch = () => {
|
|
59
|
+
if (batchFiles === 0) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
batches.push(batch);
|
|
63
|
+
batch = {};
|
|
64
|
+
batchFiles = 0;
|
|
65
|
+
batchBytes = 0;
|
|
66
|
+
};
|
|
67
|
+
for (const [filename, filepath] of entries) {
|
|
68
|
+
const fileBytes = (await stat(filepath)).size;
|
|
69
|
+
if (batchFiles >= MAX_UPLOAD_BATCH_FILES || (batchBytes > 0 && batchBytes + fileBytes > maxBytes)) {
|
|
70
|
+
flushBatch();
|
|
71
|
+
}
|
|
72
|
+
batch[filename] = filepath;
|
|
73
|
+
batchFiles += 1;
|
|
74
|
+
batchBytes += fileBytes;
|
|
75
|
+
}
|
|
76
|
+
flushBatch();
|
|
77
|
+
return batches;
|
|
78
|
+
};
|
|
27
79
|
export const formatResponseErrorData = (data) => {
|
|
28
80
|
if (data === undefined || data === null || data === "") {
|
|
29
81
|
return undefined;
|
|
@@ -116,21 +168,21 @@ export const createServiceHttpClient = (serviceUrl, params) => {
|
|
|
116
168
|
delete: sendRequest("delete"),
|
|
117
169
|
};
|
|
118
170
|
};
|
|
119
|
-
const uploadWithRetry = async (
|
|
171
|
+
const uploadWithRetry = async (batchId, uploadAbortController, failedUploads, maxAttempts, maxSimultaneousFailures, uploadFn) => {
|
|
120
172
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
121
173
|
if (uploadAbortController.signal.aborted) {
|
|
122
174
|
return false;
|
|
123
175
|
}
|
|
124
176
|
try {
|
|
125
177
|
await uploadFn();
|
|
126
|
-
failedUploads.delete(
|
|
178
|
+
failedUploads.delete(batchId);
|
|
127
179
|
return true;
|
|
128
180
|
}
|
|
129
181
|
catch (error) {
|
|
130
182
|
if (uploadAbortController.signal.aborted) {
|
|
131
183
|
return false;
|
|
132
184
|
}
|
|
133
|
-
failedUploads.add(
|
|
185
|
+
failedUploads.add(batchId);
|
|
134
186
|
if (failedUploads.size > maxSimultaneousFailures || attempt >= maxAttempts) {
|
|
135
187
|
throw error;
|
|
136
188
|
}
|
|
@@ -139,9 +191,18 @@ const uploadWithRetry = async (filename, uploadAbortController, failedUploads, m
|
|
|
139
191
|
return false;
|
|
140
192
|
};
|
|
141
193
|
export const uploadReport = async (payload) => {
|
|
142
|
-
const { reportUuid, pluginId, files, onProgress,
|
|
143
|
-
|
|
144
|
-
if (
|
|
194
|
+
const { reportUuid, pluginId, files, onProgress, uploadBatchMaxBytes, uploadConcurrency, uploadMaxAttempts, uploadMaxSimultaneousFailures, } = payload;
|
|
195
|
+
let fileBatches;
|
|
196
|
+
if (uploadBatchMaxBytes === undefined) {
|
|
197
|
+
fileBatches = Array.isArray(files) ? files.flatMap(createUploadFileBatches) : createUploadFileBatches(files);
|
|
198
|
+
}
|
|
199
|
+
else if (Array.isArray(files)) {
|
|
200
|
+
fileBatches = (await Promise.all(files.map((fileBatch) => createUploadFileBatchesBySize(fileBatch, uploadBatchMaxBytes)))).flat();
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
fileBatches = await createUploadFileBatchesBySize(files, uploadBatchMaxBytes);
|
|
204
|
+
}
|
|
205
|
+
if (fileBatches.length === 0) {
|
|
145
206
|
return {
|
|
146
207
|
hrefs: {},
|
|
147
208
|
};
|
|
@@ -150,46 +211,119 @@ export const uploadReport = async (payload) => {
|
|
|
150
211
|
const failedUploads = new Set();
|
|
151
212
|
const hrefs = {};
|
|
152
213
|
let indexHref;
|
|
153
|
-
let
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
214
|
+
let nextBatchIndex = 0;
|
|
215
|
+
const uploadBatches = [];
|
|
216
|
+
const canUploadReportFilesInBatch = !!payload.addReportFiles;
|
|
217
|
+
const canUploadAssetsInBatch = !!payload.addReportAssets;
|
|
218
|
+
for (const [batchIndex, batchFiles] of fileBatches.entries()) {
|
|
219
|
+
const reportDataFiles = [];
|
|
220
|
+
const assetFiles = [];
|
|
221
|
+
for (const [filename, filepath] of Object.entries(batchFiles)) {
|
|
222
|
+
const filePayload = { filename, filepath, signal: uploadAbortController.signal };
|
|
223
|
+
if (isReportDataFile(filename)) {
|
|
224
|
+
reportDataFiles.push(filePayload);
|
|
159
225
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
226
|
+
else {
|
|
227
|
+
assetFiles.push(filePayload);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (reportDataFiles.length > 0) {
|
|
231
|
+
if (canUploadReportFilesInBatch) {
|
|
232
|
+
uploadBatches.push({
|
|
233
|
+
batchId: `batch-${batchIndex}-report`,
|
|
234
|
+
kind: "report",
|
|
235
|
+
files: reportDataFiles,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
reportDataFiles.forEach((filePayload, fileIndex) => {
|
|
240
|
+
uploadBatches.push({
|
|
241
|
+
batchId: `batch-${batchIndex}-report-${fileIndex}`,
|
|
242
|
+
kind: "report",
|
|
243
|
+
files: [filePayload],
|
|
170
244
|
});
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (assetFiles.length > 0) {
|
|
249
|
+
if (canUploadAssetsInBatch) {
|
|
250
|
+
uploadBatches.push({
|
|
251
|
+
batchId: `batch-${batchIndex}-asset`,
|
|
252
|
+
kind: "asset",
|
|
253
|
+
files: assetFiles,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
assetFiles.forEach((filePayload, fileIndex) => {
|
|
258
|
+
uploadBatches.push({
|
|
259
|
+
batchId: `batch-${batchIndex}-asset-${fileIndex}`,
|
|
260
|
+
kind: "asset",
|
|
261
|
+
files: [filePayload],
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
const uploadFileBatch = async ({ kind, files: batchFiles }) => {
|
|
268
|
+
if (batchFiles.length === 0 || uploadAbortController.signal.aborted) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (kind === "report") {
|
|
272
|
+
if (payload.addReportFiles) {
|
|
273
|
+
const batchResult = await payload.addReportFiles({
|
|
274
|
+
reportUuid,
|
|
275
|
+
pluginId,
|
|
276
|
+
files: batchFiles,
|
|
277
|
+
signal: uploadAbortController.signal,
|
|
278
|
+
});
|
|
279
|
+
for (const [filename, fileUrl] of Object.entries(batchResult)) {
|
|
280
|
+
hrefs[filename] = fileUrl;
|
|
281
|
+
if (filename === "index.html") {
|
|
282
|
+
indexHref = fileUrl;
|
|
283
|
+
}
|
|
171
284
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
for (const filePayload of batchFiles) {
|
|
288
|
+
const fileUrl = await payload.addReportFile({
|
|
289
|
+
reportUuid,
|
|
290
|
+
pluginId,
|
|
291
|
+
...filePayload,
|
|
177
292
|
});
|
|
293
|
+
hrefs[filePayload.filename] = fileUrl;
|
|
294
|
+
if (filePayload.filename === "index.html") {
|
|
295
|
+
indexHref = fileUrl;
|
|
296
|
+
}
|
|
178
297
|
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
else if (payload.addReportAssets) {
|
|
301
|
+
await payload.addReportAssets({
|
|
302
|
+
files: batchFiles,
|
|
303
|
+
signal: uploadAbortController.signal,
|
|
179
304
|
});
|
|
180
|
-
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
for (const filePayload of batchFiles) {
|
|
308
|
+
await payload.addReportAsset(filePayload);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
onProgress?.(batchFiles.length);
|
|
312
|
+
};
|
|
313
|
+
const uploadNext = async () => {
|
|
314
|
+
while (!uploadAbortController.signal.aborted) {
|
|
315
|
+
const batchIndex = nextBatchIndex++;
|
|
316
|
+
if (batchIndex >= uploadBatches.length) {
|
|
181
317
|
return;
|
|
182
318
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
319
|
+
const batch = uploadBatches[batchIndex];
|
|
320
|
+
const uploaded = await uploadWithRetry(batch.batchId, uploadAbortController, failedUploads, uploadMaxAttempts, uploadMaxSimultaneousFailures, async () => uploadFileBatch(batch));
|
|
321
|
+
if (!uploaded || uploadAbortController.signal.aborted) {
|
|
322
|
+
return;
|
|
188
323
|
}
|
|
189
|
-
onProgress?.();
|
|
190
324
|
}
|
|
191
325
|
};
|
|
192
|
-
const uploadTasks = Array.from({ length: Math.min(uploadConcurrency,
|
|
326
|
+
const uploadTasks = Array.from({ length: Math.min(uploadConcurrency, uploadBatches.length) }, () => uploadNext());
|
|
193
327
|
try {
|
|
194
328
|
await Promise.all(uploadTasks);
|
|
195
329
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { UploadReportFilePayload } from "../model.js";
|
|
2
|
+
export declare const readUploadContent: (payload: UploadReportFilePayload) => Promise<Buffer<ArrayBufferLike>>;
|
|
3
|
+
export declare const createUploadForm: (files: UploadReportFilePayload[], createUploadBlob: (content: Buffer, filename: string) => Blob, signal?: AbortSignal, mapFilename?: (filename: string) => string) => Promise<{
|
|
4
|
+
entries: {
|
|
5
|
+
filename: string;
|
|
6
|
+
reportFilename: string;
|
|
7
|
+
content: Buffer<ArrayBufferLike>;
|
|
8
|
+
}[];
|
|
9
|
+
form: import("undici-types").FormData;
|
|
10
|
+
}>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
export const readUploadContent = async (payload) => {
|
|
3
|
+
const { file, filepath, signal } = payload;
|
|
4
|
+
if (file) {
|
|
5
|
+
return file;
|
|
6
|
+
}
|
|
7
|
+
if (!filepath) {
|
|
8
|
+
throw new Error("File or filepath is required");
|
|
9
|
+
}
|
|
10
|
+
return signal ? readFile(filepath, { signal }) : readFile(filepath);
|
|
11
|
+
};
|
|
12
|
+
export const createUploadForm = async (files, createUploadBlob, signal, mapFilename) => {
|
|
13
|
+
const entries = await Promise.all(files.map(async ({ filename, ...filePayload }) => {
|
|
14
|
+
const reportFilename = mapFilename ? mapFilename(filename) : filename;
|
|
15
|
+
return {
|
|
16
|
+
filename,
|
|
17
|
+
reportFilename,
|
|
18
|
+
content: await readUploadContent({ filename, ...filePayload, signal: signal ?? filePayload.signal }),
|
|
19
|
+
};
|
|
20
|
+
}));
|
|
21
|
+
const form = new FormData();
|
|
22
|
+
for (const { reportFilename, content } of entries) {
|
|
23
|
+
form.append("filename", reportFilename);
|
|
24
|
+
form.append("file", createUploadBlob(content, reportFilename), reportFilename);
|
|
25
|
+
}
|
|
26
|
+
return { entries, form };
|
|
27
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@allurereport/service",
|
|
3
|
-
"version": "3.14.
|
|
3
|
+
"version": "3.14.2",
|
|
4
4
|
"description": "Allure Service API",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"allure",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"lint:fix": "oxlint --import-plugin --fix src test features stories"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@allurereport/core-api": "3.14.
|
|
34
|
-
"@allurereport/plugin-api": "3.14.
|
|
33
|
+
"@allurereport/core-api": "3.14.2",
|
|
34
|
+
"@allurereport/plugin-api": "3.14.2",
|
|
35
35
|
"axios": "^1.18.1",
|
|
36
36
|
"open": "^11.0.0"
|
|
37
37
|
},
|