Skip to content

OU-1040: feat/absolute start dates#749

Open
rioloc wants to merge 10 commits intoopenshift:mainfrom
rioloc:feat/absolute-start-dates
Open

OU-1040: feat/absolute start dates#749
rioloc wants to merge 10 commits intoopenshift:mainfrom
rioloc:feat/absolute-start-dates

Conversation

@rioloc
Copy link
Member

@rioloc rioloc commented Jan 30, 2026

Problem

Start dates in tooltips are relatives to the numer of selected days as time span.

For example, if today is 30 Jan 2026 and an incident started on 22 Jan 2026, the Start date will be displayed as:

  • 29 Jan 2026 if "Last 1 Day" is selected
  • 27 Jan 2026 if "Last 3 Days" is selected
  • 23 Jan 2026 if "Last 7 Days" is selected
  • 22 Jan 2026 if "Last 15 Days" is selected. (The only correct one)

Fix

The absolute start date of an incident/alert is always displayed, and it is not related to the number of selected days.

Solution

Absolute timestamps for cluster_health_components_map{}, for incidents, and ALERTS{} for alerts are retrieved by performing an instant query call to Prometheus in order to get the min_over_time(timestamp(cluster_health_components_map{})), which return the timestamp of the first datapoint for that metric. (Same for ALERTS).
The result is saved into redux store and then used to match related incident/alert in order to update the Start date displayed in the tooltip.

Before

main.webm

After

feat.absolute-start-dates.webm

Summary by CodeRabbit

  • New Features

    • Alerts and incidents are grouped so identical alerts share rows and severity segments are split for clearer status tracking.
    • Charts and tables now use a 15-day window for richer historical context.
  • Improvements

    • More accurate start/end timestamps and 5-minute-aligned rounding for charts and tables.
    • Trailing padding removed from intermediate severity segments to prevent visual overlap.

@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Jan 30, 2026
@openshift-ci-robot
Copy link

openshift-ci-robot commented Jan 30, 2026

@rioloc: This pull request references OU-1040 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the bug to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Problem

Start dates in tooltips are relatives to the numer of selected days as time span.

For example, if today is 30 Jan 2026 and an incident started on 22 Jan 2026, the Start date will be displayed as:

  • 29 Jan 2026 if "Last 1 Day" is selected
  • 27 Jan 2026 if "Last 3 Days" is selected
  • 23 Jan 2026 if "Last 7 Days" is selected
  • 22 Jan 2026 if "Last 15 Days" is selected. (The only correct one)

Fix

The absolute start date of an incident/alert is always displayed, and it is not related to the number of selected days.

Solution

Absolute timestamps for cluster_health_components_map{}, for incidents, and ALERTS{} for alerts are retrieved by performoring an instant query call to Prometheus in order to get the min_over_time(timestamp(cluster_health_components_map{})), which return the timestamp of the first datapoint for that metric. (Same for ALERTS).
The result is saved into redux store and then used to match related incident/alert in order to then update the Start date displayed in the tooltip.

Before

main.webm

After

feat.absolute-start-dates.webm

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci openshift-ci bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jan 30, 2026
@openshift-ci
Copy link
Contributor

openshift-ci bot commented Jan 30, 2026

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@openshift-ci-robot
Copy link

openshift-ci-robot commented Jan 30, 2026

@rioloc: This pull request references OU-1040 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the bug to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Problem

Start dates in tooltips are relatives to the numer of selected days as time span.

For example, if today is 30 Jan 2026 and an incident started on 22 Jan 2026, the Start date will be displayed as:

  • 29 Jan 2026 if "Last 1 Day" is selected
  • 27 Jan 2026 if "Last 3 Days" is selected
  • 23 Jan 2026 if "Last 7 Days" is selected
  • 22 Jan 2026 if "Last 15 Days" is selected. (The only correct one)

Fix

The absolute start date of an incident/alert is always displayed, and it is not related to the number of selected days.

Solution

Absolute timestamps for cluster_health_components_map{}, for incidents, and ALERTS{} for alerts are retrieved by performing an instant query call to Prometheus in order to get the min_over_time(timestamp(cluster_health_components_map{})), which return the timestamp of the first datapoint for that metric. (Same for ALERTS).
The result is saved into redux store and then used to match related incident/alert in order to then update the Start date displayed in the tooltip.

Before

main.webm

After

feat.absolute-start-dates.webm

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci-robot
Copy link

openshift-ci-robot commented Jan 30, 2026

@rioloc: This pull request references OU-1040 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the bug to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Problem

Start dates in tooltips are relatives to the numer of selected days as time span.

For example, if today is 30 Jan 2026 and an incident started on 22 Jan 2026, the Start date will be displayed as:

  • 29 Jan 2026 if "Last 1 Day" is selected
  • 27 Jan 2026 if "Last 3 Days" is selected
  • 23 Jan 2026 if "Last 7 Days" is selected
  • 22 Jan 2026 if "Last 15 Days" is selected. (The only correct one)

Fix

The absolute start date of an incident/alert is always displayed, and it is not related to the number of selected days.

Solution

Absolute timestamps for cluster_health_components_map{}, for incidents, and ALERTS{} for alerts are retrieved by performing an instant query call to Prometheus in order to get the min_over_time(timestamp(cluster_health_components_map{})), which return the timestamp of the first datapoint for that metric. (Same for ALERTS).
The result is saved into redux store and then used to match related incident/alert in order to update the Start date displayed in the tooltip.

Before

main.webm

After

feat.absolute-start-dates.webm

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@DavidRajnoha
Copy link
Contributor

/test e2e-incidents

@rioloc rioloc marked this pull request as ready for review February 2, 2026 09:22
@openshift-ci openshift-ci bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Feb 2, 2026
@DavidRajnoha
Copy link
Contributor

/test e2e-incidents

@DavidRajnoha
Copy link
Contributor

/label qe-approved

@openshift-ci openshift-ci bot added the qe-approved Signifies that QE has signed off on this PR label Feb 5, 2026
@openshift-ci-robot
Copy link

openshift-ci-robot commented Feb 5, 2026

@rioloc: This pull request references OU-1040 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the bug to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Problem

Start dates in tooltips are relatives to the numer of selected days as time span.

For example, if today is 30 Jan 2026 and an incident started on 22 Jan 2026, the Start date will be displayed as:

  • 29 Jan 2026 if "Last 1 Day" is selected
  • 27 Jan 2026 if "Last 3 Days" is selected
  • 23 Jan 2026 if "Last 7 Days" is selected
  • 22 Jan 2026 if "Last 15 Days" is selected. (The only correct one)

Fix

The absolute start date of an incident/alert is always displayed, and it is not related to the number of selected days.

Solution

Absolute timestamps for cluster_health_components_map{}, for incidents, and ALERTS{} for alerts are retrieved by performing an instant query call to Prometheus in order to get the min_over_time(timestamp(cluster_health_components_map{})), which return the timestamp of the first datapoint for that metric. (Same for ALERTS).
The result is saved into redux store and then used to match related incident/alert in order to update the Start date displayed in the tooltip.

Before

main.webm

After

feat.absolute-start-dates.webm

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

- add incidentsTimestamp in redux store
- add two extra calls to retrieve min_over_time and last_over_time for
  incidents
- enrich incident with absolute datapoints timestamps
- update IncidentTooltip labels with absolute Start and End times
…entsTable on page refresh

- Fix initial state type mismatch for incidentsTimestamps and alertsTimestamps
  (was [] instead of { minOverTime: [], lastOverTime: [] })
- Add defensive check in matchTimestampMetricForIncident for undefined timestamps
- Refactor incidents useEffect to fetch timestamps and incidents in parallel,
  then use fetched values directly instead of stale closure values
- Refactor alerts useEffect with same pattern and add guards for empty
  incidentForAlertProcessing and timeRanges
- Add timeRanges and rules to alerts useEffect dependency array
- Remove unused alertsTimestamps selector

Assisted-By: Claude Opus 4.5
@rioloc rioloc force-pushed the feat/absolute-start-dates branch from 14440d2 to b8f4ed7 Compare February 6, 2026 09:14
@rioloc
Copy link
Member Author

rioloc commented Feb 6, 2026

/test e2e-incidents

@rioloc
Copy link
Member Author

rioloc commented Feb 6, 2026

/retest

2 similar comments
@etmurasaki
Copy link
Contributor

/retest

@rioloc
Copy link
Member Author

rioloc commented Feb 6, 2026

/retest

@etmurasaki
Copy link
Contributor

/retest

@openshift-ci openshift-ci bot added the lgtm Indicates that a PR is ready to be merged. label Feb 9, 2026
@openshift-ci
Copy link
Contributor

openshift-ci bot commented Feb 9, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: PeterYurkovich, rioloc

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Feb 9, 2026
@rioloc
Copy link
Member Author

rioloc commented Feb 10, 2026

/hold

@openshift-ci openshift-ci bot added do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. and removed lgtm Indicates that a PR is ready to be merged. labels Feb 10, 2026
@openshift-ci
Copy link
Contributor

openshift-ci bot commented Feb 10, 2026

New changes are detected. LGTM label has been removed.

@etmurasaki
Copy link
Contributor

/retest

@rioloc rioloc force-pushed the feat/absolute-start-dates branch from 3f05003 to 8b40410 Compare February 11, 2026 00:40
@rioloc rioloc force-pushed the feat/absolute-start-dates branch from 8b40410 to a5864d2 Compare February 11, 2026 00:48
@etmurasaki
Copy link
Contributor

/test okd-scos-images

@etmurasaki
Copy link
Contributor

linter error, please take a look

@openshift-merge-robot openshift-merge-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Feb 16, 2026
@openshift-merge-robot
Copy link
Contributor

PR needs rebase.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@openshift-ci-robot
Copy link

openshift-ci-robot commented Feb 16, 2026

@rioloc: This pull request references OU-1040 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the bug to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Problem

Start dates in tooltips are relatives to the numer of selected days as time span.

For example, if today is 30 Jan 2026 and an incident started on 22 Jan 2026, the Start date will be displayed as:

  • 29 Jan 2026 if "Last 1 Day" is selected
  • 27 Jan 2026 if "Last 3 Days" is selected
  • 23 Jan 2026 if "Last 7 Days" is selected
  • 22 Jan 2026 if "Last 15 Days" is selected. (The only correct one)

Fix

The absolute start date of an incident/alert is always displayed, and it is not related to the number of selected days.

Solution

Absolute timestamps for cluster_health_components_map{}, for incidents, and ALERTS{} for alerts are retrieved by performing an instant query call to Prometheus in order to get the min_over_time(timestamp(cluster_health_components_map{})), which return the timestamp of the first datapoint for that metric. (Same for ALERTS).
The result is saved into redux store and then used to match related incident/alert in order to update the Start date displayed in the tooltip.

Before

main.webm

After

feat.absolute-start-dates.webm

Summary by CodeRabbit

  • Bug Fixes
  • Enhanced timestamp accuracy for alert and incident start and end times by using more precise underlying data sources instead of calculated values.
  • New Features
  • Alerts are now grouped and aggregated by their name, namespace, and severity level, consolidating identical alerts into shared rows on the incidents chart.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@coderabbitai
Copy link

coderabbitai bot commented Feb 16, 2026

Warning

Rate limit exceeded

@rioloc has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 14 minutes and 26 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

