diff --git a/web/src/components/alerts/IncidentAlertTriggersTable.vue b/web/src/components/alerts/IncidentAlertTriggersTable.vue index 50c866cc88..39d7584901 100644 --- a/web/src/components/alerts/IncidentAlertTriggersTable.vue +++ b/web/src/components/alerts/IncidentAlertTriggersTable.vue @@ -146,7 +146,7 @@ export default defineComponent({ { name: "correlation_reason", field: "correlation_reason", - label: "Corelation Reason", + label: "Correlation Reason", align: "right", sortable: false, style: "width: 150px", diff --git a/web/src/plugins/correlation/CorrelatedLogsTable.vue b/web/src/plugins/correlation/CorrelatedLogsTable.vue index 34ef02c815..c0878c05d0 100644 --- a/web/src/plugins/correlation/CorrelatedLogsTable.vue +++ b/web/src/plugins/correlation/CorrelatedLogsTable.vue @@ -271,15 +271,6 @@ along with this program. If not, see .

{{ t("correlation.logs.noDataDetails") }}

- diff --git a/web/src/plugins/traces/metrics/TracesAnalysisDashboard.vue b/web/src/plugins/traces/metrics/TracesAnalysisDashboard.vue index b31734192b..0c3980c48a 100644 --- a/web/src/plugins/traces/metrics/TracesAnalysisDashboard.vue +++ b/web/src/plugins/traces/metrics/TracesAnalysisDashboard.vue @@ -27,34 +27,35 @@ along with this program. If not, see . -
+
-
- - - - - - - - {{ t('latencyInsights.durationLabel') }} {{ formatTimeWithSuffix(durationFilter.start) }} - {{ formatTimeWithSuffix(durationFilter.end) }} - - - - - - - - - + + + + + + + +
+ +
+ Baseline + {{ formatSmartTimestamp(baselineTimeRange.startTime, baselineTimeRange.endTime).start }} + → + {{ formatSmartTimestamp(baselineTimeRange.startTime, baselineTimeRange.endTime).end }} +
+ + +
+ Selected + {{ formatSmartTimestamp(selectedTimeRangeDisplay.startTime, selectedTimeRangeDisplay.endTime).start }} + → + {{ formatSmartTimestamp(selectedTimeRangeDisplay.startTime, selectedTimeRangeDisplay.endTime).end }} +
+ + + + {{ filterMetadata }}
@@ -556,6 +557,45 @@ const baselineTimeRange = computed(() => { return props.timeRange; }); +// Compute selected time range from filters +const selectedTimeRangeDisplay = computed(() => { + // Check for time-based filter from any RED metrics panel + if (props.rateFilter?.timeStart && props.rateFilter?.timeEnd) { + return { + startTime: props.rateFilter.timeStart, + endTime: props.rateFilter.timeEnd + }; + } else if (props.durationFilter?.timeStart && props.durationFilter?.timeEnd) { + return { + startTime: props.durationFilter.timeStart, + endTime: props.durationFilter.timeEnd + }; + } else if (props.errorFilter?.timeStart && props.errorFilter?.timeEnd) { + return { + startTime: props.errorFilter.timeStart, + endTime: props.errorFilter.timeEnd + }; + } + return null; +}); + +// Check if a selected time range exists (brush selection) +const hasSelectedTimeRange = computed(() => { + return selectedTimeRangeDisplay.value !== null; +}); + +// Additional filter metadata (duration, rate, or error count) +const filterMetadata = computed(() => { + if (props.analysisType === 'latency' && props.durationFilter && !props.durationFilter.timeStart) { + return `${t('latencyInsights.durationLabel')} ${formatTimeWithSuffix(props.durationFilter.start)} - ${formatTimeWithSuffix(props.durationFilter.end)}`; + } else if (props.analysisType === 'volume' && props.rateFilter && !props.rateFilter.timeStart) { + return `${t('volumeInsights.rateLabel')} ${props.rateFilter.start} - ${props.rateFilter.end} traces/interval`; + } else if (props.analysisType === 'error' && props.errorFilter && !props.errorFilter.timeStart) { + return `${t('errorInsights.errorsGreaterThan')} ${props.errorFilter.start}`; + } + return null; +}); + const loadAnalysis = async () => { try { // Determine which filter to use based on active analysis type @@ -677,6 +717,63 @@ const formatTimestamp = (microseconds: number) => { return `${hours}:${minutes}:${seconds}`; }; +const formatFullTimestamp = (microseconds: number) => { + const date = new Date(microseconds / 1000); + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const day = String(date.getDate()).padStart(2, "0"); + const hours = String(date.getHours()).padStart(2, "0"); + const minutes = String(date.getMinutes()).padStart(2, "0"); + const seconds = String(date.getSeconds()).padStart(2, "0"); + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; +}; + +const formatCompactTimestamp = (microseconds: number) => { + const date = new Date(microseconds / 1000); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const day = String(date.getDate()).padStart(2, "0"); + const hours = String(date.getHours()).padStart(2, "0"); + const minutes = String(date.getMinutes()).padStart(2, "0"); + const seconds = String(date.getSeconds()).padStart(2, "0"); + // Format as MM/DD HH:MM:SS (more compact) + return `${month}/${day} ${hours}:${minutes}:${seconds}`; +}; + +// Smart timestamp formatter - shows date only once if same day +const formatSmartTimestamp = (startMicroseconds: number, endMicroseconds: number) => { + const startDate = new Date(startMicroseconds / 1000); + const endDate = new Date(endMicroseconds / 1000); + + const formatTime = (date: Date) => { + const hours = String(date.getHours()).padStart(2, "0"); + const minutes = String(date.getMinutes()).padStart(2, "0"); + const seconds = String(date.getSeconds()).padStart(2, "0"); + return `${hours}:${minutes}:${seconds}`; + }; + + const formatDate = (date: Date) => { + const month = String(date.getMonth() + 1).padStart(2, "0"); + const day = String(date.getDate()).padStart(2, "0"); + return `${month}/${day}`; + }; + + const isSameDay = startDate.toDateString() === endDate.toDateString(); + + if (isSameDay) { + // Same day: show "MM/DD HH:MM:SS" for start, only "HH:MM:SS" for end + return { + start: `${formatDate(startDate)} ${formatTime(startDate)}`, + end: formatTime(endDate) + }; + } else { + // Different days: show both full timestamps + return { + start: `${formatDate(startDate)} ${formatTime(startDate)}`, + end: `${formatDate(endDate)} ${formatTime(endDate)}` + }; + } +}; + const formatTimeWithSuffix = (milliseconds: number) => { if (milliseconds >= 1000) { return `${(milliseconds / 1000).toFixed(2)}s`; @@ -875,6 +972,28 @@ watch( margin: 8px 0px; } + // Time range chips styling - matching chart colors + .time-range-chip { + font-size: 0.7rem; + line-height: 1.2; + transition: all 0.2s ease; + + &.baseline-chip { + // Red background matching baseline in charts + background: rgba(239, 68, 68, 0.12); + color: rgba(0, 0, 0, 0.85); + border: 1px solid rgba(239, 68, 68, 0.3); + } + + &.selected-chip { + // Orange background matching selected in charts + background: rgba(251, 146, 60, 0.15); + color: rgba(0, 0, 0, 0.85); + border: 1px solid rgba(251, 146, 60, 0.4); + font-weight: 500; + } + } + .analysis-content { flex: 1; overflow: hidden; // Changed to hidden - q-splitter handles overflow @@ -951,5 +1070,22 @@ body.body--dark { background-color: rgba(255, 255, 255, 0.05); } } + + // Time range chips dark mode - matching chart colors + .time-range-chip { + &.baseline-chip { + // Red background for baseline + background: rgba(239, 68, 68, 0.2); + color: rgba(255, 255, 255, 0.9); + border: 1px solid rgba(239, 68, 68, 0.4); + } + + &.selected-chip { + // Orange background for selected + background: rgba(251, 146, 60, 0.25); + color: rgba(255, 255, 255, 0.9); + border: 1px solid rgba(251, 146, 60, 0.5); + } + } }