diff --git a/packages/insomnia-app/app/common/analytics.js b/packages/insomnia-app/app/common/analytics.js index 63746bfe5e..d39bb1aa7b 100644 --- a/packages/insomnia-app/app/common/analytics.js +++ b/packages/insomnia-app/app/common/analytics.js @@ -90,6 +90,21 @@ export function trackPageView(path: string) { }); } +export async function getDeviceId(): Promise { + const settings = await models.settings.getOrCreate(); + + let { deviceId } = settings; + if (!deviceId) { + // Migrate old GA ID into settings model if needed + const oldId = (window && window.localStorage.getItem('gaClientId')) || null; + deviceId = oldId || uuid.v4(); + + await models.settings.update(settings, { deviceId }); + } + + return deviceId; +} + // ~~~~~~~~~~~~~~~~~ // // Private Functions // // ~~~~~~~~~~~~~~~~~ // @@ -129,15 +144,7 @@ export async function _trackPageView(location: string) { } async function _getDefaultParams(): Promise> { - const settings = await models.settings.getOrCreate(); - - // Migrate old GA ID into settings model - let { deviceId } = settings; - if (!deviceId) { - const oldId = (window && window.localStorage.gaClientId) || null; - deviceId = oldId || uuid.v4(); - await models.settings.update(settings, { deviceId }); - } + const deviceId = await getDeviceId(); // Prepping user agent string prior to sending to GA due to Electron base UA not being GA friendly. const ua = String(window?.navigator?.userAgent) diff --git a/packages/insomnia-app/app/common/send-request.js b/packages/insomnia-app/app/common/send-request.js index 0a2d5d5ded..bb02fb9137 100644 --- a/packages/insomnia-app/app/common/send-request.js +++ b/packages/insomnia-app/app/common/send-request.js @@ -1,5 +1,5 @@ import * as db from './database'; -import { types as modelTypes } from '../models'; +import { types as modelTypes, stats } from '../models'; import { send } from '../network/network'; import { getBodyBuffer } from '../models/response'; @@ -24,6 +24,7 @@ export async function getSendRequestCallbackMemDb(environmentId, memDB) { export function getSendRequestCallback(environmentId) { return async function sendRequest(requestId) { + stats.incrementExecutedRequests(); return sendAndTransform(requestId, environmentId); }; } diff --git a/packages/insomnia-app/app/models/stats.js b/packages/insomnia-app/app/models/stats.js index f4bb07dfa9..f54f699c27 100644 --- a/packages/insomnia-app/app/models/stats.js +++ b/packages/insomnia-app/app/models/stats.js @@ -1,6 +1,9 @@ // @flow import * as db from '../common/database'; import type { BaseModel } from './index'; +import type { Workspace } from './workspace'; +import type { RequestGroup } from './request-group'; +import { isRequest, isGrpcRequest } from './helpers/is-model'; export const name = 'Stats'; export const type = 'Stats'; @@ -14,6 +17,9 @@ type BaseStats = { currentVersion: string | null, lastVersion: string | null, launches: number, + createdRequests: number, + deletedRequests: number, + executedRequests: number, }; export type Stats = BaseModel & BaseStats; @@ -25,6 +31,9 @@ export function init(): BaseStats { currentVersion: null, lastVersion: null, launches: 0, + createdRequests: 0, + deletedRequests: 0, + executedRequests: 0, }; } @@ -49,3 +58,40 @@ export async function get(): Promise { return results[0]; } } + +export async function incrementRequestStats({ + createdRequests, + deletedRequests, + executedRequests, +}: $Shape) { + const stats = await get(); + await update({ + ...(createdRequests && { createdRequests: stats.createdRequests + createdRequests }), + ...(deletedRequests && { deletedRequests: stats.deletedRequests + deletedRequests }), + ...(executedRequests && { executedRequests: stats.executedRequests + executedRequests }), + }); +} + +export async function incrementCreatedRequests() { + await incrementRequestStats({ createdRequests: 1 }); +} + +export async function incrementDeletedRequests() { + await incrementRequestStats({ deletedRequests: 1 }); +} + +export async function incrementExecutedRequests() { + await incrementRequestStats({ executedRequests: 1 }); +} + +export async function incrementCreatedRequestsForDescendents(doc: Workspace | RequestGroup) { + const docs = await db.withDescendants(doc); + const requests = docs.filter(doc => isRequest(doc) || isGrpcRequest(doc)); + await incrementRequestStats({ createdRequests: requests.length }); +} + +export async function incrementDeletedRequestsForDescendents(doc: Workspace | RequestGroup) { + const docs = await db.withDescendants(doc); + const requests = docs.filter(doc => isRequest(doc) || isGrpcRequest(doc)); + await incrementRequestStats({ deletedRequests: requests.length }); +} diff --git a/packages/insomnia-app/app/network/grpc/index.js b/packages/insomnia-app/app/network/grpc/index.js index d3910a6e43..ebea8142a9 100644 --- a/packages/insomnia-app/app/network/grpc/index.js +++ b/packages/insomnia-app/app/network/grpc/index.js @@ -50,6 +50,9 @@ export const sendUnary = async (requestId: string, respond: ResponseCallbacks): // Create callback const callback = _createUnaryCallback(requestId, respond); + // Update request stats + models.stats.incrementExecutedRequests(); + // Make call const call = client.makeUnaryRequest( selectedMethod.path, @@ -89,6 +92,9 @@ export const startClientStreaming = async ( // Create callback const callback = _createUnaryCallback(requestId, respond); + // Update request stats + models.stats.incrementExecutedRequests(); + // Make call const call = client.makeClientStreamRequest( selectedMethod.path, @@ -131,6 +137,9 @@ export const startServerStreaming = async ( return; } + // Update request stats + models.stats.incrementExecutedRequests(); + // Make call const call = client.makeServerStreamRequest( selectedMethod.path, @@ -170,6 +179,9 @@ export const startBidiStreaming = async ( return; } + // Update request stats + models.stats.incrementExecutedRequests(); + // Make call const call = client.makeBidiStreamRequest( selectedMethod.path, diff --git a/packages/insomnia-app/app/ui/components/dropdowns/document-card-dropdown.js b/packages/insomnia-app/app/ui/components/dropdowns/document-card-dropdown.js index f24305df8c..a42efbc608 100644 --- a/packages/insomnia-app/app/ui/components/dropdowns/document-card-dropdown.js +++ b/packages/insomnia-app/app/ui/components/dropdowns/document-card-dropdown.js @@ -51,6 +51,8 @@ class DocumentCardDropdown extends React.PureComponent { const newWorkspace = await db.duplicate(workspace, { name: newName }); await models.apiSpec.updateOrCreateForParentId(newWorkspace._id, { fileName: newName }); + models.stats.incrementCreatedRequestsForDescendents(newWorkspace); + handleSetActiveWorkspace(newWorkspace._id); }, }); @@ -94,6 +96,9 @@ class DocumentCardDropdown extends React.PureComponent { if (isLastWorkspace) { await models.workspace.create({ name: getAppName(), scope: 'spec' }); } + + await models.stats.incrementDeletedRequestsForDescendents(workspace); + await models.workspace.remove(workspace); }, }); diff --git a/packages/insomnia-app/app/ui/components/dropdowns/request-actions-dropdown.js b/packages/insomnia-app/app/ui/components/dropdowns/request-actions-dropdown.js index dce324d65e..89eea4da04 100644 --- a/packages/insomnia-app/app/ui/components/dropdowns/request-actions-dropdown.js +++ b/packages/insomnia-app/app/ui/components/dropdowns/request-actions-dropdown.js @@ -13,6 +13,7 @@ import { hotKeyRefs } from '../../../common/hotkeys'; import * as misc from '../../../common/misc'; import { isRequest } from '../../../models/helpers/is-model'; import * as requestOperations from '../../../models/helpers/request-operations'; +import { incrementDeletedRequests } from '../../../models/stats'; @autobind class RequestActionsDropdown extends PureComponent { @@ -44,6 +45,7 @@ class RequestActionsDropdown extends PureComponent { _handleRemove() { const { request } = this.props; + incrementDeletedRequests(); return requestOperations.remove(request); } diff --git a/packages/insomnia-app/app/ui/components/dropdowns/request-group-actions-dropdown.js b/packages/insomnia-app/app/ui/components/dropdowns/request-group-actions-dropdown.js index 5a2b5b0c01..4ae0ef947b 100644 --- a/packages/insomnia-app/app/ui/components/dropdowns/request-group-actions-dropdown.js +++ b/packages/insomnia-app/app/ui/components/dropdowns/request-group-actions-dropdown.js @@ -80,7 +80,9 @@ class RequestGroupActionsDropdown extends React.PureComponent { this.props.handleCreateRequestGroup(this.props.requestGroup._id); } - _handleDeleteFolder() { + async _handleDeleteFolder() { + await models.stats.incrementDeletedRequestsForDescendents(this.props.requestGroup); + models.requestGroup.remove(this.props.requestGroup); } diff --git a/packages/insomnia-app/app/ui/components/modals/request-settings-modal.js b/packages/insomnia-app/app/ui/components/modals/request-settings-modal.js index e0a60eb7e1..83726a97f5 100644 --- a/packages/insomnia-app/app/ui/components/modals/request-settings-modal.js +++ b/packages/insomnia-app/app/ui/components/modals/request-settings-modal.js @@ -183,6 +183,8 @@ class RequestSettingsModal extends React.PureComponent { setTimeout(() => { this.setState({ justCopied: false }); }, 2000); + + models.stats.incrementCreatedRequests(); } async show({ request, forceEditMode }: RequestSettingsModalOptions) { diff --git a/packages/insomnia-app/app/ui/components/toast.js b/packages/insomnia-app/app/ui/components/toast.js index d5deea4127..0b9ea41ca7 100644 --- a/packages/insomnia-app/app/ui/components/toast.js +++ b/packages/insomnia-app/app/ui/components/toast.js @@ -20,6 +20,7 @@ import * as fetch from '../../account/fetch'; import imgSrcDesigner from '../images/insomnia-designer-logo.png'; import imgSrcCore from '../images/insomnia-core-logo.png'; import { APP_ID_INSOMNIA } from '../../../config'; +import { getDeviceId } from '../../common/analytics'; const LOCALSTORAGE_KEY = 'insomnia::notifications::seen'; @@ -127,6 +128,10 @@ class Toast extends React.PureComponent { autoUpdatesDisabled: !settings.updateAutomatically, disableUpdateNotification: settings.disableUpdateNotification, updateChannel: settings.updateChannel, + deviceId: await getDeviceId(), + createdRequests: stats.createdRequests, + deletedRequests: stats.deletedRequests, + executedRequests: stats.executedRequests, }; notification = await fetch.post('/notification', data, session.getCurrentSessionId()); diff --git a/packages/insomnia-app/app/ui/components/wrapper.js b/packages/insomnia-app/app/ui/components/wrapper.js index cb3fd610c2..e67058198c 100644 --- a/packages/insomnia-app/app/ui/components/wrapper.js +++ b/packages/insomnia-app/app/ui/components/wrapper.js @@ -429,11 +429,15 @@ class Wrapper extends React.PureComponent { title: 'Deleting Last Workspace', message: 'Since you deleted your only workspace, a new one has been created for you.', onConfirm: async () => { + await models.stats.incrementDeletedRequestsForDescendents(activeWorkspace); + await models.workspace.create({ name: getAppName() }); await models.workspace.remove(activeWorkspace); }, }); } else { + await models.stats.incrementDeletedRequestsForDescendents(activeWorkspace); + await models.workspace.remove(activeWorkspace); } } diff --git a/packages/insomnia-app/app/ui/containers/app.js b/packages/insomnia-app/app/ui/containers/app.js index 6ed371141b..b9b8a94844 100644 --- a/packages/insomnia-app/app/ui/containers/app.js +++ b/packages/insomnia-app/app/ui/containers/app.js @@ -209,6 +209,7 @@ class App extends PureComponent { const parentId = activeRequest ? activeRequest.parentId : activeWorkspace._id; const request = await models.request.create({ parentId, name: 'New Request' }); await this._handleSetActiveRequest(request._id); + models.stats.incrementCreatedRequests(); }, ], [ @@ -236,6 +237,7 @@ class App extends PureComponent { return; } await requestOperations.remove(activeRequest); + models.stats.incrementDeletedRequests(); }, }); }, @@ -338,6 +340,7 @@ class App extends PureComponent { parentId, onComplete: requestId => { this._handleSetActiveRequest(requestId); + models.stats.incrementCreatedRequests(); }, }); } @@ -350,7 +353,9 @@ class App extends PureComponent { label: 'New Name', selectText: true, onComplete: async name => { - await models.requestGroup.duplicate(requestGroup, { name }); + const newRequestGroup = await models.requestGroup.duplicate(requestGroup, { name }); + + models.stats.incrementCreatedRequestsForDescendents(newRequestGroup); }, }); } @@ -373,6 +378,7 @@ class App extends PureComponent { onComplete: async name => { const newRequest = await requestOperations.duplicate(request, { name }); await this._handleSetActiveRequest(newRequest._id); + models.stats.incrementCreatedRequests(); }, }); } @@ -404,6 +410,9 @@ class App extends PureComponent { if (!isYes) { return; } + + await models.stats.incrementDeletedRequestsForDescendents(workspace); + await models.workspace.remove(workspace); }, }); @@ -422,6 +431,8 @@ class App extends PureComponent { const newWorkspace = await db.duplicate(workspace, { name }); await this.props.handleSetActiveWorkspace(newWorkspace._id); callback(); + + models.stats.incrementCreatedRequestsForDescendents(newWorkspace); }, }); } @@ -650,12 +661,8 @@ class App extends PureComponent { return; } - // NOTE: Since request is by far the most popular event, we will throttle - // it so that we only track it if the request has changed since the last one - const key = request._id; - if (this._sendRequestTrackingKey !== key) { - this._sendRequestTrackingKey = key; - } + // Update request stats + models.stats.incrementExecutedRequests(); // Start loading handleStartLoading(requestId); @@ -738,12 +745,8 @@ class App extends PureComponent { return; } - // NOTE: Since request is by far the most popular event, we will throttle - // it so that we only track it if the request has changed since the last noe - const key = `${request._id}::${request.modified}`; - if (this._sendRequestTrackingKey !== key) { - this._sendRequestTrackingKey = key; - } + // Update request stats + models.stats.incrementExecutedRequests(); handleStartLoading(requestId); diff --git a/packages/insomnia-app/app/ui/redux/modules/global.js b/packages/insomnia-app/app/ui/redux/modules/global.js index 58f48c4ce1..125ae624f5 100644 --- a/packages/insomnia-app/app/ui/redux/modules/global.js +++ b/packages/insomnia-app/app/ui/redux/modules/global.js @@ -313,6 +313,10 @@ function handleImportResult(result: ImportResult, errorMessage: string): Array