Skip to content

Commit

Permalink
feat(pro): init review comments related api
Browse files Browse the repository at this point in the history
  • Loading branch information
iamhyc committed Feb 9, 2024
1 parent 43dd13e commit 4d18c27
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 15 deletions.
11 changes: 9 additions & 2 deletions src/api/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ export interface MisspellingItemSchema {
suggestions: string[]
}

export interface UserInfoSchema {
id: string,
first_name: string,
last_name?: string,
email: string,
}

export interface MemberEntity {
_id: string,
first_name: string,
Expand Down Expand Up @@ -111,7 +118,7 @@ export interface ProjectLabelResponseSchema {
}

export interface ProjectUpdateMeta {
users: {id:string, first_name:string, last_name?:string, email:string}[],
users: UserInfoSchema[],
start_ts: number,
end_ts: number,
}
Expand Down Expand Up @@ -154,7 +161,7 @@ export interface ProjectMessageResponseSchema {
content: string,
timestamp: number,
user_id: string,
user: {id:string, first_name:string, last_name?:string, email:string},
user: UserInfoSchema,
clientId: string,
}

Expand Down
97 changes: 96 additions & 1 deletion src/api/extendedBase.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { BaseAPI, Identity } from "./base";
import { BaseAPI, Identity, ResponseSchema, UserInfoSchema } from "./base";

export interface ProjectLinkedFileProvider {
provider: 'project_file',
Expand All @@ -12,6 +12,49 @@ export interface UrlLinkedFileProvider {
url: string,
}

export interface CommentThreadSchema {
doc_id?: string, // to be filled in via API call
messages: CommentThreadMessageSchema[],
resolved?: boolean,
resolved_at?: string, //ISO format date string
resolved_by_user_id?: string,
resolved_by_user?: UserInfoSchema,
}

export interface CommentThreadMessageSchema {
id: string,
content: string,
timestamp: number,
user_id: string,
room_id?: string,
edited_at?: number,
user: UserInfoSchema,
}

export interface DocumentReviewSchema {
id: string,
metadata: {user_id:string, ts:string}, //"ts" is ISO format date string
}

export interface DocumentReviewChangeSchema extends DocumentReviewSchema {
op: {p:number, i?:string, d?:string},
}

export interface DocumentReviewCommentSchema extends DocumentReviewSchema {
op: {p:number, c:string, t:string}, // "c" for quoted text, "t" for thread_id
thread?: CommentThreadSchema, // to be filled in via API call
}

export interface DocumentRangesSchema {
changes: DocumentReviewChangeSchema[],
comments: DocumentReviewCommentSchema[],
}

export interface ExtendedResponseSchema extends ResponseSchema {
threads: {[threadId:string]: CommentThreadSchema},
ranges: {[docId:string]: DocumentRangesSchema},
}

export class ExtendedBaseAPI extends BaseAPI {
async refreshLinkedFile(identity:Identity, project_id:string, file_id:string) {
this.setIdentity(identity);
Expand All @@ -28,4 +71,56 @@ export class ExtendedBaseAPI extends BaseAPI {
return {message};
}, {'X-Csrf-Token': identity.csrfToken});
}

async getAllCommentThreads(identity: Identity, project_id: string) {
this.setIdentity(identity);
return await this.request('GET', `project/${project_id}/threads`, undefined, (res) => {
const threads = JSON.parse(res!);
return {threads};
}) as ExtendedResponseSchema;
}

async getAllDocumentRanges(identity: Identity, project_id: string) {
this.setIdentity(identity);
return await this.request('GET', `project/${project_id}/ranges`, undefined, (res) => {
const rangeList = JSON.parse(res!) as {id:string, ranges:DocumentRangesSchema}[];
const ranges = Object.assign({}, ...rangeList.map((r) => ({[r.id]: r.ranges})));
return {ranges};
}) as ExtendedResponseSchema;
}

async resolveCommentThread(identity: Identity, project_id: string, thread_id: string) {
this.setIdentity(identity);
return await this.request('POST', `project/${project_id}/thread/${thread_id}/resolve`);
}

async reopenResolvedCommentThread(identity: Identity, project_id: string, thread_id: string) {
this.setIdentity(identity);
return await this.request('POST', `project/${project_id}/thread/${thread_id}/reopen`);
}

async deleteResolvedCommentThread(identity: Identity, project_id: string, doc_id: string, thread_id: string) {
this.setIdentity(identity);
return await this.request('DELETE', `project/${project_id}/doc/${doc_id}/thread/${thread_id}`);
}

async postCommentThreadMessage(identity: Identity, project_id: string, thread_id: string, content: string) {
this.setIdentity(identity);
return await this.request('POST', `project/${project_id}/thread/${thread_id}/messages`, {content});
}

async deleteCommentThreadMessage(identity: Identity, project_id: string, thread_id: string, message_id: string) {
this.setIdentity(identity);
return await this.request('DELETE', `project/${project_id}/thread/${thread_id}/messages/${message_id}`);
}

async editCommentThreadMessage(identity: Identity, project_id: string, thread_id: string, message_id: string, content: string) {
this.setIdentity(identity);
return await this.request('POST', `project/${project_id}/thread/${thread_id}/messages/${message_id}/edit`, {content});
}

async acceptTrackChanges(identity: Identity, project_id: string, doc_id: string, change_ids: string[]) {
this.setIdentity(identity);
return await this.request('POST', `project/${project_id}/doc/${doc_id}/changes/accept`, {change_ids});
}
}
49 changes: 48 additions & 1 deletion src/api/socketio.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { Identity, BaseAPI, ProjectMessageResponseSchema } from './base';
import { Identity, BaseAPI, ProjectMessageResponseSchema, UserInfoSchema } from './base';
import { FileEntity, DocumentEntity, FileRefEntity, FileType, FolderEntity, ProjectEntity } from '../core/remoteFileSystemProvider';
import { EventBus } from '../utils/eventBus';
import { SocketIOAlt } from './socketioAlt';
import { CommentThreadMessageSchema } from './extendedBase';

export interface UpdateUserSchema {
id: string,
Expand Down Expand Up @@ -38,6 +39,8 @@ export interface UpdateSchema {
i?: string, //insert
d?: string, //delete
u?: boolean, //isUndo
c?: string, //quoted text
t?: string, //thread id
}[],
v: number, //doc version number
lastV?: number, //last version number
Expand All @@ -46,6 +49,7 @@ export interface UpdateSchema {
source: string, //socketio client id
ts: number, //unix timestamp
user_id: string,
tc?: string, //track change id
}
}

Expand All @@ -66,6 +70,14 @@ export interface EventsHandler {
onSpellCheckLanguageUpdated?: (language:string) => void,
onCompilerUpdated?: (compiler:string) => void,
onRootDocUpdated?: (rootDocId:string) => void,
// [Server Pro] comment
onCommentThreadResolved?: (threadId:string, userInfo:UserInfoSchema) => void,
onCommentThreadReopen?: (threadId:string) => void,
onCommentThreadDeleted?: (threadId:string) => void,
onCommentThreadMessageCreated?: (threadId:string, message:CommentThreadMessageSchema) => void,
onCommentThreadMessageEdited?: (threadId:string, messageId:string, content:string) => void,
onCommentThreadMessageDeleted?: (threadId:string, messageId:string) => void,
onAcceptTrackChanges?: (docId:string, tcIds: string[]) => void,
}

type ConnectionScheme = 'Alt' | 'v1' | 'v2';
Expand Down Expand Up @@ -260,6 +272,41 @@ export class SocketIOAPI {
handler(rootDocId);
});
break;
case handlers.onCommentThreadResolved:
this.socket.on('resolve-thread', (threadId:string, userInfo:UserInfoSchema) => {
handler(threadId, userInfo);
});
break;
case handlers.onCommentThreadReopen:
this.socket.on('reopen-thread', (threadId:string) => {
handler(threadId);
});
break;
case handlers.onCommentThreadDeleted:
this.socket.on('delete-thread', (threadId:string) => {
handler(threadId);
});
break;
case handlers.onCommentThreadMessageCreated:
this.socket.on('new-comment', (threadId:string, message:CommentThreadMessageSchema) => {
handler(threadId, message);
});
break;
case handlers.onCommentThreadMessageEdited:
this.socket.on('edit-message', (threadId:string, messageId:string, content:string) => {
handler(threadId, messageId, content);
});
break;
case handlers.onCommentThreadMessageDeleted:
this.socket.on('delete-message', (threadId:string, messageId:string) => {
handler(threadId, messageId);
});
break;
case handlers.onAcceptTrackChanges:
this.socket.on('accept-changes', (docId:string, tcIds: string[]) => {
handler(docId, tcIds);
});
break;
default:
break;
}
Expand Down
73 changes: 69 additions & 4 deletions src/core/remoteFileSystemProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { GlobalStateManager } from '../utils/globalStateManager';
import { ClientManager } from '../collaboration/clientManager';
import { EventBus } from '../utils/eventBus';
import { SCMCollectionProvider } from '../scm/scmCollectionProvider';
import { ExtendedBaseAPI, ProjectLinkedFileProvider, UrlLinkedFileProvider } from '../api/extendedBase';
import { DocumentRangesSchema, ExtendedBaseAPI, ProjectLinkedFileProvider, UrlLinkedFileProvider } from '../api/extendedBase';

const __OUTPUTS_ID = `${ROOT_NAME}-outputs`;

Expand All @@ -34,6 +34,7 @@ export interface DocumentEntity extends FileEntity {
lastVersion?: number,
localCache?: string,
remoteCache?: string,
ranges?: DocumentRangesSchema,
}

export interface FileRefEntity extends FileEntity {
Expand Down Expand Up @@ -518,9 +519,10 @@ export class VirtualFileSystem extends vscode.Disposable {
EventBus.fire('fileWillOpenEvent', {uri});
return new TextEncoder().encode(content);
} else {
const res = await this.socket.joinDoc(fileEntity._id);
const content = res.docLines.join('\n');
doc.version = res.version;
const {docLines,version,ranges} = await this.socket.joinDoc(fileEntity._id);
doc.version = version;
doc.ranges = ranges;
const content = docLines.join('\n');
doc.remoteCache = content;
doc.localCache = content;
EventBus.fire('fileWillOpenEvent', {uri});
Expand Down Expand Up @@ -1176,6 +1178,69 @@ export class VirtualFileSystem extends vscode.Disposable {
return false;
}
}

async getAllDocumentReviews() {
const identity = await GlobalStateManager.authenticate(this.context, this.serverName);
// get all document ranges and threads
const rangesRes = await (this.api as ExtendedBaseAPI).getAllDocumentRanges(identity, this.projectId);
if (rangesRes.type==='success') {
const threadsRes = await (this.api as ExtendedBaseAPI).getAllCommentThreads(identity, this.projectId);
if (threadsRes.type==='success') {
for (const [docId, range] of Object.entries(rangesRes.ranges)) {
for (const comment of range.comments) {
comment.thread = threadsRes.threads[comment.op.t];
threadsRes.threads[comment.op.t].doc_id = docId;
}
}
const [ranges, threads] = [rangesRes.ranges, threadsRes.threads];
return {ranges, threads};
}
}
// return undefined if failed
return undefined;
}

async resolveCommentThread(threadId: string) {
const identity = await GlobalStateManager.authenticate(this.context, this.serverName);
const res = await (this.api as ExtendedBaseAPI).resolveCommentThread(identity, this.projectId, threadId);
return res.type==='success'? true : false;
}

async reopenResolvedCommentThread(threadId: string) {
const identity = await GlobalStateManager.authenticate(this.context, this.serverName);
const res = await (this.api as ExtendedBaseAPI).reopenResolvedCommentThread(identity, this.projectId, threadId);
return res.type==='success'? true : false;
}

async deleteResolvedCommentThread(docId: string, threadId: string) {
const identity = await GlobalStateManager.authenticate(this.context, this.serverName);
const res = await (this.api as ExtendedBaseAPI).deleteResolvedCommentThread(identity, this.projectId, docId, threadId);
return res.type==='success'? true : false;
}

async postCommentThreadMessage(threadId: string, content: string) {
const identity = await GlobalStateManager.authenticate(this.context, this.serverName);
const res = await (this.api as ExtendedBaseAPI).postCommentThreadMessage(identity, this.projectId, threadId, content);
return res.type==='success'? true : false;
}

async deleteCommentThreadMessage(threadId: string, messageId: string) {
const identity = await GlobalStateManager.authenticate(this.context, this.serverName);
const res = await (this.api as ExtendedBaseAPI).deleteCommentThreadMessage(identity, this.projectId, threadId, messageId);
return res.type==='success'? true : false;
}

async editCommentThreadMessage(threadId: string, messageId: string, content: string) {
const identity = await GlobalStateManager.authenticate(this.context, this.serverName);
const res = await (this.api as ExtendedBaseAPI).editCommentThreadMessage(identity, this.projectId, threadId, messageId, content);
return res.type==='success'? true : false;
}

async acceptTrackChanges(docId: string, changeIds: string[]) {
const identity = await GlobalStateManager.authenticate(this.context, this.serverName);
const res = await (this.api as ExtendedBaseAPI).acceptTrackChanges(identity, this.projectId, docId, changeIds);
return res.type==='success'? true : false;
}
}

export class RemoteFileSystemProvider implements vscode.FileSystemProvider {
Expand Down
9 changes: 2 additions & 7 deletions src/scm/historyViewProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as vscode from 'vscode';
import { EventBus } from '../utils/eventBus';
import { VirtualFileSystem, parseUri } from '../core/remoteFileSystemProvider';
import { OUTPUT_FOLDER_NAME, ROOT_NAME } from '../consts';
import { ProjectLabelResponseSchema } from '../api/base';
import { ProjectLabelResponseSchema, UserInfoSchema } from '../api/base';

interface HistoryRecord {
before?: number,
Expand All @@ -13,12 +13,7 @@ interface HistoryRecord {
[toV:number]: {
fromV: number,
timestamp: number,
users: {
id: string,
first_name: string,
last_name?: string,
email: string,
}[]
users: UserInfoSchema[]
}
},
labels: {[version:number]: ProjectLabelResponseSchema[]},
Expand Down

0 comments on commit 4d18c27

Please sign in to comment.