Administration (Jira Cloud)

Subpages:



GDPR - necessary permissions

Permissions required: "Browse users and groups" -> global permission.

According GDPR, this app does not store any personal data by itself. All user entered data are stored within Jira properties within your own Jira instance hosted by Atlassian: no data will be stored outside your Jira instance at all!



Create/Modify Group Sign-Off fields

Please create new fields of type "Group Sign-Off" as many as you need, like "Steering Committee", "Product Board" etc.

  1. Switch into your Jira Administration → System → Group Sign-Off fields (you have to scroll down the menu to find this item) or alternatively, click on "configure" within the related section on page "Manage apps" as shown on the sample screenshot on the right.

  2. Click on the icon "+" to add a new field

    1. enter a unique name for your new field

    2. on tab "(dynamic) Rule", enter your rule for determine the related deciders (users) as well as the sign-off formula

    3. on tab "Context", enter all valid issue types as well as projects to specify which issues will display this new field

    4. on tab "Context", you can optionally upload own icons for the buttons "decline" and "sign-off" being presented to the deciders

    5. on tab "Auto-Transitions", you can optionally specify the transition ID of your related workflow (see: allowed issues as configured on tab "Context"), which will be triggered automatically in case of a final "sign-off" or "decline"

    6. finish by hitting on the button "Create"

  3. Click on the pencil-icon in the middle of the screen to edit that row's field configurations (analog to create such a field)

  4. Click on the tray-icon in the middle of the screen to move that row's field on tab "inactive fields": all data are kept but inactive fields will not be displayed on screen. On tab "inactive fields", you can finally delete a field or revert your action by moving it back to "active fields".







Static Rule

You have to determine, who is allowed to make a decision. Using the feature of setting a default value of a customfield, you can define it once and it will be taken over while creating a new issue automatically.

A definition of a decision is divided into two parts and stored as value within the related customfield:

  • the list of responsible users being able and allowed to decide and

  • a rule to determine the final result of the decision.


The list starts at the first character, first line with the login name of the responsible user: one user per line. Each line has to be terminated by a line-break. If the login name is unknown, it will not be displayed on the screen. The list of users end with a blank line for a better overview.

The rule for the decision starts with "sign-off=" followed by a boolean expression. Internal validation of the rule will be done by replacing all login names by their related decisions (sign-off = true and decline = false, pending = nothing). As soon as this rule results in a final TRUE or FALSE, it triggers the configured listener (see below). Having no individual decisions at the beginning or necessary pending ones, the result cannot be determined and nothing happens. The boolean expression can contain brackets "(" and ")" as well as "AND" and "OR" to describe the relationship of individual decisions. If the IT-boss or her/his representative can decide, one of both sign-offs is enough, and the business product owner has to sign-off as well the rule would be like: (boss OR representative) AND productOwner.

GDPR makes it necessary to use account ids

If an automatic conversion from a user name or email into an account id is not possible, which depends on various scenarios and cannot be simply answered, you have to use the account id of deciders instead. Generally, that should not be necessary but in case you need it for uniqueness, you can find the accountId of a user as described below.

How to get the account id of a user?

The easiest way to find your account id is to click on your icon on the sidebar (left/most down icon with your avatar image) and then on the "Profile" link. Then, have a look at the URL within your browser: your accountId is the string after the last "/" marked bold in the sample below:

Sample: https://******.atlassian.net/people/557058:7b5dfd59-30f7-4f0e-864d-34fb8ba6e452

Such accountId has to be adjusted by replacing the colon (":") by double underscores ("__") and all minus ("-") by a single underscore ("_").

Simple Example of a definition

fpolscheit
representative

sign-off=(fpolscheit OR representative)

 If you want to put additional information to the user name, you can add this as a comment straight after the user name and enclosed by /* and */:

Simple Example with additional user infos

fpolscheit /* Manager */
representative /* Alternative Voting */

sign-off=(fpolscheit /* Manager */ OR representative /* Alternative Voting */)



By clicking on "Update", each user name will be replaced by it's related account id before storage within a Jira property due to GDPR. If the automatic migration to an account id is not possible, a WARNING hint will be written at the end of the rule as comment and a related warning sign will be displayed on the right side of the field name within the overview list of all group sign-off fields. Such scenario occurs, if for example a specified user name is not unique (API request /rest/api/3/user/search?query=username results in multiple account ids).

Use comments to display them as additional infos:







Dynamic Rule(s) for more complex but flexible approaches

