Web-panel duplicates after event

I have a plugin that is able to detect when an issue is created or updated and do some stuff then it saves the result using active objects and is displayed with a web panel.

The problem occurs when I open a issue with the result already calculated and stored. The plugin correctly displays the result but when the issue is updated and the plugin recalculates the result the error occurs.

The original message from when the issue was opened is displayed after the new updated message. This problem only occurs when I make the web panel headless. If I update the issue again then it replaces the other updated message so no matter how many times I update the issue the original message from the issue was opened by the user stays at the end.

I can’t show all my code but here is some that seem relevant and if anything else is needed I will try to provide it.


<?xml version="1.0" encoding="UTF-8"?>

<atlassian-plugin key="${atlassian.plugin.key}" name="${project.name}" plugins-version="2"> 
    <vendor name="${project.organization.name}" url="${project.organization.url}"/>  
    <param name="plugin-icon">images/pluginIcon.png</param>  
    <param name="plugin-logo">images/pluginLogo.png</param> 
  <!-- add our i18n resource -->  
  <resource type="i18n" name="i18n" location="quality-listener-plugin"/>  
  <!-- add our web resources -->  
  <web-resource key="quality-listener" name="Quality Listener">
    <description>The Listener module for this plugin.</description>
    <resource type="download" name="quality-listener-plugin.css" location="/css/quality-listener-plugin.css"/>  
    <resource type="download" name="quality-listener-plugin.js" location="/js/quality-listener-plugin.js"/>  
    <resource type="download" name="images/" location="/images"/>  
  <web-panel name="Quality Feedback" i18n-name-key="quality-feedback.name" key="quality-feedback" location="atl.jira.view.issue.left.context" weight="201"> 
    <description key="quality-feedback.description">The UI module for this plugin.</description>  
    <context-provider class="com.XXX.plugins.listener.QualityFeedback"/>  
    <resource name="view" type="velocity" location="templates/quality-feedback.vm"/>
    <label key="quality-feedback.title"/>
    <param name="headless" value="true"/>
  <ao key="ao-module" name="Quality Storage">
    <description>The AO module for this plugin.</description>
  <servlet name="Quality Servlet" key="quality-servlet" class="com.XXX.plugins.listener.QualityServlet">
    <description>The Testing Servlet for this plugin. FOR TESTING USE ONLY</description>

Event Listener:

public class IssueCreatedResolvedListener implements InitializingBean, DisposableBean {
    private static final Logger log = LoggerFactory.getLogger(IssueCreatedResolvedListener.class);

    private final EventPublisher eventPublisher;

    private final QualityService qualityService;

    public IssueCreatedResolvedListener(EventPublisher eventPublisher, QualityService qualityService) {
        this.eventPublisher = eventPublisher;
        this.qualityService = checkNotNull(qualityService);

    public void afterPropertiesSet() throws Exception {
        log.info("Enabling Plugin");

    public void destroy() throws Exception {
        log.info("Killing Plugin");

    public void onIssueEvent(IssueEvent issueEvent) throws Exception {
        Long eventTypeID = issueEvent.getEventTypeId();
        Issue issue = issueEvent.getIssue();
            setQuality(getFields(null, issue), issue);
        else if(eventTypeID.equals(EventType.ISSUE_UPDATED_ID)) {
            List changeItems = issueEvent.getChangeLog().getRelated("ChildChangeItem");
            Iterator iter = changeItems.iterator();
            String request = getFields(iter, issue);
            if(request == null)
            setQuality(request, issue);

    public void setQuality(String request, Issue issue) throws Exception {
            log.info("{} to be evaluated.", request);
            try {
                URL url = new URL("");
                HttpURLConnection con = (HttpURLConnection) url.openConnection();

                String urlParameters = "details=" + request;
                DataOutputStream wr = new DataOutputStream(con.getOutputStream());

                BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
                String inputLine;
                StringBuffer response = new StringBuffer();
                while((inputLine = in.readLine()) != null) {
                qualityService.addUpdate(issue.getId(), Integer.parseInt(response.toString()));
                log.info("Issue {} has the quality {}.", issue.getId(), response.toString());
            catch(Exception e)
                log.error("Connection could not be established for event occurring on Issue {}.", issue.getId());

    public String getFields(Iterator iter, Issue issue) {
        String request = null;
        if(iter == null){
            request = issue.getDescription() + " " + (String) issue.getCustomFieldValue(ComponentAccessor.getCustomFieldManager().getCustomFieldObjectByName("Incident Resolution Comment"));
            return request;
        while(iter.hasNext()) {
            Map item = (Map) iter.next();
            if(item.get("field").equals("description") || item.get("field").equals("Incident Resolution Comment")) {
                request = issue.getDescription() + " " + (String) issue.getCustomFieldValue(ComponentAccessor.getCustomFieldManager().getCustomFieldObjectByName("Incident Resolution Comment"));
        return request;

Note: This problem occurs on any update despite nothing happening if the description or incident resolution comment isn’t the cause of the update.

Web Panel:

public class QualityFeedback extends AbstractJiraContextProvider {
    private static final Logger log = LoggerFactory.getLogger(IssueCreatedResolvedListener.class);

    private final QualityService qualityService;

    public QualityFeedback(QualityService qualityService) {
        this.qualityService = checkNotNull(qualityService);

    public Map<String, Object> getContextMap(ApplicationUser applicationUser, JiraHelper jiraHelper) {
        Map<String, Object> contextMap = new HashMap<>();
        Issue currentIssue = (Issue) jiraHelper.getContextParams().get("issue");
        List<QualityEntity> entities = qualityService.find(currentIssue.getId());
        log.debug("{}", entities);
        if(entities.size() == 1)
            contextMap.put("Quality", entities.get(0).getQuality());
        return contextMap;

Note: There should only ever be 1 entity for each issue and this is true so I don’t believe this issue is related to the active objects.

Velocity template:

#if ($Quality)
    #if ($Quality == 1)
        <span style="font-weight: bold; color: green;">This issue is high quality.</span>
    #elseif ($Quality == 0)
        <span style="font-weight: bold; color: red;">This issue is low quality. Add more details to the description or resolution if necessary.</span>

I have the same problem. Can you let me know if wrapping your VM content in a <div> helps? I suspect that Jira is trying to find and delete our panel so it can load in fresh content but can’t find a handle.

No wrapping in <div> didn’t help. The issue still occurs however with div the lines are separate instead of being next to each other.

I noticed something interesting I added back the head and when I checked the issue the text that was there had no head but after updating the description of the issue the new result appeared and the old result also stayed. The new result had the head like expected which means that the web panel displaying the old result is not created when opening the issue like I expected but is stored with the issue. I don’t know how to fix this however just something I noticed which appears to be the cause. Why this happens without the head and doesn’t occur with the head is still a mystery to me though.

I was able to solve this issue however I am unsure if this is a work around to a mistake I made elsewhere or a bug with headless web panels. Anyways I noticed that without a header the web panel was only a span whereas with a header the web panel was wrapped in a div. I tried as @sfbehnke suggested to wrap the vm with a div and even with the div containing the same id that was used by the div with the header enabled it still would duplicate the original result. I didn’t realize that the div needs to include module in the class as well once I added the div with the key of the web panel as the id and the class as module then the web panel stopped being duplicated.

1 Like

Nice! Thanks! I am totally hitting this while using a headless one too. I’ll see if it works for me too. Thanks again!

Stumbled on this again, confirming that using an DIV with an ID and the class “module” works fine for headless panels.