Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Table of Contents

Subpages:

Child pages (Children Display)


Global prerequisite(s)

Info

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

(System) Admins: configure fields for Company-managed as well as Team-managed projects

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 determining 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 your 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""sign-off" or "decline"

    6. on tab “Blocking transitions“, you must configure which workflow transitions, identified by ID, will be blocked until this Group Sign-Off field is finally decided. After creating this field, do not forget to add the condition “GSO: Block transition until finally decided” to these workflow transitions.

    7. 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 the screen. On tab "inactive fields", you can finally delete a field or revert your action by moving it back to "active fields".

Configuration of Group Sign-Off fields in Team-managed projects by the team

Additionally to all admins, using the global app configuration, all project-specific Group Sign-Off fields can be maintained within team-managed projects by the team themselves (see section “Team-managed projects“ in Usage (Jira Cloud) ).

How to get the transition ID for auto-transitions?

In team-managed projects, Atlassian has improved the GUI to edit workflows but hides the internal transition ID. As a transition name is optional, this cannot be an alternative because it is not guaranteed unique, although it would be the easiest approach. Therefore, you have to do the following 5 steps to get the transition ID within team-managed projects, once:

In company-managed projects, you can switch into the Jira Administration as a system admin. There, open the related workflow and click on EDIT, then switch from the graphical view to the text view to see the transition IDs of all your transitions:




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 ("_").

Info

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 */:

Info

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 have 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 access all issue fields like issue.summary, issue.customfield_xyz, etc. as JSON objects; see sample extract, below:

Code Block
issue = {
  "statuscategorychangedate": "2021-10-03T22:13:08.902+0200",
  "fixVersions": [],
  "resolution": null,
  "lastViewed": "2021-11-04T17:23:09.785+0100",
  "priority": {"self": "https://polscheit.atlassian.net/rest/api/2/priority/3","iconUrl": "https://polscheit.atlassian.net/images/icons/priorities/major.svg","name": "Major","id": "3"},
  "labels": [],
  "aggregatetimeoriginalestimate": null,
  "timeestimate": null,
  "versions": [],
  "customfield_11549": null,
  "issuelinks": [],
  "assignee": {"name": "unassigned"},
  "status": {"self": "https://polscheit.atlassian.net/rest/api/2/status/10002","description": "","iconUrl": "https://polscheit.atlassian.net/images/icons/statuses/open.png","name": "To Do","id": "10002","statusCategory": {"self": "https://polscheit.atlassian.net/rest/api/2/statuscategory/2","id": 2,"key": "new","colorName": "blue-gray","name": "To Do"}},
  "components": [],
  "customfield_11536": [{"self": "https://polscheit.atlassian.net/rest/api/2/customFieldOption/10159","value": "administrators","id": "10159"}],
  "aggregatetimeestimate": null,
  "subtasks": [],
  "reporter": {"self": "https://polscheit.atlassian.net/rest/api/2/user?accountId=557058%3A7b5dfd59-30f7-4f0e-864d-34fb8ba6e452","accountId": "557058:7b5dfd59-30f7-4f0e-864d-34fb8ba6e452","avatarUrls": {"48x48": "https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/initials/FP-0.png","24x24": "https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/initials/FP-0.png","16x16": "https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/initials/FP-0.png","32x32": "https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/initials/FP-0.png"},"displayName": "Frank Polscheit","active": true,"timeZone": "Europe/Berlin","accountType": "atlassian"},
  "issuetype": {"self": "https://polscheit.atlassian.net/rest/api/2/issuetype/10001","id": "10001","description": "A user story. Created by JIRA Software - do not edit or delete.","iconUrl": "https://polscheit.atlassian.net/secure/viewavatar?size=medium&avatarId=10515&avatarType=issuetype","name": "Story","subtask": false,"avatarId": 10515,"hierarchyLevel": 0},
  /* ... and many others left out within this sample extraction */
}

Additional functionalities are also provided via an helper object (see samples below):

  • 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.

(warning) *) 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.

Info

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".


Info

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 answered generally, you have to use the account id of the 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 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:


