ScriptRunner how to add comment editor to fragment dialog

Zhiyong Liu October 30, 2017

I have a Web Item added by scripted fragment to open up a dialog. How can I add a Rich Text Editor for comment input to the dialog? For example, the editor you see after you click the Assign user button. 

Thanks in advance for any tip.  

4 answers

3 votes
Josh Nystrom March 8, 2018

Hi Zhiyong,

Our team just finalized a working version of Rich Text in a custom dialog, so hopefully by sharing it here we can help you, or any others seeking this functionality.

There may still be attributes that can be removed from the markup, but, so far, this is as clean as I've managed while retaining good function. Here is the REST Endpoint code+markup:

import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import groovy.transform.BaseScript

import javax.ws.rs.core.MediaType
import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.config.properties.APKeys

@BaseScript CustomEndpointDelegate delegate

richTextCommentDialog() { MultivaluedMap queryParams ->
String issueId = queryParams.getFirst("issueId")
String issueKey = queryParams.getFirst("issueKey")
String baseUrl = ComponentAccessor.getApplicationProperties().getString(APKeys.JIRA_BASEURL)

def dialog =
"""
<div id="rt-dialog" class="jira-dialog box-shadow popup-width-custom" data-aui-modal="true" data-aui-remove-on-hide="true" style="width: 810px; margin-left: -406px; margin-top: -434.5px;">
<div class="jira-dialog-heading">
<h2 title="Rich-text comment on: $issueKey">Rich-text comment on: $issueKey</h2>
</div>
<div class="field-group aui-field-wikiedit comment-input">
<div class="jira-wikifield" field-id="comment" renderer-type="atlassian-wiki-renderer" issue-key="$issueKey" resolved="">
<div class="wiki-edit">
<div id="comment-wiki-edit" class="wiki-edit-content">
<textarea class="textarea long-field wiki-textfield mentionable wiki-editor-initialised" cols="60" id="commentText" name="commentText" wrap="virtual" data-issuekey="$issueKey" resolved="" style="min-height: 174px; max-height: 540px;"></textarea>
<div class="rte-container">
<rich-editor contenteditable="true"></rich-editor>
</div>
</div>
</div>
<div class="field-tools">
<button class="jira-icon-button fullscreen wiki-preview" id="comment-preview_link" type="button">
</button>
</div>
</div>
<div class="save-options wiki-button-bar">
</div>
<div class="buttons-container form-footer">
<div class="buttons"><span class="icon throbber"></span><button id="rt-dialog-submit" class="aui-button">Submit</button><button id="rt-dialog-cancel" class="aui-button aui-button-link">Cancel</button>
</div>
</div>
</div>

<script>
AJS.dialog2("#rt-dialog").on("show", function() {
document.getElementById('commentText').focus();
});

AJS.\$("#rt-dialog-submit").click(function(e) {
var text = AJS.\$("#commentText").val();
var comment = {"body": text
};
AJS.\$.ajaxSetup({
baseUrl: '""" + baseUrl + """'
});

AJS.\$("#rt-dialog-submit").before('<span class="aui-icon aui-icon-wait">Submitting...</span>');
AJS.\$("#rt-dialog-submit").prop("disabled", true);

AJS.\$.ajax({
url : '/rest/api/2/issue/""" + issueId + """/comment',
headers: { "Content-Type" : "application/json" },
type: 'POST',
data : JSON.stringify(comment),
cache : false,
success: function(response){
JIRA.trigger(JIRA.Events.REFRESH_ISSUE_PAGE, [JIRA.Issue.getIssueId()]);
JIRA.trigger("GH.RapidBoard.causeBoardReload");

var myFlag = AJS.flag({
type: 'success',
title: 'Comment added successfully',
close: 'auto'
});

AJS.dialog2("#rt-dialog").remove();
},
error: function(response){
console.log(JSON.stringify(response));
var myFlag = AJS.flag({
type: 'error',
title: 'Oh no!',
body: JSON.parse(response.responseText).errors.comment,
close: 'manual'
});

AJS.dialog2("#rt-dialog").remove();
}
});
});

AJS.\$("#rt-dialog-cancel").click(function(e) {
AJS.dialog2("#rt-dialog").remove();
});
</script>
</div>
"""
Response.ok().type(MediaType.TEXT_HTML).entity(dialog.toString()).build()
}

