Skip to content

Commit 2bb13db

Browse files
use our new watchBulkOperation rendering when --watch is provided
1 parent a532bc7 commit 2bb13db

File tree

5 files changed

+94
-29
lines changed

5 files changed

+94
-29
lines changed

packages/app/src/cli/commands/app/execute.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export default class Execute extends AppLinkedCommand {
3939
storeFqdn: store.shopDomain,
4040
query: flags.query,
4141
variables: flags.variables,
42+
watch: flags.watch,
4243
})
4344

4445
return {app: appContextResult.app}

packages/app/src/cli/flags.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,9 @@ export const bulkOperationFlags = {
5555
env: 'SHOPIFY_FLAG_STORE',
5656
parse: async (input) => normalizeStoreFqdn(input),
5757
}),
58+
watch: Flags.boolean({
59+
description: 'Wait for bulk operation results before exiting.',
60+
env: 'SHOPIFY_FLAG_WATCH',
61+
default: false,
62+
}),
5863
}

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

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import {executeBulkOperation} from './execute-bulk-operation.js'
22
import {runBulkOperationQuery} from './run-query.js'
33
import {runBulkOperationMutation} from './run-mutation.js'
4+
import {watchBulkOperation} from './watch-bulk-operation.js'
45
import {AppLinkedInterface} from '../../models/app/app.js'
56
import {renderSuccess, renderWarning} from '@shopify/cli-kit/node/ui'
67
import {ensureAuthenticatedAdmin} from '@shopify/cli-kit/node/session'
78
import {describe, test, expect, vi, beforeEach} from 'vitest'
89

910
vi.mock('./run-query.js')
1011
vi.mock('./run-mutation.js')
12+
vi.mock('./watch-bulk-operation.js')
1113
vi.mock('@shopify/cli-kit/node/ui')
1214
vi.mock('@shopify/cli-kit/node/session')
1315

@@ -135,10 +137,11 @@ describe('executeBulkOperation', () => {
135137
query,
136138
})
137139

138-
expect(renderSuccess).toHaveBeenCalledWith({
139-
headline: 'Bulk operation started successfully!',
140-
body: 'Congrats!',
141-
})
140+
expect(renderSuccess).toHaveBeenCalledWith(
141+
expect.objectContaining({
142+
headline: 'Bulk operation started.',
143+
}),
144+
)
142145
})
143146

144147
test('renders warning when user errors are present', async () => {
@@ -165,4 +168,36 @@ describe('executeBulkOperation', () => {
165168

166169
expect(renderSuccess).not.toHaveBeenCalled()
167170
})
171+
172+
test('executeBulkOperation waits until operation completes and prints final results when watch flag is true', async () => {
173+
const query = '{ products { edges { node { id } } } }'
174+
const mockResponse = {
175+
bulkOperation: successfulBulkOperation,
176+
userErrors: [],
177+
}
178+
const completedOperation = {
179+
...successfulBulkOperation,
180+
status: 'COMPLETED',
181+
url: 'https://example.com/download',
182+
objectCount: '650',
183+
}
184+
185+
vi.mocked(runBulkOperationQuery).mockResolvedValue(mockResponse as any)
186+
vi.mocked(watchBulkOperation).mockResolvedValue(completedOperation as any)
187+
188+
await executeBulkOperation({
189+
app: mockApp,
190+
storeFqdn,
191+
query,
192+
watch: true,
193+
})
194+
195+
expect(watchBulkOperation).toHaveBeenCalledWith(mockAdminSession, successfulBulkOperation.id)
196+
expect(renderSuccess).toHaveBeenCalledWith(
197+
expect.objectContaining({
198+
headline: expect.stringContaining('Bulk operation succeeded.'),
199+
body: expect.arrayContaining([expect.stringContaining('https://example.com/download')]),
200+
}),
201+
)
202+
})
168203
})

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

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import {runBulkOperationQuery} from './run-query.js'
22
import {runBulkOperationMutation} from './run-mutation.js'
3+
import {watchBulkOperation, type BulkOperation} from './watch-bulk-operation.js'
4+
import {formatBulkOperationStatus} from './format-bulk-operation-status.js'
35
import {AppLinkedInterface} from '../../models/app/app.js'
4-
import {renderSuccess, renderInfo, renderWarning} from '@shopify/cli-kit/node/ui'
6+
import {renderSuccess, renderInfo, renderError, renderWarning} from '@shopify/cli-kit/node/ui'
57
import {outputContent, outputToken} from '@shopify/cli-kit/node/output'
68
import {ensureAuthenticatedAdmin} from '@shopify/cli-kit/node/session'
79
import {AbortError} from '@shopify/cli-kit/node/error'
@@ -12,10 +14,11 @@ interface ExecuteBulkOperationInput {
1214
storeFqdn: string
1315
query: string
1416
variables?: string[]
17+
watch?: boolean
1518
}
1619

