mirror of
https://github.com/balena-io/balena-cli.git
synced 2024-12-22 15:02:22 +00:00
Merge pull request #1410 from balena-io/oclif-mixpanel
Add missing oclif-based commands to mixpanel tracking
This commit is contained in:
commit
596d1bdc21
@ -4,7 +4,7 @@
|
|||||||
"target": "es2017",
|
"target": "es2017",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"strictPropertyInitialization": false,
|
"strictPropertyInitialization": false,
|
||||||
"noUnusedLocals": false,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"preserveConstEnums": true,
|
"preserveConstEnums": true,
|
||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
|
@ -154,5 +154,17 @@ exports.run = (argv) ->
|
|||||||
else
|
else
|
||||||
capitanoExecuteAsync(cli)
|
capitanoExecuteAsync(cli)
|
||||||
|
|
||||||
Promise.all([events.trackCommand(cli), runCommand()])
|
trackCommand = ->
|
||||||
|
getMatchCommandAsync = Promise.promisify(capitano.state.getMatchCommand)
|
||||||
|
getMatchCommandAsync(cli.command)
|
||||||
|
.then (command) ->
|
||||||
|
# cmdSignature is literally a string like, for example:
|
||||||
|
# "push <applicationOrDevice>"
|
||||||
|
# ("applicationOrDevice" is NOT replaced with its actual value)
|
||||||
|
# In case of failures like an inexistent or invalid command,
|
||||||
|
# command.signature.toString() returns '*'
|
||||||
|
cmdSignature = command.signature.toString()
|
||||||
|
events.trackCommand(cmdSignature)
|
||||||
|
|
||||||
|
Promise.all([trackCommand(), runCommand()])
|
||||||
.catch(require('./errors').handleError)
|
.catch(require('./errors').handleError)
|
||||||
|
@ -19,7 +19,7 @@ import { Main } from '@oclif/command';
|
|||||||
import { ExitError } from '@oclif/errors';
|
import { ExitError } from '@oclif/errors';
|
||||||
|
|
||||||
import { AppOptions } from './app';
|
import { AppOptions } from './app';
|
||||||
import { handleError } from './errors';
|
import { trackPromise } from './hooks/prerun/track';
|
||||||
|
|
||||||
class CustomMain extends Main {
|
class CustomMain extends Main {
|
||||||
protected _helpOverride(): boolean {
|
protected _helpOverride(): boolean {
|
||||||
@ -36,7 +36,7 @@ class CustomMain extends Main {
|
|||||||
* oclif CLI entrypoint
|
* oclif CLI entrypoint
|
||||||
*/
|
*/
|
||||||
export function run(command: string[], options: AppOptions) {
|
export function run(command: string[], options: AppOptions) {
|
||||||
return CustomMain.run(command).then(
|
const runPromise = CustomMain.run(command).then(
|
||||||
() => {
|
() => {
|
||||||
if (!options.noFlush) {
|
if (!options.noFlush) {
|
||||||
return require('@oclif/command/flush');
|
return require('@oclif/command/flush');
|
||||||
@ -46,8 +46,12 @@ export function run(command: string[], options: AppOptions) {
|
|||||||
// oclif sometimes exits with ExitError code 0 (not an error)
|
// oclif sometimes exits with ExitError code 0 (not an error)
|
||||||
if (error instanceof ExitError && error.oclif.exit === 0) {
|
if (error instanceof ExitError && error.oclif.exit === 0) {
|
||||||
return;
|
return;
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
handleError(error);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
return Promise.all([trackPromise, runPromise]).catch(
|
||||||
|
require('./errors').handleError,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ import { exitWithExpectedError } from './utils/patterns';
|
|||||||
|
|
||||||
export interface AppOptions {
|
export interface AppOptions {
|
||||||
// Prevent the default behaviour of flushing stdout after running a command
|
// Prevent the default behaviour of flushing stdout after running a command
|
||||||
noFlush: boolean;
|
noFlush?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -160,7 +160,7 @@ function isOclifCommand(argvSlice: string[]): [boolean, boolean] {
|
|||||||
* CLI entrypoint, but see also `bin/balena` and `bin/balena-dev` which
|
* CLI entrypoint, but see also `bin/balena` and `bin/balena-dev` which
|
||||||
* call this function.
|
* call this function.
|
||||||
*/
|
*/
|
||||||
export function run(cliArgs = process.argv, options: AppOptions): void {
|
export function run(cliArgs = process.argv, options: AppOptions = {}): void {
|
||||||
// globalInit() must be called very early on (before other imports) because
|
// globalInit() must be called very early on (before other imports) because
|
||||||
// it sets up Sentry error reporting, global HTTP proxy settings, balena-sdk
|
// it sets up Sentry error reporting, global HTTP proxy settings, balena-sdk
|
||||||
// shared options, and performs node version requirement checks.
|
// shared options, and performs node version requirement checks.
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
*/
|
*/
|
||||||
import BalenaSdk = require('balena-sdk');
|
import BalenaSdk = require('balena-sdk');
|
||||||
import Promise = require('bluebird');
|
import Promise = require('bluebird');
|
||||||
import * as Capitano from 'capitano';
|
|
||||||
import _ = require('lodash');
|
import _ = require('lodash');
|
||||||
import Mixpanel = require('mixpanel');
|
import Mixpanel = require('mixpanel');
|
||||||
import Raven = require('raven');
|
import Raven = require('raven');
|
||||||
@ -24,7 +23,6 @@ import Raven = require('raven');
|
|||||||
import packageJSON = require('../package.json');
|
import packageJSON = require('../package.json');
|
||||||
|
|
||||||
const getBalenaSdk = _.once(() => BalenaSdk.fromSharedOptions());
|
const getBalenaSdk = _.once(() => BalenaSdk.fromSharedOptions());
|
||||||
const getMatchCommandAsync = Promise.promisify(Capitano.state.getMatchCommand);
|
|
||||||
const getMixpanel = _.once<any>(() => {
|
const getMixpanel = _.once<any>(() => {
|
||||||
const settings = require('balena-settings-client');
|
const settings = require('balena-settings-client');
|
||||||
return Mixpanel.init('00000000000000000000000000000000', {
|
return Mixpanel.init('00000000000000000000000000000000', {
|
||||||
@ -34,7 +32,7 @@ const getMixpanel = _.once<any>(() => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export function trackCommand(capitanoCli: Capitano.Cli) {
|
export function trackCommand(commandSignature: string) {
|
||||||
const balena = getBalenaSdk();
|
const balena = getBalenaSdk();
|
||||||
return Promise.props({
|
return Promise.props({
|
||||||
balenaUrl: balena.settings.get('balenaUrl'),
|
balenaUrl: balena.settings.get('balenaUrl'),
|
||||||
@ -42,20 +40,20 @@ export function trackCommand(capitanoCli: Capitano.Cli) {
|
|||||||
mixpanel: getMixpanel(),
|
mixpanel: getMixpanel(),
|
||||||
})
|
})
|
||||||
.then(({ username, balenaUrl, mixpanel }) => {
|
.then(({ username, balenaUrl, mixpanel }) => {
|
||||||
return getMatchCommandAsync(capitanoCli.command).then(command => {
|
return Promise.try(() => {
|
||||||
Raven.mergeContext({
|
Raven.mergeContext({
|
||||||
user: {
|
user: {
|
||||||
id: username,
|
id: username,
|
||||||
username,
|
username,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
// `command.signature.toString()` results in a string like, for example:
|
// commandSignature is a string like, for example:
|
||||||
// "push <applicationOrDevice>"
|
// "push <applicationOrDevice>"
|
||||||
// That's literally so: "applicationOrDevice" is NOT replaced with
|
// That's literally so: "applicationOrDevice" is NOT replaced with
|
||||||
// the actual application ID or device ID. The purpose is find out the
|
// the actual application ID or device ID. The purpose is find out the
|
||||||
// most / least used command verbs, so we can focus our development
|
// most / least used command verbs, so we can focus our development
|
||||||
// effort where it is most beneficial to end users.
|
// effort where it is most beneficial to end users.
|
||||||
return mixpanel.track(`[CLI] ${command.signature.toString()}`, {
|
return mixpanel.track(`[CLI] ${commandSignature}`, {
|
||||||
distinct_id: username,
|
distinct_id: username,
|
||||||
version: packageJSON.version,
|
version: packageJSON.version,
|
||||||
node: process.version,
|
node: process.version,
|
||||||
|
44
lib/hooks/prerun/track.ts
Normal file
44
lib/hooks/prerun/track.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2019 Balena Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
import { Hook } from '@oclif/config';
|
||||||
|
|
||||||
|
// note: trackPromise is subject to a Bluebird.timeout, defined in events.ts
|
||||||
|
export let trackPromise: PromiseLike<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is an oclif 'prerun' hook. This hook runs after the command line is
|
||||||
|
* parsed by oclif, but before the command's run() function is called.
|
||||||
|
* See: https://oclif.io/docs/hooks
|
||||||
|
*
|
||||||
|
* This hook is used to track CLI command signatures with mixpanel. This
|
||||||
|
* is the oclif version of what is already done for Capitano commands.
|
||||||
|
*
|
||||||
|
* A command signature is something like "env add NAME [VALUE]". That's
|
||||||
|
* literally so: 'NAME' and 'VALUE' are NOT replaced with actual values.
|
||||||
|
*/
|
||||||
|
const hook: Hook<'prerun'> = async function(options) {
|
||||||
|
const events = await import('../../events');
|
||||||
|
const usage: string | string[] | undefined = options.Command.usage;
|
||||||
|
const cmdSignature =
|
||||||
|
usage == null ? '*' : typeof usage === 'string' ? usage : usage.join(' ');
|
||||||
|
|
||||||
|
// Intentionally do not await for the track promise here, in order to
|
||||||
|
// run the command tracking and the command itself in parallel.
|
||||||
|
trackPromise = events.trackCommand(cmdSignature);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default hook;
|
@ -31,6 +31,7 @@
|
|||||||
"assets": [
|
"assets": [
|
||||||
"build/actions-oclif",
|
"build/actions-oclif",
|
||||||
"build/auth/pages/*.ejs",
|
"build/auth/pages/*.ejs",
|
||||||
|
"build/hooks",
|
||||||
"node_modules/resin-discoverable-services/services/**/*"
|
"node_modules/resin-discoverable-services/services/**/*"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -71,6 +72,9 @@
|
|||||||
"oclif": {
|
"oclif": {
|
||||||
"bin": "balena",
|
"bin": "balena",
|
||||||
"commands": "./build/actions-oclif",
|
"commands": "./build/actions-oclif",
|
||||||
|
"hooks": {
|
||||||
|
"prerun": "./build/hooks/prerun/track"
|
||||||
|
},
|
||||||
"macos": {
|
"macos": {
|
||||||
"identifier": "io.balena.cli",
|
"identifier": "io.balena.cli",
|
||||||
"sign": "Developer ID Installer: Rulemotion Ltd (66H43P8FRG)"
|
"sign": "Developer ID Installer: Rulemotion Ltd (66H43P8FRG)"
|
||||||
|
Loading…
Reference in New Issue
Block a user