PromucFlow_constructor/app/client/src/utils/storage.ts
Hetu Nandu 742ec95687
fix: Version update user analytics (#25396)
Collects more info about how users are updating their app to help us
understand the effectiveness of current update prompts

fixes #24962
2023-07-18 08:50:27 +05:30

552 lines
15 KiB
TypeScript

import log from "loglevel";
import moment from "moment";
import localforage from "localforage";
import type { VersionUpdateState } from "../sagas/WebsocketSagas/versionUpdatePrompt";
export const STORAGE_KEYS: {
[id: string]: string;
} = {
AUTH_EXPIRATION: "Auth.expiration",
ROUTE_BEFORE_LOGIN: "RedirectPath",
COPIED_WIDGET: "CopiedWidget",
GROUP_COPIED_WIDGETS: "groupCopiedWidgets",
POST_WELCOME_TOUR: "PostWelcomeTour",
RECENT_ENTITIES: "RecentEntities",
TEMPLATES_NOTIFICATION_SEEN: "TEMPLATES_NOTIFICATION_SEEN",
ONBOARDING_FORM_IN_PROGRESS: "ONBOARDING_FORM_IN_PROGRESS",
ENABLE_START_SIGNPOSTING: "ENABLE_START_SIGNPOSTING",
FIRST_TIME_USER_ONBOARDING_APPLICATION_IDS:
"FIRST_TIME_USER_ONBOARDING_APPLICATION_IDS",
FIRST_TIME_USER_ONBOARDING_INTRO_MODAL_VISIBILITY:
"FIRST_TIME_USER_ONBOARDING_INTRO_MODAL_VISIBILITY",
HIDE_CONCURRENT_EDITOR_WARNING_TOAST: "HIDE_CONCURRENT_EDITOR_WARNING_TOAST",
APP_THEMING_BETA_SHOWN: "APP_THEMING_BETA_SHOWN",
FIRST_TIME_USER_ONBOARDING_TELEMETRY_CALLOUT_VISIBILITY:
"FIRST_TIME_USER_ONBOARDING_TELEMETRY_CALLOUT_VISIBILITY",
SIGNPOSTING_APP_STATE: "SIGNPOSTING_APP_STATE",
AI_TRIGGERED: "AI_TRIGGERED",
FEATURE_WALKTHROUGH: "FEATURE_WALKTHROUGH",
USER_SIGN_UP: "USER_SIGN_UP",
VERSION_UPDATE_STATE: "VERSION_UPDATE_STATE",
};
const store = localforage.createInstance({
name: "Appsmith",
});
export const resetAuthExpiration = () => {
const expireBy = moment().add(1, "h").format();
store.setItem(STORAGE_KEYS.AUTH_EXPIRATION, expireBy).catch((error) => {
log.error("Unable to set expiration time");
log.error(error);
});
};
export const hasAuthExpired = async () => {
const expireBy: string | null = await store.getItem(
STORAGE_KEYS.AUTH_EXPIRATION,
);
if (expireBy && moment().isAfter(moment(expireBy))) {
return true;
}
return false;
};
export const saveCopiedWidgets = async (widgetJSON: string) => {
try {
await store.setItem(STORAGE_KEYS.COPIED_WIDGET, widgetJSON);
return true;
} catch (error) {
log.error("An error occurred when storing copied widget: ", error);
return false;
}
};
const getStoredUsersBetaFlags = (email: any) => {
return store.getItem(email);
};
const setStoredUsersBetaFlags = (email: any, userBetaFlagsObj: any) => {
return store.setItem(email, userBetaFlagsObj);
};
export const setBetaFlag = async (email: any, key: string, value: any) => {
const userBetaFlagsObj: any = await getStoredUsersBetaFlags(email);
const updatedObj = {
...userBetaFlagsObj,
[key]: value,
};
setStoredUsersBetaFlags(email, updatedObj);
};
export const getBetaFlag = async (email: any, key: string) => {
const userBetaFlagsObj: any = await getStoredUsersBetaFlags(email);
return userBetaFlagsObj && userBetaFlagsObj[key];
};
export const getReflowOnBoardingFlag = async (email: any) => {
const userBetaFlagsObj: any = await getStoredUsersBetaFlags(email);
return (
userBetaFlagsObj && userBetaFlagsObj[STORAGE_KEYS.REFLOW_ONBOARDED_FLAG]
);
};
export const getCopiedWidgets = async () => {
try {
const copiedWidgetData: string | null = await store.getItem(
STORAGE_KEYS.COPIED_WIDGET,
);
if (copiedWidgetData && copiedWidgetData.length > 0) {
return JSON.parse(copiedWidgetData);
}
} catch (error) {
log.error("An error occurred when fetching copied widget: ", error);
return;
}
return [];
};
export const setPostWelcomeTourState = async (flag: boolean) => {
try {
await store.setItem(STORAGE_KEYS.POST_WELCOME_TOUR, flag);
return true;
} catch (error) {
log.error("An error occurred when setting post welcome tour state", error);
return false;
}
};
export const getPostWelcomeTourState = async () => {
try {
return await store.getItem(STORAGE_KEYS.POST_WELCOME_TOUR);
} catch (error) {
log.error("An error occurred when getting post welcome tour state", error);
}
};
export const setRecentAppEntities = async (entities: any, appId: string) => {
try {
const recentEntities =
((await store.getItem(STORAGE_KEYS.RECENT_ENTITIES)) as Record<
string,
any
>) || {};
recentEntities[appId] = entities;
await store.setItem(STORAGE_KEYS.RECENT_ENTITIES, recentEntities);
} catch (error) {
log.error("An error occurred while saving recent entities");
log.error(error);
}
};
export const fetchRecentAppEntities = async (recentEntitiesKey: string) => {
try {
const recentEntities = (await store.getItem(
STORAGE_KEYS.RECENT_ENTITIES,
)) as Record<string, any>;
return (recentEntities && recentEntities[recentEntitiesKey]) || [];
} catch (error) {
log.error("An error occurred while fetching recent entities");
log.error(error);
}
};
export const deleteRecentAppEntities = async (appId: string) => {
try {
const recentEntities =
((await store.getItem(STORAGE_KEYS.RECENT_ENTITIES)) as Record<
string,
any
>) || {};
if (typeof recentEntities === "object") {
// todo (rishabh s) purge recent entities across branches
delete recentEntities[appId];
}
await store.setItem(STORAGE_KEYS.RECENT_ENTITIES, recentEntities);
} catch (error) {
log.error("An error occurred while saving recent entities");
log.error(error);
}
};
export const setOnboardingFormInProgress = async (flag?: boolean) => {
try {
await store.setItem(STORAGE_KEYS.ONBOARDING_FORM_IN_PROGRESS, flag);
return true;
} catch (error) {
log.error("An error occurred when setting ONBOARDING_FORM_IN_PROGRESS");
log.error(error);
return false;
}
};
export const getOnboardingFormInProgress = async () => {
try {
const onboardingFormInProgress = await store.getItem(
STORAGE_KEYS.ONBOARDING_FORM_IN_PROGRESS,
);
return onboardingFormInProgress;
} catch (error) {
log.error("An error occurred while fetching ONBOARDING_FORM_IN_PROGRESS");
log.error(error);
}
};
export const setEnableStartSignposting = async (flag: boolean) => {
try {
await store.setItem(STORAGE_KEYS.ENABLE_START_SIGNPOSTING, flag);
return true;
} catch (error) {
log.error("An error occurred while setting ENABLE_START_SIGNPOSTING");
log.error(error);
}
};
export const getEnableStartSignposting = async () => {
try {
const enableStartSignposting: string | null = await store.getItem(
STORAGE_KEYS.ENABLE_START_SIGNPOSTING,
);
return enableStartSignposting;
} catch (error) {
log.error("An error occurred while fetching ENABLE_START_SIGNPOSTING");
log.error(error);
}
};
export const setFirstTimeUserOnboardingApplicationId = async (id: string) => {
try {
let ids: unknown = await store.getItem(
STORAGE_KEYS.FIRST_TIME_USER_ONBOARDING_APPLICATION_IDS,
);
if (ids) {
ids = JSON.parse(ids as string);
if (Array.isArray(ids) && !ids.includes(id)) {
ids.push(id);
}
} else {
ids = [id];
}
await store.setItem(
STORAGE_KEYS.FIRST_TIME_USER_ONBOARDING_APPLICATION_IDS,
JSON.stringify(ids),
);
return true;
} catch (error) {
log.error(
"An error occurred while setting FIRST_TIME_USER_ONBOARDING_APPLICATION_IDS",
);
log.error(error);
}
};
export const removeFirstTimeUserOnboardingApplicationId = async (
id: string,
) => {
try {
let ids: unknown = await store.getItem(
STORAGE_KEYS.FIRST_TIME_USER_ONBOARDING_APPLICATION_IDS,
);
if (ids) {
ids = JSON.parse(ids as string);
if (Array.isArray(ids)) {
ids = ids.filter((exisitingId) => exisitingId !== id);
await store.setItem(
STORAGE_KEYS.FIRST_TIME_USER_ONBOARDING_APPLICATION_IDS,
JSON.stringify(ids),
);
}
}
return true;
} catch (error) {
log.error(
"An error occurred while setting FIRST_TIME_USER_ONBOARDING_APPLICATION_IDS",
);
log.error(error);
}
};
export const removeAllFirstTimeUserOnboardingApplicationIds = async () => {
try {
await store.setItem(
STORAGE_KEYS.FIRST_TIME_USER_ONBOARDING_APPLICATION_IDS,
JSON.stringify([]),
);
return true;
} catch (error) {
log.error(
"An error occurred while resetting FIRST_TIME_USER_ONBOARDING_APPLICATION_IDS",
);
log.error(error);
}
};
export const getFirstTimeUserOnboardingApplicationIds = async () => {
try {
const ids = await store.getItem(
STORAGE_KEYS.FIRST_TIME_USER_ONBOARDING_APPLICATION_IDS,
);
if (ids) {
if (Array.isArray(JSON.parse(ids as string))) {
return JSON.parse(ids as string);
}
}
return [];
} catch (error) {
log.error(
"An error occurred while fetching FIRST_TIME_USER_ONBOARDING_APPLICATION_IDS",
);
log.error(error);
}
};
export const setFirstTimeUserOnboardingIntroModalVisibility = async (
flag: boolean,
) => {
try {
await store.setItem(
STORAGE_KEYS.FIRST_TIME_USER_ONBOARDING_INTRO_MODAL_VISIBILITY,
flag,
);
return true;
} catch (error) {
log.error(
"An error occurred while setting FIRST_TIME_USER_ONBOARDING_INTRO_MODAL_VISIBILITY",
);
log.error(error);
}
};
export const getFirstTimeUserOnboardingIntroModalVisibility = async () => {
try {
const flag: string | null = await store.getItem(
STORAGE_KEYS.FIRST_TIME_USER_ONBOARDING_INTRO_MODAL_VISIBILITY,
);
return flag;
} catch (error) {
log.error(
"An error occurred while fetching FIRST_TIME_USER_ONBOARDING_INTRO_MODAL_VISIBILITY",
);
log.error(error);
}
};
export const hideConcurrentEditorWarningToast = async () => {
try {
await store.setItem(
STORAGE_KEYS.HIDE_CONCURRENT_EDITOR_WARNING_TOAST,
true,
);
return true;
} catch (error) {
log.error(
"An error occurred while setting HIDE_CONCURRENT_EDITOR_WARNING_TOAST",
);
log.error(error);
}
};
export const getIsConcurrentEditorWarningToastHidden = async () => {
try {
const flag = await store.getItem(
STORAGE_KEYS.HIDE_CONCURRENT_EDITOR_WARNING_TOAST,
);
return flag;
} catch (error) {
log.error(
"An error occurred while fetching HIDE_CONCURRENT_EDITOR_WARNING_TOAST",
);
log.error(error);
}
};
export const getTemplateNotificationSeen = async () => {
try {
const seenTemplateNotifications = await store.getItem(
STORAGE_KEYS.TEMPLATES_NOTIFICATION_SEEN,
);
return seenTemplateNotifications;
} catch (error) {
log.error(
"An error occurred while getting TEMPLATES_NOTIFICATION_SEEN flag: ",
error,
);
return false;
}
};
export const setTemplateNotificationSeen = async (flag: boolean) => {
try {
await store.setItem(STORAGE_KEYS.TEMPLATES_NOTIFICATION_SEEN, flag);
return true;
} catch (error) {
log.error(
"An error occurred while setting TEMPLATES_NOTIFICATION_SEEN flag: ",
error,
);
return false;
}
};
export const getFirstTimeUserOnboardingTelemetryCalloutIsAlreadyShown =
async () => {
try {
const flag = await store.getItem(
STORAGE_KEYS.FIRST_TIME_USER_ONBOARDING_TELEMETRY_CALLOUT_VISIBILITY,
);
return flag;
} catch (error) {
log.error(
"An error occurred while fetching FIRST_TIME_USER_ONBOARDING_TELEMETRY_CALLOUT_VISIBILITY",
);
log.error(error);
}
};
export const setFirstTimeUserOnboardingTelemetryCalloutVisibility = async (
flag: boolean,
) => {
try {
await store.setItem(
STORAGE_KEYS.FIRST_TIME_USER_ONBOARDING_TELEMETRY_CALLOUT_VISIBILITY,
flag,
);
return true;
} catch (error) {
log.error(
"An error occurred while fetching FIRST_TIME_USER_ONBOARDING_TELEMETRY_CALLOUT_VISIBILITY",
);
log.error(error);
}
};
export const setAIPromptTriggered = async () => {
try {
let noOfTimesAITriggered: number = await getAIPromptTriggered();
if (noOfTimesAITriggered >= 5) {
return noOfTimesAITriggered;
}
noOfTimesAITriggered += 1;
await store.setItem(STORAGE_KEYS.AI_TRIGGERED, noOfTimesAITriggered);
return noOfTimesAITriggered;
} catch (error) {
log.error("An error occurred while setting AI_TRIGGERED");
log.error(error);
return 0;
}
};
export const getAIPromptTriggered = async () => {
try {
const flag: number | null = await store.getItem(STORAGE_KEYS.AI_TRIGGERED);
if (flag === null) return 0;
return flag;
} catch (error) {
log.error("An error occurred while fetching AI_TRIGGERED");
log.error(error);
return 0;
}
};
export const setFeatureFlagShownStatus = async (key: string, value: any) => {
try {
let flagsJSON: Record<string, any> | null = await store.getItem(
STORAGE_KEYS.FEATURE_WALKTHROUGH,
);
if (typeof flagsJSON === "object" && flagsJSON) {
flagsJSON[key] = value;
} else {
flagsJSON = { [key]: value };
}
await store.setItem(STORAGE_KEYS.FEATURE_WALKTHROUGH, flagsJSON);
return true;
} catch (error) {
log.error("An error occurred while updating FEATURE_WALKTHROUGH");
log.error(error);
}
};
export const getFeatureFlagShownStatus = async (key: string) => {
try {
const flagsJSON: Record<string, any> | null = await store.getItem(
STORAGE_KEYS.FEATURE_WALKTHROUGH,
);
if (typeof flagsJSON === "object" && flagsJSON) {
return !!flagsJSON[key];
}
return false;
} catch (error) {
log.error("An error occurred while reading FEATURE_WALKTHROUGH");
log.error(error);
}
};
export const setUserSignedUpFlag = async (email: string) => {
try {
let userSignedUp: Record<string, any> | null = await store.getItem(
STORAGE_KEYS.USER_SIGN_UP,
);
if (typeof userSignedUp === "object" && userSignedUp) {
userSignedUp[email] = Date.now();
} else {
userSignedUp = { [email]: Date.now() };
}
await store.setItem(STORAGE_KEYS.USER_SIGN_UP, userSignedUp);
return true;
} catch (error) {
log.error("An error occurred while updating USER_SIGN_UP");
log.error(error);
}
};
export const isUserSignedUpFlagSet = async (email: string) => {
try {
const userSignedUp: Record<string, any> | null = await store.getItem(
STORAGE_KEYS.USER_SIGN_UP,
);
if (typeof userSignedUp === "object" && userSignedUp) {
return !!userSignedUp[email];
}
return false;
} catch (error) {
log.error("An error occurred while reading USER_SIGN_UP");
log.error(error);
}
};
export const setVersionUpdateState = async (state: VersionUpdateState) => {
try {
await store.setItem(STORAGE_KEYS.VERSION_UPDATE_STATE, state);
} catch (e) {
log.error("An error occurred while storing version update state", e);
}
};
export const getVersionUpdateState =
async (): Promise<VersionUpdateState | null> => {
return await store.getItem<VersionUpdateState | null>(
STORAGE_KEYS.VERSION_UPDATE_STATE,
);
};
export const removeVersionUpdateState = async () => {
return store.removeItem(STORAGE_KEYS.VERSION_UPDATE_STATE);
};