1720
export async function executeBulkOperation(input: ExecuteBulkOperationInput): Promise<void> {
18-
const {app, storeFqdn, query, variables} = input
21+
const {app, storeFqdn, query, variables, watch = false} = input
1922

2023
renderInfo({
2124
headline: 'Starting bulk operation.',
@@ -49,31 +52,45 @@ export async function executeBulkOperation(input: ExecuteBulkOperationInput): Pr
4952
return
5053
}
5154

52-
const result = bulkOperationResponse?.bulkOperation
53-
if (result) {
54-
const infoSections = [
55-
{
56-
title: 'Bulk Operation Created',
57-
body: [
58-
{
59-
list: {
60-
items: [
61-
outputContent`ID: ${outputToken.cyan(result.id)}`.value,
62-
outputContent`Status: ${outputToken.yellow(result.status)}`.value,
63-
outputContent`Created: ${outputToken.gray(String(result.createdAt))}`.value,
64-
],
65-
},
66-
},
67-
],
68-
},
69-
]
55+
const createdOperation = bulkOperationResponse?.bulkOperation
56+
if (createdOperation) {
57+
if (watch) {
58+
const finishedOperation = await watchBulkOperation(adminSession, createdOperation.id)
59+
renderBulkOperationResult(finishedOperation)
60+
} else {
61+
renderBulkOperationResult(createdOperation)
62+
}
63+
}
64+
}
7065

71-
renderInfo({customSections: infoSections})
66+
function renderBulkOperationResult(operation: BulkOperation): void {
67+
const headline = formatBulkOperationStatus(operation).value
68+
const items = [
69+
outputContent`ID: ${outputToken.cyan(operation.id)}`.value,
70+
outputContent`Status: ${outputToken.yellow(operation.status)}`.value,
71+
outputContent`Created at: ${outputToken.gray(String(operation.createdAt))}`.value,
72+
operation.completedAt
73+
? outputContent`Completed at: ${outputToken.gray(String(operation.completedAt))}`.value
74+
: null,
75+
].filter((item) => item !== null)
7276

73-
renderSuccess({
74-
headline: 'Bulk operation started successfully!',
75-
body: 'Congrats!',
76-
})
77+
const customSections = [{body: [{list: {items}}]}]
78+
79+
switch (operation.status) {
80+
case 'CREATED':
81+
renderSuccess({headline: 'Bulk operation started.', customSections})
82+
break
83+
case 'COMPLETED':
84+
if (operation.url) {
85+
const downloadMessage = outputContent`Download results ${outputToken.link('here.', operation.url)}`.value
86+
renderSuccess({headline, body: [downloadMessage], customSections})
87+
} else {
88+
renderSuccess({headline, customSections})
89+
}
90+
break
91+
default:
92+
renderError({headline, customSections})
93+
break
7794
}
7895
}
7996

packages/cli/oclif.manifest.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -901,6 +901,13 @@
901901
"hidden": false,
902902
"name": "verbose",
903903
"type": "boolean"
904+
},
905+
"watch": {
906+
"allowNo": false,
907+
"description": "Wait for bulk operation results before exiting.",
908+
"env": "SHOPIFY_FLAG_WATCH",
909+
"name": "watch",
910+
"type": "boolean"
904911
}
905912
},
906913
"hasDynamicHelp": false,

0 commit comments

Comments
 (0)