Instead of explicitly declare a list of users and a sign-off rule using boolean algebra, you can specify a dynamic rule, which extracts the users out of referred other fields like multi-user pickers. A dynamic rule MUST start with a first line just containing "// conditional rule". Then, users and a rule has to be defined as described below:

Within a dynamic rule, the reserved words in blue within the following complex example are mandatory!
Within a condition, you can use all methods provided by JIRA's issue API. The helper object provides the following methods:

  • contains(collection, string)

  • contains(collection, id as number)

  • getCF(issue, customfieldName)

  • getCFms(issue, customfieldName) to retrieve a customfield's value in milliseconds

  • getUsersByCustomfield(issue, customfieldName, operand) to retrieve a (list of) user(s)
    customfieldName can be any custom field of type single-user picker or multi-user picker

  • *) getUsersByProjectRole(issue, projectRoleName, operand) to retrieve a list of users

  • *) getUsersByGroup(issue, groupName, operand) to retrieve a list of users

  • concat(list, operand) to append a list of users using logical operand like "AND" or "OR" within rule definition and "," within user definition

  • log(data) to write data into the user's browser console output.

If you are using getUsersByCustomfield() etc. then the name of the referred element (e.g. field) will be displayed in gray in addition to the user name. So, a user knows her/his context if displayed multiple times, for example by belonging to various internal roles.

 *) According to Atlassian's restrictions, access to users per project role or group can only be done by users with admin permissions! Therefore, each time you modify the members of a group or project role, you have to click on the "sync" button as admin in order to provide this infos to the Group Sign-Off app in the context of all logged-in users.

Complex Example with different syntax: use a customfield (multi-user or user picker) to dynamically specify voters

// conditional rule

users =""+helper.getUsersByCustomfield(issue,"Board Members",",");
rule =""+helper.getUsersByCustomfield(issue,"Board Members","OR");



Please use the exact syntax above for dynamically specifying the list of user(s) based on the referred custom field. Users must be a comma separated list, that why the operand is a comma. The rule must be a sequence of users, concatinated by a boolean operand (AND or OR). By the first voting, the content of the customfield, referenced by name as parameter, will be taken over and the members will become a fix list.

Instead of using the custom field's name, you can also use it's ID: please put that number into the quotes like "10023" instead of "Board Members".



Complex Example with different syntax: a set of conditional definitions depending on issue's data

// conditional rule
if ( helper.contains(issue.components, "Component A") ) {
     users = "fpolscheit, admin";
     rule    = "fpolscheit OR admin";
} else if ( helper.contains(issue.components, "Component B") && (issue.priority.name == "Major")) { 
     users = "admin, tester"; 
     rule    = "admin AND tester"; 
} else { 
     users = "admin"; 
     rule    = "admin"; 
}

GDPR makes it necessary to use account ids

If an automatic conversion from a user name or email into an account id is not possible, which depends on various scenarios and cannot be simply answered, you have to use the account id of deciders instead. Generally, that should not be necessary but in case you need it for uniqueness, you can find the accountId of a user as described below.

How to get the account id of a user?

The easiest way to find your account id is to click on your icon on the sidebar (left/most down icon with your avatar image) and then on the "Profile" link. Then, have a look at the URL within your browser: your accountId is the string after the last "/" marked bold in the sample below:

Sample: https://******.atlassian.net/people/557058:7b5dfd59-30f7-4f0e-864d-34fb8ba6e452

Such accountId has to be adjusted by replacing the colon (":") by double underscores ("__") and all minus ("-") by a single underscore ("_") if being used as a hardcoded reference within your dynamic rule.

Access issue's data for conditions

You can also access all fields of an issue, like within the following sample of extracting the issue's reporter, and use fields' information for approval:



Complex Example accessing issue's functions to dynamically retrieve approver name(s), here: issue's reporter

// conditional rule
users = "" + issue.reporter.accountId.replace(/:/g,"__").replace(/-/g,"_");
rule    = "" + issue.reporter.accountId.replace(/:/g,"__").replace(/-/g,"_");

You can use "helper.log(issue)" to display all available fields and the elements' structure within your browser's console.

If you are using multiple custom fields to retrieve all users, who have to decide all together, you have to concatenate the custom fields content and use a logical operator within the dynamic rule. The sample below illustrates proper usage.

 

Concat of multiple custom fields' contents

// conditional rule

users =""+helper.getUsersByCustomfield(issue,"Board Members",",") + helper.concat(helper.getUsersByCustomfield(issue,"Manager",",") , ",");
rule =""+helper.getUsersByCustomfield(issue,"Board Members","OR") + helper.concat(helper.getUsersByCustomfield(issue,"Manager","OR") , "AND");



