Skip to content

Commit 1d5ce98

Browse files
authored
Polish timeline (#6814)
- Show assign event in timeline when assignment is made - Consolidate multiple nearby assignments - Timestamp color
1 parent dfd97fb commit 1d5ce98

8 files changed

Lines changed: 78 additions & 49 deletions

File tree

src/common/timelineEvent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export interface MergedEvent {
9595
export interface AssignEvent {
9696
id: number;
9797
event: EventType.Assigned;
98-
assignee: IAccount;
98+
assignees: IAccount[];
9999
actor: IActor;
100100
createdAt: string;
101101
}

src/github/issueOverview.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { IAccount, ILabel, IMilestone, IProject, IProjectItem, RepoAccessAndMerg
1717
import { IssueModel } from './issueModel';
1818
import { getAssigneesQuickPickItems, getLabelOptions, getMilestoneFromQuickPick, getProjectFromQuickPick } from './quickPicks';
1919
import { isInCodespaces, vscodeDevPrLink } from './utils';
20-
import { Issue, ProjectItemsReply } from './views';
20+
import { ChangeAssigneesReply, Issue, ProjectItemsReply } from './views';
2121

2222
export class IssueOverviewPanel<TItem extends IssueModel = IssueModel> extends WebviewBase {
2323
public static ID: string = 'IssueOverviewPanel';
@@ -387,9 +387,12 @@ export class IssueOverviewPanel<TItem extends IssueModel = IssueModel> extends W
387387
if (allAssignees) {
388388
const newAssignees: IAccount[] = allAssignees.map(item => item.user);
389389
await this._item.replaceAssignees(newAssignees);
390-
await this._replyMessage(message, {
390+
const events = await this._item.getIssueTimelineEvents();
391+
const reply: ChangeAssigneesReply = {
391392
assignees: newAssignees,
392-
});
393+
events
394+
};
395+
await this._replyMessage(message, reply);
393396
}
394397
} catch (e) {
395398
vscode.window.showErrorMessage(formatError(e));
@@ -448,12 +451,15 @@ export class IssueOverviewPanel<TItem extends IssueModel = IssueModel> extends W
448451
const currentUser = await this._folderRepositoryManager.getCurrentUser();
449452
const alreadyAssigned = this._item.assignees?.find(user => user.login === currentUser.login);
450453
if (!alreadyAssigned) {
451-
const newAssigness = (this._item.assignees ?? []).concat(currentUser);
452-
await this._item.replaceAssignees(newAssigness);
454+
const newAssignees = (this._item.assignees ?? []).concat(currentUser);
455+
await this._item.replaceAssignees(newAssignees);
453456
}
454-
this._replyMessage(message, {
455-
assignees: this._item.assignees,
456-
});
457+
const events = await this._item.getIssueTimelineEvents();
458+
const reply: ChangeAssigneesReply = {
459+
assignees: this._item.assignees ?? [],
460+
events
461+
};
462+
this._replyMessage(message, reply);
457463
} catch (e) {
458464
vscode.window.showErrorMessage(formatError(e));
459465
}

src/github/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1039,7 +1039,7 @@ export function parseGraphQLTimelineEvents(
10391039
normalizedEvents.push({
10401040
id: assignEv.id,
10411041
event: type,
1042-
assignee: parseAccount(assignEv.user, githubRepository),
1042+
assignees: [parseAccount(assignEv.user, githubRepository)],
10431043
actor: parseAccount(assignEv.actor),
10441044
createdAt: assignEv.createdAt,
10451045
});

src/github/views.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ export interface ProjectItemsReply {
101101
projectItems: IProjectItem[] | undefined;
102102
}
103103

104+
export interface ChangeAssigneesReply {
105+
assignees: IAccount[];
106+
events: TimelineEvent[];
107+
}
108+
104109
export interface MergeArguments {
105110
title: string | undefined;
106111
description: string | undefined;

webviews/common/context.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { createContext } from 'react';
77
import { IComment } from '../../src/common/comment';
88
import { EventType, ReviewEvent, TimelineEvent } from '../../src/common/timelineEvent';
99
import { IProjectItem, MergeMethod, ReadyForReview, ReviewState } from '../../src/github/interface';
10-
import { MergeArguments, MergeResult, ProjectItemsReply, PullRequest } from '../../src/github/views';
10+
import { ChangeAssigneesReply, MergeArguments, MergeResult, ProjectItemsReply, PullRequest } from '../../src/github/views';
1111
import { getState, setState, updateState } from './cache';
1212
import { getMessageHandler, MessageHandler } from './message';
1313

@@ -79,8 +79,8 @@ export class PRContext {
7979
public removeProject = (project: IProjectItem) => this.postMessage({ command: 'pr.remove-project', args: project });
8080
public addMilestone = () => this.postMessage({ command: 'pr.add-milestone' });
8181
public removeMilestone = () => this.postMessage({ command: 'pr.remove-milestone' });
82-
public addAssignees = () => this.postMessage({ command: 'pr.change-assignees' });
83-
public addAssigneeYourself = () => this.postMessage({ command: 'pr.add-assignee-yourself' });
82+
public addAssignees = (): Promise<ChangeAssigneesReply> => this.postMessage({ command: 'pr.change-assignees' });
83+
public addAssigneeYourself = (): Promise<ChangeAssigneesReply> => this.postMessage({ command: 'pr.add-assignee-yourself' });
8484
public addLabels = () => this.postMessage({ command: 'pr.add-labels' });
8585
public create = () => this.postMessage({ command: 'pr.open-create' });
8686

webviews/components/sidebar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export default function Sidebar({ reviewers, labels, hasWritePermission, isIssue
6262
<div id="assignees" className="section">
6363
<div className="section-header" onClick={async () => {
6464
const newAssignees = await addAssignees();
65-
updatePR({ assignees: newAssignees.assignees });
65+
updatePR({ assignees: newAssignees.assignees, events: newAssignees.events });
6666
}}>
6767
<div className="section-title">Assignees</div>
6868
{hasWritePermission ? (
@@ -94,7 +94,7 @@ export default function Sidebar({ reviewers, labels, hasWritePermission, isIssue
9494
className="assign-yourself"
9595
onClick={async () => {
9696
const newAssignees = await addAssigneeYourself();
97-
updatePR({ assignees: newAssignees.assignees });
97+
updatePR({ assignees: newAssignees.assignees, events: newAssignees.events });
9898
}}
9999
>
100100
assign yourself

webviews/components/timeline.tsx

Lines changed: 51 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -21,37 +21,53 @@ import { ReviewType } from '../../src/github/views';
2121
import PullRequestContext from '../common/context';
2222
import { CommentView } from './comment';
2323
import Diff from './diff';
24-
import { assigneeIcon, commitIcon, linkIcon, mergeIcon, plusIcon } from './icon';
24+
import { commitIcon, mergeIcon, plusIcon } from './icon';
2525
import { nbsp } from './space';
2626
import { Timestamp } from './timestamp';
2727
import { AuthorLink, Avatar } from './user';
2828

29-
export const Timeline = ({ events }: { events: TimelineEvent[] }) => (
30-
<>
31-
{events.map(event => {
32-
switch (event.event) {
33-
case EventType.Committed:
34-
return <CommitEventView key={`commit${event.id}`} {...event} />;
35-
case EventType.Reviewed:
36-
return <ReviewEventView key={`review${event.id}`} {...event} />;
37-
case EventType.Commented:
38-
return <CommentEventView key={`comment${event.id}`} {...event} />;
39-
case EventType.Merged:
40-
return <MergedEventView key={`merged${event.id}`} {...event} />;
41-
case EventType.Assigned:
42-
return <AssignEventView key={`assign${event.id}`} {...event} />;
43-
case EventType.HeadRefDeleted:
44-
return <HeadDeleteEventView key={`head${event.id}`} {...event} />;
45-
case EventType.CrossReferenced:
46-
return <CrossReferencedEventView key={`cross${event.id}`} {...event} />;
47-
case EventType.NewCommitsSinceReview:
48-
return <NewCommitsSinceReviewEventView key={`newCommits${event.id}`} />;
49-
default:
50-
throw new UnreachableCaseError(event);
29+
export const Timeline = ({ events }: { events: TimelineEvent[] }) => {
30+
const consolidatedEvents: TimelineEvent[] = [];
31+
for (let i = 0; i < events.length; i++) {
32+
if ((i > 0) && (events[i].event === EventType.Assigned) && (consolidatedEvents[consolidatedEvents.length - 1].event === EventType.Assigned)) {
33+
const lastEvent = consolidatedEvents[consolidatedEvents.length - 1] as AssignEvent;
34+
const newEvent = events[i] as AssignEvent;
35+
if (new Date(lastEvent.createdAt).getTime() + (1000 * 60 * 10) > new Date(newEvent.createdAt).getTime()) { // within 10 minutes
36+
if (lastEvent.assignees.every(a => a.id !== newEvent.assignees[0].id)) {
37+
lastEvent.assignees = [...lastEvent.assignees, ...newEvent.assignees];
38+
}
39+
lastEvent.createdAt = newEvent.createdAt;
40+
} else {
41+
consolidatedEvents.push(newEvent);
5142
}
52-
})}
53-
</>
54-
);
43+
} else {
44+
consolidatedEvents.push(events[i]);
45+
}
46+
}
47+
48+
return <>{consolidatedEvents.map(event => {
49+
switch (event.event) {
50+
case EventType.Committed:
51+
return <CommitEventView key={`commit${event.id}`} {...event} />;
52+
case EventType.Reviewed:
53+
return <ReviewEventView key={`review${event.id}`} {...event} />;
54+
case EventType.Commented:
55+
return <CommentEventView key={`comment${event.id}`} {...event} />;
56+
case EventType.Merged:
57+
return <MergedEventView key={`merged${event.id}`} {...event} />;
58+
case EventType.Assigned:
59+
return <AssignEventView key={`assign${event.id}`} {...event} />;
60+
case EventType.HeadRefDeleted:
61+
return <HeadDeleteEventView key={`head${event.id}`} {...event} />;
62+
case EventType.CrossReferenced:
63+
return <CrossReferencedEventView key={`cross${event.id}`} {...event} />;
64+
case EventType.NewCommitsSinceReview:
65+
return <NewCommitsSinceReviewEventView key={`newCommits${event.id}`} />;
66+
default:
67+
throw new UnreachableCaseError(event);
68+
}
69+
})}</>;
70+
};
5571

5672
export default Timeline;
5773

@@ -63,7 +79,6 @@ const CommitEventView = (event: CommitEvent) => (
6379
<div className="avatar-container">
6480
<Avatar for={event.author} />
6581
</div>
66-
<AuthorLink for={event.author} />
6782
<div className="message-container">
6883
<a className="message" href={event.htmlUrl} title={event.htmlUrl}>
6984
{event.message.substr(0, event.message.indexOf('\n') > -1 ? event.message.indexOf('\n') : event.message.length)}
@@ -290,8 +305,6 @@ const CrossReferencedEventView = (event: CrossReferencedEvent) => {
290305
return (
291306
<div className="comment-container commit">
292307
<div className="commit-message">
293-
{linkIcon}
294-
{nbsp}
295308
<div className="avatar-container">
296309
<Avatar for={event.actor} />
297310
</div>
@@ -307,19 +320,24 @@ const CrossReferencedEventView = (event: CrossReferencedEvent) => {
307320
);
308321
};
309322

323+
function joinWithAnd(arr: JSX.Element[]): JSX.Element {
324+
if (arr.length === 0) return <></>;
325+
if (arr.length === 1) return arr[0];
326+
if (arr.length === 2) return <>{arr[0]} and {arr[1]}</>;
327+
return <>{arr.slice(0, -1).map(item => <>{item}, </>)} and {arr[arr.length - 1]}</>;
328+
}
329+
310330
const AssignEventView = (event: AssignEvent) => {
311-
const { actor, assignee } = event;
331+
const { actor, assignees } = event;
312332
return (
313333
<div className="comment-container commit">
314334
<div className="commit-message">
315-
{assigneeIcon}
316-
{nbsp}
317335
<div className="avatar-container">
318336
<Avatar for={actor} />
319337
</div>
320338
<AuthorLink for={actor} />
321339
<div className="message">
322-
assigned <AuthorLink for={assignee} /> to this pull request
340+
assigned {joinWithAnd(assignees.map(a => <AuthorLink key={a.id} for={a} />))} to this pull request
323341
</div>
324342
</div>
325343
<Timestamp date={event.createdAt} />

webviews/editorWebview/index.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -951,7 +951,7 @@ code {
951951

952952
.timestamp,
953953
.timestamp:hover {
954-
color: inherit;
954+
color: var(--vscode-descriptionForeground);;
955955
white-space: nowrap;
956956
}
957957

0 commit comments

Comments
 (0)