Skip to content

Commit be7631d

Browse files
authored
Allow viewing multiple session logs per PR (#7113)
- Adds `View session` links to all sessions in a PR - Show session name in the view - Styling polish
1 parent 9c9736b commit be7631d

10 files changed

Lines changed: 76 additions & 54 deletions

File tree

src/common/timelineEvent.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,12 +160,16 @@ export interface SessionPullInfo {
160160
pullId: number;
161161
}
162162

163+
export interface SessionLinkInfo extends SessionPullInfo {
164+
sessionIndex: number;
165+
}
166+
163167
export interface CopilotStartedEvent {
164168
id: string;
165169
event: EventType.CopilotStarted;
166170
createdAt: string;
167171
onBehalfOf: IAccount;
168-
sessionLink?: SessionPullInfo;
172+
sessionLink?: SessionLinkInfo;
169173
}
170174

171175
export interface CopilotFinishedEvent {
@@ -180,7 +184,7 @@ export interface CopilotFinishedErrorEvent {
180184
event: EventType.CopilotFinishedError;
181185
createdAt: string;
182186
onBehalfOf: IAccount;
183-
sessionLink: SessionPullInfo;
187+
sessionLink: SessionLinkInfo;
184188
}
185189

186190
export type TimelineEvent = CommitEvent | ReviewEvent | CommentEvent | NewCommitsSinceReviewEvent | MergedEvent | AssignEvent | UnassignEvent | HeadRefDeleteEvent | CrossReferencedEvent | ClosedEvent | ReopenedEvent | CopilotStartedEvent | CopilotFinishedEvent | CopilotFinishedErrorEvent;

src/github/copilotRemoteAgent.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -376,35 +376,34 @@ export class CopilotRemoteAgentManager extends Disposable {
376376
return await capi.getLogsFromZipUrl(lastRun.logs_url);
377377
}
378378

379-
async getMostRecentSessionLogsFromPullRequest(pullRequestId: number, completedOnly = true): Promise<IAPISessionLogs | undefined> {
379+
async getSessionLogFromPullRequest(pullRequestId: number, sessionIndex = 0, completedOnly = true): Promise<IAPISessionLogs | undefined> {
380380
const capi = await this.copilotApi;
381381
if (!capi) {
382382
return undefined;
383383
}
384384

385385
const sessions = await capi.getAllSessions(pullRequestId);
386-
const mostRecentSession = sessions.filter(s => !completedOnly || s.state === 'completed').at(0);
387-
if (!mostRecentSession) {
386+
const session = sessions.filter(s => !completedOnly || s.state === 'completed').at(sessionIndex);
387+
if (!session) {
388388
return undefined;
389389
}
390390

391-
const logs = await capi.getLogsFromSession(mostRecentSession.id);
392-
return { sessionId: mostRecentSession.id, logs };
391+
const logs = await capi.getLogsFromSession(session.id);
392+
return { sessionId: session.id, logs };
393393
}
394394

395-
async getSessionUrlFromPullRequest(pullRequestId: number | undefined): Promise<string | undefined> {
395+
async getSessionUrlFromPullRequest(pullRequestId: number, sessionIndex = 0, completedOnly = true): Promise<string | undefined> {
396396
const capi = await this.copilotApi;
397397
if (!capi) {
398398
return undefined;
399399
}
400400

401401
const sessions = await capi.getAllSessions(pullRequestId);
402-
const completedSessions = sessions.filter(s => s.state === 'completed');
403-
if (completedSessions.length === 0) {
402+
const session = sessions.filter(s => !completedOnly || s.state === 'completed').at(sessionIndex);
403+
if (!session) {
404404
return undefined;
405405
}
406-
const mostRecentSession = this.getLatestRun(completedSessions);
407-
return `${this.workflowRunUrlBase}${mostRecentSession.workflow_run_id}`;
406+
return `${this.workflowRunUrlBase}${session.workflow_run_id}`;
408407
}
409408

410409
async getSessionLogsFromSessionId(sessionId: string): Promise<IAPISessionLogs> {

src/github/pullRequestOverview.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { disposeAll } from '../common/lifecycle';
1313
import Logger from '../common/logger';
1414
import { DEFAULT_MERGE_METHOD, PR_SETTINGS_NAMESPACE } from '../common/settingKeys';
1515
import { ITelemetry } from '../common/telemetry';
16-
import { EventType, ReviewEvent, SessionPullInfo, TimelineEvent } from '../common/timelineEvent';
16+
import { EventType, ReviewEvent, SessionLinkInfo, TimelineEvent } from '../common/timelineEvent';
1717
import { asPromise, formatError } from '../common/utils';
1818
import { IRequestMessage, PULL_REQUEST_OVERVIEW_VIEW_TYPE } from '../common/webview';
1919
import { SessionLogViewManager } from '../view/sessionLogView';
@@ -360,7 +360,7 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode
360360
case 'pr.revert':
361361
return this.revert(message);
362362
case 'pr.open-session-log':
363-
return this.openSessionLog(message.args.link);
363+
return this.openSessionLog(message);
364364
case 'pr.cancel-coding-agent':
365365
return this.cancelCodingAgent(message);
366366
}
@@ -458,9 +458,9 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode
458458
}
459459
}
460460

461-
private async openSessionLog(_message: IRequestMessage<{ link: SessionPullInfo }>): Promise<void> {
461+
private async openSessionLog(_message: IRequestMessage<{ link: SessionLinkInfo }>): Promise<void> {
462462
try {
463-
SessionLogViewManager.instance?.openForPull(this._item);
463+
await SessionLogViewManager.instance?.openForPull(this._item, _message.args.link);
464464
} catch (e) {
465465
Logger.error(`Open session log view failed: ${formatError(e)}`, PullRequestOverviewPanel.ID);
466466
}

src/github/utils.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,23 +1002,28 @@ export function parseSelectRestTimelineEvents(
10021002
events: OctokitCommon.ListEventsForTimelineResponse[]
10031003
): Common.TimelineEvent[] {
10041004
const parsedEvents: Common.TimelineEvent[] = [];
1005-
const sessionLink: Common.SessionPullInfo = {
1005+
1006+
const prSessionLink = {
10061007
host: issueModel.githubRepository.remote.gitProtocol.host,
10071008
owner: issueModel.githubRepository.remote.owner,
10081009
repo: issueModel.githubRepository.remote.repositoryName,
1009-
pullId: issueModel.number
1010+
pullId: issueModel.number,
10101011
};
1011-
let indexLastStart = -1;
1012+
1013+
let sessionIndex = 0;
10121014
for (const event of events) {
10131015
const eventNode = event as { created_at?: string; node_id?: string; actor: RestAccount };
10141016
if (eventNode.created_at && eventNode.node_id) {
10151017
if (event.event === 'copilot_work_started') {
1016-
indexLastStart = parsedEvents.length;
10171018
parsedEvents.push({
10181019
id: eventNode.node_id,
10191020
event: Common.EventType.CopilotStarted,
10201021
createdAt: eventNode.created_at,
1021-
onBehalfOf: parseAccount(eventNode.actor)
1022+
onBehalfOf: parseAccount(eventNode.actor),
1023+
sessionLink: {
1024+
...prSessionLink,
1025+
sessionIndex
1026+
}
10221027
});
10231028
} else if (event.event === 'copilot_work_finished') {
10241029
parsedEvents.push({
@@ -1027,21 +1032,23 @@ export function parseSelectRestTimelineEvents(
10271032
createdAt: eventNode.created_at,
10281033
onBehalfOf: parseAccount(eventNode.actor)
10291034
});
1035+
sessionIndex++;
10301036
} else if (event.event === 'copilot_work_finished_failure') {
1037+
sessionIndex++;
10311038
parsedEvents.push({
10321039
id: eventNode.node_id,
10331040
event: Common.EventType.CopilotFinishedError,
10341041
createdAt: eventNode.created_at,
10351042
onBehalfOf: parseAccount(eventNode.actor),
1036-
sessionLink
1043+
sessionLink: {
1044+
...prSessionLink,
1045+
sessionIndex
1046+
}
10371047
});
10381048
}
10391049
}
10401050
}
1041-
if (indexLastStart > -1) {
1042-
const startEvent: Common.CopilotStartedEvent = parsedEvents[indexLastStart] as Common.CopilotStartedEvent;
1043-
startEvent.sessionLink = sessionLink;
1044-
}
1051+
10451052
return parsedEvents;
10461053
}
10471054

src/lm/tools/activePullRequestTool.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export class ActivePullRequestTool implements vscode.LanguageModelTool<FetchIssu
9898
): Promise<string | string[]> {
9999
let copilotSteps: string | string[] = [];
100100
try {
101-
const logs = await this.copilotRemoteAgentManager.getMostRecentSessionLogsFromPullRequest(pullRequest.id);
101+
const logs = await this.copilotRemoteAgentManager.getSessionLogFromPullRequest(pullRequest.id);
102102
if (!logs) {
103103
throw new Error('Could not get session logs');
104104
}

src/view/sessionLogView.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type * as messages from '../../webviews/sessionLogView/messages';
88
import { Disposable, disposeAll } from '../common/lifecycle';
99
import Logger from '../common/logger';
1010
import { ITelemetry } from '../common/telemetry';
11-
import { SessionPullInfo } from '../common/timelineEvent';
11+
import { SessionLinkInfo, SessionPullInfo } from '../common/timelineEvent';
1212
import { CopilotApi, getCopilotApi } from '../github/copilotApi';
1313
import { CopilotRemoteAgentManager, IAPISessionLogs } from '../github/copilotRemoteAgent';
1414
import { CredentialStore } from '../github/credentials';
@@ -82,11 +82,17 @@ export class SessionLogViewManager extends Disposable implements vscode.WebviewP
8282
super.dispose();
8383
}
8484

85-
async openForPull(pullRequest: PullRequestModel): Promise<void> {
85+
async openForPull(pullRequest: PullRequestModel, link: SessionLinkInfo): Promise<void> {
8686
try {
87-
const sessionLogs = await this.copilotAgentManager.getMostRecentSessionLogsFromPullRequest(pullRequest.id, false);
87+
// TODO: We should not block opening the webview here. When does this actually fail?
88+
89+
// Session indexes are oldest to newest
90+
// But the sessions api returns newest to oldest
91+
// Use a reverse index to get the correct session
92+
const sessionLogs = await this.copilotAgentManager.getSessionLogFromPullRequest(pullRequest.id, -1 - link.sessionIndex, false);
8893
if (!sessionLogs) {
89-
throw new Error('No sessions found for this pull request.');
94+
vscode.window.showErrorMessage(vscode.l10n.t('Could not find session.'));
95+
return;
9096
}
9197

9298
const existingPanel = this.getPanelForPullRequest(pullRequest);

webviews/common/context.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import { createContext } from 'react';
77
import { CloseResult } from '../../common/views';
88
import { IComment } from '../../src/common/comment';
9-
import { EventType, ReviewEvent, SessionPullInfo, TimelineEvent } from '../../src/common/timelineEvent';
9+
import { EventType, ReviewEvent, SessionLinkInfo, TimelineEvent } from '../../src/common/timelineEvent';
1010
import { IProjectItem, MergeMethod, ReadyForReview } from '../../src/github/interface';
1111
import { CancelCodingAgentReply, ChangeAssigneesReply, MergeArguments, MergeResult, ProjectItemsReply, PullRequest, SubmitReviewReply } from '../../src/github/views';
1212
import { getState, setState, updateState } from './cache';
@@ -256,7 +256,7 @@ export class PRContext {
256256
});
257257
};
258258

259-
public openSessionLog = (link: SessionPullInfo) => this.postMessage({ command: 'pr.open-session-log', args: { link } });
259+
public openSessionLog = (link: SessionLinkInfo) => this.postMessage({ command: 'pr.open-session-log', args: { link } });
260260

261261
setPR = (pr: PullRequest) => {
262262
this.pr = pr;

webviews/sessionLogView/index.css

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,27 +47,36 @@
4747
}
4848

4949
.session-header-title {
50-
h1 .codicon {
51-
font-size: inherit;
50+
margin-bottom: 1em;
51+
52+
.codicon {
53+
font-size: inherit !important;
5254
}
5355

5456
h1,
57+
h2,
58+
h3 {
59+
display: block;
60+
margin: 0.4em 0;
61+
}
62+
5563
h2 {
56-
margin: 0a.4em 0;
64+
font-size: 1.2em;
65+
margin-bottom: 0.5em;
66+
67+
}
68+
69+
.pull-request-info {
70+
margin-bottom: 1em;
5771
}
5872

59-
h2 a {
73+
.pull-request-link {
6074
color: inherit;
6175

6276
.pull-request-id {
6377
color: var(--vscode-textLink-foreground);
6478
}
6579
}
66-
67-
nav ul {
68-
list-style: none;
69-
padding: 0;
70-
}
7180
}
7281

7382
.session-pull-button {

webviews/sessionLogView/messages.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { SessionPullInfo } from '../../src/common/timelineEvent';
6+
import { SessionLinkInfo } from '../../src/common/timelineEvent';
77
import { SessionInfo } from './sessionsApi';
88

9-
export type PullInfo = SessionPullInfo & {
9+
export type PullInfo = SessionLinkInfo & {
1010
title: string;
1111
};
1212

webviews/sessionLogView/sessionView.tsx

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,24 +52,21 @@ const SessionHeader: React.FC<SessionHeaderProps> = ({ info, pullInfo }) => {
5252
<header className="session-header">
5353
<div className='session-header-title'>
5454
<h1>Coding Agent Session Log</h1>
55+
<h2>{info.name}</h2>
5556

5657
{pullInfo && <>
57-
<h2>
58-
<a onClick={() => {
58+
<h3 className='pull-request-info'>
59+
<a className='pull-request-link' onClick={() => {
5960
vscode.postMessage({ type: 'openPullRequestView' });
6061
}} title='Back to pull request'>
6162
<span className="icon"><i className={'codicon codicon-git-pull-request'}></i></span> {pullInfo.title} <span className="pull-request-id">#{pullInfo.pullId}</span>
6263
</a>
63-
</h2>
64+
</h3>
6465

6566
<nav>
66-
<ul>
67-
<li>
68-
<button onClick={() => {
69-
vscode.postMessage({ type: 'openOnWeb' });
70-
}}>Open on GitHub</button>
71-
</li>
72-
</ul>
67+
<button onClick={() => {
68+
vscode.postMessage({ type: 'openOnWeb' });
69+
}} title='Open session log on GitHub.com'>Open on GitHub</button>
7370
</nav>
7471
</>}
7572
</div>

0 commit comments

Comments
 (0)