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

Support destination filters for device mode action destinations #597

Merged
merged 21 commits into from
Oct 12, 2022

Conversation

danieljackins
Copy link
Contributor

@danieljackins danieljackins commented Sep 15, 2022

This PR allows destination filters to work with action destinations, by adding destination middleware support to them.

This change was tested by adding destination filters to an action destination and making sure events and properties were dropped properly.

[x] I've included a changeset (psst. run yarn changeset. Read about changesets here).

@changeset-bot
Copy link

changeset-bot bot commented Sep 15, 2022

🦋 Changeset detected

Latest commit: 91f0d8e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@segment/analytics-next Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@danieljackins danieljackins requested review from chrisradek and a team September 22, 2022 15:21
Copy link
Contributor

@chrisradek chrisradek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall I think this looks great, excited to see this middleware support added to action destinations!

Things I wanted to check:

  1. We may need to move alternativeNames to the plugin interface then include it in our checks here:
    return (
    denyList[p.name] ??
    (p.name === 'Segment.io' ? true : denyList.All) !== false
    )

    This way if someone is currently disabling/enabling based on the current plugin name (like FullStory (actions) trackEvent) it still works.
  2. Can we apply the above logic for logging/metrics as well?
  3. I commented that we should only apply destination filters on destination plugins - wonder if that should apply for all destination middleware as well. What are your thoughts there given action destinations can have non-destination actions.

.changeset/silver-mugs-provide.md Outdated Show resolved Hide resolved
packages/browser/src/plugins/ajs-destination/index.ts Outdated Show resolved Hide resolved
packages/browser/src/plugins/remote-loader/index.ts Outdated Show resolved Hide resolved
packages/browser/src/plugins/remote-loader/index.ts Outdated Show resolved Hide resolved
packages/browser/src/plugins/remote-loader/index.ts Outdated Show resolved Hide resolved
packages/browser/src/plugins/remote-loader/index.ts Outdated Show resolved Hide resolved

return new Context(modifiedEvent)
}

Copy link
Contributor

@silesky silesky Sep 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an opportunity here to be more DRY and shave off some bytes?

Something like:

private _createMethod(methodName: 'track' | 'page' | 'identify' | 'alias' | 'group' | 'screen') {
    return async (ctx: Context): Promise<Context> => {
      if (!this.action[methodName]) return ctx

      const transformedContext = await this.transform(ctx)
      await this.action[methodName](transformedContext)

      return ctx
    }
  }
  
  alias = _createMethod('track')
  group = _createMethod('group')
   ... etc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks!

@danieljackins
Copy link
Contributor Author

Overall I think this looks great, excited to see this middleware support added to action destinations!

Things I wanted to check:

  1. We may need to move alternativeNames to the plugin interface then include it in our checks here:

    return (
    denyList[p.name] ??
    (p.name === 'Segment.io' ? true : denyList.All) !== false
    )

    This way if someone is currently disabling/enabling based on the current plugin name (like FullStory (actions) trackEvent) it still works.

  2. Can we apply the above logic for logging/metrics as well?

  3. I commented that we should only apply destination filters on destination plugins - wonder if that should apply for all destination middleware as well. What are your thoughts there given action destinations can have non-destination actions.

  1. Done!
  2. Not exactly sure what you're getting at here
  3. I think it would be safer to not have that restriction just in case they're trying to use them on 'enrichment' actions or something else

@chrisradek
Copy link
Contributor

  1. Not exactly sure what you're getting at here

The context keeps logs/stats about which plugins were ran. Currently you can see which actions are ran and some stats about them. With these changes I think we'll see the wrapper plugin's name instead, so Fullstory instead of Fullstory (actions) trackEvent - also makes it look like we're running the same plugin multiple times if a destination has more than 1 action.

from our dev environment:
image

if (
routing.length &&
routingMiddleware &&
plugin.type === 'destination'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth adding a comment that we only want to apply destination filters to destination actions so that we don't cause issues for hybrid action destinations (where we simply augment the event and send it to the cloud rather than directly to the destination)

I see future selves forgetting why we limited this 😄

}

unload(ctx: Context, analytics: Analytics): Promise<unknown> | unknown {
return this.action.unload && this.action.unload(ctx, analytics)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just in case you aren’t a Typescript nerd, you can shorten this to “return this.action.unload?.(ctx, analytics)” IIRC

@@ -232,9 +232,16 @@ export class EventQueue extends Emitter {
return true
}

const alternativeNameMatch =
p.alternativeNames &&
p.alternativeNames.filter((name) =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional changing nitpick! p.alternativeNames?.filter would be a bit nicer IMO

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I typically agree - though the size savings are not insignificant due to the transpilations that occur (assuming variables/field names get minified). See this as an example. (Just a tradeoff - if it makes the code more readable then go with the optional chaining - I was just surprised how much larger the transpiled code was)

Copy link
Contributor

@silesky silesky Sep 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting... that's a nice data point. I think I still prefer the optional chain... I wonder if babel would do a better job?

We use optional chaining all over, so IMO we should just use it and not care about size and hope the tooling gets better (or attempt to apply more optimization). or, if we're going to ban it, we should talk about it and then ban it and do a global refactor and use a lint rule to enforce. But If we're going to use it once I'd prefer to use it liberally for readability and then at some future point we target it for cost savings.

private async transform(ctx: Context): Promise<Context> {
const modifiedEvent = await applyDestinationMiddleware(
this.name,
klona(ctx.event),
Copy link
Contributor

@silesky silesky Oct 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't aware that applyDestinationMiddleware mutated the event

  • Can we avoid that? (If so, we'd want to clone the event inside of the applyDestinationMiddleware function, right?)
  • Are there any tests that would fail if klona wasn't included?

This is the kind of code smell stuff that I think can get confusing later if the why is not documented (in tests, comments, etc).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do need to mutate the event I believe, but I moved the cloning logic into applyDestinationMiddleware and added a test that will fail if the original context gets modified (ie. if klona gets removed) 👍

Copy link
Contributor

@chrisradek chrisradek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks great! Just had 1 question that might be an issue.

packages/browser/src/browser/index.ts Show resolved Hide resolved
// Explicit integration option takes precedence, `All: false` does not apply to Segment.io
return (
denyList[p.name] ??
alternativeNameMatch ??
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't alternativeNameMismatch an array of strings here? I think that'd be evaluated as a truthy value so we'd never hit the next conditional, or am I missing something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch - fixed the logic and added a new test

danieljackins and others added 2 commits October 12, 2022 10:54
Co-authored-by: Christopher Radek <14189820+chrisradek@users.noreply.github.com>
Copy link
Contributor

@chrisradek chrisradek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me! :shipit:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants