Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[rewrite branch] Add analytics as middleware #2223

Merged
merged 5 commits into from
Aug 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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