Hi all,
I've written a soy template that I'd like to validate before I call doPost. Is there something available in a built-in library that can perform the validation for me (ie: required fields)? And if so, how do I need to modify my atlassian-plugin.xml file and where can I find an example of a validation. If this is not an option, the closest match that I could find on how to include js properly was here - Is there a way to use the existing soy to display an error message?
Also, assuming there was an error processing the request in the Java backend, is there a way to display an error using the soy temlpate?
MainServlet.java
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.atlassian.sal.api.pluginsettings.PluginSettingsFactory; import com.atlassian.soy.renderer.SoyException; import com.atlassian.soy.renderer.SoyTemplateRenderer; import com.atlassian.stash.hook.repository.AsyncPostReceiveRepositoryHook; import com.atlassian.stash.hook.repository.RepositoryHookContext; import com.atlassian.stash.repository.RefChange; import com.atlassian.stash.repository.Repository; import com.atlassian.stash.repository.RepositoryCloneLinksRequest; import com.atlassian.stash.repository.RepositoryService; import com.atlassian.stash.setting.RepositorySettingsValidator; import com.atlassian.stash.setting.Settings; import com.atlassian.stash.setting.SettingsValidationErrors; import com.atlassian.stash.util.NamedLink; import com.google.common.collect.ImmutableMap; import javax.annotation.Nonnull; import javax.servlet.*; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Collection; import java.util.Map; import java.util.Set; @SuppressWarnings({"serial", "unused"}) public class MainServlet extends HttpServlet implements AsyncPostReceiveRepositoryHook, RepositorySettingsValidator{ private static final Logger log = LoggerFactory.getLogger(MainServlet.class); private final RepositoryService repositoryService; private final PluginSettingsFactory pluginSettingsFactory; protected final SoyTemplateRenderer soyTemplateRenderer; protected final String completeModuleKey = "org.lds.alm.anthillbuild:anthill-soy-templates"; private boolean debug = true; public MainServlet(SoyTemplateRenderer soyTemplateRenderer, RepositoryService repositoryService, PluginSettingsFactory pluginSettingsFactory){ this.repositoryService = repositoryService; this.pluginSettingsFactory = pluginSettingsFactory; this.soyTemplateRenderer = soyTemplateRenderer; } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String[] components = req.getPathInfo().split("/"); if(components.length < 3){ if(debug) resp.sendError(HttpServletResponse.SC_NOT_FOUND, "components.length < 3"); else resp.sendError(HttpServletResponse.SC_NOT_FOUND); return; } Repository repository = repositoryService.getBySlug(components[1], components[2]); if(repository == null){ if(debug) resp.sendError(HttpServletResponse.SC_NOT_FOUND, "repository == null"); else resp.sendError(HttpServletResponse.SC_NOT_FOUND); return; } //Get SSH URL Set<NamedLink> lnk = repositoryService.getCloneLinks(new RepositoryCloneLinksRequest.Builder().repository(repository).protocol("ssh").build()); String cloneUrl = lnk.iterator().next().getHref(); String templateName = "anthill.ui.main"; render(resp, templateName, ImmutableMap.<String, Object> builder() .put("repository", repository) .put("ssh", cloneUrl) .put("projectName", "(projectName here)") .build() ); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("doPost"); doGet(req, resp); } protected void render(HttpServletResponse resp, String templateName, Map<String, Object> data) throws IOException, ServletException { render(resp, templateName, data, null); } protected void render(HttpServletResponse resp, String templateName, Map<String, Object> data, Map<String, Object> injectedData) throws IOException, ServletException{ resp.setContentType("text/html;charset=UTF-8"); try { if(injectedData == null){ soyTemplateRenderer.render(resp.getWriter(), completeModuleKey, templateName, data); } else{ soyTemplateRenderer.render(resp.getWriter(), completeModuleKey, templateName, data, injectedData); } } catch (SoyException e) { Throwable cause = e.getCause(); if (cause instanceof IOException) { throw (IOException) cause; } throw new ServletException(e); } } @Override public void validate(Settings settings, SettingsValidationErrors errors, Repository repository) { errors.addFormError("testing errors"); if(settings.getString("projectName").equalsIgnoreCase("value")) errors.addFieldError("projectName", "project name equals value"); } @Override public void postReceive(RepositoryHookContext arg0, Collection<RefChange> arg1) { System.out.println(""); } }
Anthill.soy
{namespace anthill.ui} /** * UI for Anthill/Stash * @param repository Repository * @param ssh Link * @param? errors Errors (if applicable) */ {template .main} <html> <head> <meta name="decorator" content="stash.repository.settings"> <meta name="repositorySlug" content="{$repository.slug}"> <meta name="projectKey" content="{$repository.project.key}"> <meta name="activeTab" content="repository-settings-plugin-tab"> </head> <title> {$ssh} </title> <body> <h2>My Form</h2> <div class="aui"> {call aui.form.form} {param action: ''/} {param content} {call aui.form.textField} {param id: 'projectName' /} {param value: 'Value' /} {param labelContent: 'Project Name' /} {param isRequired: true /} {param extraClasses: 'long' /} {param errorTexts: $errors ? 'there was an error' : null /} {/call} {call aui.form.submit} {param id: 'submit' /} {param type: 'submit' /} {param text: 'Create' /} {/call} {/param} {/call} </div> </body> </html> {/template}
Community moderators have prevented the ability to post new answers.
Not sure if I get the question right, but if you simply want to make sure that required fields are filled out before the form can be submitted, you can do it with
{param isRequired: 'true' /}
in the .soy file, like this:
{namespace com.tekla.stash.myplugin} /** * @param config * @param? errors */ {template .formContents} {call aui.form.textField} {param id: 'project' /} {param value: $config['project'] /} {param labelContent} {stash_i18n('com.tekla.stash.myplugin.config.label', 'Project name')} {/param} {param isRequired: 'true' /} {param descriptionText: stash_i18n('com.tekla.stash.myplugin.config.description', 'Description.') /} {param extraClasses: 'long' /} {param errorTexts: $errors ? $errors['project'] : null /} {/call} {call aui.form.textField} {param id: 'repo' /} {param value: $config['repo'] /} {param labelContent} {stash_i18n('com.tekla.stash.myplugin.config.label', 'Repo name')} {/param} {param isRequired: 'true' /} {param descriptionText: stash_i18n('com.tekla.stash.myplugin.config.description', 'Description.') /} {param extraClasses: 'long' /} {param errorTexts: $errors ? $errors['repo'] : null /} {/call} {/template}
@BalazsYou've hit it right on the head. I wasn't aware of the errorTexts param. The next question I'd have to ask is how to add content to the errors param? Is there a Java function provided by Stash that I can use to add custom errors and then display them? I recently stumbled across this question which is exactly what I'm trying to do but I don't understand how to get the errors back to the form.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You do it with SettingsValidationErrors.addFormError and SettingsValidationErrors.addFieldError, like this:
/** * Validates the parent URL on the configuration form. * @param settings contains the values from the config form * @param errors validation errors need to be put into this object * @param repository the repo for which the hook is being configured */ @Override public void validate(@Nonnull Settings settings, @Nonnull SettingsValidationErrors errors, @Nonnull Repository repository) { String projectName = settings.getString("project"); String repoName = settings.getString("repo"); if (null == projectName) { errors.addFormError("The project field is not defined on the config form."); return; } if (null == repoName) { errors.addFormError("The repo field is not defined on the config form."); return; } if (projectName.isEmpty()) { errors.addFieldError("project", "Please supply a project name."); } if (repoName.isEmpty()) { errors.addFieldError("repo", "Please supply a repo name."); } Project proj = projectService.getByName(projectName); if (null == proj) { errors.addFieldError("project", "There is no such project."); } else if (null == repositoryService.getBySlug(proj.getKey(), repoName)) { errors.addFieldError("repo", "There is no such repo in the given project."); } }
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You do it with SettingsValidationErrors.addFormError and SettingsValidationErrors.addFieldError, like this:
public void validate(@Nonnull Settings settings, @Nonnull SettingsValidationErrors errors, @Nonnull Repository repository) { String projectName = settings.getString("project"); String repoName = settings.getString("repo"); if (null == projectName) { errors.addFormError("The project field is not defined on the config form."); return; } if (null == repoName) { errors.addFormError("The repo field is not defined on the config form."); return; } if (projectName.isEmpty()) { errors.addFieldError("project", "Please supply a project name."); } if (repoName.isEmpty()) { errors.addFieldError("repo", "Please supply a repo name."); } Project proj = projectService.getByName(projectName); if (null == proj) { errors.addFieldError("project", "There is no such project."); } else if (null == repositoryService.getBySlug(proj.getKey(), repoName)) { errors.addFieldError("repo", "There is no such repo in the given project."); } }
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
So I'll have to imlpement the Settings, SettingsValidationErrors, Repository, and RepositorySettingsValidator interfaces - correct? Once I go through that mess, do I just call RepositorySettingsValidator.validate in my doPost inside of my repository servlet?
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
It is simpler than that. Your class has to implement RepositorySettingsValidator and override the validate method. I updated the code snippet above, the @override and the XML comments were missing. The validate method will be called by Stash at the right time, no need to worry about that.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
@BalazsSorry, I was out of town for the weekend.
I created a class that implements RepositorySettingsValidator (below) but when my doPost is called from my servlet class, validate is never called. I included a validator in my atlassian-plugin.xml but unfortunately that didn't solve the problem.
atlassian-plugin.xml
<servlet name="Main Servlet" i18n-name-key="main-servlet.name" key="main-servlet" class="com.example.servlet.MainServlet"> <description key="main-servlet.description">The Main Servlet Plugin</description> <url-pattern>/mainservlet/*</url-pattern> <validator>com.example.servlet.Validation</validator> </servlet>
Validation.java
package com.example.servlet; import javax.annotation.Nonnull; import com.atlassian.stash.repository.Repository; import com.atlassian.stash.setting.RepositorySettingsValidator; import com.atlassian.stash.setting.Settings; import com.atlassian.stash.setting.SettingsValidationErrors; public class Validation implements RepositorySettingsValidator{ /** * Validates the parent URL on the configuration form. * @param settings contains the values from the config form * @param errors validation errors need to be put into this object * @param repository the repo for which the hook is being configured */ @Override public void validate(@Nonnull Settings settings, @Nonnull SettingsValidationErrors errors, @Nonnull Repository repository) { errors.addFormError("testing errors"); //test error if(settings.getString("projectName").equalsIgnoreCase("value")) errors.addFieldError("projectName", "project name equals value"); } }
I know I'm missing something small - I'm just not sure what it is.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
The RepositorySettingsValidator interface has to be implemented by your main plugin class, not a separate one:
public class MyClass implements AsyncPostReceiveRepositoryHook, RepositorySettingsValidator { ... @Override public void validate(@Nonnull Settings settings, @Nonnull SettingsValidationErrors errors, @Nonnull Repository repository) { ... } }
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Honestly, this is becoming very frustrating. I don't understand how the validation works via creating your own form as a servlet. Above I've included the contents of my servlet class as it stands as well as the soy file. I've inserted breakpoints in the validate and postReceive functions and they're never called. Is what I'm trying to accomplish even possible?
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Try checking your code against this example. Something is not wired together correctly.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Oh ok, I am not sure about stash.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Thank you for the quick response Yagnesh. I read through the documentation and it's good for Confluence, but can I follow the same tutorials for Stash? I added a line for the js file in my atlassian-plugin.xml (below) and the AJS.toInit method to my main.js file but it's not called when the page loads.
atlassian-plugin.xml (Stash):
<web-resource key="anthill-resources" name="Anthill Build Web Resources"> <dependency>com.atlassian.auiplugin:ajs</dependency> <transformation extension="soy"> <transformer key="soyTransformer" /> </transformation> <resource type="download" name="main.js" location="js/main.js" /> <resource type="download" name="anthill.soy.js" location="/templates/anthill.soy"/> <!-- --> </web-resource>
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi Josh,
The soy templates are actually rendered as javascript in the atlassian-plugin.xml, which means they are actually defined as a javascript file in the web-resource element, like so
<resource type="download" name="someform.soy.js" location="/soy/someform.soy" />
With that being said, confluence provides APIs for doing some stuff whenever the soy template is rendered. This is mostly used in context of blueprint wizards:
https://developer.atlassian.com/display/CONFDEV/Javascript+API+for+blueprint+wizards
As given in the doc above, you can do the validation in the submit method of the blueprint. To sum up you can do the following steps (Refer teh doc for more info)
1) Create a javascript file and define it in your web-resource.
2) The basic structure of your js would be like this:
AJS.toInit(function($) { function postRender(e, state) { } function validate() { } function submit(e, state) { // Call validate from here } //Do Confluence.Blueprint.setWizard here });
Also if you want to do any server validation, u can do it via AJAX calls in the body of validate function provided in the code snippet above. One common way to do that would be to configure your own Action classes. More on that here :
https://developer.atlassian.com/display/CONFDEV/Adding+a+Custom+Action+to+Confluence
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Community moderators have prevented the ability to post new answers.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.