The PR adds time-windowed (15-day) processing, firstTimestamp propagation, gap-aware alert splitting, severity-based incident splitting, chart-bar generation over grouped items with startDate, new/updated utilities (DAY_MS, rounding/matching, padding removal), and multiple test updates to cover the behavior.

Changes

Cohort / File(s) Summary
Page orchestration
web/src/components/Incidents/IncidentsPage.tsx
Adds DAY_MS import; computes fixed 15-day fetch window; guards empty selections; fetches alerts/incidents using daysSpan; dispatches setAlertsData & setAlertsTableData and setFilteredIncidentsData; tighter error handling and expanded effect deps.
Alert processing
web/src/components/Incidents/processAlerts.ts, web/src/components/Incidents/processAlerts.spec.ts
convertToAlerts signature gains daysSpanMs; deduplicateAlerts now splits alert series on gaps (>5min); firstTimestamp propagated and used for window-clamping and padding; tests updated to pass daysSpanMs and validate gap-splitting, firstTimestamp, and padding.
Incident processing
web/src/components/Incidents/processIncidents.ts, web/src/components/Incidents/processIncidents.spec.ts
convertToIncidents signature gains daysSpanMs; incidents split by severity changes (splitBySeverityChange); firstTimestamp tracked from full series; window filtering and padding applied; tests updated to use DAY_MS and new padding arg.
Utilities & chart helpers
web/src/components/Incidents/utils.ts, web/src/components/Incidents/utils.spec.ts
Exports DAY_MS; adds rounding and matching helpers (roundTimestampToFiveMinutes, roundDateToInterval, matchTimestampMetricForIncident, getCurrentTime, insertPaddingPointsForChart, removeTrailingPaddingFromSeveritySegments); refactors createIncidentsChartBars and createAlertsChartBars to accept arrays and include startDate; extensive unit tests added.
Chart components
web/src/components/Incidents/AlertsChart/AlertsChart.tsx, web/src/components/Incidents/IncidentsChart/IncidentsChart.tsx
AlertsChart groups alerts by identity (alertname, namespace, severity) so identical alerts share a row; tooltips use datum.startDate. IncidentsChart groups by group_id, removes trailing padding from non-last segments, reassigns consecutive x positions to remove gaps, and uses datum.startDate in tooltips.
Tables / UI rows
web/src/components/Incidents/IncidentsDetailsRowTable.tsx, web/src/components/Incidents/IncidentsTable.tsx
Start/end sorting and rendering now prefer firstTimestamp when >0 with fallbacks to previous fields (alertsStartFiring/alertsEndFiring); timestamps rendered in ms as before.
Model changes
web/src/components/Incidents/model.ts
Adds firstTimestamp: number to Alert/Incident/IncidentsDetailsAlert types and a severity field on Incident; expands AlertsIntervalsArray tuple to optionally include a numeric element.
Minor / no-op
web/src/components/Incidents/api.ts
No functional changes — only end-of-file newline touched in this diff.

Sequence Diagram(s)

sequenceDiagram
    participant Page as IncidentsPage
    participant Fetch as Fetch(prometheus)
    participant ProcA as processAlerts
    participant ProcI as processIncidents
    participant Chart as Chart Components
    participant Table as Table Components

    Page->>Page: compute daysSpanMs = 15 * DAY_MS
    Page->>Fetch: request alerts (15-day window)
    Page->>Fetch: request incidents (15-day window)
    Fetch-->>Page: alertsResults, incidentsResults

    Page->>ProcA: convertToAlerts(alertsResults, now, daysSpanMs)
    Note over ProcA: group by identity<br/>dedupe + split on gaps (>5min)<br/>compute firstTimestamp, clamp to window
    ProcA-->>Page: alerts[] (with firstTimestamp)

    Page->>ProcI: convertToIncidents(incidentsResults, now, daysSpanMs)
    Note over ProcI: group by group_id<br/>split by severity runs<br/>compute firstTimestamp, clamp/pad to window
    ProcI-->>Page: incidents[] (with firstTimestamp)

    Page->>Page: dispatch setAlertsData & setAlertsTableData<br/>dispatch setFilteredIncidentsData

    Page->>Chart: pass grouped alerts/incidents
    Chart->>Chart: group rows, remove trailing padding, create bars with startDate
    Chart-->>Page: rendered charts

    Page->>Table: pass incidents with firstTimestamp
    Table->>Table: sort by firstTimestamp, render composite timestamps
    Table-->>Page: rendered tables
Loading

Estimated Code Review Effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

🐰 I hopped through fifteen days of code and time,
Splitting gaps and stitching severity lines,
I nudged firstTimestamp into every row,
Trimmed padding tails so charts could flow,
Now alerts and incidents align in rhyme. 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'OU-1040: feat/absolute start dates' clearly describes the main change: implementing absolute (rather than relative) start dates for incidents and alerts, independent of the selected time range.
Docstring Coverage ✅ Passed Docstring coverage is 90.91% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
web/src/components/Incidents/IncidentsTable.tsx (1)

94-99: ⚠️ Potential issue | 🟠 Major

Missing fallback when firstTimestamp is 0.

When no timestamp match is found, firstTimestamp defaults to 0, causing getMinStartDate to return 0 — which renders as Jan 1, 1970 in the <Timestamp> component (Line 183) and breaks sort order. IncidentsDetailsRowTable.tsx (Lines 28-29) guards against this with firstTimestamp > 0 ? firstTimestamp : alertsStartFiring, but this file lacks the same fallback.

Proposed fix: add fallback to alertsStartFiring
     return Math.min(
-      ...alert.alertsExpandedRowData.map((alertData) => alertData.firstTimestamp),
+      ...alert.alertsExpandedRowData.map((alertData) =>
+        alertData.firstTimestamp > 0 ? alertData.firstTimestamp : alertData.alertsStartFiring,
+      ),
     );
web/src/components/Incidents/IncidentsPage.tsx (1)

400-403: ⚠️ Potential issue | 🟡 Minor

Inconsistent error logging: console.log here vs console.error in the alerts fetch (line 313).

The alerts-fetching .catch on line 313 uses console.error but the incidents-fetching .catch here still uses console.log.

Proposed fix
      .catch((err) => {
        // eslint-disable-next-line no-console
-        console.log(err);
+        console.error(err);
      });
web/src/components/Incidents/utils.ts (1)

406-450: ⚠️ Potential issue | 🟠 Major

Update AlertsChartBar type definition to include startDate field, or remove it from the function if unused.

The createAlertsChartBars function (line 430) adds startDate: new Date(intervalFirstTimestamp * 1000) to each returned object, but the AlertsChartBar type definition in model.ts (lines 137-150) does not include this field. The function has an explicit return type annotation of AlertsChartBar[], creating a type mismatch. Additionally, startDate is not accessed anywhere in the codebase, suggesting it may be dead code. Either add startDate: Date to the AlertsChartBar type or remove it from the function.

🤖 Fix all issues with AI agents
In `@web/src/components/Incidents/api.ts`:
- Around line 180-187: The code should call consoleFetchJSON(url) directly
(remove the redundant Promise.resolve) and defensively access the result to
avoid a TypeError: capture the response from consoleFetchJSON and use
response.data?.result || [] when constructing the returned PrometheusResponse
(keep the same shape with resultType: 'matrix'), ensuring you still return the
object as PrometheusResponse; reference the variables/functions response,
consoleFetchJSON and the PrometheusResponse return shape and mirror the optional
chaining used in fetchDataForIncidentsAndAlerts.

In `@web/src/components/Incidents/IncidentsChart/IncidentsChart.tsx`:
- Around line 86-99: enrichedIncidentsData currently sets firstTimestamp to 0
when matchTimestampMetricForIncident returns undefined, causing "Jan 1, 1970" in
tooltips; change the fallback in the useMemo mapping so firstTimestamp uses the
incident's earliest value timestamp (e.g., the first element in incident.values)
when matchedMinTimestamp is undefined, parsing that timestamp with parseInt
(instead of '0'), and keep the rest of the mapping unchanged; this touches
enrichedIncidentsData, matchTimestampMetricForIncident usage, and will prevent
createIncidentsChartBars from receiving epoch-zero values for firstTimestamp.