Info

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.

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

 

Info

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


ATTENTION:
If the group “Board Members” has no members, then the resulting code within this rule will cause a syntax error!

Explanation:

The concat-function appends the second parameter to the rule first, if the first parameter (passed list of deciders) is not empty. Having no members in the group “Board Members”, the variable “users” is still an empty string. So, assuming that the group “Manager” contains the deciders a, b, and c, this would result in the following content “ AND (a OR b OR c)“ which is not a correct expression (syntax error).

The following sample takes care of this by checking if the related variable “users” or “rule” is empty and uses different operators for these cases. It is important that for users, the operator is whether a comma or an empty string and for the rule, the operator is the necessary logical AND/OR string or a blank (single space)!

Info

Concat of multiple groups, which may be also empty without a decider and 4-eyes-principle

// conditional rule
users ="";
rule ="";
if (issue.status.name == "my approval state"){
["my group 1", "my group 2"].forEach(group => {
users += helper.concat(helper.getUsersByGroup(issue,group,","), users.length ? ",":"");
rule += helper.concat(helper.getUsersByGroup(issue,group,"OR"), rule.length ? "AND":" ");
});
}
removed=helper.getUsersByCustomfield(issue,"Participating Developers", ",");

The last line above ensures, that all members of the multi-user custom field, named “Participating Developers“, will be automatically removed/excluded from the list of deciders: this ensures a 4-eyes principle, also if one or more of them are members of the referenced groups.

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

Info

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:

Info

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");
}

Code Block
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
Code Block
// 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:

Component name controls deciders as members of related project role
Code Block
// 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

Info

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

const space = " ";

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" : space );
    }    
});
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


Info

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.

Info

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().

The following code illustrates how to easily use loops (here: forEach). You have to adjust the related issue’s status in line 5 and the list of project roles, containing the deciders:

Info

Complex sample: one member of each project role must sign-off or one member of all must decline

// conditional rule
users = "";
rule = "";
if (issue.status.name == "Necessary Approval") {
["Product Marketing","IT Operations","Stakeholders"].forEach(projectRole => {
var roleUsers = "" + helper.getUsersByProjectRole(issue,projectRole, ",");
if (roleUsers.length > 0) {
if (users !== "") users += ",";
users += roleUsers;
if (rule == "") {
rule = "(";
} else rule += " AND ";
rule += "check(true, 1, " + roleUsers + ")";
}
});
if (rule.length > 0) rule += ") OR check(false, 1, " + users + ")"; }




Modify your workflow(s)

using new

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 When a decision is finally signed off or declined, you can configure auto-transitions to ease usage. In such a case, you generally do not want users to trigger this workflow transition by clickinging interactively. So, you must block this by adding a workflow condition by : "Block transition until signed-until signed off" or "Block transition until declineduntil 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 blocks it. As soon as a final decision is evaluated, the configured workflow transition will be executed in the context of the logged-in user's context based on the result (signed-off / declined).

Also, you can configure the workflow condition “Block transition for none-pending issues only (must be configured on the tab GSO 'rule')“ to steer if the related workflow transition is displayed and executable by normal users or not. Just call the function helper.setPending(true) or helper.setPending(false) within your Group Sign-Off definition for an issue to display or hide the related workflow transitionssteer if the related workflow transition is displayed and executable by normal users or not. Just call the function helper.setPending(true) or helper.setPending(false) within your Group Sign-Off definition for an issue to display or hide the related workflow transitions.

Suppose a final decision is the prerequisite for a workflow transition being triggered by a user. In that case, you must specify this transition on the tab “Blocking transitions” of the related Group Sign-Off field and add the workflow condition “Block transition until finally decided” to that transition:

Bildschirmfoto 2024-04-25 um 17.12.54.pngImage AddedBildschirmfoto 2024-04-25 um 17.07.01.pngImage Added

Once an issue is finally decided (signed-off or declined), that configured workflow transition is available for users and will be displayed on the screen.

Auto-transitions with required fields

If you have got a business scenario in which you for want 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 “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.