Things to note:

  • In the dialog definition, the attribute [data-aui-modal="true"] prevents easy closure of the dialog. Find that attribute on this Atlassian doc under the Options heading for more information - https://docs.atlassian.com/aui/7.3.3/docs/dialog2.html
  • The Strings 'issueId' and 'issueKey' are parameters passed in from the 'Run code and display dialog' fragment as follows:
    /rest/scriptrunner/latest/custom/richTextCommentDialog?issueId=${issue.id}&issueKey=${issue.getKey()}
  • The 'field-tools' div is not displayed, but seems to be a leftover from the previous comment model and is necessary for the dialog to be displayed successfully
  • One quirk we were encountering caused the removal of the 'Alt-S' Submit and 'Alt-~' Cancel functionality
  • We performed dialog .remove() instead of .hide() to ensure that the action can be performed multiple times without a hard refresh
  • Successful submissions cause a refresh of the current issue view or rapid board

We didn't pursue this just for kicks though. We have a client group that will be using this to easily add comments that have visibility restricted to a specific project role. Please see my follow-up post for the Script Fragment and REST endpoint that include Developer restricted button and comment visibility.

Hope this helps!

Josh

Zhiyong Liu March 8, 2018

Thanks very much Josh for sharing the solution. I will integrate it with my code. 

Josh Nystrom March 8, 2018

<removed multi-post>

Josh Nystrom March 8, 2018

<removed multi-post>

Josh Nystrom March 8, 2018

You're most welcome Zhiyong. Happy to help!

As mentioned, I have an expanded example that I'll post here as well (comment with 'comment visible to Developers', and visibility of the button restricted to that role).

Here is the Script Fragment:

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.project.ProjectManager
import com.atlassian.jira.security.roles.ProjectRoleManager
import com.atlassian.jira.security.roles.ProjectRoleActors

if (jiraHelper.project?.key != "<Your project's KEY here>")
return false

def currentUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()
ProjectManager projectManager = ComponentAccessor.getProjectManager()
ProjectRoleManager projectRoleManager = ComponentAccessor.getComponent(ProjectRoleManager)
ProjectRoleActors developers = projectRoleManager.getProjectRoleActors(projectRoleManager.getProjectRole("Developers"), issue.getProjectObject())
return developers.contains(currentUser)


And here is the REST Endpoint:

import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import groovy.transform.BaseScript

import javax.ws.rs.core.MediaType
import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.config.properties.APKeys

@BaseScript CustomEndpointDelegate delegate

addDeveloperPermissionedCommentDialog() { MultivaluedMap queryParams ->
String issueId = queryParams.getFirst("issueId")
String issueKey = queryParams.getFirst("issueKey")
String baseUrl = ComponentAccessor.getApplicationProperties().getString(APKeys.JIRA_BASEURL)

def dialog =
"""
<div id="dev-only-dialog" class="jira-dialog box-shadow popup-width-custom" data-aui-modal="true" data-aui-remove-on-hide="true" style="width: 810px; margin-left: -406px; margin-top: -434.5px;">
<div class="jira-dialog-heading">
<h2 title="Developer-only comment on: $issueKey">Developer-only comment on: $issueKey</h2>
</div>
<div class="field-group aui-field-wikiedit comment-input">
<div class="jira-wikifield" field-id="comment" renderer-type="atlassian-wiki-renderer" issue-key="$issueKey" resolved="">
<div class="wiki-edit">
<div id="comment-wiki-edit" class="wiki-edit-content">
<textarea class="textarea long-field wiki-textfield mentionable wiki-editor-initialised" cols="60" id="devCommentText" name="devCommentText" wrap="virtual" data-issuekey="$issueKey" resolved="" style="min-height: 174px; max-height: 540px;"></textarea>
<div class="rte-container">
<rich-editor contenteditable="true"></rich-editor>
</div>

</div>
</div>
<div class="field-tools">
<button class="jira-icon-button fullscreen wiki-preview" id="comment-preview_link" type="button">
</button>
</div>
</div>
<div class="save-options wiki-button-bar">
</div>
<div class="buttons-container form-footer">
<div class="buttons"><span class="icon throbber"></span><button id="dev-only-dialog-submit" class="aui-button">Submit</button><button id="dev-only-dialog-cancel" class="aui-button aui-button-link">Cancel</button>
</div>
</div>
</div>

<script>
AJS.dialog2("#dev-only-dialog").on("show", function() {
document.getElementById('devCommentText').focus();
});

AJS.\$("#dev-only-dialog-submit").click(function(e) {
var text = AJS.\$("#devCommentText").val();
var comment = {"body": text,
"visibility": {
"type": "role",
"value": "Developers"
}};

AJS.\$.ajaxSetup({
baseUrl: '""" + baseUrl + """'
});

AJS.\$("#dev-only-dialog-submit").before('<span class="aui-icon aui-icon-wait">Submitting...</span>');
AJS.\$("#dev-only-dialog-submit").prop("disabled", true);

AJS.\$.ajax({
url : '/rest/api/2/issue/""" + issueId + """/comment',
headers: { "Content-Type" : "application/json" },
type: 'POST',
data : JSON.stringify(comment),
cache : false,
success: function(response){
JIRA.trigger(JIRA.Events.REFRESH_ISSUE_PAGE, [JIRA.Issue.getIssueId()]);
JIRA.trigger("GH.RapidBoard.causeBoardReload");

var myFlag = AJS.flag({
type: 'success',
title: 'Comment added successfully',
close: 'auto'
});

AJS.dialog2("#dev-only-dialog").remove();
},
error: function(response){
console.log(JSON.stringify(response));
var myFlag = AJS.flag({
type: 'error',
title: 'Oh no!',
body: JSON.parse(response.responseText).errors.comment,
close: 'manual'
});

AJS.dialog2("#dev-only-dialog").remove();
}
});
});

AJS.\$("#dev-only-dialog-cancel").click(function(e) {
AJS.dialog2("#dev-only-dialog").remove();
});
</script>
</div>
"""
Response.ok().type(MediaType.TEXT_HTML).entity(dialog.toString()).build()
}

