Vaadin23 route navigation with parameters - vaadin

I have the following route:
#Route(value = "jobs/:jobId", layout = JobsLayout.class)
#AnonymousAllowed
public class ViewJob extends VerticalLayout implements BeforeEnterObserver, HasUrlParameter<String> {
When I access the application by the following url:
/jobs/456/test
I correctly enters ViewJob view.
Now, I'd like to programmatically move to this view from another Vaadin view. For this, I'm trying to do the following:
UI.getCurrent().navigate(ViewJob.class, "456/test");
but the application fails with the following exception:
com.vaadin.flow.router.NotFoundException: No route found for the given navigation target 'com.example.ui.views.job.view.ViewJob' and parameters '{___url_parameter=456/test}'
at com.vaadin.flow.router.RouteConfiguration.getUrl(RouteConfiguration.java:515)
at com.vaadin.flow.component.UI.navigate(UI.java:914)
What am I doing wrong and how to correctly navigate to the ViewJob view?
UPDATED
The goal - I'm trying to implement the logic similar to StackOverflow - when you access the question page without a slug, for example Vaadin23 route navigation with parameters you will be automatically redirected to the correct url with slug added - Vaadin23 route navigation with parameters
For that I have the following view:
#Route(value = "jobs", layout = JobsLayout.class)
#AnonymousAllowed
public class AllJobsView extends VerticalLayout implements LocaleChangeObserver, HasUrlParameter<String> {
#Override
public void setParameter(BeforeEvent event, #OptionalParameter String parameter) {
try {
Long jobId = Long.parseLong(parameter);
Vacancy vacancy = vacancyService.findById(jobId, VaadinUtils.getCurrentLocaleIso6391());
UI.getCurrent().navigate(ViewJob.class, new RouteParameters(Map.of(ViewJob.JOB_ID_PARAMETER, parameter, HasUrlParameterFormat.PARAMETER_NAME, vacancy.getNameSlug())));
return;
} catch (Exception e) {
e.printStackTrace();
}
This is ViewJob view:
#Route(value = "jobs/:jobId", layout = JobsLayout.class)
#AnonymousAllowed
public class ViewJob extends VerticalLayout implements BeforeEnterObserver, HasUrlParameter<String> {
....
}
When I try to access /jobs - AllJobsView is correctly rendered.
When I try to access /jobs/507/vacancy5 - JobView is correctly rendered
but when I try to access /jobs/507 the following redirection logic is involed from AllJobsView.setParameter method:
UI.getCurrent().navigate(ViewJob.class, new RouteParameters(Map.of(ViewJob.JOB_ID_PARAMETER, parameter, HasUrlParameterFormat.PARAMETER_NAME, vacancy.getNameSlug())));
After that the user is correctly navigated to /jobs/507/vacancy5 url but the JobView content is not rendered.. only JobsLayout part.
What am I doing wrong?

You have defined a route parameter in the url (:jobId). This has to match with the route parameter given to the URL:
UI.getCurrent().navigate(ViewJob.class, new RouteParameter("jobId", "456"));

Related

URL Parameters in Vaadin LoginOverlay

I have created a Vaadin 21 project with the Vaadin App Starter. The application uses authentication, so the user first has to login on a LoginView:
#PageTitle("Login")
#Route(value = "login")
public class LoginView extends LoginOverlay implements BeforeEnterListener {
public LoginView() {
setAction("login");
LoginI18n i18n = LoginI18n.createDefault();
i18n.setHeader(new LoginI18n.Header());
i18n.getHeader().setTitle("My App");
i18n.getHeader().setDescription("Login using user/user or admin/admin");
i18n.setAdditionalInformation(null);
setI18n(i18n);
setForgotPasswordButtonVisible(false);
setOpened(true);
}
#Override
public void beforeEnter(BeforeEnterEvent beforeEnterEvent) {
Map<String,List<String>> parametersMap = beforeEnterEvent.getLocation().getQueryParameters().getParameters();
if (parametersMap.contains("myid")) {
// Do something
}
}
}
I want to transmit an id in the URL parameters to customize the LoginView, so I added the beforeEnter method to retrieve the URL parameter. But if I open my app in the browser with the URL http://localhost:8080/login?myid=5, the beforeEnter method is called but there is no URL parameter in the parametersMap, it is empty. How it is possible to transmit a URL parameter in a LoginOverlay View?
If you add
logging.level.org.springframework.security=DEBUG
to your src/main/resources/application.properties you will see that Spring Security is the one that prevents your URL parameters from reaching the login view:
2021-09-14 14:01:21.194 DEBUG 24566 --- [io-23342-exec-1] o.s.s.w.a.i.FilterSecurityInterceptor : Failed to authorize filter invocation [GET /login?myid=5] with attributes [authenticated]
2021-09-14 14:01:21.195 DEBUG 24566 --- [io-23342-exec-1] o.s.s.web.DefaultRedirectStrategy : Redirecting to http://localhost:23342/login
You can allow this by changing your Spring Security configuration method to e.g.
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().regexMatchers("/login\\?myid=.*").permitAll();
super.configure(http);
setLoginView(http, LoginView.class, LOGOUT_URL);
}
Now you also need to fix a small problem in your code, which is that you must implement BeforeEnterObserver instead of BeforeEnterListener in your view class for beforeEnter to be called.

How to extend Jenkins job page with new links and icons

I'm developing my first Jenkins plugin and followed the tutorial at wiki.jenkins-ci.org. After adding a BuildStep and generating the results I now want to publish them to the user. I would like to do this via a new link entry on the job page and a corrsponding result view page.
Unfortunatelly I do not find the right extension points for the navigation bar at the left side, the main navigation links in the center as well as the new target page. Can somebody point me in the right direction or give me a link to a tutorial or blog post that explains this scenario?
Thanks
Root Action and Actions are different. The first one goes only to initial page (root), the second one can be attach to a Project/Job or to a Build.
To create a Root Action, just need to create a class that it's:
Annotated with #Extension (so it can be found and automatically
loaded by Jenkins)
Implements RootAction Interface
Override 3 methods: getIconFileName(), getDisplayName() and getUrlName()
For example:
#Extension
public class GoogleRootAction implements RootAction{
#Override
public String getIconFileName() {
return "clipboard.png";
}
#Override
public String getDisplayName() {
return "Google URL";
}
#Override
public String getUrlName() {
return "http://www.google.pt";
}
}
To create an Action at a Project it's more complicated, and there's more than a way, depending of what you want.
But first, the class Action itself is the easy part, since it's very similar to a class RootAction. It's not annotated with #Extension and implements Action interface instead of RootAction.
For example:
public class LatestConsoleProjectAction implements Action {
private AbstractProject<?, ?> project;
#Override
public String getIconFileName() {
return (Jenkins.RESOURCE_PATH + "/images/48x48/terminal.png").replaceFirst("^/", "");
}
#Override
public String getDisplayName() {
return Messages.Latest_Console_Project_Action();
}
#Override
public String getUrlName() {
return "lastBuild/console";
}
public LatestConsoleProjectAction(final AbstractProject<?, ?> project) {
this.project = project;
}
}
The tricky part is to inform jenkins that this class Action exists. As I said, there are different ways.
For instance, one can associate an Action to a Builder or Publisher or other by just overriding getProjectAction() method at those classes.
For example:
#Override
public Action getProjectAction(AbstractProject<?, ?> project) {
return new LatestConsoleProjectAction(project);
}
But this way, the Action link will only show on Project left menu, if the corresponding Builder or Publisher is used by the job (or selected at Job configurations).
Another way, that always shows your Action link on left menu, it's create a factory class to inform jenkins. There are many factories, but at my example I will use TransientProjectActionFactory class.
For this, one will need to create a class that:
It's annotated with #Extensions
Extends TransientProjectActionFactory class (or another Factory class)
Override createFor method to create your class Action associated with Project object
For example:
#Extension
public class LatestConsoleProjectActionFactory extends TransientProjectActionFactory {
#Override
public Collection<? extends Action> createFor(AbstractProject abstractProject) {
return Collections.singletonList(new LatestConsoleProjectAction(abstractProject));
}
}
One can still filter project object to just the projects types you want. The one you don't want, just return Collections.emptyList().
Beside this two ways, I think there are others. You can see this link to reference:
https://wiki.jenkins-ci.org/display/JENKINS/Action+and+its+family+of+subtypes
Although, they refer to addAction method and others, but I couldn't use it (I have 2.19.2 Jenkins version).
Also they refer groovy, but I didn't try it, since I want to stick with Java :)
Btw, my example will create an action link to open console page of last build. Useful to avoid selecting last build and then select his console page.
After a lot of trial and error I figured out the solution.
All in all you need two different things in your project:
1) A class that inherits from ProminentProjectAction:
import hudson.model.ProminentProjectAction;
public class MyProjectAction implements ProminentProjectAction {
#Override
public String getIconFileName() {
// return the path to the icon file
return "/images/jenkins.png";
}
#Override
public String getDisplayName() {
// return the label for your link
return "MyActionLink";
}
#Override
public String getUrlName() {
// defines the suburl, which is appended to ...jenkins/job/jobname
return "myactionpage";
}
}
2) Even more important is that you add this action somehow to your project.
In my case I wanted to show the link if and only if the related build step of my plugin is configured for the actual project. So I took my Builder class and overwrote the getProjectActionsMethod.
public class MyBuilder extends Builder {
...
#Override
public Collection<? extends Action> getProjectActions(AbstractProject<?,?> project) {
List<Action> actions = new ArrayList<>();
actions.add(new MyProjectAction());
return actions;
}
}
Maybe this is not the perfect solution yet (because I'm still trying to figure out how all the artifacts are working together), but it might give people which want to implement the same a good starting point.
The page, which is loaded after clicking the link is defined as index.jelly file under source/main/resources and an underlying package with the name of the package of your Action class appended by its class name (e.g. src/main/resources/org/example/myplugin/MyProjectAction).
As it happens, there was a plugin workshop by Steven Christou at the recent Jenkins User Conference in Boston, which covered this case. You need to add a new RootAction, as shown in the following code from the JUC session
package org.jenkinsci.plugins.JUCBeer;
import hudson.Extension;
import hudson.model.RootAction;
#Extension
public class JenkinsRootAction implements RootAction {
public String getIconFileName() {
return "/images/jenkins.png";
}
public String getDisplayName() {
return "Jenkins home page";
}
public String getUrlName() {
return "http://jenkins-ci.org";
}
}
https://github.com/jenkinsci/s3explorer-plugin is my Jenkins plugin that adds an S3 Explorer link to all Jenkins project's side-panel.
An addition to #dchang comment:
I managed to make this functionality work also on pipelines by extending TransientActionFactory<WorkflowJob>:
#Extension
public static class PipelineLatestConsoleProjectActionFactory extends TransientActionFactory<WorkflowJob> {
#Override
public Class<WorkflowJob> type() {
return WorkflowJob.class;
}
#Nonnull
#Override
public Collection<? extends Action> createFor(#Nonnull WorkflowJob job) {
return Collections.singletonList(new LatestConsoleProjectAction(job));
}
}

Custom routes management

Is it possible to use custom routes handling code?
For example client requests server on http://server.com/api/v1/json/profile/ and my code calls ApiController, MyAction action with parameters version=1, format=json, action=profile.
Something like this? You'll have to use a different parameter name for action so you don't have a conflict with the controller action.
.MapRoute("name", "api/v{version}/{format}/{_action}", new { controller = "ApiController", action = "MyAction" });
EDIT made version work the way you wanted.
I would start off by renaming the "action" parameter to something else otherwise the route is going to get very confusing (maybe call it purpose?). Also, I believe something like the following would work:
routes.MapRoute(
// name of your route
"MyRoute",
// route template
"api/v{version}/{format}/{purpose}",
// default route values
new {
controller = "ApiController",
action = "MyAction",
},
// constraints placed on parameters (make sure they are valid)
new {
version = #"^\d+$", // number only (can also include decimals)
format = #"^(json|text|xml)$", // if you want filtering...
}
);
Then:
public ApiController : Controller
{
public ActionResult MyAction(Int32 version, String format, String purpose)
{
throw new NotImplementedException();
}
}

How can I set up a simple route with areas in ASP.NET MVC3?

I want to use Areas so I set up the following:
public class ContentAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Content";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Content_default",
"Content/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
}
What I would like is for a person who enters the following URL to be directed to a controller inside my Content area.
www.stackoverflow.com/Content/0B020D/test-data
I would like a person entering any URL with "/Content/" followed by six characters to be sent to:
- Page action in a controller named ItemController
- Six characters passed as the parameter id
- Optional text after that (test-data in this case) to be put into parameter title
How can I do this? I am not very familiar with setting up routes when using areas.
the six digits to be put into a variable called ID
So you're looking for something like
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Content_default",
"Content/{id}/{optional}",
new { controller = "ItemController", action = "TheActionYouWantThisToAllRouteTo" }
}
This would default everything to one controller and action method (which you have to specify in your instance). You can then get the data like so:
public ActionResult TheActionYouWantThisToAllRouteTo (string id, string optional)
{
// Do what you need to do
}
The way the routes are setup, you can name the pieces of information you want in a URL by wrapping it in a pair of { } curly braces. If you'd rather the name of optional to be isTestData then you would just change the route to read "Content/{id}/{isTestData}".
Note: Since you didn't specify the default action method you want this to route to, I substituted it with TheActionYouWantThisToAllRouteTo. Change that string to read the action method you want this to all go to. This also means you can't have a "regular" controller named ContentController, either.
Edit
Stephen Walther has a good blog post on custom route constraints. It can be found here. It should be a good start to get done what you need.

How to get the anchor of url in play's controller

Suppose I have a controller and an action:
public Questions extends BaseController {
public static void show(id) {
// how to get the anchor here??
}
}
A url with anchor like http://aaa.com/questions/show?id=123#555 will map to this action, and how can we get the anchor 555 in action show?
To add anchor to url, i use redirect method and Router class, so for your case :
public Questions extends BaseController {
public static void show(id) {
some stuff...
Map<String, Object> args = new HashMap<String, Object>();
args.put("id", questionID);
String url = Router.getFullUrl("Questions.show", args);
redirect(url + "#555");
}
}
If I understand you correctly, it is not possible to send the anchor part of a url back to the server as part of the http request. It is used locally only, for anchoring a page for intra page navigation.
See this post for more info Retrieving Anchor Link In URL for ASP.Net
However, if you want to add the anchor in your view, rather than using redirects and using the router in the controller, you can simply do it in the view (where it best belongs)
<a href='#{Questions.show(id)}#555'>link to question</a>

Resources