In `@web/src/components/Incidents/processAlerts.spec.ts`:
- Around line 570-582: The test fixtures for AlertsTimestamps are using key
"value" (singular) but splitAbsoluteTimestampsByGap expects entries shaped like
production (entries have "values" as an array of [timestamp,string] pairs);
update the test objects in alertsTimestamps.minOverTime (and the three other
timestamp-matching tests) to use values: [[matchedMinTimestamp,
matchedMinTimestamp.toString()]] (i.e., change "value: [...]" to "values:
[[...]]") so they match the shape consumed by splitAbsoluteTimestampsByGap and
the AlertsTimestamps type.

In `@web/src/components/Incidents/processAlerts.ts`:
- Line 344: The code assumes incidents[0] exists when computing
incidentFirstTimestamp in processAlerts (after calling mergeIncidentsByKey),
which can throw if the merged incidents array is empty; update the public
function in processAlerts.ts to defensively handle an empty array by checking
incidents.length (or the result of mergeIncidentsByKey) before accessing
incidents[0].firstTimestamp and either return early/skip processing, or set
incidentFirstTimestamp to a safe default (e.g., undefined) and propagate that
state so callers won’t dereference a missing element; reference the variable
incidentFirstTimestamp and the mergeIncidentsByKey call to find where to add the
guard.

In `@web/src/components/Incidents/processIncidents.ts`:
- Around line 194-225: processIncidentsForAlerts currently falls back to '0'
when matchTimestampMetricForIncident returns no match, producing epoch
timestamps; change the fallback for firstTimestamp to use the incident's
earliest value timestamp (from incident.values) before finally defaulting to
'0'. Specifically, when computing firstTimestamp in processIncidentsForAlerts,
pick matchedMinTimestamp?.value?.[1] if present, otherwise use the timestamp
from incident.values (the earliest entry), and then parseInt that result to set
firstTimestamp; update any parseInt usage accordingly to avoid defaulting
straight to epoch.

In `@web/src/components/Incidents/utils.spec.ts`:
- Around line 156-179: The test setup and assertion for
matchTimestampMetricForIncident are placed directly in the describe block,
causing them to run during collection and orphaning subsequent it blocks; wrap
the variable declarations (incident, timestamps, result) and the
expect(result).toBe(...) assertion inside a new it('returns matching timestamp
metric', () => { ... }) block, and move the describe closing token (the final
"});") to after the existing it blocks so all it tests (including those on lines
181–270) are nested inside describe('matchTimestampMetricForIncident'); keep
references to matchTimestampMetricForIncident, incident, timestamps, and result
when relocating code.

In `@web/src/components/Incidents/utils.ts`:
- Around line 325-326: The code constructs startDate from
incident.firstTimestamp using new Date(incident.firstTimestamp * 1000) which
yields 1970-01-01 for a 0 timestamp; change the construction so that when
incident.firstTimestamp is missing/0 you set startDate to null (or undefined)
instead of a Date(0), update the startDate type to Date | null in the relevant
interface/shape (the startDate property in the object/type), and adjust
consumers (e.g., IncidentsChart.tsx and any rendering logic) to handle null
startDate appropriately; specifically locate the new Date(...) call and replace
it with a guarded expression that only creates a Date when
incident.firstTimestamp > 0 and otherwise assigns null, and update the type
annotations for startDate to match.
🧹 Nitpick comments (9)
web/src/components/Incidents/model.ts (1)

26-34: Consider replacing Array<any> with a proper type for Prometheus instant query results.

Both IncidentsTimestamps and AlertsTimestamps use Array<any> for minOverTime and lastOverTime. The data shape from Prometheus instant queries is well-known (an object with metric: Record<string, string> and value: [number, string]). Typing these properly would catch misuse at compile time and improve discoverability.

Additionally, the two types are structurally identical — a single shared type (or type alias) would reduce duplication.

Proposed typing improvement
+export type TimestampResult = {
+  metric: Record<string, string>;
+  value: [number, string];
+};
+
-export type IncidentsTimestamps = {
-  minOverTime: Array<any>;
-  lastOverTime: Array<any>;
-};
-
-export type AlertsTimestamps = {
-  minOverTime: Array<any>;
-  lastOverTime: Array<any>;
-};
+export type TimestampData = {
+  minOverTime: Array<TimestampResult>;
+  lastOverTime: Array<TimestampResult>;
+};
+
+export type IncidentsTimestamps = TimestampData;
+export type AlertsTimestamps = TimestampData;
web/src/components/Incidents/api.ts (1)

157-160: fetch parameter is accepted but unused.

The fetch parameter is never referenced in the function body — consoleFetchJSON is called directly on Line 180. This matches the pattern in fetchDataForIncidentsAndAlerts (which also accepts but ignores fetch), but it's misleading. Consider removing it or actually using it for consistency and testability.

web/src/components/Incidents/processIncidents.spec.ts (1)

1037-1065: Redundant emptyTimestamps re-declarations in edge case tests.

The emptyTimestamps fixture at Lines 1039-1042 and 1055-1058 duplicates the one already declared in the parent describe scope (Lines 672-675). The locally scoped copies can be removed since they're identical and the parent-scope variable is accessible.

Proposed cleanup
   describe('edge cases', () => {
     it('should handle empty array', () => {
-      const emptyTimestamps: IncidentsTimestamps = {
-        minOverTime: [],
-        lastOverTime: [],
-      };
       const result = processIncidentsForAlerts([], emptyTimestamps);
       expect(result).toEqual([]);
     });

     it('should handle single incident', () => {
       const incidents: PrometheusResult[] = [
         {
           metric: { group_id: 'incident1', silenced: 'true' },
           values: [[1000, '2']],
         },
       ];

-      const emptyTimestamps: IncidentsTimestamps = {
-        minOverTime: [],
-        lastOverTime: [],
-      };
-
       const result = processIncidentsForAlerts(incidents, emptyTimestamps);
web/src/store/actions.ts (1)

193-197: Consider adding parameter types to the new action creators.

Both setIncidentsTimestamps and setAlertsTimestamps accept untyped parameters. While this is consistent with existing action creators in this file (e.g., setIncidents, setAlertsData), adding IncidentsTimestamps / AlertsTimestamps types would improve type safety and discoverability.

web/src/components/Incidents/processAlerts.ts (2)

76-107: Gap-splitting logic is duplicated between deduplicateAlerts and splitAbsoluteTimestampsByGap.

Both iterate sorted values, detect gaps > PROMETHEUS_QUERY_INTERVAL_SECONDS, and split into sub-intervals. Consider extracting a shared helper like splitValuesByGap(sortedValues, threshold) to reduce duplication.

Also applies to: 116-141


393-408: Variable firstTimestamp on line 395 shadows the outer scope and serves a different meaning.

Line 395 assigns firstTimestamp from paddedValues (chart start position), while line 402 uses alert.firstTimestamp (absolute start). Both are named similarly but mean different things. Renaming the local to chartFirstTimestamp or paddedFirstTimestamp would improve readability.

web/src/components/Incidents/IncidentsPage.tsx (2)

265-274: Alert timestamp values are reshaped to single-element arrays — verify downstream consumers expect this.

The mapping on lines 271-273 transforms each alert value from [queryTimestamp, alertTimestamp] to [roundedTimestamp] (single-element array). Downstream code in splitAbsoluteTimestampsByGap and assignAbsoluteFirstTimestamps accesses values[x][0], so this works — but the non-standard shape is fragile and undocumented. A comment clarifying why value[1] is the actual alert firing timestamp (from the timestamp() PromQL function) and why value[0] (the query evaluation time) is dropped would help future maintainers.


338-349: Hardcoded 15d lookback for min_over_time query — should ideally align with the maximum selectable days filter.

The Prometheus query min_over_time(timestamp(cluster_health_components_map)[15d:5m]) hardcodes a 15-day lookback. This happens to match the current max selectable range ("15 days"), but if the max range is ever extended, these will silently diverge. Consider extracting this as a constant.

web/src/components/Incidents/utils.ts (1)

955-966: All parameters and return typed as any — limits type safety and IDE support.

While defensively coded, this function could benefit from minimal typing. Even a simple interface for the timestamp metric shape would catch mismatches (e.g., the value vs values issue in tests).

Comment on lines 180 to 187
const response = await Promise.resolve(consoleFetchJSON(url));
return {
status: 'success',
data: {
resultType: 'matrix',
result: response.data.result,
},
} as PrometheusResponse;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add defensive access on response.data.result to avoid potential TypeError.

If the API returns an unexpected shape (e.g., an error response without a data field), response.data.result at Line 185 will throw. The existing fetchDataForIncidentsAndAlerts (Line 145) uses optional chaining (r.data?.result || []) for this reason.

Also, Promise.resolve(consoleFetchJSON(url)) is redundant — consoleFetchJSON already returns a Promise.

Proposed fix
-  const response = await Promise.resolve(consoleFetchJSON(url));
+  const response = await consoleFetchJSON(url);
   return {
     status: 'success',
     data: {
       resultType: 'matrix',
-      result: response.data.result,
+      result: response?.data?.result || [],
     },
   } as PrometheusResponse;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const response = await Promise.resolve(consoleFetchJSON(url));
return {
status: 'success',
data: {
resultType: 'matrix',
result: response.data.result,
},
} as PrometheusResponse;
const response = await consoleFetchJSON(url);
return {
status: 'success',
data: {
resultType: 'matrix',
result: response?.data?.result || [],
},
} as PrometheusResponse;
🤖 Prompt for AI Agents
In `@web/src/components/Incidents/api.ts` around lines 180 - 187, The code should
call consoleFetchJSON(url) directly (remove the redundant Promise.resolve) and
defensively access the result to avoid a TypeError: capture the response from
consoleFetchJSON and use response.data?.result || [] when constructing the
returned PrometheusResponse (keep the same shape with resultType: 'matrix'),
ensuring you still return the object as PrometheusResponse; reference the
variables/functions response, consoleFetchJSON and the PrometheusResponse return
shape and mirror the optional chaining used in fetchDataForIncidentsAndAlerts.

);

const incidentFirstTimestamp = Math.min(...timestamps);
const incidentFirstTimestamp = incidents[0].firstTimestamp;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential TypeError if incidents array is empty after merging.

incidents[0].firstTimestamp will throw if mergeIncidentsByKey returns an empty array. While the caller in IncidentsPage.tsx guards against an empty incidentForAlertProcessing, this function is public and should be defensive.

Proposed fix
-  const incidentFirstTimestamp = incidents[0].firstTimestamp;
+  const incidentFirstTimestamp = incidents[0]?.firstTimestamp ?? 0;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const incidentFirstTimestamp = incidents[0].firstTimestamp;
const incidentFirstTimestamp = incidents[0]?.firstTimestamp ?? 0;
🤖 Prompt for AI Agents
In `@web/src/components/Incidents/processAlerts.ts` at line 344, The code assumes
incidents[0] exists when computing incidentFirstTimestamp in processAlerts
(after calling mergeIncidentsByKey), which can throw if the merged incidents
array is empty; update the public function in processAlerts.ts to defensively
handle an empty array by checking incidents.length (or the result of
mergeIncidentsByKey) before accessing incidents[0].firstTimestamp and either
return early/skip processing, or set incidentFirstTimestamp to a safe default
(e.g., undefined) and propagate that state so callers won’t dereference a
missing element; reference the variable incidentFirstTimestamp and the
mergeIncidentsByKey call to find where to add the guard.

Comment on lines 194 to 225
export const processIncidentsForAlerts = (
incidents: Array<PrometheusResult>,
): Array<Partial<Incident>> => {
return incidents.map((incident, index) => {
incidentsTimestamps: IncidentsTimestamps,
) => {
const matchedIncidents = incidents.map((incident) => {
// expand matchTimestampMetricForIncident here
const matchedMinTimestamp = matchTimestampMetricForIncident(
incident.metric,
incidentsTimestamps.minOverTime,
);

return {
...incident,
firstTimestamp: parseInt(matchedMinTimestamp?.value?.[1] ?? '0'),
};
});

return matchedIncidents.map((incident, index) => {
// Read silenced value from cluster_health_components_map metric label
// If missing, default to false
const silenced = incident.metric.silenced === 'true';

// Return the processed incident
return {
const retval = {
...incident.metric,
values: incident.values,
x: incidents.length - index,
silenced,
firstTimestamp: incident.firstTimestamp,
};
return retval;
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Same epoch-fallback issue: firstTimestamp defaults to 0 when no timestamp match is found.

Line 207 uses the same parseInt(matchedMinTimestamp?.value?.[1] ?? '0') pattern already flagged in IncidentsChart.tsx. When no match exists, firstTimestamp becomes 0 (epoch), which propagates misleading dates into downstream alert processing. Consider using the incident's earliest value timestamp as a fallback here too.

🤖 Prompt for AI Agents
In `@web/src/components/Incidents/processIncidents.ts` around lines 194 - 225,
processIncidentsForAlerts currently falls back to '0' when
matchTimestampMetricForIncident returns no match, producing epoch timestamps;
change the fallback for firstTimestamp to use the incident's earliest value
timestamp (from incident.values) before finally defaulting to '0'. Specifically,
when computing firstTimestamp in processIncidentsForAlerts, pick
matchedMinTimestamp?.value?.[1] if present, otherwise use the timestamp from
incident.values (the earliest entry), and then parseInt that result to set
firstTimestamp; update any parseInt usage accordingly to avoid defaulting
straight to epoch.

Comment on lines 156 to 179
const incident = {
group_id: 'group1',
src_alertname: 'Alert1',
src_namespace: 'ns1',
component: 'comp1',
src_severity: 'warning',
};

const timestamps = [
{
metric: {
group_id: 'group1',
src_alertname: 'Alert1',
src_namespace: 'ns1',
component: 'comp1',
src_severity: 'warning',
},
value: [1704067200, '1704067200'],
},
];

const result = matchTimestampMetricForIncident(incident, timestamps);
expect(result).toBe(timestamps[0]);
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Test assertions and it blocks are incorrectly scoped — assertions at describe level, subsequent tests orphaned.

Lines 156–178 contain variable declarations and expect assertions directly inside the describe('matchTimestampMetricForIncident') body (not wrapped in an it block). These will execute during Jest's collection phase, not as a proper test. Additionally, the describe closes at line 179, so the it blocks on lines 181–270 become top-level tests, detached from their intended describe group.

This looks like a missing it(...) wrapper around lines 156–178.

Proposed fix
-  const incident = {
+  it('should match when all fields match exactly', () => {
+    const incident = {
       group_id: 'group1',
       src_alertname: 'Alert1',
       src_namespace: 'ns1',
       component: 'comp1',
       src_severity: 'warning',
     };

     const timestamps = [
       {
         metric: {
           group_id: 'group1',
           src_alertname: 'Alert1',
           src_namespace: 'ns1',
           component: 'comp1',
           src_severity: 'warning',
         },
         value: [1704067200, '1704067200'],
       },
     ];

     const result = matchTimestampMetricForIncident(incident, timestamps);
     expect(result).toBe(timestamps[0]);
-});
+  });

-it('should not match when group_id differs', () => {
+  it('should not match when group_id differs', () => {

And similarly re-indent the remaining it blocks (lines 181–270) to be inside the describe by moving the closing }); of the describe to after line 270.

🤖 Prompt for AI Agents
In `@web/src/components/Incidents/utils.spec.ts` around lines 156 - 179, The test
setup and assertion for matchTimestampMetricForIncident are placed directly in
the describe block, causing them to run during collection and orphaning
subsequent it blocks; wrap the variable declarations (incident, timestamps,
result) and the expect(result).toBe(...) assertion inside a new it('returns
matching timestamp metric', () => { ... }) block, and move the describe closing
token (the final "});") to after the existing it blocks so all it tests
(including those on lines 181–270) are nested inside
describe('matchTimestampMetricForIncident'); keep references to
matchTimestampMetricForIncident, incident, timestamps, and result when
relocating code.

Comment on lines +325 to 326
startDate: Date;
fill: string;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Same concern: startDate derived from firstTimestamp which may be epoch (0) for unmatched incidents.

new Date(incident.firstTimestamp * 1000) on line 350 will produce "Jan 1, 1970" if firstTimestamp is 0 (the default when no timestamp match is found). This is the display-side of the issue flagged in IncidentsChart.tsx.

Also applies to: 350-350

🤖 Prompt for AI Agents
In `@web/src/components/Incidents/utils.ts` around lines 325 - 326, The code
constructs startDate from incident.firstTimestamp using new
Date(incident.firstTimestamp * 1000) which yields 1970-01-01 for a 0 timestamp;
change the construction so that when incident.firstTimestamp is missing/0 you
set startDate to null (or undefined) instead of a Date(0), update the startDate
type to Date | null in the relevant interface/shape (the startDate property in
the object/type), and adjust consumers (e.g., IncidentsChart.tsx and any
rendering logic) to handle null startDate appropriately; specifically locate the
new Date(...) call and replace it with a guarded expression that only creates a
Date when incident.firstTimestamp > 0 and otherwise assigns null, and update the
type annotations for startDate to match.

@rioloc rioloc force-pushed the feat/absolute-start-dates branch 2 times, most recently from ceb6fcd to bb1ffe8 Compare February 17, 2026 10:19
@openshift-ci-robot
Copy link

openshift-ci-robot commented Feb 17, 2026

@rioloc: This pull request references OU-1040 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the bug to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Problem

Start dates in tooltips are relatives to the numer of selected days as time span.

For example, if today is 30 Jan 2026 and an incident started on 22 Jan 2026, the Start date will be displayed as:

  • 29 Jan 2026 if "Last 1 Day" is selected
  • 27 Jan 2026 if "Last 3 Days" is selected
  • 23 Jan 2026 if "Last 7 Days" is selected
  • 22 Jan 2026 if "Last 15 Days" is selected. (The only correct one)

Fix

The absolute start date of an incident/alert is always displayed, and it is not related to the number of selected days.

Solution

Absolute timestamps for cluster_health_components_map{}, for incidents, and ALERTS{} for alerts are retrieved by performing an instant query call to Prometheus in order to get the min_over_time(timestamp(cluster_health_components_map{})), which return the timestamp of the first datapoint for that metric. (Same for ALERTS).
The result is saved into redux store and then used to match related incident/alert in order to update the Start date displayed in the tooltip.

Before

main.webm

After

feat.absolute-start-dates.webm

Summary by CodeRabbit

  • New Features

  • Alerts now grouped by alert name, namespace, and severity for improved visibility—identical alerts share rows.

  • Incidents split by severity changes, providing clearer status tracking.

  • Enhanced 15-day historical window for better context and analysis.

  • Improvements

  • Improved accuracy of start and end date display in incident details and charts.

  • Better timestamp handling for alert and incident records.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@rioloc rioloc force-pushed the feat/absolute-start-dates branch from bb1ffe8 to 7ce5c6a Compare February 17, 2026 10:23
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
web/src/components/Incidents/IncidentsPage.tsx (1)

340-344: ⚠️ Potential issue | 🟡 Minor

Inconsistent error logging: console.log vs console.error.

Line 343 uses console.log(err) while the analogous catch at line 281 uses console.error(err). Use console.error consistently for error paths.

Proposed fix
-        console.log(err);
+        console.error(err);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Incidents/IncidentsPage.tsx` around lines 340 - 344, In
IncidentsPage component's promise catch block that currently reads .catch((err)
=> { console.log(err); }) replace console.log with console.error to match the
other error path and use consistent error logging (keep the
eslint-disable-next-line no-console if needed); target the .catch arrow function
where console logging is used and change it to console.error(err).
web/src/components/Incidents/IncidentsTable.tsx (1)

94-99: ⚠️ Potential issue | 🟡 Minor

getMinStartDate can surface epoch when firstTimestamp is 0.

If any alertData.firstTimestamp is still 0 (the current default in processIncidentsForAlerts), Math.min(...) will return 0 and the <Timestamp> at line 183 will display "Jan 1, 1970". The root cause is the firstTimestamp: 0 placeholder in processIncidents.ts (line 313), but this function should also guard:

Proposed defensive fix
  const getMinStartDate = (alert: GroupedAlert): number => {
    if (!alert.alertsExpandedRowData || alert.alertsExpandedRowData.length === 0) {
      return 0;
    }
-   return Math.min(...alert.alertsExpandedRowData.map((alertData) => alertData.firstTimestamp));
+   const timestamps = alert.alertsExpandedRowData
+     .map((alertData) => alertData.firstTimestamp)
+     .filter((ts) => ts > 0);
+   return timestamps.length > 0 ? Math.min(...timestamps) : 0;
  };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Incidents/IncidentsTable.tsx` around lines 94 - 99,
getMinStartDate currently returns 0 when alerts list is empty but will also
return 0 if any firstTimestamp values include the placeholder 0, causing a "Jan
1, 1970" display; update getMinStartDate to filter out non-positive/zero
firstTimestamp values before taking Math.min (e.g., collect
alert.alertsExpandedRowData.map(a => a.firstTimestamp).filter(ts => ts > 0) and
if that filtered array is empty return 0, otherwise return
Math.min(...filtered)), referencing getMinStartDate and
alert.alertsExpandedRowData/firstTimestamp to locate the change.
web/src/components/Incidents/processAlerts.ts (1)

286-298: ⚠️ Potential issue | 🟠 Major

Incident matching should include component to align with alert deduplication logic

The matchingIncident lookup omits component (lines 286-298), but alerts are explicitly deduplicated by (alertname, namespace, component, severity) (see deduplicateAlerts in the same file). Additionally, matchTimestampMetricForIncident in utils.ts includes component in its matching criteria. If two alerts share the same alertname, namespace, and severity but differ in component, this could return an incorrect match. Consider adding component to the matching condition to align with how alerts are grouped and how other timestamp matching is performed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Incidents/processAlerts.ts` around lines 286 - 298, The
incident lookup in the matchingIncident find call omits component and can return
wrong matches; update the predicate used in incidents.find (the matchingIncident
logic) to also compare incident.component === alert.metric.component so it
aligns with deduplicateAlerts and matchTimestampMetricForIncident in utils.ts;
ensure you update the predicate that currently checks src_alertname,
src_namespace, and src_severity to include src_component/component comparison as
well.
🧹 Nitpick comments (5)
web/src/components/Incidents/utils.ts (3)

312-335: Orphaned JSDoc block for createIncidentsChartBars.

Lines 312–321 contain a JSDoc comment describing createIncidentsChartBars, but it's immediately followed by a second JSDoc block (lines 322–335) for removeTrailingPaddingFromSeveritySegments. The first JSDoc is detached from its target function (which starts at line 349) and will not be associated by tooling.

Either move the first JSDoc directly above line 349 or remove the duplicate since the function is self-descriptive from context.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Incidents/utils.ts` around lines 312 - 335, The first
JSDoc for createIncidentsChartBars is orphaned and separated from the function
declaration (createIncidentsChartBars) by another JSDoc for
removeTrailingPaddingFromSeveritySegments; move the JSDoc block currently at
lines 312–321 so it immediately precedes the createIncidentsChartBars function
declaration (or delete that duplicate comment if you prefer) to ensure tooling
associates the correct documentation with createIncidentsChartBars and keep the
removeTrailingPaddingFromSeveritySegments JSDoc directly above its function.

994-1005: Prefer typed parameters over any in matchTimestampMetricForIncident.

The function uses any for both incident and timestamps parameters. Since the Incident type already exists in model.ts and the timestamps have a known Prometheus result shape, using proper types would catch label mismatches at compile time and improve discoverability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Incidents/utils.ts` around lines 994 - 1005, The function
matchTimestampMetricForIncident currently uses `any` for `incident` and
`timestamps`; replace these with concrete types by importing the existing
`Incident` type from model.ts and defining/using a typed shape for the timestamp
entries (e.g., a Prometheus result or an interface with `metric: { group_id,
src_alertname, src_namespace, component, src_severity }` and any needed value
fields). Update the signature to `incident: Incident` and `timestamps:
Array<YourTimestampType>` (or a named alias), adjust the runtime null/array
guard to use the typed parameter, and ensure the code references the typed
`timestamp.metric` fields so TypeScript will validate label names at compile
time.

349-352: Untyped value parameter in getSeverityName.

The inner function getSeverityName on line 350 accepts an untyped value parameter. Adding an explicit type (e.g., string) improves clarity and catches misuse at compile time.

Proposed fix
-  const getSeverityName = (value) => {
+  const getSeverityName = (value: string) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Incidents/utils.ts` around lines 349 - 352, The inner
helper getSeverityName inside createIncidentsChartBars currently accepts an
untyped parameter; update its signature to explicitly type the parameter (e.g.,
value: string) and optionally the return type (e.g., : string) so TypeScript can
enforce correct usage and catch misuse of non-string severity values; locate
getSeverityName in createIncidentsChartBars and add the parameter and return
type annotations.
web/src/components/Incidents/IncidentsPage.tsx (1)

240-241: Always fetching 15 days of data regardless of user's selection.

Both fetch effects unconditionally use getIncidentsTimeRanges(15 * DAY_MS, currentTime). When the user selects "Last 1 day", this still fetches 15 daily chunks. This is intentional for absolute timestamp computation, but consider whether the performance cost is acceptable — especially for clusters with dense alert histories.

Also applies to: 305-306

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Incidents/IncidentsPage.tsx` around lines 240 - 241, The
code unconditionally computes fetchTimeRanges via getIncidentsTimeRanges(15 *
DAY_MS, currentTime) (using DAY_MS, currentTime and fetchTimeRanges) which
causes both fetch effects to fetch 15 days of chunks even when the user selected
a shorter range; change the logic so you only request the full 15-day history
when you actually need it to compute firstTimestamp (e.g., compute
firstTimestamp lazily or call getIncidentsTimeRanges(15 * DAY_MS, currentTime)
only when selectedRange !== '1d' is false and firstTimestamp is unresolved),
otherwise compute fetchTimeRanges from the user-selected range (e.g.,
getIncidentsTimeRanges(selectedRangeMs, currentTime)) and avoid the expensive
15-day fetch; update both places where fetchTimeRanges is used (around where
fetch effects are defined) to use this conditional behavior.
web/src/components/Incidents/processIncidents.spec.ts (1)

779-779: as any[] casts indicate return type mismatch.

The casts at lines 779 and 843 work around a typing gap between processIncidentsForAlerts's return type and the assertions. Consider updating the function's return type or adding a proper type to avoid silent type erosion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Incidents/processIncidents.spec.ts` at line 779, The test
is using "as any[]" to silence a return-type mismatch from
processIncidentsForAlerts; update the function's declared return type (or export
a precise type alias) so callers/tests can rely on a concrete shape instead of
casting. Specifically, adjust the signature of processIncidentsForAlerts to
return the correct array type (e.g., IncidentAlert[] or a named type matching
the objects used in the spec) or export a ReturnType alias and use that in the
spec assertions to remove the "as any[]" casts and retain strong typing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/src/components/Incidents/IncidentsPage.tsx`:
- Around line 232-283: The effect calls convertToAlerts(..., daysSpan) but
daysSpan is not in the useEffect dependency array, causing a stale closure;
update the dependency array for the useEffect that contains convertToAlerts (the
effect that maps fetchTimeRanges and dispatches setAlertsData /
setAlertsTableData) to include daysSpan so the effect re-runs when the days
filter changes.
- Around line 273-277: The loading state is being set using a potentially stale
filteredData; update the logic so the effect that calls
dispatch(setAlertsAreLoading(...)) bases its decision on fresh data: either
include filteredData in that effect's dependency array (so the effect re-runs
when filteredData changes) or, better, compute loading from the newly fetched
alerts response (e.g., use the fetchedAlerts length/result instead of
filteredData) and then dispatch setAlertsAreLoading accordingly; locate the code
around filteredData, dispatch, and setAlertsAreLoading in IncidentsPage.tsx (the
effect that currently checks isEmpty(filteredData)) and change it to use the
latest fetched alerts or add filteredData to the effect dependencies to avoid
the race.

In `@web/src/components/Incidents/processAlerts.ts`:
- Around line 302-304: The calculation of alertFirstTimestamp can produce NaN
when alert.values is empty; defensively guard against undefined by checking
alert.values and alert.values[0] before subtracting
PROMETHEUS_QUERY_INTERVAL_SECONDS (e.g., treat missing values as null/0 or skip
the alert), so update the code around alertFirstTimestamp in processAlerts.ts to
compute a safe fallback, and ensure downstream uses (firstTimestamp and
alertsStartFiring) handle the fallback consistently; reference the
alertFirstTimestamp variable, PROMETHEUS_QUERY_INTERVAL_SECONDS constant, and
the deduplicateAlerts-derived alert.values to locate and fix the logic.

In `@web/src/components/Incidents/processIncidents.spec.ts`:
- Around line 806-827: The test currently asserts the placeholder firstTimestamp
of 0 returned by processIncidentsForAlerts; once you implement the TODO in
processIncidentsForAlerts so that firstTimestamp is derived like convertToAlerts
does, update this spec to compute the expected firstTimestamp from the provided
Prometheus values and assert that result[0].firstTimestamp equals that derived
value (replace the hardcoded 0 check). Refer to processIncidentsForAlerts and
convertToAlerts to locate the conversion logic and ensure the test mirrors the
same timestamp derivation.

In `@web/src/components/Incidents/processIncidents.ts`:
- Around line 184-191: The current logic only records the first severity in
severityMetrics for a result, which misses later severity changes; update the
block that handles a result (the code that checks result.values.length and
result.metric.src_alertname !== 'Watchdog') to iterate over all entries in
result.values and record each distinct severity value (e.g., value[1]) into
severityMetrics.get(groupId) mapping to result.metric; keep the existing guard
that initializes severityMetrics.set(groupId, new Map()) when missing and ensure
you deduplicate so each severity value for that groupId maps to the metric used
later by groupSeverityMetrics.get(segmentSeverity).

---

Outside diff comments:
In `@web/src/components/Incidents/IncidentsPage.tsx`:
- Around line 340-344: In IncidentsPage component's promise catch block that
currently reads .catch((err) => { console.log(err); }) replace console.log with
console.error to match the other error path and use consistent error logging
(keep the eslint-disable-next-line no-console if needed); target the .catch
arrow function where console logging is used and change it to
console.error(err).

In `@web/src/components/Incidents/IncidentsTable.tsx`:
- Around line 94-99: getMinStartDate currently returns 0 when alerts list is
empty but will also return 0 if any firstTimestamp values include the
placeholder 0, causing a "Jan 1, 1970" display; update getMinStartDate to filter
out non-positive/zero firstTimestamp values before taking Math.min (e.g.,
collect alert.alertsExpandedRowData.map(a => a.firstTimestamp).filter(ts => ts >
0) and if that filtered array is empty return 0, otherwise return
Math.min(...filtered)), referencing getMinStartDate and
alert.alertsExpandedRowData/firstTimestamp to locate the change.

In `@web/src/components/Incidents/processAlerts.ts`:
- Around line 286-298: The incident lookup in the matchingIncident find call
omits component and can return wrong matches; update the predicate used in
incidents.find (the matchingIncident logic) to also compare incident.component
=== alert.metric.component so it aligns with deduplicateAlerts and
matchTimestampMetricForIncident in utils.ts; ensure you update the predicate
that currently checks src_alertname, src_namespace, and src_severity to include
src_component/component comparison as well.

---

Duplicate comments:
In `@web/src/components/Incidents/processIncidents.ts`:
- Around line 300-316: processIncidentsForAlerts currently returns
firstTimestamp: 0 which yields epoch dates; replace that placeholder by
computing the incident's earliest value timestamp from incident.values and
assign it to firstTimestamp. Locate processIncidentsForAlerts and use the
incident.values array (each value entry contains a timestamp) to derive the
minimum/earliest timestamp (convert entries to Number if they are strings) and
set firstTimestamp to that value (keep previous behaviour if values is empty by
using undefined or null as appropriate for downstream components).

In `@web/src/components/Incidents/utils.spec.ts`:
- Around line 158-181: The test declarations and assertions for
matchTimestampMetricForIncident are placed directly inside the describe block
(variables incident, timestamps and the expect call) rather than inside an it
block, causing orphaned tests; wrap the block that declares incident, timestamps
and calls expect(matchTimestampMetricForIncident(...)) in a proper it(...) test
(e.g., it('matches timestamp metric for incident', () => { ... })) and move the
describe(...) closing brace so it comes after the final it block (so all
subsequent it tests remain inside the describe).

In `@web/src/components/Incidents/utils.ts`:
- Around line 386-388: The startDate is set to new Date(incident.firstTimestamp
* 1000) which yields the epoch when firstTimestamp is 0; change the assignment
in the object (where group_id, nodata, startDate are set) to guard against a
zero timestamp (from processIncidentsForAlerts) by using a conditional: if
incident.firstTimestamp is a positive number create new
Date(incident.firstTimestamp * 1000) otherwise set startDate to null (or
undefined) so tooltips won't show 1970-01-01; update any downstream consumers
expecting a Date to handle the nullable value accordingly.

---

Nitpick comments:
In `@web/src/components/Incidents/IncidentsPage.tsx`:
- Around line 240-241: The code unconditionally computes fetchTimeRanges via
getIncidentsTimeRanges(15 * DAY_MS, currentTime) (using DAY_MS, currentTime and
fetchTimeRanges) which causes both fetch effects to fetch 15 days of chunks even
when the user selected a shorter range; change the logic so you only request the
full 15-day history when you actually need it to compute firstTimestamp (e.g.,
compute firstTimestamp lazily or call getIncidentsTimeRanges(15 * DAY_MS,
currentTime) only when selectedRange !== '1d' is false and firstTimestamp is
unresolved), otherwise compute fetchTimeRanges from the user-selected range
(e.g., getIncidentsTimeRanges(selectedRangeMs, currentTime)) and avoid the
expensive 15-day fetch; update both places where fetchTimeRanges is used (around
where fetch effects are defined) to use this conditional behavior.

In `@web/src/components/Incidents/processIncidents.spec.ts`:
- Line 779: The test is using "as any[]" to silence a return-type mismatch from
processIncidentsForAlerts; update the function's declared return type (or export
a precise type alias) so callers/tests can rely on a concrete shape instead of
casting. Specifically, adjust the signature of processIncidentsForAlerts to
return the correct array type (e.g., IncidentAlert[] or a named type matching
the objects used in the spec) or export a ReturnType alias and use that in the
spec assertions to remove the "as any[]" casts and retain strong typing.

In `@web/src/components/Incidents/utils.ts`:
- Around line 312-335: The first JSDoc for createIncidentsChartBars is orphaned
and separated from the function declaration (createIncidentsChartBars) by
another JSDoc for removeTrailingPaddingFromSeveritySegments; move the JSDoc
block currently at lines 312–321 so it immediately precedes the
createIncidentsChartBars function declaration (or delete that duplicate comment
if you prefer) to ensure tooling associates the correct documentation with
createIncidentsChartBars and keep the removeTrailingPaddingFromSeveritySegments
JSDoc directly above its function.
- Around line 994-1005: The function matchTimestampMetricForIncident currently
uses `any` for `incident` and `timestamps`; replace these with concrete types by
importing the existing `Incident` type from model.ts and defining/using a typed
shape for the timestamp entries (e.g., a Prometheus result or an interface with
`metric: { group_id, src_alertname, src_namespace, component, src_severity }`
and any needed value fields). Update the signature to `incident: Incident` and
`timestamps: Array<YourTimestampType>` (or a named alias), adjust the runtime
null/array guard to use the typed parameter, and ensure the code references the
typed `timestamp.metric` fields so TypeScript will validate label names at
compile time.
- Around line 349-352: The inner helper getSeverityName inside
createIncidentsChartBars currently accepts an untyped parameter; update its
signature to explicitly type the parameter (e.g., value: string) and optionally
the return type (e.g., : string) so TypeScript can enforce correct usage and
catch misuse of non-string severity values; locate getSeverityName in
createIncidentsChartBars and add the parameter and return type annotations.

Comment on lines 232 to +283
useEffect(() => {
(async () => {
const currentTime = incidentsLastRefreshTime;
Promise.all(
timeRanges.map(async (range) => {
const response = await fetchDataForIncidentsAndAlerts(
safeFetch,
range,
createAlertsQuery(incidentForAlertProcessing),
);
return response.data.result;
}),
)
.then((results) => {
const prometheusResults = results.flat();
const alerts = convertToAlerts(
prometheusResults,
incidentForAlertProcessing,
currentTime,
);
// Guard: don't process if no incidents selected or timeRanges not ready
if (incidentForAlertProcessing.length === 0 || timeRanges.length === 0) {
return;
}

const currentTime = incidentsLastRefreshTime;

// Always fetch 15 days of alert data so firstTimestamp is computed from full history
const fetchTimeRanges = getIncidentsTimeRanges(15 * DAY_MS, currentTime);

Promise.all(
fetchTimeRanges.map(async (range) => {
const response = await fetchDataForIncidentsAndAlerts(
safeFetch,
range,
createAlertsQuery(incidentForAlertProcessing),
);
return response.data.result;
}),
)
.then((alertsResults) => {
const prometheusResults = alertsResults.flat();
const alerts = convertToAlerts(
prometheusResults,
incidentForAlertProcessing,
currentTime,
daysSpan,
);
dispatch(
setAlertsData({
alertsData: alerts,
}),
);
if (rules && alerts) {
dispatch(
setAlertsData({
alertsData: alerts,
setAlertsTableData({
alertsTableData: groupAlertsForTable(alerts, rules),
}),
);
if (rules && alerts) {
dispatch(
setAlertsTableData({
alertsTableData: groupAlertsForTable(alerts, rules),
}),
);
}
if (!isEmpty(filteredData)) {
dispatch(setAlertsAreLoading({ alertsAreLoading: false }));
} else {
dispatch(setAlertsAreLoading({ alertsAreLoading: true }));
}
})
.catch((err) => {
// eslint-disable-next-line no-console
console.log(err);
});
})();
}, [incidentForAlertProcessing]);
}
if (!isEmpty(filteredData)) {
dispatch(setAlertsAreLoading({ alertsAreLoading: false }));
} else {
dispatch(setAlertsAreLoading({ alertsAreLoading: true }));
}
})
.catch((err) => {
// eslint-disable-next-line no-console
console.error(err);
});
}, [incidentForAlertProcessing, timeRanges, rules]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

daysSpan used inside effect but missing from dependency array — stale closure risk.

Line 259 passes daysSpan to convertToAlerts, but daysSpan is not listed in the dependency array at line 283 ([incidentForAlertProcessing, timeRanges, rules]). When the user changes the days filter, daysSpan updates via a separate useEffect (line 222), but this effect won't re-run for that change alone. The captured daysSpan in the closure may be stale, causing alerts to be filtered against the wrong time window.

Proposed fix
-  }, [incidentForAlertProcessing, timeRanges, rules]);
+  }, [incidentForAlertProcessing, timeRanges, rules, daysSpan]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Incidents/IncidentsPage.tsx` around lines 232 - 283, The
effect calls convertToAlerts(..., daysSpan) but daysSpan is not in the useEffect
dependency array, causing a stale closure; update the dependency array for the
useEffect that contains convertToAlerts (the effect that maps fetchTimeRanges
and dispatches setAlertsData / setAlertsTableData) to include daysSpan so the
effect re-runs when the days filter changes.

Comment on lines +302 to +304
// firstTimestamp is absolute over the full 15-day data (before N-day filtering),
// with the padding offset applied to match chart rendering
const alertFirstTimestamp = alert.values[0]?.[0] - PROMETHEUS_QUERY_INTERVAL_SECONDS;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

alertFirstTimestamp may be NaN if alert.values is unexpectedly empty.

While the current flow should guarantee non-empty alert.values from deduplicateAlerts, the optional chaining alert.values[0]?.[0] returns undefined when empty, making undefined - 300 = NaN. This propagates silently into firstTimestamp and alertsStartFiring.

Consider a defensive fallback:

Proposed fix
-      const alertFirstTimestamp = alert.values[0]?.[0] - PROMETHEUS_QUERY_INTERVAL_SECONDS;
+      const alertFirstTimestamp = (alert.values[0]?.[0] ?? sortedValues[0][0]) - PROMETHEUS_QUERY_INTERVAL_SECONDS;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Incidents/processAlerts.ts` around lines 302 - 304, The
calculation of alertFirstTimestamp can produce NaN when alert.values is empty;
defensively guard against undefined by checking alert.values and alert.values[0]
before subtracting PROMETHEUS_QUERY_INTERVAL_SECONDS (e.g., treat missing values
as null/0 or skip the alert), so update the code around alertFirstTimestamp in
processAlerts.ts to compute a safe fallback, and ensure downstream uses
(firstTimestamp and alertsStartFiring) handle the fallback consistently;
reference the alertFirstTimestamp variable, PROMETHEUS_QUERY_INTERVAL_SECONDS
constant, and the deduplicateAlerts-derived alert.values to locate and fix the
logic.

Comment on lines +806 to +827
describe('firstTimestamp', () => {
it('should default firstTimestamp to 0', () => {
const incidents: PrometheusResult[] = [
{
metric: {
group_id: 'incident1',
src_alertname: 'TestAlert',
src_namespace: 'test-namespace',
component: 'test-component',
src_severity: 'critical',
},
values: [[1704067300, '2']],
},
];

const result = processIncidentsForAlerts(incidents);
expect(result).toHaveLength(1);
// processIncidentsForAlerts sets firstTimestamp to 0;
// the real firstTimestamp is computed in convertToAlerts from alert data
expect(result[0].firstTimestamp).toBe(0);
});
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Test documents the firstTimestamp: 0 placeholder.

This test confirms the unfinished work in processIncidentsForAlerts. Once the TODO at processIncidents.ts:312 is resolved (as suggested in that file's review), this test should be updated to assert the correct derived value rather than 0.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Incidents/processIncidents.spec.ts` around lines 806 -
827, The test currently asserts the placeholder firstTimestamp of 0 returned by
processIncidentsForAlerts; once you implement the TODO in
processIncidentsForAlerts so that firstTimestamp is derived like convertToAlerts
does, update this spec to compute the expected firstTimestamp from the provided
Prometheus values and assert that result[0].firstTimestamp equals that derived
value (replace the hardcoded 0 check). Refer to processIncidentsForAlerts and
convertToAlerts to locate the conversion logic and ensure the test mirrors the
same timestamp derivation.

@openshift-ci-robot
Copy link

openshift-ci-robot commented Feb 17, 2026

@rioloc: This pull request references OU-1040 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the bug to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Problem

Start dates in tooltips are relatives to the numer of selected days as time span.

For example, if today is 30 Jan 2026 and an incident started on 22 Jan 2026, the Start date will be displayed as:

  • 29 Jan 2026 if "Last 1 Day" is selected
  • 27 Jan 2026 if "Last 3 Days" is selected
  • 23 Jan 2026 if "Last 7 Days" is selected
  • 22 Jan 2026 if "Last 15 Days" is selected. (The only correct one)

Fix

The absolute start date of an incident/alert is always displayed, and it is not related to the number of selected days.

Solution

Absolute timestamps for cluster_health_components_map{}, for incidents, and ALERTS{} for alerts are retrieved by performing an instant query call to Prometheus in order to get the min_over_time(timestamp(cluster_health_components_map{})), which return the timestamp of the first datapoint for that metric. (Same for ALERTS).
The result is saved into redux store and then used to match related incident/alert in order to update the Start date displayed in the tooltip.

Before

main.webm

After

feat.absolute-start-dates.webm

Summary by CodeRabbit

  • New Features

  • Alerts and incidents are grouped so identical alerts share rows and severity segments are split for clearer status tracking.

  • Charts and tables now use a 15-day window for richer historical context.

  • Improvements

  • More accurate start/end timestamps and 5-minute-aligned rounding for charts and tables.

  • Trailing padding removed from intermediate severity segments to prevent visual overlap.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
web/src/components/Incidents/IncidentsChart/IncidentsChart.tsx (1)

112-117: ⚠️ Potential issue | 🟡 Minor

a[0].x will throw if createIncidentsChartBars returns an empty array for any group.

If a group's incidents all produce empty consolidateAndMergeIntervals results, createIncidentsChartBars returns []. Accessing a[0].x on an empty array yields undefined, and undefined - undefined produces NaN, which silently corrupts sort order. Worse, bars.map(...) on an empty inner array followed by bar[0].group_id at line 272 will throw.

Proposed defensive fix
-    const chartBars = adjustedGroups.map((group) => createIncidentsChartBars(group, dateValues));
+    const chartBars = adjustedGroups
+      .map((group) => createIncidentsChartBars(group, dateValues))
+      .filter((bars) => bars.length > 0);
     chartBars.sort((a, b) => a[0].x - b[0].x);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Incidents/IncidentsChart/IncidentsChart.tsx` around lines
112 - 117, createIncidentsChartBars can return [] for some groups which makes
chartBars.sort((a,b)=>a[0].x - b[0].x) and later accesses like bars[0].group_id
unsafe; filter out empty bars immediately after creating chartBars (e.g., const
chartBars = adjustedGroups.map(...).filter(b => b.length > 0)) so the sort and
subsequent map can safely use a[0].x and bars[0].group_id, then proceed to sort
by a[0].x and reassign consecutive x values as before; also ensure any other
downstream code that assumes non-empty bars (references to bars[0]) only runs
after this filtering.
web/src/components/Incidents/IncidentsPage.tsx (1)

341-344: ⚠️ Potential issue | 🟡 Minor

Inconsistent error logging: console.log here vs console.error at line 281.

The incidents fetch catch block uses console.log while the alerts fetch uses console.error. Both should use console.error for consistency and proper log-level semantics.

Proposed fix
       .catch((err) => {
         // eslint-disable-next-line no-console
-        console.log(err);
+        console.error(err);
       });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Incidents/IncidentsPage.tsx` around lines 341 - 344, The
catch block handling the incidents fetch currently uses console.log((err)) which
is inconsistent with the alerts fetch that uses console.error; update the
incidents fetch catch (the .catch((err) => { console.log(err); }) block) to call
console.error(err) instead (and remove the eslint-disable-next-line no-console
if no longer needed) so error logging is consistent and uses the proper log
level.
web/src/components/Incidents/model.ts (1)

95-124: ⚠️ Potential issue | 🟠 Major

Add lastTimestamp field to IncidentsDetailsAlert type definition.

IncidentsDetailsRowTable.tsx (line 63–64) accesses alertDetails.lastTimestamp, but the IncidentsDetailsAlert type does not declare this field. This causes a TypeScript type error.

Fix
export type IncidentsDetailsAlert = {
  alertname: string;
  alertsStartFiring: number;
  alertsEndFiring: number;
  alertstate: string;
  component: string;
  layer: string;
  name: string;
  namespace: string;
  resolved: boolean;
  severity: Severity;
  x: number;
  firstTimestamp: number;
+ lastTimestamp: number;
  values: Array<Timestamps>;
  silenced: boolean;
  rule: {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Incidents/model.ts` around lines 95 - 124, The
IncidentsDetailsAlert type is missing the lastTimestamp field used elsewhere;
update the IncidentsDetailsAlert type (in model.ts) to include lastTimestamp:
number (or number | undefined if it can be absent) so references like
alertDetails.lastTimestamp in IncidentsDetailsRowTable.tsx type-check correctly.
🧹 Nitpick comments (8)
web/src/components/Incidents/model.ts (1)

18-18: severity: any weakens type safety.

A Severity type is already defined at line 63 of this file. Using any here bypasses compile-time checks. If this needs to accommodate the numeric string values ('0', '1', '2') in addition to the named severity type, consider a union like Severity | string instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Incidents/model.ts` at line 18, The property declaration
severity: any weakens type safety—change the property on the incident model (the
severity field) to use the existing Severity type instead of any, and if you
must accept numeric string values include them in a union (e.g., Severity | '0'
| '1' | '2' or Severity | string); update the severity declaration where it
appears in this file to reference the Severity type defined later (Severity)
rather than any so the compiler enforces valid values.
web/src/components/Incidents/IncidentsPage.tsx (1)

240-241: Always fetching 15 days of data regardless of the selected time filter.

Both the alerts fetch (line 241) and incidents fetch (line 306) use 15 * DAY_MS for the fetch window. When the user selects "1 day", this fetches 15× more data than displayed. This is an intentional trade-off to compute absolute firstTimestamp from the full history, but it could impact performance for clusters with many incidents/alerts.

Consider documenting this trade-off with a comment or exploring whether a targeted instant query for just the firstTimestamp would be more efficient.

Also applies to: 305-306

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Incidents/IncidentsPage.tsx` around lines 240 - 241,
Currently both the alerts and incidents fetch call getIncidentsTimeRanges(15 *
DAY_MS, currentTime) regardless of the selected time filter, which unnecessarily
fetches 15× more data; change the fetch so it uses the user-selected duration
(e.g., pass the selected time filter duration instead of 15 * DAY_MS when
computing fetchTimeRanges for alerts and incidents) and compute firstTimestamp
with a targeted instant query helper (create a new helper like
fetchFirstTimestampForAlerts or fetchFirstTimestampForIncidents that runs a
single instant Prometheus/DB query for the earliest timestamp matching the
alert/incident selector) and use that value instead of pulling full 15-day
history; if you choose not to implement the instant query now, at minimum add a
clear comment next to getIncidentsTimeRanges/const fetchTimeRanges explaining
the deliberate 15-day trade-off and the potential performance impact so future
reviewers see why we fetch extra history.
web/src/components/Incidents/processAlerts.ts (1)

247-256: Math.min(...[]) returns Infinity when all incidents have empty values — safe but fragile.

If mergeIncidentsByKey returns incidents that all have empty values, timestamps will be []. Math.min(...[])Infinity and Math.max(...[])-Infinity. This works by accident (all alerts get filtered out), but a large timestamps array could also hit the call-stack limit for spread in Math.min/Math.max.

Consider using a reduce or explicit check:

Proposed hardening
   const timestamps = incidents.flatMap(
     (incident) => incident.values?.map((value) => value[0]) ?? [],
   );

+  if (timestamps.length === 0) {
+    return [];
+  }
+
   const incidentFirstTimestamp = Math.min(...timestamps);
   const incidentLastTimestamp = Math.max(...timestamps);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Incidents/processAlerts.ts` around lines 247 - 256, The
current use of Math.min(...timestamps)/Math.max(...timestamps) is fragile for
empty or very large timestamp arrays; replace the spread calls with array
reduces (e.g., reduce to compute min and max with
Number.POSITIVE_INFINITY/NEGATIVE_INFINITY as initial values) and then add an
early guard: if there were no timestamps (min === Infinity or max === -Infinity)
handle that case (e.g., return/skip this incident group or set
effectiveFirstTimestamp to nDaysStartSeconds) before computing
effectiveFirstTimestamp; update the logic around timestamps,
incidentFirstTimestamp, incidentLastTimestamp, and effectiveFirstTimestamp
accordingly.
web/src/components/Incidents/processIncidents.spec.ts (1)

806-827: Test documents the firstTimestamp: 0 placeholder — consider tracking the TODO.

The test correctly asserts the current behavior where processIncidentsForAlerts sets firstTimestamp: 0. The comment at line 823-824 explains the intent. However, the corresponding // TODO SET ME at processIncidents.ts:312 should be tracked to avoid shipping permanently.

Would you like me to open an issue to track resolving the TODO SET ME for firstTimestamp in processIncidentsForAlerts?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Incidents/processIncidents.spec.ts` around lines 806 -
827, The test highlights that processIncidentsForAlerts currently sets
firstTimestamp to a placeholder 0 (see TODO SET ME in the
processIncidentsForAlerts implementation) and the TODO must be tracked; either
compute the real firstTimestamp from the incident values (e.g., deriving the
earliest timestamp from the Prometheus values array) in
processIncidentsForAlerts or create a tracking issue and replace the inline "//
TODO SET ME" with a clear TODO that references that issue (include the issue ID
and a short note that convertToAlerts currently computes the real value), and
update the test comment accordingly so the placeholder behavior is explicitly
tied to the tracking ticket.
web/src/components/Incidents/utils.ts (3)

349-352: Untyped value parameter in getSeverityName.

Line 350's (value) lacks a type annotation — will cause lint/TS issues if strict settings are enabled. Should be (value: string).

Fix
-  const getSeverityName = (value) => {
+  const getSeverityName = (value: string) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Incidents/utils.ts` around lines 349 - 352, The helper
getSeverityName inside createIncidentsChartBars is missing a type for its
parameter; update the function signature getSeverityName to accept a typed
parameter (e.g., change (value) to (value: string)) and optionally add an
explicit return type (string) to satisfy strict TypeScript/lint rules while
keeping the same conditional logic.

988-1006: matchTimestampMetricForIncident uses any throughout — loses type safety.

The function signature (incident: any, timestamps: Array<any>): any defeats TypeScript checks entirely. At minimum, type the parameters to the shapes actually used (e.g., Pick<Incident, 'group_id' | 'src_alertname' | 'src_namespace' | 'component' | 'src_severity'> for incident, and Array<PrometheusResult> or a narrower type for timestamps).

Suggested typed signature
-export const matchTimestampMetricForIncident = (incident: any, timestamps: Array<any>): any => {
+type IncidentMatchKey = Pick<Incident, 'group_id' | 'src_alertname' | 'src_namespace' | 'component' | 'src_severity'>;
+
+export const matchTimestampMetricForIncident = (
+  incident: IncidentMatchKey,
+  timestamps: Array<PrometheusResult>,
+): PrometheusResult | undefined => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Incidents/utils.ts` around lines 988 - 1006,
matchTimestampMetricForIncident currently uses any for both parameters and
return which removes type safety; replace these anys by a precise typed
signature: use
Pick<Incident,'group_id'|'src_alertname'|'src_namespace'|'component'|'src_severity'>
for the incident param (or the existing Incident type if available) and type
timestamps as Array<PrometheusResult> or a narrower interface that exposes
metric: { group_id: string; src_alertname: string; src_namespace: string;
component?: string; src_severity: string } and make the return type the matching
PrometheusResult | undefined; update the function signature and imports so
TypeScript can validate the timestamp.metric property access inside
matchTimestampMetricForIncident.

188-193: Math.min(...a.values.map(...)) in sortByEarliestTimestamp has the same spread-overflow risk.

Same concern as flagged in processIncidents.ts — spreading a large values array into Math.min can hit the call-stack limit.

Safer alternative
-    const earliestA = Math.min(...a.values.map((value) => value[0]));
-    const earliestB = Math.min(...b.values.map((value) => value[0]));
+    const earliestA = a.values.reduce((min, v) => Math.min(min, v[0]), Infinity);
+    const earliestB = b.values.reduce((min, v) => Math.min(min, v[0]), Infinity);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Incidents/utils.ts` around lines 188 - 193, The
sortByEarliestTimestamp function currently computes earliestA/earliestB by using
Math.min with a spread over a.values.map(...) which can overflow the call stack
for large arrays; replace that spread usage with a safe min computation (e.g.,
iterate or use Array.prototype.reduce) to find the minimum timestamp for
a.values and b.values, keeping the rest of the comparator logic in items.sort
intact and updating references to earliestA and earliestB within that
comparator.
web/src/components/Incidents/processIncidents.ts (1)

214-216: Math.max(...array) on potentially large value arrays risks stack overflow.

Math.max and Math.min with spread syntax are limited by the JS engine's maximum call-stack size (typically ~65 K–125 K args). If a long-running incident accumulates many data points, this could throw a RangeError.

Suggested safer alternative
-        const resultLatestTimestamp = Math.max(...result.values.map((v) => v[0]));
-        const existingLatestTimestamp = Math.max(...existingIncident.values.map((v) => v[0]));
+        const resultLatestTimestamp = result.values.reduce((max, v) => Math.max(max, v[0]), -Infinity);
+        const existingLatestTimestamp = existingIncident.values.reduce((max, v) => Math.max(max, v[0]), -Infinity);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Incidents/processIncidents.ts` around lines 214 - 216, The
current computation of resultLatestTimestamp and existingLatestTimestamp uses
Math.max(...array) which can cause a RangeError for very large arrays; replace
those spread calls in the processIncidents logic with an O(n) safe reduction
(e.g., iterate over result.values and existingIncident.values or use
Array.prototype.reduce to compute the max timestamp using
Number.NEGATIVE_INFINITY as the initial value) and assign the results to
resultLatestTimestamp and existingLatestTimestamp, preserving the existing guard
that both arrays are non-empty; ensure you reference the variables
result.values, existingIncident.values, resultLatestTimestamp and
existingLatestTimestamp in the update.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/src/components/Incidents/utils.ts`:
- Around line 336-347: The exported function
removeTrailingPaddingFromSeveritySegments can throw when sorting because the
comparator assumes a.values[0][0] exists; update the comparator to defensively
handle incidents with missing or empty values (e.g., treat missing as +Infinity
or use 0 as a fallback) and ensure downstream code in the map also guards
against incident.values being undefined before slicing (e.g., default to [] or
check Array.isArray(incident.values) and length > 1). Modify the comparator used
in sorted = [...group].sort(...) and the mapping branch that slices
incident.values so the function never dereferences values[0][0] on
empty/undefined arrays.

---

Outside diff comments:
In `@web/src/components/Incidents/IncidentsChart/IncidentsChart.tsx`:
- Around line 112-117: createIncidentsChartBars can return [] for some groups
which makes chartBars.sort((a,b)=>a[0].x - b[0].x) and later accesses like
bars[0].group_id unsafe; filter out empty bars immediately after creating
chartBars (e.g., const chartBars = adjustedGroups.map(...).filter(b => b.length
> 0)) so the sort and subsequent map can safely use a[0].x and bars[0].group_id,
then proceed to sort by a[0].x and reassign consecutive x values as before; also
ensure any other downstream code that assumes non-empty bars (references to
bars[0]) only runs after this filtering.

In `@web/src/components/Incidents/IncidentsPage.tsx`:
- Around line 341-344: The catch block handling the incidents fetch currently
uses console.log((err)) which is inconsistent with the alerts fetch that uses
console.error; update the incidents fetch catch (the .catch((err) => {
console.log(err); }) block) to call console.error(err) instead (and remove the
eslint-disable-next-line no-console if no longer needed) so error logging is
consistent and uses the proper log level.

In `@web/src/components/Incidents/model.ts`:
- Around line 95-124: The IncidentsDetailsAlert type is missing the
lastTimestamp field used elsewhere; update the IncidentsDetailsAlert type (in
model.ts) to include lastTimestamp: number (or number | undefined if it can be
absent) so references like alertDetails.lastTimestamp in
IncidentsDetailsRowTable.tsx type-check correctly.

---

Duplicate comments:
In `@web/src/components/Incidents/IncidentsDetailsRowTable.tsx`:
- Around line 61-67: The TypeScript error is caused by the missing lastTimestamp
field on the IncidentsDetailsAlert type used by alertDetails; update the
IncidentsDetailsAlert interface/type in model.ts to include lastTimestamp
(number) so usages like the Timestamp call in IncidentsDetailsRowTable
(referencing alertDetails.lastTimestamp) compile; ensure the type aligns with
alertsEndFiring and other timestamp fields.

In `@web/src/components/Incidents/IncidentsPage.tsx`:
- Around line 232-283: The effect in useEffect that fetches alerts captures
daysSpan and incidentsLastRefreshTime but they are not listed in the dependency
array, causing a stale closure; update the dependency array for the effect (the
one using incidentForAlertProcessing, timeRanges, rules) to include daysSpan and
incidentsLastRefreshTime (and any other stable values you rely on), and remove
the eslint-disable react-hooks/exhaustive-deps so the linter can help catch
future omissions; ensure the effect body still uses incidentsLastRefreshTime as
currentTime and daysSpan when calling convertToAlerts after adding them to the
deps.

In `@web/src/components/Incidents/processAlerts.ts`:
- Around line 302-304: alertFirstTimestamp can become NaN when alert.values is
empty; change the calculation inside processAlerts (the alertFirstTimestamp
assignment) to defensively handle missing values by falling back to a safe
timestamp (e.g., use alert.values?.[0]?.[0] ?? alert.startsAt ?? Date.now() /
1000) before subtracting PROMETHEUS_QUERY_INTERVAL_SECONDS, or skip this alert
when values is empty; update the alertFirstTimestamp expression accordingly and
preserve the subtraction of PROMETHEUS_QUERY_INTERVAL_SECONDS only after using
the fallback; reference the alertFirstTimestamp variable and deduplicateAlerts
flow to ensure compatibility.

In `@web/src/components/Incidents/processIncidents.ts`:
- Around line 307-315: The returned object currently sets firstTimestamp: 0
(TODO) which yields epoch dates; update the construction inside
processIncidentsForAlerts (the block creating retval) to compute firstTimestamp
from the incident data instead of hardcoding 0 — e.g. derive the earliest
timestamp from incident.values (or fallback to an existing incident.metric.start
if available) by finding the minimum timestamp field on the values array and
using that as firstTimestamp, with a safe fallback (null or undefined) if no
timestamps exist so downstream code doesn't get Date(0).
- Around line 184-191: The code stores only one metric per severity in
severityMetrics (using severityMetrics.get(groupId).set(severityValue,
result.metric)), which causes later results with the same severityValue but
different src_* labels (e.g., different src_alertname) to overwrite earlier
ones; change the storage to preserve all contributing metrics for a severity:
when handling a result in processIncidents (variables: severityMetrics, groupId,
result, result.metric, severityValue), append result.metric to a list/array for
that severity (create the array if missing) instead of blindly calling set(), or
alternatively only set if the key is absent depending on desired semantics,
ensuring multiple metrics per severity are retained.

In `@web/src/components/Incidents/utils.spec.ts`:
- Around line 158-181: The test file has setup variables and an expect call
executing at describe-collection time for the suite named
"matchTimestampMetricForIncident", causing subsequent it blocks to be orphaned;
fix by moving the closing of the describe('matchTimestampMetricForIncident')
block so that the variable declarations, the expect(call to
matchTimestampMetricForIncident), and all the it(...) tests are nested inside
that describe, or alternatively wrap those declarations/assertion into a proper
it(...) test; adjust only the describe closure so the suite encloses everything
related to matchTimestampMetricForIncident.

In `@web/src/components/Incidents/utils.ts`:
- Around line 386-388: The startDate is being set to new
Date(incident.firstTimestamp * 1000) which yields the Unix epoch when
incident.firstTimestamp is 0; update the logic around startDate (where it's set
from incident.firstTimestamp in the mapping that produces group_id, nodata,
startDate) to only create a Date when incident.firstTimestamp is a positive
number (e.g., incident.firstTimestamp > 0) and otherwise set startDate to
null/undefined so the UI doesn't render "Jan 1, 1970"; reference the
incident.firstTimestamp and the startDate assignment in this utility to
implement the conditional.

---

Nitpick comments:
In `@web/src/components/Incidents/IncidentsPage.tsx`:
- Around line 240-241: Currently both the alerts and incidents fetch call
getIncidentsTimeRanges(15 * DAY_MS, currentTime) regardless of the selected time
filter, which unnecessarily fetches 15× more data; change the fetch so it uses
the user-selected duration (e.g., pass the selected time filter duration instead
of 15 * DAY_MS when computing fetchTimeRanges for alerts and incidents) and
compute firstTimestamp with a targeted instant query helper (create a new helper
like fetchFirstTimestampForAlerts or fetchFirstTimestampForIncidents that runs a
single instant Prometheus/DB query for the earliest timestamp matching the
alert/incident selector) and use that value instead of pulling full 15-day
history; if you choose not to implement the instant query now, at minimum add a
clear comment next to getIncidentsTimeRanges/const fetchTimeRanges explaining
the deliberate 15-day trade-off and the potential performance impact so future
reviewers see why we fetch extra history.

In `@web/src/components/Incidents/model.ts`:
- Line 18: The property declaration severity: any weakens type safety—change the
property on the incident model (the severity field) to use the existing Severity
type instead of any, and if you must accept numeric string values include them
in a union (e.g., Severity | '0' | '1' | '2' or Severity | string); update the
severity declaration where it appears in this file to reference the Severity
type defined later (Severity) rather than any so the compiler enforces valid
values.

In `@web/src/components/Incidents/processAlerts.ts`:
- Around line 247-256: The current use of
Math.min(...timestamps)/Math.max(...timestamps) is fragile for empty or very
large timestamp arrays; replace the spread calls with array reduces (e.g.,
reduce to compute min and max with Number.POSITIVE_INFINITY/NEGATIVE_INFINITY as
initial values) and then add an early guard: if there were no timestamps (min
=== Infinity or max === -Infinity) handle that case (e.g., return/skip this
incident group or set effectiveFirstTimestamp to nDaysStartSeconds) before
computing effectiveFirstTimestamp; update the logic around timestamps,
incidentFirstTimestamp, incidentLastTimestamp, and effectiveFirstTimestamp
accordingly.

In `@web/src/components/Incidents/processIncidents.spec.ts`:
- Around line 806-827: The test highlights that processIncidentsForAlerts
currently sets firstTimestamp to a placeholder 0 (see TODO SET ME in the
processIncidentsForAlerts implementation) and the TODO must be tracked; either
compute the real firstTimestamp from the incident values (e.g., deriving the
earliest timestamp from the Prometheus values array) in
processIncidentsForAlerts or create a tracking issue and replace the inline "//
TODO SET ME" with a clear TODO that references that issue (include the issue ID
and a short note that convertToAlerts currently computes the real value), and
update the test comment accordingly so the placeholder behavior is explicitly
tied to the tracking ticket.

In `@web/src/components/Incidents/processIncidents.ts`:
- Around line 214-216: The current computation of resultLatestTimestamp and
existingLatestTimestamp uses Math.max(...array) which can cause a RangeError for
very large arrays; replace those spread calls in the processIncidents logic with
an O(n) safe reduction (e.g., iterate over result.values and
existingIncident.values or use Array.prototype.reduce to compute the max
timestamp using Number.NEGATIVE_INFINITY as the initial value) and assign the
results to resultLatestTimestamp and existingLatestTimestamp, preserving the
existing guard that both arrays are non-empty; ensure you reference the
variables result.values, existingIncident.values, resultLatestTimestamp and
existingLatestTimestamp in the update.

In `@web/src/components/Incidents/utils.ts`:
- Around line 349-352: The helper getSeverityName inside
createIncidentsChartBars is missing a type for its parameter; update the
function signature getSeverityName to accept a typed parameter (e.g., change
(value) to (value: string)) and optionally add an explicit return type (string)
to satisfy strict TypeScript/lint rules while keeping the same conditional
logic.
- Around line 988-1006: matchTimestampMetricForIncident currently uses any for
both parameters and return which removes type safety; replace these anys by a
precise typed signature: use
Pick<Incident,'group_id'|'src_alertname'|'src_namespace'|'component'|'src_severity'>
for the incident param (or the existing Incident type if available) and type
timestamps as Array<PrometheusResult> or a narrower interface that exposes
metric: { group_id: string; src_alertname: string; src_namespace: string;
component?: string; src_severity: string } and make the return type the matching
PrometheusResult | undefined; update the function signature and imports so
TypeScript can validate the timestamp.metric property access inside
matchTimestampMetricForIncident.
- Around line 188-193: The sortByEarliestTimestamp function currently computes
earliestA/earliestB by using Math.min with a spread over a.values.map(...) which
can overflow the call stack for large arrays; replace that spread usage with a
safe min computation (e.g., iterate or use Array.prototype.reduce) to find the
minimum timestamp for a.values and b.values, keeping the rest of the comparator
logic in items.sort intact and updating references to earliestA and earliestB
within that comparator.

Comment on lines +336 to +347
export function removeTrailingPaddingFromSeveritySegments(group: Incident[]): Incident[] {
if (group.length <= 1) return group;

const sorted = [...group].sort((a, b) => a.values[0][0] - b.values[0][0]);

return sorted.map((incident, idx) => {
if (idx < sorted.length - 1 && incident.values.length > 1) {
return { ...incident, values: incident.values.slice(0, -1) };
}
return incident;
});
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

removeTrailingPaddingFromSeveritySegments sorts by first value but doesn't guard against empty values.

If any incident in the group has an empty values array, a.values[0][0] on line 339 will throw a TypeError. While current callers likely filter these out, this is a public export that could be called with unexpected data.

Defensive sort comparator
-  const sorted = [...group].sort((a, b) => a.values[0][0] - b.values[0][0]);
+  const sorted = [...group].sort((a, b) => (a.values[0]?.[0] ?? 0) - (b.values[0]?.[0] ?? 0));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function removeTrailingPaddingFromSeveritySegments(group: Incident[]): Incident[] {
if (group.length <= 1) return group;
const sorted = [...group].sort((a, b) => a.values[0][0] - b.values[0][0]);
return sorted.map((incident, idx) => {
if (idx < sorted.length - 1 && incident.values.length > 1) {
return { ...incident, values: incident.values.slice(0, -1) };
}
return incident;
});
}
export function removeTrailingPaddingFromSeveritySegments(group: Incident[]): Incident[] {
if (group.length <= 1) return group;
const sorted = [...group].sort((a, b) => (a.values[0]?.[0] ?? 0) - (b.values[0]?.[0] ?? 0));
return sorted.map((incident, idx) => {
if (idx < sorted.length - 1 && incident.values.length > 1) {
return { ...incident, values: incident.values.slice(0, -1) };
}
return incident;
});
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Incidents/utils.ts` around lines 336 - 347, The exported
function removeTrailingPaddingFromSeveritySegments can throw when sorting
because the comparator assumes a.values[0][0] exists; update the comparator to
defensively handle incidents with missing or empty values (e.g., treat missing
as +Infinity or use 0 as a fallback) and ensure downstream code in the map also
guards against incident.values being undefined before slicing (e.g., default to
[] or check Array.isArray(incident.values) and length > 1). Modify the
comparator used in sorted = [...group].sort(...) and the mapping branch that
slices incident.values so the function never dereferences values[0][0] on
empty/undefined arrays.

@rioloc rioloc force-pushed the feat/absolute-start-dates branch from 7ce5c6a to 12db5ce Compare February 17, 2026 10:32
@rioloc rioloc force-pushed the feat/absolute-start-dates branch from 12db5ce to 04c8326 Compare February 17, 2026 10:34
@openshift-ci
Copy link
Contributor

openshift-ci bot commented Feb 17, 2026

@rioloc: The following tests failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/e2e-incidents b8f4ed7 link false /test e2e-incidents
ci/prow/images 04c8326 link true /test images
ci/prow/lint 04c8326 link true /test lint
ci/prow/translations 04c8326 link true /test translations
ci/prow/e2e-monitoring 04c8326 link false /test e2e-monitoring
ci/prow/periodics-images 04c8326 link true /test periodics-images
ci/prow/verify-deps 04c8326 link true /test verify-deps
ci/prow/e2e-aws-ovn 04c8326 link true /test e2e-aws-ovn
ci/prow/okd-scos-images 04c8326 link true /test okd-scos-images

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. qe-approved Signifies that QE has signed off on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants