Skip to content
Merged
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
12 changes: 10 additions & 2 deletions packages/cli-kit/src/public/node/session.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import {
ensureAuthenticatedPartners,
ensureAuthenticatedStorefront,
ensureAuthenticatedThemes,
setLastSeenUserId,
} from './session.js'

import {getAppAutomationToken} from './environment.js'
import {shopifyFetch} from './http.js'
import {ApplicationToken} from '../../private/node/session/schema.js'
import {ensureAuthenticated, setLastSeenAuthMethod, setLastSeenUserIdAfterAuth} from '../../private/node/session.js'
import {ApplicationToken} from '../../private/node/session/schema.js'
import {
exchangeCustomPartnerToken,
exchangeAppAutomationTokenForAppManagementAccessToken,
Expand All @@ -30,10 +31,17 @@ const partnersToken: ApplicationToken = {

vi.mock('../../private/node/session.js')
vi.mock('../../private/node/session/exchange.js')
vi.mock('../../private/node/session/store.js')
vi.mock('./environment.js')
vi.mock('./http.js')

describe('store command analytics session helpers', () => {
test('sets last seen user id through the public session helper', () => {
setLastSeenUserId('store-user-id')

expect(setLastSeenUserIdAfterAuth).toHaveBeenCalledWith('store-user-id')
})
})

describe('ensureAuthenticatedStorefront', () => {
test('returns only storefront token if success', async () => {
// Given
Expand Down
9 changes: 9 additions & 0 deletions packages/cli-kit/src/public/node/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ export interface Session {

export type AccountInfo = UserAccountInfo | ServiceAccountInfo | UnknownAccountInfo

/**
* Records the user ID that should be attached to command analytics for this process.
*
* @param userId - User identifier to report on the command analytics event.
*/
export function setLastSeenUserId(userId: string): void {
setLastSeenUserIdAfterAuth(userId)
}

interface UserAccountInfo {
type: 'UserAccount'
email: string
Expand Down
1 change: 1 addition & 0 deletions packages/store/src/cli/commands/store/auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {createStoreAuthPresenter} from '../../services/store/auth/result.js'
import {describe, expect, test, vi} from 'vitest'

vi.mock('../../services/store/auth/index.js')
vi.mock('../../services/store/metrics.js')
vi.mock('../../services/store/auth/result.js', () => ({
createStoreAuthPresenter: vi.fn((format: 'text' | 'json') => ({format})),
}))
Expand Down
1 change: 1 addition & 0 deletions packages/store/src/cli/commands/store/execute.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {beforeEach, describe, expect, test, vi} from 'vitest'

vi.mock('../../services/store/execute/index.js')
vi.mock('../../services/store/execute/result.js')
vi.mock('../../services/store/metrics.js')

describe('store execute command', () => {
beforeEach(() => {
Expand Down
3 changes: 3 additions & 0 deletions packages/store/src/cli/services/store/auth/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import {authenticateStoreWithApp} from './index.js'
import {setStoredStoreAppSession} from './session-store.js'
import {STORE_AUTH_APP_CLIENT_ID} from './config.js'
import {setLastSeenUserId} from '@shopify/cli-kit/node/session'
import {describe, expect, test, vi} from 'vitest'

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

Expand Down Expand Up @@ -52,6 +54,7 @@ describe('store auth service', () => {
}),
)
expect(presenter.success).toHaveBeenCalledWith(result)
expect(setLastSeenUserId).toHaveBeenCalledWith('42')

const storedSession = vi.mocked(setStoredStoreAppSession).mock.calls[0]![0]
expect(storedSession.store).toBe('shop.myshopify.com')
Expand Down
2 changes: 2 additions & 0 deletions packages/store/src/cli/services/store/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {createPkceBootstrap} from './pkce.js'
import {mergeRequestedAndStoredScopes, parseStoreAuthScopes, resolveGrantedScopes} from './scopes.js'
import {resolveExistingStoreAuthScopes, type ResolvedStoreAuthScopes} from './existing-scopes.js'
import {createStoreAuthPresenter, type StoreAuthPresenter, type StoreAuthResult} from './result.js'
import {setLastSeenUserId} from '@shopify/cli-kit/node/session'
import {openURL} from '@shopify/cli-kit/node/system'
import {outputContent, outputDebug, outputToken} from '@shopify/cli-kit/node/output'
import {AbortError} from '@shopify/cli-kit/node/error'
Expand Down Expand Up @@ -73,6 +74,7 @@ export async function authenticateStoreWithApp(
if (!userId) {
throw new AbortError('Shopify did not return associated user information for the online access token.')
}
setLastSeenUserId(userId)

const now = Date.now()
const expiresAt = tokenResponse.expires_in ? new Date(now + tokenResponse.expires_in * 1000).toISOString() : undefined
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import {fetchPublicApiVersions} from './admin-transport.js'
import {loadStoredStoreSession} from '../auth/session-lifecycle.js'
import {STORE_AUTH_APP_CLIENT_ID} from '../auth/config.js'
import {AbortError} from '@shopify/cli-kit/node/error'
import {setLastSeenUserId} from '@shopify/cli-kit/node/session'
import {beforeEach, describe, expect, test, vi} from 'vitest'

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

expect(loadStoredStoreSession).toHaveBeenCalledWith(store)
expect(setLastSeenUserId).toHaveBeenCalledWith('42')
expect(fetchPublicApiVersions).toHaveBeenCalledWith({
adminSession: {token: 'token', storeFqdn: store},
session: storedSession,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {fetchPublicApiVersions} from './admin-transport.js'
import {loadStoredStoreSession} from '../auth/session-lifecycle.js'
import {AbortError} from '@shopify/cli-kit/node/error'
import {setLastSeenUserId} from '@shopify/cli-kit/node/session'
import type {AdminSession} from '@shopify/cli-kit/node/session'
import type {StoredStoreAppSession} from '../auth/session-store.js'

Expand Down Expand Up @@ -37,6 +38,7 @@ export async function prepareAdminStoreGraphQLContext(input: {
userSpecifiedVersion?: string
}): Promise<AdminStoreGraphQLContext> {
const session = await loadStoredStoreSession(input.store)
setLastSeenUserId(session.userId)
const adminSession = {
token: session.accessToken,
storeFqdn: session.store,
Expand Down
23 changes: 23 additions & 0 deletions packages/store/src/cli/services/store/metrics.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {recordStoreFqdnMetadata} from './metrics.js'
import {hashString} from '@shopify/cli-kit/node/crypto'
import {addPublicMetadata} from '@shopify/cli-kit/node/metadata'
import {beforeEach, describe, expect, test, vi} from 'vitest'

vi.mock('@shopify/cli-kit/node/crypto')
vi.mock('@shopify/cli-kit/node/metadata')

describe('store command metrics', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.mocked(hashString).mockReturnValue('hashed-store')
})

test('records the hashed store fqdn', async () => {
await recordStoreFqdnMetadata('shop.myshopify.com')

expect(addPublicMetadata).toHaveBeenCalledWith(expect.any(Function))
expect(vi.mocked(addPublicMetadata).mock.calls[0]![0]()).toEqual({store_fqdn_hash: 'hashed-store'})
expect(hashString).toHaveBeenCalledWith('shop.myshopify.com')
})

})
6 changes: 6 additions & 0 deletions packages/store/src/cli/services/store/metrics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {hashString} from '@shopify/cli-kit/node/crypto'
import {addPublicMetadata} from '@shopify/cli-kit/node/metadata'

export async function recordStoreFqdnMetadata(storeFqdn: string): Promise<void> {
await addPublicMetadata(() => ({store_fqdn_hash: hashString(storeFqdn)}))
}
35 changes: 35 additions & 0 deletions packages/store/src/cli/utilities/store-command.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import StoreCommand from './store-command.js'
import {addPublicMetadata, addSensitiveMetadata} from '@shopify/cli-kit/node/metadata'
import {hashString} from '@shopify/cli-kit/node/crypto'
import {Flags} from '@oclif/core'
import {beforeEach, describe, expect, test, vi} from 'vitest'

vi.mock('@shopify/cli-kit/node/metadata')
vi.mock('@shopify/cli-kit/node/crypto')

class TestStoreCommand extends StoreCommand {
static flags = {
store: Flags.string({required: true}),
}

async run(): Promise<void> {
await this.parse(TestStoreCommand)
}
}

describe('StoreCommand', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.mocked(hashString).mockReturnValue('hashed-store')
})

test('records shared store command metadata when parsing --store', async () => {
await TestStoreCommand.run(['--store', 'shop.myshopify.com'])

expect(addSensitiveMetadata).toHaveBeenCalledWith(expect.any(Function))
expect(vi.mocked(addSensitiveMetadata).mock.calls[0]![0]()).toEqual({store_fqdn: 'shop.myshopify.com'})
expect(addPublicMetadata).toHaveBeenCalledWith(expect.any(Function))
const publicMetadataCalls = vi.mocked(addPublicMetadata).mock.calls.map((call) => call[0]())
expect(publicMetadataCalls).toContainEqual({store_fqdn_hash: 'hashed-store'})
})
})
2 changes: 2 additions & 0 deletions packages/store/src/cli/utilities/store-command.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {recordStoreFqdnMetadata} from '../services/store/metrics.js'
import Command, {type ArgOutput, type FlagOutput} from '@shopify/cli-kit/node/base-command'
import {addSensitiveMetadata} from '@shopify/cli-kit/node/metadata'

Expand All @@ -21,6 +22,7 @@ export default abstract class StoreCommand extends Command {
const storeFqdn = (result.flags as {store?: unknown}).store
if (typeof storeFqdn === 'string' && storeFqdn.length > 0) {
await addSensitiveMetadata(() => ({store_fqdn: storeFqdn}))
await recordStoreFqdnMetadata(storeFqdn)
}
return result
}
Expand Down
Loading