Reworked analytics

This commit is contained in:
Gregory Schier 2016-10-26 20:41:30 -07:00
parent b76f748e73
commit 889c9dbbd4
20 changed files with 123 additions and 66 deletions

View File

@ -6,7 +6,7 @@ import {SEGMENT_WRITE_KEY} from './constants';
let analytics = null;
let userId = null;
export async function initAnalytics () {
export async function initLegacyAnalytics () {
analytics = new Analytics(SEGMENT_WRITE_KEY);
if (!userId) {
@ -14,7 +14,7 @@ export async function initAnalytics () {
userId = stats._id;
// Recurse now that we have a userId
return await initAnalytics();
return await initLegacyAnalytics();
}
analytics.identify({
@ -31,7 +31,7 @@ export async function initAnalytics () {
console.log(`-- Analytics Initialized for ${userId} --`);
}
export function trackEvent (event, properties = {}) {
export function trackLegacyEvent (event, properties = {}) {
// Don't track events if we haven't set them up yet
if (analytics) {
// Add base properties

View File

@ -6,6 +6,8 @@ export const LOCALSTORAGE_KEY = 'insomnia.state';
export const DB_PERSIST_INTERVAL = 1000 * 60 * 10;
export const DEBOUNCE_MILLIS = 100;
export const REQUEST_TIME_TO_SHOW_COUNTER = 1; // Seconds
export const GA_ID = 'UA-9837747-12';
export const GA_HOST = 'desktop.insomnia.rest';
export const CHANGELOG_URL = 'https://changelog.insomnia.rest/changelog.json';
export const STATUS_CODE_PEBKAC = -333;
export const LARGE_RESPONSE_MB = 10;

View File

@ -28,12 +28,11 @@ function lookup (url, forceIPv4) {
}
const v6 = results.find(r => r.family === 6);
const v4 = results.find(r => r.family === 4);
if (v6) {
resolve(v6.address);
} else {
resolve(v4.address);
// If no v6, return the first result
resolve(results[0].address);
}
});
})

55
app/backend/ganalytics.js Normal file
View File

@ -0,0 +1,55 @@
import * as constants from './constants';
import {isDevelopment} from './appInfo';
let _ga = null;
let _sessionId = null;
export function initAnalytics () {
if (isDevelopment()) {
console.log('-- Not initializing analytics for dev --');
return;
}
if (!_sessionId) {
_ga = _initGA();
}
if (!localStorage['gaClientId']) {
localStorage.setItem('gaClientId', require('node-uuid').v4());
}
const _sessionId = window.localStorage['gaClientId'];
_ga('create', constants.GA_ID, {'storage': 'none', 'clientId': _sessionId});
// Disable URL protocol check
_ga('set', 'checkProtocolTask', () => null);
// Set a fake location
_ga('set', 'location', `https://${constants.GA_HOST}/`);
// Track the initial page view
_ga('send', 'pageview');
console.log(`-- Analytics Initialized for ${_sessionId} --`);
}
export function trackEvent (category, action, label) {
_ga && _ga('send', 'event', category, action, label);
}
function _initGA () {
(function (i, s, o, g, r, a, m) {
i['GoogleAnalyticsObject'] = r;
i[r] = i[r] || function () {
(i[r].q = i[r].q || []).push(arguments)
}, i[r].l = 1 * new Date();
a = s.createElement(o),
m = s.getElementsByTagName(o)[0];
a.async = 1;
a.src = g;
m.parentNode.insertBefore(a, m)
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
return window.ga;
}

View File

@ -1,10 +1,20 @@
const fs = require('fs');
const path = require('path');
const mkdirp = require('mkdirp');
class LocalStorage {
constructor (path) {
this._path = path;
this._timeouts = {};
// No need to wait for this.
mkdirp(path, err => {
if (err) {
console.warn('[localStorage] Failed to create directory', path, err);
} else {
console.log('[localStorage] initialized');
}
});
}
/**

View File

@ -3,6 +3,8 @@
<head>
<meta charset="utf-8">
<title>Insomnia</title>
<script>
</script>
</head>
<body>
<div id="root"></div>
@ -37,9 +39,8 @@
// Sentry
if (process.env.INSOMNIA_ENV !== 'development') {
Raven.config('https://fb3242f902b54cdd934b8ffa204426c0:23430fbe203a4189a68efb63c38fc50b@app.getsentry.com/88289', {
allowSecretKey: true,
logger: 'renderer',
Raven.config('https://786e43ae199c4757a9ea4a48a9abd17d@sentry.io/109702', {
logger: 'electron.renderer',
environment: process.env.INSOMNIA_ENV || 'production',
level: 'warning',
release: require('./package.json').version,

View File

@ -12,11 +12,10 @@ const IS_LINUX = !IS_MAC && !IS_WINDOWS;
var raven = require('raven');
var ravenClient = new raven.Client(
'https://fb3242f902b54cdd934b8ffa204426c0:23430fbe203a' +
'4189a68efb63c38fc50b@app.getsentry.com/88289', {
'https://786e43ae199c4757a9ea4a48a9abd17d@sentry.io/109702', {
environment: process.env.INSOMNIA_ENV || 'production',
release: require('./package.json').version,
logger: 'main'
logger: 'electron.main'
});
if (!IS_DEV) {
@ -66,13 +65,10 @@ process.on('uncaughtException', e => {
});
autoUpdater.on('error', e => {
// Failed to launch auto updater
if (IS_DEV) {
console.error(e);
} else if (e.toString().indexOf('')) {
ravenClient.captureError(e, {
level: 'warning'
});
} else {
// Don't report autoUpdater error. They are way too noisy
}
});

View File

@ -11,11 +11,13 @@
"dependencies": {
"analytics-node": "^2.1.0",
"electron-context-menu": "^0.4.0",
"electron-cookies": "^1.1.0",
"electron-squirrel-startup": "^1.0.0",
"hkdf": "0.0.2",
"httpsnippet": "git@github.com:getinsomnia/httpsnippet.git#a3a2c0a0167fa844bf92df52a1442fa1d68a9053",
"json-lint": "^0.1.0",
"jsonpath-plus": "^0.15.0",
"mkdirp": "^0.5.1",
"nedb": "^1.8.0",
"node-forge": "^0.6.43",
"node-uuid": "^1.4.7",

View File

@ -1,6 +1,5 @@
import React, {Component, PropTypes} from 'react';
import {Tab, Tabs, TabList, TabPanel} from 'react-tabs';
import KeyValueEditor from './base/KeyValueEditor';
import RequestHeadersEditor from './editors/RequestHeadersEditor';
import ContentTypeDropdown from './dropdowns/ContentTypeDropdown';
@ -9,11 +8,11 @@ import BodyEditor from './editors/BodyEditor';
import AuthEditor from './editors/AuthEditor';
import {UrlBar} from './UrlBar.elm';
import ElmComponent from './ElmComponent';
import {getContentTypeName} from '../../backend/contentTypes';
import {getContentTypeFromHeaders} from '../../backend/contentTypes';
import {
getContentTypeName,
getContentTypeFromHeaders
} from '../../backend/contentTypes';
import {MOD_SYM} from '../../backend/constants';
import {trackEvent} from '../../backend/analytics';
import {debounce} from '../lib/debounce';
class RequestPane extends Component {
@ -104,31 +103,27 @@ class RequestPane extends Component {
<Tabs className="pane__body">
<TabList>
<Tab>
<button
onClick={e => trackEvent('Request Tab Clicked', {name: 'Body'})}>
<button>
{getContentTypeName(getContentTypeFromHeaders(request.headers))}
</button>
<ContentTypeDropdown
updateRequestContentType={updateRequestContentType}/>
</Tab>
<Tab>
<button
onClick={e => trackEvent('Request Tab Clicked', {name: 'Auth'})}>
<button>
Auth {request.authentication.username ?
<i className="fa fa-lock txt-sm"></i> : ''}
</button>
</Tab>
<Tab>
<button
onClick={e => trackEvent('Request Tab Clicked', {name: 'Params'})}>
<button>
Query {request.parameters.length ?
<span
className="txt-sm">({request.parameters.length})</span> : null}
</button>
</Tab>
<Tab>
<button
onClick={e => trackEvent('Request Tab Clicked', {name: 'Headers'})}>
<button>
Headers {request.headers.length ? (
<span
className="txt-sm">({request.headers.length})</span> ) : null}

View File

@ -11,11 +11,13 @@ import {
getPreviewModeName,
PREVIEW_MODE_SOURCE
} from '../../backend/previewModes';
import {REQUEST_TIME_TO_SHOW_COUNTER, MOD_SYM} from '../../backend/constants';
import {trackEvent} from '../../backend/analytics';
import {
REQUEST_TIME_TO_SHOW_COUNTER,
MOD_SYM,
RESPONSE_CODE_DESCRIPTIONS
} from '../../backend/constants';
import {getSetCookieHeaders} from '../../backend/util';
import {cancelCurrentRequest} from '../../backend/network';
import {RESPONSE_CODE_DESCRIPTIONS} from '../../backend/constants';
class ResponsePane extends Component {
constructor (props) {
@ -165,8 +167,7 @@ class ResponsePane extends Component {
<Tabs className="pane__body">
<TabList>
<Tab>
<button
onClick={e => trackEvent('Response Tab Clicked', {name: 'Body'})}>
<button>
{getPreviewModeName(previewMode)}
</button>
<PreviewModeDropdown
@ -175,8 +176,7 @@ class ResponsePane extends Component {
/>
</Tab>
<Tab>
<button
onClick={e => trackEvent('Cookies Tab Clicked', {name: 'Cookies'})}>
<button>
Cookies {cookieHeaders.length ? (
<span className="txt-sm">
({cookieHeaders.length})
@ -185,8 +185,7 @@ class ResponsePane extends Component {
</button>
</Tab>
<Tab>
<button
onClick={e => trackEvent('Response Tab Clicked', {name: 'Headers'})}>
<button>
Headers {response.headers.length ? (
<span className="txt-sm">
({response.headers.length})

View File

@ -42,7 +42,6 @@ import {getModal} from '../modals/index';
import AlertModal from '../modals/AlertModal';
import Link from '../base/Link';
import {DEBOUNCE_MILLIS} from '../../../backend/constants';
import {trackEvent} from '../../../backend/analytics';
const BASE_CODEMIRROR_OPTIONS = {
@ -159,7 +158,6 @@ class Editor extends Component {
_handleBeautify () {
this._prettify(this.codeMirror.getValue());
trackEvent('Beautify', {mode: this.props.mode});
}
async _prettify (code) {

View File

@ -1,8 +1,6 @@
import React, {PropTypes} from 'react';
import Dropdown from '../base/Dropdown';
import {CONTENT_TYPES, getContentTypeName} from '../../../backend/contentTypes';
import {trackEvent} from '../../../backend/analytics';
const ContentTypeDropdown = ({updateRequestContentType}) => {
return (
@ -14,7 +12,6 @@ const ContentTypeDropdown = ({updateRequestContentType}) => {
{CONTENT_TYPES.map(contentType => (
<li key={contentType}>
<button onClick={e => {
trackEvent('Changed Content-Type', {contentType});
updateRequestContentType(contentType)
}}>{getContentTypeName(contentType)}</button>
</li>

View File

@ -1,8 +1,6 @@
import React, {PropTypes} from 'react';
import Dropdown from '../base/Dropdown';
import {PREVIEW_MODES, getPreviewModeName} from '../../../backend/previewModes';
import {trackEvent} from '../../../backend/analytics';
const PreviewModeDropdown = ({updatePreviewMode}) => (
<Dropdown>
@ -13,7 +11,6 @@ const PreviewModeDropdown = ({updatePreviewMode}) => (
{PREVIEW_MODES.map(previewMode => (
<li key={previewMode}>
<button onClick={() => {
trackEvent('Changed Preview Mode', {previewMode});
updatePreviewMode(previewMode);
}}>{getPreviewModeName(previewMode)}</button>
</li>

View File

@ -6,7 +6,7 @@ import ModalHeader from '../base/ModalHeader';
import ModalBody from '../base/ModalBody';
import MethodTag from '../tags/MethodTag';
import * as db from '../../../backend/database';
import {trackEvent} from '../../../backend/analytics';
import {trackLegacyEvent} from '../../../backend/analytics';
class RequestSwitcherModal extends Component {
@ -165,7 +165,7 @@ class RequestSwitcherModal extends Component {
}
show () {
trackEvent('Show Quick Switcher');
trackEvent('Modal', 'Show', 'Quick Switcher');
this.modal.show();
this._handleChange('');
}

View File

@ -36,7 +36,7 @@ import * as GlobalActions from '../redux/modules/global';
import * as RequestActions from '../redux/modules/requests';
import * as db from '../../backend/database';
import {importCurl} from '../../backend/export/curl';
import {trackEvent} from '../../backend/analytics';
import {trackLegacyEvent} from '../../backend/analytics';
import {getAppVersion} from '../../backend/appInfo';
import {getModal} from '../components/modals/index';
@ -420,14 +420,14 @@ class App extends Component {
});
// Do The Analytics
trackEvent('App Launched');
trackLegacyEvent('App Launched');
// Update Stats Object
const {lastVersion, launches} = await db.stats.get();
const firstLaunch = !lastVersion;
if (firstLaunch) {
// TODO: Show a welcome message
trackEvent('First Launch');
trackLegacyEvent('First Launch');
} else if (lastVersion !== getAppVersion()) {
getModal(ChangelogModal).show();
}

View File

@ -15,7 +15,8 @@ import {initStore} from './redux/initstore';
import {initDB} from '../backend/database';
import {initSync} from '../backend/sync';
import {getAppVersion} from '../backend/appInfo';
import {initAnalytics} from '../backend/analytics';
import {initLegacyAnalytics} from '../backend/analytics';
import {initAnalytics} from '../backend/ganalytics';
// Don't inject component styles (use our own)
Tabs.setUseDefaultStyles(false);
@ -29,6 +30,7 @@ console.log(`-- Loading App v${getAppVersion()} --`);
await initSync();
await initStore(store.dispatch);
await initAnalytics();
await initLegacyAnalytics();
console.log('-- Rendering App --');
render(
<Provider store={store}><App /></Provider>,

View File

@ -3,7 +3,7 @@ import {combineReducers} from 'redux';
import fs from 'fs';
import {importJSON, exportJSON} from '../../../backend/export/database';
import {trackEvent} from '../../../backend/analytics';
import {trackEvent} from '../../../backend/ganalytics';
const LOAD_START = 'global/load-start';
const LOAD_STOP = 'global/load-stop';
@ -123,7 +123,7 @@ export function importFile (workspace) {
if (!paths) {
// It was cancelled, so let's bail out
dispatch(loadStop());
trackEvent('Import Cancel');
trackEvent('import', 'Cancel');
return;
}
@ -133,13 +133,13 @@ export function importFile (workspace) {
dispatch(loadStop());
if (err) {
trackEvent('Import Fail');
trackEvent('Import', 'Failure');
console.warn('Import Failed', err);
return;
}
importJSON(workspace, data);
trackEvent('Import');
trackEvent('Import', 'Success');
});
})
});
@ -200,7 +200,7 @@ export function exportFile (parentDoc = null) {
electron.remote.dialog.showSaveDialog(options, filename => {
if (!filename) {
trackEvent('Export Cancel');
trackEvent('Export', 'Cancel');
// It was cancelled, so let's bail out
dispatch(loadStop());
return;
@ -209,10 +209,10 @@ export function exportFile (parentDoc = null) {
fs.writeFile(filename, json, {}, err => {
if (err) {
console.warn('Export failed', err);
trackEvent('Export Fail');
trackEvent('Export', 'Failure');
return;
}
trackEvent('Export');
trackEvent('Export', 'Success');
dispatch(loadStop());
});
});

View File

@ -1,7 +1,8 @@
import {combineReducers} from 'redux';
import {trackEvent} from '../../../backend/analytics';
import {trackLegacyEvent} from '../../../backend/analytics';
import * as network from '../../../backend/network';
import {trackEvent} from '../../../backend/ganalytics';
export const REQUEST_SEND_START = 'requests/start';
export const REQUEST_SEND_STOP = 'requests/stop';
@ -44,7 +45,8 @@ export function send(request) {
return async function (dispatch) {
dispatch({type: REQUEST_SEND_START, requestId: request._id});
trackEvent('Request Send');
trackEvent('Request', 'Send');
trackLegacyEvent('Request Send');
try {
await network.send(request._id);

View File

@ -61,11 +61,13 @@
"classnames": "^2.2.3",
"codemirror": "^5.18.2",
"electron-context-menu": "^0.4.0",
"electron-cookies": "^1.1.0",
"electron-squirrel-startup": "^1.0.0",
"hkdf": "0.0.2",
"httpsnippet": "git@github.com:getinsomnia/httpsnippet.git#a3a2c0a0167fa844bf92df52a1442fa1d68a9053",
"json-lint": "^0.1.0",
"jsonpath-plus": "^0.15.0",
"mkdirp": "^0.5.1",
"mousetrap": "^1.6.0",
"nedb": "^1.8.0",
"node-forge": "^0.6.43",

View File

@ -10,7 +10,7 @@ echo "-- Creating Release $APP_VERSION --"
# Create a new release #
# ~~~~~~~~~~~~~~~~~~~~ #
curl https://app.getsentry.com/api/0/projects/schierco/insomnia-electron/releases/ \
curl https://app.getsentry.com/api/0/projects/schierco/insomnia-app/releases/ \
-X POST \
-u "$SENTRY_TOKEN:" \
-H 'Content-Type: application/json' \
@ -23,20 +23,20 @@ echo "-- Uploading Source Maps for $APP_VERSION --"
# Upload files for the given release #
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
curl https://app.getsentry.com/api/0/projects/schierco/insomnia-electron/releases/${APP_VERSION}/files/ \
curl https://app.getsentry.com/api/0/projects/schierco/insomnia-app/releases/${APP_VERSION}/files/ \
-X POST \
-u "$SENTRY_TOKEN:" \
-F file=@./build/bundle.js \
-F name="bundle.js"
curl https://app.getsentry.com/api/0/projects/schierco/insomnia-electron/releases/${APP_VERSION}/files/ \
curl https://app.getsentry.com/api/0/projects/schierco/insomnia-app/releases/${APP_VERSION}/files/ \
-X POST \
-u "$SENTRY_TOKEN:" \
-F file=@./build/bundle.js \
-F name="bundle.js"
# Upload a file for the given release
curl https://app.getsentry.com/api/0/projects/schierco/insomnia-electron/releases/${APP_VERSION}/files/ \
curl https://app.getsentry.com/api/0/projects/schierco/insomnia-app/releases/${APP_VERSION}/files/ \
-X POST \
-u "$SENTRY_TOKEN:" \
-F file=@./build/bundle.js.map \