Fire webhooks from internal action matching results - #479
Conversation
| let markdown = '' | ||
|
|
||
| if (tokenParts[0] === 'user') { | ||
| if (tokenParts[1] == 'name') { |
There was a problem hiding this comment.
| if (tokenParts[1] == 'name') { | |
| if (tokenParts[1] === 'name') { |
and a few below, too! Python->JS conversion can get so annoying 😂
| } else { | ||
| const propertyName = `$${tokenParts[1]}` | ||
| const property = event.properties?.[propertyName] | ||
| text = markdown = stringify(property) |
There was a problem hiding this comment.
I'm not sure multiple assignment is equivalent in JS & Python 👀 : https://stackoverflow.com/questions/1758576/multiple-left-hand-assignment-with-javascript
But since text and markdown are already defined, this should be the same?
There was a problem hiding this comment.
👍 Rearranged to avoid weird multiple assigment
| return [messageText, messageMarkdown] | ||
| } | ||
|
|
||
| export class HookCannon { |
There was a problem hiding this comment.
what is Cannon supposed to imply here? (a bit lost with the naming 😅 )
Is it because it finds and fires hooks?
There was a problem hiding this comment.
Literally yes, it definitely may be too "fun" LOL. Feel free to propose something clearer
There was a problem hiding this comment.
As someone who called a thread-killer the Terminator, yes, I'm very happy with a hook cannon 😂 .
Although hookCannon.findAndFireHooks feels like it's a HookCannonAndLogistician.
To make things clearer, perhaps a HookManager? It seems to fit in well with our other managers, and performs a similar role to the others (although most of them fetch, while this one fires)
There was a problem hiding this comment.
Renamed to, drum roll, HookCommander
|
|
||
| if (database === 'clickhouse') { | ||
| expect(queryCounter).toBe(11 + 14 /* event & prop definitions */) | ||
| expect(queryCounter).toBe(10 + 14 /* event & prop definitions */) |
There was a problem hiding this comment.
👀
which query doesn't happen anymore?
There was a problem hiding this comment.
We don't do shouldSendWebhooks anymore 🎉
| expect(organization!.name).toEqual('TEST ORG') | ||
| expect(hub.db.postgresQuery).toHaveBeenCalledTimes(0) | ||
|
|
||
| jest.spyOn(global.Date, 'now').mockImplementation(() => new Date('2020-02-27 11:00:36').getTime()) |
There was a problem hiding this comment.
Is this testing the cache TTL ?
There was a problem hiding this comment.
Yes, this whole organization-manager thing is almost totally copied from TeamManager code, because it's very much alike
neilkakkar
left a comment
There was a problem hiding this comment.
A couple of questions, but otherwise looks good to me!
yakkomajuri
left a comment
There was a problem hiding this comment.
didn't run it yet but mostly looks good - let me try it out
| import { UUID } from './utils/utils' | ||
| import { ActionManager } from './worker/ingestion/action-manager' | ||
| import { ActionMatcher } from './worker/ingestion/action-matcher' | ||
| import { HookCannon } from './worker/ingestion/hooks' |
| switch (typeof value) { | ||
| case 'string': | ||
| return value | ||
| case 'undefined': |
There was a problem hiding this comment.
I think JSON.stringify(undefined) returns "undefined" already
There was a problem hiding this comment.
It in fact returns undefined instead of "undefined", which caught me off guard for a bit 😅
| if (url.includes('discord.com')) { | ||
| return WebhookType.Discord | ||
| } | ||
| return WebhookType.Teams |
There was a problem hiding this comment.
Is this a good approach? Probably legacy so should be fine, but while this might not be harmful, I think I'd like some explicit Teams checking with an additional unknown/unspecified type, just for clarity.
I regularly use https://webhook.site/ for example
There was a problem hiding this comment.
Well, Teams is just the default behavior this way. I don't know what webhook.site accepts and I don't want to support it, so Teams format it is.
There was a problem hiding this comment.
That makes sense yeah - webhook.site is just a webhook dump.
When I added this comment I didn't see what the type was being used for, but yeah, if it's just formatting the message/payload, that's perfectly fine
| const actionName = stringify(action.name) | ||
| let actionMarkdown: string | ||
| if (webhookType === WebhookType.Slack) { | ||
| actionMarkdown = `<${siteUrl}/action/${action.id}|${action.name}>` |
There was a problem hiding this comment.
maybe use actionName instead of action.name here too? (and below)
| } | ||
|
|
||
| export function getTokens(messageFormat: string): [string[], string] { | ||
| const matchedTokens = messageFormat.match(/(?<=\[)(.*?)(?=\])/g) || [] |
There was a problem hiding this comment.
nit: as you know, I always appreciate a regex match example in a comment
|
|
||
| const timeout = timeoutGuard(`Still running "fetchOrganization". Timeout warning after 30 sec!`) | ||
| try { | ||
| const organizationQueryResult = await this.db.postgresQuery( |
There was a problem hiding this comment.
genuine q: we have SQL queries spread in multiple areas of the codebase, db, sql, and individual queries in some files here and there. do we have any sort of convention for where they should be?
There was a problem hiding this comment.
I think for some more bespoke stuff (like TeamManager.updateEventNamesAndProperties), it doesn't make sense to make it common via DB, BUT thanks for bringing this line up – I updated it to use DB.fetchOrganization (and similarly in TeamManager with fetchTeam)
|
Ran this locally. Action calculated fine the first time and sent the relevant webhooks. Second time though the UI told me the action was recalculated but the events didn't update and I didn't get the webhooks. Got some errors for Don't have time to investigate, could be a fluke - just putting it out here for context. |
|
What do you mean by "the events didn't update and I didn't get the webhooks"? You shouldn't get webhooks on action recalculation with this, the way Django does resend webhooks on action recalculation is unintended behavior. |
|
Right yeah, ofc - webhooks should fire as events come in. I guess the correlation I saw was what got me back into the old thinking model. Either way, the action didn't recalculate a second time and I didn't get webhooks for all events that were sent in 🤔 |
|
I'd say feel free to ignore though if this works for you - I did indeed get some webhooks so the system is working |
|
Hm, all OK for me, recalculation should definitely not be affected because that's Django and not touched by PostHog/posthog#4794. I think we should be safe to go ahead |
…ostHog/plugin-server#479) * Fire webhooks from internal action matching results * Add Zapier `postEventToRestHook` * Remove offloading of hooks to Python via Celery * Fire Zapier in the plugin server * Port webhook formatting tests over * Update queryCounter expectations * Update queryCounter expectations again * Fully test and fix webhook formatting * Reorganize hook firing into HookCannon * Remove duplicated test * Test OrganizationManager * Add end-to-end test for action matching saving * Add (almost) end-to-end tests for firing hooks * Run prettier * Rearrange `text = markdown = foo` * Fix `==`s * Rename `HookCannon` * Add TOKENS_REGEX comment * Use `fetchOrganization` and `fetchTeam` from DB
Changes
Resolves #459. Webhooks fired directly here instead of in Python with Celery.
Checklist
Updated Settings section in README.md, if settings are affected