diff --git a/packages/builder/src/analytics/PosthogClient.js b/packages/builder/src/analytics/PosthogClient.ts similarity index 63% rename from packages/builder/src/analytics/PosthogClient.js rename to packages/builder/src/analytics/PosthogClient.ts index f541b69b13..fe41989a66 100644 --- a/packages/builder/src/analytics/PosthogClient.js +++ b/packages/builder/src/analytics/PosthogClient.ts @@ -1,9 +1,12 @@ import posthog from "posthog-js" -import { Events } from "./constants" export default class PosthogClient { - constructor(token) { + token: string + initialised: boolean + + constructor(token: string) { this.token = token + this.initialised = false } init() { @@ -12,6 +15,8 @@ export default class PosthogClient { posthog.init(this.token, { autocapture: false, capture_pageview: false, + // disable by default + disable_session_recording: true, }) posthog.set_config({ persistence: "cookie" }) @@ -22,7 +27,7 @@ export default class PosthogClient { * Set the posthog context to the current user * @param {String} id - unique user id */ - identify(id) { + identify(id: string) { if (!this.initialised) return posthog.identify(id) @@ -32,7 +37,7 @@ export default class PosthogClient { * Update user metadata associated with current user in posthog * @param {Object} meta - user fields */ - updateUser(meta) { + updateUser(meta: Record) { if (!this.initialised) return posthog.people.set(meta) @@ -43,28 +48,22 @@ export default class PosthogClient { * @param {String} event - event identifier * @param {Object} props - properties for the event */ - captureEvent(eventName, props) { - if (!this.initialised) return - - props.sourceApp = "builder" - posthog.capture(eventName, props) - } - - /** - * Submit NPS feedback to posthog. - * @param {Object} values - NPS Values - */ - npsFeedback(values) { - if (!this.initialised) return - - localStorage.setItem(Events.NPS.SUBMITTED, Date.now()) - - const prefixedFeedback = {} - for (let key in values) { - prefixedFeedback[`feedback_${key}`] = values[key] + captureEvent(event: string, props: Record) { + if (!this.initialised) { + return } - posthog.capture(Events.NPS.SUBMITTED, prefixedFeedback) + props.sourceApp = "builder" + posthog.capture(event, props) + } + + enableSessionRecording() { + if (!this.initialised) { + return + } + posthog.set_config({ + disable_session_recording: false, + }) } /** diff --git a/packages/builder/src/analytics/index.js b/packages/builder/src/analytics/index.js index aa83f3c7ab..12bd548e9b 100644 --- a/packages/builder/src/analytics/index.js +++ b/packages/builder/src/analytics/index.js @@ -31,6 +31,10 @@ class AnalyticsHub { posthog.captureEvent(eventName, props) } + enableSessionRecording() { + posthog.enableSessionRecording() + } + async logout() { posthog.logout() } diff --git a/packages/builder/src/stores/portal/admin.ts b/packages/builder/src/stores/portal/admin.ts index 90e3a5cdc9..6ac8b00b73 100644 --- a/packages/builder/src/stores/portal/admin.ts +++ b/packages/builder/src/stores/portal/admin.ts @@ -8,6 +8,7 @@ import { SystemStatusResponse, } from "@budibase/types" import { BudiStore } from "../BudiStore" +import Analytics from "../../analytics" interface AdminState extends GetEnvironmentResponse { loaded: boolean @@ -33,6 +34,8 @@ export class AdminStore extends BudiStore { await this.getEnvironment() // enable system status checks in the cloud if (get(this.store).cloud) { + // in cloud allow this + Analytics.enableSessionRecording() await this.getSystemStatus() this.checkStatus() } diff --git a/packages/string-templates/src/helpers/constants.ts b/packages/string-templates/src/helpers/constants.ts index ee84a1dc47..fb6bf4e4f5 100644 --- a/packages/string-templates/src/helpers/constants.ts +++ b/packages/string-templates/src/helpers/constants.ts @@ -36,6 +36,7 @@ export const HelperFunctionNames = { ALL: "all", LITERAL: "literal", JS: "js", + DECODE_ID: "decodeId", } export const LITERAL_MARKER = "%LITERAL%" diff --git a/packages/string-templates/src/helpers/index.ts b/packages/string-templates/src/helpers/index.ts index fe74a6d711..ea09c2c545 100644 --- a/packages/string-templates/src/helpers/index.ts +++ b/packages/string-templates/src/helpers/index.ts @@ -25,13 +25,29 @@ function isObject(value: string | any[]) { ) } -const HELPERS = [ +export const HELPERS = [ // external helpers new Helper(HelperFunctionNames.OBJECT, (value: any) => { return new Handlebars.SafeString(JSON.stringify(value)) }), // javascript helper new Helper(HelperFunctionNames.JS, processJS, false), + new Helper(HelperFunctionNames.DECODE_ID, (_id: string | { _id: string }) => { + if (!_id) { + return [] + } + // have to replace on the way back as we swapped out the double quotes + // when encoding, but JSON can't handle the single quotes + const id = typeof _id === "string" ? _id : _id._id + const decoded: string = decodeURIComponent(id).replace(/'/g, '"') + try { + const parsed = JSON.parse(decoded) + return Array.isArray(parsed) ? parsed : [parsed] + } catch (err) { + // wasn't json - likely was handlebars for a many to many + return [_id] + } + }), // this help is applied to all statements new Helper( HelperFunctionNames.ALL, diff --git a/packages/string-templates/test/helpers.spec.ts b/packages/string-templates/test/helpers.spec.ts index 12de4f1c29..7ef09cb2a4 100644 --- a/packages/string-templates/test/helpers.spec.ts +++ b/packages/string-templates/test/helpers.spec.ts @@ -517,3 +517,44 @@ describe("helper overlap", () => { expect(output).toEqual("a") }) }) + +describe("Test the decodeId helper", () => { + it("should decode a valid encoded ID", async () => { + const encodedId = encodeURIComponent("[42]") // "%5B42%5D" + const output = await processString("{{ decodeId id }}", { id: encodedId }) + expect(output).toBe("42") + }) + + it("Should return an unchanged string if the string isn't encoded", async () => { + const unencodedId = "forty-two" + const output = await processString("{{ decodeId id }}", { id: unencodedId }) + expect(output).toBe("forty-two") + }) + + it("Should return a string of comma-separated IDs when passed multiple IDs in a URI encoded array", async () => { + const encodedIds = encodeURIComponent("[1,2,3]") // "%5B1%2C2%2C3%5D" + const output = await processString("{{ decodeId id }}", { id: encodedIds }) + expect(output).toBe("1,2,3") + }) + + it("Handles empty array gracefully", async () => { + const output = await processString("{{ decodeId value }}", { + value: [], + }) + expect(output).toBe("[[]]") + }) + + it("Handles undefined gracefully", async () => { + const output = await processString("{{ decodeId value }}", { + value: undefined, + }) + expect(output).toBe("") + }) + + it("Handles null gracefully", async () => { + const output = await processString("{{ decodeId value }}", { + value: undefined, + }) + expect(output).toBe("") + }) +})