Another sample of displaying the Group Sign-Off field in a certain status, only:

Restricted displaying depending on issue's status

// conditional rule

users ="";
rule ="";
if (issue.status.name == "Pending Approval") {
    users += helper.getUsersByProjectRole(issue,"Approver",",");
    rule += helper.getUsersByProjectRole(issue,"Approver","AND");
}



Another sample of displaying the Group Sign-Off field depending on a certain value of a multi-select custom field:

Restricted displaying depending on an issue's multi-select custom field

// conditional rule

users ="";
rule ="";
if (issue.customfield_xyz.find(item => item.value== "my option")) {
    users += helper.getUsersByProjectRole(issue,"Approver",",");
    rule += helper.getUsersByProjectRole(issue,"Approver","AND");
}

1 2 Behave differently if the issue's reporter is a member of a specified project role:
If reporter belongs to project role 'X' then use special behavior
1 2 3 4 5 6 7 8 9 10 // conditional rule if (helper.removeContexts(helper.getUsersByProjectRole(issue,"Administrators",",")).indexOf(issue.reporter.accountId.replace(/:/g,"__").replace(/-/g,"_")) > -1) { // issue has been created by a member of project role 'Administrators' // no decisions are necessary: ignore group sign-Off (do nothing) users = ""; rule = ""; } else { users =""+helper.getUsersByCustomfield(issue,"Approvers",","); rule =""+helper.getUsersByCustomfield(issue,"Approvers","OR"); }



If you have configured a project role for each component having the same names then you can easily create a dynamic rule for group sign-offs:

1 2 3 4 5 6 7 8 // conditional rule users = ""; rule = ""; issue.components.forEach(component => { users += helper.concat(helper.getUsersByProjectRole(issue, component.name, ","), ",").substring(users.length ? 0:2); rule += helper.concat(helper.getUsersByProjectRole(issue, component.name, "OR"), "AND").substring(rule.length ? 0: "AND".length+1); }); rule += " OR check(false, 1, " + users + ")";

Out of each project role at least one decider has to sign-off to get finally into “sign-offed”.
If only one decider, independently from her/his project role, declines then it gets finally into the state “declined”.

Deciders are all members of the groups referenced by a multi-select custom field but not the current reporter

This is a very special case and you have to put in some internal knowledge of the app: due to GDPR, not all users have got the permission to retrieve user data. This can be the case for deciders as well and then, they would not be listed and cannot approve nor decline. To avoid such a scenario, the app internally caches all members of referenced Jira project roles as well as groups. Groups are detected by their name automatically. Within the definition below, the group name is determined at run-time by taking the value/selection of the multi-select custom field. Therefore, the automatism for detecting the group names cannot work, here. Instead, all possible groups have to be declared explicitly.

If you have any further question, please do not hesitate to contact me at frank@polscheit.de

// conditional rule
users = "";
rule = "";

var caching = helper.getUsersByGroup(issue, "group-A", ",") + "," +
helper.getUsersByGroup(issue, "group-B", ",") + "," +
helper.getUsersByGroup(issue, "group-C", ",") ;

var teams = helper.getCF(issue,"Impacted Teams");
if (teams) teams.forEach(team => {
    if (team) {
        users += helper.concat(helper.getUsersByGroup(issue,team.value,","), users.length ? ",":"");
        rule += helper.concat(helper.getUsersByGroup(issue,team.value,"OR"), rule.length ? "AND":"");
    }    
});
removed=issue.reporter.accountId.replace(/:/g,"__").replace(/-/g,"_");



Special Function(s)

  • check(param1, param2, ...)

with param-1: Boolean value TRUE or FALSE, whereas true indicates sign-off and false indicates decline
param-2: threshold, resp. minimum amount of decisions as specified within param1 to return the value of param1, otherwise this function will return null
param-3, ..., param-n: comma-separated list of deciders



Advanced Sample: sign-Off rule based on function calls and boolean algebra

fpolscheit
representative
anotherPerson

 sign-off=check(true, 2, fpolscheit,representative,anotherPerson) OR check(false, 1, fpolscheit,representative,anotherPerson)



If at least one decider declines, the sign-off will be "declined" or if at least two deciders have voted for sign-off, the result will be "signed-off".

Based on 3 possible results of function check(null, true, false), the sequence of both function calls combined with OR is important: if you change both, it will not work properly! Based on boolean logic, false OR null results in null, whereas null OR false results in false which is what you would like to get. So, the check for declined votes via check(false,...) has to be the second part after the OR-connector.

