From ef53807414ace612b0d9db7918bb1875faabdb0f Mon Sep 17 00:00:00 2001 From: Hetu Nandu Date: Fri, 20 Dec 2024 09:08:38 +0530 Subject: [PATCH 1/5] chore: Masking for response tables --- .../components/PluginActionResponse/components/Table.tsx | 8 ++++++-- app/client/src/utils/Analytics/mixpanel.ts | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Table.tsx b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Table.tsx index d833e5b43e0c..3eb85a11b4fd 100644 --- a/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Table.tsx +++ b/app/client/src/PluginActionEditor/components/PluginActionResponse/components/Table.tsx @@ -311,7 +311,11 @@ function Table(props: TableProps) { {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */} {row.cells.map((cell: any, cellIndex: number) => { return ( -
+
{cell.render("Cell")}
); @@ -344,7 +348,7 @@ function Table(props: TableProps) { {headerGroups.map((headerGroup: any, index: number) => (
{headerGroup.headers.map( diff --git a/app/client/src/utils/Analytics/mixpanel.ts b/app/client/src/utils/Analytics/mixpanel.ts index cc7f6e382886..e1a2ccd68f97 100644 --- a/app/client/src/utils/Analytics/mixpanel.ts +++ b/app/client/src/utils/Analytics/mixpanel.ts @@ -32,6 +32,8 @@ class MixpanelSingleton { this.mixpanel = loadedMixpanel; this.mixpanel.init(mixpanel.apiKey, { record_sessions_percent: 100, + record_block_selector: ".mp-block", + record_mask_text_selector: ".mp-mask", }); await this.addSegmentMiddleware(); From 9a4b074098ba9d44e25ed63d2c8bbb21134452f4 Mon Sep 17 00:00:00 2001 From: Hetu Nandu Date: Fri, 20 Dec 2024 10:05:24 +0530 Subject: [PATCH 2/5] chore: Peek overlay, other response types and logs --- .../CodeEditor/PeekOverlayPopup/PeekOverlayPopup.tsx | 1 + .../editorComponents/Debugger/LogItem/LogItem.tsx | 6 +++--- .../src/components/editorComponents/ReadOnlyEditor.tsx | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/client/src/components/editorComponents/CodeEditor/PeekOverlayPopup/PeekOverlayPopup.tsx b/app/client/src/components/editorComponents/CodeEditor/PeekOverlayPopup/PeekOverlayPopup.tsx index 7b0f0bd55c02..80ba7bbf0079 100644 --- a/app/client/src/components/editorComponents/CodeEditor/PeekOverlayPopup/PeekOverlayPopup.tsx +++ b/app/client/src/components/editorComponents/CodeEditor/PeekOverlayPopup/PeekOverlayPopup.tsx @@ -157,6 +157,7 @@ export function PeekOverlayPopUpContent( > {(dataType === "object" || dataType === "array") && jsData !== null && ( e.stopPropagation()} > @@ -325,7 +325,7 @@ export function LogItem(props: LogItemProps) { if (typeof logDatum === "object") { return ( e.stopPropagation()} > @@ -334,7 +334,7 @@ export function LogItem(props: LogItemProps) { ); } else { return ( - + {`${logDatum} `} ); diff --git a/app/client/src/components/editorComponents/ReadOnlyEditor.tsx b/app/client/src/components/editorComponents/ReadOnlyEditor.tsx index de707a0923be..7817c6109fec 100644 --- a/app/client/src/components/editorComponents/ReadOnlyEditor.tsx +++ b/app/client/src/components/editorComponents/ReadOnlyEditor.tsx @@ -40,6 +40,7 @@ function ReadOnlyEditor(props: Props) { isReadOnly: true, isRawView: props.isRawView, border: CodeEditorBorder.NONE, + className: "mp-mask", }; return ; From 0caca192b06cdec7dae59dfbffde244409d7789f Mon Sep 17 00:00:00 2001 From: Hetu Nandu Date: Fri, 20 Dec 2024 10:34:57 +0530 Subject: [PATCH 3/5] chore: Widgets masking --- app/client/src/pages/AppViewer/AppPage/AppPage.tsx | 6 +++++- app/client/src/pages/Editor/Canvas.tsx | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/client/src/pages/AppViewer/AppPage/AppPage.tsx b/app/client/src/pages/AppViewer/AppPage/AppPage.tsx index 7b4b3ad3b037..5f9d27d7dc39 100644 --- a/app/client/src/pages/AppViewer/AppPage/AppPage.tsx +++ b/app/client/src/pages/AppViewer/AppPage/AppPage.tsx @@ -53,7 +53,11 @@ export function AppPage(props: AppPageProps) { ref={pageViewWrapperRef} sidebarWidth={sidebarWidth} > - + {widgetsStructure.widgetId && renderAppsmithCanvas(widgetsStructure as WidgetProps)} diff --git a/app/client/src/pages/Editor/Canvas.tsx b/app/client/src/pages/Editor/Canvas.tsx index 66173de99e2f..742e6d8a74b1 100644 --- a/app/client/src/pages/Editor/Canvas.tsx +++ b/app/client/src/pages/Editor/Canvas.tsx @@ -92,7 +92,7 @@ const Canvas = (props: CanvasProps) => { Date: Fri, 20 Dec 2024 11:37:48 +0530 Subject: [PATCH 4/5] chore: Add feature flagging support --- app/client/src/ce/entities/FeatureFlag.ts | 8 +++++ app/client/src/ce/sagas/userSagas.tsx | 36 +++++++++++++++++++++- app/client/src/ce/utils/AnalyticsUtil.tsx | 11 +++++-- app/client/src/utils/Analytics/mixpanel.ts | 19 ++++++++++-- 4 files changed, 67 insertions(+), 7 deletions(-) diff --git a/app/client/src/ce/entities/FeatureFlag.ts b/app/client/src/ce/entities/FeatureFlag.ts index eb124bcebf55..d3048a86f9f1 100644 --- a/app/client/src/ce/entities/FeatureFlag.ts +++ b/app/client/src/ce/entities/FeatureFlag.ts @@ -49,6 +49,11 @@ export const FEATURE_FLAG = { release_gs_all_sheets_options_enabled: "release_gs_all_sheets_options_enabled", ab_premium_datasources_view_enabled: "ab_premium_datasources_view_enabled", + kill_session_recordings_enabled: "kill_session_recordings_enabled", + config_mask_session_recordings_enabled: + "config_mask_session_recordings_enabled", + config_user_session_recordings_enabled: + "config_user_session_recordings_enabled", } as const; export type FeatureFlag = keyof typeof FEATURE_FLAG; @@ -91,6 +96,9 @@ export const DEFAULT_FEATURE_FLAG_VALUE: FeatureFlags = { release_table_html_column_type_enabled: false, release_gs_all_sheets_options_enabled: false, ab_premium_datasources_view_enabled: false, + kill_session_recordings_enabled: false, + config_user_session_recordings_enabled: true, + config_mask_session_recordings_enabled: true, }; export const AB_TESTING_EVENT_KEYS = { diff --git a/app/client/src/ce/sagas/userSagas.tsx b/app/client/src/ce/sagas/userSagas.tsx index ff8409ef4fd8..7da583fe3430 100644 --- a/app/client/src/ce/sagas/userSagas.tsx +++ b/app/client/src/ce/sagas/userSagas.tsx @@ -74,6 +74,7 @@ import type { } from "reducers/uiReducers/usersReducer"; import { selectFeatureFlags } from "ee/selectors/featureFlagsSelectors"; import { getFromServerWhenNoPrefetchedResult } from "sagas/helper"; +import type { SessionRecordingConfig } from "utils/Analytics/mixpanel"; export function* getCurrentUserSaga(action?: { payload?: { userProfile?: ApiResponse }; @@ -107,9 +108,42 @@ export function* getCurrentUserSaga(action?: { } } +function* getSessionRecordingConfig() { + const featureFlags: FeatureFlags = yield select(selectFeatureFlags); + + // This is a tenant level flag to kill session recordings + // If this is true, we do not do any session recordings + if (featureFlags.kill_session_recordings_enabled) { + return { + enabled: false, + mask: false, + }; + } + + // This is a user level flag to control session recordings for a user + // If this is false, we do not do any session recordings + if (!featureFlags.config_user_session_recordings_enabled) { + return { + enabled: false, + mask: false, + }; + } + + // Now we know that both tenant and user level flags are not blocking session recordings + return { + enabled: true, + // Check if we need to mask the session recordings from feature flags + mask: featureFlags.config_mask_session_recordings_enabled, + }; +} + function* initTrackers(currentUser: User) { try { - yield call(AnalyticsUtil.initialize, currentUser); + const sessionRecordingConfig: SessionRecordingConfig = yield call( + getSessionRecordingConfig, + ); + + yield call(AnalyticsUtil.initialize, currentUser, sessionRecordingConfig); } catch (e) { log.error(e); } diff --git a/app/client/src/ce/utils/AnalyticsUtil.tsx b/app/client/src/ce/utils/AnalyticsUtil.tsx index 622ad9c59c2a..f7ea262255ac 100644 --- a/app/client/src/ce/utils/AnalyticsUtil.tsx +++ b/app/client/src/ce/utils/AnalyticsUtil.tsx @@ -6,7 +6,9 @@ import type { EventName } from "ee/utils/analyticsUtilTypes"; import type { EventProperties } from "@segment/analytics-next"; import SegmentSingleton from "utils/Analytics/segment"; -import MixpanelSingleton from "utils/Analytics/mixpanel"; +import MixpanelSingleton, { + type SessionRecordingConfig, +} from "utils/Analytics/mixpanel"; import SentryUtil from "utils/Analytics/sentry"; import SmartlookUtil from "utils/Analytics/smartlook"; import TrackedUser from "ee/utils/Analytics/trackedUser"; @@ -25,7 +27,10 @@ export enum AnalyticsEventType { let blockErrorLogs = false; let segmentAnalytics: SegmentSingleton | null = null; -async function initialize(user: User) { +async function initialize( + user: User, + sessionRecordingConfig: SessionRecordingConfig, +) { SentryUtil.init(); await SmartlookUtil.init(); @@ -34,7 +39,7 @@ async function initialize(user: User) { await segmentAnalytics.init(); // Mixpanel needs to be initialized after Segment - await MixpanelSingleton.getInstance().init(); + await MixpanelSingleton.getInstance().init(sessionRecordingConfig); // Identify the user after all services are initialized await identifyUser(user); diff --git a/app/client/src/utils/Analytics/mixpanel.ts b/app/client/src/utils/Analytics/mixpanel.ts index e1a2ccd68f97..ce51bf807a86 100644 --- a/app/client/src/utils/Analytics/mixpanel.ts +++ b/app/client/src/utils/Analytics/mixpanel.ts @@ -4,6 +4,11 @@ import { getAppsmithConfigs } from "ee/configs"; import SegmentSingleton from "./segment"; import type { ID } from "@segment/analytics-next"; +export interface SessionRecordingConfig { + enabled: boolean; + mask: boolean; +} + class MixpanelSingleton { private static instance: MixpanelSingleton; private mixpanel: OverridedMixpanel | null = null; @@ -17,13 +22,21 @@ class MixpanelSingleton { } // Segment needs to be initialized before Mixpanel - public async init(): Promise { + public async init({ + enabled, + mask, + }: SessionRecordingConfig): Promise { if (this.mixpanel) { log.warn("Mixpanel is already initialized."); return true; } + // Do not initialize Mixpanel if session recording is disabled + if (!enabled) { + return false; + } + try { const { default: loadedMixpanel } = await import("mixpanel-browser"); const { mixpanel } = getAppsmithConfigs(); @@ -32,8 +45,8 @@ class MixpanelSingleton { this.mixpanel = loadedMixpanel; this.mixpanel.init(mixpanel.apiKey, { record_sessions_percent: 100, - record_block_selector: ".mp-block", - record_mask_text_selector: ".mp-mask", + record_block_selector: mask ? ".mp-block" : "", + record_mask_text_selector: mask ? ".mp-mask" : "", }); await this.addSegmentMiddleware(); From 15f654e07d5a5ece528d1f7f3319104d0c350b99 Mon Sep 17 00:00:00 2001 From: Hetu Nandu Date: Fri, 20 Dec 2024 15:53:55 +0530 Subject: [PATCH 5/5] chore: Update selector --- app/client/cypress/support/Pages/DataSources.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/client/cypress/support/Pages/DataSources.ts b/app/client/cypress/support/Pages/DataSources.ts index 83c001243741..ff559f6e4784 100644 --- a/app/client/cypress/support/Pages/DataSources.ts +++ b/app/client/cypress/support/Pages/DataSources.ts @@ -147,7 +147,7 @@ export class DataSources { option + "']"; _queryTableResponse = - "//div[@data-guided-tour-id='query-table-response']//div[@class='tbody']//div[@class ='td']"; + "//div[@data-guided-tour-id='query-table-response']//div[@class='tbody']//div[@class ='td mp-mask']"; _queryResponseHeader = (header: string) => "//div[@data-guided-tour-id='query-table-response']//div[@class='table']//div[@role ='columnheader']//span[text()='" + header +