Bildschirmaufnahme 2021-08-06 um 18.36.50.mov

Bildschirmfoto 2024-04-25 um 17.07.01.pngImage Added

Image Removed

Image Modified

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

Enable justification of decisions by comments

  • optionJustifyDecisionByComment: force entering a comment to justify any decision, regardless if declining or signing-off

Disable forcing a comment if decline

  • optionNoCommentIfDecline: by default, if a decider declines, a comment is forced to justify, but using this option it is ignored

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"

  • optionUndo=true|false(is default) : by default, no undo button will be displayed. Having set this option to TRUE, any decider can revoke/undo previous own decisions and set it back to pending.

  • please read sub-page for more details concerning “revert all decisions“: How to Config "Revert decisions" in the Cloud

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.

Info

Sample of a (static) rule with appended option

fpolscheit
representative

sign-off=(fpolscheit OR representative)

optionNoCommentIfDecline


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)

Search via JQL

You can search via JQL like:

  • project = TEST AND issue.property[approvals].pending ~ currentUser()
    Retrieve all issues being pending for a decision of the current user.

  • project = TEST AND issue.property[approvals].signedOff ~ currentUser()
    Retrieve all issues being signed-off/approved by the current user.

  • project = TEST AND issue.property[approvals].declined ~ currentUser()
    Retrieve all issues being declined by the current user.

Bulk decisions & project panel “my pending decisions”

Available since April 2022 for all issues being created or getting group sign-off decisions at that date or later.

By default, bulk decisions are enabled for all projects. Within the project configuration of Group Sign-Off, you can enable/disable this feature by clicking on the related checkbox as shown on the screen copy on the right.

Bulk operations are available on the project panel “Group Sign-Off” for the current user’s pending decisions by clicking on the button “sign-off” or “decline” within the first line of the table of pending decisions (“All x issues“) or within any grouping line for all belonging issues.

GSO-bulk-with-flexible-grouping.movGSO-new-feature_MyPendingDecisionsAndBulkUpdate.mov

Integration into Confluence as a macro kind of “Jira issues”

See: Group Sign-off for Jira inside Confluence Cloud [free extension/macro]

Integration as a new gadget on your dashboard

Since October 2022, you can add a Group Sign-Off gadget to your Jira dashboard. To get this, you may have to upgrade the app if not done automatically by Atlassian. If you do not find the Group Sign-Off gadget within the search result for adding a gadget to your dashboard, please switch to the Jira administration and open the page “manage apps”. There, click on the link “upload app” as shown on the right and enter the following URL:

https://polscheit.de/plugins/jira/group-sign-off/cloud/atlassian-connect.json

If you have any questions, please do not hesitate to contact me per email at frank@polscheit.de

Having done all administrative tasks, you will find the new gadget …

Next, please choose one of your saved named JQL filters or, alternatively, select an agile board to specify which issues shall be loaded. Additionally, you must select all fields you want to see on the dashboard and click on the button “+” to put them into the section of “displayed fields”. You can click on the trash icon to remove a field from the screen. Use the icon with the three lines, right to the trash icon, and drag’n drop to re-order your fields.

Enabling the option “Condensed Fields” by clicking the checkbox will display all selected Group Sign-Off fields of the section “Displayed Fields” within a single column on the dashboard having one bubble per field. The bubbles are colored as follows:

  • green: finally signed-off / approved

  • red: finally declined

  • yellow: pending decision(s)

  • white: n/a or not ready yet (based on your rules)

Finally, you will see your updated dashboard with the new Group Sign-Off gadget shown on the right.

Audit logging

Since Febr. 25th, 2024, all modifications of group sign-off fields will be logged, and this can be viewed on the tab “Auditing”: differences are highlighted for faster recognition.

Bildschirmfoto 2024-02-26 um 16.53.21.png

Sequential approvals

If you do not want to decide in parallel because your business case expects a sequence of approvals (the second decider cannot approve until the first one did before, etc.), then you have to configure this as follows. Per approval step, you canalso use multiple deciders or just a single one having the logic configured like a normal static rule. The following receipt illustrate of how to configure such an implementation, which you can modify to take care for your business needs.

  1. create your group sign-off field and configure the initial/first step of who has to decide:

