Skip to content
Draft
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
109 changes: 78 additions & 31 deletions packages/cli-kit/src/public/node/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,52 @@ interface ReportAnalyticsEventOptions {
exitMode: CommandExitMode
}

export async function sendAnalyticsEventFromFile(payloadFile: string): Promise<void> {
const {readFile, removeFile} = await import('./fs.js')
try {
const payloadStr = await readFile(payloadFile)
const payload = JSON.parse(payloadStr)

// remove file
await removeFile(payloadFile)

const doMonorail = async () => {
if (payload.skipMonorailAnalytics) return
const response = await publishMonorailEvent(MONORAIL_COMMAND_TOPIC, payload.public, payload.sensitive)
if (response.type === 'error') {
outputDebug(response.message)
}
}

const doOpenTelemetry = async () => {
if (payload.skipMetricAnalytics) return

const active = payload.public.cmd_all_timing_active_ms ?? 0
const network = payload.public.cmd_all_timing_network_ms ?? 0
const prompt = payload.public.cmd_all_timing_prompts_ms ?? 0

return recordMetrics(
{
skipMetricAnalytics: payload.skipMetricAnalytics,
cliVersion: payload.public.cli_version,
owningPlugin: payload.public.cmd_all_plugin ?? '@shopify/cli',
command: payload.public.command,
exitMode: payload.public.cmd_all_exit,
},
{
active,
network,
prompt,
},
)
}

await Promise.all([doMonorail(), doOpenTelemetry()])
} catch (error) {
outputDebug(`Failed to send analytics in background: ${error}`)
}
}

/**
* Report an analytics event, sending it off to Monorail -- Shopify's internal analytics service.
*
Expand All @@ -45,8 +91,7 @@ interface ReportAnalyticsEventOptions {
export async function reportAnalyticsEvent(options: ReportAnalyticsEventOptions): Promise<void> {
try {
const payload = await buildPayload(options)
if (payload === undefined) {
// Nothing to log
if (payload === undefined || payload.public.command === 'send-analytics') {
return
}

Expand All @@ -65,40 +110,42 @@ export async function reportAnalyticsEvent(options: ReportAnalyticsEventOptions)

const skipMonorailAnalytics = !alwaysLogAnalytics() && analyticsDisabled()
const skipMetricAnalytics = !alwaysLogMetrics() && analyticsDisabled()
if (skipMonorailAnalytics || skipMetricAnalytics) {
if (skipMonorailAnalytics && skipMetricAnalytics) {
outputDebug(outputContent`Skipping command analytics, payload: ${outputToken.json(payload)}`)
return
}

const doMonorail = async () => {
if (skipMonorailAnalytics) {
return
}
const response = await publishMonorailEvent(MONORAIL_COMMAND_TOPIC, payload.public, payload.sensitive)
if (response.type === 'error') {
outputDebug(response.message)
}
}
const doOpenTelemetry = async () => {
const active = payload.public.cmd_all_timing_active_ms ?? 0
const network = payload.public.cmd_all_timing_network_ms ?? 0
const prompt = payload.public.cmd_all_timing_prompts_ms ?? 0
outputDebug(outputContent`Sending command analytics in background, payload: ${outputToken.json(payload)}`)

return recordMetrics(
{
skipMetricAnalytics,
cliVersion: payload.public.cli_version,
owningPlugin: payload.public.cmd_all_plugin ?? '@shopify/cli',
command: payload.public.command,
exitMode: options.exitMode,
},
{
active,
network,
prompt,
},
)
const {joinPath} = await import('./path.js')
const {tmpdir} = await import('node:os')
const {writeFile} = await import('./fs.js')

const payloadPath = joinPath(tmpdir(), `shopify-cli-analytics-${Date.now()}.json`)

const fullPayload = {
...payload,
skipMonorailAnalytics,
skipMetricAnalytics,
}
await Promise.all([doMonorail(), doOpenTelemetry()])

await writeFile(payloadPath, JSON.stringify(fullPayload))

const {exec} = await import('./system.js')
const argv = process.argv
if (!argv[0] || !argv[1]) return
const nodeBinary = argv[0]
const shopifyBinary = argv[1]
const args = [shopifyBinary, 'send-analytics', '--payload-file', payloadPath]

// eslint-disable-next-line no-void
void exec(nodeBinary, args, {
background: true,
env: {...process.env, SHOPIFY_CLI_NO_ANALYTICS: '1'},
externalErrorHandler: async (error: unknown) => {
outputDebug(`Failed to send analytics in background: ${(error as Error).message}`)
},
})

// eslint-disable-next-line no-catch-all/no-catch-all
} catch (error) {
Expand Down
3 changes: 2 additions & 1 deletion packages/cli-kit/src/public/node/hooks/postrun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ export const hook: Hook.Postrun = async ({config, Command}) => {
const command = Command.id.replace(/:/g, ' ')
outputDebug(`Completed command ${command}`)

if (!command.includes('notifications') && !command.includes('upgrade')) await autoUpgradeIfNeeded()
if (!command.includes('notifications') && !command.includes('upgrade') && !command.includes('send-analytics'))
await autoUpgradeIfNeeded()
postRunHookCompleted = true
}

Expand Down
1 change: 1 addition & 0 deletions packages/cli-kit/src/public/node/notifications-system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const COMMANDS_TO_SKIP = [
'theme:init',
'hydrogen:init',
'cache:clear',
'send-analytics',
]

function url(): string {
Expand Down
26 changes: 26 additions & 0 deletions packages/cli/oclif.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5669,6 +5669,32 @@
"strict": true,
"usage": "search [query]"
},
"send-analytics": {
"aliases": [
],
"args": {
},
"enableJsonFlag": false,
"flags": {
"payload-file": {
"hasDynamicHelp": false,
"hidden": true,
"multiple": false,
"name": "payload-file",
"required": true,
"type": "option"
}
},
"hasDynamicHelp": false,
"hidden": true,
"hiddenAliases": [
],
"id": "send-analytics",
"pluginAlias": "@shopify/cli",
"pluginName": "@shopify/cli",
"pluginType": "core",
"strict": true
},
"store:auth": {
"aliases": [
],
Expand Down
19 changes: 19 additions & 0 deletions packages/cli/src/cli/commands/send-analytics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Command from '@shopify/cli-kit/node/base-command'
import {Flags} from '@oclif/core'
import {sendAnalyticsEventFromFile} from '@shopify/cli-kit/node/analytics'

export default class SendAnalytics extends Command {
static hidden = true

static flags = {
'payload-file': Flags.string({
hidden: true,
required: true,
}),
}

async run(): Promise<void> {
const {flags} = await this.parse(SendAnalytics)
await sendAnalyticsEventFromFile(flags['payload-file'])
}
}
2 changes: 2 additions & 0 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import VersionCommand from './cli/commands/version.js'
import Search from './cli/commands/search.js'
import Upgrade from './cli/commands/upgrade.js'
import SendAnalytics from './cli/commands/send-analytics.js'
import Logout from './cli/commands/auth/logout.js'
import Login from './cli/commands/auth/login.js'
import CommandFlags from './cli/commands/debug/command-flags.js'
Expand Down Expand Up @@ -147,6 +148,7 @@ export const COMMANDS: any = {
search: Search,
upgrade: Upgrade,
version: VersionCommand,
'send-analytics': SendAnalytics,
help: HelpCommand,
'auth:logout': Logout,
'auth:login': Login,
Expand Down
Loading