-
Notifications
You must be signed in to change notification settings - Fork 193
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ARC-2316: use backend to send analytics from SPA (#2333)
* ARC-2316: use backend to send analytics from SPA * ARC-2316: fix lint * ARC-2316: fix tests * ARC-2316: address comments * ARC-2316: missing tests * ARC-2316: export types * ARC-2316: fix imports * ARC-2316: fix build
- Loading branch information
Showing
40 changed files
with
689 additions
and
321 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import { analyticsProxyClient } from "./analytics-proxy-client"; | ||
import { waitFor } from "@testing-library/react"; | ||
import * as nock from "nock"; | ||
import { axiosRest } from "../api/axiosInstance"; | ||
import { ScreenEventProps, TrackEventProps, UIEventProps } from "./types"; | ||
|
||
const MY_TOKEN = "myToken"; | ||
|
||
/* eslint-disable @typescript-eslint/no-explicit-any*/ | ||
(global as any).AP = { | ||
getLocation: jest.fn(), | ||
context: { | ||
getContext: jest.fn(), | ||
getToken: (callback: (token: string) => void) => { | ||
callback(MY_TOKEN); | ||
} | ||
}, | ||
navigator: { | ||
go: jest.fn(), | ||
reload: jest.fn() | ||
} | ||
}; | ||
|
||
describe("analytics-proxy-client", () => { | ||
const BASE_URL = "http://localhost"; | ||
const ANALYTICS_PROXY_URL = "/rest/app/cloud/analytics-proxy"; | ||
const UI_PROPS: UIEventProps = { | ||
actionSubject: "startToConnect", action: "clicked" | ||
}; | ||
const TRACK_PROPS: TrackEventProps = { | ||
actionSubject: "finishOAuthFlow", action: "success" | ||
}; | ||
const SCREEN_PROPS: ScreenEventProps = { | ||
name: "StartConnectionEntryScreen" | ||
}; | ||
const ATTRS = { myAttr: "foobar" }; | ||
|
||
beforeEach(() => { | ||
axiosRest.defaults.baseURL = BASE_URL; | ||
|
||
nock(BASE_URL) | ||
.options(ANALYTICS_PROXY_URL) | ||
.reply(200, undefined, { | ||
"Access-Control-Allow-Methods": "OPTIONS, GET, HEAD, POST", | ||
"Access-Control-Allow-Origin": "*", | ||
"Access-Control-Allow-Headers": "Authorization", | ||
"Allow": "OPTIONS, GET, HEAD, POST" | ||
}); | ||
}); | ||
|
||
afterEach(() => { | ||
axiosRest.defaults.baseURL = undefined; | ||
}); | ||
|
||
it("add source to UI events", async () => { | ||
const expectedNock = nock(BASE_URL) | ||
.post(ANALYTICS_PROXY_URL, { | ||
eventType: "ui", | ||
eventProperties: { | ||
...UI_PROPS, | ||
source: "spa" | ||
}, | ||
eventAttributes: ATTRS | ||
}) | ||
.matchHeader("Authorization", MY_TOKEN) | ||
.reply(202, {}); | ||
|
||
analyticsProxyClient.sendUIEvent(UI_PROPS, ATTRS); | ||
|
||
await waitFor(() => { | ||
expect(expectedNock.isDone()).toBeTruthy(); | ||
}); | ||
}); | ||
|
||
it("add source to Track events", async () => { | ||
const expectedNock = nock(BASE_URL) | ||
.post(ANALYTICS_PROXY_URL, { | ||
eventType: "track", | ||
eventProperties: { | ||
...TRACK_PROPS, | ||
source: "spa" | ||
}, | ||
eventAttributes: ATTRS | ||
}) | ||
.matchHeader("Authorization", MY_TOKEN) | ||
.reply(202, {}); | ||
|
||
analyticsProxyClient.sendTrackEvent(TRACK_PROPS, ATTRS); | ||
|
||
await waitFor(() => { | ||
expect(expectedNock.isDone()).toBeTruthy(); | ||
}); | ||
}); | ||
|
||
it("sends Screen event as it is", async () => { | ||
const expectedNock = nock(BASE_URL) | ||
.post(ANALYTICS_PROXY_URL, { | ||
eventType: "screen", | ||
eventProperties: SCREEN_PROPS, | ||
eventAttributes: ATTRS | ||
}) | ||
.matchHeader("Authorization", MY_TOKEN) | ||
.reply(202, {}); | ||
|
||
analyticsProxyClient.sendScreenEvent(SCREEN_PROPS, ATTRS); | ||
|
||
await waitFor(() => { | ||
expect(expectedNock.isDone()).toBeTruthy(); | ||
}); | ||
}); | ||
|
||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { AnalyticClient, ScreenEventProps, TrackEventProps, UIEventProps } from "./types"; | ||
import { axiosRest } from "../api/axiosInstance"; | ||
import { reportError } from "../utils"; | ||
const sendAnalytics = (eventType: string, eventProperties: Record<string, unknown>, eventAttributes?: Record<string, unknown>) => { | ||
axiosRest.post(`/rest/app/cloud/analytics-proxy`, | ||
{ | ||
eventType, | ||
eventProperties, | ||
eventAttributes | ||
}).catch(reportError); | ||
}; | ||
export const analyticsProxyClient: AnalyticClient = { | ||
sendScreenEvent: function(eventProps: ScreenEventProps, attributes?: Record<string, unknown>) { | ||
sendAnalytics("screen", eventProps, attributes); | ||
}, | ||
sendUIEvent: function (eventProps: UIEventProps, attributes?: Record<string, unknown>) { | ||
sendAnalytics("ui", { | ||
...eventProps, | ||
source: "spa" | ||
}, attributes); | ||
}, | ||
sendTrackEvent: function (eventProps: TrackEventProps, attributes?: Record<string, unknown>) { | ||
sendAnalytics("track", { | ||
...eventProps, | ||
source: "spa" | ||
}, attributes); | ||
} | ||
}; |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,35 @@ | ||
import { AnalyticClient, ScreenNames } from "./types"; | ||
import { useEffect } from "react"; | ||
|
||
import { loadSoxAnalyticClient } from "./sox-analytics-client"; | ||
import { noopAnalyticsClient } from "./noop-analytics-client"; | ||
import { analyticsProxyClient } from "./analytics-proxy-client"; | ||
|
||
const analyticsClient: AnalyticClient = loadSoxAnalyticClient() || noopAnalyticsClient; | ||
const analyticsClient: AnalyticClient = analyticsProxyClient; | ||
|
||
export default analyticsClient; | ||
|
||
export const useEffectScreenEvent = (name: ScreenNames, attributes?: Record<string, string | number>) => { | ||
let lastSent = ""; | ||
|
||
//stringify so that in the useEffect dependency array it is comparing real content, instead of object instance refence. | ||
//Otherwise it may cause unnecessary fireing to the analytics backend when attribute object instance changed, skewing our analytics dashboard | ||
const jsonStrAttr = JSON.stringify(attributes || {}); | ||
export const useEffectScreenEvent = (name: ScreenNames, attributes?: Record<string, unknown>) => { | ||
// TODO: add better serialization because JSON.stringify() does not guarantee the ordering of the elements, which | ||
// means theoretically it could output different strings for the same object | ||
const attributesSerialized = JSON.stringify(attributes || {}); | ||
|
||
useEffect(() => { | ||
// TODO: for some reason it may fire several events for the same event. Please fix! | ||
if (lastSent === name + attributesSerialized) { | ||
return; | ||
} | ||
analyticsClient.sendScreenEvent({ | ||
name, | ||
attributes: { | ||
...JSON.parse(jsonStrAttr) | ||
} | ||
}); | ||
}, [ name, jsonStrAttr]); | ||
name | ||
// To make lint happy, otherwise (if attributes) are used it complains that it is not in the list of dependencies | ||
}, JSON.parse(attributesSerialized)); | ||
|
||
lastSent = name + attributesSerialized; | ||
|
||
// Use serialized attributes to make useEffect() compare real content instead of references to an object. | ||
// Otherwise, it may fire unnecessary events when the reference is changed but not the content, | ||
// thus skewing the dashboards. | ||
}, [ name, attributesSerialized ]); | ||
|
||
}; | ||
|
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.