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))
CODE

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));
CODE

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)));
CODE

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));
CODE

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.

ActionEffect
Adding rows to a static structure

(tick) Structure is modified

Moving item X from group A to group B, where groups are provided by a grouper by field F(tick) 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(tick) Link L: Y→X is deleted
Moving issue upwards when structure is sorted by Agile Rank(tick) Issue's Rank is changed
Adding an issue to an arbitrary JQL query result(minus) 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)(minus) 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.