Product events are generated when users perform actions in Atlassian products. Apps can subscribe to a list of product events using a trigger in the manifest.
Events are passed to your app via the event
parameter.
1 2export async function run(event, context) { console.log("event: " + JSON.stringify(event)); return true; }
To receive a product event with a Forge app, your Jira projects or Confluence spaces must be accessible by the default user group. This is because the system user created for your Forge app is only a member of the default user group. If a restricted Jira project or Confluence space generates a product event, your Forge app is unable to receive the event.
To learn more about this issue and for updates on when this behavior is improved, see the FRGE-212 Jira issue.
There are two types of retry events:
Install the latest Forge events package by running:
npm i @forge/events
You can request a retry for a product event trigger by returning an InvocationError
object. This is defined in the @forge/events
package
You can only retry an event for a maximum of four times.
Additional options can be included in the InvocationError
via a RetryOptions
object, allowing you to provide more information about what went wrong and configure the retry.
Event payload schema
1 2RetryOptions { retryAfter: number // retry trigger after in seconds retryReason: InvocationErrorCode // reason why the error occured retryData: any // additional data to assist retry logic } enum InvocationErrorCode { // There was a rate limit upstream that caused the Application to fail. FUNCTION_UPSTREAM_RATE_LIMITED = "FUNCTION_UPSTREAM_RATE_LIMITED", // Some application level error occurred and a retry is warranted FUNCTION_RETRY_REQUEST = "FUNCTION_RETRY_REQUEST" }
retryAfter limitation
The maximum retryAfter
value is 900
seconds (15 minutes). Any retryAfter
values exceeding this limit are lowered to 900
seconds.
retryReason values
Value | Description |
---|---|
FUNCTION_UPSTREAM_RATE_LIMITED | Rate limit upstream that caused the app to fail |
FUNCTION_RETRY_REQUEST | Unclassified error occurred during the app that the developer would like to retry |
Example for requesting a retry
In the following sample code, the app calls an upstream Jira Product API and is rate limited. A retry is requested with a timeout that is equal to the backoff time provided by the Jira Product API and the retry reason is FUNCTION_UPSTREAM_RATE_LIMITED
as there was an upstream dependency that was rate limited.
1 2import {InvocationError, InvocationErrorCode} from '@forge/events' import {asApp, route} from '@forge/api' export async function run(event, context) { const userName = 'john'; const response = await asApp().requestJira(route`/rest/api/3/user/search?query=${userName}`, { headers: { 'Accept': 'application/json' } }); if(response.headers.has('Retry-After')){ return new InvocationError({ // The App can request the retry to happen after a certain time period elapses retryAfter: parseInt(response.headers.get('Retry-After')), // The App should provide a reason as to why they are retrying. // This reason will be feedback to the event payload on the retry // and is to help the developer discern the initial failure reason. retryReason: InvocationErrorCode.FUNCTION_UPSTREAM_RATE_LIMITED, retryData: { userName: userName } }); } }
Example for handling a retry
In the following sample code the app checks whether or not the retryContext
field
exists in the event payload. If it exists, it means that the app is currently handling
a retry and the app can handle the retry accordingly. The app can request another retry
if another retryable error occurs, but note that the event can only be retried up to 4 times.
Properties
There will be one extra object retryContext
in the event payload for a retry. This object contains three properties:
retryCount
: number of times (max 4) the app requested for retryretryReason:
reason why the error occurredretryData
: additional data to assist retry logic1 2import { RetryOptions, InvocationError, InvocationErrorCode } from "@forge/events" export async function onIssueCreated(event, context) { try { // retryContext will be populated if this is a retry if (event.retryContext) { const { retryCount, retryReason, retryData } = event.retryContext; handleRetry(retryCount, retryReason, retryData, event); } else { handleEvent(event); } } catch(error) { // If the event is retryable, the App can request for another retry // although, note that the maximum number of retries is 4 if (e instanceof RetryableException) { const retryOptions: RetryOptions = { retryAfter: calculateBackOffTime(event.retryContext), retryReason: InvocationErrorCode.FUNCTION_RETRY_REQUEST, retryData: getRetryData(e, event) } return new InvocationError(retryOptions); } } }
Platform level errors cannot be captured by the app. Examples include timeouts and Out of memory(OOM) errors. If a platform error occurs, the Forge platform will automatically retry the event on behalf of you.
retryReason values
Value | Description |
---|---|
FUNCTION_OUT_OF_MEMORY | The function ran out of memory (allocated by Forge Platform/Lambda) in the previous attempt |
FUNCTION_TIME_OUT | The function timed out during the previous attempt |
FUNCTION_PLATFORM_RATE_LIMITED | An infrastructure Quota/Rate Limit occurred during the previous attempt at running the function |
FUNCTION_PLATFORM_UNKNOWN_ERROR | An undefined error occurred during the previous attempt at running the function |
This section describes a Forge preview feature. Preview features are deemed stable; however, they remain under active development and may be subject to shorter deprecation windows. Preview features are suitable for early adopters in production environments.
We release preview features so partners and developers can study, test, and integrate them prior to General Availability (GA). For more information, see Forge release phases: EAP, Preview, and GA.
Forge apps subscribed to a list of product events can now declare
an expression
property in the filter property
in each of the trigger module definitions in
the manifest to specify which events they should receive.
You can build event filtering expressions using the following subset of the Jira expressions language:
Expressions used in event filtering support only the following expressions types:
The content of the event payload is accessible under the event
variable that is
a Map type. Please refer to
the event payload documentation to understand the exact structure of the event
variable for each event type.
Note that type references defined for events on the mentioned paged are not translated to types in expressions language. This means that you should treat the type reference values as one of the supported expression types.
The expression has to evaluate to the
Boolean
type
to filter out events. If the expression evaluates to true
, the event will be delivered to the app. If the expression
evaluates to false
, the event will be filtered out and dropped. Any other evaluation outcome will be considered
an error and result in the event being filtered out. This includes scenarios like performing mathematical operations
on non-compatible types or accessing object properties that aren’t defined in the event payload.
You can use the Expressions playground to test your event filtering expressions against the provided payload.
A log will be shown in the developer console in case of such errors. Note that those messages will not be logged in the Forge CLI while tunneling; they're visible only in the app logs.
You can specify what should happen when an error occurs while evaluating the expression using the
onError
property.
You can also use the runtime error handling, available in expressions to return a custom error message that will be shown in logs.
1 2modules: trigger: events: filter: # actual expression to evaluate: "event.issue.fields?.issueType.name == 'Bug'" expression: | try { return event.issue.fields?.issueType.name == 'Bug'; } catch (e) { throw `Unexpected error: "${e.location}" - ${e.message}.`; } # log error message in case of failure and receive event onError: RECEIVE_AND_LOG
Some restrictions apply to expressions. While the limits are high enough not to interfere with any intended usage, it's important to realize that they do exist.
Expressions have to use the correct syntax and semantics and contain at most 10,000 characters.
While in the Jira expressions the restrictions are based on the number of the expensive operations, in expressions used in event filtering, restrictions are based on the approximate number of all operations performed during the evaluation. Expressions may execute up to 50,000 steps, where a step is any of the high-level operations, such as arithmetic, accessing a property, accessing a variable, or calling a function.
To verify the complexity of the expression, the Forge CLI will analyze the expression before the app deployment. If the analysis shows the expression is too complex, the deployment will fail.
The error will contain information about the estimated complexity to help with the simplification process. An example
analysis will look similar to the following example: N*M*L*K + 5*N*M*L + 10*N*M + 15*N + 20
, where:
N
, M
, L
, and K
are the inferred variables used in the expressionN*M*L*K
, 5*N*M*L
, 10*N*M
, 15*N
are the estimated number of operations performed per each variable combination20
is the constant number of all other operations performed during the evaluationThe restrictions for the analysis are based on the following limits:
Currently, expressions used in event filtering don't support the following Jira expressions features:
To filter out all the Jira issue events where the issue type is a bug, we should check it this data is available in the event payload of the Jira issue events.
By looking at the type reference of the Jira issue event,
we can see that the issue
object has a fields
object that may contain the issueType
object.
1 2interface Issue { id: string; key: string; fields: { issueType?: any; // (... other fields ...) }; } // (other types...)
By looking at
the example of the payload, the data
for issue type is available under the issue.fields.issueType.name
path.
1 2{ "issue": { "fields": { "issuetype": { "self": "https://example.atlassian.net/rest/api/2/issuetype/10001", "id": "10001", "description": "Functionality or a feature expressed as a user goal.", "iconUrl": "https://example.atlassian.net/secure/viewavatar?size=medium&avatarId=10315&avatarType=issuetype", "name": "Story", "subtask": false, "avatarId": 10315 }, (... other issue fields ...) } }, (... other fields ...) }
Since the event payload is available in the event
variable as
a Map type, we have to use
event.issue.fields?.issueType.name == 'Bug'
as the expression.
The trigger module definition in the manifest should look like this:
1 2modules: trigger: - key: jira-issue-trigger-filtering-by-expression-for-bug-issue-type function: main events: - avi:jira:created:issue - avi:jira:updated:issue - avi:jira:deleted:issue - avi:jira:assigned:issue - avi:jira:viewed:issue - avi:jira:mentioned:issue filter: expression: "event.issue.fields?.issueType.name == 'Bug'" function: - key: main handler: index.run
After the app is deployed, events whose payload have Bug
as the issue type will be delivered and trigger the main
function. For other and undefined issue types, events will be filtered out.
When using product events, your Forge app must have permission from the site admin to access
the data it provides within the event payload. The OAuth scope required is documented under
each product event. Note, running the forge lint
command picks up these required scopes.
See Permissions for detailed information about each scope. See Add scopes to call an Atlassian REST API to add new scopes to your app.
Rate this page: