Updated: Async Events in Forge does not work (anymore)

[NOTE: see reply below for a minimal new app implementation, simply create a confluence ui macro app with name queue-test and use the code in that reply]

I had to create a queue solution in my Forge app. At first, it worked brilliantly. It was easy to implement from https://developer.atlassian.com/platform/forge/runtime-reference/async-events-api/. Then — for me, out of nowhere — yesterday, suddenly my consumer was getting undefined payloads and contexts. I’ve been wrestling with this issue ever since. I’ve now removed all of the Async Events (queue) code in my app, except for a simple test setup. I still get this issue and I am starting to wonder if it is me (probably, and then most likely something with async/await/Promise), or maybe (in part also) forge.

The relevant snippets from manifest.yml:

modules:
  trigger:
    - key: dl-spike-one-trigger-pageviewed
      function: dl-trigger-pageviewed
      events:
        - avi:confluence:viewed:page
  consumer:
    - key: test-consumer
      queue: test-updates
      resolver:
        function: test-listen
        method: test-event-listener
  function:
    - key: test-listen
      handler: index.testQueueHandler
    - key: dl-trigger-pageviewed
      handler: index.dl_handleTriggerPageViewed
permissions:
  scopes:
    - read:confluence-content.summary
    - read:confluence-content.all
    - read:page:confluence
    - storage:app
    - read:space:confluence
    - read:confluence-props
    - write:confluence-props

The relevant snippets from the code:

import { Queue } from '@forge/events';
import Resolver from "@forge/resolver";

const testQueue = new Queue({ key: 'test-updates' });

const testResolver = new Resolver();
testResolver.define("test-event-listener", async ({ eventPayload, eventContext }) => {
  console.log(`TEST event queue listener`);
  console.log(`TEST event queue listener: All info about my context: ${JSON.stringify(eventContext, null, 2)}`);
  console.log(`TEST event queue listener: All info about my payload: ${JSON.stringify(eventPayload, null, 2)}`);
  if (eventPayload === undefined || eventContext == undefined) {
    console.log(`TEST event queue listener: ERROR in QUEUE`);
   }
});
export const testQueueHandler = testResolver.getDefinitions();

export async function dl_handleTriggerPageViewed( event, context) {
  console.log( `dl_handleTriggerPageViewed`);
  await testQueue.push( "Hello, world! I viewed a page!");
};

And what I see in the log:

INFO    2023-03-25T12:41:23.179Z 16b37462-6bc7-45d4-b5dd-cd581d549428 dl_handleTriggerPageViewed
INFO    2023-03-25T12:41:24.847Z bd1eadb7-ccf4-4a64-ab01-dd1a7146cd71 TEST event queue listener
INFO    2023-03-25T12:41:24.847Z bd1eadb7-ccf4-4a64-ab01-dd1a7146cd71 TEST event queue listener: All info about my context: undefined
INFO    2023-03-25T12:41:24.847Z bd1eadb7-ccf4-4a64-ab01-dd1a7146cd71 TEST event queue listener: All info about my payload: undefined
INFO    2023-03-25T12:41:24.847Z bd1eadb7-ccf4-4a64-ab01-dd1a7146cd71 TEST event queue listener: ERROR in QUEUE

In other words, the queue mechanism is operational, but payload and event are undefined. For me, this has happened ‘suddenly’ and I’ve not been able to link it to something I have done. I get the feeling something goes wrong outside of my influence (forge itself). Which is normally not the case of course (I’m normally to blame myself) but now, I’m no longer certain.

I’m working entirely in development, in case that matters.

I would really appreciate a solution here. I’m completely stuck and have been for days now and I’m totally out of ideas what trial/error I could do. I’ve done everything I could think of, including but not limited to:

  • Change names
  • Uninstall/reinstall
  • Create an entire separate queue/consumer for testing (this code)
  • Try to push from an existing async function in my app instead of via the trigger handler (actually, that was the main use case to start with, the trigger handler is the simplest example I can share)
  • Try to push from a non-async render function using .then()
  • Try to push from a render function using useState()

And much more, but no luck. My queue messages aren’t coming back.

1 Like

I’ve created an entire new minimal implementation based on a new app created with forge create:

manifest.yml:

