Skip to content

Commit bc6326c

Browse files
Merge pull request #6694 from Shopify/use_integer_for_status_id
Support numeric IDs in bulk operation status command
2 parents 915db4f + 8dffee6 commit bc6326c

File tree

6 files changed

+82
-16
lines changed

6 files changed

+82
-16
lines changed

packages/app/src/cli/commands/app/bulk/status.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import {appFlags} from '../../../flags.js'
22
import AppLinkedCommand, {AppLinkedCommandOutput} from '../../../utilities/app-linked-command.js'
33
import {prepareAppStoreContext} from '../../../utilities/execute-command-helpers.js'
4-
import {getBulkOperationStatus, listBulkOperations} from '../../../services/bulk-operations/bulk-operation-status.js'
4+
import {
5+
getBulkOperationStatus,
6+
listBulkOperations,
7+
normalizeBulkOperationId,
8+
} from '../../../services/bulk-operations/bulk-operation-status.js'
59
import {Flags} from '@oclif/core'
610
import {globalFlags} from '@shopify/cli-kit/node/cli'
711
import {normalizeStoreFqdn} from '@shopify/cli-kit/node/context/fqdn'
@@ -18,7 +22,8 @@ export default class BulkStatus extends AppLinkedCommand {
1822
...globalFlags,
1923
...appFlags,
2024
id: Flags.string({
21-
description: 'The bulk operation ID. If not provided, lists all bulk operations in the last 7 days.',
25+
description:
26+
'The bulk operation ID (numeric ID or full GID). If not provided, lists all bulk operations in the last 7 days.',
2227
env: 'SHOPIFY_FLAG_ID',
2328
}),
2429
store: Flags.string({
@@ -38,7 +43,7 @@ export default class BulkStatus extends AppLinkedCommand {
3843
await getBulkOperationStatus({
3944
organization: appContextResult.organization,
4045
storeFqdn: store.shopDomain,
41-
operationId: flags.id,
46+
operationId: normalizeBulkOperationId(flags.id),
4247
remoteApp: appContextResult.remoteApp,
4348
})
4449
} else {

packages/app/src/cli/services/bulk-operations/bulk-operation-status.test.ts

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import {getBulkOperationStatus, listBulkOperations} from './bulk-operation-status.js'
1+
import {
2+
getBulkOperationStatus,
3+
listBulkOperations,
4+
normalizeBulkOperationId,
5+
extractBulkOperationId,
6+
} from './bulk-operation-status.js'
27
import {GetBulkOperationByIdQuery} from '../../api/graphql/bulk-operations/generated/get-bulk-operation-by-id.js'
38
import {OrganizationApp, Organization, OrganizationSource} from '../../models/organization.js'
49
import {ListBulkOperationsQuery} from '../../api/graphql/bulk-operations/generated/list-bulk-operations.js'
@@ -36,6 +41,37 @@ afterEach(() => {
3641
mockAndCaptureOutput().clear()
3742
})
3843

44+
describe('normalizeBulkOperationId', () => {
45+
test('returns GID as-is when already in GID format', () => {
46+
const gid = 'gid://shopify/BulkOperation/123'
47+
expect(normalizeBulkOperationId(gid)).toBe(gid)
48+
})
49+
50+
test('converts numeric ID to GID format', () => {
51+
expect(normalizeBulkOperationId('123')).toBe('gid://shopify/BulkOperation/123')
52+
expect(normalizeBulkOperationId('456789')).toBe('gid://shopify/BulkOperation/456789')
53+
})
54+
55+
test('returns non-numeric, non-GID string as-is', () => {
56+
const invalidId = 'invalid-id'
57+
expect(normalizeBulkOperationId(invalidId)).toBe(invalidId)
58+
})
59+
})
60+
61+
describe('extractBulkOperationId', () => {
62+
test('extracts numeric ID from GID', () => {
63+
expect(extractBulkOperationId('gid://shopify/BulkOperation/123')).toBe('123')
64+
expect(extractBulkOperationId('gid://shopify/BulkOperation/456789')).toBe('456789')
65+
})
66+
67+
test('returns input as-is if not a valid GID format', () => {
68+
expect(extractBulkOperationId('gid://shopify/BulkOperation/ABC')).toBe('gid://shopify/BulkOperation/ABC')
69+
expect(extractBulkOperationId('BulkOperation/123')).toBe('BulkOperation/123')
70+
expect(extractBulkOperationId('invalid-id')).toBe('invalid-id')
71+
expect(extractBulkOperationId('123')).toBe('123')
72+
})
73+
})
74+
3975
describe('getBulkOperationStatus', () => {
4076
function mockBulkOperation(
4177
overrides?: Partial<NonNullable<GetBulkOperationByIdQuery['bulkOperation']>>,
@@ -222,15 +258,15 @@ describe('listBulkOperations', () => {
222258
│ │
223259
╰──────────────────────────────────────────────────────────────────────────────╯
224260
225-
ID STATUS COU DATE CREATED DATE RESULTS
226-
T FINISHED
261+
I STATUS COUNT DATE CREATED DATE FINISHED RESULTS
227262
228-
──────────────── ────── ─── ──────────── ─────────── ───────────────────────────
229-
──────────── ── ── ─────── ─────── ───────────────────
230-
gid://shopify/Bu COMPLE 123 2025-11-10 2025-11-10 download ( https://example.
231-
kOperation/1 ED 5K 12:37:52 16:37:12 com/results.jsonl )
232-
gid://shopify/Bu RUNNIN 100 2025-11-11
233-
kOperation/2 15:37:52"
263+
─ ─────── ───── ────────────── ────────────── ──────────────────────────────────
264+
─ ──── ──── ────────────
265+
1 COMPLET 123.5 2025-11-10 2025-11-10 download (
266+
D 12:37:52 16:37:12 https://example.com/results.jsonl
267+
)
268+
2 RUNNING 100 2025-11-11
269+
15:37:52"
234270
`)
235271
})
236272

packages/app/src/cli/services/bulk-operations/bulk-operation-status.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,27 @@ import colors from '@shopify/cli-kit/node/colors'
2121

2222
const API_VERSION = '2026-01'
2323

24+
export function normalizeBulkOperationId(id: string): string {
25+
// If already a GID, return as-is
26+
if (id.startsWith('gid://')) {
27+
return id
28+
}
29+
30+
// If numeric, convert to GID
31+
if (/^\d+$/.test(id)) {
32+
return `gid://shopify/BulkOperation/${id}`
33+
}
34+
35+
// Otherwise return as-is (let API handle any errors)
36+
return id
37+
}
38+
39+
export function extractBulkOperationId(gid: string): string {
40+
// Extract the numeric ID from a GID like "gid://shopify/BulkOperation/123"
41+
const match = gid.match(/^gid:\/\/shopify\/BulkOperation\/(\d+)$/)
42+
return match?.[1] ?? gid
43+
}
44+
2445
interface GetBulkOperationStatusOptions {
2546
organization: Organization
2647
storeFqdn: string
@@ -104,7 +125,7 @@ export async function listBulkOperations(options: ListBulkOperationsOptions): Pr
104125
})
105126

106127
const operations = response.bulkOperations.nodes.map((operation) => ({
107-
id: operation.id,
128+
id: extractBulkOperationId(operation.id),
108129
status: formatStatus(operation.status),
109130
count: formatCount(operation.objectCount as number),
110131
dateCreated: formatDate(new Date(String(operation.createdAt))),

packages/app/src/cli/services/bulk-operations/execute-bulk-operation.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ describe('executeBulkOperation', () => {
365365

366366
expect(renderInfo).toHaveBeenCalledWith({
367367
headline: `Bulk operation ${createdBulkOperation.id} is still running in the background.`,
368-
body: ['Monitor its progress with:', {command: expect.stringContaining('shopify app bulk status')}],
368+
body: ['Monitor its progress with:\n', {command: expect.stringContaining('shopify app bulk status')}],
369369
})
370370
expect(downloadBulkOperationResults).not.toHaveBeenCalled()
371371
})

packages/app/src/cli/services/bulk-operations/execute-bulk-operation.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {runBulkOperationMutation} from './run-mutation.js'
33
import {watchBulkOperation, type BulkOperation} from './watch-bulk-operation.js'
44
import {formatBulkOperationStatus} from './format-bulk-operation-status.js'
55
import {downloadBulkOperationResults} from './download-bulk-operation-results.js'
6+
import {extractBulkOperationId} from './bulk-operation-status.js'
67
import {
78
createAdminSessionAsApp,
89
validateSingleOperation,
@@ -169,7 +170,10 @@ function validateGraphQLDocument(graphqlOperation: string, variablesJsonl?: stri
169170
}
170171

171172
function statusCommandHelpMessage(operationId: string): TokenItem {
172-
return ['Monitor its progress with:', {command: `shopify app bulk status --id="${operationId}}"`}]
173+
return [
174+
'Monitor its progress with:\n',
175+
{command: `shopify app bulk status --id=${extractBulkOperationId(operationId)}`},
176+
]
173177
}
174178

175179
function isMutation(graphqlOperation: string): boolean {

packages/cli/oclif.manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@
263263
"type": "option"
264264
},
265265
"id": {
266-
"description": "The bulk operation ID. If not provided, lists all bulk operations in the last 7 days.",
266+
"description": "The bulk operation ID (numeric ID or full GID). If not provided, lists all bulk operations in the last 7 days.",
267267
"env": "SHOPIFY_FLAG_ID",
268268
"hasDynamicHelp": false,
269269
"multiple": false,

0 commit comments

Comments
 (0)