fix: retain complete package failure telemetry
Include immediately cleaned successful files and byte aggregates when finalizing package results so mixed cleanup states remain partial instead of failed. Carry additive download, offline, extraction, remux, and cleanup failure counters alongside the prioritized display phase, aggregate them into run results, and expose cleanup failures in the final summary.
This commit is contained in:
@@ -42,6 +42,7 @@ export interface RunResult {
|
|||||||
remuxFailures: number;
|
remuxFailures: number;
|
||||||
downloadFailures: number;
|
downloadFailures: number;
|
||||||
offlineFailures: number;
|
offlineFailures: number;
|
||||||
|
cleanupFailures: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RunResultInput {
|
export interface RunResultInput {
|
||||||
@@ -289,10 +290,11 @@ export function buildRunResult(input: RunResultInput): RunResult {
|
|||||||
extractionDurationSeconds: sum((result) => result.extractionDurationSeconds),
|
extractionDurationSeconds: sum((result) => result.extractionDurationSeconds),
|
||||||
remuxDurationSeconds: sum((result) => result.remuxDurationSeconds),
|
remuxDurationSeconds: sum((result) => result.remuxDurationSeconds),
|
||||||
postProcessDurationSeconds: sum((result) => result.postProcessDurationSeconds),
|
postProcessDurationSeconds: sum((result) => result.postProcessDurationSeconds),
|
||||||
extractionFailures: packages.reduce((total, result) => total + result.archiveOperations.filter((operation) => operation.status === "failed").length, 0),
|
extractionFailures: sum((result) => result.extractionFailures),
|
||||||
remuxFailures: packages.reduce((total, result) => total + result.remuxOperations.filter((operation) => operation.status === "failed").length, 0),
|
remuxFailures: sum((result) => result.remuxFailures),
|
||||||
downloadFailures: packages.filter((result) => result.failurePhase === "download").reduce((total, result) => total + result.failedFiles, 0),
|
downloadFailures: sum((result) => result.downloadFailures),
|
||||||
offlineFailures: packages.filter((result) => /offline|not found|nicht gefunden/i.test(result.errorCategory)).reduce((total, result) => total + result.failedFiles, 0)
|
offlineFailures: sum((result) => result.offlineFailures),
|
||||||
|
cleanupFailures: sum((result) => result.cleanupFailures)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,7 +325,8 @@ export function buildRunNotificationEvent(result: RunResult): NotificationEvent
|
|||||||
{ name: "Entpackfehler", value: String(result.extractionFailures), inline: true },
|
{ name: "Entpackfehler", value: String(result.extractionFailures), inline: true },
|
||||||
{ name: "Remuxfehler", value: String(result.remuxFailures), inline: true },
|
{ name: "Remuxfehler", value: String(result.remuxFailures), inline: true },
|
||||||
{ name: "Downloadfehler", value: String(result.downloadFailures), inline: true },
|
{ name: "Downloadfehler", value: String(result.downloadFailures), inline: true },
|
||||||
{ name: "Offline", value: String(result.offlineFailures), inline: true }
|
{ name: "Offline", value: String(result.offlineFailures), inline: true },
|
||||||
|
{ name: "Cleanupfehler", value: String(result.cleanupFailures), inline: true }
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,7 +123,8 @@ export function finalizePackageResult(telemetry: PackageTelemetry): PackageResul
|
|||||||
.map((operation) => ({ ...operation, itemIds: [...operation.itemIds] }));
|
.map((operation) => ({ ...operation, itemIds: [...operation.itemIds] }));
|
||||||
const remuxOperations = (telemetry.remuxOperations ?? packageEntry.remuxOperations ?? [])
|
const remuxOperations = (telemetry.remuxOperations ?? packageEntry.remuxOperations ?? [])
|
||||||
.map((operation) => ({ ...operation }));
|
.map((operation) => ({ ...operation }));
|
||||||
const completedDownloads = telemetry.items.filter((item) => item.status === "completed").length;
|
const cleanedCompletedDownloads = Math.max(0, Math.floor(finiteNonNegative(packageEntry.cleanedCompletedItemCount)));
|
||||||
|
const completedDownloads = cleanedCompletedDownloads + telemetry.items.filter((item) => item.status === "completed").length;
|
||||||
const failedDownloads = telemetry.items.filter((item) => item.status === "failed");
|
const failedDownloads = telemetry.items.filter((item) => item.status === "failed");
|
||||||
const cancelledDownloads = telemetry.items.filter((item) => item.status === "cancelled").length;
|
const cancelledDownloads = telemetry.items.filter((item) => item.status === "cancelled").length;
|
||||||
const failedArchives = archiveOperations.filter((operation) => operation.status === "failed").length;
|
const failedArchives = archiveOperations.filter((operation) => operation.status === "failed").length;
|
||||||
@@ -133,7 +134,12 @@ export function finalizePackageResult(telemetry: PackageTelemetry): PackageResul
|
|||||||
const audioStripFailures = Math.max(0, Math.floor(finiteNonNegative(packageEntry.audioStripSummary?.failed)));
|
const audioStripFailures = Math.max(0, Math.floor(finiteNonNegative(packageEntry.audioStripSummary?.failed)));
|
||||||
const remuxFailures = Math.max(failedRemuxOperations, audioStripFailures);
|
const remuxFailures = Math.max(failedRemuxOperations, audioStripFailures);
|
||||||
const cleanupErrorCategory = String(telemetry.cleanupErrorCategory ?? packageEntry.cleanupErrorCategory ?? "").trim();
|
const cleanupErrorCategory = String(telemetry.cleanupErrorCategory ?? packageEntry.cleanupErrorCategory ?? "").trim();
|
||||||
const postProcessFailures = failedArchives + remuxFailures + (cleanupErrorCategory ? 1 : 0);
|
const downloadFailureCategories = failedDownloads.map((item) =>
|
||||||
|
projectPackageFailureCategory("download", item.lastError || item.fullStatus)
|
||||||
|
);
|
||||||
|
const offlineFailures = downloadFailureCategories.filter((category) => category === "Offline").length;
|
||||||
|
const cleanupFailures = cleanupErrorCategory ? 1 : 0;
|
||||||
|
const postProcessFailures = failedArchives + remuxFailures + cleanupFailures;
|
||||||
const postProcessCancellations = cancelledArchives + cancelledRemuxOperations;
|
const postProcessCancellations = cancelledArchives + cancelledRemuxOperations;
|
||||||
const failedFiles = failedDownloads.length + postProcessFailures;
|
const failedFiles = failedDownloads.length + postProcessFailures;
|
||||||
const cancelledFiles = cancelledDownloads + postProcessCancellations;
|
const cancelledFiles = cancelledDownloads + postProcessCancellations;
|
||||||
@@ -143,8 +149,10 @@ export function finalizePackageResult(telemetry: PackageTelemetry): PackageResul
|
|||||||
const postProcessStartedAt = finiteNonNegative(packageEntry.postProcessStartedAt);
|
const postProcessStartedAt = finiteNonNegative(packageEntry.postProcessStartedAt);
|
||||||
const postProcessCompletedAt = finiteNonNegative(packageEntry.postProcessCompletedAt);
|
const postProcessCompletedAt = finiteNonNegative(packageEntry.postProcessCompletedAt);
|
||||||
const completedAt = finiteNonNegative(packageEntry.terminalAt);
|
const completedAt = finiteNonNegative(packageEntry.terminalAt);
|
||||||
const downloadedBytes = telemetry.items.reduce((total, item) => total + finiteNonNegative(item.downloadedBytes), 0);
|
const downloadedBytes = finiteNonNegative(packageEntry.cleanedDownloadedBytes)
|
||||||
const totalBytes = telemetry.items.reduce((total, item) => total + finiteNonNegative(item.totalBytes ?? item.downloadedBytes), 0);
|
+ telemetry.items.reduce((total, item) => total + finiteNonNegative(item.downloadedBytes), 0);
|
||||||
|
const totalBytes = finiteNonNegative(packageEntry.cleanedTotalBytes)
|
||||||
|
+ telemetry.items.reduce((total, item) => total + finiteNonNegative(item.totalBytes ?? item.downloadedBytes), 0);
|
||||||
const downloadDurationSeconds = durationSecondsBetween(startedAt, downloadEndedAt);
|
const downloadDurationSeconds = durationSecondsBetween(startedAt, downloadEndedAt);
|
||||||
const extractionDurationSeconds = sumOperationDurationSeconds(archiveOperations);
|
const extractionDurationSeconds = sumOperationDurationSeconds(archiveOperations);
|
||||||
const remuxDurationSeconds = sumOperationDurationSeconds(remuxOperations);
|
const remuxDurationSeconds = sumOperationDurationSeconds(remuxOperations);
|
||||||
@@ -177,6 +185,11 @@ export function finalizePackageResult(telemetry: PackageTelemetry): PackageResul
|
|||||||
successfulFiles,
|
successfulFiles,
|
||||||
failedFiles,
|
failedFiles,
|
||||||
cancelledFiles,
|
cancelledFiles,
|
||||||
|
downloadFailures: failedDownloads.length,
|
||||||
|
offlineFailures,
|
||||||
|
extractionFailures: failedArchives,
|
||||||
|
remuxFailures,
|
||||||
|
cleanupFailures,
|
||||||
archiveCount: archiveOperations.length,
|
archiveCount: archiveOperations.length,
|
||||||
partCount: archiveOperations.reduce((total, operation) => total + Math.max(0, Math.floor(finiteNonNegative(operation.partCount))), 0),
|
partCount: archiveOperations.reduce((total, operation) => total + Math.max(0, Math.floor(finiteNonNegative(operation.partCount))), 0),
|
||||||
outputCount: Math.max(0, Math.floor(finiteNonNegative(telemetry.outputCount ?? packageEntry.outputCount))),
|
outputCount: Math.max(0, Math.floor(finiteNonNegative(telemetry.outputCount ?? packageEntry.outputCount))),
|
||||||
|
|||||||
@@ -546,6 +546,11 @@ export interface PackageResult {
|
|||||||
successfulFiles: number;
|
successfulFiles: number;
|
||||||
failedFiles: number;
|
failedFiles: number;
|
||||||
cancelledFiles: number;
|
cancelledFiles: number;
|
||||||
|
downloadFailures: number;
|
||||||
|
offlineFailures: number;
|
||||||
|
extractionFailures: number;
|
||||||
|
remuxFailures: number;
|
||||||
|
cleanupFailures: number;
|
||||||
archiveCount: number;
|
archiveCount: number;
|
||||||
partCount: number;
|
partCount: number;
|
||||||
outputCount: number;
|
outputCount: number;
|
||||||
|
|||||||
@@ -401,6 +401,11 @@ describe("NotificationOutbox", () => {
|
|||||||
successfulFiles: 0,
|
successfulFiles: 0,
|
||||||
failedFiles: 1,
|
failedFiles: 1,
|
||||||
cancelledFiles: 0,
|
cancelledFiles: 0,
|
||||||
|
downloadFailures: 1,
|
||||||
|
offlineFailures: 0,
|
||||||
|
extractionFailures: 0,
|
||||||
|
remuxFailures: 0,
|
||||||
|
cleanupFailures: 0,
|
||||||
archiveCount: 0,
|
archiveCount: 0,
|
||||||
partCount: 0,
|
partCount: 0,
|
||||||
outputCount: 0,
|
outputCount: 0,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
durationSecondsBetween,
|
durationSecondsBetween,
|
||||||
finalizePackageResult
|
finalizePackageResult
|
||||||
} from "../src/main/package-telemetry";
|
} from "../src/main/package-telemetry";
|
||||||
|
import { buildRunResult } from "../src/main/notification-events";
|
||||||
import { normalizeLoadedSession } from "../src/main/storage";
|
import { normalizeLoadedSession } from "../src/main/storage";
|
||||||
|
|
||||||
function packageEntry(overrides: Partial<PackageEntry> = {}): PackageEntry {
|
function packageEntry(overrides: Partial<PackageEntry> = {}): PackageEntry {
|
||||||
@@ -174,6 +175,28 @@ describe("package lifecycle telemetry", () => {
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("includes immediately cleaned successes and bytes in a mixed package result", () => {
|
||||||
|
const failedItem = downloadItem("item-3", "failed");
|
||||||
|
const result = finalizePackageResult(telemetry({
|
||||||
|
package: packageEntry({
|
||||||
|
status: "failed",
|
||||||
|
itemIds: [failedItem.id],
|
||||||
|
cleanedCompletedItemCount: 2,
|
||||||
|
cleanedDownloadedBytes: 2_000,
|
||||||
|
cleanedTotalBytes: 2_000
|
||||||
|
}),
|
||||||
|
items: [failedItem]
|
||||||
|
}));
|
||||||
|
|
||||||
|
expect(result).toEqual(expect.objectContaining({
|
||||||
|
status: "partial",
|
||||||
|
successfulFiles: 2,
|
||||||
|
failedFiles: 1,
|
||||||
|
downloadedBytes: 2_000,
|
||||||
|
totalBytes: 3_000
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
it("classifies a failed download without error text as a download-phase failure", () => {
|
it("classifies a failed download without error text as a download-phase failure", () => {
|
||||||
const item = { ...downloadItem("item-1", "failed"), lastError: "", fullStatus: "" };
|
const item = { ...downloadItem("item-1", "failed"), lastError: "", fullStatus: "" };
|
||||||
const result = finalizePackageResult(telemetry({
|
const result = finalizePackageResult(telemetry({
|
||||||
@@ -319,6 +342,39 @@ describe("package lifecycle telemetry", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("retains additive failure counters when cleanup is the primary failure phase", () => {
|
||||||
|
const failedItem = { ...downloadItem("item-1", "failed"), lastError: "Host ist offline" };
|
||||||
|
const packageResult = finalizePackageResult(telemetry({
|
||||||
|
items: [failedItem],
|
||||||
|
archiveOperations: [archiveOperation({ status: "failed", errorCategory: "archive-error" })],
|
||||||
|
remuxOperations: [remuxOperation({ status: "failed", errorCategory: "remux-error" })],
|
||||||
|
cleanupErrorCategory: "cleanup-error"
|
||||||
|
}));
|
||||||
|
const runResult = buildRunResult({
|
||||||
|
id: "mixed-failures",
|
||||||
|
stopped: false,
|
||||||
|
startedAt: 1_000,
|
||||||
|
completedAt: 166_000,
|
||||||
|
packages: [packageResult]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(packageResult).toEqual(expect.objectContaining({
|
||||||
|
failurePhase: "cleanup",
|
||||||
|
downloadFailures: 1,
|
||||||
|
offlineFailures: 1,
|
||||||
|
extractionFailures: 1,
|
||||||
|
remuxFailures: 1,
|
||||||
|
cleanupFailures: 1
|
||||||
|
}));
|
||||||
|
expect(runResult).toEqual(expect.objectContaining({
|
||||||
|
downloadFailures: 1,
|
||||||
|
offlineFailures: 1,
|
||||||
|
extractionFailures: 1,
|
||||||
|
remuxFailures: 1,
|
||||||
|
cleanupFailures: 1
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
it("uses audio-strip outcomes when no individual remux operation was recorded", () => {
|
it("uses audio-strip outcomes when no individual remux operation was recorded", () => {
|
||||||
const items = [downloadItem("item-1"), downloadItem("item-2")];
|
const items = [downloadItem("item-1"), downloadItem("item-2")];
|
||||||
const result = finalizePackageResult(telemetry({
|
const result = finalizePackageResult(telemetry({
|
||||||
|
|||||||
Reference in New Issue
Block a user