From df3eed245ffebac284b14857a7073ea3896b4300 Mon Sep 17 00:00:00 2001 From: Marcin Lewandowski Date: Wed, 26 Jun 2024 12:59:37 +0200 Subject: [PATCH] RavenDB-21428 Ongoing tasks view: Subscription connection errors can't be copied and are truncated on the view --- .../typescript/common/generalUtils.ts | 8 +- .../database/status/ongoingTasksStats.ts | 98 +++++++++++++++---- .../wwwroot/Content/css/pages/metrics.less | 7 +- 3 files changed, 85 insertions(+), 28 deletions(-) diff --git a/src/Raven.Studio/typescript/common/generalUtils.ts b/src/Raven.Studio/typescript/common/generalUtils.ts index 5b996d05f94..0c768663db8 100644 --- a/src/Raven.Studio/typescript/common/generalUtils.ts +++ b/src/Raven.Studio/typescript/common/generalUtils.ts @@ -351,16 +351,16 @@ class genUtils { message = message.toString(); } - const lineBreakIdx = Math.min(message.indexOf("\r"), message.indexOf("\r")); - if (lineBreakIdx !== -1 && lineBreakIdx < 256) { - return message.substr(0, lineBreakIdx); + const lineBreakIdx = Math.min(message.indexOf("\n"), message.indexOf("\r")); + if (lineBreakIdx !== -1 && lineBreakIdx < limit) { + return message.substring(0, lineBreakIdx); } if (message.length < limit) { return message; } - return message.substr(0, limit) + "..."; + return message.substring(0, limit) + "..."; } static sortedAlphaNumericIndex(items: T[], newItem: T, extractor: (item: T) => string): number { diff --git a/src/Raven.Studio/typescript/viewmodels/database/status/ongoingTasksStats.ts b/src/Raven.Studio/typescript/viewmodels/database/status/ongoingTasksStats.ts index 009c6cb96b4..15ea8470ad8 100644 --- a/src/Raven.Studio/typescript/viewmodels/database/status/ongoingTasksStats.ts +++ b/src/Raven.Studio/typescript/viewmodels/database/status/ongoingTasksStats.ts @@ -22,6 +22,8 @@ import TaskUtils from "components/utils/TaskUtils"; import EtlType = Raven.Client.Documents.Operations.ETL.EtlType; import DatabaseUtils from "components/utils/DatabaseUtils"; import liveQueueSinkStatsWebSocketClient from "common/liveQueueSinkStatsWebSocketClient"; +import showDataDialog from "viewmodels/common/showDataDialog"; +import app from "durandal/app"; type treeActionType = "toggleTrack" | "trackItem" | "gapItem" | "previewEtlScript" | "previewSinkScript" | "subscriptionErrorItem" | "subscriptionPendingItem" | "subscriptionConnectionItem" | "previewSubscriptionQuery"; @@ -208,17 +210,43 @@ class hitTest { } onMouseDown() { + if (!this.overTooltip()) { + this.removeTooltip(); + } this.cursor(graphHelper.prefixStyle("grabbing")); } onMouseUp() { this.cursor(graphHelper.prefixStyle("grab")); } + private overTooltip(): boolean { + const tooltip = document.querySelector(".tooltip"); + if (!tooltip) { + return false; + } + + const [mouseX, mouseY] = d3.mouse(document.querySelector("body")); + const tooltipPosition = tooltip.getBoundingClientRect(); + if (mouseX < tooltipPosition.x - 2 || mouseX > tooltipPosition.x + tooltipPosition.width) { + return false; + } + + if (mouseY < tooltipPosition.y - 2 || mouseY > tooltipPosition.y + tooltipPosition.height) { + return false; + } + + return true; + } onMouseMove() { const clickLocation = d3.mouse(this.container.node()); const items = this.findItems(clickLocation[0], clickLocation[1]); - + + if (this.overTooltip()) { + // over tooltip - do nothing + return; + } + const overToggleTrack = items.find(x => x.actionType === "toggleTrack"); const currentPreviewEtlItem = items.find(x => x.actionType === "previewEtlScript"); @@ -241,21 +269,21 @@ class hitTest { const currentTrackEventItem = items.find(x => x.actionType === "subscriptionErrorItem"); if (currentTrackEventItem) { - this.handleSubscriptionErrorTooltip(currentTrackEventItem.arg as subscriptionErrorItemInfo , clickLocation[0], clickLocation[1]); + this.handleSubscriptionErrorTooltip(currentTrackEventItem.arg as subscriptionErrorItemInfo , clickLocation[0], currentTrackEventItem.maxY + ongoingTasksStats.brushSectionHeight); this.cursor("auto"); return; } const currentTrackPendingItem = items.find(x => x.actionType === "subscriptionPendingItem"); if (currentTrackPendingItem) { - this.handleSubscriptionPendingTooltip(currentTrackPendingItem.arg as subscriptionPendingItemInfo, clickLocation[0], clickLocation[1]); + this.handleSubscriptionPendingTooltip(currentTrackPendingItem.arg as subscriptionPendingItemInfo, clickLocation[0], currentTrackPendingItem.maxY + ongoingTasksStats.brushSectionHeight); this.cursor("auto"); return; } const currentTrackConnectionItem = items.find(x => x.actionType === "subscriptionConnectionItem"); if (currentTrackConnectionItem) { - this.handleSubscriptionConnectionTooltip(currentTrackConnectionItem.arg as subscriptionConnectionItemInfo, clickLocation[0], clickLocation[1]); + this.handleSubscriptionConnectionTooltip(currentTrackConnectionItem.arg as subscriptionConnectionItemInfo, clickLocation[0], currentTrackConnectionItem.maxY + ongoingTasksStats.brushSectionHeight); this.cursor("auto"); return; } @@ -292,6 +320,12 @@ class ongoingTasksStats extends shardViewModelBase { view = require("views/database/status/ongoingTasksStats.html"); + private static readonly showDetailsButton = ``; + /* static */ static readonly brushSectionHeight = 40; private static readonly brushSectionTrackWorkHeight = 22; @@ -396,6 +430,8 @@ class ongoingTasksStats extends shardViewModelBase { private inProgressAnimator: inProgressAnimator; private firstDataChunkDrawn = false; + + private currentDetails: string = null; /* d3 */ @@ -562,6 +598,14 @@ class ongoingTasksStats extends shardViewModelBase { () => this.hideTooltip()); this.enableLiveView(); + + const $body = $("body"); + this.registerDisposableDelegateHandler($body, "click", ".js-task-details-btn", (event: JQueryEventObject) => { + event.preventDefault(); + app.showBootstrapDialog(new showDataDialog("Error details", this.currentDetails, "plain")); + + this.hideTooltip(); + }); } private initCanvases() { @@ -1763,11 +1807,11 @@ class ongoingTasksStats extends shardViewModelBase { if (errorType === "ConnectionRejected") { errorIcon = "\uea45"; iconStyle = this.colors.tracks.ConnectionRejected; - this.drawErrorBackgound(context, iconStyle, x, y - 1); + this.drawErrorBackground(context, iconStyle, x, y - 1); } else { errorIcon = "\uea44"; iconStyle = this.colors.tracks.ConnectionAborted; - this.drawErrorBackgound(context, iconStyle, x, y - 1); + this.drawErrorBackground(context, iconStyle, x, y - 1); } context.fillStyle = iconStyle; @@ -1775,7 +1819,7 @@ class ongoingTasksStats extends shardViewModelBase { context.fillText(errorIcon, x - 8, y + 6); } - private drawErrorBackgound(context: CanvasRenderingContext2D, outlineColor: string, x: number, y: number): void { + private drawErrorBackground(context: CanvasRenderingContext2D, outlineColor: string, x: number, y: number): void { // draw background context.beginPath(); context.arc(x, y, 9, 0, 2 * Math.PI); @@ -1821,7 +1865,7 @@ class ongoingTasksStats extends shardViewModelBase { } } - // 3. Draw inner/nested operations/stripes.. + // 3. Draw inner/nested operations/stripes... if (op.Operations && op.Operations.length > 0) { this.drawStripes(context, op.Operations, currentX, yStart + yOffset, yOffset, extentFunc, perfItemWithCache, trackName); } @@ -2003,10 +2047,10 @@ class ongoingTasksStats extends shardViewModelBase { tooltipHtml += `
Size of all batches:
${generalUtils.formatBytesToSize(itemInfo.totalBatchSize)}
`; if (itemInfo.exceptionText) { - tooltipHtml += `
Message:
${itemInfo.exceptionText}
`; + tooltipHtml += `
Message:
${generalUtils.trimMessage(itemInfo.exceptionText, 1024)}
${ongoingTasksStats.showDetailsButton}
`; } - this.handleTooltip(itemInfo, x, y, tooltipHtml); + this.handleTooltip(itemInfo, x, y, tooltipHtml, itemInfo.exceptionText); } } @@ -2017,13 +2061,14 @@ class ongoingTasksStats extends shardViewModelBase { let tooltipHtml = `
${itemInfo.title}
`; tooltipHtml += `
Client URI:
${itemInfo.clientUri}
`; tooltipHtml += `
Strategy:
${itemInfo.strategy}
`; - tooltipHtml += `
Message:
${itemInfo.exceptionText}
`; - this.handleTooltip(itemInfo, x, y, tooltipHtml); + tooltipHtml += `
Message:
${generalUtils.trimMessage(itemInfo.exceptionText, 1024)}
${ongoingTasksStats.showDetailsButton}
`; + this.handleTooltip(itemInfo, x, y, tooltipHtml, itemInfo.exceptionText); } } private handleTrackTooltip(context: trackItemContext, x: number, y: number) { const currentDatum = this.tooltip.datum(); + let details: string = null; if (currentDatum !== context.item) { const type = context.rootStats.Type; @@ -2129,7 +2174,8 @@ class ongoingTasksStats extends shardViewModelBase { tooltipHtml += `
Included Time Series entries:
${elementWithData.NumberOfIncludedTimeSeriesEntries.toLocaleString()} (size: ${generalUtils.formatBytesToSize(elementWithData.SizeOfIncludedTimeSeriesInBytes)})
`; if (elementWithData.Exception) { - tooltipHtml += `
Message:
${elementWithData.Exception}
`; + tooltipHtml += `
Message:
${generalUtils.trimMessage(elementWithData.Exception, 1024)}
`; + details = elementWithData.Exception; } } break; @@ -2144,7 +2190,10 @@ class ongoingTasksStats extends shardViewModelBase { const baseElement = context.rootStats as Raven.Client.Documents.Replication.ReplicationPerformanceBase; if (baseElement.Errors) { tooltipHtml += `
Errors:
`; - baseElement.Errors.forEach(err => tooltipHtml += `
Errors:
${err.Error}
`); + baseElement.Errors.forEach(err => + tooltipHtml += `
Errors:
${generalUtils.trimMessage(err.Error, 1024)}
`); + + details = baseElement.Errors.map(x => x.Timestamp + ": " + x.Error).join("\r\n"); } } } else { // child item @@ -2309,7 +2358,11 @@ class ongoingTasksStats extends shardViewModelBase { } } - this.handleTooltip(context.item, x, y, tooltipHtml); + if (details) { + tooltipHtml += `
Details:
${ongoingTasksStats.showDetailsButton}
`; + } + + this.handleTooltip(context.item, x, y, tooltipHtml, details); } } @@ -2342,8 +2395,11 @@ class ongoingTasksStats extends shardViewModelBase { } private handleTooltip(element: taskOperation | timeGapInfo | performanceBaseWithCache | subscriptionErrorItemInfo | subscriptionPendingItemInfo, - x: number, y: number, tooltipHtml: string) { + x: number, y: number, tooltipHtml: string, details: string = undefined) { if (element && !this.dialogVisible) { + + this.currentDetails = details; + this.tooltip .style('display', undefined) .html(tooltipHtml) @@ -2353,25 +2409,25 @@ class ongoingTasksStats extends shardViewModelBase { const tooltipWidth = $tooltip.width(); const tooltipHeight = $tooltip.height(); - x = Math.min(x, Math.max(this.totalWidth - tooltipWidth, 0)); + x = Math.min(x - 80, Math.max(this.totalWidth - tooltipWidth, 0)); y = Math.min(y, Math.max(this.totalHeight - tooltipHeight, 0)); this.tooltip - .style("left", (x + 10) + "px") - .style("top", (y + 10) + "px"); + .style("left", (x + 2) + "px") + .style("top", (y + 1) + "px"); this.tooltip .transition() .duration(250) .style("opacity", 1); - - } else { this.hideTooltip(); } } private hideTooltip() { + this.currentDetails = null; + this.tooltip.transition() .duration(250) .style("opacity", 0) diff --git a/src/Raven.Studio/wwwroot/Content/css/pages/metrics.less b/src/Raven.Studio/wwwroot/Content/css/pages/metrics.less index f0275d2cdcc..c8a0c1677e1 100644 --- a/src/Raven.Studio/wwwroot/Content/css/pages/metrics.less +++ b/src/Raven.Studio/wwwroot/Content/css/pages/metrics.less @@ -5,6 +5,10 @@ .tooltip-inner { white-space: nowrap; + + .tooltip-li .value { + word-wrap: break-word; + } } .extent { @@ -35,9 +39,6 @@ rect.pane { .ongoing-tasks-stats { - .tooltip { - pointer-events: none; - } .tooltip-inner { max-width: 400px; }