Dashboard and Gadgets
Enterprise Jira users often need to automatically generate dashboards with gadgets, to provide a consistent management-level overview of progress. These could be created either on demand, or automatically in response to version or project-created events.
Some gadgets take a JQL string, but others require a saved filter, so you may also need to generate one or more saved filters at the same time as creating a dashboard. Take care that you share both the dashboard and any dependent saved filters with your target users - in the example we share it globally.
The secret to perfecting this is (as always) to start small and work with a simple script in the Script Console. Once that’s working you can wire it to events such as version created/resolved etc using a Script Listener.
Start by creating a template dashboard using the normal UI. When you are happy you can run the following script, which will dump the configuration of the dashboard to the console. Make sure you start with only a single gadget to keep things simple.
import com.atlassian.jira.bc.JiraServiceContext
import com.atlassian.jira.bc.JiraServiceContextImpl
import com.atlassian.jira.bc.portal.PortalPageService
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.portal.PortletConfiguration
import groovy.json.JsonOutput
import groovy.xml.MarkupBuilder
def user = ComponentAccessor.jiraAuthenticationContext.getLoggedInUser()
JiraServiceContext sourceUserServiceCtx = new JiraServiceContextImpl(user)
def portalPageService = ComponentAccessor.getComponent(PortalPageService)
def dashId = 10203L // <1>
def output = portalPageService.getPortletConfigurations(sourceUserServiceCtx, dashId).collect { col ->
col.collect { PortletConfiguration gadget ->
[
row : gadget.row,
column : gadget.column,
color : gadget.color,
openSocialSpecUri: gadget.openSocialSpecUri.getOrNull().toString(),
completeModuleKey: gadget.completeModuleKey.getOrNull().toString(),
userPrefs : gadget.userPrefs,
]
}
}
def prettyOutput = JsonOutput.prettyPrint(JsonOutput.toJson(output))
log.debug(prettyOutput)
def writer = new StringWriter()
def builder = new MarkupBuilder(writer)
builder.pre(prettyOutput)
writer
Line 13: Change the dashboard ID to the one you want to inspect
Now, plug those values into the script below, and execute:
import io.atlassian.fugue.Option
import com.atlassian.gadgets.dashboard.Color
import com.atlassian.gadgets.dashboard.Layout
import com.atlassian.jira.bc.JiraServiceContext
import com.atlassian.jira.bc.JiraServiceContextImpl
import com.atlassian.jira.bc.portal.PortalPageService
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.search.SearchRequest
import com.atlassian.jira.issue.search.SearchRequestManager
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.portal.PortalPage
import com.atlassian.jira.portal.PortletConfigurationManager
import com.atlassian.jira.sharing.SharedEntity
def user = ComponentAccessor.jiraAuthenticationContext.getLoggedInUser()
JiraServiceContext sourceUserServiceCtx = new JiraServiceContextImpl(user)
def portalPageService = ComponentAccessor.getComponent(PortalPageService)
def portletConfigurationManager = ComponentAccessor.getComponent(PortletConfigurationManager)
def searchRequestManager = ComponentAccessor.getComponent(SearchRequestManager)
def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
def dashboardTemplate = new PortalPage.Builder()
.name("My new dash")
.description("Info....")
.owner(user)
.layout(Layout.AA)
.permissions(SharedEntity.SharePermissions.GLOBAL) // <1>
.build()
def query = jqlQueryParser.parseQuery("project = JRA") // <2>
// share filter globally
def templateSearchRequest = new SearchRequest(query, user, "JRA issues", "my description")
templateSearchRequest.setPermissions(SharedEntity.SharePermissions.GLOBAL)
def newSearchRequest = searchRequestManager.create(templateSearchRequest)
// note that portal page names must be unique per user
if (!portalPageService.validateForCreate(sourceUserServiceCtx, dashboardTemplate)) {
return sourceUserServiceCtx.errorCollection
}
def targetDash = portalPageService.createPortalPage(sourceUserServiceCtx, dashboardTemplate)
portletConfigurationManager.addDashBoardItem(targetDash.id, 0, 0, // <3>
Option.some(URI.create('rest/gadgets/1.0/g/com.atlassian.jira.gadgets:assigned-to-me-gadget/gadgets/assigned-to-me-gadget.xml')),
Color.color1,
[
'isConfigured': 'true', // <4>
'refresh' : '15',
'sortColumn' : '',
'columnNames' : 'issuetype|issuekey|summary|priority',
'num' : '10'
],
Option.none()
)
portletConfigurationManager.addDashBoardItem(targetDash.id, 1, 0,
Option.some(URI.create('rest/gadgets/1.0/g/com.atlassian.jira.gadgets:filter-results-gadget/gadgets/filter-results-gadget.xml')),
Color.color2,
[
"filterId" : newSearchRequest.id.toString(), // <5>
'isConfigured': 'true',
'refresh' : 'false',
"isPopup" : "false",
'columnNames' : 'issuetype|issuekey|summary|priority',
'num' : '10'
],
Option.none() // <6>
)
log.debug("Created dashboard: $targetDash.id")
Line 28: Shared globally, change to suit
Line 31: Sample query - you could plug in versions from a VersionCreated event for instance
Line 45: Numbers are column and row position
Line 49: User configuration for the gadget - take from the dumped configuration
Line 62: Generated search request
Line 69: Module key - empty for built-in gadgets
This will create a new dashboard with two configured gadgets. You should end up with a new dashboard:
Jira doesn’t like it if you generate multiple dashboards for the same user with the exact same name. When testing delete the dashboard after each run.