Skip to content

Commit

Permalink
fix: a couple of fixes for intercom integration; improved tests logging
Browse files Browse the repository at this point in the history
  • Loading branch information
vklimontovich committed Nov 30, 2023
1 parent fccd2d2 commit 0da4836
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 66 deletions.
109 changes: 82 additions & 27 deletions libs/core-functions/__tests__/intercom-destination.test.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,60 @@
import { testJitsuFunction, TestOptions } from "./lib/testing-lib";
import { IntercomDestinationCredentials } from "../src/meta";
import IntercomDestination from "../src/functions/intercom-destination";
import { setGlobalLogLevel, setServerLogColoring } from "juava";

setServerLogColoring(true);
setGlobalLogLevel("debug");

test("test", async () => {
if (!process.env.TEST_INTERCOM_DESTINATION_CONFIG) {
console.log("TEST_INTERCOM_DESTINATION_CONFIG is not set, skipping test");
return;
}
const opts: TestOptions<IntercomDestinationCredentials> = {
const email = "[email protected]";
const userId = "user-id-ds";

const workspaceId = "workspace-id-dm";
const workspaceName = "Dunder Mifflin";
const workspaceSlug = "dunder-mifflin";
const name = "Dwight Schrute";

let opts: TestOptions<IntercomDestinationCredentials>;
opts = {
func: IntercomDestination,
configEnvVar: "TEST_INTERCOM_DESTINATION_CONFIG",
events: [
{
type: "identify",
userId: "cleviyagu0000zl13jld7acac",
traits: {
email: "[email protected]",
name: "Vladimir Klimontovich",
},
userId: userId,
traits: { email, name },
timestamp: "2023-11-28T20:37:14.693Z",
sentAt: "2023-11-28T20:37:14.693Z",
messageId: "7qfgopt6mo22xk2tqs0tb",
groupId: "cl9sotck40002tt2b18i2x430",
groupId: workspaceId,
context: {},
receivedAt: "2023-11-28T20:37:14.799Z",
},
{
type: "group",
groupId: "cl9sotck40002tt2b18i2x430",
traits: {
workspaceSlug: "jitsu",
workspaceName: "Jitsu Playground",
workspaceId: "cl9sotck40002tt2b18i2x430",
type: "track",
event: "user_created",
properties: {},
userId: userId,
timestamp: "2023-11-29T16:55:50.255Z",
sentAt: "2023-11-29T16:55:50.255Z",
messageId: "22ccyzg8enx2duj3bcit8h",
writeKey: "FDiExsmGYePJa651vnN7LyhMQYq972s1:***",
context: {
traits: { email, name, externalId: `ext-${userId}` },
page: {},
clientIds: {},
campaign: {},
},
},
{
type: "group",
groupId: workspaceId,
traits: { workspaceSlug, workspaceName, workspaceId, name: workspaceName },
timestamp: "2023-11-28T20:37:14.673Z",
sentAt: "2023-11-28T20:37:14.673Z",
messageId: "1xdx6pryjnuqgi4jz362j",
Expand All @@ -41,30 +63,63 @@ test("test", async () => {
},
{
type: "track",
event: "workspace_access",
properties: {
workspaceSlug: "jitsu",
workspaceName: "Jitsu Playground",
workspaceId: "cl9sotck40002tt2b18i2x430",
event: "workspace_created",
properties: {},
userId,
timestamp: "2023-11-29T19:02:36.535Z",
sentAt: "2023-11-29T19:02:36.535Z",
messageId: "223drh0xi901pujmvl2kds",
writeKey: "FDiExsmGYePJa651vnN7LyhMQYq972s1:***",
groupId: "clpk4wd6n0000l90fmzoahn63",
context: {
traits: {
workspaceName,
workspaceId,
email,
name,
externalId: `ext-${userId}`,
},
page: {},
clientIds: {},
campaign: {},
},
receivedAt: "2023-11-29T19:02:36.643Z",
},
{
type: "page",
userId,
groupId: workspaceId,
timestamp: "2023-11-29T19:02:36.152Z",
messageId: "1m6c2acu28b1bt4eak2qk1",
context: {
traits: {
email,
name,
externalId: `ext-${userId}`,
},
page: {
title: "Jitsu",
url: "https://use.jitsu.com/",
path: `/${workspaceSlug}`,
},
},
userId: "cleviyagu0000zl13jld7acac",
},
{
type: "track",
event: "workspace_access",
properties: { workspaceSlug, workspaceName, workspaceId },
userId,
anonymousId: "7c3f1bac-2e04-4ebf-8d34-95d56fb126ac",
timestamp: "2023-11-28T20:36:42.201Z",
sentAt: "2023-11-28T20:36:42.201Z",
messageId: "24zebq8rj3414ubowikeup",
groupId: "cl9sotck40002tt2b18i2x430",
groupId: workspaceId,
context: {
library: {
name: "@jitsu/js",
version: "0.0.0",
},
traits: {
workspaceSlug: "jitsu",
workspaceName: "Jitsu Playground",
workspaceId: "cl9sotck40002tt2b18i2x430",
email: "[email protected]",
name: "Vladimir Klimontovich",
},
traits: { workspaceSlug, workspaceName, workspaceId, email, name },
page: {},
clientIds: {},
campaign: {},
Expand Down
29 changes: 24 additions & 5 deletions libs/core-functions/__tests__/lib/testing-lib.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AnalyticsInterface, AnalyticsServerEvent } from "@jitsu/protocols/analytics";
import { requireDefined } from "juava";
import { getLog, logFormat, requireDefined } from "juava";
import { AnyEvent, EventContext, FuncReturn, FunctionContext, JitsuFunction } from "@jitsu/protocols/functions";
import nodeFetch from "node-fetch-commonjs";
import { createStore } from "./mem-store";
Expand All @@ -20,6 +20,20 @@ export type TestOptions<T = any> = {
export function prefixLogMessage(level: string, msg: any) {
return `[${level}] ${msg}`;
}
const testLogger = getLog("function-tester");

function toDate(timestamp?: string | number | Date): Date {
if (!timestamp) {
return new Date();
}
if (typeof timestamp === "string") {
return new Date(timestamp);
} else if (typeof timestamp === "number") {
return new Date(timestamp);
} else {
return timestamp;
}
}

export async function testJitsuFunction<T = any>(opts: TestOptions<T>): Promise<FuncReturn> {
const config =
Expand All @@ -36,15 +50,20 @@ export async function testJitsuFunction<T = any>(opts: TestOptions<T>): Promise<
const func = opts.func;
const fetch = nodeFetch;
const log = {
info: (msg: any, ...args: any[]) => console.log(prefixLogMessage("INFO", msg), args),
error: (msg: any, ...args: any[]) => console.error(prefixLogMessage("ERROR", msg), args),
debug: (msg: any, ...args: any[]) => console.debug(prefixLogMessage("DEBUG", msg), args),
warn: (msg: any, ...args: any[]) => console.warn(prefixLogMessage("WARN", msg), args),
info: (msg: any, ...args: any[]) => testLogger.atInfo().log(msg, ...args),
error: (msg: any, ...args: any[]) => testLogger.atError().log(msg, ...args),
debug: (msg: any, ...args: any[]) => testLogger.atDebug().log(msg, ...args),
warn: (msg: any, ...args: any[]) => testLogger.atWarn().log(msg, ...args),
};

let res: AnyEvent[] = null;
for (const event of events) {
try {
testLogger
.atInfo()
.log(
`📌Testing ${logFormat.bold(event.event || event.type)} message of ${toDate(event.timestamp).toISOString()}`
);
const r = await func(event, {
props: config,
fetch,
Expand Down
2 changes: 1 addition & 1 deletion libs/core-functions/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ module.exports = {
testMatch: ["**/__tests__/**/*.test.ts"],
testEnvironment: "node",
runner: "jest-runner",
testMatch: ["**/__tests__/**/*.test.ts"],
"setupFiles": ["./jest.setup.js"]
};
8 changes: 8 additions & 0 deletions libs/core-functions/jest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

global.console = {
log: message => process.stdout.write(message + '\n'),
error: console.error,
warn: console.warn,
info: console.info,
debug: console.debug,
};
17 changes: 9 additions & 8 deletions libs/core-functions/src/functions/intercom-destination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { FullContext, JitsuFunction } from "@jitsu/protocols/functions";
import { AnalyticsServerEvent } from "@jitsu/protocols/analytics";
import { IntercomDestinationCredentials } from "../meta";
import { JsonFetcher, jsonFetcher } from "./lib/json-fetch";
import omit from "lodash/omit";
import { isEqual, pick } from "lodash";
import { requireDefined } from "juava";

Expand Down Expand Up @@ -210,15 +209,16 @@ async function createOrUpdateContact(event: AnalyticsServerEvent, { jsonFetch, l
Accept: "application/json",
"Content-Type": "application/json",
},
body: forComparison.external_id ? omit(newContact, "external_id") : newContact,
body: newContact,
//body: forComparison.external_id ? omit(newContact, "external_id") : newContact,
});
}
return contact.id;
}
}

const IntercomDestination: JitsuFunction<AnalyticsServerEvent, IntercomDestinationCredentials> = async (event, ctx) => {
const jsonFetch = jsonFetcher(ctx.fetch);
const jsonFetch = jsonFetcher(ctx.fetch, { log: ctx.log, debug: true });
let intercomContactId: string | undefined;
let intercomCompanyId: string | undefined;
if (event.type === "identify") {
Expand All @@ -230,7 +230,9 @@ const IntercomDestination: JitsuFunction<AnalyticsServerEvent, IntercomDestinati
if (!intercomCompanyId) {
intercomCompanyId = (await getCompanyByGroupId(event.groupId, { ...ctx, jsonFetch }))?.id;
if (!intercomCompanyId) {
ctx.log.info(`Intercom company ${event.groupId} not found`);
ctx.log.warn(
`Intercom company ${event.groupId} not found. It's coming from ${event.type} event. Following .group() call might fix it`
);
return;
}
}
Expand All @@ -254,19 +256,19 @@ const IntercomDestination: JitsuFunction<AnalyticsServerEvent, IntercomDestinati
});
}

if (event.type !== "identify" && event.type !== "group" && event.type !== "page") {
if (event.type !== "identify" && event.type !== "group") {
const email = event.context?.traits?.email || event.traits?.email;
const userId = event.userId;
if (!email && !userId) {
//doesn't make sense to send event without email or userId, Intercom won't know how to link it to a user
}
const intercomEvent = {
type: "event",
event_name: event.type === "track" ? event.event : event.type,
event_name: event.type === "track" ? event.event : event.type === "page" ? "page-view" : "unknown",
created_at: Math.round(toDate(event.timestamp).getTime() / 1000),
user_id: userId || undefined,
email: email || undefined,
metadata: event.properties,
metadata: { ...event.properties, url: event.context?.page?.url || undefined },
};
await jsonFetch(`https://api.intercom.io/events`, {
headers: {
Expand All @@ -276,7 +278,6 @@ const IntercomDestination: JitsuFunction<AnalyticsServerEvent, IntercomDestinati
},
body: intercomEvent,
});
ctx.log.debug(`Intercom event:\n${JSON.stringify(intercomEvent, null, 2)}`);
}
};

Expand Down
22 changes: 15 additions & 7 deletions libs/core-functions/src/functions/lib/json-fetch.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FetchOpts, FetchType } from "@jitsu/protocols/functions";
import { FetchOpts, FetchType, FunctionLogger } from "@jitsu/protocols/functions";

export type JsonFetchOpts = Omit<FetchOpts, "body"> & {
body?: any;
Expand Down Expand Up @@ -34,7 +34,10 @@ export class JsonFetchError extends Error {
}
}

export function jsonFetcher(fetch: FetchType): JsonFetcher {
export function jsonFetcher(
fetch: FetchType,
{ log, debug }: { log: FunctionLogger; debug?: boolean } = { log: console }
): JsonFetcher {
return async (url: string, options?: JsonFetchOpts) => {
const method = options?.method || (options?.body ? "POST" : "GET");
const bodyStr =
Expand All @@ -50,16 +53,21 @@ export function jsonFetcher(fetch: FetchType): JsonFetcher {
body: bodyStr,
});
let responseText = await response.text();
if (debug) {
const message = `${method} ${url}${response.ok ? "🟢" : "🔴"}${response.status} ${response.statusText}.${
bodyStr ? `\n📨Request body:\n${prettifyJson(bodyStr)}` : ""
}\n📩Response body${responseText ? `: \n${prettifyJson(responseText)}` : " is empty"}`;
if (response.ok) {
log.debug(message);
} else {
log.warn(message);
}
}
if (!response.ok) {
if (responseText.length > maxResponseLen) {
responseText =
responseText.substring(0, maxResponseLen) + "...(truncated, total length: " + responseText.length + ")";
}
console.log(
`Request error: ${method} ${url} failed with status ${response.status} ${response.statusText}: ${prettifyJson(
responseText
)}${bodyStr ? `. Request body: ${prettifyJson(bodyStr)}` : ""}`
);
throw new JsonFetchError(
response.status,
`${method} ${url} failed with status ${response.status} ${response.statusText}: ${responseText}`
Expand Down
31 changes: 20 additions & 11 deletions libs/juava/src/log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function getComponent() {
}

let globalLogLevel: LogLevel = "info" as LogLevel;
let enableServerLogsColoring: boolean = false;
let enableServerLogsColoring: boolean = !(process.env.CI === "1" || process.env.CI === "true");
let enableJsonFormat: boolean = false;

export function setGlobalLogLevel(level: LogLevel) {
Expand Down Expand Up @@ -83,14 +83,23 @@ function inBrowser() {
return typeof window !== "undefined";
}

function colorConsoleMessage(color: Color, str: string): string {
return !inBrowser() && enableServerLogsColoring && color !== undefined
? str
.split("\n")
.map(line => `${colorsAnsi[color]}${line}\x1b[0m`)
.join("\n")
: str;
}
export const logFormat = {
bold(msg: string): string {
return msg;
},
italic(msg: string): string {
return msg;
},
color(color: Color, str: string): string {
//to implement bold and italic we should split string by [0m and apply bold/italic to each part
return !inBrowser() && enableServerLogsColoring && color !== undefined
? str
.split("\n")
.map(line => `${colorsAnsi[color]}${line}\x1b[0m`)
.join("\n")
: str;
},
};

//process.stdout is not available on Vercel's edge runtime
const writeln = process.stdout
Expand Down Expand Up @@ -125,9 +134,9 @@ function dispatch(msg: LogMessage) {
if (enableJsonFormat) {
writeln(JSON.stringify({ time: msg.date, level: msg.level, msg: lines.join("\n") }));
} else {
const border = "";
const border = ""; // = "|";
const messageFormatted = lines.join(`\n${border} `);
writeln(colorConsoleMessage(levelColor, messageFormatted));
writeln(logFormat.color(levelColor, messageFormatted));
}
}
msg.dispatched = true;
Expand Down
Loading

2 comments on commit 0da4836

@vercel
Copy link

@vercel vercel bot commented on 0da4836 Nov 30, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

new-jitsu-ee-api – ./webapps/ee-api

new-jitsu-ee-api.staging.jitsu.com
new-jitsu-ee-api-git-newjitsu.staging.jitsu.com
ee.jitsu.dev
onetag-ee-api.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 0da4836 Nov 30, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

new-jitsu – ./webapps/console

ag.ru
logu.au
ozon.ru
sse.ere
erxes.io
baidu.dom
sambla.se
bobsec.com
sambla.com
agro4u.life
bluetick.ai
protontv.eu
t.quenti.io
alicesec.com
dev.aclis.io
docs.dh19.de
docs.dh19.eu
joseviso.com
mydomain.dom
t.democo.dev
t.shoppub.io
t2.jitsu.com
timeplus.com
zoopsign.com
*.d.jitsu.com
beta.mitzu.io
d.versatus.io
data.light.so
data.loudy.co
data.schej.it
imusician.app
imusician.pro
jitsu.logu.au
jitsu.www1.ru
t.thequack.ai
thinkr.com.br
use.jitsu.com
usepolygon.io
www.sambla.se
ajewellers.com
data.uselog.io
gpt.whatfa.com
sidetrekai.com
t.papermark.io
t.saasmonk.app
use2.jitsu.com
www.kellen.top
*.dataspecc.com
app.bluetick.ai
caddy.jitsu.com
data.askloan.tw
enterticket.com
events.mitzu.io
jitsu.efeer.com
jitsu.ivve.tech
krestomatio.com
sevenbillion.co
xrt.webxr.tools
www.sevenbillion.co
analytics.mtrsvc.com
data.embeddables.com
mercury.stagehub.com
store.sidetrekai.com
teslahenry.github.io
data.hogarlylabs.tech
event.clickncruise.hu
event.clickncruise.ro
test-domain.jitsu.com
teste.fazcomex.com.br
analytics.dev.knekt.io
loraboutiquedental.com
notion.twelftree.co.uk
dev-portal.zoopsign.com
event.tradejobsnz.co.nz
savvy-replay.jitsu.tech
data.analytics-smart.com
event.clickncruise.co.uk
jt.fairhopeweb.github.io
savvy-replay2.jitsu.tech
savvy-replay3.jitsu.tech
savvy-replay4.jitsu.tech
track.alquimiaweb.com.br
track.pressance-group.jp
track.uniquecafes.com.br
colectha.agenciavoolu.com
kolectha.agenciavoolu.com
lp.loraboutiquedental.com
stage-portal.zoopsign.com
new-jitsu.staging.jitsu.com
lodercom-colectha.voolu.shop
warehouse1.trendstyle.com.au
d0.livingdesignsfurniture.com
jitsu.precisaosistemas.com.br
analytics.inspiresolutions.app
canvas.livingdesignsfurniture.com
analytics.dev.inspiresolutions.app
clm2jikrm00002v6r5l6niws3.d.jitsu.com
new-jitsu-git-newjitsu.staging.jitsu.com
3000-rajaraodv-customerdemo-nmpsqwflswt.ws-us102.gitpod.io
new.jitsu.dev

Please sign in to comment.