From 8ec4e0143202dfadd7eb353b4137cb829c01e230 Mon Sep 17 00:00:00 2001 From: Nick Wesselman <27013789+nickwesselman@users.noreply.github.com> Date: Sun, 7 Dec 2025 21:33:49 -0500 Subject: [PATCH 1/2] poll more frequently the first 10 times --- .../watch-bulk-operation.test.ts | 28 +++++++++++++++++++ .../bulk-operations/watch-bulk-operation.ts | 16 +++++++---- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/packages/app/src/cli/services/bulk-operations/watch-bulk-operation.test.ts b/packages/app/src/cli/services/bulk-operations/watch-bulk-operation.test.ts index 510f48633e..ffdbb64546 100644 --- a/packages/app/src/cli/services/bulk-operations/watch-bulk-operation.test.ts +++ b/packages/app/src/cli/services/bulk-operations/watch-bulk-operation.test.ts @@ -111,6 +111,34 @@ describe('watchBulkOperation', () => { ) }) + test('uses 1 second interval for first 10 polls, then 5 seconds', async () => { + // Mock 12 running responses, then completed + vi.mocked(adminRequestDoc) + .mockResolvedValueOnce({bulkOperation: runningOperation}) + .mockResolvedValueOnce({bulkOperation: runningOperation}) + .mockResolvedValueOnce({bulkOperation: runningOperation}) + .mockResolvedValueOnce({bulkOperation: runningOperation}) + .mockResolvedValueOnce({bulkOperation: runningOperation}) + .mockResolvedValueOnce({bulkOperation: runningOperation}) + .mockResolvedValueOnce({bulkOperation: runningOperation}) + .mockResolvedValueOnce({bulkOperation: runningOperation}) + .mockResolvedValueOnce({bulkOperation: runningOperation}) + .mockResolvedValueOnce({bulkOperation: runningOperation}) + .mockResolvedValueOnce({bulkOperation: runningOperation}) + .mockResolvedValueOnce({bulkOperation: runningOperation}) + .mockResolvedValueOnce({bulkOperation: completedOperation}) + + await watchBulkOperation(mockAdminSession, operationId, abortController.signal, () => {}) + + // Verify first 10 polls use 1 second interval + for (let i = 0; i < 10; i++) { + expect(sleep).toHaveBeenNthCalledWith(i + 1, 1) + } + // Verify subsequent polls use 5 second interval + expect(sleep).toHaveBeenNthCalledWith(11, 5) + expect(sleep).toHaveBeenNthCalledWith(12, 5) + }) + describe('when signal is aborted during polling', () => { beforeEach(() => { let callCount = 0 diff --git a/packages/app/src/cli/services/bulk-operations/watch-bulk-operation.ts b/packages/app/src/cli/services/bulk-operations/watch-bulk-operation.ts index 9af33fe2b7..7fdbd9361b 100644 --- a/packages/app/src/cli/services/bulk-operations/watch-bulk-operation.ts +++ b/packages/app/src/cli/services/bulk-operations/watch-bulk-operation.ts @@ -11,7 +11,9 @@ import {renderSingleTask} from '@shopify/cli-kit/node/ui' import {AbortSignal} from '@shopify/cli-kit/node/abort' const TERMINAL_STATUSES = ['COMPLETED', 'FAILED', 'CANCELED', 'EXPIRED'] -const POLL_INTERVAL_SECONDS = 5 +const INITIAL_POLL_INTERVAL_SECONDS = 1 +const REGULAR_POLL_INTERVAL_SECONDS = 5 +const INITIAL_POLL_COUNT = 10 const API_VERSION = '2026-01' export type BulkOperation = NonNullable @@ -47,6 +49,8 @@ async function* pollBulkOperation( operationId: string, abortSignal: AbortSignal, ): AsyncGenerator { + let pollCount = 0 + while (true) { // eslint-disable-next-line no-await-in-loop const response = await fetchBulkOperation(adminSession, operationId) @@ -63,11 +67,13 @@ async function* pollBulkOperation( yield latestOperationState } + pollCount++ + + // Use shorter interval for the first 10 polls, then switch to regular interval + const pollInterval = pollCount <= INITIAL_POLL_COUNT ? INITIAL_POLL_INTERVAL_SECONDS : REGULAR_POLL_INTERVAL_SECONDS + // eslint-disable-next-line no-await-in-loop - await Promise.race([ - sleep(POLL_INTERVAL_SECONDS), - new Promise((resolve) => abortSignal.addEventListener('abort', resolve)), - ]) + await Promise.race([sleep(pollInterval), new Promise((resolve) => abortSignal.addEventListener('abort', resolve))]) } } From 9d4706ed0101aaf176b9a008cae11cef41e907a0 Mon Sep 17 00:00:00 2001 From: Nick Wesselman <27013789+nickwesselman@users.noreply.github.com> Date: Sun, 7 Dec 2025 21:34:02 -0500 Subject: [PATCH 2/2] Don't display object count of 0 --- .../format-bulk-operation-status.test.ts | 6 ++++++ .../bulk-operations/format-bulk-operation-status.ts | 10 +++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/app/src/cli/services/bulk-operations/format-bulk-operation-status.test.ts b/packages/app/src/cli/services/bulk-operations/format-bulk-operation-status.test.ts index f20ce88d58..59e19d9b82 100644 --- a/packages/app/src/cli/services/bulk-operations/format-bulk-operation-status.test.ts +++ b/packages/app/src/cli/services/bulk-operations/format-bulk-operation-status.test.ts @@ -34,6 +34,12 @@ describe('formatBulkOperationStatus', () => { expect(result.value).toContain('(42 objects written)') }) + test('formats RUNNING status without object count when count is 0', () => { + const result = formatBulkOperationStatus(createMockOperation({status: 'RUNNING', type: 'QUERY', objectCount: '0'})) + expect(result.value).toBe('Bulk operation in progress') + expect(result.value).not.toContain('objects read') + }) + test('formats CREATED status', () => { const result = formatBulkOperationStatus(createMockOperation({status: 'CREATED'})) expect(result.value).toBe('Starting') diff --git a/packages/app/src/cli/services/bulk-operations/format-bulk-operation-status.ts b/packages/app/src/cli/services/bulk-operations/format-bulk-operation-status.ts index 90e5e9871f..b7c997cd71 100644 --- a/packages/app/src/cli/services/bulk-operations/format-bulk-operation-status.ts +++ b/packages/app/src/cli/services/bulk-operations/format-bulk-operation-status.ts @@ -6,9 +6,13 @@ export function formatBulkOperationStatus( ): TokenizedString { switch (operation.status) { case 'RUNNING': - return outputContent`Bulk operation in progress ${outputToken.gray( - `(${String(operation.objectCount)} objects ${operation.type === 'MUTATION' ? 'written' : 'read'})`, - )}` + return outputContent`Bulk operation in progress${ + (operation.objectCount as number) > 0 + ? outputToken.gray( + ` (${String(operation.objectCount)} objects ${operation.type === 'MUTATION' ? 'written' : 'read'})`, + ) + : '' + }` case 'CREATED': return outputContent`Starting` case 'COMPLETED':