Changing Structure Content
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.
Forest Coordinates
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.
forestSource.apply(new ForestAction.Add(CoreIdentities.issue(10000), under, after, before))
Adding a sub-forest
To add multiple rows in one action, use ForestAction.Add
that receives an ItemForest
.
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 ItemIdentity
.
To create an ItemForest
, you need to use either ImmutableItemForest
or ItemForestBuilderImpl
.
ItemForest itemForest = new ItemForestBuilderImpl()
.nextRow(CoreIdentities.textFolder("My Issues"))
.nextLevel()
.nextRow(CoreIdentities.issue(10000))
.nextRow(CoreIdentities.issue(10001))
.build();
forestSource.apply(new ForestAction.Add(itemForest, under, after, before));
Removing a sub-tree
To remove a row, use ForestAction.Remove
and pass the row ID being removed.
forestSource.apply(new ForestAction.Remove(LongArray.create(100, 101, 102)));
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 ForestAction.Move
.
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.
forestSource.apply(new ForestAction.Move(LongArray.create(100, 101, 102), under, after, before));
Inspecting the Results
A call to ForestSource.apply()
will finish successfully if the operation has been completed and throw a StructureException
otherwise.
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 ActionResult
as 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.
Action | Effect |
---|---|
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
Each 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.
Permissions
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.