diff --git a/app/actions/definitions/collections.tsx b/app/actions/definitions/collections.tsx
index 8efabd190..accc602c4 100644
--- a/app/actions/definitions/collections.tsx
+++ b/app/actions/definitions/collections.tsx
@@ -23,6 +23,7 @@ const ColorCollectionIcon = ({ collection }: { collection: Collection }) => {
export const openCollection = createAction({
name: ({ t }) => t("Open collection"),
+ analyticsName: "Open collection",
section: CollectionSection,
shortcut: ["o", "c"],
icon: ,
@@ -42,6 +43,7 @@ export const openCollection = createAction({
export const createCollection = createAction({
name: ({ t }) => t("New collection"),
+ analyticsName: "New collection",
section: CollectionSection,
icon: ,
keywords: "create",
@@ -60,6 +62,7 @@ export const createCollection = createAction({
export const editCollection = createAction({
name: ({ t, isContextMenu }) =>
isContextMenu ? `${t("Edit")}…` : t("Edit collection"),
+ analyticsName: "Edit collection",
section: CollectionSection,
icon: ,
visible: ({ stores, activeCollectionId }) =>
@@ -85,6 +88,7 @@ export const editCollection = createAction({
export const editCollectionPermissions = createAction({
name: ({ t, isContextMenu }) =>
isContextMenu ? `${t("Permissions")}…` : t("Collection permissions"),
+ analyticsName: "Collection permissions",
section: CollectionSection,
icon: ,
visible: ({ stores, activeCollectionId }) =>
@@ -104,6 +108,7 @@ export const editCollectionPermissions = createAction({
export const starCollection = createAction({
name: ({ t }) => t("Star"),
+ analyticsName: "Star collection",
section: CollectionSection,
icon: ,
keywords: "favorite bookmark",
@@ -129,6 +134,7 @@ export const starCollection = createAction({
export const unstarCollection = createAction({
name: ({ t }) => t("Unstar"),
+ analyticsName: "Unstar collection",
section: CollectionSection,
icon: ,
keywords: "unfavorite unbookmark",
diff --git a/app/actions/definitions/documents.tsx b/app/actions/definitions/documents.tsx
index ba9b0a3dd..78dd82bbf 100644
--- a/app/actions/definitions/documents.tsx
+++ b/app/actions/definitions/documents.tsx
@@ -45,6 +45,7 @@ import {
export const openDocument = createAction({
name: ({ t }) => t("Open document"),
+ analyticsName: "Open document",
section: DocumentSection,
shortcut: ["o", "d"],
keywords: "go to",
@@ -69,6 +70,7 @@ export const openDocument = createAction({
export const createDocument = createAction({
name: ({ t }) => t("New document"),
+ analyticsName: "New document",
section: DocumentSection,
icon: ,
keywords: "create",
@@ -82,6 +84,7 @@ export const createDocument = createAction({
export const starDocument = createAction({
name: ({ t }) => t("Star"),
+ analyticsName: "Star document",
section: DocumentSection,
icon: ,
keywords: "favorite bookmark",
@@ -106,6 +109,7 @@ export const starDocument = createAction({
export const unstarDocument = createAction({
name: ({ t }) => t("Unstar"),
+ analyticsName: "Unstar document",
section: DocumentSection,
icon: ,
keywords: "unfavorite unbookmark",
@@ -131,6 +135,7 @@ export const unstarDocument = createAction({
export const publishDocument = createAction({
name: ({ t }) => t("Publish"),
+ analyticsName: "Publish document",
section: DocumentSection,
icon: ,
visible: ({ activeDocumentId, stores }) => {
@@ -171,6 +176,7 @@ export const publishDocument = createAction({
export const unpublishDocument = createAction({
name: ({ t }) => t("Unpublish"),
+ analyticsName: "Unpublish document",
section: DocumentSection,
icon: ,
visible: ({ activeDocumentId, stores }) => {
@@ -196,6 +202,7 @@ export const unpublishDocument = createAction({
export const subscribeDocument = createAction({
name: ({ t }) => t("Subscribe"),
+ analyticsName: "Subscribe to document",
section: DocumentSection,
icon: ,
visible: ({ activeDocumentId, stores }) => {
@@ -227,6 +234,7 @@ export const subscribeDocument = createAction({
export const unsubscribeDocument = createAction({
name: ({ t }) => t("Unsubscribe"),
+ analyticsName: "Unsubscribe from document",
section: DocumentSection,
icon: ,
visible: ({ activeDocumentId, stores }) => {
@@ -258,6 +266,7 @@ export const unsubscribeDocument = createAction({
export const downloadDocumentAsHTML = createAction({
name: ({ t }) => t("HTML"),
+ analyticsName: "Download document as HTML",
section: DocumentSection,
keywords: "html export",
icon: ,
@@ -276,6 +285,7 @@ export const downloadDocumentAsHTML = createAction({
export const downloadDocumentAsPDF = createAction({
name: ({ t }) => t("PDF"),
+ analyticsName: "Download document as PDF",
section: DocumentSection,
keywords: "export",
icon: ,
@@ -303,6 +313,7 @@ export const downloadDocumentAsPDF = createAction({
export const downloadDocumentAsMarkdown = createAction({
name: ({ t }) => t("Markdown"),
+ analyticsName: "Download document as Markdown",
section: DocumentSection,
keywords: "md markdown export",
icon: ,
@@ -322,6 +333,7 @@ export const downloadDocumentAsMarkdown = createAction({
export const downloadDocument = createAction({
name: ({ t, isContextMenu }) =>
isContextMenu ? t("Download") : t("Download document"),
+ analyticsName: "Download document",
section: DocumentSection,
icon: ,
keywords: "export",
@@ -335,6 +347,7 @@ export const downloadDocument = createAction({
export const duplicateDocument = createAction({
name: ({ t, isContextMenu }) =>
isContextMenu ? t("Duplicate") : t("Duplicate document"),
+ analyticsName: "Duplicate document",
section: DocumentSection,
icon: ,
keywords: "copy",
@@ -371,7 +384,7 @@ export const pinDocumentToCollection = createAction({
collectionName,
});
},
-
+ analyticsName: "Pin document to collection",
section: DocumentSection,
icon: ,
iconInContextMenu: false,
@@ -407,6 +420,7 @@ export const pinDocumentToCollection = createAction({
*/
export const pinDocumentToHome = createAction({
name: ({ t }) => t("Pin to home"),
+ analyticsName: "Pin document to home",
section: DocumentSection,
icon: ,
iconInContextMenu: false,
@@ -438,6 +452,7 @@ export const pinDocumentToHome = createAction({
export const pinDocument = createAction({
name: ({ t }) => t("Pin"),
+ analyticsName: "Pin document",
section: DocumentSection,
icon: ,
children: [pinDocumentToCollection, pinDocumentToHome],
@@ -446,6 +461,7 @@ export const pinDocument = createAction({
export const printDocument = createAction({
name: ({ t, isContextMenu }) =>
isContextMenu ? t("Print") : t("Print document"),
+ analyticsName: "Print document",
section: DocumentSection,
icon: ,
visible: ({ activeDocumentId }) => !!(activeDocumentId && window.print),
@@ -456,6 +472,7 @@ export const printDocument = createAction({
export const importDocument = createAction({
name: ({ t }) => t("Import document"),
+ analyticsName: "Import document",
section: DocumentSection,
icon: ,
keywords: "upload",
@@ -504,6 +521,7 @@ export const importDocument = createAction({
export const createTemplate = createAction({
name: ({ t }) => t("Templatize"),
+ analyticsName: "Templatize document",
section: DocumentSection,
icon: ,
keywords: "new create template",
@@ -536,8 +554,9 @@ export const createTemplate = createAction({
export const openRandomDocument = createAction({
id: "random",
- section: DocumentSection,
name: ({ t }) => t(`Open random document`),
+ analyticsName: "Open random document",
+ section: DocumentSection,
icon: ,
perform: ({ stores, activeDocumentId }) => {
const documentPaths = stores.collections.pathsToDocuments.filter(
@@ -555,9 +574,10 @@ export const openRandomDocument = createAction({
export const searchDocumentsForQuery = (searchQuery: string) =>
createAction({
id: "search",
- section: DocumentSection,
name: ({ t }) =>
t(`Search documents for "{{searchQuery}}"`, { searchQuery }),
+ analyticsName: "Search documents",
+ section: DocumentSection,
icon: ,
perform: () => history.push(searchPath(searchQuery)),
visible: ({ location }) => location.pathname !== searchPath(),
@@ -565,6 +585,7 @@ export const searchDocumentsForQuery = (searchQuery: string) =>
export const moveDocument = createAction({
name: ({ t }) => t("Move"),
+ analyticsName: "Move document",
section: DocumentSection,
icon: ,
visible: ({ activeDocumentId, stores }) => {
@@ -593,6 +614,7 @@ export const moveDocument = createAction({
export const archiveDocument = createAction({
name: ({ t }) => t("Archive"),
+ analyticsName: "Archive document",
section: DocumentSection,
icon: ,
visible: ({ activeDocumentId, stores }) => {
@@ -618,6 +640,7 @@ export const archiveDocument = createAction({
export const deleteDocument = createAction({
name: ({ t }) => t("Delete"),
+ analyticsName: "Delete document",
section: DocumentSection,
icon: ,
dangerous: true,
@@ -652,6 +675,7 @@ export const deleteDocument = createAction({
export const permanentlyDeleteDocument = createAction({
name: ({ t }) => t("Permanently delete"),
+ analyticsName: "Permanently delete document",
section: DocumentSection,
icon: ,
dangerous: true,
@@ -686,6 +710,7 @@ export const permanentlyDeleteDocument = createAction({
export const openDocumentHistory = createAction({
name: ({ t }) => t("History"),
+ analyticsName: "Open document history",
section: DocumentSection,
icon: ,
visible: ({ activeDocumentId, stores }) => {
@@ -706,6 +731,7 @@ export const openDocumentHistory = createAction({
export const openDocumentInsights = createAction({
name: ({ t }) => t("Insights"),
+ analyticsName: "Open document insights",
section: DocumentSection,
icon: ,
visible: ({ activeDocumentId, stores }) => {
diff --git a/app/actions/definitions/navigation.tsx b/app/actions/definitions/navigation.tsx
index a14ad3240..4c7d85bdf 100644
--- a/app/actions/definitions/navigation.tsx
+++ b/app/actions/definitions/navigation.tsx
@@ -43,6 +43,7 @@ import {
export const navigateToHome = createAction({
name: ({ t }) => t("Home"),
+ analyticsName: "Navigate to home",
section: NavigationSection,
shortcut: ["d"],
icon: ,
@@ -54,12 +55,14 @@ export const navigateToRecentSearchQuery = (searchQuery: SearchQuery) =>
createAction({
section: RecentSearchesSection,
name: searchQuery.query,
+ analyticsName: "Navigate to recent search query",
icon: ,
perform: () => history.push(searchPath(searchQuery.query)),
});
export const navigateToDrafts = createAction({
name: ({ t }) => t("Drafts"),
+ analyticsName: "Navigate to drafts",
section: NavigationSection,
icon: ,
perform: () => history.push(draftsPath()),
@@ -68,6 +71,7 @@ export const navigateToDrafts = createAction({
export const navigateToTemplates = createAction({
name: ({ t }) => t("Templates"),
+ analyticsName: "Navigate to templates",
section: NavigationSection,
icon: ,
perform: () => history.push(templatesPath()),
@@ -76,6 +80,7 @@ export const navigateToTemplates = createAction({
export const navigateToArchive = createAction({
name: ({ t }) => t("Archive"),
+ analyticsName: "Navigate to archive",
section: NavigationSection,
shortcut: ["g", "a"],
icon: ,
@@ -85,6 +90,7 @@ export const navigateToArchive = createAction({
export const navigateToTrash = createAction({
name: ({ t }) => t("Trash"),
+ analyticsName: "Navigate to trash",
section: NavigationSection,
icon: ,
perform: () => history.push(trashPath()),
@@ -93,6 +99,7 @@ export const navigateToTrash = createAction({
export const navigateToSettings = createAction({
name: ({ t }) => t("Settings"),
+ analyticsName: "Navigate to settings",
section: NavigationSection,
shortcut: ["g", "s"],
icon: ,
@@ -103,6 +110,7 @@ export const navigateToSettings = createAction({
export const navigateToProfileSettings = createAction({
name: ({ t }) => t("Profile"),
+ analyticsName: "Navigate to profile settings",
section: NavigationSection,
iconInContextMenu: false,
icon: ,
@@ -111,6 +119,7 @@ export const navigateToProfileSettings = createAction({
export const navigateToAccountPreferences = createAction({
name: ({ t }) => t("Preferences"),
+ analyticsName: "Navigate to account preferences",
section: NavigationSection,
iconInContextMenu: false,
icon: ,
@@ -119,6 +128,7 @@ export const navigateToAccountPreferences = createAction({
export const openAPIDocumentation = createAction({
name: ({ t }) => t("API documentation"),
+ analyticsName: "Open API documentation",
section: NavigationSection,
iconInContextMenu: false,
icon: ,
@@ -127,6 +137,7 @@ export const openAPIDocumentation = createAction({
export const openFeedbackUrl = createAction({
name: ({ t }) => t("Send us feedback"),
+ analyticsName: "Open feedback",
section: NavigationSection,
iconInContextMenu: false,
icon: ,
@@ -135,12 +146,14 @@ export const openFeedbackUrl = createAction({
export const openBugReportUrl = createAction({
name: ({ t }) => t("Report a bug"),
+ analyticsName: "Open bug report",
section: NavigationSection,
perform: () => window.open(githubIssuesUrl()),
});
export const openChangelog = createAction({
name: ({ t }) => t("Changelog"),
+ analyticsName: "Open changelog",
section: NavigationSection,
iconInContextMenu: false,
icon: ,
@@ -149,6 +162,7 @@ export const openChangelog = createAction({
export const openKeyboardShortcuts = createAction({
name: ({ t }) => t("Keyboard shortcuts"),
+ analyticsName: "Open keyboard shortcuts",
section: NavigationSection,
shortcut: ["?"],
iconInContextMenu: false,
@@ -166,6 +180,7 @@ export const downloadApp = createAction({
t("Download {{ platform }} app", {
platform: isMac() ? "macOS" : "Windows",
}),
+ analyticsName: "Download app",
section: NavigationSection,
iconInContextMenu: false,
icon: ,
@@ -177,6 +192,7 @@ export const downloadApp = createAction({
export const logout = createAction({
name: ({ t }) => t("Log out"),
+ analyticsName: "Log out",
section: NavigationSection,
icon: ,
perform: () => stores.auth.logout(),
diff --git a/app/actions/definitions/revisions.tsx b/app/actions/definitions/revisions.tsx
index ed41ffea7..93f3be098 100644
--- a/app/actions/definitions/revisions.tsx
+++ b/app/actions/definitions/revisions.tsx
@@ -10,6 +10,7 @@ import { documentHistoryUrl, matchDocumentHistory } from "~/utils/routeHelpers";
export const restoreRevision = createAction({
name: ({ t }) => t("Restore revision"),
+ analyticsName: "Restore revision",
icon: ,
section: RevisionSection,
visible: ({ activeDocumentId, stores }) =>
@@ -50,6 +51,7 @@ export const restoreRevision = createAction({
export const copyLinkToRevision = createAction({
name: ({ t }) => t("Copy link"),
+ analyticsName: "Copy link to revision",
icon: ,
section: RevisionSection,
perform: async ({ activeDocumentId, stores, t }) => {
diff --git a/app/actions/definitions/settings.tsx b/app/actions/definitions/settings.tsx
index 9372b1b0e..5405c606f 100644
--- a/app/actions/definitions/settings.tsx
+++ b/app/actions/definitions/settings.tsx
@@ -7,6 +7,7 @@ import { SettingsSection } from "~/actions/sections";
export const changeToDarkTheme = createAction({
name: ({ t }) => t("Dark"),
+ analyticsName: "Change to dark theme",
icon: ,
iconInContextMenu: false,
keywords: "theme dark night",
@@ -17,6 +18,7 @@ export const changeToDarkTheme = createAction({
export const changeToLightTheme = createAction({
name: ({ t }) => t("Light"),
+ analyticsName: "Change to light theme",
icon: ,
iconInContextMenu: false,
keywords: "theme light day",
@@ -27,6 +29,7 @@ export const changeToLightTheme = createAction({
export const changeToSystemTheme = createAction({
name: ({ t }) => t("System"),
+ analyticsName: "Change to system theme",
icon: ,
iconInContextMenu: false,
keywords: "theme system default",
@@ -38,6 +41,7 @@ export const changeToSystemTheme = createAction({
export const changeTheme = createAction({
name: ({ t, isContextMenu }) =>
isContextMenu ? t("Appearance") : t("Change theme"),
+ analyticsName: "Change theme",
placeholder: ({ t }) => t("Change theme to"),
icon: () =>
stores.ui.resolvedTheme === "light" ? : ,
diff --git a/app/actions/definitions/teams.tsx b/app/actions/definitions/teams.tsx
index b9334e421..f5bb08a91 100644
--- a/app/actions/definitions/teams.tsx
+++ b/app/actions/definitions/teams.tsx
@@ -14,6 +14,7 @@ export const createTeamsList = ({ stores }: { stores: RootStore }) => {
stores.auth.availableTeams?.map((session) => ({
id: `switch-${session.id}`,
name: session.name,
+ analyticsName: "Switch workspace",
section: TeamSection,
keywords: "change switch workspace organization team",
icon: () => (
@@ -38,6 +39,7 @@ export const createTeamsList = ({ stores }: { stores: RootStore }) => {
export const switchTeam = createAction({
name: ({ t }) => t("Switch workspace"),
placeholder: ({ t }) => t("Select a workspace"),
+ analyticsName: "Switch workspace",
keywords: "change switch workspace organization team",
section: TeamSection,
visible: ({ stores }) =>
@@ -47,6 +49,7 @@ export const switchTeam = createAction({
export const createTeam = createAction({
name: ({ t }) => `${t("New workspace")}…`,
+ analyticsName: "New workspace",
keywords: "create change switch workspace organization team",
section: TeamSection,
icon: ,
diff --git a/app/actions/definitions/users.tsx b/app/actions/definitions/users.tsx
index dbfd08b77..07ddcc276 100644
--- a/app/actions/definitions/users.tsx
+++ b/app/actions/definitions/users.tsx
@@ -7,6 +7,7 @@ import { UserSection } from "~/actions/sections";
export const inviteUser = createAction({
name: ({ t }) => `${t("Invite people")}…`,
+ analyticsName: "Invite people",
icon: ,
keywords: "team member workspace user",
section: UserSection,
diff --git a/app/actions/index.ts b/app/actions/index.ts
index 738022f71..a88a20a08 100644
--- a/app/actions/index.ts
+++ b/app/actions/index.ts
@@ -9,6 +9,7 @@ import {
MenuItemButton,
MenuItemWithChildren,
} from "~/types";
+import Analytics from "~/utils/Analytics";
function resolve(value: any, context: ActionContext): T {
return typeof value === "function" ? value(context) : value;
@@ -17,6 +18,21 @@ function resolve(value: any, context: ActionContext): T {
export function createAction(definition: Optional): Action {
return {
...definition,
+ perform: (context) => {
+ // We muse use the specific analytics name here as the action name is
+ // translated and potentially contains user strings.
+ if (definition.analyticsName) {
+ Analytics.track("perform_action", definition.analyticsName, {
+ context: context.isButton
+ ? "button"
+ : context.isCommandBar
+ ? "commandbar"
+ : "contextmenu",
+ });
+ }
+
+ return definition.perform?.(context);
+ },
id: uuidv4(),
};
}
@@ -93,12 +109,13 @@ export function actionToKBar(
{
id: action.id,
name: resolvedName,
+ analyticsName: action.analyticsName,
section: resolvedSection,
placeholder: resolvedPlaceholder,
keywords: action.keywords ?? "",
shortcut: action.shortcut || [],
icon: resolvedIcon,
- perform: action.perform ? () => action?.perform?.(context) : undefined,
+ perform: () => action.perform?.(context),
},
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
].concat(children.map((child) => ({ ...child, parent: action.id })));
diff --git a/app/types.ts b/app/types.ts
index 9f12c805b..01261a47b 100644
--- a/app/types.ts
+++ b/app/types.ts
@@ -89,6 +89,7 @@ export type ActionContext = {
export type Action = {
type?: undefined;
id: string;
+ analyticsName?: string;
name: ((context: ActionContext) => string) | string;
section: ((context: ActionContext) => string) | string;
shortcut?: string[];
diff --git a/app/utils/Analytics.ts b/app/utils/Analytics.ts
new file mode 100644
index 000000000..8b07b5032
--- /dev/null
+++ b/app/utils/Analytics.ts
@@ -0,0 +1,26 @@
+/**
+ * Helper class to track events across various analytics integrations
+ */
+export default class Analytics {
+ /**
+ * Send an event to Analytics
+ *
+ * @param event The event name
+ * @param action The action name
+ */
+ public static track = (
+ event: string,
+ action: string,
+ metadata?: Record
+ ) => {
+ // GA3
+ ga?.("send", "event", event, action);
+
+ // GA4
+ window.dataLayer?.push({
+ event,
+ action,
+ ...metadata,
+ });
+ };
+}