Skip to content

Commit c544346

Browse files
committed
Add events for shopify theme dev and shopify theme console commands
1 parent 3a2f619 commit c544346

File tree

16 files changed

+97
-38
lines changed

16 files changed

+97
-38
lines changed

packages/cli-kit/src/private/node/analytics/error-categorizer.test.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ describe('categorizeError', () => {
3636
const errors = [
3737
new Error('ENOENT: no such file or directory'),
3838
new Error('EACCES: permission denied'),
39-
new Error('File not found: theme.liquid'),
4039
new Error('Cannot read directory'),
4140
new Error('Invalid path provided'),
4241
]
@@ -117,9 +116,9 @@ describe('categorizeError', () => {
117116

118117
// When
119118
// Then
120-
expect(categorizeError(errors[0])).toBe(ErrorCategory.Parsing)
121-
expect(categorizeError(errors[1])).toBe(ErrorCategory.Parsing)
122-
expect(categorizeError(errors[2])).toBe(ErrorCategory.Parsing)
119+
expect(categorizeError(errors[0])).toBe(ErrorCategory.Json)
120+
expect(categorizeError(errors[1])).toBe(ErrorCategory.Json)
121+
expect(categorizeError(errors[2])).toBe(ErrorCategory.Json)
123122
expect(categorizeError(errors[3])).toBe(ErrorCategory.Authentication)
124123
})
125124

@@ -134,7 +133,7 @@ describe('categorizeError', () => {
134133
// When
135134
// Then
136135
expect(categorizeError(errors[0])).toBe(ErrorCategory.Validation)
137-
expect(categorizeError(errors[1])).toBe(ErrorCategory.Parsing)
136+
expect(categorizeError(errors[1])).toBe(ErrorCategory.Validation)
138137
expect(categorizeError(errors[2])).toBe(ErrorCategory.Validation)
139138
})
140139

packages/cli-kit/src/private/node/analytics/error-categorizer.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
export enum ErrorCategory {
2+
Liquid = 'LIQUID',
23
ThemeCheck = 'THEME_CHECK',
34
Network = 'NETWORK',
45
FileSystem = 'FILE_SYSTEM',
56
Authentication = 'AUTHENTICATION',
67
Validation = 'VALIDATION',
78
Permission = 'PERMISSION',
89
RateLimit = 'RATE_LIMIT',
9-
Parsing = 'PARSING',
10+
Json = 'JSON',
1011
Unknown = 'UNKNOWN',
1112
}
1213

1314
const ERROR_CATEGORY_TERMS = {
15+
[ErrorCategory.Liquid]: ['liquid'],
16+
[ErrorCategory.Json]: ['json', 'parse response'],
1417
[ErrorCategory.ThemeCheck]: ['theme check'],
1518
[ErrorCategory.Authentication]: ['unauthorized', 'forbidden', 'auth', 'token', 'credential'],
1619
[ErrorCategory.Network]: [
@@ -31,7 +34,6 @@ const ERROR_CATEGORY_TERMS = {
3134
[ErrorCategory.FileSystem]: ['enoent', 'eacces', 'file', 'directory', 'path'],
3235
[ErrorCategory.Permission]: ['permission', 'denied', 'access', 'insufficient'],
3336
[ErrorCategory.RateLimit]: ['rate limit', 'too many requests', 'throttle'],
34-
[ErrorCategory.Parsing]: ['parse', 'syntax', 'json', 'invalid'],
3537
[ErrorCategory.Validation]: ['validation', 'invalid', 'required'],
3638
}
3739

packages/cli-kit/src/private/node/analytics/storage.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ describe('analytics/storage', () => {
9696
expect(errorEntry?.timestamp).toBeGreaterThanOrEqual(beforeTime)
9797

9898
expect(data.events.length).toBe(1)
99-
expect(data.events[0]?.name).toBe('error:NETWORK:Network request failed')
99+
expect(data.events[0]?.name).toBe('error:network:network-request-failed')
100100
})
101101

102102
test('records a string error', () => {
@@ -134,7 +134,7 @@ describe('analytics/storage', () => {
134134
// Then
135135
const data = compileData()
136136
const event = data.events[0]
137-
expect(event?.name).toBe(`error:UNKNOWN:${'A'.repeat(50)}`)
137+
expect(event?.name).toBe(`error:unknown:${'a'.repeat(50)}`)
138138
})
139139

140140
test('records multiple errors', () => {

packages/cli-kit/src/private/node/analytics/storage.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,23 @@ export function recordError(error: unknown): void {
6767
const category = categorizeError(error)
6868
const errorEntry: ErrorEntry = {
6969
category,
70-
message: error instanceof Error ? error.message : String(error),
70+
message: (error instanceof Error ? error.message : String(error)).substring(0, 200),
7171
timestamp: Date.now(),
7272
}
7373

74+
if (errorEntry.category === ErrorCategory.Unknown && !errorEntry.message) {
75+
return
76+
}
77+
7478
_runtimeAnalyticsStore.errors.push(errorEntry)
7579

76-
recordEvent(`error:${category}:${errorEntry.message.substring(0, 50)}`)
80+
const normalizedErrorCategory = category.toLowerCase()
81+
const normalizedErrorMessage = errorEntry.message
82+
.substring(0, 50)
83+
.replace(/[^a-zA-Z0-9]/g, '-')
84+
.toLowerCase()
85+
86+
recordEvent(`error:${normalizedErrorCategory}:${normalizedErrorMessage}`)
7787
}
7888

7989
export function recordRetry(url: string, operation: string): void {

packages/theme/src/cli/commands/theme/console.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {validateThemePassword} from '../../services/flags-validation.js'
66
import {globalFlags} from '@shopify/cli-kit/node/cli'
77
import {ensureAuthenticatedThemes} from '@shopify/cli-kit/node/session'
88
import {Flags} from '@oclif/core'
9+
import {recordEvent} from '@shopify/cli-kit/node/analytics'
910

1011
export default class Console extends ThemeCommand {
1112
static summary = 'Shopify Liquid REPL (read-eval-print loop) tool'
@@ -43,6 +44,9 @@ export default class Console extends ThemeCommand {
4344
const adminSession = await ensureAuthenticatedThemes(store, themeAccessPassword)
4445

4546
const {themeId, storePassword} = await ensureReplEnv(adminSession, flags['store-password'])
47+
48+
recordEvent('theme-command:console:single-env:authenticated')
49+
4650
await initializeRepl(adminSession, themeId, url, themeAccessPassword, storePassword)
4751
}
4852
}

packages/theme/src/cli/commands/theme/dev.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {Flags} from '@oclif/core'
1111
import {globalFlags} from '@shopify/cli-kit/node/cli'
1212
import {Theme} from '@shopify/cli-kit/node/themes/types'
1313
import {ensureAuthenticatedThemes} from '@shopify/cli-kit/node/session'
14+
import {recordEvent} from '@shopify/cli-kit/node/analytics'
1415
import type {ErrorOverlayMode, LiveReload} from '../../utilities/theme-environment/types.js'
1516

1617
export default class Dev extends ThemeCommand {
@@ -133,6 +134,8 @@ You can run this command only in a directory that matches the [default Shopify t
133134
const store = ensureThemeStore(flags)
134135
const adminSession = await ensureAuthenticatedThemes(store, flags.password)
135136

137+
recordEvent('theme-command:dev:single-env:authenticated')
138+
136139
let theme: Theme
137140

138141
if (flags.theme) {

packages/theme/src/cli/services/flags-validation.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {recordError} from '@shopify/cli-kit/node/analytics'
12
import {AbortError} from '@shopify/cli-kit/node/error'
23

34
/**
@@ -19,5 +20,5 @@ export function validateThemePassword(password?: string): void {
1920

2021
if (password.startsWith('shptka_')) return
2122

22-
throw new AbortError('Invalid password. Please generate a new password from the Theme Access app.')
23+
throw recordError(new AbortError('Invalid password. Please generate a new password from the Theme Access app.'))
2324
}

packages/theme/src/cli/utilities/notifier.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {recordError} from '@shopify/cli-kit/node/analytics'
12
import {outputDebug, outputWarn} from '@shopify/cli-kit/node/output'
23
import fs from 'fs/promises'
34

@@ -31,6 +32,8 @@ export class Notifier {
3132
}
3233
// eslint-disable-next-line no-catch-all/no-catch-all
3334
} catch (error) {
35+
recordError(error)
36+
3437
let message = `Failed to notify filechange listener at ${this.notifyPath}`
3538
if (error instanceof Error) {
3639
message = message.concat(`: ${error.message}`)

packages/theme/src/cli/utilities/theme-environment/dev-server-session.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {fetchThemeAssets} from '@shopify/cli-kit/node/themes/api'
55
import {AbortError} from '@shopify/cli-kit/node/error'
66
import {outputDebug, outputContent, outputToken} from '@shopify/cli-kit/node/output'
77
import {AdminSession, ensureAuthenticatedStorefront, ensureAuthenticatedThemes} from '@shopify/cli-kit/node/session'
8+
import {recordError, recordEvent} from '@shopify/cli-kit/node/analytics'
89

910
// 30 minutes in miliseconds.
1011
const SESSION_TIMEOUT_IN_MS = 30 * 60 * 1000
@@ -32,6 +33,7 @@ export async function initializeDevServerSession(
3233
session.refresh = async () => {
3334
outputDebug('Refreshing theme session...')
3435
const newSession = await fetchDevServerSession(themeId, adminSession, adminPassword, storefrontPassword)
36+
recordEvent('theme-service:session:refreshed')
3537
Object.assign(session, newSession)
3638
return newSession
3739
}
@@ -113,10 +115,12 @@ export async function abortOnMissingRequiredFile(themeId: string, adminSession:
113115
const requiredAssets = await fetchThemeAssets(Number(themeId), REQUIRED_THEME_FILES, adminSession)
114116

115117
if (requiredAssets.length !== REQUIRED_THEME_FILES.length) {
116-
throw new AbortError(
117-
outputContent`Theme ${outputToken.cyan(themeId)} is missing required files. Run ${outputToken.cyan(
118-
`shopify theme delete -t ${themeId}`,
119-
)} to delete it, then try your command again.`.value,
118+
throw recordError(
119+
new AbortError(
120+
outputContent`Theme ${outputToken.cyan(themeId)} is missing required files. Run ${outputToken.cyan(
121+
`shopify theme delete -t ${themeId}`,
122+
)} to delete it, then try your command again.`.value,
123+
),
120124
)
121125
}
122126

packages/theme/src/cli/utilities/theme-environment/hot-reload/server.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {renderError, renderInfo, renderWarning} from '@shopify/cli-kit/node/ui'
1818
import {extname, joinPath} from '@shopify/cli-kit/node/path'
1919
import {parseJSON} from '@shopify/theme-check-node'
2020
import {readFile} from '@shopify/cli-kit/node/fs'
21+
import {recordError, recordEvent} from '@shopify/cli-kit/node/analytics'
2122
import EventEmitter from 'node:events'
2223
import type {
2324
HotReloadEvent,
@@ -288,16 +289,21 @@ export function getHotReloadHandler(theme: Theme, ctx: DevServerContext): EventH
288289
replaceExtensionTemplates: getExtensionInMemoryTemplates(ctx),
289290
})
290291
.then(async (response) => {
291-
if (!response.ok) throw createFetchError(response)
292+
if (!response.ok) {
293+
recordEvent('theme-service:hot-reload:section:render-failed')
294+
throw createFetchError(response)
295+
}
292296

293297
return patchRenderingResponse(ctx, response)
294298
})
295299
.catch(async (error: Error) => {
296300
const {status, statusText, ...errorInfo} = extractFetchErrorInfo(
297-
error,
301+
recordError(error),
298302
'Failed to render section on Hot Reload',
299303
)
300304

305+
recordEvent('theme-service:hot-reload:section:render-error')
306+
301307
if (!appBlockId) renderWarning(errorInfo)
302308

303309
return new Response(null, {status, statusText})

0 commit comments

Comments
 (0)