chore: send segment anonymous id (#19122)
Add segment's `anonymousId` as a header in all API calls. cached id -> [details](https://segment.com/docs/connections/sources/catalog/libraries/website/javascript/identity/#segment-id-persistence) On Page load actions: - If segment is enabled: - and cached id exists -> trigger with cached id - if cached id doesn’t exist, we wait for max 2 seconds. - if segment init is success -> trigger with anonymous id - if failed/delayed -> trigger without anonymous id - If segment is disabled we don’t wait at all and anonymous id is not sent. Signed-off-by: Shrikant Sharat Kandula <shrikant@appsmith.com> Co-authored-by: Shrikant Sharat Kandula <shrikant@appsmith.com> Co-authored-by: Hetu Nandu <hetu@appsmith.com>
This commit is contained in:
parent
537186f70c
commit
071b992710
9
app/client/src/actions/analyticsActions.ts
Normal file
9
app/client/src/actions/analyticsActions.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { ReduxActionTypes } from "ce/constants/ReduxActionConstants";
|
||||
|
||||
export const segmentInitSuccess = () => ({
|
||||
type: ReduxActionTypes.SEGMENT_INITIALIZED,
|
||||
});
|
||||
|
||||
export const segmentInitUncertain = () => ({
|
||||
type: ReduxActionTypes.SEGMENT_INIT_UNCERTAIN,
|
||||
});
|
||||
|
|
@ -18,10 +18,13 @@ import { AUTH_LOGIN_URL } from "constants/routes";
|
|||
import { getCurrentGitBranch } from "selectors/gitSyncSelectors";
|
||||
import getQueryParamsObject from "utils/getQueryParamsObject";
|
||||
import { UserCancelledActionExecutionError } from "sagas/ActionExecution/errorUtils";
|
||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
import { getAppsmithConfigs } from "ce/configs";
|
||||
|
||||
const executeActionRegex = /actions\/execute/;
|
||||
const timeoutErrorRegex = /timeout of (\d+)ms exceeded/;
|
||||
export const axiosConnectionAbortedCode = "ECONNABORTED";
|
||||
const appsmithConfig = getAppsmithConfigs();
|
||||
|
||||
const makeExecuteActionResponse = (response: any): ActionExecutionResponse => ({
|
||||
...response.data,
|
||||
|
|
@ -40,6 +43,7 @@ const is404orAuthPath = () => {
|
|||
// this will be used to calculate the time taken for an action
|
||||
// execution request
|
||||
export const apiRequestInterceptor = (config: AxiosRequestConfig) => {
|
||||
config.headers = config.headers ?? {};
|
||||
const branch =
|
||||
getCurrentGitBranch(store.getState()) || getQueryParamsObject().branch;
|
||||
if (branch && config.headers) {
|
||||
|
|
@ -49,6 +53,11 @@ export const apiRequestInterceptor = (config: AxiosRequestConfig) => {
|
|||
config.timeout = 1000 * 120; // increase timeout for git specific APIs
|
||||
}
|
||||
|
||||
const anonymousId = AnalyticsUtil.getAnonymousId();
|
||||
appsmithConfig.segment.enabled &&
|
||||
anonymousId &&
|
||||
(config.headers["x-anonymous-user-id"] = anonymousId);
|
||||
|
||||
return { ...config, timer: performance.now() };
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -757,6 +757,8 @@ export const ReduxActionTypes = {
|
|||
"SET_DATASOURCE_DEFAULT_KEY_VALUE_PAIR_SET",
|
||||
RESET_DATASOURCE_DEFAULT_KEY_VALUE_PAIR_SET:
|
||||
"RESET_DATASOURCE_DEFAULT_KEY_VALUE_PAIR_SET",
|
||||
SEGMENT_INITIALIZED: "SEGMENT_INITIALIZED",
|
||||
SEGMENT_INIT_UNCERTAIN: "SEGMENT_INIT_UNCERTAIN",
|
||||
};
|
||||
|
||||
export type ReduxActionType = typeof ReduxActionTypes[keyof typeof ReduxActionTypes];
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ import { CanvasLevelsReduxState } from "reducers/entityReducers/autoHeightReduce
|
|||
import { LintErrors } from "reducers/lintingReducers/lintErrorsReducers";
|
||||
import lintErrorReducer from "reducers/lintingReducers";
|
||||
import { AutoHeightUIState } from "reducers/uiReducers/autoHeightReducer";
|
||||
import { AnalyticsReduxState } from "reducers/uiReducers/analyticsReducer";
|
||||
|
||||
export const reducerObject = {
|
||||
entities: entityReducer,
|
||||
|
|
@ -86,6 +87,7 @@ export const reducerObject = {
|
|||
|
||||
export interface AppState {
|
||||
ui: {
|
||||
analytics: AnalyticsReduxState;
|
||||
editor: EditorReduxState;
|
||||
propertyPane: PropertyPaneReduxState;
|
||||
tableFilterPane: TableFilterPaneReduxState;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { call, put, select, take } from "redux-saga/effects";
|
||||
import { call, put, race, select, take } from "redux-saga/effects";
|
||||
import {
|
||||
ReduxAction,
|
||||
ReduxActionWithPromise,
|
||||
|
|
@ -56,6 +56,13 @@ import {
|
|||
getFirstTimeUserOnboardingIntroModalVisibility,
|
||||
} from "utils/storage";
|
||||
import { initializeAnalyticsAndTrackers } from "utils/AppsmithUtils";
|
||||
import { getAppsmithConfigs } from "ce/configs";
|
||||
import { getSegmentState } from "selectors/analyticsSelectors";
|
||||
import {
|
||||
segmentInitUncertain,
|
||||
segmentInitSuccess,
|
||||
} from "actions/analyticsActions";
|
||||
import { SegmentState } from "reducers/uiReducers/analyticsReducer";
|
||||
|
||||
export function* createUserSaga(
|
||||
action: ReduxActionWithPromise<CreateUserRequest>,
|
||||
|
|
@ -96,6 +103,25 @@ export function* createUserSaga(
|
|||
}
|
||||
}
|
||||
|
||||
export function* waitForSegmentInit(skipWithAnonymousId: boolean) {
|
||||
if (skipWithAnonymousId && AnalyticsUtil.getAnonymousId()) return;
|
||||
yield call(waitForFetchUserSuccess);
|
||||
const currentUser: User | undefined = yield select(getCurrentUser);
|
||||
const segmentState: SegmentState | undefined = yield select(getSegmentState);
|
||||
const appsmithConfig = getAppsmithConfigs();
|
||||
|
||||
if (
|
||||
currentUser?.enableTelemetry &&
|
||||
appsmithConfig.segment.enabled &&
|
||||
!segmentState
|
||||
) {
|
||||
yield race([
|
||||
take(ReduxActionTypes.SEGMENT_INITIALIZED),
|
||||
take(ReduxActionTypes.SEGMENT_INIT_UNCERTAIN),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
export function* getCurrentUserSaga() {
|
||||
try {
|
||||
PerformanceTracker.startAsyncTracking(
|
||||
|
|
@ -108,7 +134,15 @@ export function* getCurrentUserSaga() {
|
|||
//@ts-expect-error: response is of type unknown
|
||||
const { enableTelemetry } = response.data;
|
||||
if (enableTelemetry) {
|
||||
initializeAnalyticsAndTrackers();
|
||||
const promise = initializeAnalyticsAndTrackers();
|
||||
if (promise instanceof Promise) {
|
||||
const result: boolean = yield promise;
|
||||
if (result) {
|
||||
yield put(segmentInitSuccess());
|
||||
} else {
|
||||
yield put(segmentInitUncertain());
|
||||
}
|
||||
}
|
||||
}
|
||||
yield put(initAppLevelSocketConnection());
|
||||
yield put(initPageLevelSocketConnection());
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ import { fetchJSLibraries } from "actions/JSLibraryActions";
|
|||
import CodemirrorTernService from "utils/autocomplete/CodemirrorTernService";
|
||||
import { selectFeatureFlags } from "selectors/usersSelectors";
|
||||
import FeatureFlags from "entities/FeatureFlags";
|
||||
import { waitForSegmentInit } from "ce/sagas/userSagas";
|
||||
|
||||
export default class AppEditorEngine extends AppEngine {
|
||||
constructor(mode: APP_MODE) {
|
||||
|
|
@ -135,6 +136,8 @@ export default class AppEditorEngine extends AppEngine {
|
|||
throw new ActionsNotFoundError(
|
||||
`Unable to fetch actions for the application: ${applicationId}`,
|
||||
);
|
||||
|
||||
yield call(waitForSegmentInit, true);
|
||||
yield put(fetchAllPageEntityCompletion([executePageLoadActions()]));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import AppEngine, { ActionsNotFoundError, AppEnginePayload } from ".";
|
|||
import { fetchJSLibraries } from "actions/JSLibraryActions";
|
||||
import FeatureFlags from "entities/FeatureFlags";
|
||||
import { selectFeatureFlags } from "selectors/usersSelectors";
|
||||
import { waitForSegmentInit } from "ce/sagas/userSagas";
|
||||
|
||||
export default class AppViewerEngine extends AppEngine {
|
||||
constructor(mode: APP_MODE) {
|
||||
|
|
@ -113,6 +114,7 @@ export default class AppViewerEngine extends AppEngine {
|
|||
`Unable to fetch actions for the application: ${applicationId}`,
|
||||
);
|
||||
|
||||
yield call(waitForSegmentInit, true);
|
||||
yield put(fetchAllPageEntityCompletion([executePageLoadActions()]));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
37
app/client/src/reducers/uiReducers/analyticsReducer.ts
Normal file
37
app/client/src/reducers/uiReducers/analyticsReducer.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { ReduxActionTypes } from "ce/constants/ReduxActionConstants";
|
||||
import { createReducer } from "utils/ReducerUtils";
|
||||
|
||||
export type SegmentState = "INIT_SUCCESS" | "INIT_UNCERTAIN";
|
||||
|
||||
export const initialState: AnalyticsReduxState = {
|
||||
telemetry: {},
|
||||
};
|
||||
|
||||
export interface AnalyticsReduxState {
|
||||
telemetry: {
|
||||
segmentState?: SegmentState;
|
||||
};
|
||||
}
|
||||
|
||||
export const handlers = {
|
||||
[ReduxActionTypes.SEGMENT_INITIALIZED]: (
|
||||
state: AnalyticsReduxState,
|
||||
): AnalyticsReduxState => ({
|
||||
...state,
|
||||
telemetry: {
|
||||
...state.telemetry,
|
||||
segmentState: "INIT_SUCCESS",
|
||||
},
|
||||
}),
|
||||
[ReduxActionTypes.SEGMENT_INIT_UNCERTAIN]: (
|
||||
state: AnalyticsReduxState,
|
||||
): AnalyticsReduxState => ({
|
||||
...state,
|
||||
telemetry: {
|
||||
...state.telemetry,
|
||||
segmentState: "INIT_UNCERTAIN",
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
export default createReducer(initialState, handlers);
|
||||
|
|
@ -45,8 +45,10 @@ import guidedTourReducer from "./guidedTourReducer";
|
|||
import libraryReducer from "./libraryReducer";
|
||||
import appSettingsPaneReducer from "./appSettingsPaneReducer";
|
||||
import autoHeightUIReducer from "./autoHeightReducer";
|
||||
import analyticsReducer from "./analyticsReducer";
|
||||
|
||||
const uiReducer = combineReducers({
|
||||
analytics: analyticsReducer,
|
||||
editor: editorReducer,
|
||||
errors: errorReducer,
|
||||
propertyPane: propertyPaneReducer,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,11 @@ import AppEngineFactory from "entities/Engine/factory";
|
|||
import { call } from "redux-saga/effects";
|
||||
import { startAppEngine } from "sagas/InitSagas";
|
||||
|
||||
jest.mock("../../api/Api", () => ({
|
||||
__esModule: true,
|
||||
default: class Api {},
|
||||
}));
|
||||
|
||||
describe("tests the sagas in initSagas", () => {
|
||||
it("tests the order of execute in startAppEngine", () => {
|
||||
const action = {
|
||||
|
|
|
|||
4
app/client/src/selectors/analyticsSelectors.tsx
Normal file
4
app/client/src/selectors/analyticsSelectors.tsx
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import { AppState } from "ce/reducers";
|
||||
|
||||
export const getSegmentState = (state: AppState) =>
|
||||
state.ui.analytics.telemetry.segmentState;
|
||||
|
|
@ -323,59 +323,68 @@ class AnalyticsUtil {
|
|||
}
|
||||
|
||||
static initializeSegment(key: string) {
|
||||
(function init(window: any) {
|
||||
const analytics = (window.analytics = window.analytics || []);
|
||||
if (!analytics.initialize) {
|
||||
if (analytics.invoked) {
|
||||
log.error("Segment snippet included twice.");
|
||||
} else {
|
||||
analytics.invoked = !0;
|
||||
analytics.methods = [
|
||||
"trackSubmit",
|
||||
"trackClick",
|
||||
"trackLink",
|
||||
"trackForm",
|
||||
"pageview",
|
||||
"identify",
|
||||
"reset",
|
||||
"group",
|
||||
"track",
|
||||
"ready",
|
||||
"alias",
|
||||
"debug",
|
||||
"page",
|
||||
"once",
|
||||
"off",
|
||||
"on",
|
||||
];
|
||||
analytics.factory = function(t: any) {
|
||||
return function() {
|
||||
const e = Array.prototype.slice.call(arguments); //eslint-disable-line prefer-rest-params
|
||||
e.unshift(t);
|
||||
analytics.push(e);
|
||||
return analytics;
|
||||
const initPromise = new Promise<boolean>((resolve) => {
|
||||
(function init(window: any) {
|
||||
const analytics = (window.analytics = window.analytics || []);
|
||||
if (!analytics.initialize) {
|
||||
if (analytics.invoked) {
|
||||
log.error("Segment snippet included twice.");
|
||||
} else {
|
||||
analytics.invoked = !0;
|
||||
analytics.methods = [
|
||||
"trackSubmit",
|
||||
"trackClick",
|
||||
"trackLink",
|
||||
"trackForm",
|
||||
"pageview",
|
||||
"identify",
|
||||
"reset",
|
||||
"group",
|
||||
"track",
|
||||
"ready",
|
||||
"alias",
|
||||
"debug",
|
||||
"page",
|
||||
"once",
|
||||
"off",
|
||||
"on",
|
||||
];
|
||||
analytics.factory = function(t: any) {
|
||||
return function() {
|
||||
const e = Array.prototype.slice.call(arguments); //eslint-disable-line prefer-rest-params
|
||||
e.unshift(t);
|
||||
analytics.push(e);
|
||||
return analytics;
|
||||
};
|
||||
};
|
||||
}
|
||||
for (let t: any = 0; t < analytics.methods.length; t++) {
|
||||
const e = analytics.methods[t];
|
||||
analytics[e] = analytics.factory(e);
|
||||
}
|
||||
analytics.load = function(t: any, e: any) {
|
||||
const n = document.createElement("script");
|
||||
n.type = "text/javascript";
|
||||
n.async = !0;
|
||||
// Ref: https://www.notion.so/appsmith/530051a2083040b5bcec15a46121aea3
|
||||
n.src = "https://a.appsmith.com/reroute/" + t + "/main.js";
|
||||
const a: any = document.getElementsByTagName("script")[0];
|
||||
a.parentNode.insertBefore(n, a);
|
||||
analytics._loadOptions = e;
|
||||
};
|
||||
analytics.ready(() => {
|
||||
resolve(true);
|
||||
});
|
||||
setTimeout(() => {
|
||||
resolve(false);
|
||||
}, 2000);
|
||||
analytics.SNIPPET_VERSION = "4.1.0";
|
||||
analytics.load(key);
|
||||
analytics.page();
|
||||
}
|
||||
for (let t: any = 0; t < analytics.methods.length; t++) {
|
||||
const e = analytics.methods[t];
|
||||
analytics[e] = analytics.factory(e);
|
||||
}
|
||||
analytics.load = function(t: any, e: any) {
|
||||
const n = document.createElement("script");
|
||||
n.type = "text/javascript";
|
||||
n.async = !0;
|
||||
// Ref: https://www.notion.so/appsmith/530051a2083040b5bcec15a46121aea3
|
||||
n.src = "https://a.appsmith.com/reroute/" + t + "/main.js";
|
||||
const a: any = document.getElementsByTagName("script")[0];
|
||||
a.parentNode.insertBefore(n, a);
|
||||
analytics._loadOptions = e;
|
||||
};
|
||||
analytics.SNIPPET_VERSION = "4.1.0";
|
||||
analytics.load(key);
|
||||
analytics.page();
|
||||
}
|
||||
})(window);
|
||||
})(window);
|
||||
});
|
||||
return initPromise;
|
||||
}
|
||||
|
||||
static logEvent(eventName: EventName, eventData: any = {}) {
|
||||
|
|
@ -478,6 +487,16 @@ class AnalyticsUtil {
|
|||
}
|
||||
}
|
||||
|
||||
static getAnonymousId() {
|
||||
const windowDoc: any = window;
|
||||
const { segment } = getAppsmithConfigs();
|
||||
if (windowDoc.analytics && windowDoc.analytics.user) {
|
||||
return windowDoc.analytics.user().anonymousId();
|
||||
} else if (segment.enabled) {
|
||||
return localStorage.getItem("ajs_anonymous_id")?.replaceAll('"', "");
|
||||
}
|
||||
}
|
||||
|
||||
static reset() {
|
||||
const windowDoc: any = window;
|
||||
if (windowDoc.Intercom) {
|
||||
|
|
|
|||
|
|
@ -72,10 +72,10 @@ export const initializeAnalyticsAndTrackers = () => {
|
|||
if (appsmithConfigs.segment.enabled && !(window as any).analytics) {
|
||||
if (appsmithConfigs.segment.apiKey) {
|
||||
// This value is only enabled for Appsmith's cloud hosted version. It is not set in self-hosted environments
|
||||
AnalyticsUtil.initializeSegment(appsmithConfigs.segment.apiKey);
|
||||
return AnalyticsUtil.initializeSegment(appsmithConfigs.segment.apiKey);
|
||||
} else if (appsmithConfigs.segment.ceKey) {
|
||||
// This value is set in self-hosted environments. But if the analytics are disabled, it's never used.
|
||||
AnalyticsUtil.initializeSegment(appsmithConfigs.segment.ceKey);
|
||||
return AnalyticsUtil.initializeSegment(appsmithConfigs.segment.ceKey);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
package com.appsmith.server.helpers;
|
||||
|
||||
import com.appsmith.server.constants.FieldName;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public class ExchangeUtils {
|
||||
|
||||
public static final String HEADER_ANONYMOUS_USER_ID = "X-Anonymous-User-Id";
|
||||
|
||||
/**
|
||||
* Returns the value of `X-Anonymous-User-Id` header, from the _current_ request. Since this gets the header from
|
||||
* the current request, it has to be called from a request context. It won't work in new background contexts, like
|
||||
* when calling `.subscribe()` on a Mono.
|
||||
* @return a Mono that resolves to the value of the `X-Anonymous-User-Id` header, if present. Else, `FieldName.ANONYMOUS_USER`.
|
||||
*/
|
||||
public static Mono<String> getAnonymousUserIdFromCurrentRequest() {
|
||||
return Mono.deferContextual(Mono::just)
|
||||
.map(contextView -> ObjectUtils.defaultIfNull(
|
||||
contextView.get(ServerWebExchange.class).getRequest().getHeaders().getFirst(HEADER_ANONYMOUS_USER_ID),
|
||||
FieldName.ANONYMOUS_USER
|
||||
))
|
||||
.defaultIfEmpty(FieldName.ANONYMOUS_USER);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -94,7 +94,7 @@ public class GitFileUtils {
|
|||
try {
|
||||
Mono<Path> repoPathMono = fileUtils.saveApplicationToGitRepo(baseRepoSuffix, applicationReference, branchName).cache();
|
||||
return Mono.zip(repoPathMono, sessionUserService.getCurrentUser())
|
||||
.map(tuple -> {
|
||||
.flatMap(tuple -> {
|
||||
stopwatch.stopTimer();
|
||||
Path repoPath = tuple.getT1();
|
||||
// Path to repo will be : ./container-volumes/git-repo/workspaceId/defaultApplicationId/repoName/
|
||||
|
|
@ -104,8 +104,8 @@ public class GitFileUtils {
|
|||
FieldName.FLOW_NAME, stopwatch.getFlow(),
|
||||
"executionTime", stopwatch.getExecutionTime()
|
||||
);
|
||||
analyticsService.sendEvent(AnalyticsEvents.UNIT_EXECUTION_TIME.getEventName(), tuple.getT2().getUsername(), data);
|
||||
return repoPath;
|
||||
return analyticsService.sendEvent(AnalyticsEvents.UNIT_EXECUTION_TIME.getEventName(), tuple.getT2().getUsername(), data)
|
||||
.thenReturn(repoPath);
|
||||
});
|
||||
} catch (IOException | GitAPIException e) {
|
||||
log.error("Error occurred while saving files to local git repo: ", e);
|
||||
|
|
@ -239,7 +239,7 @@ public class GitFileUtils {
|
|||
Mono<ApplicationGitReference> appReferenceMono = fileUtils
|
||||
.reconstructApplicationReferenceFromGitRepo(workspaceId, defaultApplicationId, repoName, branchName);
|
||||
return Mono.zip(appReferenceMono, sessionUserService.getCurrentUser())
|
||||
.map(tuple -> {
|
||||
.flatMap(tuple -> {
|
||||
ApplicationGitReference applicationReference = tuple.getT1();
|
||||
// Extract application metadata from the json
|
||||
ApplicationJson metadata = getApplicationResource(applicationReference.getMetadata(), ApplicationJson.class);
|
||||
|
|
@ -252,8 +252,8 @@ public class GitFileUtils {
|
|||
FieldName.FLOW_NAME, stopwatch.getFlow(),
|
||||
"executionTime", stopwatch.getExecutionTime()
|
||||
);
|
||||
analyticsService.sendEvent(AnalyticsEvents.UNIT_EXECUTION_TIME.getEventName(), tuple.getT2().getUsername(), data);
|
||||
return applicationJson;
|
||||
return analyticsService.sendEvent(AnalyticsEvents.UNIT_EXECUTION_TIME.getEventName(), tuple.getT2().getUsername(), data)
|
||||
.thenReturn(applicationJson);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@ public interface AnalyticsServiceCE {
|
|||
|
||||
void identifyInstance(String instanceId, String role, String useCase);
|
||||
|
||||
void sendEvent(String event, String userId, Map<String, ?> properties);
|
||||
Mono<Void> sendEvent(String event, String userId, Map<String, ?> properties);
|
||||
|
||||
void sendEvent(String event, String userId, Map<String, ?> properties, boolean hashUserId);
|
||||
Mono<Void> sendEvent(String event, String userId, Map<String, ?> properties, boolean hashUserId);
|
||||
|
||||
<T extends BaseDomain> Mono<T> sendObjectEvent(AnalyticsEvents event, T object, Map<String, Object> extraProperties);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.appsmith.server.domains.NewAction;
|
|||
import com.appsmith.server.domains.NewPage;
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.domains.UserData;
|
||||
import com.appsmith.server.helpers.ExchangeUtils;
|
||||
import com.appsmith.server.helpers.PolicyUtils;
|
||||
import com.appsmith.server.helpers.UserUtils;
|
||||
import com.appsmith.server.services.ConfigService;
|
||||
|
|
@ -19,9 +20,9 @@ import com.segment.analytics.messages.TrackMessage;
|
|||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
|
@ -118,14 +119,14 @@ public class AnalyticsServiceCEImpl implements AnalyticsServiceCE {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void sendEvent(String event, String userId, Map<String, ?> properties) {
|
||||
sendEvent(event, userId, properties, true);
|
||||
public Mono<Void> sendEvent(String event, String userId, Map<String, ?> properties) {
|
||||
return sendEvent(event, userId, properties, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendEvent(String event, String userId, Map<String, ?> properties, boolean hashUserId) {
|
||||
public Mono<Void> sendEvent(String event, String userId, Map<String, ?> properties, boolean hashUserId) {
|
||||
if (!isActive()) {
|
||||
return;
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
// Can't update the properties directly as it's throwing ImmutableCollection error
|
||||
|
|
@ -158,17 +159,26 @@ public class AnalyticsServiceCEImpl implements AnalyticsServiceCE {
|
|||
}
|
||||
|
||||
final String finalUserId = userId;
|
||||
configService.getInstanceId()
|
||||
.map(instanceId -> {
|
||||
TrackMessage.Builder messageBuilder = TrackMessage.builder(event).userId(finalUserId);
|
||||
|
||||
return Mono.zip(
|
||||
ExchangeUtils.getAnonymousUserIdFromCurrentRequest(),
|
||||
configService.getInstanceId()
|
||||
.defaultIfEmpty("unknown-instance-id")
|
||||
).map(tuple -> {
|
||||
final String userIdFromClient = tuple.getT1();
|
||||
final String instanceId = tuple.getT2();
|
||||
String userIdToSend = finalUserId;
|
||||
if (FieldName.ANONYMOUS_USER.equals(finalUserId)) {
|
||||
userIdToSend = StringUtils.defaultIfEmpty(userIdFromClient, FieldName.ANONYMOUS_USER);
|
||||
}
|
||||
TrackMessage.Builder messageBuilder = TrackMessage.builder(event).userId(userIdToSend);
|
||||
analyticsProperties.put("originService", "appsmith-server");
|
||||
analyticsProperties.put("instanceId", instanceId);
|
||||
messageBuilder = messageBuilder.properties(analyticsProperties);
|
||||
analytics.enqueue(messageBuilder);
|
||||
return instanceId;
|
||||
})
|
||||
.subscribeOn(Schedulers.boundedElastic())
|
||||
.subscribe();
|
||||
.then();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -197,7 +207,15 @@ public class AnalyticsServiceCEImpl implements AnalyticsServiceCE {
|
|||
.switchIfEmpty(Mono.just(anonymousUser));
|
||||
|
||||
return userMono
|
||||
.map(user -> {
|
||||
.flatMap(user -> Mono.zip(
|
||||
user.isAnonymous()
|
||||
? ExchangeUtils.getAnonymousUserIdFromCurrentRequest()
|
||||
: Mono.just(user.getUsername()),
|
||||
Mono.just(user)
|
||||
))
|
||||
.flatMap(tuple -> {
|
||||
final String id = tuple.getT1();
|
||||
final User user = tuple.getT2();
|
||||
|
||||
// In case the user is anonymous, don't raise an event, unless it's a signup, logout, page view or action execution event.
|
||||
boolean isEventUserSignUpOrLogout = object instanceof User && (event == AnalyticsEvents.CREATE || event == AnalyticsEvents.LOGOUT);
|
||||
|
|
@ -205,13 +223,13 @@ public class AnalyticsServiceCEImpl implements AnalyticsServiceCE {
|
|||
boolean isEventActionExecution = object instanceof NewAction && event == AnalyticsEvents.EXECUTE_ACTION;
|
||||
boolean isAvoidLoggingEvent = user.isAnonymous() && !(isEventUserSignUpOrLogout || isEventPageView || isEventActionExecution);
|
||||
if (isAvoidLoggingEvent) {
|
||||
return object;
|
||||
return Mono.just(object);
|
||||
}
|
||||
|
||||
final String username = (object instanceof User ? (User) object : user).getUsername();
|
||||
|
||||
HashMap<String, Object> analyticsProperties = new HashMap<>();
|
||||
analyticsProperties.put("id", username);
|
||||
analyticsProperties.put("id", id);
|
||||
analyticsProperties.put("oid", object.getId());
|
||||
if (extraProperties != null) {
|
||||
analyticsProperties.putAll(extraProperties);
|
||||
|
|
@ -219,8 +237,8 @@ public class AnalyticsServiceCEImpl implements AnalyticsServiceCE {
|
|||
analyticsProperties.remove(FieldName.EVENT_DATA);
|
||||
}
|
||||
|
||||
sendEvent(eventTag, username, analyticsProperties);
|
||||
return object;
|
||||
return sendEvent(eventTag, username, analyticsProperties)
|
||||
.thenReturn(object);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import com.appsmith.server.constants.GitDefaultCommitMessage;
|
|||
import com.appsmith.server.constants.SerialiseApplicationObjective;
|
||||
import com.appsmith.server.domains.Application;
|
||||
import com.appsmith.server.domains.ApplicationMode;
|
||||
import com.appsmith.server.dtos.ApplicationJson;
|
||||
import com.appsmith.server.domains.GitApplicationMetadata;
|
||||
import com.appsmith.server.domains.GitAuth;
|
||||
import com.appsmith.server.domains.GitDeployKeys;
|
||||
|
|
@ -27,6 +26,7 @@ import com.appsmith.server.domains.Plugin;
|
|||
import com.appsmith.server.domains.UserData;
|
||||
import com.appsmith.server.domains.Workspace;
|
||||
import com.appsmith.server.dtos.ApplicationImportDTO;
|
||||
import com.appsmith.server.dtos.ApplicationJson;
|
||||
import com.appsmith.server.dtos.GitCommitDTO;
|
||||
import com.appsmith.server.dtos.GitConnectDTO;
|
||||
import com.appsmith.server.dtos.GitDocsDTO;
|
||||
|
|
@ -93,8 +93,6 @@ import static com.appsmith.external.constants.GitConstants.EMPTY_COMMIT_ERROR_ME
|
|||
import static com.appsmith.external.constants.GitConstants.GIT_CONFIG_ERROR;
|
||||
import static com.appsmith.external.constants.GitConstants.GIT_PROFILE_ERROR;
|
||||
import static com.appsmith.external.constants.GitConstants.MERGE_CONFLICT_BRANCH_NAME;
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_ACTIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_PAGES;
|
||||
import static com.appsmith.server.constants.CommentConstants.APPSMITH_BOT_USERNAME;
|
||||
import static com.appsmith.server.constants.FieldName.DEFAULT;
|
||||
import static com.appsmith.server.helpers.DefaultResourcesUtils.createDefaultIdsOrUpdateWithGivenResourceIds;
|
||||
|
|
@ -2455,10 +2453,7 @@ public class GitServiceCEImpl implements GitServiceCE {
|
|||
);
|
||||
analyticsProps.put(FieldName.EVENT_DATA, eventData);
|
||||
return sessionUserService.getCurrentUser()
|
||||
.map(user -> {
|
||||
analyticsService.sendEvent(eventName, user.getUsername(), analyticsProps);
|
||||
return application;
|
||||
});
|
||||
.flatMap(user -> analyticsService.sendEvent(eventName, user.getUsername(), analyticsProps).thenReturn(application));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -237,17 +237,16 @@ public class MockDataServiceCEImpl implements MockDataServiceCE {
|
|||
}
|
||||
|
||||
return sessionUserService.getCurrentUser()
|
||||
.map(user -> {
|
||||
analyticsService.sendEvent(
|
||||
AnalyticsEvents.CREATE.getEventName(),
|
||||
user.getUsername(),
|
||||
Map.of(
|
||||
"MockDataSource", defaultIfNull(name, ""),
|
||||
"orgId", defaultIfNull(workspaceId, "")
|
||||
)
|
||||
);
|
||||
return user;
|
||||
});
|
||||
.flatMap(user ->
|
||||
analyticsService.sendEvent(
|
||||
AnalyticsEvents.CREATE.getEventName(),
|
||||
user.getUsername(),
|
||||
Map.of(
|
||||
"MockDataSource", defaultIfNull(name, ""),
|
||||
"orgId", defaultIfNull(workspaceId, "")
|
||||
)
|
||||
).thenReturn(user)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1059,7 +1059,7 @@ public class CreateDBTablePageSolutionCEImpl implements CreateDBTablePageSolutio
|
|||
private Mono<CRUDPageResponseDTO> sendGenerateCRUDPageAnalyticsEvent(CRUDPageResponseDTO crudPage, Datasource datasource, String pluginName) {
|
||||
PageDTO page = crudPage.getPage();
|
||||
return sessionUserService.getCurrentUser()
|
||||
.map(currentUser -> {
|
||||
.flatMap(currentUser -> {
|
||||
try {
|
||||
final Map<String, Object> data = Map.of(
|
||||
"applicationId", page.getApplicationId(),
|
||||
|
|
@ -1069,13 +1069,13 @@ public class CreateDBTablePageSolutionCEImpl implements CreateDBTablePageSolutio
|
|||
"datasourceId", datasource.getId(),
|
||||
"organizationId", datasource.getWorkspaceId()
|
||||
);
|
||||
analyticsService.sendEvent(AnalyticsEvents.GENERATE_CRUD_PAGE.getEventName(), currentUser.getUsername(), data);
|
||||
return analyticsService.sendEvent(AnalyticsEvents.GENERATE_CRUD_PAGE.getEventName(), currentUser.getUsername(), data)
|
||||
.thenReturn(crudPage);
|
||||
} catch (Exception e) {
|
||||
log.warn("Error sending generate CRUD DB table page data point", e);
|
||||
}
|
||||
return crudPage;
|
||||
return Mono.just(crudPage);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -480,7 +480,7 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica
|
|||
});
|
||||
})
|
||||
.then(currentUserMono)
|
||||
.map(user -> {
|
||||
.flatMap(user -> {
|
||||
stopwatch.stopTimer();
|
||||
final Map<String, Object> data = Map.of(
|
||||
FieldName.APPLICATION_ID, applicationId,
|
||||
|
|
@ -490,8 +490,8 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica
|
|||
FieldName.FLOW_NAME, stopwatch.getFlow(),
|
||||
"executionTime", stopwatch.getExecutionTime()
|
||||
);
|
||||
analyticsService.sendEvent(AnalyticsEvents.UNIT_EXECUTION_TIME.getEventName(), user.getUsername(), data);
|
||||
return applicationJson;
|
||||
return analyticsService.sendEvent(AnalyticsEvents.UNIT_EXECUTION_TIME.getEventName(), user.getUsername(), data)
|
||||
.thenReturn(applicationJson);
|
||||
})
|
||||
.then(allCustomJSLibListMono)
|
||||
.map(allCustomLibList -> {
|
||||
|
|
@ -1182,7 +1182,7 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica
|
|||
.then(applicationService.update(importedApplication.getId(), importedApplication))
|
||||
.then(sendImportExportApplicationAnalyticsEvent(importedApplication.getId(), AnalyticsEvents.IMPORT))
|
||||
.zipWith(currUserMono)
|
||||
.map(tuple -> {
|
||||
.flatMap(tuple -> {
|
||||
Application application = tuple.getT1();
|
||||
stopwatch.stopTimer();
|
||||
stopwatch.stopAndLogTimeInMillis();
|
||||
|
|
@ -1195,8 +1195,8 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica
|
|||
FieldName.FLOW_NAME, stopwatch.getFlow(),
|
||||
"executionTime", stopwatch.getExecutionTime()
|
||||
);
|
||||
analyticsService.sendEvent(AnalyticsEvents.UNIT_EXECUTION_TIME.getEventName(), tuple.getT2().getUsername(), data);
|
||||
return application;
|
||||
return analyticsService.sendEvent(AnalyticsEvents.UNIT_EXECUTION_TIME.getEventName(), tuple.getT2().getUsername(), data)
|
||||
.thenReturn(application);
|
||||
});
|
||||
})
|
||||
.onErrorResume(throwable -> {
|
||||
|
|
|
|||
|
|
@ -227,7 +227,8 @@ public class UserSignupCEImpl implements UserSignupCE {
|
|||
configService.getInstanceId()
|
||||
.map(instanceId -> {
|
||||
log.debug("Installation setup complete.");
|
||||
analyticsService.sendEvent(
|
||||
analyticsService.identifyInstance(instanceId, userData.getRole(), userData.getUseCase());
|
||||
return analyticsService.sendEvent(
|
||||
AnalyticsEvents.INSTALLATION_SETUP_COMPLETE.getEventName(),
|
||||
instanceId,
|
||||
Map.of(
|
||||
|
|
@ -238,9 +239,7 @@ public class UserSignupCEImpl implements UserSignupCE {
|
|||
"goal", ObjectUtils.defaultIfNull(userData.getUseCase(), "")
|
||||
),
|
||||
false
|
||||
);
|
||||
analyticsService.identifyInstance(instanceId, userData.getRole(), userData.getUseCase());
|
||||
return instanceId;
|
||||
).thenReturn(instanceId);
|
||||
}),
|
||||
envManager.applyChanges(Map.of(
|
||||
APPSMITH_DISABLE_TELEMETRY.name(),
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user