Skip to content

Commit fdf4250

Browse files
committed
fix: minor history fixes
1 parent f4c0418 commit fdf4250

File tree

3 files changed

+85
-35
lines changed

3 files changed

+85
-35
lines changed

.changeset/brave-cars-grin.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@smartthings/cli": patch
3+
---
4+
5+
handle epoch timestamps for history, honor `after` flag strictly in JSON and YAML output

packages/cli/src/__tests__/lib/commands/history-util.test.ts

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
jest.mock('inquirer')
2020

2121
describe('history-util', () => {
22-
describe('epochTime', () => {
22+
describe('toEpochTime', () => {
2323
it ('handles ISO input', () => {
2424
expect(toEpochTime('2022-08-01T22:41:42.559Z')).toBe(1659393702559)
2525
})
@@ -29,7 +29,11 @@ describe('history-util', () => {
2929
expect(toEpochTime('8/1/2022, 6:41:42 PM')).toBe(expected)
3030
})
3131

32-
it ('handles undefined input', () => {
32+
it ('handles input in millis since epoch', () => {
33+
expect(toEpochTime('1700596752000')).toBe(1700596752000)
34+
})
35+
36+
it('handles undefined input', () => {
3337
expect(toEpochTime(undefined)).toBeUndefined()
3438
})
3539
})
@@ -189,8 +193,8 @@ describe('history-util', () => {
189193
hasNext.mockReturnValueOnce(true)
190194
hasNext.mockReturnValueOnce(true)
191195
hasNext.mockReturnValueOnce(false)
192-
promptSpy.mockResolvedValueOnce({ more: '' })
193-
promptSpy.mockResolvedValueOnce({ more: 'y' })
196+
promptSpy.mockResolvedValueOnce({ more: true })
197+
promptSpy.mockResolvedValueOnce({ more: true })
194198

195199
await writeDeviceEventsTable(commandMock, dataMock)
196200

@@ -201,8 +205,8 @@ describe('history-util', () => {
201205

202206
it('returns next page until canceled', async () => {
203207
hasNext.mockReturnValue(true)
204-
promptSpy.mockResolvedValueOnce({ more: '' })
205-
promptSpy.mockResolvedValueOnce({ more: 'n' })
208+
promptSpy.mockResolvedValueOnce({ more: true })
209+
promptSpy.mockResolvedValueOnce({ more: false })
206210

207211
await writeDeviceEventsTable(commandMock, dataMock)
208212

@@ -231,8 +235,12 @@ describe('history-util', () => {
231235

232236
const params = { locationId: 'location-id', deviceId: 'device-id' }
233237
const items: DeviceActivity[] = []
234-
for (let index = 0; index < maxItemsPerRequest * maxRequestsBeforeWarning + 10; index++) {
235-
items.push({ deviceId: 'history-device-id', text: `item${index}` } as DeviceActivity)
238+
const totalNumItems = maxItemsPerRequest * maxRequestsBeforeWarning + 10
239+
for (let index = 0; index < totalNumItems; index++) {
240+
items.push({
241+
deviceId: 'history-device-id',
242+
text: `item${index}`,
243+
} as DeviceActivity)
236244
}
237245

238246
const promptMock = jest.mocked(inquirer.prompt)
@@ -353,5 +361,38 @@ describe('history-util', () => {
353361
expect(hasNextMock).toHaveBeenCalledTimes(6)
354362
expect(nextMock).toHaveBeenCalledTimes(6)
355363
})
364+
365+
it('stops paging when results are before specified after', async () => {
366+
const epochTimestamp = 1701097200 // 2023/11/27 9:00 a.m. CST
367+
const makeActivity = (index: number, epoch: number): DeviceActivity => ({
368+
deviceId: 'history-device-id',
369+
text: `item${index}`,
370+
epoch,
371+
} as DeviceActivity)
372+
const firstPage = [
373+
makeActivity(0, epochTimestamp + 600),
374+
makeActivity(1, epochTimestamp + 500),
375+
makeActivity(2, epochTimestamp + 400),
376+
]
377+
const secondPage = [
378+
makeActivity(3, epochTimestamp + 300),
379+
makeActivity(4, epochTimestamp + 200),
380+
makeActivity(5, epochTimestamp + 100),
381+
]
382+
const historyResponse = makeHistoryResponse(firstPage)
383+
historyDevicesMock.mockResolvedValueOnce(historyResponse)
384+
hasNextMock.mockReturnValue(true)
385+
nextMock.mockImplementationOnce(async () => historyResponse.items = secondPage)
386+
387+
const paramsWithAfter = { ...params, after: epochTimestamp + 350 }
388+
389+
const result = await getHistory(client, 300, 3, paramsWithAfter)
390+
expect(result).toStrictEqual(firstPage)
391+
392+
expect(historyDevicesMock).toHaveBeenCalledTimes(1)
393+
expect(historyDevicesMock).toHaveBeenCalledWith(paramsWithAfter)
394+
expect(hasNextMock).toHaveBeenCalledTimes(1)
395+
expect(nextMock).toHaveBeenCalledTimes(1)
396+
})
356397
})
357398
})

packages/cli/src/lib/commands/history-util.ts

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,15 @@ export const historyFlags = {
3636
}),
3737
}
3838

39-
export function toEpochTime(date?: string): number | undefined {
40-
if (date) {
41-
return new Date(date).getTime()
42-
}
43-
}
39+
export const toEpochTime = (date?: string): number | undefined =>
40+
date ? (date.match(/^\d+$/) ? Number(date) : new Date(date).getTime()) : undefined
4441

45-
export function sortEvents(list: DeviceActivity[]): DeviceActivity[] {
46-
return list.sort((a, b) => a.epoch === b.epoch ? 0 : (a.epoch < b.epoch ? 1 : -1))
47-
}
42+
export const sortEvents = (list: DeviceActivity[]): DeviceActivity[] => list.sort((a, b) => b.epoch - a.epoch)
4843

49-
export function getNextDeviceEvents(table: Table, items: DeviceActivity[], options: Partial<DeviceActivityOptions>): void {
44+
export const getNextDeviceEvents = (
45+
table: Table,
46+
items: DeviceActivity[],
47+
options: Partial<DeviceActivityOptions>): void => {
5048
for (const item of items) {
5149
const date = new Date(item.time)
5250
const value = JSON.stringify(item.value)
@@ -64,36 +62,37 @@ export function getNextDeviceEvents(table: Table, items: DeviceActivity[], optio
6462
}
6563
}
6664

67-
export async function writeDeviceEventsTable(
65+
export const writeDeviceEventsTable = async (
6866
command: SmartThingsCommandInterface,
6967
data: PaginatedList<DeviceActivity>,
70-
options?: Partial<DeviceActivityOptions>): Promise<void> {
71-
68+
options?: Partial<DeviceActivityOptions>): Promise<void> => {
7269
const opts = { includeName: false, utcTimeFormat: false, ...options }
7370
const head = options && options.includeName ?
7471
['Time', 'Device Name', 'Component', 'Capability', 'Attribute', 'Value'] :
7572
['Time', 'Component', 'Capability', 'Attribute', 'Value']
7673

7774
if (data.items) {
78-
let table = command.tableGenerator.newOutputTable({ isList: true, head })
75+
const table = command.tableGenerator.newOutputTable({ isList: true, head })
7976
getNextDeviceEvents(table, sortEvents(data.items), opts)
8077
process.stdout.write(table.toString())
8178

82-
let more = 'y'
83-
while (more.toLowerCase() !== 'n' && data.hasNext()) {
84-
more = (await inquirer.prompt({
85-
type: 'input',
79+
while (data.hasNext()) {
80+
const more = (await inquirer.prompt({
81+
type: 'confirm',
8682
name: 'more',
87-
message: 'Fetch more history records ([y]/n)?',
88-
})).more
89-
90-
if (more.toLowerCase() !== 'n') {
91-
table = command.tableGenerator.newOutputTable({ isList: true, head })
92-
await data.next()
93-
if (data.items.length) {
94-
getNextDeviceEvents(table, sortEvents(data.items), opts)
95-
process.stdout.write(table.toString())
96-
}
83+
message: 'Fetch more history records?',
84+
default: true,
85+
})).more as boolean
86+
87+
if (!more) {
88+
break
89+
}
90+
91+
const table = command.tableGenerator.newOutputTable({ isList: true, head })
92+
await data.next()
93+
if (data.items.length) {
94+
getNextDeviceEvents(table, sortEvents(data.items), opts)
95+
process.stdout.write(table.toString())
9796
}
9897
}
9998
}
@@ -132,6 +131,11 @@ export const getHistory = async (client: SmartThingsClient, limit: number, perRe
132131
const items: DeviceActivity[] = [...history.items]
133132
while (items.length < limit && history.hasNext()) {
134133
await history.next()
134+
// The API allows the user to continue to view history from before the specified "after"
135+
// with paging so we stop processing if we get items that come before the specified "after".
136+
if ((params.after && history.items[0].epoch <= params.after)) {
137+
break
138+
}
135139
items.push(...history.items.slice(0, limit - items.length))
136140
}
137141
return items

0 commit comments

Comments
 (0)