);
@@ -344,7 +348,7 @@ function Table(props: TableProps) {
{headerGroups.map((headerGroup: any, index: number) => (
{headerGroup.headers.map(
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/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 ;
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) => {
{
+ 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,6 +45,8 @@ class MixpanelSingleton {
this.mixpanel = loadedMixpanel;
this.mixpanel.init(mixpanel.apiKey, {
record_sessions_percent: 100,
+ record_block_selector: mask ? ".mp-block" : "",
+ record_mask_text_selector: mask ? ".mp-mask" : "",
});
await this.addSegmentMiddleware();