Skip to content

Commit 426706b

Browse files
Merge pull request #6695 from Shopify/poll_bulk_operations_faster
Improve bulk operation polling with faster initial checks and hide zero counts
2 parents bc6326c + 9d4706e commit 426706b

File tree

4 files changed

+52
-8
lines changed

4 files changed

+52
-8
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ describe('formatBulkOperationStatus', () => {
3434
expect(result.value).toContain('(42 objects written)')
3535
})
3636

37+
test('formats RUNNING status without object count when count is 0', () => {
38+
const result = formatBulkOperationStatus(createMockOperation({status: 'RUNNING', type: 'QUERY', objectCount: '0'}))
39+
expect(result.value).toBe('Bulk operation in progress')
40+
expect(result.value).not.toContain('objects read')
41+
})
42+
3743
test('formats CREATED status', () => {
3844
const result = formatBulkOperationStatus(createMockOperation({status: 'CREATED'}))
3945
expect(result.value).toBe('Starting')

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,13 @@ export function formatBulkOperationStatus(
66
): TokenizedString {
77
switch (operation.status) {
88
case 'RUNNING':
9-
return outputContent`Bulk operation in progress ${outputToken.gray(
10-
`(${String(operation.objectCount)} objects ${operation.type === 'MUTATION' ? 'written' : 'read'})`,
11-
)}`
9+
return outputContent`Bulk operation in progress${
10+
(operation.objectCount as number) > 0
11+
? outputToken.gray(
12+
` (${String(operation.objectCount)} objects ${operation.type === 'MUTATION' ? 'written' : 'read'})`,
13+
)
14+
: ''
15+
}`
1216
case 'CREATED':
1317
return outputContent`Starting`
1418
case 'COMPLETED':

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,34 @@ describe('watchBulkOperation', () => {
111111
)
112112
})
113113

114+
test('uses 1 second interval for first 10 polls, then 5 seconds', async () => {
115+
// Mock 12 running responses, then completed
116+
vi.mocked(adminRequestDoc)
117+
.mockResolvedValueOnce({bulkOperation: runningOperation})
118+
.mockResolvedValueOnce({bulkOperation: runningOperation})
119+
.mockResolvedValueOnce({bulkOperation: runningOperation})
120+
.mockResolvedValueOnce({bulkOperation: runningOperation})
121+
.mockResolvedValueOnce({bulkOperation: runningOperation})
122+
.mockResolvedValueOnce({bulkOperation: runningOperation})
123+
.mockResolvedValueOnce({bulkOperation: runningOperation})
124+
.mockResolvedValueOnce({bulkOperation: runningOperation})
125+
.mockResolvedValueOnce({bulkOperation: runningOperation})
126+
.mockResolvedValueOnce({bulkOperation: runningOperation})
127+
.mockResolvedValueOnce({bulkOperation: runningOperation})
128+
.mockResolvedValueOnce({bulkOperation: runningOperation})
129+
.mockResolvedValueOnce({bulkOperation: completedOperation})
130+
131+
await watchBulkOperation(mockAdminSession, operationId, abortController.signal, () => {})
132+
133+
// Verify first 10 polls use 1 second interval
134+
for (let i = 0; i < 10; i++) {
135+
expect(sleep).toHaveBeenNthCalledWith(i + 1, 1)
136+
}
137+
// Verify subsequent polls use 5 second interval
138+
expect(sleep).toHaveBeenNthCalledWith(11, 5)
139+
expect(sleep).toHaveBeenNthCalledWith(12, 5)
140+
})
141+
114142
describe('when signal is aborted during polling', () => {
115143
beforeEach(() => {
116144
let callCount = 0

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

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ import {renderSingleTask} from '@shopify/cli-kit/node/ui'
1111
import {AbortSignal} from '@shopify/cli-kit/node/abort'
1212

1313
const TERMINAL_STATUSES = ['COMPLETED', 'FAILED', 'CANCELED', 'EXPIRED']
14-
const POLL_INTERVAL_SECONDS = 5
14+
const INITIAL_POLL_INTERVAL_SECONDS = 1
15+
const REGULAR_POLL_INTERVAL_SECONDS = 5
16+
const INITIAL_POLL_COUNT = 10
1517
const API_VERSION = '2026-01'
1618

1719
export type BulkOperation = NonNullable<GetBulkOperationByIdQuery['bulkOperation']>
@@ -47,6 +49,8 @@ async function* pollBulkOperation(
4749
operationId: string,
4850
abortSignal: AbortSignal,
4951
): AsyncGenerator<BulkOperation, BulkOperation> {
52+
let pollCount = 0
53+
5054
while (true) {
5155
// eslint-disable-next-line no-await-in-loop
5256
const response = await fetchBulkOperation(adminSession, operationId)
@@ -63,11 +67,13 @@ async function* pollBulkOperation(
6367
yield latestOperationState
6468
}
6569

70+
pollCount++
71+
72+
// Use shorter interval for the first 10 polls, then switch to regular interval
73+
const pollInterval = pollCount <= INITIAL_POLL_COUNT ? INITIAL_POLL_INTERVAL_SECONDS : REGULAR_POLL_INTERVAL_SECONDS
74+
6675
// eslint-disable-next-line no-await-in-loop
67-
await Promise.race([
68-
sleep(POLL_INTERVAL_SECONDS),
69-
new Promise((resolve) => abortSignal.addEventListener('abort', resolve)),
70-
])
76+
await Promise.race([sleep(pollInterval), new Promise((resolve) => abortSignal.addEventListener('abort', resolve))])
7177
}
7278
}
7379

0 commit comments

Comments
 (0)