2. extend the global tab “Rule” to append the next steps depending on the current sequence number (step) and the the final result: just append all deciders and their rule of the next step if the current step is finally signed-off:

Having successfully configured your group sign-off field, like “sequential approval” in my sample above, as well as extended the global rule tab for the additional, sequential steps, then you will get the following result:

Being logged in as “Frank Polscheit”: I approve, the issue automatically reloads because it is finally signed-off (finish of the initial/first step), the group sign-off field “sequential approval” is automatically extended by the deciders of the second step, logged in as “Nicole Polscheit”: I also approve …. etc. until no further step is configured and that field will be come finally signed-off.

Alternatively, you can also use any dynamic rule definition within the sequential steps instead of a static rule: this gives you a maximum of flexibility! Sure, it’s not pretty easy and you may need some minutes reading and understanding the sample code, but your users will love it.

Info
  1. The last step of a sequence must have no “optionSeqNo” to indicate, that no further step will come to avoid any configured auto-transition of an issue while within the middle of a sequence!

  2. The optionSeqNo is case sensitive and you must write it exactly like this!

Being familiar with the general concept, you can configure multiple steps. If it is possible that you do not have additional approvers within a sequential step, then you have to forward that rule to the next step as illustrated in the more complex sample below: this is easier extendable and much better for maintenance!

Code Block
languagejs
const fieldname = "Capex Sequential Approval";  /* group sign-off field name, which is used for a sequential approval instead of a multi-approval in parallel */

/* the following sample handles sequential approvals: */
if (issue.status.name === "Pending Approval") {  
  const field = groupSignOff.fields.active.global.find(data => data.name === fieldname);
  if (field && helper.isSignedOff(issue, fieldname)) {    
    const sequentialStep = (seqNo, hasNextStep, additionalDeciders, ruleExtension) => {      
      const currentRule = issue.properties[fieldname.replace(/ /g, "+")]?.rule;
      if (currentRule?.includes(`optionSeqNo=${seqNo}`)) {
        const previousDeciders = groupSignOff.getDeciders(currentRule);
        const previousRule = groupSignOff.getRule(currentRule);
        const newRule = additionalDeciders?.length > 0 
          ? `${previousDeciders.join("\n")}\n${additionalDeciders}\nsign-off = (${previousRule}) AND (${ruleExtension})`+ (hasNextStep ? `\noptionSeqNo=${seqNo+1}`:'')
          :  `${previousDeciders.join("\n")}\nsign-off = ${previousRule}` + (hasNextStep ? `\noptionSeqNo=${seqNo+1}`:'');
        issue.properties[fieldname.replace(/ /g, "+")].rule = newRule;
        issue.setRule(fieldname, newRule);
      }     
    };
    /* sequential steps processed after the initial default, configured within the group sign-off field definition: */
    sequentialStep(1, true,  helper.getUsersByCustomfield(issue, "Maintenance Support Approvers", "\n"), helper.getUsersByCustomfield(issue, "Maintenance Support Approvers", "AND"));
    sequentialStep(2, true,  helper.getUsersByCustomfield(issue, "Operations Leadership", "\n"),         helper.getUsersByCustomfield(issue, "Operations Leadership", "AND"));
    sequentialStep(3, true,  helper.getUsersByCustomfield(issue, "Process Engineer(s)", "\n"),           helper.getUsersByCustomfield(issue, "Process Engineer(s)", "AND"));
    sequentialStep(4, false, helper.getUsersByCustomfield(issue, "Manufacturing Engineering", "\n"),     helper.getUsersByCustomfield(issue, "Manufacturing Engineering", "AND"));
}

Sample video on how it will look like on screen (user perspective): see group sign-off field “sequential approval

Bildschirmaufnahme 2023-06-08 um 15.09.03.mov

The decisions of all prior approvals steps are taken and the newer approvals are appended, so that you get a full overview, who has decided what and when, although the configurations are extended step-by-step after each (partial)final sign-off.