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

fix: show available client routes #19050

Merged
merged 11 commits into from
Apr 3, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.TreeMap;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.apache.commons.io.IOUtils;
Expand All @@ -35,7 +38,8 @@
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Html;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.internal.hilla.EndpointRequestUtil;
import com.vaadin.flow.di.Lookup;
import com.vaadin.flow.router.internal.ClientRoutesProvider;
import com.vaadin.flow.server.HttpStatusCode;
import com.vaadin.flow.server.VaadinService;
import com.vaadin.flow.server.frontend.FrontendUtils;
Expand Down Expand Up @@ -116,8 +120,10 @@ private static String readHtmlFile(String templateName) {
}

private String getRoutes(BeforeEnterEvent event) {
List<Element> routeElements = new ArrayList<>();
List<RouteData> routes = event.getSource().getRegistry()
.getRegisteredRoutes();

Map<String, Class<? extends Component>> routeTemplates = new TreeMap<>();

for (RouteData route : routes) {
Expand All @@ -127,14 +133,21 @@ private String getRoutes(BeforeEnterEvent event) {
.put(alias.getTemplate(), alias.getNavigationTarget()));
}

List<Element> routeElements = new ArrayList<>();
routeTemplates.forEach(
(k, v) -> routeElements.add(routeTemplateToHtml(k, v)));

routeElements.addAll(getClientRoutes());
return routeElements.stream().map(Element::outerHtml)
.collect(Collectors.joining());
}

private List<Element> getClientRoutes() {
return FrontendUtils.getClientRoutes().stream()
.filter(route -> !route.contains("$layout"))
.map(route -> route.replace("$index", ""))
caalador marked this conversation as resolved.
Show resolved Hide resolved
.map(this::clientRouteToHtml).toList();
}

private Element routeTemplateToHtml(String routeTemplate,
Class<? extends Component> navigationTarget) {
String text = routeTemplate;
Expand All @@ -156,6 +169,23 @@ private Element routeTemplateToHtml(String routeTemplate,
}
}

private Element clientRouteToHtml(String route) {
String text = route;
if (text.isEmpty()) {
text = "<root>";
}
if (!route.contains(":")) {
return elementAsLink(route, text);
} else {
if (Pattern.compile(":\\w+\\?").matcher(route).find()) {
text += " (supports optional parameter)";
} else {
text += " (requires parameter)";
}
return new Element(Tag.LI).text(text);
}
}

private Element elementAsLink(String url, String text) {
Element link = new Element(Tag.A).attr("href", url).text(text);
return new Element(Tag.LI).appendChild(link);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2000-2024 Vaadin Ltd.
*
* Licensed 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 com.vaadin.flow.router.internal;

import java.io.Serializable;
import java.util.List;

/**
* Interface for providing client side routes.
*/
public interface ClientRoutesProvider extends Serializable {

/**
* Get a list of client side routes.
*
* @return a list of client side routes. Not null.
*/
List<String> getClientRoutes();
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import com.vaadin.flow.internal.Pair;
import com.vaadin.flow.internal.StringUtil;
import com.vaadin.flow.internal.hilla.EndpointRequestUtil;
import com.vaadin.flow.router.internal.ClientRoutesProvider;
import com.vaadin.flow.server.AbstractConfiguration;
import com.vaadin.flow.server.Constants;
import com.vaadin.flow.server.VaadinService;
Expand Down Expand Up @@ -1434,4 +1435,20 @@ public static boolean isReactModuleAvailable(Options options) {
return false;
}
}

/**
* Get all available client routes in a distinct list of route paths
* collected from all {@link ClientRoutesProvider} implementations found
* with Vaadin {@link Lookup}.
*
* @return a list of available client routes
*/
public static List<String> getClientRoutes() {
return Optional.ofNullable(VaadinService.getCurrent())
.map(VaadinService::getContext).stream()
.flatMap(ctx -> ctx.getAttribute(Lookup.class)
.lookupAll(ClientRoutesProvider.class).stream())
caalador marked this conversation as resolved.
Show resolved Hide resolved
.flatMap(provider -> provider.getClientRoutes().stream())
.filter(Objects::nonNull).distinct().toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,17 @@
*/
package com.vaadin.flow.spring.test;

import java.util.List;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

import com.vaadin.flow.router.internal.ClientRoutesProvider;

@SpringBootApplication
@Configuration
@EnableWebSecurity
Expand All @@ -30,4 +35,25 @@ public static void main(String[] args) {
SpringApplication.run(TestServletInitializer.class, args);
}

@Bean
public ClientRoutesProvider hillaClientRoutesProvider() {
return new ClientRoutesProvider() {
@Override
public List<String> getClientRoutes() {
return List.of("$index", "$layout", "/hilla",
"/hilla/person/:id", "/hilla/persons/:id?");
}
};
}

@Bean
public ClientRoutesProvider anotherhillaClientRoutesProvider() {
return new ClientRoutesProvider() {
@Override
public List<String> getClientRoutes() {
return List.of("/$layout", "/hilla", "/hilla/hilla/$index",
"/anotherhilla");
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2000-2024 Vaadin Ltd.
*
* Licensed 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 com.vaadin.flow.spring.test;

import java.util.List;

import org.junit.Assert;
import org.junit.Test;

public class HillaRoutesRegisteredIT extends AbstractSpringTest {

@Test
public void assertClientRoutesRegistered() {
String nonExistingRoutePath = "non-existing-route";
getDriver().get(getContextRootURL() + '/' + nonExistingRoutePath);
waitForDevServer();
Assert.assertTrue(getDriver().getPageSource().contains(String
.format("Could not navigate to '%s'", nonExistingRoutePath)));

if (getDriver().getPageSource().contains(
"This detailed message is only shown when running in development mode.")) {
var expectedClientRoutes = List.of("<a href=\"\">&lt;root&gt;</a>",
"<a href=\"/hilla\">/hilla</a>",
"<li>/hilla/person/:id (requires parameter)</li>",
"<li>/hilla/persons/:id? (supports optional parameter)</li>",
"<a href=\"/hilla/hilla/\">/hilla/hilla/</a>",
"<a href=\"/anotherhilla\">/anotherhilla</a>");
for (String route : expectedClientRoutes) {
Assert.assertTrue(
String.format("Expected client route %s is missing",
route),
getDriver().getPageSource().contains(route));
}
}
}
}