modules:
  macro:
    - key: queue-test-hello-world
      function: main
      title: queue-test
      description: Inserts Hello world!
  trigger:
    - key: dl-spike-one-trigger-pageviewed
      function: dl-trigger-pageviewed
      events:
        - avi:confluence:viewed:page
  function:
    - key: main
      handler: index.run
    - key: dl-trigger-pageviewed
      handler: index.dl_handleTriggerPageViewed
    - key: test-listen
      handler: index.testQueueHandler
  consumer:
    - key: test-consumer
      queue: test-updates
      resolver:
        function: test-listen
        method: test-event-listener
app:
  id: ari:cloud:ecosystem::app/<your id here>
permissions:
  scopes:
    - read:confluence-content.summary
    - read:confluence-content.all
    - read:page:confluence
    - storage:app
    - read:space:confluence
    - read:confluence-props
    - write:confluence-props

src/index.jsx:

import ForgeUI, { render, Fragment, Macro, Text } from "@forge/ui";
import { Queue } from '@forge/events';
import Resolver from "@forge/resolver";

const testQueue = new Queue({ key: 'test-updates' });

const App = () => {
  return (
    <Fragment>
      <Text>Hello world!</Text>
    </Fragment>
  );
};

export const run = render(
  <Macro
    app={<App />}
  />
);

const testResolver = new Resolver();
testResolver.define("test-event-listener", async ({ eventPayload, eventContext }) => {
  console.log(`TEST event queue listener`);
  if (eventPayload === undefined || eventContext === undefined) {
    console.log(`TEST event queue listener: ERROR in QUEUE`);
   }
   else {
    console.log(`TEST event queue listener: All info about my context: ${JSON.stringify(eventContext, null, 2)}`);
    console.log(`TEST event queue listener: All info about my payload: ${JSON.stringify(eventPayload, null, 2)}`);
   }
});
export const testQueueHandler = testResolver.getDefinitions();

export async function dl_handleTriggerPageViewed( event, context) {
  console.log( `dl_handleTriggerPageViewed`);
  await testQueue.push( "Hello, world! I viewed a page!");
};

Logging:

INFO    2023-03-26T13:17:15.421Z aa532218-aaec-4829-a109-afdc513ece3d TEST event queue listener
INFO    2023-03-26T13:17:15.421Z aa532218-aaec-4829-a109-afdc513ece3d TEST event queue listener: ERROR in QUEUE

This has been installed on the same site but used in a different space.

Maybe someone can test this minimal example in their site? Just create a Confluence macro hello world app and replace manifest.yml and index.jsx with the data above (except app: id:). It would really help me to get past this.

@gerb

@GerbenWierda could you try renaming the parameters to

testResolver.define("test-event-listener", async ({ payload, context }) => {
  console.log(`TEST event queue listener`);
  if (payload === undefined || context === undefined) {
    console.log(`TEST event queue listener: ERROR in QUEUE`);
   }
   else {
    console.log(`TEST event queue listener: All info about my context: ${JSON.stringify(context, null, 2)}`);
    console.log(`TEST event queue listener: All info about my payload: ${JSON.stringify(payload, null, 2)}`);
   }
});

it would be consistent with our documentation https://developer.atlassian.com/platform/forge/runtime-reference/async-events-api/
also you could try to print the received object

testResolver.define("test-event-listener", async (arg) => {
  console.log(`TEST event queue listener`, JSON.stringify(arg));
});
1 Like

@TomaszKanafa This actually solves it.

It is rather hard to know what are ‘free choices’ in the documentation and what is not. For me, the function definition was fine and variable names are arbitrary, but apparently this is not the case (maybe because of something in snapshotting??).

Hi @GerbenWierda ,

It is rather hard to know what are ‘free choices’ in the documentation and what is not. For me, the function definition was fine and variable names are arbitrary, but apparently this is not the case (maybe because of something in snapshotting??).

The payload and context variables are not function parameters, but rather fields of a the resolver’s function parameter. Here is an alternate way to write your resolver code:

testResolver.define("test-event-listener", async (queueItem) => {
  const eventPayload = queueItem.payload;
  const eventContext = queueItem.context;
  console.log(`TEST event queue listener...`);
  console.log(`TEST event queue listener: All info about my context: ${JSON.stringify(eventContext, null, 2)}`);
  console.log(`TEST event queue listener: All info about my payload: ${JSON.stringify(eventPayload, null, 2)}`);
  if (eventPayload === undefined || eventContext == undefined) {
    console.log(`TEST event queue listener: ERROR in QUEUE`);
  }
});

Regards,
Dugald

2 Likes