Skip to content

Commit a48bf33

Browse files
author
Keegan Irby
committed
Add LiveTail StatusBarItems
1 parent 5de67b5 commit a48bf33

File tree

3 files changed

+158
-20
lines changed

3 files changed

+158
-20
lines changed

packages/core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts

Lines changed: 82 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@ import { TailLogGroupWizard } from '../wizard/tailLogGroupWizard'
88
import { CancellationError } from '../../../shared/utilities/timeoutUtils'
99
import { LiveTailSession, LiveTailSessionConfiguration } from '../registry/liveTailSession'
1010
import { LiveTailSessionRegistry } from '../registry/liveTailSessionRegistry'
11-
import { LiveTailSessionLogEvent, StartLiveTailResponseStream } from '@aws-sdk/client-cloudwatch-logs'
12-
import { ToolkitError } from '../../../shared'
11+
import {
12+
LiveTailSessionLogEvent,
13+
LiveTailSessionUpdate,
14+
StartLiveTailResponseStream,
15+
} from '@aws-sdk/client-cloudwatch-logs'
16+
import { convertToTimeString, globals, ToolkitError } from '../../../shared'
1317

1418
export async function tailLogGroup(
1519
registry: LiveTailSessionRegistry,
@@ -35,13 +39,17 @@ export async function tailLogGroup(
3539
registry.set(session.uri, session)
3640

3741
const document = await prepareDocument(session)
38-
registerTabChangeCallback(session, registry, document)
42+
const timer = startSessionTimer(session)
43+
hideShowStatusBarItemsOnActiveEditor(session, document)
44+
registerTabChangeCallback(session, registry, document, timer)
45+
3946
const stream = await session.startLiveTailSession()
4047

41-
await handleSessionStream(stream, document, session)
48+
await handleSessionStream(stream, document, session, timer)
4249
}
4350

44-
export function closeSession(sessionUri: vscode.Uri, registry: LiveTailSessionRegistry) {
51+
export function closeSession(sessionUri: vscode.Uri, registry: LiveTailSessionRegistry, timer: NodeJS.Timer) {
52+
globals.clock.clearInterval(timer)
4553
const session = registry.get(sessionUri)
4654
if (session === undefined) {
4755
throw new ToolkitError(`No LiveTail session found for URI: ${sessionUri.toString()}`)
@@ -63,27 +71,35 @@ async function prepareDocument(session: LiveTailSession): Promise<vscode.TextDoc
6371
await clearDocument(textDocument)
6472
await vscode.window.showTextDocument(textDocument, { preview: false })
6573
await vscode.languages.setTextDocumentLanguage(textDocument, 'log')
74+
session.showStatusBarItems()
6675
return textDocument
6776
}
6877

6978
async function handleSessionStream(
7079
stream: AsyncIterable<StartLiveTailResponseStream>,
7180
document: vscode.TextDocument,
72-
session: LiveTailSession
81+
session: LiveTailSession,
82+
timer: NodeJS.Timer
7383
) {
74-
for await (const event of stream) {
75-
if (event.sessionUpdate !== undefined && event.sessionUpdate.sessionResults !== undefined) {
76-
const formattedLogEvents = event.sessionUpdate.sessionResults.map<string>((logEvent) =>
77-
formatLogEvent(logEvent)
78-
)
79-
if (formattedLogEvents.length !== 0) {
80-
//Determine should scroll before adding new lines to doc because adding large
81-
//amount of new lines can push bottom of file out of view before scrolling.
82-
const editorsToScroll = getTextEditorsToScroll(document)
83-
await updateTextDocumentWithNewLogEvents(formattedLogEvents, document, session.maxLines)
84-
editorsToScroll.forEach(scrollTextEditorToBottom)
84+
try {
85+
for await (const event of stream) {
86+
if (event.sessionUpdate !== undefined && event.sessionUpdate.sessionResults !== undefined) {
87+
const formattedLogEvents = event.sessionUpdate.sessionResults.map<string>((logEvent) =>
88+
formatLogEvent(logEvent)
89+
)
90+
if (formattedLogEvents.length !== 0) {
91+
//Determine should scroll before adding new lines to doc because adding large
92+
//amount of new lines can push bottom of file out of view before scrolling.
93+
const editorsToScroll = getTextEditorsToScroll(document)
94+
await updateTextDocumentWithNewLogEvents(formattedLogEvents, document, session.maxLines)
95+
editorsToScroll.forEach(scrollTextEditorToBottom)
96+
}
97+
updateStatusBarItemsOnStreamEvent(session, event.sessionUpdate)
8598
}
8699
}
100+
} catch (err) {
101+
globals.clock.clearInterval(timer)
102+
throw err
87103
}
88104
}
89105

@@ -147,6 +163,52 @@ function trimOldestLines(
147163
edit.delete(document.uri, range)
148164
}
149165

166+
function updateStatusBarItemsOnStreamEvent(session: LiveTailSession, event: LiveTailSessionUpdate) {
167+
updateIsSampledStatusBar(session.statusBarItems.isSampled, event)
168+
updateEventRateStatusBar(session.statusBarItems.eventRate, event)
169+
}
170+
171+
function updateIsSampledStatusBar(isSampledStatusBarItem: vscode.StatusBarItem, event: LiveTailSessionUpdate) {
172+
let isSampled: boolean
173+
//sessionMetadata is expected to always be present on a LiveTail stream update
174+
if (event.sessionMetadata === undefined || event.sessionMetadata.sampled === undefined) {
175+
isSampled = false
176+
} else {
177+
isSampled = event.sessionMetadata.sampled
178+
}
179+
isSampledStatusBarItem.text = `Sampled: ${isSampled ? 'Yes' : 'No'}`
180+
return isSampledStatusBarItem
181+
}
182+
183+
function updateEventRateStatusBar(eventRateStatusBarItem: vscode.StatusBarItem, event: LiveTailSessionUpdate) {
184+
let numEvents
185+
if (event.sessionResults === undefined) {
186+
numEvents = 0
187+
} else {
188+
numEvents = event.sessionResults.length
189+
}
190+
eventRateStatusBarItem.text = `${numEvents} events/sec.`
191+
return eventRateStatusBarItem
192+
}
193+
194+
function hideShowStatusBarItemsOnActiveEditor(session: LiveTailSession, document: vscode.TextDocument) {
195+
vscode.window.onDidChangeActiveTextEditor((editor) => {
196+
if (editor?.document === document) {
197+
session.showStatusBarItems()
198+
} else {
199+
session.hideStatusBarItems()
200+
}
201+
})
202+
}
203+
204+
function startSessionTimer(session: LiveTailSession): NodeJS.Timer {
205+
return globals.clock.setInterval(() => {
206+
const elapsedTime = session.getLiveTailSessionDuration()
207+
const timeString = convertToTimeString(elapsedTime)
208+
session.statusBarItems.sessionTimer.text = `LiveTail Session Timer: ${timeString}`
209+
}, 500)
210+
}
211+
150212
/**
151213
* The LiveTail session should be automatically closed if the user does not have the session's
152214
* document in any Tab in their editor.
@@ -162,12 +224,13 @@ function trimOldestLines(
162224
function registerTabChangeCallback(
163225
session: LiveTailSession,
164226
registry: LiveTailSessionRegistry,
165-
document: vscode.TextDocument
227+
document: vscode.TextDocument,
228+
timer: NodeJS.Timer
166229
) {
167230
vscode.window.tabGroups.onDidChangeTabs((tabEvent) => {
168231
const isOpen = isLiveTailSessionOpenInAnyTab(session)
169232
if (!isOpen) {
170-
closeSession(session.uri, registry)
233+
closeSession(session.uri, registry, timer)
171234
void clearDocument(document)
172235
}
173236
})

packages/core/src/awsService/cloudWatchLogs/registry/liveTailSession.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ import { Settings, ToolkitError } from '../../../shared'
1414
import { createLiveTailURIFromArgs } from './liveTailSessionRegistry'
1515
import { getUserAgent } from '../../../shared/telemetry/util'
1616

17+
export type LiveTailSessionStatusBarItems = {
18+
isSampled: vscode.StatusBarItem
19+
eventRate: vscode.StatusBarItem
20+
sessionTimer: vscode.StatusBarItem
21+
}
22+
1723
export type LiveTailSessionConfiguration = {
1824
logGroupName: string
1925
logStreamFilter?: LogStreamFilterResponse
@@ -33,6 +39,9 @@ export class LiveTailSession {
3339
private logEventFilterPattern?: string
3440
private _maxLines: number
3541
private _uri: vscode.Uri
42+
private _statusBarItems: LiveTailSessionStatusBarItems
43+
private startTime: number | undefined
44+
private endTime: number | undefined
3645

3746
static settings = new CloudWatchLogsSettings(Settings.instance)
3847

@@ -48,6 +57,7 @@ export class LiveTailSession {
4857
}
4958
this._maxLines = LiveTailSession.settings.get('limit', 10000)
5059
this._uri = createLiveTailURIFromArgs(configuration)
60+
this._statusBarItems = this.createStatusBarItems()
5161
}
5262

5363
public get maxLines() {
@@ -62,6 +72,10 @@ export class LiveTailSession {
6272
return this._logGroupName
6373
}
6474

75+
public get statusBarItems(): LiveTailSessionStatusBarItems {
76+
return this._statusBarItems
77+
}
78+
6579
public async startLiveTailSession(): Promise<AsyncIterable<StartLiveTailResponseStream>> {
6680
const command = this.buildStartLiveTailCommand()
6781
try {
@@ -71,17 +85,33 @@ export class LiveTailSession {
7185
if (!commandOutput.responseStream) {
7286
throw new ToolkitError('LiveTail session response stream is undefined.')
7387
}
88+
this.startTime = Date.now()
89+
this.endTime = undefined
7490
return commandOutput.responseStream
7591
} catch (e) {
7692
throw new ToolkitError('Encountered error while trying to start LiveTail session.')
7793
}
7894
}
7995

8096
public stopLiveTailSession() {
97+
this.endTime = Date.now()
98+
this.disposeStatusBarItems()
8199
this.liveTailClient.abortController.abort()
82100
this.liveTailClient.cwlClient.destroy()
83101
}
84102

103+
public getLiveTailSessionDuration(): number {
104+
//Never started
105+
if (this.startTime === undefined) {
106+
return 0
107+
}
108+
//Currently running
109+
if (this.endTime === undefined) {
110+
return Date.now() - this.startTime
111+
}
112+
return this.endTime - this.startTime
113+
}
114+
85115
private buildStartLiveTailCommand(): StartLiveTailCommand {
86116
let logStreamNamePrefix = undefined
87117
let logStreamName = undefined
@@ -102,4 +132,30 @@ export class LiveTailSession {
102132
logEventFilterPattern: this.logEventFilterPattern ? this.logEventFilterPattern : undefined,
103133
})
104134
}
135+
136+
private createStatusBarItems(): LiveTailSessionStatusBarItems {
137+
return {
138+
sessionTimer: vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 0),
139+
eventRate: vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 1),
140+
isSampled: vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 2),
141+
}
142+
}
143+
144+
public showStatusBarItems() {
145+
this.statusBarItems.eventRate.show()
146+
this.statusBarItems.isSampled.show()
147+
this.statusBarItems.sessionTimer.show()
148+
}
149+
150+
public hideStatusBarItems() {
151+
this.statusBarItems.eventRate.hide()
152+
this.statusBarItems.isSampled.hide()
153+
this.statusBarItems.sessionTimer.hide()
154+
}
155+
156+
private disposeStatusBarItems() {
157+
this.statusBarItems.eventRate.dispose()
158+
this.statusBarItems.isSampled.dispose()
159+
this.statusBarItems.sessionTimer.dispose()
160+
}
105161
}

packages/core/src/test/awsService/cloudWatchLogs/commands/tailLogGroup.test.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55

66
import * as sinon from 'sinon'
7+
import * as FakeTimers from '@sinonjs/fake-timers'
78
import * as vscode from 'vscode'
89

910
import assert from 'assert'
@@ -18,6 +19,7 @@ import {
1819
} from '../../../../awsService/cloudWatchLogs/wizard/tailLogGroupWizard'
1920
import { getTestWindow } from '../../../shared/vscode/window'
2021
import { CloudWatchLogsSettings } from '../../../../awsService/cloudWatchLogs/cloudWatchLogsUtils'
22+
import { installFakeClock } from '../../../testUtil'
2123

2224
describe('TailLogGroup', function () {
2325
const testLogGroup = 'test-log-group'
@@ -31,11 +33,22 @@ describe('TailLogGroup', function () {
3133
let cloudwatchSettingsSpy: sinon.SinonSpy
3234
let wizardSpy: sinon.SinonSpy
3335

36+
let clock: FakeTimers.InstalledClock
37+
38+
before(function () {
39+
clock = installFakeClock()
40+
})
41+
3442
beforeEach(function () {
43+
clock.reset()
3544
sandbox = sinon.createSandbox()
3645
registry = new LiveTailSessionRegistry()
3746
})
3847

48+
after(function () {
49+
clock.uninstall()
50+
})
51+
3952
afterEach(function () {
4053
sandbox.restore()
4154
})
@@ -112,15 +125,18 @@ describe('TailLogGroup', function () {
112125
.callsFake(async function () {
113126
return
114127
})
128+
// const fakeClock = installFakeClock()
129+
const timer = setInterval(() => {}, 1000)
115130
const session = new LiveTailSession({
116131
logGroupName: testLogGroup,
117132
region: testRegion,
118133
})
119134
registry.set(session.uri, session)
120135

121-
closeSession(session.uri, registry)
136+
closeSession(session.uri, registry, timer)
122137
assert.strictEqual(0, registry.size)
123138
assert.strictEqual(true, stopLiveTailSessionSpy.calledOnce)
139+
assert.strictEqual(0, clock.countTimers())
124140
})
125141

126142
it('clearDocument clears all text from document', async function () {
@@ -172,6 +188,9 @@ describe('TailLogGroup', function () {
172188
const updateFrames: StartLiveTailResponseStream[] = logEvents.map((event) => {
173189
return {
174190
sessionUpdate: {
191+
sessionMetadata: {
192+
sampled: false,
193+
},
175194
sessionResults: [event],
176195
},
177196
}

0 commit comments

Comments
 (0)