Skip to content

Commit 4445519

Browse files
authored
Merge pull request #7428 from Shopify/dlm-store-command-metrics
Add store command analytics attribution
2 parents 93f0225 + c9d1937 commit 4445519

12 files changed

Lines changed: 97 additions & 2 deletions

File tree

packages/cli-kit/src/public/node/session.test.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ import {
66
ensureAuthenticatedPartners,
77
ensureAuthenticatedStorefront,
88
ensureAuthenticatedThemes,
9+
setLastSeenUserId,
910
} from './session.js'
1011

1112
import {getAppAutomationToken} from './environment.js'
1213
import {shopifyFetch} from './http.js'
13-
import {ApplicationToken} from '../../private/node/session/schema.js'
1414
import {ensureAuthenticated, setLastSeenAuthMethod, setLastSeenUserIdAfterAuth} from '../../private/node/session.js'
15+
import {ApplicationToken} from '../../private/node/session/schema.js'
1516
import {
1617
exchangeCustomPartnerToken,
1718
exchangeAppAutomationTokenForAppManagementAccessToken,
@@ -30,10 +31,17 @@ const partnersToken: ApplicationToken = {
3031

3132
vi.mock('../../private/node/session.js')
3233
vi.mock('../../private/node/session/exchange.js')
33-
vi.mock('../../private/node/session/store.js')
3434
vi.mock('./environment.js')
3535
vi.mock('./http.js')
3636

37+
describe('store command analytics session helpers', () => {
38+
test('sets last seen user id through the public session helper', () => {
39+
setLastSeenUserId('store-user-id')
40+
41+
expect(setLastSeenUserIdAfterAuth).toHaveBeenCalledWith('store-user-id')
42+
})
43+
})
44+
3745
describe('ensureAuthenticatedStorefront', () => {
3846
test('returns only storefront token if success', async () => {
3947
// Given

packages/cli-kit/src/public/node/session.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,15 @@ export interface Session {
4242

4343
export type AccountInfo = UserAccountInfo | ServiceAccountInfo | UnknownAccountInfo
4444

45+
/**
46+
* Records the user ID that should be attached to command analytics for this process.
47+
*
48+
* @param userId - User identifier to report on the command analytics event.
49+
*/
50+
export function setLastSeenUserId(userId: string): void {
51+
setLastSeenUserIdAfterAuth(userId)
52+
}
53+
4554
interface UserAccountInfo {
4655
type: 'UserAccount'
4756
email: string

packages/store/src/cli/commands/store/auth.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {createStoreAuthPresenter} from '../../services/store/auth/result.js'
44
import {describe, expect, test, vi} from 'vitest'
55

66
vi.mock('../../services/store/auth/index.js')
7+
vi.mock('../../services/store/metrics.js')
78
vi.mock('../../services/store/auth/result.js', () => ({
89
createStoreAuthPresenter: vi.fn((format: 'text' | 'json') => ({format})),
910
}))

packages/store/src/cli/commands/store/execute.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {beforeEach, describe, expect, test, vi} from 'vitest'
55

66
vi.mock('../../services/store/execute/index.js')
77
vi.mock('../../services/store/execute/result.js')
8+
vi.mock('../../services/store/metrics.js')
89

910
describe('store execute command', () => {
1011
beforeEach(() => {

packages/store/src/cli/services/store/auth/index.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import {authenticateStoreWithApp} from './index.js'
22
import {setStoredStoreAppSession} from './session-store.js'
33
import {STORE_AUTH_APP_CLIENT_ID} from './config.js'
4+
import {setLastSeenUserId} from '@shopify/cli-kit/node/session'
45
import {describe, expect, test, vi} from 'vitest'
56

67
vi.mock('./session-store.js')
8+
vi.mock('@shopify/cli-kit/node/session')
79
vi.mock('@shopify/cli-kit/node/system', () => ({openURL: vi.fn().mockResolvedValue(true)}))
810
vi.mock('@shopify/cli-kit/node/crypto', () => ({randomUUID: vi.fn().mockReturnValue('state-123')}))
911

@@ -52,6 +54,7 @@ describe('store auth service', () => {
5254
}),
5355
)
5456
expect(presenter.success).toHaveBeenCalledWith(result)
57+
expect(setLastSeenUserId).toHaveBeenCalledWith('42')
5558

5659
const storedSession = vi.mocked(setStoredStoreAppSession).mock.calls[0]![0]
5760
expect(storedSession.store).toBe('shop.myshopify.com')

packages/store/src/cli/services/store/auth/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {createPkceBootstrap} from './pkce.js'
66
import {mergeRequestedAndStoredScopes, parseStoreAuthScopes, resolveGrantedScopes} from './scopes.js'
77
import {resolveExistingStoreAuthScopes, type ResolvedStoreAuthScopes} from './existing-scopes.js'
88
import {createStoreAuthPresenter, type StoreAuthPresenter, type StoreAuthResult} from './result.js'
9+
import {setLastSeenUserId} from '@shopify/cli-kit/node/session'
910
import {openURL} from '@shopify/cli-kit/node/system'
1011
import {outputContent, outputDebug, outputToken} from '@shopify/cli-kit/node/output'
1112
import {AbortError} from '@shopify/cli-kit/node/error'
@@ -73,6 +74,7 @@ export async function authenticateStoreWithApp(
7374
if (!userId) {
7475
throw new AbortError('Shopify did not return associated user information for the online access token.')
7576
}
77+
setLastSeenUserId(userId)
7678

7779
const now = Date.now()
7880
const expiresAt = tokenResponse.expires_in ? new Date(now + tokenResponse.expires_in * 1000).toISOString() : undefined

packages/store/src/cli/services/store/execute/admin-context.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import {fetchPublicApiVersions} from './admin-transport.js'
33
import {loadStoredStoreSession} from '../auth/session-lifecycle.js'
44
import {STORE_AUTH_APP_CLIENT_ID} from '../auth/config.js'
55
import {AbortError} from '@shopify/cli-kit/node/error'
6+
import {setLastSeenUserId} from '@shopify/cli-kit/node/session'
67
import {beforeEach, describe, expect, test, vi} from 'vitest'
78

89
vi.mock('../auth/session-lifecycle.js', () => ({loadStoredStoreSession: vi.fn()}))
10+
vi.mock('@shopify/cli-kit/node/session')
911
vi.mock('./admin-transport.js', () => ({
1012
fetchPublicApiVersions: vi.fn(),
1113
// runAdminStoreGraphQLOperation isn't exercised here, but we re-export it for type completeness.
@@ -37,6 +39,7 @@ describe('prepareAdminStoreGraphQLContext', () => {
3739
const result = await prepareAdminStoreGraphQLContext({store})
3840

3941
expect(loadStoredStoreSession).toHaveBeenCalledWith(store)
42+
expect(setLastSeenUserId).toHaveBeenCalledWith('42')
4043
expect(fetchPublicApiVersions).toHaveBeenCalledWith({
4144
adminSession: {token: 'token', storeFqdn: store},
4245
session: storedSession,

packages/store/src/cli/services/store/execute/admin-context.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {fetchPublicApiVersions} from './admin-transport.js'
22
import {loadStoredStoreSession} from '../auth/session-lifecycle.js'
33
import {AbortError} from '@shopify/cli-kit/node/error'
4+
import {setLastSeenUserId} from '@shopify/cli-kit/node/session'
45
import type {AdminSession} from '@shopify/cli-kit/node/session'
56
import type {StoredStoreAppSession} from '../auth/session-store.js'
67

@@ -37,6 +38,7 @@ export async function prepareAdminStoreGraphQLContext(input: {
3738
userSpecifiedVersion?: string
3839
}): Promise<AdminStoreGraphQLContext> {
3940
const session = await loadStoredStoreSession(input.store)
41+
setLastSeenUserId(session.userId)
4042
const adminSession = {
4143
token: session.accessToken,
4244
storeFqdn: session.store,
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import {recordStoreFqdnMetadata} from './metrics.js'
2+
import {hashString} from '@shopify/cli-kit/node/crypto'
3+
import {addPublicMetadata} from '@shopify/cli-kit/node/metadata'
4+
import {beforeEach, describe, expect, test, vi} from 'vitest'
5+
6+
vi.mock('@shopify/cli-kit/node/crypto')
7+
vi.mock('@shopify/cli-kit/node/metadata')
8+
9+
describe('store command metrics', () => {
10+
beforeEach(() => {
11+
vi.clearAllMocks()
12+
vi.mocked(hashString).mockReturnValue('hashed-store')
13+
})
14+
15+
test('records the hashed store fqdn', async () => {
16+
await recordStoreFqdnMetadata('shop.myshopify.com')
17+
18+
expect(addPublicMetadata).toHaveBeenCalledWith(expect.any(Function))
19+
expect(vi.mocked(addPublicMetadata).mock.calls[0]![0]()).toEqual({store_fqdn_hash: 'hashed-store'})
20+
expect(hashString).toHaveBeenCalledWith('shop.myshopify.com')
21+
})
22+
23+
})
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import {hashString} from '@shopify/cli-kit/node/crypto'
2+
import {addPublicMetadata} from '@shopify/cli-kit/node/metadata'
3+
4+
export async function recordStoreFqdnMetadata(storeFqdn: string): Promise<void> {
5+
await addPublicMetadata(() => ({store_fqdn_hash: hashString(storeFqdn)}))
6+
}

0 commit comments

Comments
 (0)