Let JQL find work items that a group approved or rejected

Description

Problem

Approval Path JQL can find work items waiting for a group's decision (approvalPendingForGroup, thependingApproverGroupNames field). Once the group decides, it drops out of these results, and there is no way to find work items that the group approved or rejected. Teams that report on or audit decisions by group cannot do this today.

Change

  • New indexed field approvedApproverGroupNames: names of groups whose Group step or Vote step finished as Approved.

  • New indexed field rejectedApproverGroupNames: the same for steps that finished as Rejected.

  • New JQL functions approvalApprovedByGroup("<group name>") and approvalRejectedByGroup("<group name>"), working like approvalPendingForGroup, with the in / not in operators.

  • A step counts when the group decided on it. A Group step with the Notify action finishes without a decision and is not counted. A group whose step is still pending or was skipped is not counted.

  • The name is the one stored on the step, the same value pendingApproverGroupNames uses. A Group step built from a user-picker field is listed under the field's name.

  • The approval's own status does not matter: a group that approved a step of an approval that is still in progress, or that later ran past its deadline, is found.

  • Deleting or archiving an approval removes its groups from both fields, unless the same group made the same decision in another active approval on that work item.

  • An approval whose validity expired stops counting, whether or not it is relaunched. On Jira this is the only way an approval restarts; the relaunched approval adds the group again only after a new decision.

  • Work items where groups decided before this change is released are also found. A one-time migration at backend start rewrites the property of every work item that has such a decision (two Jira calls per work item, throttled).

  • The "Searching approvals with JQL" page describes the new fields and functions.

Fixes included

  1. not in returned only work items that had some other value in the field: Jira evaluates the negated clause as "not equal", which skips work items whose field is empty or that have no approvals at all. It now returns every work item the matching in query does not return. This applies to all Approval Path JQL functions, the existing ones included, so saved filters that use not in with an argument return more work items than before.

Out of scope

  • Approval Path for Confluence (JQL applies to Jira only). The property is shared, so the two fields are also written to the Confluence page property; nothing indexes or reads them there.

Release order

  • Deploy the Forge manifest (new functions and indexed paths) before the backend. Jira indexes a property path only when the property is written after the manifest declares that path. If the backend's migration (MigrationV27_BackfillDecidedGroupNames) runs first, its values are not indexed and the migration has to be run again.

Acceptance criteria

  • After a Group step is approved, issue.property[approvalPath].approvedApproverGroupNames = "<group>" and issue in approvalApprovedByGroup("<group>") return the work item.

  • After a Group step is rejected, issue.property[approvalPath].rejectedApproverGroupNames = "<group>" and issue in approvalRejectedByGroup("<group>") return the work item.

  • A Vote step that finished as Approved or Rejected behaves the same way.

  • not in with the new functions returns the remaining work items, including work items without any approval. The existing functions do the same (fix 1).

  • A group whose step is still pending, was skipped, or was only notified does not appear in either new field.

  • After an approval is deleted or archived, or its validity expires, the work item is no longer returned for that approval's groups, unless the same group made the same decision in another active approval on that work item. After a relaunch, the group returns to the field only after a new decision.

  • A work item where a group decided before the release is returned by the new queries.

  • Existing JQL fields and functions work as today, except not in (fix 1).

Manual tester notes

  • Names on qa carry the environment suffix: issue.property[approvalPathQa], approvalApprovedByGroupQa,approvalRejectedByGroupQa.

  • Jira indexes a property change with a delay, usually seconds, sometimes up to a minute. Repeat a query before reporting a miss.

  • Backfill: before the release, note a work item whose group already decided. After the release, check the backend log for #M27(number of work items found, backfilled, failed) and run the new queries for that work item. A backend that starts without a valid Forge system token for a site (for example after a long downtime) updates nothing for that site and still records the migration as done; compare the backfilled count with the number found.

  • Restart: use a definition with validity expiration and relaunch on expiry. Approve it, set the validity date a few minutes ahead on the approval, wait for the expiry (checked every minute). The group is gone from approvedApproverGroupNames until the relaunched approval is approved again. Repeat with a definition that does not relaunch: the group is gone and stays gone.

  • Same group twice: two approvals on one work item approved by the same group; delete or archive one, the work item is still returned; delete or archive the second, it is not.

  • Parallel group: a group that rejects inside a parallel group that stays open is listed as rejected right away, because the step's own decision counts.

  • Group names match without regard to case but otherwise exactly, including a trailing space. A name with a double quote and angle brackets (michal and joe XSS "><script>alert(1001)</script>) is stored in the property but found neither by the new fields nor by pendingApproverGroupNames, with either JQL quoting style; that is how Jira indexes such a value and is unchanged.

  • not in (fix 1): for each function compare in and not in over the same set of work items; together they return every work item once. Jira caches a function's result per argument for a while, so use an argument that was not queried before the release, or a different letter case of the group name.

  • Regression: approvalPending, approvalApproved, approvalRejected, approvalPendingFor, approvalPendingForGroup, approvalPendingForEmail, approvalPendingForAnyStep and their fields return what they returned before.

  • Known limit, unchanged by this ticket: when a work item is moved to another project and the old project is later deleted, its finished approvals are removed without a property refresh, so the work item keeps its old values in these fields as it does in the existing ones.