From 5f263eb95ccb303f0ea393643362315730f0733c Mon Sep 17 00:00:00 2001 From: Leandro Beretta Date: Wed, 26 Nov 2025 15:07:01 -0300 Subject: [PATCH 1/4] active since field added --- web/locales/en/plugin__netobserv-plugin.json | 2 + web/src/components/health/alert-details.tsx | 6 +++ web/src/components/health/alert-row.tsx | 2 + web/src/components/health/rule-details.tsx | 1 + web/src/utils/datetime.ts | 43 ++++++++++++++++++++ 5 files changed, 54 insertions(+) diff --git a/web/locales/en/plugin__netobserv-plugin.json b/web/locales/en/plugin__netobserv-plugin.json index afd3fe408..7c8865cf6 100644 --- a/web/locales/en/plugin__netobserv-plugin.json +++ b/web/locales/en/plugin__netobserv-plugin.json @@ -246,6 +246,8 @@ "Navigate to alert details": "Navigate to alert details", "State": "State", "Severity": "Severity", + "Active since": "Active since", + "Yesterday": "Yesterday", "Labels": "Labels", "Description": "Description", "Navigate to network traffic": "Navigate to network traffic", diff --git a/web/src/components/health/alert-details.tsx b/web/src/components/health/alert-details.tsx index bb083145f..3f31f4bb0 100644 --- a/web/src/components/health/alert-details.tsx +++ b/web/src/components/health/alert-details.tsx @@ -3,6 +3,7 @@ import * as React from 'react'; import { Label, Text, TextContent, TextVariants } from '@patternfly/react-core'; import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; +import { formatActiveSince } from '../../utils/datetime'; import { valueFormat } from '../../utils/format'; import { AlertWithRuleName, getAlertFilteredLabels, getAlertLink } from './health-helper'; @@ -25,6 +26,11 @@ export const AlertDetails: React.FC = ({ resourceName, alert {alert.state} {alert.labels.severity} + {alert.activeAt && ( + + {formatActiveSince(alert.activeAt)} + + )} {labels.length === 0 ? t('None') diff --git a/web/src/components/health/alert-row.tsx b/web/src/components/health/alert-row.tsx index 51c6ef9cf..122ac7a7f 100644 --- a/web/src/components/health/alert-row.tsx +++ b/web/src/components/health/alert-row.tsx @@ -3,6 +3,7 @@ import * as React from 'react'; import { Label, Tooltip } from '@patternfly/react-core'; import { useTranslation } from 'react-i18next'; +import { formatActiveSince } from '../../utils/datetime'; import { valueFormat } from '../../utils/format'; import { HealthColorSquare } from './health-color-square'; import { AlertWithRuleName, getAlertFilteredLabels, getAlertLink, getTrafficLink } from './health-helper'; @@ -39,6 +40,7 @@ export const AlertRow: React.FC = ({ kind, resourceName, alert, w )} {alert.state} {alert.labels.severity} + {alert.activeAt && {formatActiveSince(alert.activeAt)}} {labels.length === 0 ? t('None') diff --git a/web/src/components/health/rule-details.tsx b/web/src/components/health/rule-details.tsx index 9209e0706..c30232ffb 100644 --- a/web/src/components/health/rule-details.tsx +++ b/web/src/components/health/rule-details.tsx @@ -23,6 +23,7 @@ export const RuleDetails: React.FC = ({ kind, info, wide }) => {t('Summary')} {t('State')} {t('Severity')} + {t('Active since')} {t('Labels')} {t('Value')} {t('Description')} diff --git a/web/src/utils/datetime.ts b/web/src/utils/datetime.ts index 48599e35d..fea317979 100644 --- a/web/src/utils/datetime.ts +++ b/web/src/utils/datetime.ts @@ -123,3 +123,46 @@ export const computeStepInterval = (range: TimeRange | number) => { stepSeconds: step }; }; + +/** + * Formats a timestamp for "Active since" display with relative or absolute time. + * - Today: Shows only time (14:23) + * - Yesterday: Shows "Yesterday, HH:MM" + * - Last 7 days: Shows day of week and time (Tue, 14:23) + * - Older: Shows full date and time (2025-11-24 14:23) + */ +export const formatActiveSince = (timestamp: string): string => { + const activeDate = new Date(timestamp); + const now = new Date(); + + // Calculate time difference in milliseconds + const diffMs = now.getTime() - activeDate.getTime(); + const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); + + // Format time as HH:MM + const timeStr = twentyFourHourTime(activeDate, false); + + // Today: show only time + if (diffDays === 0 && now.getDate() === activeDate.getDate()) { + return timeStr; + } + + // Yesterday: show "Yesterday, HH:MM" + const yesterday = new Date(now); + yesterday.setDate(yesterday.getDate() - 1); + if (activeDate.getDate() === yesterday.getDate() && + activeDate.getMonth() === yesterday.getMonth() && + activeDate.getFullYear() === yesterday.getFullYear()) { + return `Yesterday, ${timeStr}`; + } + + // Last 7 days: show day of week and time + if (diffDays < 7) { + const weekdayFormatter = new Intl.DateTimeFormat(getLanguage(), { weekday: 'short' }); + const weekday = weekdayFormatter.format(activeDate); + return `${weekday}, ${timeStr}`; + } + + // Older: show full date and time (YYYY-MM-DD HH:MM) + return `${toISODateString(activeDate)} ${timeStr}`; +}; From 318df1741e116d812a5c48c94935885bbe9e6203 Mon Sep 17 00:00:00 2001 From: Leandro Beretta Date: Wed, 26 Nov 2025 16:52:02 -0300 Subject: [PATCH 2/4] fix format --- web/locales/en/plugin__netobserv-plugin.json | 1 - web/src/components/health/alert-details.tsx | 4 +--- web/src/utils/datetime.ts | 8 +++++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/web/locales/en/plugin__netobserv-plugin.json b/web/locales/en/plugin__netobserv-plugin.json index 7c8865cf6..1d6127901 100644 --- a/web/locales/en/plugin__netobserv-plugin.json +++ b/web/locales/en/plugin__netobserv-plugin.json @@ -247,7 +247,6 @@ "State": "State", "Severity": "Severity", "Active since": "Active since", - "Yesterday": "Yesterday", "Labels": "Labels", "Description": "Description", "Navigate to network traffic": "Navigate to network traffic", diff --git a/web/src/components/health/alert-details.tsx b/web/src/components/health/alert-details.tsx index 3f31f4bb0..c5e3f000e 100644 --- a/web/src/components/health/alert-details.tsx +++ b/web/src/components/health/alert-details.tsx @@ -27,9 +27,7 @@ export const AlertDetails: React.FC = ({ resourceName, alert {alert.state} {alert.labels.severity} {alert.activeAt && ( - - {formatActiveSince(alert.activeAt)} - + {formatActiveSince(alert.activeAt)} )} {labels.length === 0 diff --git a/web/src/utils/datetime.ts b/web/src/utils/datetime.ts index fea317979..d7b1cadb9 100644 --- a/web/src/utils/datetime.ts +++ b/web/src/utils/datetime.ts @@ -150,9 +150,11 @@ export const formatActiveSince = (timestamp: string): string => { // Yesterday: show "Yesterday, HH:MM" const yesterday = new Date(now); yesterday.setDate(yesterday.getDate() - 1); - if (activeDate.getDate() === yesterday.getDate() && - activeDate.getMonth() === yesterday.getMonth() && - activeDate.getFullYear() === yesterday.getFullYear()) { + if ( + activeDate.getDate() === yesterday.getDate() && + activeDate.getMonth() === yesterday.getMonth() && + activeDate.getFullYear() === yesterday.getFullYear() + ) { return `Yesterday, ${timeStr}`; } From ec3af40668bdd9b589812b4d51ccda067669bda9 Mon Sep 17 00:00:00 2001 From: Leandro Beretta Date: Thu, 27 Nov 2025 09:58:18 -0300 Subject: [PATCH 3/4] add tests to activeSince function --- web/locales/en/plugin__netobserv-plugin.json | 3 +- web/src/components/health/alert-details.tsx | 2 +- web/src/components/health/alert-row.tsx | 2 +- web/src/utils/__tests__/datetime.spec.ts | 67 +++++++++++++++++++- web/src/utils/datetime.ts | 4 +- 5 files changed, 72 insertions(+), 6 deletions(-) diff --git a/web/locales/en/plugin__netobserv-plugin.json b/web/locales/en/plugin__netobserv-plugin.json index 1d6127901..3615c3910 100644 --- a/web/locales/en/plugin__netobserv-plugin.json +++ b/web/locales/en/plugin__netobserv-plugin.json @@ -584,5 +584,6 @@ "The top DNS response code extracted from DNS response headers compared to total over the selected interval": "The top DNS response code extracted from DNS response headers compared to total over the selected interval", "rates": "rates", "with total": "with total", - "Invalid custom panel id": "Invalid custom panel id" + "Invalid custom panel id": "Invalid custom panel id", + "Yesterday": "Yesterday" } diff --git a/web/src/components/health/alert-details.tsx b/web/src/components/health/alert-details.tsx index c5e3f000e..5d94cc242 100644 --- a/web/src/components/health/alert-details.tsx +++ b/web/src/components/health/alert-details.tsx @@ -27,7 +27,7 @@ export const AlertDetails: React.FC = ({ resourceName, alert {alert.state} {alert.labels.severity} {alert.activeAt && ( - {formatActiveSince(alert.activeAt)} + {formatActiveSince(t, alert.activeAt)} )} {labels.length === 0 diff --git a/web/src/components/health/alert-row.tsx b/web/src/components/health/alert-row.tsx index 122ac7a7f..bfbbccee2 100644 --- a/web/src/components/health/alert-row.tsx +++ b/web/src/components/health/alert-row.tsx @@ -40,7 +40,7 @@ export const AlertRow: React.FC = ({ kind, resourceName, alert, w )} {alert.state} {alert.labels.severity} - {alert.activeAt && {formatActiveSince(alert.activeAt)}} + {alert.activeAt && {formatActiveSince(t, alert.activeAt)}} {labels.length === 0 ? t('None') diff --git a/web/src/utils/__tests__/datetime.spec.ts b/web/src/utils/__tests__/datetime.spec.ts index 469b2c429..b9f37bb63 100644 --- a/web/src/utils/__tests__/datetime.spec.ts +++ b/web/src/utils/__tests__/datetime.spec.ts @@ -1,4 +1,6 @@ -import { toISODateString, twentyFourHourTime } from '../datetime'; +import { TFunction } from 'react-i18next'; +import { formatActiveSince, toISODateString, twentyFourHourTime } from '../datetime'; +import { getLanguage } from '../language'; describe('datetime', () => { it('should toISODateString', () => { @@ -13,3 +15,66 @@ describe('datetime', () => { expect(twentyFourHourTime(new Date('1955-11-05T06:15:00'))).toBe('06:15'); }); }); + +describe('formatActiveSince', () => { + const FIXED_NOW = new Date('2025-11-27T15:00:00'); + const tMock: TFunction = ((key: string) => key) as unknown as TFunction; + + beforeAll(() => { + jest.useFakeTimers(); + jest.setSystemTime(FIXED_NOW); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + it('should format "today" as only time', () => { + const ts = '2025-11-27T10:15:00'; + const date = new Date(ts); + + const expectedTime = twentyFourHourTime(date, false); + + const result = formatActiveSince(tMock, ts); + + expect(result).toBe(expectedTime); + }); + + it('should format "yesterday" as "Yesterday, HH:MM"', () => { + const ts = '2025-11-26T22:00:00'; + const date = new Date(ts); + + const expectedTime = twentyFourHourTime(date, false); + + const result = formatActiveSince(tMock,ts); + + expect(result).toBe(`Yesterday, ${expectedTime}`); + }); + + it('should format dates within the last 7 days as "Weekday, HH:MM"', () => { + const ts = '2025-11-25T09:30:00'; + const date = new Date(ts); + + const weekdayFormatter = new Intl.DateTimeFormat(getLanguage(), { + weekday: 'short', + }); + const weekday = weekdayFormatter.format(date); + const time = twentyFourHourTime(date, false); + + const result = formatActiveSince(tMock, ts); + + expect(result).toBe(`${weekday}, ${time}`); + }); + + it('should format older dates with "YYYY-MM-DD HH:MM"', () => { + const ts = '2025-11-07T14:00:00'; + const date = new Date(ts); + + const expectedDate = toISODateString(date); + const expectedTime = twentyFourHourTime(date, false); + + const result = formatActiveSince(tMock, ts); + + expect(result).toBe(`${expectedDate} ${expectedTime}`); + }); +}); diff --git a/web/src/utils/datetime.ts b/web/src/utils/datetime.ts index d7b1cadb9..c66c92157 100644 --- a/web/src/utils/datetime.ts +++ b/web/src/utils/datetime.ts @@ -131,7 +131,7 @@ export const computeStepInterval = (range: TimeRange | number) => { * - Last 7 days: Shows day of week and time (Tue, 14:23) * - Older: Shows full date and time (2025-11-24 14:23) */ -export const formatActiveSince = (timestamp: string): string => { +export const formatActiveSince = (t: TFunction, timestamp: string): string => { const activeDate = new Date(timestamp); const now = new Date(); @@ -155,7 +155,7 @@ export const formatActiveSince = (timestamp: string): string => { activeDate.getMonth() === yesterday.getMonth() && activeDate.getFullYear() === yesterday.getFullYear() ) { - return `Yesterday, ${timeStr}`; + return `${t('Yesterday')}, ${timeStr}`; } // Last 7 days: show day of week and time From ba0058ae3947d60a452e6e447f393473f7ccdd4b Mon Sep 17 00:00:00 2001 From: Leandro Beretta Date: Thu, 27 Nov 2025 10:41:07 -0300 Subject: [PATCH 4/4] fix fmt --- web/locales/en/plugin__netobserv-plugin.json | 4 ++-- web/src/utils/__tests__/datetime.spec.ts | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/web/locales/en/plugin__netobserv-plugin.json b/web/locales/en/plugin__netobserv-plugin.json index 3615c3910..268e86918 100644 --- a/web/locales/en/plugin__netobserv-plugin.json +++ b/web/locales/en/plugin__netobserv-plugin.json @@ -516,6 +516,7 @@ "Last 2 days": "Last 2 days", "Last 1 week": "Last 1 week", "Last 2 weeks": "Last 2 weeks", + "Yesterday": "Yesterday", "Value is empty": "Value is empty", "Value is malformed": "Value is malformed", "Not a valid Kubernetes name": "Not a valid Kubernetes name", @@ -584,6 +585,5 @@ "The top DNS response code extracted from DNS response headers compared to total over the selected interval": "The top DNS response code extracted from DNS response headers compared to total over the selected interval", "rates": "rates", "with total": "with total", - "Invalid custom panel id": "Invalid custom panel id", - "Yesterday": "Yesterday" + "Invalid custom panel id": "Invalid custom panel id" } diff --git a/web/src/utils/__tests__/datetime.spec.ts b/web/src/utils/__tests__/datetime.spec.ts index b9f37bb63..8fd480dc1 100644 --- a/web/src/utils/__tests__/datetime.spec.ts +++ b/web/src/utils/__tests__/datetime.spec.ts @@ -19,7 +19,7 @@ describe('datetime', () => { describe('formatActiveSince', () => { const FIXED_NOW = new Date('2025-11-27T15:00:00'); const tMock: TFunction = ((key: string) => key) as unknown as TFunction; - + beforeAll(() => { jest.useFakeTimers(); jest.setSystemTime(FIXED_NOW); @@ -46,17 +46,17 @@ describe('formatActiveSince', () => { const expectedTime = twentyFourHourTime(date, false); - const result = formatActiveSince(tMock,ts); + const result = formatActiveSince(tMock, ts); expect(result).toBe(`Yesterday, ${expectedTime}`); }); it('should format dates within the last 7 days as "Weekday, HH:MM"', () => { - const ts = '2025-11-25T09:30:00'; + const ts = '2025-11-25T09:30:00'; const date = new Date(ts); const weekdayFormatter = new Intl.DateTimeFormat(getLanguage(), { - weekday: 'short', + weekday: 'short' }); const weekday = weekdayFormatter.format(date); const time = twentyFourHourTime(date, false); @@ -67,7 +67,7 @@ describe('formatActiveSince', () => { }); it('should format older dates with "YYYY-MM-DD HH:MM"', () => { - const ts = '2025-11-07T14:00:00'; + const ts = '2025-11-07T14:00:00'; const date = new Date(ts); const expectedDate = toISODateString(date);