This is an automated email from the ASF dual-hosted git repository.
gongchao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hertzbeat.git
The following commit(s) were added to refs/heads/master by this push:
new 8962253dd2 [manager] bugfix: fix incorrect status page date
calculation (#3962)
8962253dd2 is described below
commit 8962253dd24a892a51d8482bc12dc2743ce7907d
Author: N.Bhanu Prasad <[email protected]>
AuthorDate: Sat Jan 17 17:38:12 2026 +0530
[manager] bugfix: fix incorrect status page date calculation (#3962)
Signed-off-by: Tomsun28 <[email protected]>
Co-authored-by: Duansg <[email protected]>
Co-authored-by: Tomsun28 <[email protected]>
Co-authored-by: Copilot <[email protected]>
---
.../service/impl/StatusPageServiceImpl.java | 176 ++++++++++++++-------
.../impl/StatusPageServiceImplTimeTest.java | 142 +++++++++++++++++
2 files changed, 260 insertions(+), 58 deletions(-)
diff --git
a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/StatusPageServiceImpl.java
b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/StatusPageServiceImpl.java
index 0fa0ef9e69..22946902d8 100644
---
a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/StatusPageServiceImpl.java
+++
b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/StatusPageServiceImpl.java
@@ -18,9 +18,8 @@
package org.apache.hertzbeat.manager.service.impl;
import java.time.Instant;
-import java.time.LocalDateTime;
import java.time.ZoneId;
-import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
@@ -131,45 +130,70 @@ public class StatusPageServiceImpl implements
StatusPageService {
componentStatus.setInfo(component);
List<StatusPageHistory> histories = new LinkedList<>();
// query today status
- LocalDateTime nowTime = LocalDateTime.now();
- LocalDateTime todayStartTime =
nowTime.withHour(0).withMinute(0).withSecond(0).withNano(0);
- ZoneOffset zoneOffset =
ZoneId.systemDefault().getRules().getOffset(Instant.now());
- long nowTimestamp = nowTime.toInstant(zoneOffset).toEpochMilli();
- long todayStartTimestamp =
todayStartTime.toInstant(zoneOffset).toEpochMilli();
+ ZoneId zoneId = ZoneId.systemDefault();
+
+ Instant now = Instant.now();
+ long nowTimestamp = now.toEpochMilli();
+
+ long todayStartTimestamp = now
+ .atZone(zoneId)
+ .toLocalDate()
+ .atStartOfDay(zoneId)
+ .toInstant()
+ .toEpochMilli();
+
List<StatusPageHistory> todayStatusPageHistoryList =
statusPageHistoryDao
.findStatusPageHistoriesByComponentIdAndTimestampBetween(component.getId(),
todayStartTimestamp, nowTimestamp);
StatusPageHistory todayStatus =
combineOneDayStatusPageHistory(todayStatusPageHistoryList, component,
nowTimestamp);
histories.add(todayStatus);
// query 30d component status history
- LocalDateTime preTime =
todayStartTime.minusDays(HISTORY_SPAN_DAYS);
- long preTimestamp = preTime.toInstant(zoneOffset).toEpochMilli();
+ long preTimestamp = now
+ .atZone(zoneId)
+ .toLocalDate()
+ .minusDays(HISTORY_SPAN_DAYS)
+ .atStartOfDay(zoneId)
+ .toInstant()
+ .toEpochMilli();
+
List<StatusPageHistory> history = statusPageHistoryDao
.findStatusPageHistoriesByComponentIdAndTimestampBetween(component.getId(),
preTimestamp, todayStartTimestamp);
LinkedList<StatusPageHistory> historyList = new
LinkedList<>(history);
- historyList.sort((o1, o2) -> (int) (o1.getTimestamp() -
o2.getTimestamp()));
- LocalDateTime endTime = todayStartTime.minusSeconds(1);
- LocalDateTime startTime =
endTime.withHour(0).withMinute(0).withSecond(0).withNano(0);
- for (int index = 0; index < HISTORY_SPAN_DAYS; index++) {
- long startTimestamp =
startTime.toInstant(zoneOffset).toEpochMilli();
- long endTimestamp =
endTime.toInstant(zoneOffset).toEpochMilli();
- List<StatusPageHistory> thisDayHistory =
historyList.stream().filter(item ->
- item.getTimestamp() >= startTimestamp &&
item.getTimestamp() <= endTimestamp)
- .collect(Collectors.toList());
+ historyList.sort((o1, o2) -> Long.compare(o1.getTimestamp(),
o2.getTimestamp()));
+ ZonedDateTime end = Instant.ofEpochMilli(todayStartTimestamp)
+ .atZone(zoneId)
+ .minusSeconds(1); // yesterday 23:59:59 local time
+
+ for (int i = 0; i < HISTORY_SPAN_DAYS; i++) {
+ long endTimestamp = end.toInstant().toEpochMilli();
+
+ long startTimestamp = end.toLocalDate()
+ .atStartOfDay(zoneId)
+ .toInstant()
+ .toEpochMilli();
+
+ List<StatusPageHistory> thisDayHistory = historyList.stream()
+ .filter(h -> h.getTimestamp() >= startTimestamp &&
h.getTimestamp() <= endTimestamp)
+ .collect(Collectors.toList());
+
if (thisDayHistory.isEmpty()) {
- StatusPageHistory statusPageHistory =
StatusPageHistory.builder().timestamp(endTimestamp)
-
.componentId(component.getId()).state(CommonConstants.STATUS_PAGE_COMPONENT_STATE_UNKNOWN).build();
- histories.add(statusPageHistory);
+ histories.add(StatusPageHistory.builder()
+ .timestamp(endTimestamp)
+ .componentId(component.getId())
+
.state(CommonConstants.STATUS_PAGE_COMPONENT_STATE_UNKNOWN)
+ .build());
} else if (thisDayHistory.size() == 1) {
histories.add(thisDayHistory.get(0));
} else {
- StatusPageHistory statusPageHistory =
combineOneDayStatusPageHistory(thisDayHistory, component, endTimestamp);
- histories.add(statusPageHistory);
+ StatusPageHistory merged =
+ combineOneDayStatusPageHistory(thisDayHistory,
component, endTimestamp);
+ histories.add(merged);
statusPageHistoryDao.deleteAll(thisDayHistory);
- statusPageHistoryDao.save(statusPageHistory);
+ statusPageHistoryDao.save(merged);
}
- startTime = startTime.minusDays(1);
- endTime = endTime.minusDays(1);
+
+ end = end.minusDays(1);
}
+
componentStatus.setHistory(histories);
componentStatusList.add(componentStatus);
}
@@ -215,50 +239,86 @@ public class StatusPageServiceImpl implements
StatusPageService {
@Override
public ComponentStatus queryComponentStatus(long id) {
- StatusPageComponent component =
statusPageComponentDao.findById(id).orElseThrow(() -> new
IllegalArgumentException("component not found"));
+ StatusPageComponent component = statusPageComponentDao.findById(id)
+ .orElseThrow(() -> new IllegalArgumentException("component not
found"));
+
ComponentStatus componentStatus = new ComponentStatus();
componentStatus.setInfo(component);
List<StatusPageHistory> histories = new LinkedList<>();
- // query today status
- LocalDateTime nowTime = LocalDateTime.now();
- LocalDateTime todayStartTime =
nowTime.withHour(0).withMinute(0).withSecond(0).withNano(0);
- ZoneOffset zoneOffset =
ZoneId.systemDefault().getRules().getOffset(Instant.now());
- long nowTimestamp = nowTime.toInstant(zoneOffset).toEpochMilli();
- long todayStartTimestamp =
todayStartTime.toInstant(zoneOffset).toEpochMilli();
- List<StatusPageHistory> todayStatusPageHistoryList =
statusPageHistoryDao
-
.findStatusPageHistoriesByComponentIdAndTimestampBetween(component.getId(),
todayStartTimestamp, nowTimestamp);
- StatusPageHistory todayStatus =
combineOneDayStatusPageHistory(todayStatusPageHistoryList, component,
nowTimestamp);
+
+ ZoneId zoneId = ZoneId.systemDefault();
+
+ Instant now = Instant.now();
+ long nowTimestamp = now.toEpochMilli();
+
+ long todayStartTimestamp = now
+ .atZone(zoneId)
+ .toLocalDate()
+ .atStartOfDay(zoneId)
+ .toInstant()
+ .toEpochMilli();
+
+ // Today
+ List<StatusPageHistory> todayStatusPageHistoryList =
+
statusPageHistoryDao.findStatusPageHistoriesByComponentIdAndTimestampBetween(
+ component.getId(), todayStartTimestamp, nowTimestamp);
+
+ StatusPageHistory todayStatus =
+ combineOneDayStatusPageHistory(todayStatusPageHistoryList,
component, nowTimestamp);
+
histories.add(todayStatus);
- // query 30d component status history
- LocalDateTime preTime = todayStartTime.minusDays(HISTORY_SPAN_DAYS);
- long preTimestamp = preTime.toInstant(zoneOffset).toEpochMilli();
- List<StatusPageHistory> history = statusPageHistoryDao
-
.findStatusPageHistoriesByComponentIdAndTimestampBetween(component.getId(),
preTimestamp, todayStartTimestamp);
+
+ // Previous HISTORY_SPAN_DAYS days (excluding today)
+ long preTimestamp = now
+ .atZone(zoneId)
+ .toLocalDate()
+ .minusDays(HISTORY_SPAN_DAYS)
+ .atStartOfDay(zoneId)
+ .toInstant()
+ .toEpochMilli();
+
+ List<StatusPageHistory> history =
+
statusPageHistoryDao.findStatusPageHistoriesByComponentIdAndTimestampBetween(
+ component.getId(), preTimestamp, todayStartTimestamp);
+
LinkedList<StatusPageHistory> historyList = new LinkedList<>(history);
- historyList.sort((o1, o2) -> (int) (o1.getTimestamp() -
o2.getTimestamp()));
- LocalDateTime endTime = todayStartTime.minusSeconds(1);
- LocalDateTime startTime =
endTime.withHour(0).withMinute(0).withSecond(0).withNano(0);
- for (int index = 0; index < HISTORY_SPAN_DAYS; index++) {
- long startTimestamp =
startTime.toInstant(zoneOffset).toEpochMilli();
- long endTimestamp = endTime.toInstant(zoneOffset).toEpochMilli();
- List<StatusPageHistory> thisDayHistory =
historyList.stream().filter(item ->
- item.getTimestamp() >= startTimestamp &&
item.getTimestamp() <= endTimestamp)
- .collect(Collectors.toList());
+ historyList.sort((o1, o2) -> Long.compare(o1.getTimestamp(),
o2.getTimestamp()));
+
+ ZonedDateTime end = Instant.ofEpochMilli(todayStartTimestamp)
+ .atZone(zoneId)
+ .minusSeconds(1); // yesterday 23:59:59 local time
+
+ for (int i = 0; i < HISTORY_SPAN_DAYS; i++) {
+ long endTimestamp = end.toInstant().toEpochMilli();
+
+ long startTimestamp = end.toLocalDate()
+ .atStartOfDay(zoneId)
+ .toInstant()
+ .toEpochMilli();
+
+ List<StatusPageHistory> thisDayHistory = historyList.stream()
+ .filter(h -> h.getTimestamp() >= startTimestamp &&
h.getTimestamp() <= endTimestamp)
+ .collect(Collectors.toList());
+
if (thisDayHistory.isEmpty()) {
- StatusPageHistory statusPageHistory =
StatusPageHistory.builder().timestamp(endTimestamp)
-
.componentId(component.getId()).state(CommonConstants.STATUS_PAGE_COMPONENT_STATE_UNKNOWN).build();
- histories.add(statusPageHistory);
+ histories.add(StatusPageHistory.builder()
+ .timestamp(endTimestamp)
+ .componentId(component.getId())
+ .state(CommonConstants.STATUS_PAGE_COMPONENT_STATE_UNKNOWN)
+ .build());
} else if (thisDayHistory.size() == 1) {
histories.add(thisDayHistory.get(0));
} else {
- StatusPageHistory statusPageHistory =
combineOneDayStatusPageHistory(thisDayHistory, component, endTimestamp);
- histories.add(statusPageHistory);
+ StatusPageHistory merged =
+ combineOneDayStatusPageHistory(thisDayHistory, component,
endTimestamp);
+ histories.add(merged);
statusPageHistoryDao.deleteAll(thisDayHistory);
- statusPageHistoryDao.save(statusPageHistory);
+ statusPageHistoryDao.save(merged);
}
- startTime = startTime.minusDays(1);
- endTime = endTime.minusDays(1);
+
+ end = end.minusDays(1);
}
+
componentStatus.setHistory(histories);
return componentStatus;
}
diff --git
a/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/impl/StatusPageServiceImplTimeTest.java
b/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/impl/StatusPageServiceImplTimeTest.java
new file mode 100644
index 0000000000..8ad87c638e
--- /dev/null
+++
b/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/impl/StatusPageServiceImplTimeTest.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.manager.service.impl;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.when;
+
+import java.lang.reflect.Field;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.List;
+
+import org.apache.hertzbeat.common.constants.CommonConstants;
+import org.apache.hertzbeat.common.entity.manager.StatusPageComponent;
+import org.apache.hertzbeat.common.entity.manager.StatusPageHistory;
+import org.apache.hertzbeat.manager.component.status.CalculateStatus;
+import org.apache.hertzbeat.manager.dao.StatusPageComponentDao;
+import org.apache.hertzbeat.manager.dao.StatusPageHistoryDao;
+import org.apache.hertzbeat.manager.dao.StatusPageIncidentComponentBindDao;
+import org.apache.hertzbeat.manager.dao.StatusPageIncidentDao;
+import org.apache.hertzbeat.manager.dao.StatusPageOrgDao;
+import org.apache.hertzbeat.manager.pojo.dto.ComponentStatus;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+class StatusPageServiceImplTimeTest {
+
+ @Mock private StatusPageHistoryDao historyDao;
+ @Mock private StatusPageComponentDao componentDao;
+ @Mock private StatusPageOrgDao orgDao;
+ @Mock private StatusPageIncidentDao incidentDao;
+ @Mock private StatusPageIncidentComponentBindDao bindDao;
+ @Mock private CalculateStatus calculateStatus;
+
+ private StatusPageServiceImpl service;
+ private StatusPageComponent component;
+
+ @BeforeEach
+ void setup() throws Exception {
+ MockitoAnnotations.openMocks(this);
+
+ // Create service using its real constructor
+ service = new StatusPageServiceImpl(bindDao);
+
+ // Inject all @Autowired fields manually
+ inject("statusPageHistoryDao", historyDao);
+ inject("statusPageComponentDao", componentDao);
+ inject("statusPageOrgDao", orgDao);
+ inject("statusPageIncidentDao", incidentDao);
+ inject("calculateStatus", calculateStatus);
+
+ component = new StatusPageComponent();
+ component.setId(1L);
+ component.setState((byte)
CommonConstants.STATUS_PAGE_COMPONENT_STATE_NORMAL);
+
+ when(componentDao.findAll()).thenReturn(List.of(component));
+ when(calculateStatus.getCalculateStatusIntervals()).thenReturn(300);
+ when(bindDao.countByComponentId(anyLong())).thenReturn(0L);
+ }
+
+ private void inject(String field, Object value) throws Exception {
+ Field f = StatusPageServiceImpl.class.getDeclaredField(field);
+ f.setAccessible(true);
+ f.set(service, value);
+ }
+
+ @Test
+ void testMidnightBoundary() {
+ Instant midnight = LocalDate.of(2026, 1, 14)
+ .atStartOfDay(ZoneId.of("UTC"))
+ .toInstant();
+
+ StatusPageHistory before = history(midnight.minusSeconds(1),
+ CommonConstants.STATUS_PAGE_COMPONENT_STATE_ABNORMAL);
+ StatusPageHistory after = history(midnight.plusSeconds(1),
+ CommonConstants.STATUS_PAGE_COMPONENT_STATE_NORMAL);
+
+
when(historyDao.findStatusPageHistoriesByComponentIdAndTimestampBetween(anyLong(),
anyLong(), anyLong()))
+ .thenReturn(List.of(before, after));
+
+ List<ComponentStatus> result = service.queryComponentsStatus();
+ assertEquals(30, result.get(0).getHistory().size());
+ }
+
+ @Test
+ void testDstDay() {
+ ZoneId zone = ZoneId.of("America/New_York");
+ Instant dstDay = ZonedDateTime.of(2026, 3, 8, 12, 0, 0, 0,
zone).toInstant();
+
+ StatusPageHistory history = history(dstDay,
+ CommonConstants.STATUS_PAGE_COMPONENT_STATE_ABNORMAL);
+
+
when(historyDao.findStatusPageHistoriesByComponentIdAndTimestampBetween(anyLong(),
anyLong(), anyLong()))
+ .thenReturn(List.of(history));
+
+ List<ComponentStatus> result = service.queryComponentsStatus();
+ assertEquals(30, result.get(0).getHistory().size());
+ }
+
+ @Test
+ void testHistoryWindowSize() {
+ Instant tenDaysAgo = Instant.now().minus(Duration.ofDays(10));
+
+ StatusPageHistory history = history(tenDaysAgo,
+ CommonConstants.STATUS_PAGE_COMPONENT_STATE_NORMAL);
+
+
when(historyDao.findStatusPageHistoriesByComponentIdAndTimestampBetween(anyLong(),
anyLong(), anyLong()))
+ .thenReturn(List.of(history));
+
+ List<ComponentStatus> result = service.queryComponentsStatus();
+ assertEquals(30, result.get(0).getHistory().size());
+ }
+
+ private StatusPageHistory history(Instant time, int state) {
+ return StatusPageHistory.builder()
+ .timestamp(time.toEpochMilli())
+ .state((byte) state)
+ .componentId(1L)
+ .build();
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]