Skip to content

Commit

Permalink
[rewrite branch] Add analytics as middleware (#2223)
Browse files Browse the repository at this point in the history
* centralize analytics again
 - not working attempt to convert to meta key
 - fixed addCollaborator args
 - add removeCollaborator reducer with withEvent
 - Add COLLABORATOR actions
 - remove invalid comments
 - remove unused setAccountName and some more comments
 - trying event queue to add application_opened before preferences are loaded
 - documented bugs :[

* Update analytics setting when bucket connects

* Set initial accountName in Redux init vs. dispatched action

* Send index request for preferences

* Persist account name properly

Co-authored-by: Dennis Snell <dennis.snell@automattic.com>
  • Loading branch information
codebykat and dmsnell authored Aug 3, 2020
1 parent 023ae11 commit a43566c
Show file tree
Hide file tree
Showing 20 changed files with 282 additions and 61 deletions.
4 changes: 2 additions & 2 deletions lib/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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;
Expand All @@ -57,6 +57,7 @@ class AppComponent extends Component<Props> {

this.toggleShortcuts(true);

recordEvent('application_opened');
__TEST__ && window.testEvents.push('booted');
}

Expand Down Expand Up @@ -186,7 +187,6 @@ const mapDispatchToProps: S.MapDispatch<DispatchProps> = (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)),
Expand Down
2 changes: 0 additions & 2 deletions lib/boot-with-auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
4 changes: 2 additions & 2 deletions lib/boot-without-auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -79,7 +79,7 @@ class AppWithoutAuth extends Component<Props, State> {
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(() => {
Expand Down
8 changes: 6 additions & 2 deletions lib/boot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ const clearStorage = (): Promise<void> =>
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');
Expand Down Expand Up @@ -173,7 +179,6 @@ const run = (
bootWithToken(
() => {
bootLoggingOut();
analytics.tracks.recordEvent('user_signed_out');
clearStorage().then(() => {
if (window.webConfig?.signout) {
window.webConfig.signout(forceReload);
Expand All @@ -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);
}
);
Expand Down
6 changes: 4 additions & 2 deletions lib/dialogs/import/source-importer/executor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -47,6 +47,7 @@ type OwnProps = {

type DispatchProps = {
importNote: (note: T.Note) => any;
recordEvent: (eventName: string, eventProperties: T.JSONSerializable) => any;
};

type Props = OwnProps & DispatchProps;
Expand Down Expand Up @@ -82,7 +83,7 @@ class ImportExecutor extends Component<Props> {
finalNoteCount: arg,
isDone: true,
});
analytics.tracks.recordEvent('importer_import_completed', {
this.props.recordEvent('importer_import_completed', {
source: sourceSlug,
note_count: arg,
});
Expand Down Expand Up @@ -180,6 +181,7 @@ class ImportExecutor extends Component<Props> {

const mapDispatchToProps: S.MapDispatch<DispatchProps> = {
importNote: actions.data.importNote,
recordEvent,
};

export default connect(null, mapDispatchToProps)(ImportExecutor);
21 changes: 9 additions & 12 deletions lib/dialogs/share/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -24,9 +23,11 @@ type StateProps = {
};

type DispatchProps = {
addCollaborator: (noteId: T.EntityId, collaborator: T.TagName) => any;
closeDialog: () => any;
editNote: (noteId: T.EntityId, changes: Partial<T.Note>) => any;
publishNote: (noteId: T.EntityId, shouldPublish: boolean) => any;
removeCollaborator: (noteId: T.EntityId, collaborator: T.TagName) => any;
};

type Props = StateProps & DispatchProps;
Expand All @@ -40,29 +41,23 @@ export class ShareDialog extends Component<Props> {
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();
this.collaboratorElement.value = '';

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 = () => {
Expand Down Expand Up @@ -215,9 +210,11 @@ const mapStateToProps: S.MapState<StateProps> = ({
});

const mapDispatchToProps: S.MapDispatch<DispatchProps> = {
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);
2 changes: 0 additions & 2 deletions lib/navigation-bar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -55,7 +54,6 @@ export class NavigationBar extends Component<Props> {

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
Expand Down
2 changes: 0 additions & 2 deletions lib/search-bar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -69,7 +68,6 @@ const mapDispatchToProps: S.MapDispatch<DispatchProps, OwnProps> = (
) => ({
onNewNote: (content: string) => {
dispatch(createNote(content));
analytics.tracks.recordEvent('list_note_created');
},
toggleNavigation: () => {
dispatch(toggleNavigation());
Expand Down
2 changes: 0 additions & 2 deletions lib/search-field/index.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -113,7 +112,6 @@ const mapStateToProps: S.MapState<StateProps> = ({
const mapDispatchToProps: S.MapDispatch<DispatchProps> = (dispatch) => ({
onSearch: (query: string) => {
dispatch(search(query));
analytics.tracks.recordEvent('list_notes_searched');
},
});

Expand Down
16 changes: 16 additions & 0 deletions lib/state/action-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -279,6 +292,7 @@ export type TagRefresh = Action<

export type ActionType =
| AcknowledgePendingChange
| AddCollaborator
| AddNoteTag
| ChangeConnectionStatus
| CloseNote
Expand Down Expand Up @@ -313,10 +327,12 @@ export type ActionType =
| PinNote
| PublishNote
| ReallyLogout
| RecordEvent
| RemoteNoteUpdate
| RemoteNoteDeleteForever
| RemoteTagDelete
| RemoteTagUpdate
| RemoveCollaborator
| RemoveNoteTag
| RenameTag
| ReorderTag
Expand Down
27 changes: 27 additions & 0 deletions lib/state/analytics/actions.ts
Original file line number Diff line number Diff line change
@@ -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<A.RecordEvent> = (
eventName: string,
eventProperties?: T.JSONSerializable
) =>
withEvent(
eventName,
eventProperties
)({
type: 'RECORD_EVENT',
});
Loading

0 comments on commit a43566c

Please sign in to comment.