diff --git a/lib/app.tsx b/lib/app.tsx index a83773202..2538066a1 100644 --- a/lib/app.tsx +++ b/lib/app.tsx @@ -9,6 +9,7 @@ import DialogRenderer from './dialog-renderer'; import { isElectron, isMac } from './utils/platform'; import classNames from 'classnames'; import { createNote, closeNote, toggleNavigation } from './state/ui/actions'; +import { recordEvent } from './state/analytics/middleware'; import * as settingsActions from './state/settings/actions'; @@ -36,7 +37,6 @@ type DispatchProps = { createNote: () => any; focusSearchField: () => any; openTagList: () => any; - setAccountName: (name: string) => any; setLineLength: (length: T.LineLength) => any; setNoteDisplay: (displayMode: T.ListDisplayMode) => any; setSortType: (sortType: T.SortType) => any; @@ -57,6 +57,7 @@ class AppComponent extends Component { this.toggleShortcuts(true); + recordEvent('application_opened'); __TEST__ && window.testEvents.push('booted'); } @@ -186,7 +187,6 @@ const mapDispatchToProps: S.MapDispatch = (dispatch) => { createNote: () => dispatch(createNote()), focusSearchField: () => dispatch(actions.ui.focusSearchField()), openTagList: () => dispatch(toggleNavigation()), - setAccountName: (name) => dispatch(settingsActions.setAccountName(name)), setLineLength: (length) => dispatch(settingsActions.setLineLength(length)), setNoteDisplay: (displayMode) => dispatch(settingsActions.setNoteDisplay(displayMode)), diff --git a/lib/boot-with-auth.tsx b/lib/boot-with-auth.tsx index fd31f4d0d..e6bc694be 100644 --- a/lib/boot-with-auth.tsx +++ b/lib/boot-with-auth.tsx @@ -35,8 +35,6 @@ export const bootWithToken = ( username, initSimperium(logout, token, username, createWelcomeNote) ).then((store) => { - store.dispatch(actions.settings.setAccountName(username)); - Object.defineProperties(window, { dispatch: { get() { diff --git a/lib/boot-without-auth.tsx b/lib/boot-without-auth.tsx index 00aea48b8..c5620519f 100644 --- a/lib/boot-without-auth.tsx +++ b/lib/boot-without-auth.tsx @@ -2,7 +2,7 @@ import React, { Component } from 'react'; import { render } from 'react-dom'; import { Auth as AuthApp } from './auth'; import { Auth as SimperiumAuth } from 'simperium'; -import analytics from './analytics'; +import { recordEvent } from './state/analytics/middleware'; import { validatePassword } from './utils/validate-password'; import getConfig from '../get-config'; @@ -79,7 +79,7 @@ class AppWithoutAuth extends Component { throw new Error('missing access token'); } - analytics.tracks.recordEvent('user_account_created'); + recordEvent('user_account_created'); this.props.onAuth(user.access_token, username, true); }) .catch(() => { diff --git a/lib/boot.ts b/lib/boot.ts index f740a0f2d..17f377887 100644 --- a/lib/boot.ts +++ b/lib/boot.ts @@ -18,6 +18,12 @@ const clearStorage = (): Promise => localStorage.removeItem('localQueue:tag'); localStorage.removeItem('stored_user'); + const settings = localStorage.getItem('simpleNote'); + if (settings) { + const { accountName, ...otherSettings } = settings; + localStorage.setItem('simpleNote', otherSettings); + } + Promise.all([ new Promise((resolve) => { const r = indexedDB.deleteDatabase('ghost'); @@ -173,7 +179,6 @@ const run = ( bootWithToken( () => { bootLoggingOut(); - analytics.tracks.recordEvent('user_signed_out'); clearStorage().then(() => { if (window.webConfig?.signout) { window.webConfig.signout(forceReload); @@ -196,7 +201,6 @@ const run = ( bootWithoutAuth( (token: string, username: string, createWelcomeNote: boolean) => { saveAccount(token, username); - analytics.tracks.recordEvent('user_signed_in'); run(token, username, createWelcomeNote); } ); diff --git a/lib/dialogs/import/source-importer/executor/index.tsx b/lib/dialogs/import/source-importer/executor/index.tsx index 948287072..bf54840a4 100644 --- a/lib/dialogs/import/source-importer/executor/index.tsx +++ b/lib/dialogs/import/source-importer/executor/index.tsx @@ -2,8 +2,8 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; import { throttle } from 'lodash'; -import analytics from '../../../../analytics'; import actions from '../../../../state/actions'; +import { recordEvent } from '../../../../state/analytics/middleware'; import PanelTitle from '../../../../components/panel-title'; import TransitionFadeInOut from '../../../../components/transition-fade-in-out'; @@ -47,6 +47,7 @@ type OwnProps = { type DispatchProps = { importNote: (note: T.Note) => any; + recordEvent: (eventName: string, eventProperties: T.JSONSerializable) => any; }; type Props = OwnProps & DispatchProps; @@ -82,7 +83,7 @@ class ImportExecutor extends Component { finalNoteCount: arg, isDone: true, }); - analytics.tracks.recordEvent('importer_import_completed', { + this.props.recordEvent('importer_import_completed', { source: sourceSlug, note_count: arg, }); @@ -180,6 +181,7 @@ class ImportExecutor extends Component { const mapDispatchToProps: S.MapDispatch = { importNote: actions.data.importNote, + recordEvent, }; export default connect(null, mapDispatchToProps)(ImportExecutor); diff --git a/lib/dialogs/share/index.tsx b/lib/dialogs/share/index.tsx index d9cdc5444..652da663c 100644 --- a/lib/dialogs/share/index.tsx +++ b/lib/dialogs/share/index.tsx @@ -3,7 +3,6 @@ import { connect } from 'react-redux'; import { includes, isEmpty } from 'lodash'; import MD5 from 'md5.js'; -import analytics from '../../analytics'; import ClipboardButton from '../../components/clipboard-button'; import isEmailTag from '../../utils/is-email-tag'; import Dialog from '../../dialog'; @@ -24,9 +23,11 @@ type StateProps = { }; type DispatchProps = { + addCollaborator: (noteId: T.EntityId, collaborator: T.TagName) => any; closeDialog: () => any; editNote: (noteId: T.EntityId, changes: Partial) => any; publishNote: (noteId: T.EntityId, shouldPublish: boolean) => any; + removeCollaborator: (noteId: T.EntityId, collaborator: T.TagName) => any; }; type Props = StateProps & DispatchProps; @@ -40,8 +41,7 @@ export class ShareDialog extends Component { isEmpty(url) ? undefined : `http://simp.ly/p/${url}`; onAddCollaborator = (event) => { - const { note, noteId } = this.props; - const tags = note?.tags || []; + const { noteId } = this.props; const collaborator = this.collaboratorElement.value.trim(); event.preventDefault(); @@ -49,20 +49,15 @@ export class ShareDialog extends Component { const isSelf = this.props.settings.accountName === collaborator; - if (collaborator !== '' && tags.indexOf(collaborator) === -1 && !isSelf) { - this.props.editNote(noteId, { tags: [...tags, collaborator] }); - analytics.tracks.recordEvent('editor_note_collaborator_added'); + if (collaborator !== '' && !isSelf) { + this.props.addCollaborator(noteId, collaborator); } }; onRemoveCollaborator = (collaborator) => { - const { note, noteId } = this.props; - - let tags = note?.tags || []; - tags = tags.filter((tag) => tag !== collaborator); + const { noteId } = this.props; - this.props.editNote(noteId, { tags }); - analytics.tracks.recordEvent('editor_note_collaborator_removed'); + this.props.removeCollaborator(noteId, collaborator); }; collaborators = () => { @@ -215,9 +210,11 @@ const mapStateToProps: S.MapState = ({ }); const mapDispatchToProps: S.MapDispatch = { + addCollaborator: actions.data.addCollaborator, closeDialog: actions.ui.closeDialog, editNote: actions.data.editNote, publishNote: actions.data.publishNote, + removeCollaborator: actions.data.removeCollaborator, }; export default connect(mapStateToProps, mapDispatchToProps)(ShareDialog); diff --git a/lib/navigation-bar/index.tsx b/lib/navigation-bar/index.tsx index 80e8d2655..df0fac0f4 100644 --- a/lib/navigation-bar/index.tsx +++ b/lib/navigation-bar/index.tsx @@ -2,7 +2,6 @@ import React, { Component, Fragment } from 'react'; import { connect } from 'react-redux'; import onClickOutside from 'react-onclickoutside'; -import analytics from '../analytics'; import { isElectron } from '../utils/platform'; import ConnectionStatus from '../connection-status'; import NavigationBarItem from './item'; @@ -55,7 +54,6 @@ export class NavigationBar extends Component { onSelectTrash = () => { this.props.selectTrash(); - analytics.tracks.recordEvent('list_trash_viewed'); }; // Determine if the selected class should be applied for the 'all notes' or 'trash' rows diff --git a/lib/search-bar/index.tsx b/lib/search-bar/index.tsx index 8984cbeab..979dd7a74 100644 --- a/lib/search-bar/index.tsx +++ b/lib/search-bar/index.tsx @@ -7,7 +7,6 @@ import { connect } from 'react-redux'; /** * Internal dependencies */ -import analytics from '../analytics'; import IconButton from '../icon-button'; import NewNoteIcon from '../icons/new-note'; import SearchField from '../search-field'; @@ -69,7 +68,6 @@ const mapDispatchToProps: S.MapDispatch = ( ) => ({ onNewNote: (content: string) => { dispatch(createNote(content)); - analytics.tracks.recordEvent('list_note_created'); }, toggleNavigation: () => { dispatch(toggleNavigation()); diff --git a/lib/search-field/index.tsx b/lib/search-field/index.tsx index 493b6c757..8cc06c9f2 100644 --- a/lib/search-field/index.tsx +++ b/lib/search-field/index.tsx @@ -1,7 +1,6 @@ import React, { Component, createRef, FormEvent, KeyboardEvent } from 'react'; import { connect } from 'react-redux'; import SmallCrossIcon from '../icons/cross-small'; -import analytics from '../analytics'; import { State } from '../state'; import { search } from '../state/ui/actions'; @@ -113,7 +112,6 @@ const mapStateToProps: S.MapState = ({ const mapDispatchToProps: S.MapDispatch = (dispatch) => ({ onSearch: (query: string) => { dispatch(search(query)); - analytics.tracks.recordEvent('list_notes_searched'); }, }); diff --git a/lib/state/action-types.ts b/lib/state/action-types.ts index c07dd7470..d4f1c4a5f 100644 --- a/lib/state/action-types.ts +++ b/lib/state/action-types.ts @@ -6,6 +6,7 @@ export type Action< Args extends { [extraProps: string]: unknown } = {} > = { type: T } & Args & { meta?: { + analytics?: [string, T.JSONSerializable | undefined][]; nextNoteToOpen?: T.EntityId; searchResults?: { noteIds: T.EntityId[]; @@ -77,6 +78,10 @@ export type OpenRevision = Action< >; export type OpenTag = Action<'OPEN_TAG', { tagName: T.TagName }>; export type ReallyLogout = Action<'REALLY_LOGOUT'>; +export type RecordEvent = Action< + 'RECORD_EVENT', + { eventName: string; eventProperties?: T.JSONSerializable } +>; export type RequestNotifications = Action< 'REQUEST_NOTIFICATIONS', { sendNotifications: boolean } @@ -128,6 +133,10 @@ export type WindowResize = Action<'WINDOW_RESIZE', { innerWidth: number }>; /* * Data operations */ +export type AddCollaborator = Action< + 'ADD_COLLABORATOR', + { noteId: T.EntityId; collaboratorAccount: T.TagName } +>; export type AddNoteTag = Action< 'ADD_NOTE_TAG', { noteId: T.EntityId; tagName: T.TagName } @@ -166,6 +175,10 @@ export type PublishNote = Action< 'PUBLISH_NOTE', { noteId: T.EntityId; shouldPublish: boolean } >; +export type RemoveCollaborator = Action< + 'REMOVE_COLLABORATOR', + { noteId: T.EntityId; collaboratorAccount: T.TagName } +>; export type RemoveNoteTag = Action< 'REMOVE_NOTE_TAG', { noteId: T.EntityId; tagName: T.TagName } @@ -279,6 +292,7 @@ export type TagRefresh = Action< export type ActionType = | AcknowledgePendingChange + | AddCollaborator | AddNoteTag | ChangeConnectionStatus | CloseNote @@ -313,10 +327,12 @@ export type ActionType = | PinNote | PublishNote | ReallyLogout + | RecordEvent | RemoteNoteUpdate | RemoteNoteDeleteForever | RemoteTagDelete | RemoteTagUpdate + | RemoveCollaborator | RemoveNoteTag | RenameTag | ReorderTag diff --git a/lib/state/analytics/actions.ts b/lib/state/analytics/actions.ts new file mode 100644 index 000000000..121392bdb --- /dev/null +++ b/lib/state/analytics/actions.ts @@ -0,0 +1,27 @@ +import * as A from '../action-types'; +import * as T from '../../types'; + +export const withEvent = ( + eventName: string, + eventProperties?: T.JSONSerializable +) => (action) => ({ + ...action, + meta: { + ...action.meta, + analytics: [ + ...(action.meta?.analytics ?? []), + [eventName, eventProperties], + ], + }, +}); + +export const recordEvent: A.ActionCreator = ( + eventName: string, + eventProperties?: T.JSONSerializable +) => + withEvent( + eventName, + eventProperties + )({ + type: 'RECORD_EVENT', + }); diff --git a/lib/state/analytics/middleware.ts b/lib/state/analytics/middleware.ts new file mode 100644 index 000000000..c2896207e --- /dev/null +++ b/lib/state/analytics/middleware.ts @@ -0,0 +1,123 @@ +import analytics from '../../analytics'; + +import type * as A from '../action-types'; +import type * as S from '../'; +import type * as T from '../../types'; + +let eventQueue: T.AnalyticsRecord[] = []; + +export const recordEvent = ( + name: string, + properties: T.JSONSerializable = {} +) => { + eventQueue.push([name, properties]); +}; + +export const middleware: S.Middleware = (store) => { + const record = (name: string, properties: T.JSONSerializable = {}) => { + switch (store.getState().data.analyticsAllowed) { + case true: + analytics.tracks.recordEvent(name, properties); + return; + + case false: + return; + + case null: + eventQueue.push([name, properties]); + return; + } + }; + + return (next) => (action: A.ActionType) => { + const result = next(action); + const nextState = store.getState(); + + /* catch-all meta used by redux components for these events: + - importer_import_completed + */ + if (action.meta?.analytics?.length) { + action.meta.analytics.forEach(([eventName, eventProperties]) => + record(eventName, eventProperties) + ); + } + + switch (action.type) { + case 'SET_ANALYTICS': + if (nextState.data.analyticsAllowed === true) { + // make sure that tracking starts after preferences are loaded + eventQueue.forEach(([name, properties]) => + analytics.tracks.recordEvent(name, properties) + ); + } + eventQueue = []; + + // Global to be checked in analytics.tracks.recordEvent() + window.analyticsEnabled = action.allowAnalytics; + break; + + /* events that map to an action directly */ + case 'ADD_COLLABORATOR': + record('editor_note_collaborator_added'); + break; + case 'ADD_NOTE_TAG': + record('editor_tag_added'); + break; + case 'CREATE_NOTE': + record('list_note_created'); + break; + case 'EDIT_NOTE': + record('editor_note_edited'); + break; + case 'INSERT_TASK': + record('editor_checklist_inserted'); + break; + case 'LOGOUT': + record('user_signed_out'); + break; + case 'OPEN_NOTE': + record('list_note_opened'); + break; + case 'OPEN_TAG': + record('list_tag_viewed'); + break; + case 'REMOVE_COLLABORATOR': + record('editor_note_collaborator_removed'); + break; + case 'REMOVE_NOTE_TAG': + record('editor_tag_removed'); + break; + case 'RESTORE_OPEN_NOTE': + record('editor_note_restored'); + break; + case 'SEARCH': + if (action.searchQuery) { + // @todo this gets called for each character typed + // in search field, possibly also happening in develop? + record('list_notes_searched'); + } + break; + case 'SELECT_TRASH': + record('list_trash_viewed'); + break; + case 'setAccountName': + analytics.initialize(action.accountName); + record('user_signed_in'); + break; + case 'SHOW_DIALOG': + if (action.dialog === 'SHARE') { + record('editor_share_dialog_viewed'); + } + break; + case 'TRASH_OPEN_NOTE': + record('editor_note_deleted'); + break; + case 'TRASH_TAG': + record('list_tag_deleted'); + break; + } + return result; + }; +}; + +export default middleware; diff --git a/lib/state/data/actions.ts b/lib/state/data/actions.ts index 7e30a1eca..c5fb50203 100644 --- a/lib/state/data/actions.ts +++ b/lib/state/data/actions.ts @@ -1,6 +1,15 @@ import * as A from '../action-types'; import * as T from '../../types'; +export const addCollaborator: A.ActionCreator = ( + noteId: T.EntityId, + collaboratorAccount: T.TagName +) => ({ + type: 'ADD_COLLABORATOR', + noteId, + collaboratorAccount, +}); + export const editNote: A.ActionCreator = ( noteId: T.EntityId, changes: Partial @@ -46,6 +55,15 @@ export const publishNote: A.ActionCreator = ( shouldPublish, }); +export const removeCollaborator: A.ActionCreator = ( + noteId: T.EntityId, + collaboratorAccount: T.TagName +) => ({ + type: 'REMOVE_COLLABORATOR', + noteId, + collaboratorAccount, +}); + export const toggleAnalytics: A.ActionCreator = () => ({ type: 'TOGGLE_ANALYTICS', }); diff --git a/lib/state/data/reducer.ts b/lib/state/data/reducer.ts index 6128b08b4..a92f86a17 100644 --- a/lib/state/data/reducer.ts +++ b/lib/state/data/reducer.ts @@ -28,13 +28,19 @@ export const notes: A.Reducer> = ( action ) => { switch (action.type) { + case 'ADD_COLLABORATOR': case 'ADD_NOTE_TAG': { const note = state.get(action.noteId); if (!note) { return state; } - const tags = withTag(note.tags, action.tagName); + const tagName = + action.type === 'ADD_COLLABORATOR' + ? action.collaboratorAccount + : action.tagName; + + const tags = withTag(note.tags, tagName); return tags !== note.tags ? new Map(state).set(action.noteId, { ...note, tags }) @@ -149,13 +155,19 @@ export const notes: A.Reducer> = ( return new Map(state).set(action.noteId, { ...note, systemTags }); } + case 'REMOVE_COLLABORATOR': case 'REMOVE_NOTE_TAG': { const note = state.get(action.noteId); if (!note) { return state; } - const tags = withoutTag(note.tags, action.tagName); + const tagName = + action.type === 'REMOVE_COLLABORATOR' + ? action.collaboratorAccount + : action.tagName; + + const tags = withoutTag(note.tags, tagName); return tags !== note.tags ? new Map(state).set(action.noteId, { ...note, tags }) @@ -271,10 +283,17 @@ export const tags: A.Reducer> = ( action ) => { switch (action.type) { - case 'ADD_NOTE_TAG': - return state.has(t(action.tagName)) + case 'ADD_COLLABORATOR': + case 'ADD_NOTE_TAG': { + const tagName = + action.type === 'ADD_COLLABORATOR' + ? action.collaboratorAccount + : action.tagName; + + return state.has(t(tagName)) ? state - : new Map(state).set(t(action.tagName), { name: action.tagName }); + : new Map(state).set(t(tagName), { name: tagName }); + } case 'EDIT_NOTE': case 'IMPORT_NOTE_WITH_ID': { @@ -378,8 +397,13 @@ export const noteTags: A.Reducer>> = ( action ) => { switch (action.type) { + case 'ADD_COLLABORATOR': case 'ADD_NOTE_TAG': { - const tagHash = t(action.tagName); + const tagHash = t( + action.type === 'ADD_COLLABORATOR' + ? action.collaboratorAccount + : action.tagName + ); return new Map(state).set( tagHash, (state.get(tagHash) ?? new Set()).add(action.noteId) @@ -425,8 +449,13 @@ export const noteTags: A.Reducer>> = ( return next.delete(action.tagHash) ? next : state; } + case 'REMOVE_COLLABORATOR': case 'REMOVE_NOTE_TAG': { - const tagHash = t(action.tagName); + const tagHash = t( + action.type === 'REMOVE_COLLABORATOR' + ? action.collaboratorAccount + : action.tagName + ); const tagNotes = state.get(tagHash); if (!tagNotes) { return state; diff --git a/lib/state/index.ts b/lib/state/index.ts index 83c47affe..4b29d492b 100644 --- a/lib/state/index.ts +++ b/lib/state/index.ts @@ -17,6 +17,7 @@ import { omit } from 'lodash'; import { isElectron } from '../utils/platform'; import * as persistence from './persistence'; +import { middleware as analyticsMiddleware } from './analytics/middleware'; import dataMiddleware from './data/middleware'; import electronMiddleware from './electron/middleware'; import { middleware as searchMiddleware } from '../search'; @@ -49,20 +50,28 @@ export type State = { const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; -export const makeStore = (accountName: string, ...middlewares: Middleware[]) => +export const makeStore = ( + accountName: string | null, + ...middlewares: Middleware[] +) => persistence .loadState(accountName) .then(([initialData, persistenceMiddleware]) => createStore( reducers, - initialData, + { + ...initialData, + settings: { + ...initialData.settings, + accountName: initialData.settings?.accountName ?? accountName, + }, + }, composeEnhancers( persistState('settings', { key: 'simpleNote', slicer: (path) => (state) => ({ // Omit property from persisting [path]: omit(state[path], [ - 'accountName', 'allowNotifications', 'focusModeEnabled', ]), @@ -70,6 +79,7 @@ export const makeStore = (accountName: string, ...middlewares: Middleware[]) => }), applyMiddleware( dataMiddleware, + analyticsMiddleware, browserMiddleware, searchMiddleware, searchFieldMiddleware, diff --git a/lib/state/persistence.ts b/lib/state/persistence.ts index 1a2e830db..4353482e3 100644 --- a/lib/state/persistence.ts +++ b/lib/state/persistence.ts @@ -59,7 +59,7 @@ export const loadState = ( } try { - if (state.accountName !== accountName) { + if (accountName !== null && state.accountName !== accountName) { resolve([{}, middleware]); return; } @@ -73,10 +73,14 @@ export const loadState = ( const data: T.RecursivePartial = { data: { + analyticsAllowed: state.allowAnalytics ?? null, notes: new Map(state.notes), noteTags, tags: new Map(state.tags), }, + settings: { + accountName: state.accountName, + }, simperium: { ghosts: [new Map(state.cvs), new Map(state.ghosts)], lastRemoteUpdate: new Map(state.lastRemoteUpdate), @@ -165,6 +169,7 @@ export const saveState = (state: S.State) => { const data = { accountName: state.settings.accountName, + allowAnalytics: state.data.analyticsAllowed, notes, noteTags, tags, diff --git a/lib/state/simperium/middleware.ts b/lib/state/simperium/middleware.ts index 05e37b6ec..3bcfce154 100644 --- a/lib/state/simperium/middleware.ts +++ b/lib/state/simperium/middleware.ts @@ -161,6 +161,10 @@ export const initSimperium = ( }); }); + preferencesBucket.channel.on('ready', () => + preferencesBucket.channel.sendIndexRequest() + ); + if (createWelcomeNote) { import( /* webpackChunkName: 'welcome-message' */ '../../welcome-message' @@ -188,6 +192,7 @@ export const initSimperium = ( tagQueue.add(tagHash, Date.now() + delay); if ('production' !== process.env.NODE_ENV) { + window.preferencesBucket = preferencesBucket; window.noteBucket = noteBucket; window.tagBucket = tagBucket; window.noteQueue = noteQueue; @@ -211,13 +216,23 @@ export const initSimperium = ( const nextState = store.getState(); switch (action.type) { - case 'ADD_NOTE_TAG': - if (!prevState.data.tags.has(t(action.tagName))) { - queueTagUpdate(t(action.tagName)); + case 'ADD_COLLABORATOR': + case 'ADD_NOTE_TAG': { + const tagHash = t( + action.type === 'ADD_COLLABORATOR' + ? action.collaboratorAccount + : action.tagName + ); + + if (!prevState.data.tags.has(tagHash)) { + queueTagUpdate(tagHash); } + queueNoteUpdate(action.noteId); return result; + } + case 'REMOVE_COLLABORATOR': case 'REMOVE_NOTE_TAG': queueNoteUpdate(action.noteId); return result; @@ -308,19 +323,6 @@ export const initSimperium = ( }); return result; - case 'TOGGLE_ANALYTICS': - preferencesBucket.get('preferences-key').then((preferences) => { - preferencesBucket.update( - 'preferences-key', - { - ...preferences.data, - analytics_enabled: !preferences.data.analytics_enabled, - }, - { sync: true } - ); - }); - return result; - case 'TRASH_TAG': { tagBucket.remove(t(action.tagName)); return result; diff --git a/lib/tag-field/index.tsx b/lib/tag-field/index.tsx index fba674bf5..f12146b2a 100644 --- a/lib/tag-field/index.tsx +++ b/lib/tag-field/index.tsx @@ -8,7 +8,6 @@ import EmailToolTip from '../tag-email-tooltip'; import TagChip from '../components/tag-chip'; import TagInput from '../tag-input'; import classNames from 'classnames'; -import analytics from '../analytics'; import { tagHashOf } from '../utils/tag-hash'; import type * as S from '../state'; @@ -98,7 +97,6 @@ export class TagField extends Component { this.storeTagInput(''); invoke(this, 'tagInput.focus'); - analytics.tracks.recordEvent('editor_tag_added'); }; hasSelection = () => @@ -115,8 +113,6 @@ export class TagField extends Component { this.tagInput?.current?.focus() ); } - - analytics.tracks.recordEvent('editor_tag_removed'); }; deleteSelection = () => { diff --git a/lib/tag-suggestions/index.tsx b/lib/tag-suggestions/index.tsx index b16004851..470e0b868 100644 --- a/lib/tag-suggestions/index.tsx +++ b/lib/tag-suggestions/index.tsx @@ -1,6 +1,5 @@ import React, { Component, Fragment } from 'react'; import { connect } from 'react-redux'; -import analytics from '../analytics'; import { search } from '../state/ui/actions'; import { tagHashOf } from '../utils/tag-hash'; @@ -141,7 +140,6 @@ const mapStateToProps: S.MapState = ({ const mapDispatchToProps: S.MapDispatch = (dispatch) => ({ onSearch: (query) => { dispatch(search(query)); - analytics.tracks.recordEvent('list_notes_searched'); }, }); diff --git a/lib/types.ts b/lib/types.ts index 4f379d7d6..2cf3b16fd 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -39,6 +39,8 @@ export type Preferences = { analytics_enabled: boolean | null; }; +export type AnalyticsRecord = [string, JSONSerializable | undefined]; + export type PreferencesEntity = Entity; ///////////////////////////////////////