A more complex sample combines the check-function with other dynamic helper-functions retrieving sets of deciders: at least 2 deciders out of the project roles "Developers" and "Administrators" have to sign-off, so that this rule switches into the state "signed-off". But if 1 decider declines, it results into "declined", immediately.

Complex Sample: combination of check-function with dyn. helper-functions

// conditional rule
users=""+helper.getUsersByProjectRole(issue,"Developers",",")+helper.concat(helper.getUsersByProjectRole(issue,"Administrators",",") , ",")
rule="check(true, 2, " + helper.getUsersByProjectRole(issue,"Developers",",") + helper.concat(helper.getUsersByProjectRole(issue,"Administrators",",") , ",") + ") OR check(false, 1, " + helper.getUsersByProjectRole(issue,"Developers",",") + helper.concat(helper.getUsersByProjectRole(issue,"Administrators",",") , ",") + ")"

Expanation: using a dynamic rule, that rule must get a string in quotes like rule = "check(true, 2, x, y, z) OR check(false, 1, x, y, z)", whereas x, y, z is build by helper-functions like a concat of getUsersByProjectRole().







Modify your workflow(s) using new Condition(s)

At the point of time when a group decision shall happen, it does not make sense to click on the related button/transition and continue the workflow without synchronization with the result of the decision. So, you have to block further processing by adding a workflow condition by "Block transition until signed-off" or "Block transition until declined". 

Having configured such a condition, the related workflow transition will not be visible and clickable for any users as it is blocked by the condition. As soon as a final decision is evaluated, the configured workflow transition will be executed in the context of the logged in user based on the result (signed-off / declined).

Auto-transitions with required fields

If you have got a business scenario in which you for an issue to automatically transition it into a certain status when it’s fully signed-off/declined AND one or more fields have to be filled out, then you can configure that by creating a new workflow validator for that workflow transition like the “Field Required Validator“.

If a user has not filled out all necessary fields but a Group Sign-Off field has been finally signed-off/declined then an auto-transition with required fields will fail: the user will be notified by a related message on the screen and can use inline-editing to maintain the necessary data. Then click on the “retry” button to continue to finish the auto-transition like shown on the small video below.

 



 

 

 

 

Additional options

Additional options can be added at the end of the definition of any rule like shown below.

Disable delegation of decisions

  • optionDelegation=false|true(is default) : prevent from delegation by deciders

Disable displaying individual users

  • optionDisplayNoUsers : by default, users being identified as deciders are displayed including their voting, visible for all users. Using this option these details are omitted to protect privacy of individual decisions to third-parties not being deciders of that decision by themselves.

Disable displaying all deciders and reduce to the current user's decisions

  • optionDisplayCurrentDeciderOnly : by default, a decider can see all decisions. Depending on the number of deciders, like using various project roles within a dynamic rule, that list can be very long. To shorten that to the decisions of the current user only, please use this option.
    ATTENTION: as admin, you will still see the list of all decisions so that you have got the chance to delegate if necessary.

How to configure "Revert (all | my) decisions"

Display decision buttons for deciders only if their decision has got an effect on the final result

  • optionDecisionWithEffectOnly : by default, all deciders will see the buttons for declining or signing off. If you want to reduce that just for deciders, whose decision has got an immediate effect on the final result then use this option.
    In that case, a decline as well as a sign-off decision for all deciders without any already taken decisions will be simulated. This depends heavily on the complexity of your dynamic rule! So, it might occur that you have to contact me via email at frank@polscheit.de to assist or adapt in specific cases.



Email Notifications for all deciders

As admin, please ensure that all your deciders are members of the default Jira group named "users": it's not enough to be a member of "jira-users", only! Best practice: assign all your users to the group "users".

Then, you can dis-/enable email notifications within the app's configuration main panel by clicking on the related checkbox as displayed on the sample screen copy on the right side.

This notification feature uses Jira's internal email notification service. The sender cannot be configured by any third-party app but consider the following Atlassian documentation to set the sender email per project as a system admin: https://support.atlassian.com/jira-cloud-administration/docs/configure-jira-cloud-to-send-emails-on-behalf-of-your-domain/ .

Alternatively, each decider can create a Jira filter based on JQL, saved that under a suitable name and activate filter subscription for this named filter, which is a standard feature of Jira: you can specify the frequency and at what day and time you will receive an email with a summary of all outstanding issues to be decided etc. That is very helpful, especially if you are spammed with too many emails. Also see: Usage (Jira Cloud)#Notificationsfordeciders(searchissuesforpendingdecisionsviaJQL)