Updating a structure can be done through the same
ForestSource interface that was used for Reading Structure Content. In this article, we're assuming that you've got
forestSource local variable that you've created according to instructions in the previous article.
To make a change to a forest, you need to be able to point to a specific part of a forest. This is done by using row IDs, which uniquely identify forest rows.
- To point to a specific row in the forest, which you'd like to move or delete, you just use this row's ID.
- To point to a specific position in the forest, where you'd like to insert or move rows to, you need to use row IDs of its neighbors, or coordinates:
- "Under" coordinate is the row ID of the future parent of the inserted row, or zero if the row is placed at the top level.
- "After" coordinate is the row ID of the future preceding sibling of the inserted row under the same parent, or zero if the row is placed as the first child.
- "Before" coordinate is the row ID of the future succeeding sibling of the inserted row under the same parent, or zero if the row is placed as the last child.
Applying Forest Action
To make a change, you need to call
forestSource.apply() method, passing a specific
ForestAction that you want to apply.
Adding a single row
To add a single row to the forest, use
ForestAction.Add constructed with the
ItemIdentity of the item associated with that row.
Adding a sub-forest
To add multiple rows in one action, use
ForestAction.Add that receives an
ItemForest is a special container that is used to build a temporary forest with temporary rows, having negative row IDs. The class provides information both about the hierarchy of inserted temporary rows (via
Forest) and a mapping from the temporary row ID to the inserted
To create an
ItemForest, you need to use either
Removing a sub-tree
To remove a row, use
ForestAction.Remove and pass the row ID being removed.
All sub-rows of the removed rows will be removed as well. If you need to keep them, apply
ForestMove on them first.
Moving a sub-tree
To move a row with its sub-rows, use
You can specify one or more row IDs, which can be from the different parts of the forest. Those rows will be placed one after another at the specified position.
Inspecting the Results
A call to
ForestSource.apply() will finish successfully if the operation has been completed and throw a
You can inspect the returned
ActionResult to get information about the effects of the action (more on effects below).
You can also use
ActionResult.getRowIdReplacements() – it is a mapping from the temporary row IDs, used when adding rows, to the newly assigned real row IDs, which are now part of the structure.
Effects and Changing Dynamic Structures
You may have noticed that you can apply actions to any forest source, not necessarily a simple structure. It can be a transformed structure, or even a transformed query. A structure can also contain dynamic parts, created or adjusted by generators, and you can try to apply the actions that would affect these parts.
A successful action would produce one or more Effects (represented in the
AppliedEffect). In simple case of changing a non-dynamic structure, it would be, unsurprisingly, a structure change. In case the action involves dynamic content, the effects may differ – but the general concept is that, after the effect takes place, the updated (re-generated) structure will reflect the desired action's result.
Here are some examples of the possible effects.
|Adding rows to a static structure|
Structure is modified
|Moving item X from group A to group B, where groups are provided by a grouper by field F||The value of F for X is changed from A to B|
|Removing issue X from under issue Y, when previously X was added automatically by a Links Extender using link type L||Link L: Y→X is deleted|
|Moving issue upwards when structure is sorted by Agile Rank||Issue's Rank is changed|
|Adding an issue to an arbitrary JQL query result||StructureException is thrown – no way to force an issue to be part of a JQL result|
|Adding issue X under issue Y within the scope of a Links Extender and when issue Y is "static" (not added by the extender)||StructureInteractionException is thrown – there are two ways to interpret this action|
As generators are extensible and can be added by other plugins, the range of possible effects is not limited.
Note that in the last two examples the action is not successful. In the last example, you need to use
ForestSource.apply() with parameters, which would define whether a generator should process the action or if the issue should be inserted into the static structure.
Concurrency and Atomicity
ForestAction can be viewed as a separate transaction. It is atomic, meaning that it is either fully successful or fully failed.
There's no way to make a transaction larger. In other words, if you apply two actions to a forest source, it is possible that a concurrent action, done from another thread, is executed in between your two actions.
All actions are executed under the "current" user and with all necessary permission checks. Updating a structure requires
EDIT permission on the structure. Other effects, like changing issue fields, would require
EDIT_ISSUE permission on the subject issues.
When permissions are insufficient, the action will not succeed and a
StructureException will be thrown.
When it comes to effects applied by generators, it is a generator's responsibility to check permissions before applying an action. All generators bundled with Structure have strict permission checks.
The current user is generally managed by JIRA and is the same as the user who makes the request. However, you can use
StructureAuth class to "sudo" to another user or to bypass permission checks altogether.