Last updated May 21, 2025

Use a long-running function

By default, async event consumers time out after 55 seconds. Use cases that required longer computation have previously had to rely on breaking the task into multiple steps or batches, and queuing multiple events to do the work.

You can now configure a timeout of up to 900 seconds (15 minutes) which will allow many such use cases to be performed in a single invocation.

Use Case: Generating Reports from Jira Data

Imagine you are developing an application that generates detailed reports from Jira issues. This process can be time-consuming, especially if you are aggregating data from multiple projects and applying complex calculations. The Long-Running Compute feature allows you to handle these tasks efficiently without running into timeout issues.

Before you begin

This tutorial assumes you're already familiar with developing on Forge and the Async Events API.

Before you start, ensure you have the following:

  • An Atlassian account with access to Forge.
  • The Forge CLI installed on your machine.
  • Basic knowledge of JavaScript and Forge development.

Step 1: Set Up Your Forge Application

  1. Create a new Forge app:

    1
    2
    forge create
    

    Follow the prompts to set up your application. Name your app and choose the template appropriate to the type of app you are creating. For the purposes of this tutorial, we will use the blank template. Select the following options:

    • context: Show All
    • category: Show All
    • scroll down and choose blank

    If you intend to extend this example to use UI to trigger a long-running function, or display the results of your long-running function to the front end, you can select a template from the UI Kit or Custom UI category.

  2. Navigate to your app directory:

    1
    2
    cd your-app-name
    
  3. Add necessary permissions: Open manifest.yml and add the required permissions to access Jira data:

    1
    2
    permissions:
      scopes:
        - read:jira-work
    

    If you chose the blank template, the existing function module in the manifest can be removed, along with the sample src/index.js file.

Step 2: Create an event consumer

All long-running functions must be invoked by an async event consumer. Update the manifest.yml file to include the required event consumer module and corresponding function module:

1
2
modules:
  consumer:
    - key: queue-consumer-key
      queue: queue-consumer-name
      resolver:
      function: generate-report
      method: generate-report-event-listener
  function:
    - key: generate-report
      handler: generateReport.handler
      timeoutSeconds: 900

Notes

  • The function generateReport.handler will be invoked by the queue each time an event is pushed to it.
  • The consumer uses a resolver which has function value generate-report and method value generate-report-event-listener. The function value must match the key under the function module. The method value must be defined on the resolver object in generateReport.js for the consumer to invoke it. This will be visible in the following section.

Step 3: Implement the long-running function

Create a new long-running function: In the src directory, create a new file called generateReport.js. This is where the resolver and the long-running function is defined. The below long-running function will take 5 seconds to execute, however it can take up to 900 seconds until it gets timed out:

1
2
import Resolver from '@forge/resolver';

const resolver = new Resolver();
resolver.define('generate-report-event-listener', async ({ payload, context }) => {
    // This resolver function can take up to 900 seconds to complete
    console.log("The resolver has been invoked");
    const ret = await processGenerate(payload);
    console.log(`The resolver returned with: ${ret.body}`);
    return ret;
});

export const processGenerate = async (event) => {
    const { projectKey } = event;

    // Simulate a long-running task
    const reportData = await generateReport(projectKey);
    return {
        statusCode: 200,
        body: JSON.stringify(reportData),
    };
};

const generateReport = async (projectKey) => {
    const issues = await fetchIssuesFromJira(projectKey);
    // Perform complex calculations and aggregations
    const report = performCalculations(issues);
    return report;
};

const fetchIssuesFromJira = async (projectKey) => {
    // Simulate a delay for fetching data
    await new Promise(resolve => setTimeout(resolve, 5000));
    // Return mock data
    return [
        { id: 1, status: 'Done', points: 5 },
        { id: 2, status: 'In Progress', points: 3 },
    ];
};

const performCalculations = (issues) => {
    // Aggregate data
    const totalPoints = issues.reduce((sum, issue) => sum + issue.points, 0);
    return { totalPoints };
};

// This variable is referenced to in the manifest
export const handler = resolver.getDefinitions();

Step 4: Create a trigger to invoke the long-running function

There are many ways to invoke a function which will push events to the consumer queue, however a simple one we will use is a trigger.

  1. Create a new file src/pushToQueue.js with the following code:

    1
    2
    import { Queue } from "@forge/events";
    
    export const handler = async (_req) => {
        const queue = new Queue({key: "queue-consumer-name"});
    
        console.log("Pushing an event to the queue");
        const jobId = await queue.push({ hello: 'world' });
        console.log(`Queued job ${jobId}`);
    
        return {
            statusCode: 200,
            statusText: "Success"
        };
    }
    
  2. Update manifest.yml to include a new trigger module, and extend the current function module. The event which will invoke the trigger is avi:jira:updated:issue. This means every time an issue is updated, the trigger is invoked and pushToQueue.handler is called. Your manifest.yml file should now look like this:

    1
    2
    modules:
      consumer:
        - key: queue-consumer-key
          queue: queue-consumer-name
          resolver:
            function: generate-report
            method: generate-report-event-listener
      trigger:
        - key: invoke-lrf-when-jira-issue-updated
          function: push-to-queue
          events:
            - avi:jira:updated:issue
      function:
        - key: generate-report
          handler: generateReport.handler
          timeoutSeconds: 900
        - key: push-to-queue
          handler: pushToQueue.handler
    

Step 5: Deploy Your Application

  1. Install required dependencies: Since in this tutorial the blank template was chosen, there are no pre-installed dependencies.
    1
    2
    npm install @forge/resolver @forge/events
    
  2. Deploy your Forge app:
    1
    2
    forge deploy
    
  3. Install the app in your Jira instance:
    1
    2
    forge install
    

Step 6: Invoke the Long-Running Function

Update any Jira issue to invoke the long-running function! The console.log statements executed can be seen in the terminal if running forge tunnel or in the developer console if not. You can also see them by running forge logs. To see the output of the long-running function on your frontend, build a UI Kit or Custom UI app.

Expected output

If you have an active forge tunnel running, the expected output is as follows:

1
2
invocation: ... pushToQueue.handler
INFO    14:30:01.146  ...  Pushing an event to the queue
INFO    14:30:01.589  ...  Queued job queue-consumer-name#...

invocation: ... generateReport.handler
INFO    14:30:02.506  ...  The resolver has been invoked
INFO    14:30:07.508  ...  The resolver returned with: {"totalPoints":8}

The ... replaces anywhere an id is used in the log output. Your output will have real values.

Conclusion

Long-running functions allow Forge developers to handle complex and time-consuming tasks efficiently. By following this tutorial, you have learned how to set up a realistic use case for generating reports from Jira data.

Rate this page: