Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Sitemap] Add optional conditional rules for icon #3820

Merged
merged 2 commits into from
Oct 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import org.openhab.core.model.sitemap.sitemap.ColorArray;
import org.openhab.core.model.sitemap.sitemap.Condition;
import org.openhab.core.model.sitemap.sitemap.Frame;
import org.openhab.core.model.sitemap.sitemap.IconRule;
import org.openhab.core.model.sitemap.sitemap.VisibilityRule;
import org.openhab.core.model.sitemap.sitemap.Widget;
import org.openhab.core.types.State;
Expand All @@ -50,6 +51,7 @@
* @author Kai Kreuzer - Initial contribution
* @author Laurent Garnier - Added support for icon color
* @author Laurent Garnier - Support added for multiple AND conditions in labelcolor/valuecolor/visibility
* @author Laurent Garnier - New widget icon parameter based on conditional rules
*/
public class PageChangeListener implements EventSubscriber {

Expand Down Expand Up @@ -122,6 +124,10 @@ private Set<Item> getAllItems(EList<Widget> widgets) {
if (widget instanceof Frame frame) {
items.addAll(getAllItems(frame.getChildren()));
}
// now scan icon rules
for (IconRule rule : widget.getIconRules()) {
addItemsFromConditions(items, rule.getConditions());
}
// now scan visibility rules
for (VisibilityRule rule : widget.getVisibility()) {
addItemsFromConditions(items, rule.getConditions());
Expand Down Expand Up @@ -194,7 +200,7 @@ private Set<SitemapEvent> constructSitemapEvents(Item item, State state, List<Wi
if (!skipWidget && w instanceof Chart chartWidget) {
skipWidget = chartWidget.getRefresh() > 0;
}
if (!skipWidget || definesVisibilityOrColor(w, item.getName())) {
if (!skipWidget || definesVisibilityOrColorOrIcon(w, item.getName())) {
SitemapWidgetEvent event = constructSitemapEventForWidget(item, state, w);
events.add(event);
}
Expand All @@ -208,6 +214,8 @@ private SitemapWidgetEvent constructSitemapEventForWidget(Item item, State state
event.pageId = pageId;
event.label = itemUIRegistry.getLabel(widget);
event.widgetId = itemUIRegistry.getWidgetId(widget);
event.icon = itemUIRegistry.getCategory(widget);
event.reloadIcon = widget.getStaticIcon() == null;
event.visibility = itemUIRegistry.getVisiblity(widget);
event.descriptionChanged = false;
// event.item contains the (potentially changed) data of the item belonging to
Expand Down Expand Up @@ -248,11 +256,12 @@ private Item getItemForWidget(Widget w) {
return null;
}

private boolean definesVisibilityOrColor(Widget w, String name) {
private boolean definesVisibilityOrColorOrIcon(Widget w, String name) {
return w.getVisibility().stream().anyMatch(r -> conditionsDependsOnItem(r.getConditions(), name))
|| w.getLabelColor().stream().anyMatch(r -> conditionsDependsOnItem(r.getConditions(), name))
|| w.getValueColor().stream().anyMatch(r -> conditionsDependsOnItem(r.getConditions(), name))
|| w.getIconColor().stream().anyMatch(r -> conditionsDependsOnItem(r.getConditions(), name));
|| w.getIconColor().stream().anyMatch(r -> conditionsDependsOnItem(r.getConditions(), name))
|| w.getIconRules().stream().anyMatch(r -> conditionsDependsOnItem(r.getConditions(), name));
}

private boolean conditionsDependsOnItem(@Nullable EList<Condition> conditions, String name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
import org.openhab.core.model.sitemap.sitemap.ColorArray;
import org.openhab.core.model.sitemap.sitemap.Condition;
import org.openhab.core.model.sitemap.sitemap.Frame;
import org.openhab.core.model.sitemap.sitemap.IconRule;
import org.openhab.core.model.sitemap.sitemap.Image;
import org.openhab.core.model.sitemap.sitemap.Input;
import org.openhab.core.model.sitemap.sitemap.LinkableWidget;
Expand Down Expand Up @@ -137,6 +138,7 @@
* @author Laurent Garnier - Added support for new sitemap element Buttongrid
* @author Laurent Garnier - Added icon field for mappings used for switch element
* @author Laurent Garnier - Support added for multiple AND conditions in labelcolor/valuecolor/visibility
* @author Laurent Garnier - New widget icon parameter based on conditional rules
*/
@Component(service = { RESTResource.class, EventSubscriber.class })
@JaxrsResource
Expand Down Expand Up @@ -529,7 +531,7 @@ private PageDTO createPageBean(String sitemapName, @Nullable String title, @Null
}
bean.widgetId = widgetId;
bean.icon = itemUIRegistry.getCategory(widget);
bean.staticIcon = widget.getStaticIcon() != null;
bean.staticIcon = widget.getStaticIcon() != null || !widget.getIconRules().isEmpty();
bean.labelcolor = convertItemValueColor(itemUIRegistry.getLabelColor(widget), itemState);
bean.valuecolor = convertItemValueColor(itemUIRegistry.getValueColor(widget), itemState);
bean.iconcolor = convertItemValueColor(itemUIRegistry.getIconColor(widget), itemState);
Expand Down Expand Up @@ -759,6 +761,8 @@ private Set<GenericItem> getAllItems(EList<Widget> widgets) {
if (widget instanceof Frame frame) {
items.addAll(getAllItems(frame.getChildren()));
}
// Consider items involved in any icon condition
items.addAll(getItemsInIconCond(widget.getIconRules()));
// Consider items involved in any visibility, labelcolor, valuecolor and iconcolor condition
items.addAll(getItemsInVisibilityCond(widget.getVisibility()));
items.addAll(getItemsInColorCond(widget.getLabelColor()));
Expand All @@ -784,6 +788,14 @@ private Set<GenericItem> getItemsInColorCond(EList<ColorArray> colorList) {
return items;
}

private Set<GenericItem> getItemsInIconCond(EList<IconRule> ruleList) {
Set<GenericItem> items = new HashSet<>();
for (IconRule rule : ruleList) {
getItemsInConditions(rule.getConditions(), items);
}
return items;
}

private void getItemsInConditions(@Nullable EList<Condition> conditions, Set<GenericItem> items) {
if (conditions != null) {
for (Condition condition : conditions) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@
*
* @author Kai Kreuzer - Initial contribution
* @author Laurent Garnier - New field iconcolor
* @author Laurent Garnier - New field reloadIcon
*/
public class SitemapWidgetEvent extends SitemapEvent {

public String widgetId;

public String label;
public String icon;
public boolean reloadIcon;
public String labelcolor;
public String valuecolor;
public String iconcolor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ public class WidgetDTO {

public String label;
public String icon;
/**
* staticIcon is a boolean indicating if the widget state must be ignored when requesting the icon.
* It is set to true when the widget has either the staticIcon property set or the icon property set
* with conditional rules.
*/
public Boolean staticIcon;
public String labelcolor;
public String valuecolor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import org.openhab.core.model.sitemap.SitemapProvider;
import org.openhab.core.model.sitemap.sitemap.ColorArray;
import org.openhab.core.model.sitemap.sitemap.Condition;
import org.openhab.core.model.sitemap.sitemap.IconRule;
import org.openhab.core.model.sitemap.sitemap.Sitemap;
import org.openhab.core.model.sitemap.sitemap.VisibilityRule;
import org.openhab.core.model.sitemap.sitemap.Widget;
Expand All @@ -62,6 +63,7 @@
* Test aspects of the {@link SitemapResource}.
*
* @author Henning Treu - Initial contribution
* @author Laurent Garnier - Extended tests for static icon and icon based on conditional rules
*/
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
Expand All @@ -80,10 +82,16 @@ public class SitemapResourceTest extends JavaTest {
private static final String LABEL_COLOR_ITEM_NAME = "labelColorItemName";
private static final String VALUE_COLOR_ITEM_NAME = "valueColorItemName";
private static final String ICON_COLOR_ITEM_NAME = "iconColorItemName";
private static final String ICON_ITEM_NAME = "iconItemName";
private static final String WIDGET1_LABEL = "widget 1";
private static final String WIDGET2_LABEL = "widget 2";
private static final String WIDGET3_LABEL = "widget 3";
private static final String WIDGET1_ID = "00";
private static final String WIDGET2_ID = "01";
private static final String WIDGET3_ID = "02";
private static final String WIDGET1_ICON = "icon1";
private static final String WIDGET2_ICON = "icon2";
private static final String WIDGET3_ICON = "icon3";
private static final String CLIENT_IP = "127.0.0.1";

private @NonNullByDefault({}) SitemapResource sitemapResource;
Expand All @@ -93,6 +101,7 @@ public class SitemapResourceTest extends JavaTest {
private @NonNullByDefault({}) GenericItem labelColorItem;
private @NonNullByDefault({}) GenericItem valueColorItem;
private @NonNullByDefault({}) GenericItem iconColorItem;
private @NonNullByDefault({}) GenericItem iconItem;

private @Mock @NonNullByDefault({}) HttpHeaders headersMock;
private @Mock @NonNullByDefault({}) Sitemap defaultSitemapMock;
Expand Down Expand Up @@ -121,6 +130,7 @@ public void setup() throws Exception {
labelColorItem = new TestItem(LABEL_COLOR_ITEM_NAME);
valueColorItem = new TestItem(VALUE_COLOR_ITEM_NAME);
iconColorItem = new TestItem(ICON_COLOR_ITEM_NAME);
iconItem = new TestItem(ICON_ITEM_NAME);

when(localeServiceMock.getLocale(null)).thenReturn(Locale.US);

Expand Down Expand Up @@ -276,6 +286,30 @@ public void whenLongPollingShouldObserveItemsFromIconColorConditions() {
// return
}

@Test
public void whenLongPollingShouldObserveItemsFromIconConditions() {
ItemEvent itemEvent = mock(ItemEvent.class);
when(itemEvent.getItemName()).thenReturn(iconItem.getName());
new Thread(() -> {
try {
Thread.sleep(STATE_UPDATE_WAIT_TIME); // wait for the #getPageData call and listeners to attach to the
// item
sitemapResource.receive(itemEvent);
} catch (InterruptedException e) {
}
}).start();

// non-null is sufficient here.
when(headersMock.getRequestHeader(HTTP_HEADER_X_ATMOSPHERE_TRANSPORT)).thenReturn(List.of());

Response response = sitemapResource.getPageData(headersMock, null, SITEMAP_MODEL_NAME, SITEMAP_NAME, null,
false);

PageDTO pageDTO = (PageDTO) response.getEntity();
assertThat(pageDTO.timeout, is(false)); // assert that the item state change did trigger the blocking method to
// return
}

@Test
public void whenGetPageDataShouldReturnPageBean() throws ItemNotFoundException {
item.setState(new PercentType(50));
Expand All @@ -294,13 +328,15 @@ public void whenGetPageDataShouldReturnPageBean() throws ItemNotFoundException {
assertThat(pageDTO.timeout, is(false));

assertThat(pageDTO.widgets, notNullValue());
assertThat((Collection<?>) pageDTO.widgets, hasSize(2));
assertThat((Collection<?>) pageDTO.widgets, hasSize(3));

assertThat(pageDTO.widgets.get(0).widgetId, is(WIDGET1_ID));
assertThat(pageDTO.widgets.get(0).label, is(WIDGET1_LABEL));
assertThat(pageDTO.widgets.get(0).labelcolor, is("GREEN"));
assertThat(pageDTO.widgets.get(0).valuecolor, is("BLUE"));
assertThat(pageDTO.widgets.get(0).iconcolor, is("ORANGE"));
assertThat(pageDTO.widgets.get(0).icon, is(WIDGET1_ICON));
assertThat(pageDTO.widgets.get(0).staticIcon, is(true));
assertThat(pageDTO.widgets.get(0).state, nullValue());
assertThat(pageDTO.widgets.get(0).item, notNullValue());
assertThat(pageDTO.widgets.get(0).item.name, is(ITEM_NAME));
Expand All @@ -311,10 +347,24 @@ public void whenGetPageDataShouldReturnPageBean() throws ItemNotFoundException {
assertThat(pageDTO.widgets.get(1).labelcolor, nullValue());
assertThat(pageDTO.widgets.get(1).valuecolor, nullValue());
assertThat(pageDTO.widgets.get(1).iconcolor, nullValue());
assertThat(pageDTO.widgets.get(1).icon, is(WIDGET2_ICON));
assertThat(pageDTO.widgets.get(1).staticIcon, is(false));
assertThat(pageDTO.widgets.get(1).state, is("ON"));
assertThat(pageDTO.widgets.get(1).item, notNullValue());
assertThat(pageDTO.widgets.get(1).item.name, is(ITEM_NAME));
assertThat(pageDTO.widgets.get(1).item.state, is("50"));

assertThat(pageDTO.widgets.get(2).widgetId, is(WIDGET3_ID));
assertThat(pageDTO.widgets.get(2).label, is(WIDGET3_LABEL));
assertThat(pageDTO.widgets.get(2).labelcolor, nullValue());
assertThat(pageDTO.widgets.get(2).valuecolor, nullValue());
assertThat(pageDTO.widgets.get(2).iconcolor, nullValue());
assertThat(pageDTO.widgets.get(2).icon, is(WIDGET3_ICON));
assertThat(pageDTO.widgets.get(2).staticIcon, is(true));
assertThat(pageDTO.widgets.get(2).state, is("ON"));
assertThat(pageDTO.widgets.get(2).item, notNullValue());
assertThat(pageDTO.widgets.get(2).item.name, is(ITEM_NAME));
assertThat(pageDTO.widgets.get(2).item.state, is("50"));
}

private void configureItemUIRegistry(State state1, State state2) throws ItemNotFoundException {
Expand All @@ -324,9 +374,10 @@ private void configureItemUIRegistry(State state1, State state2) throws ItemNotF
when(itemUIRegistryMock.getItem(LABEL_COLOR_ITEM_NAME)).thenReturn(labelColorItem);
when(itemUIRegistryMock.getItem(VALUE_COLOR_ITEM_NAME)).thenReturn(valueColorItem);
when(itemUIRegistryMock.getItem(ICON_COLOR_ITEM_NAME)).thenReturn(iconColorItem);
when(itemUIRegistryMock.getItem(ICON_ITEM_NAME)).thenReturn(iconItem);

when(itemUIRegistryMock.getWidgetId(widgets.get(0))).thenReturn(WIDGET1_ID);
when(itemUIRegistryMock.getCategory(widgets.get(0))).thenReturn("");
when(itemUIRegistryMock.getCategory(widgets.get(0))).thenReturn(WIDGET1_ICON);
when(itemUIRegistryMock.getLabel(widgets.get(0))).thenReturn(WIDGET1_LABEL);
when(itemUIRegistryMock.getVisiblity(widgets.get(0))).thenReturn(true);
when(itemUIRegistryMock.getLabelColor(widgets.get(0))).thenReturn("GREEN");
Expand All @@ -335,13 +386,22 @@ private void configureItemUIRegistry(State state1, State state2) throws ItemNotF
when(itemUIRegistryMock.getState(widgets.get(0))).thenReturn(state1);

when(itemUIRegistryMock.getWidgetId(widgets.get(1))).thenReturn(WIDGET2_ID);
when(itemUIRegistryMock.getCategory(widgets.get(1))).thenReturn("");
when(itemUIRegistryMock.getCategory(widgets.get(1))).thenReturn(WIDGET2_ICON);
when(itemUIRegistryMock.getLabel(widgets.get(1))).thenReturn(WIDGET2_LABEL);
when(itemUIRegistryMock.getVisiblity(widgets.get(1))).thenReturn(true);
when(itemUIRegistryMock.getLabelColor(widgets.get(1))).thenReturn(null);
when(itemUIRegistryMock.getValueColor(widgets.get(1))).thenReturn(null);
when(itemUIRegistryMock.getIconColor(widgets.get(1))).thenReturn(null);
when(itemUIRegistryMock.getState(widgets.get(1))).thenReturn(state2);

when(itemUIRegistryMock.getWidgetId(widgets.get(2))).thenReturn(WIDGET3_ID);
when(itemUIRegistryMock.getCategory(widgets.get(2))).thenReturn(WIDGET3_ICON);
when(itemUIRegistryMock.getLabel(widgets.get(2))).thenReturn(WIDGET3_LABEL);
when(itemUIRegistryMock.getVisiblity(widgets.get(2))).thenReturn(true);
when(itemUIRegistryMock.getLabelColor(widgets.get(2))).thenReturn(null);
when(itemUIRegistryMock.getValueColor(widgets.get(2))).thenReturn(null);
when(itemUIRegistryMock.getIconColor(widgets.get(2))).thenReturn(null);
when(itemUIRegistryMock.getState(widgets.get(2))).thenReturn(state2);
}

private EList<Widget> initSitemapWidgets() {
Expand All @@ -355,6 +415,19 @@ private EList<Widget> initSitemapWidgets() {
when(w1.eClass()).thenReturn(sliderEClass);
when(w1.getLabel()).thenReturn(WIDGET1_LABEL);
when(w1.getItem()).thenReturn(ITEM_NAME);
when(w1.getIcon()).thenReturn(null);
when(w1.getStaticIcon()).thenReturn(null);

// add icon rules to the mock widget:
IconRule iconRule = mock(IconRule.class);
Condition conditon0 = mock(Condition.class);
when(conditon0.getItem()).thenReturn(ICON_ITEM_NAME);
EList<Condition> conditions0 = new BasicEList<>();
conditions0.add(conditon0);
when(iconRule.getConditions()).thenReturn(conditions0);
EList<IconRule> iconRules = new BasicEList<>();
iconRules.add(iconRule);
when(w1.getIconRules()).thenReturn(iconRules);

// add visibility rules to the mock widget:
VisibilityRule visibilityRule = mock(VisibilityRule.class);
Expand Down Expand Up @@ -400,6 +473,7 @@ private EList<Widget> initSitemapWidgets() {
iconColors.add(iconColor);
when(w1.getIconColor()).thenReturn(iconColors);

iconRules = new BasicEList<>();
visibilityRules = new BasicEList<>();
labelColors = new BasicEList<>();
valueColors = new BasicEList<>();
Expand All @@ -412,14 +486,30 @@ private EList<Widget> initSitemapWidgets() {
when(w2.eClass()).thenReturn(switchEClass);
when(w2.getLabel()).thenReturn(WIDGET2_LABEL);
when(w2.getItem()).thenReturn(ITEM_NAME);
when(w2.getIcon()).thenReturn(WIDGET2_ICON);
when(w2.getStaticIcon()).thenReturn(null);
when(w2.getIconRules()).thenReturn(iconRules);
when(w2.getVisibility()).thenReturn(visibilityRules);
when(w2.getLabelColor()).thenReturn(labelColors);
when(w2.getValueColor()).thenReturn(valueColors);
when(w2.getIconColor()).thenReturn(iconColors);

EList<Widget> widgets = new BasicEList<>(2);
Widget w3 = mock(Widget.class);
when(w3.eClass()).thenReturn(switchEClass);
when(w3.getLabel()).thenReturn(WIDGET3_LABEL);
when(w3.getItem()).thenReturn(ITEM_NAME);
when(w3.getIcon()).thenReturn(null);
when(w3.getStaticIcon()).thenReturn(WIDGET3_ICON);
when(w3.getIconRules()).thenReturn(iconRules);
when(w3.getVisibility()).thenReturn(visibilityRules);
when(w3.getLabelColor()).thenReturn(labelColors);
when(w3.getValueColor()).thenReturn(valueColors);
when(w3.getIconColor()).thenReturn(iconColors);

EList<Widget> widgets = new BasicEList<>(3);
widgets.add(w1);
widgets.add(w2);
widgets.add(w3);
return widgets;
}

Expand Down
Loading