How does atlassian format workflows programatically?

Felix Lahmer November 15, 2017

Hey there,

i am importing workflows via my own scriptrunner restendpoints (including xml-parsing, statusmapping etc.) programatically into an jira instance.

So far works like a charm. Problem is the visual workflow designer displays all the status and transitions nearly unreadable under each other.

I was wondering if i could somehow programatically format them.

I came across the fact that the jira default workflow (atlassian-jira/WEB-INF/classes/jira-workflow.xml) is formatted well, but that there are no styling information in the xml file. Because of that i searched the whole atlassian-jira sources, but could not find anything that does that formatting.

If anyone knows how-/where it´s done, please share.

2 answers

1 accepted

2 votes
Answer accepted
Patrick Hobusch February 12, 2018

If you have the JIRA Sources (I'm currently working with atlassian-jira-software-7.6.1-source), have a look at the following files:

  • jira-project/jira-components/jira-core/src/main/osworkflow/jira-workflow.xml
  • jira-project/jira-components/jira-core/src/main/java/com/atlassian/jira/upgrade/tasks/UpgradeTask_Build6123.java
  • jira-project/jira-components/jira-core/src/main/resources/system-classic-workflow-layout.json

They are mapping a JSON layout file on a XML workflow file...

If I will manage to get the JSON for an existing workflow, I will let you know. 

Patrick Hobusch February 12, 2018

So, here we go:

/**
* Create a workflow based on an exported workflow XML file.
*
* You can provide a layout file in layout format v5 with the following pattern:
* - my-workflow.xml (workflow XML file)
* - my-workflow-format.json (workflow JSON format file)
*
* @param name the workflow name
* @param description the workflow description
* @param pathXml the path to the workflow xml file
* @return the created workflow
*/
@Nullable
public JiraWorkflow createWorkflowFromXML(
@Nonnull final String name,
@Nonnull final String description,
@Nullable final String pathXml) {

if (pathXml == null || pathXml.isEmpty() || !pathXml.endsWith(".xml")) {
return null;
}

final String xml = readFile(pathXml);
if (xml == null) {
return null;
}

final String layoutKey = DigestUtils.md5Hex(name);
final String pathFormatJson = pathXml.replace(".xml", "-format.json");
final String formatJson = readFile(pathFormatJson);

try {
final WorkflowDescriptor descriptor = WorkflowUtil.convertXMLtoWorkflowDescriptor(xml);
final ConfigurableJiraWorkflow workflow = new ConfigurableJiraWorkflow(name, descriptor, workflowManager);
workflow.setDescription(description);
workflowManager.createWorkflow(authenticationContext.getLoggedInUser(), workflow);

eventPublisher.publish(new WorkflowImportedFromXmlEvent(workflow));

if (formatJson != null) {
final Map<String, Object> entryFields = ImmutableMap.of(
"entityName", "com.atlassian.jira.plugins.jira-workflow-designer",
"entityId", 1,
"propertyKey", "jira.workflow.layout.v5:" + layoutKey,
"type", 6);
final Long layoutId = ofBizDelegator.createValue("OSPropertyEntry", entryFields).getLong("id");
final Map<String, Object> textFields = ImmutableMap.of(
"id", layoutId,
"value", formatJson);
ofBizDelegator.createValue("OSPropertyText", textFields);
}

return workflow;
} catch (FactoryException e) {
String message = "Error parsing workflow XML: " + e.getMessage();
log.error(message, e);
} catch (WorkflowException e) {
String message = "Error saving workflow: " + e.getMessage();
log.error(message, e);
}

return null;
}

/**
* Find the layout for a designed workflow.
*
* This json layout file can be used to format a workflow that is exported as XML
*
* @param name the workflow name
* @return the json layout
*/
@Nullable
public String getWorkflowFormatJson(
@Nonnull final String name) {

String layoutKey = DigestUtils.md5Hex(name);

Map<String, String> filter = ImmutableMap.of(
"entityName", "com.atlassian.jira.plugins.jira-workflow-designer",
"propertyKey", "jira.workflow.layout.v5:" + layoutKey);

Optional<Long> layoutId = ofBizDelegator.findByLike("OSPropertyEntry", filter).stream()
.map(gv -> gv.getLong("id"))
.findFirst();

if (layoutId.isPresent()) {
GenericValue osPropertyText = ofBizDelegator.findById("OSPropertyText", layoutId.get());

if (osPropertyText != null) {
return (String) osPropertyText.get("value");
}
}

return null;
}

@Nullable
private String readFile(String filename) {
InputStream stream = getClass().getClassLoader().getResourceAsStream(filename);

try {
return IOUtils.toString(stream, "utf-8");
} catch (IOException e) {
log.error("Could not read file" + filename, e);
} finally {
IOUtils.closeQuietly(stream);
}

return null;
}

Note that replaced some custom stuff and did not test the code again. Also note that there are different format versions for the workflows and this code currently only works with version v5.

Cheers

Like Alain Lompo likes this
Matt Doar
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
February 12, 2018

That's handy!

Felix Lahmer March 1, 2018

Thanks @Patrick Hobusch for sharing your knowledge. I will give it a shot. 

Katherine Hagood January 26, 2021

What is the eventPublisher variable that you are using?

Katherine Hagood January 27, 2021

I was able to get an event publisher by doing the following

import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.event.api.EventPublisher

EventPublisher eventPublisher = ComponentLocator.getComponent(EventPublisher)
eventPublisher.publish(new WorkflowImportedFromXmlEvent(workflow)) 

 Using that I was able to create the workflow successfully with the formatting applied

0 votes
Matt Doar
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
November 15, 2017

I've never seen a way to format the workflow diagram programmatically

Suggest an answer

Log in or Sign up to answer