Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to write an enrichment plugin that fires after a remote integration? #1120

Closed
kwalker3690 opened this issue Jul 22, 2024 · 4 comments
Closed
Labels
bug Something isn't working

Comments

@kwalker3690
Copy link

I'm writing an 'enrichment' plugin that should add a property to any event that comes through track. However, the value of this property relies on a value added to events via an integration (the Amplitude Actions integration to be specific). Here is the code I'm using to register my plugin:

const testPlugin: Plugin = {
  name: "Session Replay Events",
  type: "enrichment",
  version: "1.0.0",

  isLoaded: () => true,
  load: loadMethod,
  track: trackMethod,
};

SegmentAnalytics.load({
  writeKey: <api key>,
  plugins: [testPlugin],
});

When debugging this, I've inspected the code and observed that testPlugin (red arrow in below screenshot) is always added before the Actions enrichment plugin (blue arrow in below screenshot)

Screenshot 2024-07-22 at 2 20 39 PM

I believe the ordering is occurring due to the ordering of plugins vs remotePlugins in this code snippet.

I'd like to have my plugin be registered in load() so that we can enrich all events, including those emitted immediately on page load (as opposed to registering my plugin after load(), which means it will not capture those initial events).

Because of the ordering, when I console log via my track method, the ctx.event.integrations object is empty, as the Amplitude Actions integration has not yet been processed:
image

Is there any way I can have my testPlugin be registered in load(), but have its track calls execute after those from remotely loaded plugins?

@silesky
Copy link
Contributor

silesky commented Jul 23, 2024

@kwalker3690 thanks for the level of detail. That's odd -- that looks like a bug, as passing it into plugins and calling register should behave identically. Regardless, .register does not get less priority than passing it directly into the plugins array. Both should capture page calls and other buffered events -- no events should be dispatched (whether buffered events or not) until enrichment plugin's .load method has resolved.

I just tested:

export const analytics = new AnalyticsBrowser();
analytics.register({
  type: "enrichment",
  name: "My Enrichment Plugin",
  version: "1.0.0",
  load: () => {
    console.log("My Enrichment Plugin loaded");
    return Promise.resolve();
  },
  page: (ctx) => {
    console.log("page");
    console.log(ctx.event.integrations);
    return ctx;
  },
  track: (ctx) => {
    console.log(ctx.event.integrations);
    return ctx;
  },
  isLoaded: () => true,
});


analytics.page();

analytics.load({ writeKey: "9lSrez3BlfLAJ7NOChrqWtILiATiycoc" });

image

@kwalker3690
Copy link
Author

@silesky thanks so much for your prompt and thorough response! You're right, rewriting my test plugin to register in advance of load solves the issue of the ordering of the plugins. There is another complication here I don't think I spelled out well in my initial post, which is that in our use case, we have an async operation in load that takes a bit of time (we are making an API request). This means that we need to wait for our async load to complete before any events should be processed, otherwise we don't have the right data for manipulating the event properties (in addition to the Actions Amplitude data received through the integration). I've made this gist to better illustrate our issue: https://gist.github.com/kwalker3690/b59e7c00be8285be98b314a58b43bd30

Here's the console output for calling register before load:
image

Oberve that the plugin load doesn't end until after the Immediate Event has fired. In comparison, when the plugin is registered as a part of the main SDK load method, the plugin load method is awaited correctly, but the integrations data is not populated:
image

Please let me know if I'm missing a nuance here! I'd be very happy if this isn't a bug I'm bothering you all with and instead is something I can solve with a tweak in ordering. Thanks!

@silesky
Copy link
Contributor

silesky commented Jul 23, 2024

@kwalker3690 That is odd behavior -- while load is always invoked before events are dispatched, at least for a given plugin, you would expect .load to always resolve before any of its own methods are called. Otherwise, there's no point in .load being an async function at all. Will make a note to look into this.

In the meantime, this pattern should still work for your use case:

  class MyPlugin implements Plugin {
    readonly type = "enrichment";
    readonly name = "Enrichment Plugin";
    readonly version = "0.0.0";
    private data!: Promise<string> 
    
    async load() {
      
      // fetch async data
      this.data = new Promise((resolve) => setTimeout(() => resolve('data'), 1000));
      
      return Promise.resolve();
    }
    
    private async enrich(ctx: Context) {
      ctx.event.context!.data = await this.data;
      console.log('enriched context:', ctx.event.integrations)
      return ctx
    }

    isLoaded() {
      return true;
    }
    
    track = this.enrich;
    page = this.enrich;
    alias = this.enrich;
    group = this.enrich;
    identify = this.enrich;
    screen = this.enrich;
  }
  
 analytics.register(new MyPlugin())

@kwalker3690
Copy link
Author

Amazing - I need to do a little more validation but at first glance I think this approach should do the trick! Thanks for working through this with me

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants