Skip to content

Commit 556e494

Browse files
dan1994mhutchie
authored andcommitted
mhutchie#482 New "Mark as Reviewed" & "Mark as Not Reviewed" actions on the file context menu in the Commit Details View when a Code Review is in progress. Squash Merge of @dan1994's PR mhutchie#483.
1 parent d0412a1 commit 556e494

File tree

5 files changed

+177
-111
lines changed

5 files changed

+177
-111
lines changed

src/extensionState.ts

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -359,24 +359,31 @@ export class ExtensionState extends Disposable {
359359
}
360360

361361
/**
362-
* Record that a file has been reviewed in a Code Review.
362+
* Update information for a specific Code Review.
363363
* @param repo The repository the Code Review is in.
364364
* @param id The ID of the Code Review.
365-
* @param file The file that has been reviewed.
365+
* @param remainingFiles The files remaining for review.
366+
* @param lastViewedFile The last viewed file. If null, don't change the last viewed file.
367+
* @returns An error message if request can't be completed.
366368
*/
367-
public updateCodeReviewFileReviewed(repo: string, id: string, file: string) {
368-
let reviews = this.getCodeReviews();
369-
if (typeof reviews[repo] !== 'undefined' && typeof reviews[repo][id] !== 'undefined') {
370-
let i = reviews[repo][id].remainingFiles.indexOf(file);
371-
if (i > -1) reviews[repo][id].remainingFiles.splice(i, 1);
372-
if (reviews[repo][id].remainingFiles.length > 0) {
373-
reviews[repo][id].lastViewedFile = file;
374-
reviews[repo][id].lastActive = (new Date()).getTime();
375-
} else {
376-
removeCodeReview(reviews, repo, id);
369+
public updateCodeReview(repo: string, id: string, remainingFiles: string[], lastViewedFile: string | null) {
370+
const reviews = this.getCodeReviews();
371+
372+
if (typeof reviews[repo] === 'undefined' || typeof reviews[repo][id] === 'undefined') {
373+
return Promise.resolve('The Code Review could not be found.');
374+
}
375+
376+
if (remainingFiles.length > 0) {
377+
reviews[repo][id].remainingFiles = remainingFiles;
378+
reviews[repo][id].lastActive = (new Date()).getTime();
379+
if (lastViewedFile !== null) {
380+
reviews[repo][id].lastViewedFile = lastViewedFile;
377381
}
378-
this.setCodeReviews(reviews);
382+
} else {
383+
removeCodeReview(reviews, repo, id);
379384
}
385+
386+
return this.setCodeReviews(reviews);
380387
}
381388

382389
/**

src/gitGraphView.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -227,9 +227,6 @@ export class GitGraphView extends Disposable {
227227
error: await this.dataSource.cleanUntrackedFiles(msg.repo, msg.directories)
228228
});
229229
break;
230-
case 'codeReviewFileReviewed':
231-
this.extensionState.updateCodeReviewFileReviewed(msg.repo, msg.id, msg.filePath);
232-
break;
233230
case 'commitDetails':
234231
let data = await Promise.all<GitCommitDetailsData, string | null>([
235232
msg.commitHash === UNCOMMITTED
@@ -576,6 +573,12 @@ export class GitGraphView extends Disposable {
576573
...await this.dataSource.getTagDetails(msg.repo, msg.tagName)
577574
});
578575
break;
576+
case 'updateCodeReview':
577+
this.sendMessage({
578+
command: 'updateCodeReview',
579+
error: await this.extensionState.updateCodeReview(msg.repo, msg.id, msg.remainingFiles, msg.lastViewedFile)
580+
});
581+
break;
579582
case 'viewDiff':
580583
this.sendMessage({
581584
command: 'viewDiff',

src/types.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -645,12 +645,6 @@ export interface ResponseCleanUntrackedFiles extends ResponseWithErrorInfo {
645645
readonly command: 'cleanUntrackedFiles';
646646
}
647647

648-
export interface RequestCodeReviewFileReviewed extends RepoRequest {
649-
readonly command: 'codeReviewFileReviewed';
650-
readonly id: string;
651-
readonly filePath: string;
652-
}
653-
654648
export interface RequestCommitDetails extends RepoRequest {
655649
readonly command: 'commitDetails';
656650
readonly commitHash: string;
@@ -1161,6 +1155,17 @@ export interface ResponseTagDetails extends ResponseWithErrorInfo {
11611155
readonly message: string;
11621156
}
11631157

1158+
export interface RequestUpdateCodeReview extends RepoRequest {
1159+
readonly command: 'updateCodeReview';
1160+
readonly id: string;
1161+
readonly remainingFiles: string[];
1162+
readonly lastViewedFile: string | null;
1163+
}
1164+
1165+
export interface ResponseUpdateCodeReview extends ResponseWithErrorInfo {
1166+
readonly command: 'updateCodeReview';
1167+
}
1168+
11641169
export interface RequestViewDiff extends RepoRequest {
11651170
readonly command: 'viewDiff';
11661171
readonly fromHash: string;
@@ -1207,7 +1212,6 @@ export type RequestMessage =
12071212
| RequestCheckoutCommit
12081213
| RequestCherrypickCommit
12091214
| RequestCleanUntrackedFiles
1210-
| RequestCodeReviewFileReviewed
12111215
| RequestCommitDetails
12121216
| RequestCompareCommits
12131217
| RequestCopyFilePath
@@ -1256,6 +1260,7 @@ export type RequestMessage =
12561260
| RequestShowErrorDialog
12571261
| RequestStartCodeReview
12581262
| RequestTagDetails
1263+
| RequestUpdateCodeReview
12591264
| RequestViewDiff
12601265
| RequestViewDiffWithWorkingFile
12611266
| RequestViewFileAtRevision
@@ -1315,6 +1320,7 @@ export type ResponseMessage =
13151320
| ResponseSetWorkspaceViewState
13161321
| ResponseStartCodeReview
13171322
| ResponseTagDetails
1323+
| ResponseUpdateCodeReview
13181324
| ResponseViewDiff
13191325
| ResponseViewDiffWithWorkingFile
13201326
| ResponseViewFileAtRevision

tests/extensionState.test.ts

Lines changed: 72 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1167,124 +1167,132 @@ describe('ExtensionState', () => {
11671167
});
11681168
});
11691169

1170-
describe('updateCodeReviewFileReviewed', () => {
1171-
it('Should remove the reviewed file, set it as the last viewed file, and update the last active time', () => {
1172-
// Setup
1170+
describe('updateCodeReview', () => {
1171+
const repo = '/path/to/repo';
1172+
const id = 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2';
1173+
const startTime = 1587559257000;
1174+
const endTime = startTime + 1000;
1175+
1176+
beforeEach(() => {
11731177
const codeReviews = {
1174-
'/path/to/repo': {
1175-
'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2': {
1176-
lastActive: 1587559257000,
1178+
[repo]: {
1179+
[id]: {
1180+
lastActive: startTime,
11771181
lastViewedFile: 'file1.txt',
11781182
remainingFiles: ['file2.txt', 'file3.txt']
11791183
}
11801184
}
11811185
};
1186+
11821187
extensionContext.workspaceState.get.mockReturnValueOnce(codeReviews);
11831188
extensionContext.workspaceState.update.mockResolvedValueOnce(null);
1189+
});
11841190

1191+
it('Should update the reviewed files and change the last viewed file', async () => {
11851192
// Run
1186-
extensionState.updateCodeReviewFileReviewed('/path/to/repo', 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2', 'file2.txt');
1193+
const result = await extensionState.updateCodeReview(repo, id, ['file3.txt'], 'file2.txt');
11871194

1188-
// Assert
1195+
// Asset
1196+
expect(result).toBeNull();
11891197
expect(extensionContext.workspaceState.update).toHaveBeenCalledWith('codeReviews', {
1190-
'/path/to/repo': {
1191-
'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2': {
1192-
lastActive: 1587559258000,
1198+
[repo]: {
1199+
[id]: {
1200+
lastActive: endTime,
11931201
lastViewedFile: 'file2.txt',
11941202
remainingFiles: ['file3.txt']
11951203
}
11961204
}
11971205
});
11981206
});
11991207

1200-
it('Should ignore removing reviewed files if it has already be stored as reviewed', () => {
1201-
// Setup
1202-
const codeReviews = {
1203-
'/path/to/repo': {
1204-
'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2': {
1205-
lastActive: 1587559257000,
1208+
it('Should update the reviewed files without changing the last viewed file', async () => {
1209+
// Run
1210+
const result = await extensionState.updateCodeReview(repo, id, ['file3.txt'], null);
1211+
1212+
// Assert
1213+
expect(result).toBeNull();
1214+
expect(extensionContext.workspaceState.update).toHaveBeenCalledWith('codeReviews', {
1215+
[repo]: {
1216+
[id]: {
1217+
lastActive: endTime,
12061218
lastViewedFile: 'file1.txt',
12071219
remainingFiles: ['file3.txt']
12081220
}
12091221
}
1210-
};
1211-
extensionContext.workspaceState.get.mockReturnValueOnce(codeReviews);
1212-
extensionContext.workspaceState.update.mockResolvedValueOnce(null);
1222+
});
1223+
});
12131224

1225+
it('Should update the not reviewed files without changing the last viewed file', async () => {
12141226
// Run
1215-
extensionState.updateCodeReviewFileReviewed('/path/to/repo', 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2', 'file2.txt');
1227+
const result = await extensionState.updateCodeReview(repo, id, ['file2.txt', 'file3.txt', 'file4.txt'], null);
12161228

12171229
// Assert
1230+
expect(result).toBeNull();
12181231
expect(extensionContext.workspaceState.update).toHaveBeenCalledWith('codeReviews', {
1219-
'/path/to/repo': {
1220-
'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2': {
1221-
lastActive: 1587559258000,
1222-
lastViewedFile: 'file2.txt',
1223-
remainingFiles: ['file3.txt']
1232+
[repo]: {
1233+
[id]: {
1234+
lastActive: endTime,
1235+
lastViewedFile: 'file1.txt',
1236+
remainingFiles: ['file2.txt', 'file3.txt', 'file4.txt']
12241237
}
12251238
}
12261239
});
12271240
});
12281241

1229-
it('Should remove the code review the last file in it has been reviewed', () => {
1230-
// Setup
1231-
const codeReviews = {
1232-
'/path/to/repo': {
1233-
'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2': {
1234-
lastActive: 1587559257000,
1242+
it('Should set the last viewed file', async () => {
1243+
// Run
1244+
const result = await extensionState.updateCodeReview(repo, id, ['file2.txt', 'file3.txt'], 'file2.txt');
1245+
1246+
// Assert
1247+
expect(result).toBeNull();
1248+
expect(extensionContext.workspaceState.update).toHaveBeenCalledWith('codeReviews', {
1249+
[repo]: {
1250+
[id]: {
1251+
lastActive: endTime,
12351252
lastViewedFile: 'file2.txt',
1236-
remainingFiles: ['file3.txt']
1253+
remainingFiles: ['file2.txt', 'file3.txt']
12371254
}
12381255
}
1239-
};
1240-
extensionContext.workspaceState.get.mockReturnValueOnce(codeReviews);
1241-
extensionContext.workspaceState.update.mockResolvedValueOnce(null);
1256+
});
1257+
});
12421258

1259+
it('Should remove the code review the last file in it has been reviewed', async () => {
12431260
// Run
1244-
extensionState.updateCodeReviewFileReviewed('/path/to/repo', 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2', 'file3.txt');
1261+
const result = await extensionState.updateCodeReview(repo, id, [], null);
12451262

12461263
// Assert
1264+
expect(result).toBeNull();
12471265
expect(extensionContext.workspaceState.update).toHaveBeenCalledWith('codeReviews', {});
12481266
});
12491267

1250-
it('Shouldn\'t change the state if no code review could be found in the specified repository', () => {
1251-
// Setup
1252-
const codeReviews = {
1253-
'/path/to/repo': {
1254-
'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2': {
1255-
lastActive: 1587559258000,
1256-
lastViewedFile: 'file1.txt',
1257-
remainingFiles: ['file2.txt', 'file3.txt']
1258-
}
1259-
}
1260-
};
1261-
extensionContext.workspaceState.get.mockReturnValueOnce(codeReviews);
1268+
it('Shouldn\'t change the state if no code review could be found in the specified repository', async () => {
1269+
// Run
1270+
const result = await extensionState.updateCodeReview(repo + '1', id, ['file3.txt'], null);
1271+
1272+
// Assert
1273+
expect(result).toBe('The Code Review could not be found.');
1274+
expect(extensionContext.workspaceState.update).toHaveBeenCalledTimes(0);
1275+
});
12621276

1277+
it('Shouldn\'t change the state if no code review could be found with the specified id', async () => {
12631278
// Run
1264-
extensionState.updateCodeReviewFileReviewed('/path/to/repo1', 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2', 'file2.txt');
1279+
const result = await extensionState.updateCodeReview(repo, id + '1', ['file3.txt'], null);
12651280

12661281
// Assert
1282+
expect(result).toBe('The Code Review could not be found.');
12671283
expect(extensionContext.workspaceState.update).toHaveBeenCalledTimes(0);
12681284
});
12691285

1270-
it('Shouldn\'t change the state if no code review could be found with the specified id', () => {
1286+
it('Should return an error message when workspaceState.update rejects', async () => {
12711287
// Setup
1272-
const codeReviews = {
1273-
'/path/to/repo': {
1274-
'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2': {
1275-
lastActive: 1587559258000,
1276-
lastViewedFile: 'file1.txt',
1277-
remainingFiles: ['file2.txt', 'file3.txt']
1278-
}
1279-
}
1280-
};
1281-
extensionContext.workspaceState.get.mockReturnValueOnce(codeReviews);
1288+
extensionContext.workspaceState.update.mockReset();
1289+
extensionContext.workspaceState.update.mockRejectedValueOnce(null);
12821290

12831291
// Run
1284-
extensionState.updateCodeReviewFileReviewed('/path/to/repo', '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', 'file2.txt');
1292+
const result = await extensionState.updateCodeReview(repo, id, ['file3.txt'], 'file2.txt');
12851293

1286-
// Assert
1287-
expect(extensionContext.workspaceState.update).toHaveBeenCalledTimes(0);
1294+
// Asset
1295+
expect(result).toBe('Visual Studio Code was unable to save the Git Graph Workspace State Memento.');
12881296
});
12891297
});
12901298

0 commit comments

Comments
 (0)