Skip to content

Commit 40308af

Browse files
move timeAgo into string.ts so it can be reused
1 parent 4efde07 commit 40308af

File tree

4 files changed

+67
-40
lines changed

4 files changed

+67
-40
lines changed

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

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -115,28 +115,6 @@ describe('getBulkOperationStatus', () => {
115115
})
116116

117117
describe('time formatting', () => {
118-
test('does not pluralize time unit when it is 1', async () => {
119-
vi.mocked(adminRequestDoc).mockResolvedValue(
120-
mockBulkOperation({completedAt: new Date(Date.now() - 60000).toISOString()}),
121-
)
122-
123-
const output = mockAndCaptureOutput()
124-
await getBulkOperationStatus({storeFqdn, operationId})
125-
126-
expect(output.output()).toMatch(/1 minute ago/)
127-
})
128-
129-
test('pluralizes time unit when it is greater than 1', async () => {
130-
vi.mocked(adminRequestDoc).mockResolvedValue(
131-
mockBulkOperation({createdAt: new Date(Date.now() - 180000).toISOString()}),
132-
)
133-
134-
const output = mockAndCaptureOutput()
135-
await getBulkOperationStatus({storeFqdn, operationId})
136-
137-
expect(output.output()).toMatch(/3 minutes ago/)
138-
})
139-
140118
test('uses "Started" for running operations', async () => {
141119
vi.mocked(adminRequestDoc).mockResolvedValue(mockBulkOperation({status: 'RUNNING'}))
142120

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

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {renderInfo, renderSuccess, renderError} from '@shopify/cli-kit/node/ui'
88
import {outputContent, outputToken} from '@shopify/cli-kit/node/output'
99
import {ensureAuthenticatedAdmin} from '@shopify/cli-kit/node/session'
1010
import {adminRequestDoc} from '@shopify/cli-kit/node/api/admin'
11+
import {timeAgo} from '@shopify/cli-kit/common/string'
1112

1213
const API_VERSION = '2026-01'
1314

@@ -64,21 +65,3 @@ function formatTimeDifference(createdAt: unknown, completedAt?: unknown): string
6465
return `Started ${timeAgo(new Date(String(createdAt)), now)}`
6566
}
6667
}
67-
68-
function timeAgo(from: Date, to: Date): string {
69-
const seconds = Math.floor((to.getTime() - from.getTime()) / 1000)
70-
if (seconds < 60) return `${formatTimeUnit(seconds, 'second')} ago`
71-
72-
const minutes = Math.floor(seconds / 60)
73-
if (minutes < 60) return `${formatTimeUnit(minutes, 'minute')} ago`
74-
75-
const hours = Math.floor(minutes / 60)
76-
if (hours < 24) return `${formatTimeUnit(hours, 'hour')} ago`
77-
78-
const days = Math.floor(hours / 24)
79-
return `${formatTimeUnit(days, 'day')} ago`
80-
}
81-
82-
function formatTimeUnit(count: number, unit: string): string {
83-
return `${count} ${unit}${count === 1 ? '' : 's'}`
84-
}

packages/cli-kit/src/public/common/string.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
linesToColumns,
77
normalizeDelimitedString,
88
pluralize,
9+
timeAgo,
910
tryParseInt,
1011
} from './string.js'
1112
import {describe, expect, test} from 'vitest'
@@ -200,3 +201,43 @@ describe('normalizeDelimitedString', () => {
200201
expect(result).toEqual('read_products,write_products')
201202
})
202203
})
204+
205+
describe('timeAgo', () => {
206+
const second = 1000
207+
const minute = 60 * second
208+
const hour = 60 * minute
209+
const day = 24 * hour
210+
const now = new Date(0)
211+
212+
test('formats seconds (singular)', () => {
213+
expect(timeAgo(new Date(now.getTime() - second), now)).toBe('1 second ago')
214+
})
215+
216+
test('formats seconds (plural)', () => {
217+
expect(timeAgo(new Date(now.getTime() - 30 * second), now)).toBe('30 seconds ago')
218+
})
219+
220+
test('formats minutes (singular)', () => {
221+
expect(timeAgo(new Date(now.getTime() - minute), now)).toBe('1 minute ago')
222+
})
223+
224+
test('formats minutes (plural)', () => {
225+
expect(timeAgo(new Date(now.getTime() - 3 * minute), now)).toBe('3 minutes ago')
226+
})
227+
228+
test('formats hours (singular)', () => {
229+
expect(timeAgo(new Date(now.getTime() - hour), now)).toBe('1 hour ago')
230+
})
231+
232+
test('formats hours (plural)', () => {
233+
expect(timeAgo(new Date(now.getTime() - 5 * hour), now)).toBe('5 hours ago')
234+
})
235+
236+
test('formats days (singular)', () => {
237+
expect(timeAgo(new Date(now.getTime() - day), now)).toBe('1 day ago')
238+
})
239+
240+
test('formats days (plural)', () => {
241+
expect(timeAgo(new Date(now.getTime() - 7 * day), now)).toBe('7 days ago')
242+
})
243+
})

packages/cli-kit/src/public/common/string.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,3 +418,28 @@ export function normalizeDelimitedString(delimitedString?: string, delimiter = '
418418

419419
return uniqueSortedItems.join(delimiter)
420420
}
421+
422+
/**
423+
* Given two dates, it returns a human-readable string representing the time elapsed between them.
424+
*
425+
* @param from - Start date.
426+
* @param to - End date.
427+
* @returns A string like "5 minutes ago" or "2 days ago".
428+
*/
429+
export function timeAgo(from: Date, to: Date): string {
430+
const seconds = Math.floor((to.getTime() - from.getTime()) / 1000)
431+
if (seconds < 60) return `${formatTimeUnit(seconds, 'second')} ago`
432+
433+
const minutes = Math.floor(seconds / 60)
434+
if (minutes < 60) return `${formatTimeUnit(minutes, 'minute')} ago`
435+
436+
const hours = Math.floor(minutes / 60)
437+
if (hours < 24) return `${formatTimeUnit(hours, 'hour')} ago`
438+
439+
const days = Math.floor(hours / 24)
440+
return `${formatTimeUnit(days, 'day')} ago`
441+
}
442+
443+
function formatTimeUnit(count: number, unit: string): string {
444+
return `${count} ${unit}${count === 1 ? '' : 's'}`
445+
}

0 commit comments

Comments
 (0)