Skip to content

Commit

Permalink
fix: add usage stats for automatic menu (#20224)
Browse files Browse the repository at this point in the history
Fixes: #19953
  • Loading branch information
tltv authored Oct 11, 2024
1 parent cc926d5 commit 4924daa
Show file tree
Hide file tree
Showing 4 changed files with 235 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package com.vaadin.flow.router.internal;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
Expand All @@ -36,6 +37,7 @@
import com.vaadin.flow.component.UI;
import com.vaadin.flow.di.Lookup;
import com.vaadin.flow.internal.AnnotationReader;
import com.vaadin.flow.internal.menu.MenuRegistry;
import com.vaadin.flow.router.DefaultRoutePathProvider;
import com.vaadin.flow.router.HasDynamicTitle;
import com.vaadin.flow.router.Layout;
Expand All @@ -47,9 +49,11 @@
import com.vaadin.flow.router.RoutePathProvider;
import com.vaadin.flow.router.RoutePrefix;
import com.vaadin.flow.router.RouterLayout;
import com.vaadin.flow.server.AbstractConfiguration;
import com.vaadin.flow.server.RouteRegistry;
import com.vaadin.flow.server.SessionRouteRegistry;
import com.vaadin.flow.server.VaadinContext;
import com.vaadin.flow.server.menu.AvailableViewInfo;

/**
* Utility class with methods for route handling.
Expand Down Expand Up @@ -583,6 +587,63 @@ public static boolean isAutolayoutEnabled(Class<?> target, String path) {

}

/**
* Check if the given registry has any auto layouts added
* with @{@link Layout} annotation.
*
* @param registry
* the registry to check
* @return {@code true} if the registry has any auto layouts
*/
public static boolean hasAutoLayout(AbstractRouteRegistry registry) {
return !registry.getLayouts().isEmpty();
}

/**
* Check if currently registered client routes use auto layout based on
* {@link AvailableViewInfo#flowLayout()}.
*
* @param configuration
* deployment configuration
* @return {@code true} if any client route has auto layout
*/
public static boolean hasClientRouteWithAutoLayout(
AbstractConfiguration configuration) {
return MenuRegistry.collectClientMenuItems(false, configuration)
.values().stream().anyMatch(AvailableViewInfo::flowLayout);
}

/**
* Check if the given registry has any routes using auto layout.
*
* @param registry
* the registry to check
* @return {@code true} if the registry has any auto layouts
*/
public static boolean hasServerRouteWithAutoLayout(
AbstractRouteRegistry registry) {
Collection<?> layouts = registry.getLayouts();
return registry.getRegisteredRoutes().stream().anyMatch(routeData -> {
String path;
if (routeData.getNavigationTarget()
.getAnnotation(Route.class) != null) {
path = getRoutePath(registry.getContext(),
routeData.getNavigationTarget());
} else {
path = resolve(registry.getContext(),
routeData.getNavigationTarget());
List<String> parentRoutePrefixes = getRoutePrefixes(
routeData.getNavigationTarget(), null, path);
path = String.join("/", parentRoutePrefixes);
}
return RouteUtil
.isAutolayoutEnabled(routeData.getNavigationTarget(), path)
&& registry.hasLayout(path)
&& collectRouteParentLayouts(registry.getLayout(path))
.stream().anyMatch(layouts::contains);
});
}

/**
* Get optional dynamic page title from the active router targets chain of a
* given UI instance.
Expand Down
18 changes: 18 additions & 0 deletions flow-server/src/main/java/com/vaadin/flow/server/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,24 @@ public final class Constants implements Serializable {
*/
public static final String STATISTIC_HAS_FLOW_ROUTE = "has-flow-route";

/**
* UsageEntry name for automatic layout. Marked used, if Layout annotation
* is used or RouteRegistry#setLayout is used directly.
*/
public static final String STATISTIC_HAS_AUTO_LAYOUT = "has-auto-layout";

/**
* UsageEntry name for client route using automatic layout. Marked used, if
* AvailableViewInfo#flowLayout is true for any client route.
*/
public static final String STATISTIC_HAS_CLIENT_ROUTE_WITH_AUTO_LAYOUT = "has-auto-layout/client";

/**
* UsageEntry name for server route using automatic layout. Marked used, if
* any server route's layout matches Layout annotated layout.
*/
public static final String STATISTIC_HAS_SERVER_ROUTE_WITH_AUTO_LAYOUT = "has-auto-layout/server";

/**
* UsageEntry name for exported web components. Marked used, if either
* WebComponentExporter or WebComponentExporterFactory is found in a project
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@
import com.vaadin.flow.router.RouteData;
import com.vaadin.flow.router.Router;
import com.vaadin.flow.router.internal.AbstractNavigationStateRenderer;
import com.vaadin.flow.router.internal.AbstractRouteRegistry;
import com.vaadin.flow.router.internal.RouteUtil;
import com.vaadin.flow.server.HandlerHelper.RequestType;
import com.vaadin.flow.server.communication.AtmospherePushConnection;
import com.vaadin.flow.server.communication.HeartbeatHandler;
Expand Down Expand Up @@ -304,6 +306,7 @@ public void init() throws ServiceException {
addRouterUsageStatistics();
}
routeDataList.stream().map(Object::toString).forEach(logger::debug);
addAutoLayoutUsageStatistics();
DevToolsToken.init(this);
}
if (getDeploymentConfiguration().isPnpmEnabled()) {
Expand Down Expand Up @@ -331,6 +334,25 @@ private void addRouterUsageStatistics() {
UsageStatistics.markAsUsed(Constants.STATISTIC_HAS_FLOW_ROUTE, null);
}

private void addAutoLayoutUsageStatistics() {
if (getRouteRegistry() instanceof AbstractRouteRegistry registry
&& RouteUtil.hasAutoLayout(registry)) {
UsageStatistics.markAsUsed(Constants.STATISTIC_HAS_AUTO_LAYOUT,
null);
if (RouteUtil.hasClientRouteWithAutoLayout(
getDeploymentConfiguration())) {
UsageStatistics.markAsUsed(
Constants.STATISTIC_HAS_CLIENT_ROUTE_WITH_AUTO_LAYOUT,
null);
}
if (RouteUtil.hasServerRouteWithAutoLayout(registry)) {
UsageStatistics.markAsUsed(
Constants.STATISTIC_HAS_SERVER_ROUTE_WITH_AUTO_LAYOUT,
null);
}
}
}

/**
* Find a route registry to use for this service.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,17 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

import org.junit.Assert;
import org.junit.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;

import com.vaadin.flow.component.Component;
Expand All @@ -43,13 +46,19 @@
import com.vaadin.flow.function.DeploymentConfiguration;
import com.vaadin.flow.internal.CurrentInstance;
import com.vaadin.flow.internal.UsageStatistics;
import com.vaadin.flow.internal.menu.MenuRegistry;
import com.vaadin.flow.router.Layout;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.router.RouteConfiguration;
import com.vaadin.flow.router.RouteData;
import com.vaadin.flow.router.Router;
import com.vaadin.flow.router.RouterLayout;
import com.vaadin.flow.server.communication.PwaHandler;
import com.vaadin.flow.server.communication.StreamRequestHandler;
import com.vaadin.flow.server.communication.WebComponentBootstrapHandler;
import com.vaadin.flow.server.communication.WebComponentProvider;
import com.vaadin.flow.server.menu.AvailableViewInfo;
import com.vaadin.flow.server.startup.ApplicationRouteRegistry;
import com.vaadin.tests.util.MockDeploymentConfiguration;

import static org.hamcrest.CoreMatchers.containsString;
Expand All @@ -68,6 +77,18 @@ public static class TestView extends Component {

}

@Route("test")
@Tag("div")
public static class AnnotatedTestView extends Component {

}

@Route(value = "flow", autoLayout = false)
@Tag("div")
public static class OptOutAutoLayoutTestView extends Component {

}

private class TestSessionDestroyListener implements SessionDestroyListener {

int callCount = 0;
Expand Down Expand Up @@ -112,10 +133,13 @@ public void should_reported_routing_server() {

Assert.assertTrue(UsageStatistics.getEntries().anyMatch(
e -> Constants.STATISTIC_ROUTING_SERVER.equals(e.getName())));
Assert.assertFalse(UsageStatistics.getEntries().anyMatch(
e -> Constants.STATISTIC_HAS_AUTO_LAYOUT.equals(e.getName())));
}

@Test
public void should_reported_routing_hybrid() {
UsageStatistics.resetEntries();
VaadinServiceInitListener initListener = event -> {
RouteConfiguration.forApplicationScope().setRoute("test",
TestView.class);
Expand All @@ -136,6 +160,116 @@ public void should_reported_routing_hybrid() {
e -> Constants.STATISTIC_ROUTING_SERVER.equals(e.getName())));
Assert.assertTrue(UsageStatistics.getEntries().anyMatch(
e -> Constants.STATISTIC_HAS_FLOW_ROUTE.equals(e.getName())));
Assert.assertFalse(UsageStatistics.getEntries().anyMatch(
e -> Constants.STATISTIC_HAS_AUTO_LAYOUT.equals(e.getName())));
}

@Test
public void should_reported_auto_layout_server() {
UsageStatistics.resetEntries();
@Layout
class AutoLayout extends Component implements RouterLayout {
}
VaadinServiceInitListener initListener = event -> {
ApplicationRouteRegistry.getInstance(event.getSource().getContext())
.setLayout(AutoLayout.class);
RouteConfiguration.forApplicationScope()
.setAnnotatedRoute(AnnotatedTestView.class);
};
MockVaadinServletService service = new MockVaadinServletService();
runWithClientRoute("test", false, service, () -> {
service.init(new MockInstantiator(initListener));

Assert.assertTrue(UsageStatistics.getEntries()
.anyMatch(e -> Constants.STATISTIC_HAS_AUTO_LAYOUT
.equals(e.getName())));
Assert.assertTrue(UsageStatistics.getEntries().anyMatch(
e -> Constants.STATISTIC_HAS_SERVER_ROUTE_WITH_AUTO_LAYOUT
.equals(e.getName())));
Assert.assertFalse(UsageStatistics.getEntries().anyMatch(
e -> Constants.STATISTIC_HAS_CLIENT_ROUTE_WITH_AUTO_LAYOUT
.equals(e.getName())));
});
}

@Test
public void should_reported_auto_layout_client() {
UsageStatistics.resetEntries();
@Layout
class AutoLayout extends Component implements RouterLayout {
}
VaadinServiceInitListener initListener = event -> {
ApplicationRouteRegistry.getInstance(event.getSource().getContext())
.setLayout(AutoLayout.class);
};
MockVaadinServletService service = new MockVaadinServletService();
runWithClientRoute("test", true, service, () -> {
service.init(new MockInstantiator(initListener));

Assert.assertTrue(UsageStatistics.getEntries()
.anyMatch(e -> Constants.STATISTIC_HAS_AUTO_LAYOUT
.equals(e.getName())));
Assert.assertFalse(UsageStatistics.getEntries().anyMatch(
e -> Constants.STATISTIC_HAS_SERVER_ROUTE_WITH_AUTO_LAYOUT
.equals(e.getName())));
Assert.assertTrue(UsageStatistics.getEntries().anyMatch(
e -> Constants.STATISTIC_HAS_CLIENT_ROUTE_WITH_AUTO_LAYOUT
.equals(e.getName())));
});
}

@Test
public void should_reported_auto_layout_routes_not_used() {
UsageStatistics.resetEntries();
@Route(value = "not-in-auto-layout")
@Tag("div")
class LayoutTestView extends Component {
}
@Layout("layout")
class AutoLayout extends Component implements RouterLayout {
}
VaadinServiceInitListener initListener = event -> {
ApplicationRouteRegistry.getInstance(event.getSource().getContext())
.setLayout(AutoLayout.class);
RouteConfiguration.forApplicationScope()
.setAnnotatedRoute(OptOutAutoLayoutTestView.class);
RouteConfiguration.forApplicationScope()
.setAnnotatedRoute(LayoutTestView.class);
};
MockVaadinServletService service = new MockVaadinServletService();
runWithClientRoute("test", false, service, () -> {
service.init(new MockInstantiator(initListener));

Assert.assertTrue(UsageStatistics.getEntries()
.anyMatch(e -> Constants.STATISTIC_HAS_AUTO_LAYOUT
.equals(e.getName())));
Assert.assertFalse(UsageStatistics.getEntries().anyMatch(
e -> Constants.STATISTIC_HAS_SERVER_ROUTE_WITH_AUTO_LAYOUT
.equals(e.getName())));
Assert.assertFalse(UsageStatistics.getEntries().anyMatch(
e -> Constants.STATISTIC_HAS_CLIENT_ROUTE_WITH_AUTO_LAYOUT
.equals(e.getName())));
});
}

private void runWithClientRoute(String route, boolean flowLayout,
MockVaadinServletService service, Runnable runnable) {
try (MockedStatic<MenuRegistry> menuRegistry = Mockito
.mockStatic(MenuRegistry.class)) {
menuRegistry
.when(() -> MenuRegistry.collectClientMenuItems(false,
service.getDeploymentConfiguration()))
.thenReturn(createClientRoutesMap(route, flowLayout));
runnable.run();
}
}

private static Map<String, AvailableViewInfo> createClientRoutesMap(
String route, boolean flowLayout) {
Map<String, AvailableViewInfo> clientViews = new HashMap<>();
clientViews.put("test", new AvailableViewInfo("Test", null, false,
route, false, false, null, null, null, flowLayout));
return clientViews;
}

@Test
Expand Down

0 comments on commit 4924daa

Please sign in to comment.