To change the target project role, just replace the two 'Developers' references.

Also, remember to point your 'Run code and display a dialog' Custom Web Item at

'/rest/scriptrunner/latest/custom/addDeveloperPermissionedCommentDialog?issueId=${issue.id}&issueKey=${issue.getKey()}'

 

We placed our button/fragment in the 'operations-top-level' section, weighted below the 'Assign' action.

Finally, we have this working as a Web Item on ScriptRunner 5.3.7, but on older versions, we were using a Web Item Provider like this:

import com.atlassian.jira.issue.Issue
import com.atlassian.jira.project.Project
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.util.velocity.VelocityRequestContextFactory
import com.atlassian.jira.security.roles.ProjectRoleManager
import com.atlassian.jira.security.roles.ProjectRoleActors
import com.atlassian.plugin.web.api.model.WebFragmentBuilder

Issue issue = (Issue)context.get('issue')
Project project = (Project)context.get('project')
def projects = ['<Your project KEY here>']
if (!projects.find{it == project?.getKey()})
return

def velocityRequestContextFactory = ComponentAccessor.getComponent(VelocityRequestContextFactory)
def baseUrl = velocityRequestContextFactory.getJiraVelocityRequestContext().getBaseUrl()
def currentUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()

ProjectRoleManager projectRoleManager = ComponentAccessor.getComponent(ProjectRoleManager)
ProjectRoleActors developers = projectRoleManager.getProjectRoleActors(projectRoleManager.getProjectRole("Developers"), issue.getProjectObject())
if(developers.contains(currentUser)
{
def fragments = []
fragments << new WebFragmentBuilder(50) //(50) is the weight
.id("dialog-provider")
.label("Dev-only comment")
.title("Dev-only comment")
.styleClass("sr-trigger-dialog")
.webItem("")
.url("${baseUrl}/rest/scriptrunner/latest/custom/addDeveloperPermissionedCommentDialog?issueId=${issue.id}&issueKey=${issue.getKey()}")
.build()
return fragments
}


Good luck, and cheers!

Josh

Like Héctor López likes this
0 votes
Josh Nystrom March 7, 2018

<removed multi-post>

0 votes
Josh Nystrom March 7, 2018

<removed multi-post>

0 votes
Jonny Carter
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
November 14, 2017

So, I've been looking into this, and I've not quite been able to divine how to use Jira's built-in rich text editor.

I'm pretty sure that the basics are:

1) Create a textarea element in your markup

2) Run some javascript that will invoke Jira's rich text editor

Obviously, the particulars of #2 evade me as yet.

https://developer.atlassian.com/jiradev/jira-platform/guides/rich-text-editor 

I expect it'll be something like 

require("jira/editor/somemodule").init( document.querySelector('textarea#mySpecialTextArea') );

...but I've not yet divined the right module or method, even after digging into Jira's source.

Jonny Carter
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
November 14, 2017

There's nothing in Atlassian's AUI docs, which makes me think it's not a simple matter of adding a class or ID to the text area. https://docs.atlassian.com/aui/latest/docs/forms.html

Suggest an answer

Log in or Sign up to answer