[sitemap] Extend chart periods to cover past and future (#4172)

* [sitemap] Extend chart periods to cover past and future

Closes openhab/openhab-webui#2518

Signed-off-by: Laurent Garnier <lg.hc@free.fr>
This commit is contained in:
lolodomo 2024-04-07 10:51:24 +02:00 committed by GitHub
parent c2cbefe55c
commit 619762a297
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 222 additions and 22 deletions

View File

@ -38,8 +38,9 @@ public class RESTConstants {
* Version 4: OH3, refactored extensions to addons (#1560)
* Version 5: transparent charts (#2502)
* Version 6: extended chart period parameter format (#3863)
* Version 7: extended chart period parameter format to cover past and future
*/
public static final String API_VERSION = "6";
public static final String API_VERSION = "7";
public static final CacheControl CACHE_CONTROL = new CacheControl();
static {

View File

@ -80,7 +80,7 @@ Chart:
(('icon=' icon=Icon) |
('icon=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') |
('staticIcon=' staticIcon=Icon))? &
('service=' service=STRING)? & ('refresh=' refresh=INT)? & ('period=' period=ID) &
('service=' service=STRING)? & ('refresh=' refresh=INT)? & ('period=' period=Period) &
('legend=' legend=BOOLEAN_OBJECT)? & ('forceasitem=' forceAsItem=BOOLEAN_OBJECT)? &
('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)*) ']')? &
('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)*) ']')? &
@ -222,6 +222,9 @@ Icon returns ecore::EString:
IconName:
(ID '-')* ID;
Period:
(ID '-')? ID;
ColorArray:
((conditions+=Condition ('AND' conditions+=Condition)*) '=')? (arg=STRING);

View File

@ -70,6 +70,7 @@ import org.slf4j.LoggerFactory;
* @author Chris Jackson - Initial contribution
* @author Holger Reichert - Support for themes, DPI, legend hiding
* @author Laurent Garnier - Extend support to ISO8601 format for chart period parameter
* @author Laurent Garnier - Extend support to past and future for chart period parameter
*/
@Component(immediate = true, service = { ChartServlet.class, Servlet.class }, configurationPid = "org.openhab.chart", //
property = Constants.SERVICE_PID + "=org.openhab.chart")
@ -100,7 +101,7 @@ public class ChartServlet extends HttpServlet {
// The URI of this servlet
public static final String SERVLET_PATH = "/chart";
private static final Duration DEFAULT_PERIOD = Duration.ofDays(1);
protected static final Duration DEFAULT_PERIOD = Duration.ofDays(1);
protected static final Map<String, ChartProvider> CHART_PROVIDERS = new ConcurrentHashMap<>();
@ -216,6 +217,7 @@ public class ChartServlet extends HttpServlet {
String periodParam = req.getParameter("period");
String timeBeginParam = req.getParameter("begin");
String timeEndParam = req.getParameter("end");
// To avoid ambiguity you are not allowed to specify period, begin and end time at the same time.
if (periodParam != null && timeBeginParam != null && timeEndParam != null) {
res.sendError(HttpServletResponse.SC_BAD_REQUEST,
@ -223,8 +225,6 @@ public class ChartServlet extends HttpServlet {
return;
}
// Read out the parameter period, begin and end and save them.
TemporalAmount period = convertToTemporalAmount(periodParam, DEFAULT_PERIOD);
ZonedDateTime timeBegin = null;
ZonedDateTime timeEnd = null;
@ -248,18 +248,11 @@ public class ChartServlet extends HttpServlet {
}
}
// Set begin and end time and check legality.
if (timeBegin == null && timeEnd == null) {
timeEnd = ZonedDateTime.now(timeZoneProvider.getTimeZone());
timeBegin = timeEnd.minus(period);
logger.debug("No begin or end is specified, use now as end and now-period as begin.");
} else if (timeEnd == null) {
timeEnd = timeBegin.plus(period);
logger.debug("No end is specified, use begin + period as end.");
} else if (timeBegin == null) {
timeBegin = timeEnd.minus(period);
logger.debug("No begin is specified, use end-period as begin");
} else if (timeEnd.isBefore(timeBegin)) {
PeriodPastFuture period = getPeriodPastFuture(periodParam);
PeriodBeginEnd beginEnd = getPeriodBeginEnd(timeBegin, timeEnd, period,
ZonedDateTime.now(timeZoneProvider.getTimeZone()));
if (beginEnd.begin() != null && beginEnd.end() != null && beginEnd.end().isBefore(beginEnd.begin())) {
res.sendError(HttpServletResponse.SC_BAD_REQUEST, "The end is before the begin.");
return;
}
@ -308,9 +301,9 @@ public class ChartServlet extends HttpServlet {
logger.debug("chart building with width {} height {} dpi {}", width, height, dpi);
try {
BufferedImage chart = provider.createChart(serviceName, req.getParameter("theme"), timeBegin, timeEnd,
height, width, req.getParameter("items"), req.getParameter("groups"), dpi, yAxisDecimalPattern,
legend);
BufferedImage chart = provider.createChart(serviceName, req.getParameter("theme"), beginEnd.begin(),
beginEnd.end(), height, width, req.getParameter("items"), req.getParameter("groups"), dpi,
yAxisDecimalPattern, legend);
// Set the content type to that provided by the chart provider
res.setContentType("image/" + provider.getChartType());
try (ImageOutputStream imageOutputStream = ImageIO.createImageOutputStream(res.getOutputStream())) {
@ -351,7 +344,8 @@ public class ChartServlet extends HttpServlet {
public void destroy() {
}
public static TemporalAmount convertToTemporalAmount(@Nullable String periodParam, TemporalAmount defaultPeriod) {
protected static @Nullable TemporalAmount convertToTemporalAmount(@Nullable String periodParam,
@Nullable TemporalAmount defaultPeriod) {
TemporalAmount period = defaultPeriod;
String convertedPeriod = convertPeriodToISO8601(periodParam);
if (convertedPeriod != null) {
@ -383,4 +377,70 @@ public class ChartServlet extends HttpServlet {
}
return "P" + newPeriod;
}
protected static PeriodPastFuture getPeriodPastFuture(@Nullable String periodParam) {
String periodParamPast = null;
String periodParamFuture = null;
TemporalAmount defaultPeriodPast = DEFAULT_PERIOD;
TemporalAmount defaultPeriodFuture = null;
if (periodParam != null) {
int idx = periodParam.indexOf("-");
if (idx < 0) {
periodParamPast = periodParam;
} else {
if (idx > 0) {
periodParamPast = periodParam.substring(0, idx);
} else {
defaultPeriodPast = null;
defaultPeriodFuture = DEFAULT_PERIOD;
}
periodParamFuture = periodParam.substring(idx + 1);
}
}
TemporalAmount periodPast = convertToTemporalAmount(periodParamPast, defaultPeriodPast);
TemporalAmount periodFuture = convertToTemporalAmount(periodParamFuture, defaultPeriodFuture);
return new PeriodPastFuture(periodPast, periodFuture);
}
protected static PeriodBeginEnd getPeriodBeginEnd(@Nullable ZonedDateTime begin, @Nullable ZonedDateTime end,
PeriodPastFuture period, ZonedDateTime now) {
ZonedDateTime timeBegin = begin;
ZonedDateTime timeEnd = end;
TemporalAmount periodPast = period.past();
TemporalAmount periodFuture = period.future();
if (timeBegin == null && timeEnd == null) {
timeBegin = timeEnd = now;
if (periodPast != null) {
timeBegin = timeBegin.minus(periodPast);
}
if (periodFuture != null) {
timeEnd = timeEnd.plus(periodFuture);
}
} else if (timeBegin != null && timeEnd == null) {
timeEnd = timeBegin;
if (periodPast != null) {
timeEnd = timeEnd.plus(periodPast);
}
if (periodFuture != null) {
timeEnd = timeEnd.plus(periodFuture);
}
} else if (timeBegin == null && timeEnd != null) {
timeBegin = timeEnd;
if (periodFuture != null) {
timeBegin = timeBegin.minus(periodFuture);
}
if (periodPast != null) {
timeBegin = timeBegin.minus(periodPast);
}
}
return new PeriodBeginEnd(Objects.requireNonNull(timeBegin), Objects.requireNonNull(timeEnd));
}
record PeriodPastFuture(@Nullable TemporalAmount past, @Nullable TemporalAmount future) {
}
record PeriodBeginEnd(ZonedDateTime begin, ZonedDateTime end) {
}
}

View File

@ -12,14 +12,19 @@
*/
package org.openhab.core.ui.internal.chart;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.*;
import java.time.Duration;
import java.time.Period;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAmount;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.core.ui.internal.chart.ChartServlet.PeriodBeginEnd;
import org.openhab.core.ui.internal.chart.ChartServlet.PeriodPastFuture;
/**
* @author Laurent Garnier - Initial contribution
@ -30,26 +35,31 @@ public class ChartServletPeriodParamTest {
@Test
public void convertToTemporalAmountFromNull() {
TemporalAmount period = ChartServlet.convertToTemporalAmount(null, Duration.ZERO);
assertNotNull(period);
assertEquals(0, period.get(ChronoUnit.SECONDS));
}
@Test
public void convertToTemporalAmountFromHours() {
TemporalAmount period = ChartServlet.convertToTemporalAmount("h", Duration.ZERO);
assertNotNull(period);
assertEquals(1 * 60 * 60, period.get(ChronoUnit.SECONDS));
period = ChartServlet.convertToTemporalAmount("12h", Duration.ZERO);
assertNotNull(period);
assertEquals(12 * 60 * 60, period.get(ChronoUnit.SECONDS));
}
@Test
public void convertToTemporalAmountFromDays() {
TemporalAmount period = ChartServlet.convertToTemporalAmount("D", Duration.ZERO);
assertNotNull(period);
assertEquals(1, period.get(ChronoUnit.DAYS));
assertEquals(0, period.get(ChronoUnit.MONTHS));
assertEquals(0, period.get(ChronoUnit.YEARS));
period = ChartServlet.convertToTemporalAmount("4D", Duration.ZERO);
assertNotNull(period);
assertEquals(4, period.get(ChronoUnit.DAYS));
assertEquals(0, period.get(ChronoUnit.MONTHS));
assertEquals(0, period.get(ChronoUnit.YEARS));
@ -58,11 +68,13 @@ public class ChartServletPeriodParamTest {
@Test
public void convertToTemporalAmountFromWeeks() {
TemporalAmount period = ChartServlet.convertToTemporalAmount("W", Duration.ZERO);
assertNotNull(period);
assertEquals(7, period.get(ChronoUnit.DAYS));
assertEquals(0, period.get(ChronoUnit.MONTHS));
assertEquals(0, period.get(ChronoUnit.YEARS));
period = ChartServlet.convertToTemporalAmount("2W", Duration.ZERO);
assertNotNull(period);
assertEquals(14, period.get(ChronoUnit.DAYS));
assertEquals(0, period.get(ChronoUnit.MONTHS));
assertEquals(0, period.get(ChronoUnit.YEARS));
@ -71,11 +83,13 @@ public class ChartServletPeriodParamTest {
@Test
public void convertToTemporalAmountFromMonths() {
TemporalAmount period = ChartServlet.convertToTemporalAmount("M", Duration.ZERO);
assertNotNull(period);
assertEquals(0, period.get(ChronoUnit.DAYS));
assertEquals(1, period.get(ChronoUnit.MONTHS));
assertEquals(0, period.get(ChronoUnit.YEARS));
period = ChartServlet.convertToTemporalAmount("3M", Duration.ZERO);
assertNotNull(period);
assertEquals(0, period.get(ChronoUnit.DAYS));
assertEquals(3, period.get(ChronoUnit.MONTHS));
assertEquals(0, period.get(ChronoUnit.YEARS));
@ -84,11 +98,13 @@ public class ChartServletPeriodParamTest {
@Test
public void convertToTemporalAmountFromYears() {
TemporalAmount period = ChartServlet.convertToTemporalAmount("Y", Duration.ZERO);
assertNotNull(period);
assertEquals(0, period.get(ChronoUnit.DAYS));
assertEquals(0, period.get(ChronoUnit.MONTHS));
assertEquals(1, period.get(ChronoUnit.YEARS));
period = ChartServlet.convertToTemporalAmount("2Y", Duration.ZERO);
assertNotNull(period);
assertEquals(0, period.get(ChronoUnit.DAYS));
assertEquals(0, period.get(ChronoUnit.MONTHS));
assertEquals(2, period.get(ChronoUnit.YEARS));
@ -97,11 +113,131 @@ public class ChartServletPeriodParamTest {
@Test
public void convertToTemporalAmountFromISO8601() {
TemporalAmount period = ChartServlet.convertToTemporalAmount("P2Y3M4D", Duration.ZERO);
assertNotNull(period);
assertEquals(4, period.get(ChronoUnit.DAYS));
assertEquals(3, period.get(ChronoUnit.MONTHS));
assertEquals(2, period.get(ChronoUnit.YEARS));
period = ChartServlet.convertToTemporalAmount("P1DT12H30M15S", Duration.ZERO);
assertNotNull(period);
assertEquals(36 * 60 * 60 + 30 * 60 + 15, period.get(ChronoUnit.SECONDS));
}
@Test
public void getPeriodPastFutureByDefault() {
PeriodPastFuture period = ChartServlet.getPeriodPastFuture(null);
assertNotNull(period.past());
assertEquals(ChartServlet.DEFAULT_PERIOD, period.past());
assertNull(period.future());
period = ChartServlet.getPeriodPastFuture("-");
assertNull(period.past());
assertNotNull(period.future());
assertEquals(ChartServlet.DEFAULT_PERIOD, period.future());
}
@Test
public void getPeriodPastFutureWithOnlyPast() {
Period duration = Period.ofDays(2);
PeriodPastFuture period = ChartServlet.getPeriodPastFuture("2D");
assertNotNull(period.past());
assertEquals(duration, period.past());
assertNull(period.future());
period = ChartServlet.getPeriodPastFuture("2D-");
assertNotNull(period.past());
assertEquals(duration, period.past());
assertNull(period.future());
}
@Test
public void getPeriodPastFutureWithOnlyFuture() {
Period duration = Period.ofMonths(3);
PeriodPastFuture period = ChartServlet.getPeriodPastFuture("-3M");
assertNull(period.past());
assertNotNull(period.future());
assertEquals(duration, period.future());
}
@Test
public void getPeriodPastFutureWithPastAndFuture() {
Period duration1 = Period.ofDays(2);
Period duration2 = Period.ofMonths(3);
PeriodPastFuture period = ChartServlet.getPeriodPastFuture("2D-3M");
assertNotNull(period.past());
assertEquals(duration1, period.past());
assertNotNull(period.future());
assertEquals(duration2, period.future());
}
@Test
public void getPeriodBeginEndWithBeginAndEnd() {
ZonedDateTime now = ZonedDateTime.of(2024, 4, 9, 12, 0, 0, 0, ZoneId.systemDefault());
ZonedDateTime begin = ZonedDateTime.of(2024, 4, 9, 11, 30, 0, 0, ZoneId.systemDefault());
ZonedDateTime end = ZonedDateTime.of(2024, 4, 9, 13, 30, 0, 0, ZoneId.systemDefault());
PeriodBeginEnd beginEnd = ChartServlet.getPeriodBeginEnd(begin, end, ChartServlet.getPeriodPastFuture("2D-3M"),
now);
assertEquals(begin, beginEnd.begin());
assertEquals(end, beginEnd.end());
}
@Test
public void getPeriodBeginEndWithBeginButNotEnd() {
ZonedDateTime now = ZonedDateTime.of(2024, 4, 9, 12, 0, 0, 0, ZoneId.systemDefault());
ZonedDateTime begin = ZonedDateTime.of(2024, 4, 9, 11, 30, 0, 0, ZoneId.systemDefault());
PeriodBeginEnd beginEnd = ChartServlet.getPeriodBeginEnd(begin, null, ChartServlet.getPeriodPastFuture("2D-3M"),
now);
assertEquals(begin, beginEnd.begin());
assertEquals(ZonedDateTime.of(2024, 7, 11, 11, 30, 0, 0, ZoneId.systemDefault()), beginEnd.end());
beginEnd = ChartServlet.getPeriodBeginEnd(begin, null, ChartServlet.getPeriodPastFuture("2D"), now);
assertEquals(begin, beginEnd.begin());
assertEquals(ZonedDateTime.of(2024, 4, 11, 11, 30, 0, 0, ZoneId.systemDefault()), beginEnd.end());
beginEnd = ChartServlet.getPeriodBeginEnd(begin, null, ChartServlet.getPeriodPastFuture("-3M"), now);
assertEquals(begin, beginEnd.begin());
assertEquals(ZonedDateTime.of(2024, 7, 9, 11, 30, 0, 0, ZoneId.systemDefault()), beginEnd.end());
}
@Test
public void getPeriodBeginEndWithEndButNotBegin() {
ZonedDateTime now = ZonedDateTime.of(2024, 4, 9, 12, 0, 0, 0, ZoneId.systemDefault());
ZonedDateTime end = ZonedDateTime.of(2024, 7, 11, 11, 30, 0, 0, ZoneId.systemDefault());
PeriodBeginEnd beginEnd = ChartServlet.getPeriodBeginEnd(null, end, ChartServlet.getPeriodPastFuture("2D-3M"),
now);
assertEquals(ZonedDateTime.of(2024, 4, 9, 11, 30, 0, 0, ZoneId.systemDefault()), beginEnd.begin());
assertEquals(end, beginEnd.end());
beginEnd = ChartServlet.getPeriodBeginEnd(null, end, ChartServlet.getPeriodPastFuture("2D"), now);
assertEquals(ZonedDateTime.of(2024, 7, 9, 11, 30, 0, 0, ZoneId.systemDefault()), beginEnd.begin());
assertEquals(end, beginEnd.end());
beginEnd = ChartServlet.getPeriodBeginEnd(null, end, ChartServlet.getPeriodPastFuture("-3M"), now);
assertEquals(ZonedDateTime.of(2024, 4, 11, 11, 30, 0, 0, ZoneId.systemDefault()), beginEnd.begin());
assertEquals(end, beginEnd.end());
}
@Test
public void getPeriodBeginEndWithPeriodButNotBeginEnd() {
ZonedDateTime now = ZonedDateTime.of(2024, 4, 9, 12, 0, 0, 0, ZoneId.systemDefault());
PeriodBeginEnd beginEnd = ChartServlet.getPeriodBeginEnd(null, null, ChartServlet.getPeriodPastFuture("2D-3M"),
now);
assertEquals(ZonedDateTime.of(2024, 4, 7, 12, 0, 0, 0, ZoneId.systemDefault()), beginEnd.begin());
assertEquals(ZonedDateTime.of(2024, 7, 9, 12, 0, 0, 0, ZoneId.systemDefault()), beginEnd.end());
beginEnd = ChartServlet.getPeriodBeginEnd(null, null, ChartServlet.getPeriodPastFuture("2D"), now);
assertEquals(ZonedDateTime.of(2024, 4, 7, 12, 0, 0, 0, ZoneId.systemDefault()), beginEnd.begin());
assertEquals(ZonedDateTime.of(2024, 4, 9, 12, 0, 0, 0, ZoneId.systemDefault()), beginEnd.end());
beginEnd = ChartServlet.getPeriodBeginEnd(null, null, ChartServlet.getPeriodPastFuture("-3M"), now);
assertEquals(ZonedDateTime.of(2024, 4, 9, 12, 0, 0, 0, ZoneId.systemDefault()), beginEnd.begin());
assertEquals(ZonedDateTime.of(2024, 7, 9, 12, 0, 0, 0, ZoneId.systemDefault()), beginEnd.end());
}
}