From de17782f37c618518858cd2c50856eaaf9b540dd Mon Sep 17 00:00:00 2001 From: Kirill Date: Sun, 18 Sep 2022 22:27:00 +0300 Subject: [PATCH 01/11] Initial implementation of custom diffs --- kotoed-js/src/main/less/code.less | 5 +- kotoed-js/src/main/ts/code/actions.ts | 125 +++++++----- .../main/ts/code/components/CodeReview.tsx | 184 +++++++++++++++++- .../main/ts/code/components/FileReview.tsx | 12 +- .../code/containers/CodeReviewContainer.tsx | 19 +- kotoed-js/src/main/ts/code/index.tsx | 11 +- kotoed-js/src/main/ts/code/reducers.ts | 59 +++++- kotoed-js/src/main/ts/code/remote/code.ts | 23 ++- kotoed-js/src/main/ts/code/state/diff.ts | 8 + kotoed-js/src/main/ts/code/state/editor.ts | 2 + kotoed-js/src/main/ts/code/state/index.ts | 6 +- kotoed-js/src/main/ts/code/util/filetree.tsx | 36 +++- kotoed-js/src/main/ts/hello.ts | 13 -- kotoed-js/src/main/ts/projects/create.tsx | 2 +- kotoed-js/webpack.config.base.ts | 1 - .../kotoed/api/SubmissionCodeVerticle.kt | 135 +++++++++---- .../kotoed/code/klones/KloneVerticle.kt | 3 +- .../research/kotoed/data/api/Data.kt | 17 +- .../kotoed/web/eventbus/guardian/Diff.kt | 28 +++ .../kotoed/web/eventbus/guardian/Kotoed.kt | 10 +- 20 files changed, 566 insertions(+), 133 deletions(-) create mode 100644 kotoed-js/src/main/ts/code/state/diff.ts delete mode 100644 kotoed-js/src/main/ts/hello.ts create mode 100644 kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/web/eventbus/guardian/Diff.kt diff --git a/kotoed-js/src/main/less/code.less b/kotoed-js/src/main/less/code.less index 1457748c..a85f2de4 100644 --- a/kotoed-js/src/main/less/code.less +++ b/kotoed-js/src/main/less/code.less @@ -130,8 +130,11 @@ .lost-found-button-container { flex: 0 1 auto } +.diff-mode-button-container { + flex: 0 1 auto +} -.lost-found-button { +.review-bottom-button { width: 100% } diff --git a/kotoed-js/src/main/ts/code/actions.ts b/kotoed-js/src/main/ts/code/actions.ts index 8b5f875d..ec62ea51 100644 --- a/kotoed-js/src/main/ts/code/actions.ts +++ b/kotoed-js/src/main/ts/code/actions.ts @@ -8,7 +8,16 @@ import { import { Comment, CommentsState, CommentState, FileComments, LineComments, ReviewComments } from "./state/comments"; -import {fetchDiff, fetchFile, fetchRootDir, File, FileDiffResult, FileType} from "./remote/code"; +import { + DiffBase, + fetchDiff, + fetchFile, + fetchRootDir, + File, + FileDiffResponse, + FileDiffResult, + FileType +} from "./remote/code"; import {FileNotFoundError} from "./errors"; import {push} from "react-router-redux"; import {Dispatch} from "redux"; @@ -22,7 +31,7 @@ import { editComment as doEditComment } from "./remote/comments"; import {Capabilities, fetchCapabilities} from "./remote/capabilities"; -import {getFilePath, getNodePath} from "./util/filetree"; +import {applyDiffToFileTree, getFilePath, getNodePath} from "./util/filetree"; import {NodePath} from "./state/blueprintTree"; import {makeCodeReviewCodePath, makeCodeReviewLostFoundPath} from "../util/url"; import {DbRecordWrapper, isStatusFinal} from "../data/verification"; @@ -33,6 +42,7 @@ import {fetchAnnotations} from "./remote/annotations"; import {ReviewAnnotations} from "./state/annotations"; import {CommentTemplates, fetchCommentTemplates} from "./remote/templates"; import natsort from "natsort"; +import {pick, typedKeys} from "../util/common"; const actionCreator = actionCreatorFactory(); @@ -114,6 +124,14 @@ interface FormLockUnlockPayload { sourceline: number } +interface DiffBasePayload { + diffBase: DiffBase +} + +interface DiffResultPayload { + diff: FileDiffResult[] +} + // Local actions export const dirExpand = actionCreator('DIR_EXPAND'); export const dirCollapse = actionCreator('DIR_COLLAPSE'); @@ -132,9 +150,10 @@ export const submissionFetch = actionCreator.async('ROOT_FETCH'); +export const rootFetch = actionCreator.async('ROOT_FETCH'); export const fileLoad = actionCreator.async('FILE_LOAD'); export const fileDiff = actionCreator.async('FILE_DIFF'); +export const diffFetch = actionCreator.async('DIFF_FETCH') // Annotation fetch actions export const annotationsFetch = actionCreator.async('ANNOTATION_FETCH'); @@ -217,36 +236,42 @@ const naturalSorter = natsort() const typeSorter = (a: File, b: File) => fileTypeDisplayOrder[a.type] - fileTypeDisplayOrder[b.type] export function fetchRootDirIfNeeded(payload: SubmissionPayload) { - return (dispatch: Dispatch, getState: () => CodeReviewState) => { - if (!getState().fileTreeState.loading) + return async (dispatch: Dispatch, getState: () => CodeReviewState) => { + const state = getState(); + if (!state.fileTreeState.loading) return Promise.resolve(); dispatch(rootFetch.started({ submissionId: payload.submissionId })); - return fetchRootDir(payload.submissionId).then((root) => { - const recursiveSorter = (node: File) => { - if (node.children == null) { - return - } - node.children.sort((a: File, b: File) => - typeSorter(a, b) || naturalSorter(a.name, b.name) - ) - for (let child of node.children) { - recursiveSorter(child) - } + const root = await fetchRootDir(payload.submissionId, state.diffState.base); + + const recursiveSorter = (node: File) => { + if (node.children == null) { + return } - recursiveSorter(root) - dispatch(rootFetch.done({ - params: { - submissionId: payload.submissionId - }, - result: { - root - } - })) - }); + node.children.sort((a: File, b: File) => + typeSorter(a, b) || naturalSorter(a.name, b.name) + ) + for (let child of node.children) { + recursiveSorter(child) + } + } + recursiveSorter(root) + + const diff = await fetchDiff(payload.submissionId, state.diffState.base); + + dispatch(rootFetch.done({ + params: { + submissionId: payload.submissionId + }, + result: { + root, + diff + } + })) + }; } @@ -310,8 +335,9 @@ export function setLostFoundPath(payload: SubmissionPayload) { export function loadFileToEditor(payload: FilePathPayload & SubmissionPayload) { - return (dispatch: Dispatch, getState: () => CodeReviewState) => { + return async (dispatch: Dispatch, getState: () => CodeReviewState) => { let {filename} = payload; + const state = getState(); dispatch(fileLoad.started({ submissionId: payload.submissionId, filename @@ -322,28 +348,31 @@ export function loadFileToEditor(payload: FilePathPayload & SubmissionPayload) { file: filename })(dispatch, getState); - fetchFile(payload.submissionId, filename).then(result => { - dispatch(fileLoad.done({ - params: { - submissionId: payload.submissionId, - filename - }, - result: { - value: result, - displayedComments: getState().commentsState.comments.get(filename, Map()), - } - })); - }); + const result = await fetchFile(payload.submissionId, filename); + dispatch(fileLoad.done({ + params: { + submissionId: payload.submissionId, + filename + }, + result: { + value: result, + displayedComments: state.commentsState.comments.get(filename, Map()), + } + })); - fetchDiff(payload.submissionId).then(result => { - dispatch(fileDiff.done({ - params: { - submissionId: payload.submissionId, - filename - }, - result: result.find((diff) => diff.toFile == filename) - })) - }); + } +} + +export function updateDiff(payload: SubmissionPayload & DiffBasePayload) { + return async (dispatch: Dispatch, getState: () => CodeReviewState) => { + dispatch(diffFetch.started(payload)) + + const diff = await fetchDiff(payload.submissionId, payload.diffBase); + + dispatch(diffFetch.done({ + params: payload, + result: diff + })) } } diff --git a/kotoed-js/src/main/ts/code/components/CodeReview.tsx b/kotoed-js/src/main/ts/code/components/CodeReview.tsx index a2f3dd14..73a0cc74 100644 --- a/kotoed-js/src/main/ts/code/components/CodeReview.tsx +++ b/kotoed-js/src/main/ts/code/components/CodeReview.tsx @@ -1,12 +1,12 @@ import * as React from "react"; -import {Button, Panel, Label} from "react-bootstrap"; +import {Button, Panel, Label, Modal, Form, FormGroup, ControlLabel, FormControl, Radio} from "react-bootstrap"; import FileReview from "./FileReview"; import FileTree from "./FileTree"; import {Comment, FileComments, LostFoundComments as LostFoundCommentsState} from "../state/comments"; import {NodePath} from "../state/blueprintTree"; import {FileNode} from "../state/filetree"; -import {List} from "immutable"; +import {List, Map} from "immutable"; import {LostFoundComments} from "./LostFoundComments"; import {CommentAggregate} from "../remote/comments"; import {UNKNOWN_FILE, UNKNOWN_LINE} from "../remote/constants"; @@ -20,7 +20,12 @@ import AggregatesLabel from "../../views/AggregatesLabel"; import {FileForms, ReviewForms} from "../state/forms"; import {ReviewAnnotations} from "../state/annotations"; import {CommentTemplates} from "../remote/templates"; -import {FileDiffChange} from "../remote/code"; +import {DiffBase, DiffBaseType, FileDiffChange, FileDiffResult} from "../remote/code"; + +import "@fortawesome/fontawesome-free/less/fontawesome.less" +import "@fortawesome/fontawesome-free/less/solid.less" +import "@fortawesome/fontawesome-free/less/regular.less" +import {ChangeEvent} from "react"; export interface CodeReviewProps { submissionId: number @@ -34,7 +39,6 @@ export interface CodeReviewProps { value: string file: string comments: FileComments - diff: Array } fileTree: { @@ -51,12 +55,19 @@ export interface CodeReviewProps { capabilities: { canPostComment: boolean + canViewTags: boolean whoAmI: string } forms: { forms: ReviewForms } + + diff: { + diff: Map + base: DiffBase + loading: boolean + } } interface CodeReviewPropsFromRouting { @@ -89,17 +100,55 @@ export interface CodeReviewCallbacks { lostFound: { onSelect: () => void } + + diff: { + onChangeDiffBase: (submissionId: number, diffBase: DiffBase) => void + } } export type CodeReviewPropsAndCallbacks = CodeReviewProps & CodeReviewCallbacks -export default class CodeReview extends React.Component { +interface CodeReviewState { + showDiffModal: boolean + baseChoice: { + type: DiffBaseType + submissionId: string|null + } +} + +export default class CodeReview extends + React.Component { + + constructor(props: CodeReviewPropsAndCallbacks & CodeReviewPropsFromRouting, context: any) { + super(props, context); + this.state = { + showDiffModal: false, + baseChoice: this.baseToFormState(props.diff.base) + } + } + + + private baseToFormState(base: DiffBase) { + return { + type: base.type, + submissionId: base.submissionId && base.submissionId.toString() || null + } + } makeOriginalLinkOrUndefined = (comment: BaseCommentToRead) => { if (this.props.comments.makeOriginalLink && comment.submissionId !== this.props.submissionId) return this.props.comments.makeOriginalLink(comment) }; + getDiffForEditor = () => { + const fileDiff = this.props.diff.diff.get(this.props.editor.file); + if (!fileDiff) { + return [] + } + + return fileDiff.changes; + } + renderRightSide = () => { switch (this.props.show) { case "lost+found": @@ -118,7 +167,7 @@ export default class CodeReview extends React.Component
-
+
+ +
{this.renderRightSide()}
+ {this.renderModal()} }; shouldRenderReview = () => this.props.submission && this.props.submission.verificationData.status === "Processed"; + renderModal = () => + { + this.setState({ + showDiffModal: false, + baseChoice: this.baseToFormState(this.props.diff.base) + }) + }}> + + Settings + + + + Diff base + this.setState({ + ...this.state, + baseChoice: { + type: "COURSE_BASE", + submissionId: null + } + })}> + Course base revision + + {" "} + this.setState({ + ...this.state, + baseChoice: { + type: "PREVIOUS_CLOSED", + submissionId: null + } + })}> + Latest closed submission + + {this.props.capabilities.canViewTags && this.setState({ + ...this.state, + baseChoice: { + type: "PREVIOUS_CHECKED", + submissionId: null + } + })}> + Latest checked submission + } + this.setState({ + ...this.state, + baseChoice: { + type: "SUBMISSION_ID", + submissionId: this.state.baseChoice.submissionId + } + })}> + Specific submission + + + + {this.state.baseChoice.type == "SUBMISSION_ID" && + Submission Id + ) => { + this.setState({ + ...this.state, + baseChoice: { + type: "SUBMISSION_ID", + submissionId: e.target.value as string || "" + } + }) + }} + /> + + } + + + + + render() { if (!this.props.submission) { diff --git a/kotoed-js/src/main/ts/code/components/FileReview.tsx b/kotoed-js/src/main/ts/code/components/FileReview.tsx index 70deb2ef..7384e13b 100644 --- a/kotoed-js/src/main/ts/code/components/FileReview.tsx +++ b/kotoed-js/src/main/ts/code/components/FileReview.tsx @@ -346,6 +346,12 @@ export default class FileReview extends ComponentWithLoading { + for (let i = 0; i < this.editor.lineCount(); i++) { + this.editor.removeLineClass(toCmLine(i), "background", "mark-line-changed"); + } + } + renderDiffChange = (change: FileDiffChange) => { let lineNumber = change.to.start; for (const lineChange of change.lines) { @@ -493,6 +499,11 @@ export default class FileReview extends ComponentWithLoading
diff --git a/kotoed-js/src/main/ts/code/containers/CodeReviewContainer.tsx b/kotoed-js/src/main/ts/code/containers/CodeReviewContainer.tsx index fa597d9e..0015a184 100644 --- a/kotoed-js/src/main/ts/code/containers/CodeReviewContainer.tsx +++ b/kotoed-js/src/main/ts/code/containers/CodeReviewContainer.tsx @@ -9,7 +9,7 @@ import { dirCollapse, dirExpand, editComment, expandHiddenComments, fileSelect, loadCode, loadLostFound, postComment, resetExpandedForLine, setCommentState, - setCodePath, setLostFoundPath, resetExpandedForLostFound, unselectFile, emphasizeComment + setCodePath, setLostFoundPath, resetExpandedForLostFound, unselectFile, emphasizeComment, updateDiff } from "../actions"; import {CodeReviewState, ScrollTo} from "../state"; import {RouteComponentProps} from "react-router-dom"; @@ -42,7 +42,6 @@ const mapStateToProps = function(store: CodeReviewState, value: store.editorState.value, file: store.editorState.fileName, comments: store.commentsState.comments.get(store.editorState.fileName, FileComments()), - diff: store.editorState.diff }, fileTree: { loading: store.fileTreeState.loading || store.fileTreeState.aggregatesLoading || store.capabilitiesState.loading, @@ -56,10 +55,16 @@ const mapStateToProps = function(store: CodeReviewState, }, capabilities: { canPostComment: store.capabilitiesState.capabilities.permissions.postComment, + canViewTags: store.capabilitiesState.capabilities.permissions.tags, whoAmI: store.capabilitiesState.capabilities.principal.denizenId, }, forms: { forms: store.formState + }, + diff: { + diff: store.diffState.diff, + base: store.diffState.base, + loading: store.diffState.loading } } }; @@ -171,6 +176,16 @@ const mapDispatchToProps = function (dispatch: Dispatch, })); } }, + + diff: { + onChangeDiffBase: (submissionId, diffBase) => { + dispatch(updateDiff({ + diffBase, + submissionId + })) + } + }, + onCodeRoute: (submissionId, filename) => { dispatch(loadCode({ submissionId, diff --git a/kotoed-js/src/main/ts/code/index.tsx b/kotoed-js/src/main/ts/code/index.tsx index 591cc865..197d80da 100644 --- a/kotoed-js/src/main/ts/code/index.tsx +++ b/kotoed-js/src/main/ts/code/index.tsx @@ -13,7 +13,13 @@ import CodeReviewContainer, { } from "./containers/CodeReviewContainer"; import { annotationsReducer, - capabilitiesReducer, commentsReducer, commentTemplateReducer, editorReducer, fileTreeReducer, formReducer, + capabilitiesReducer, + commentsReducer, + commentTemplateReducer, + diffReducer, + editorReducer, + fileTreeReducer, + formReducer, submissionReducer } from "./reducers"; @@ -37,6 +43,7 @@ export const store = createStore( capabilitiesState: capabilitiesReducer, submissionState: submissionReducer, formState: formReducer, + diffState: diffReducer, router: routerReducer }), applyMiddleware(routerMiddleware(history)), @@ -53,4 +60,4 @@ render( , - document.getElementById("code-review-app")); \ No newline at end of file + document.getElementById("code-review-app")); diff --git a/kotoed-js/src/main/ts/code/reducers.ts b/kotoed-js/src/main/ts/code/reducers.ts index 532aa112..3f78b588 100644 --- a/kotoed-js/src/main/ts/code/reducers.ts +++ b/kotoed-js/src/main/ts/code/reducers.ts @@ -9,16 +9,16 @@ import { fileSelect, rootFetch, commentAggregatesFetch, aggregatesUpdate, capabilitiesFetch, hiddenCommentsExpand, expandedResetForFile, expandedResetForLine, commentEdit, fileUnselect, expandedResetForLostFound, commentEmphasize, - submissionFetch, annotationsFetch, commentTemplateFetch, fileDiff + submissionFetch, annotationsFetch, commentTemplateFetch, fileDiff, diffFetch } from "./actions"; import { ADD_DELTA, - addAggregates, CLOSE_DELTA, makeFileNode, OPEN_DELTA, registerAddComment, registerCloseComment, + addAggregates, applyDiffToFileTree, CLOSE_DELTA, makeFileNode, OPEN_DELTA, registerAddComment, registerCloseComment, registerOpenComment, updateAggregate } from "./util/filetree"; import {NodePath} from "./state/blueprintTree"; import {UNKNOWN_FILE, UNKNOWN_LINE} from "./remote/constants"; -import {List} from "immutable"; +import {List, Map} from "immutable"; import {DbRecordWrapper} from "../data/verification"; import {SubmissionToRead} from "../data/submission"; import {SubmissionState} from "./state/submission"; @@ -26,6 +26,8 @@ import {DEFAULT_FORM_STATE, FileForms, ReviewForms} from "./state/forms"; import {CodeAnnotationsState, ReviewAnnotations} from "./state/annotations"; import {CommentTemplateState} from "./state/templates"; import {CommentTemplates} from "./remote/templates"; +import {DiffBase, fetchDiff, FileDiffResult} from "./remote/code"; +import {DiffState} from "./state/diff"; const initialFileTreeState: FileTreeState = { root: FileNode({ @@ -86,8 +88,13 @@ export const fileTreeReducer = (state: FileTreeState = initialFileTreeState, act } else if (isType(action, rootFetch.done)) { let newState = {...state}; newState.root = makeFileNode(action.payload.result.root); + // Do not copy the state since it has not been published yet + applyDiffToFileTree(newState.root, action.payload.result.diff, false); newState.loading = false; return newState; + } else if (isType(action, diffFetch.done)) { + let newState = {...state} + newState.root = FileNode(applyDiffToFileTree(state.root, action.payload.result)) } else if (isType(action, commentAggregatesFetch.done)) { let newState = {...state}; newState.root = addAggregates(newState.root, action.payload.result); @@ -128,15 +135,15 @@ export const fileTreeReducer = (state: FileTreeState = initialFileTreeState, act return state; }; -const defaultEditorState = { +const defaultEditorState: EditorState = { value: "", fileName: "", displayedComments: FileComments(), - mode: {}, loading: false, diff: [] }; + export const editorReducer = (state: EditorState = defaultEditorState, action: Action) => { if (isType(action, fileLoad.started)) { let newState = {...state}; @@ -158,6 +165,46 @@ export const editorReducer = (state: EditorState = defaultEditorState, action: A return state; }; +const defaultDiffState: DiffState = { + diff: Map(), + loading: false, + base: { + type: "PREVIOUS_CLOSED" + } +} + +function fileDiffToMap(diff: Array) { + return Map().withMutations(mutable => { + for (const diffEntry of diff) { + mutable.set(diffEntry.toFile, diffEntry) + } + }) + +} + +export const diffReducer = (state: DiffState = defaultDiffState, action: Action): DiffState => { + if (isType(action, diffFetch.done)) { + return { + loading: false, + base: action.payload.params.diffBase, + diff: fileDiffToMap(action.payload.result) + } + } else if (isType(action, diffFetch.started)) { + return { + loading: true, + base: action.payload.diffBase, + diff: state.diff + } + } else if (isType(action, rootFetch.done)) { + return { + loading: false, + base: state.base, + diff: fileDiffToMap(action.payload.result.diff) + } + } + return state +} + export const defaultCommentsState = { comments: ReviewComments(), lostFound: LostFoundComments(), @@ -390,4 +437,4 @@ export const formReducer = (state: ReviewForms = defaultFormState, action: Actio } else { return state.setIn([sourcefile, sourceline], {processing}); } -}; \ No newline at end of file +}; diff --git a/kotoed-js/src/main/ts/code/remote/code.ts b/kotoed-js/src/main/ts/code/remote/code.ts index 182dfaca..ae26e861 100644 --- a/kotoed-js/src/main/ts/code/remote/code.ts +++ b/kotoed-js/src/main/ts/code/remote/code.ts @@ -4,6 +4,8 @@ import {eventBus} from "../../eventBus"; import {ResponseWithStatus, SubmissionIdRequest} from "./common"; import {Kotoed} from "../../util/kotoed-api"; import {sendAsync} from "../../views/components/common"; +import {Generated} from "../../util/kotoed-generated"; +import ApiBindingInputs = Generated.ApiBindingInputs; export type FileType = "file" | "directory" @@ -52,10 +54,17 @@ interface FileResponse extends ResponseWithStatus { contents: string } -interface FileDiffResponse extends ResponseWithStatus { +export interface FileDiffResponse extends ResponseWithStatus { diff: Array } + +export type DiffBaseType = 'SUBMISSION_ID' | 'PREVIOUS_CLOSED' | 'PREVIOUS_CHECKED' | 'COURSE_BASE'; +export interface DiffBase { + submissionId?: number, + type: DiffBaseType +} + type IsReadyRequest = SubmissionIdRequest type IsReadyResponse = ResponseWithStatus @@ -73,10 +82,12 @@ async function repeatTillReady(doRequest: () => Pr } } -export async function fetchRootDir(submissionId: number): Promise { +export async function fetchRootDir(submissionId: number, + diffBase: DiffBase): Promise { let res = await repeatTillReady(() => { return sendAsync(Kotoed.Address.Api.Submission.Code.List, { - submissionId: submissionId + submissionId: submissionId, + diffBase }) }); return res.root!; @@ -98,11 +109,13 @@ export async function fetchFile(submissionId: number, return res.contents; } -export async function fetchDiff(submissionId: number): Promise> { +export async function fetchDiff(submissionId: number, + base: DiffBase): Promise> { let res = await repeatTillReady(() => { return sendAsync(Kotoed.Address.Api.Submission.Code.Diff, { - submissionId: submissionId + submissionId: submissionId, + base }); }); return res.diff; diff --git a/kotoed-js/src/main/ts/code/state/diff.ts b/kotoed-js/src/main/ts/code/state/diff.ts new file mode 100644 index 00000000..3d55fad4 --- /dev/null +++ b/kotoed-js/src/main/ts/code/state/diff.ts @@ -0,0 +1,8 @@ +import {DiffBase, FileDiffResult} from "../remote/code"; +import {Map} from "immutable"; + +export interface DiffState { + loading: boolean + base: DiffBase + diff: Map +} diff --git a/kotoed-js/src/main/ts/code/state/editor.ts b/kotoed-js/src/main/ts/code/state/editor.ts index ae0298d7..6494357c 100644 --- a/kotoed-js/src/main/ts/code/state/editor.ts +++ b/kotoed-js/src/main/ts/code/state/editor.ts @@ -1,8 +1,10 @@ import {FileDiffChange} from "../remote/code"; +import {FileComments} from "./comments"; export interface EditorState { fileName: string value: string + displayedComments: FileComments, loading: boolean diff: Array } diff --git a/kotoed-js/src/main/ts/code/state/index.ts b/kotoed-js/src/main/ts/code/state/index.ts index 5201d3ba..b1165553 100644 --- a/kotoed-js/src/main/ts/code/state/index.ts +++ b/kotoed-js/src/main/ts/code/state/index.ts @@ -8,6 +8,9 @@ import {SubmissionState} from "./submission"; import {ReviewForms} from "./forms"; import {CodeAnnotationsState} from "./annotations"; import {CommentTemplateState} from "./templates"; +import {DiffBase, FileDiffResult} from "../remote/code"; +import {Map} from "immutable" +import {DiffState} from "./diff"; export interface CodeReviewState { fileTreeState: FileTreeState @@ -18,9 +21,10 @@ export interface CodeReviewState { capabilitiesState: CapabilitiesState submissionState: SubmissionState formState: ReviewForms + diffState: DiffState } export interface ScrollTo { line?: number commentId?: number -} \ No newline at end of file +} diff --git a/kotoed-js/src/main/ts/code/util/filetree.tsx b/kotoed-js/src/main/ts/code/util/filetree.tsx index 7c7bb6a1..4a469dd1 100644 --- a/kotoed-js/src/main/ts/code/util/filetree.tsx +++ b/kotoed-js/src/main/ts/code/util/filetree.tsx @@ -1,6 +1,6 @@ import * as React from "react" import {Button, Panel, Label, OverlayTrigger, Tooltip} from "react-bootstrap"; -import {File} from "../remote/code"; +import {File, FileDiffResult} from "../remote/code"; import {IconClasses, Intent, Spinner} from "@blueprintjs/core"; import {CommentAggregate, CommentAggregates} from "../remote/comments"; import {FileNode, FileNodeProps, FileTreeProps, LoadingNode} from "../state/filetree"; @@ -8,6 +8,8 @@ import {NodePath} from "../state/blueprintTree"; import {FileNotFoundError} from "../errors"; import {ICON} from "@blueprintjs/core/dist/common/classes"; import AggregatesLabel from "../../views/AggregatesLabel"; +import * as _ from "lodash"; +import {Set} from "immutable" export function makeLoadingNode(idGen: (() => number)): LoadingNode { return { @@ -54,6 +56,38 @@ export function makeFileTreeProps(file: File, idGen: (() => number)|null = null) return bpNode; } +export function changedFilesAndDirs(diff: Array): Set { + return Set().withMutations(mutable => { + for (const diffItem of diff) { + const fileNameChunks = diffItem.toFile.split("/") + for (let i = 0; i < fileNameChunks.length; i++) { + mutable.add(fileNameChunks.slice(0, i + 1).join("/")); + } + } + }); +} + +function applyDiffToFileTreeHelper(props: FileNodeProps, changedFiles: Set, basePath: string|null = null) { + const isChanged = basePath ? changedFiles.contains(basePath) : false; + const iconType = isChanged ? "changed " : ""; + const nodeClass = isChanged ? "pt-tree-node-changed" : "" + + props.className = nodeClass; + props.iconName = iconType + (props.data.type == "file" ? IconClasses.DOCUMENT : IconClasses.FOLDER_CLOSE); + + if (props.childNodes) { + for (const child of props.childNodes) { + applyDiffToFileTreeHelper(child, changedFiles, + basePath ? basePath + "/" + child.data.filename : child.data.filename) + } + } + return props; +} + +export function applyDiffToFileTree(props: FileNodeProps, diff: Array, clone: boolean = true) { + return applyDiffToFileTreeHelper(clone ? _.cloneDeep(props) : props, changedFilesAndDirs(diff)) +} + export function makeFileNode(file: File) { return FileNode(makeFileTreeProps(file)); } diff --git a/kotoed-js/src/main/ts/hello.ts b/kotoed-js/src/main/ts/hello.ts deleted file mode 100644 index 516db687..00000000 --- a/kotoed-js/src/main/ts/hello.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Created by gagarski on 6/27/17. - */ - -import * as $ from 'jquery'; - -import "less/kotoed-bootstrap/bootstrap.less"; -import "bootstrap"; - - -$(function() { - -}); diff --git a/kotoed-js/src/main/ts/projects/create.tsx b/kotoed-js/src/main/ts/projects/create.tsx index 9b360acf..43ed8ad5 100644 --- a/kotoed-js/src/main/ts/projects/create.tsx +++ b/kotoed-js/src/main/ts/projects/create.tsx @@ -206,4 +206,4 @@ export class ProjectCreate extends ComponentWithLocalErrors ; } -} \ No newline at end of file +} diff --git a/kotoed-js/webpack.config.base.ts b/kotoed-js/webpack.config.base.ts index e42e12ea..0979ceec 100644 --- a/kotoed-js/webpack.config.base.ts +++ b/kotoed-js/webpack.config.base.ts @@ -35,7 +35,6 @@ const config: webpack.Configuration = { context: srcMain, entry: { - hello: kotoedEntry("./ts/hello.ts"), profile: kotoedEntry("./ts/profile/index.tsx"), profileEdit: kotoedEntry("./ts/profile/edit.tsx"), login: kotoedEntry("./ts/login/index.tsx", false), diff --git a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt index 6a1a9920..c851f2be 100644 --- a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt +++ b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt @@ -1,5 +1,7 @@ package org.jetbrains.research.kotoed.api +import io.vertx.core.json.JsonObject +import org.jetbrains.research.kotoed.data.api.Code import org.jetbrains.research.kotoed.data.api.Code.FileRecord import org.jetbrains.research.kotoed.data.api.Code.FileType.directory import org.jetbrains.research.kotoed.data.api.Code.FileType.file @@ -7,10 +9,13 @@ import org.jetbrains.research.kotoed.data.api.Code.ListResponse import org.jetbrains.research.kotoed.data.api.Code.Submission.RemoteRequest import org.jetbrains.research.kotoed.data.db.ComplexDatabaseQuery import org.jetbrains.research.kotoed.data.vcs.* +import org.jetbrains.research.kotoed.database.Tables import org.jetbrains.research.kotoed.database.enums.SubmissionState import org.jetbrains.research.kotoed.database.tables.records.CourseRecord import org.jetbrains.research.kotoed.database.tables.records.ProjectRecord import org.jetbrains.research.kotoed.database.tables.records.SubmissionRecord +import org.jetbrains.research.kotoed.database.tables.records.TagRecord +import org.jetbrains.research.kotoed.db.condition.lang.formatToQuery import org.jetbrains.research.kotoed.eventbus.Address import org.jetbrains.research.kotoed.util.* import org.jetbrains.research.kotoed.util.database.toRecord @@ -140,7 +145,7 @@ class SubmissionCodeVerticle : AbstractKotoedVerticle() { else -> { } } - val diff = submissionCodeDiff(submission, repoInfo) + val diff = submissionCodeDiff(submission, repoInfo, message.base) return SubDiffResponse(diff = diff.contents, status = repoInfo.cloneStatus) } @@ -238,6 +243,8 @@ class SubmissionCodeVerticle : AbstractKotoedVerticle() { suspend fun handleSubmissionCodeList(message: SubListRequest): ListResponse { val submission: SubmissionRecord = dbFetchAsync(SubmissionRecord().apply { id = message.submissionId }) val repoInfo = getCommitInfo(submission) + val base = message.diffBase + when (repoInfo.cloneStatus) { CloneStatus.pending -> return ListResponse(root = null, status = repoInfo.cloneStatus) CloneStatus.failed -> throw NotFound("Repository not found") @@ -252,7 +259,7 @@ class SubmissionCodeVerticle : AbstractKotoedVerticle() { revision = repoInfo.revision ) ) - val diff = submissionCodeDiff(submission, repoInfo) + val diff = submissionCodeDiff(submission, repoInfo, base) return ListResponse( root = buildCodeTree( innerResp.files, @@ -262,40 +269,9 @@ class SubmissionCodeVerticle : AbstractKotoedVerticle() { ) } - private suspend fun submissionCodeDiff(submission: SubmissionRecord, repoInfo: CommitInfo): DiffResponse { - val closedSubs = dbFindAsync(SubmissionRecord().apply { - projectId = submission.projectId - state = SubmissionState.closed - }) - - val foundationSub = closedSubs.filter { - it.datetime < submission.datetime - }.sortedByDescending { it.datetime }.firstOrNull() - - var baseRev = foundationSub?.revision - - if (baseRev == null) { - val course: CourseRecord = - dbQueryAsync( - ComplexDatabaseQuery(ProjectRecord().apply { id = submission.projectId }).join("course") - ).first().getJsonObject("course").toRecord() - - baseRev = if (course.baseRevision != "") course.baseRevision else null - - if (baseRev != null) try { - run { - sendJsonableAsync( - Address.Code.List, - InnerListRequest( - uid = repoInfo.repo.uid, - revision = baseRev - ) - ) - } - } catch (ex: Exception) { - baseRev = null - } - } + private suspend fun submissionCodeDiff(submission: SubmissionRecord, repoInfo: CommitInfo, + base: SubDiffRequest.DiffBase): DiffResponse { + val baseRev = base.getBaseRev(submission) return when (baseRev) { null -> DiffResponse(listOf()) @@ -326,4 +302,91 @@ class SubmissionCodeVerticle : AbstractKotoedVerticle() { ) } + private suspend fun SubDiffRequest.DiffBase.getBaseRev(submission: SubmissionRecord): String? = + when (type) { + Code.Submission.DiffBaseType.SUBMISSION_ID -> dbFetchAsync(SubmissionRecord().apply { + projectId = submission.projectId + id = submissionId + })?.revision + Code.Submission.DiffBaseType.PREVIOUS_CHECKED -> submission.getLatestCheckedRev() + Code.Submission.DiffBaseType.PREVIOUS_CLOSED -> submission.getPreviousClosedOrCourseRev() + Code.Submission.DiffBaseType.COURSE_BASE -> submission.getCourseBaseRev() + } + private suspend fun SubmissionRecord.getLatestClosedSub(): SubmissionRecord? = + dbQueryAsync( + ComplexDatabaseQuery(Tables.SUBMISSION) + .filter("project_id == %s and state == %s and datetime < %s" + .formatToQuery(projectId, SubmissionState.closed, datetime)) + ) + + .asSequence() + .map { + it.toRecord() + } + .filter { + it.datetime < datetime + } + .sortedByDescending { + it.datetime + } + .firstOrNull() + private suspend fun SubmissionRecord.getCourseBaseRev(): String? = + dbQueryAsync( + ComplexDatabaseQuery(ProjectRecord().apply { id = projectId }).join("course") + ) + .first() + .getJsonObject("course") + .toRecord() + .let { + if (it.baseRevision != "") it.baseRevision else null + } + + private suspend fun SubmissionRecord.getLatestCheckedRev(): String? { + val latestClosed = getLatestClosedSub() // We consider closed as checked here + val q = "project_id == %s " + + (latestClosed?.datetime?.let { "and datetime > %s" } ?: "") + val qArgs = sequence { + yield(projectId) + latestClosed?.datetime?.let { + yield(it) + } + }.toList().toTypedArray() + + val newerThanClosed = dbQueryAsync( + ComplexDatabaseQuery(Tables.SUBMISSION) + .rjoin(ComplexDatabaseQuery(Tables.SUBMISSION_TAG) + .join(Tables.TAG), "submission_id", "tags") + .filter(q.formatToQuery(*qArgs)) + ) + + val byId = newerThanClosed + .asSequence().map { + it.toRecord(SubmissionRecord::class) + }.associateBy { + it.id + } + + val tagsById = newerThanClosed + .asSequence() + .map { + it.getInteger("id") to it.getJsonArray("tags").asSequence().map { + it.uncheckedCast().getJsonObject("tag").toRecord().name + }.toSet() + } + .toMap() + + var current = this + do { + current = byId[current.parentSubmissionId] ?: return latestClosed?.revision + val tags = tagsById[current.id] ?: return latestClosed?.revision + if (CHECKED in tags) return current.revision + } while(true) + + } + private suspend fun SubmissionRecord.getPreviousClosedOrCourseRev(): String? = + getLatestClosedSub()?.revision ?: getCourseBaseRev() + + companion object { + private const val CHECKED = "checked" + } } diff --git a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/code/klones/KloneVerticle.kt b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/code/klones/KloneVerticle.kt index 13be5645..2256983e 100644 --- a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/code/klones/KloneVerticle.kt +++ b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/code/klones/KloneVerticle.kt @@ -167,7 +167,8 @@ class KloneVerticle : AbstractKotoedVerticle(), Loggable { val files: Code.ListResponse = sendJsonableAsync( Address.Api.Submission.Code.List, - Code.Submission.ListRequest(sub.getInteger("id")) + Code.Submission.ListRequest(sub.getInteger("id"), + Code.Submission.DiffRequest.DiffBase(Code.Submission.DiffBaseType.PREVIOUS_CLOSED)) ) return handleFiles( diff --git a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/data/api/Data.kt b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/data/api/Data.kt index 0ea927fa..316109e8 100644 --- a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/data/api/Data.kt +++ b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/data/api/Data.kt @@ -7,15 +7,12 @@ import org.jetbrains.research.kotoed.data.vcs.CloneStatus import org.jetbrains.research.kotoed.database.enums.SubmissionCommentState import org.jetbrains.research.kotoed.database.tables.records.SubmissionCommentRecord import org.jetbrains.research.kotoed.database.tables.records.TagRecord -import org.jetbrains.research.kotoed.util.database.toJson import org.jooq.Record import java.util.* import org.jetbrains.research.kotoed.data.buildSystem.BuildCommand import org.jetbrains.research.kotoed.database.tables.records.BuildTemplateRecord import org.jetbrains.research.kotoed.util.* -import ru.spbstu.ktuples.Tuple -import ru.spbstu.ktuples.Tuple2 enum class VerificationStatus { Unknown, @@ -88,9 +85,19 @@ object Code { val fromLine: Int? = null, val toLine: Int? = null) : Jsonable data class ReadResponse(val contents: String, val status: CloneStatus) : Jsonable - data class ListRequest(val submissionId: Int) : Jsonable - data class DiffRequest(val submissionId: Int) : Jsonable + data class ListRequest(val submissionId: Int, val diffBase: DiffRequest.DiffBase) : Jsonable + data class DiffRequest(val submissionId: Int, val base: DiffBase) : Jsonable { + class DiffBase(val type: DiffBaseType, val submissionId: Int? = null) : Jsonable { + init { + require((type == DiffBaseType.SUBMISSION_ID) == (submissionId != null)) + } + } + } data class DiffResponse(val diff: List, val status: CloneStatus) : Jsonable + + enum class DiffBaseType { + SUBMISSION_ID, PREVIOUS_CLOSED, PREVIOUS_CHECKED, COURSE_BASE + } } object Course { diff --git a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/web/eventbus/guardian/Diff.kt b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/web/eventbus/guardian/Diff.kt new file mode 100644 index 00000000..fbb1a287 --- /dev/null +++ b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/web/eventbus/guardian/Diff.kt @@ -0,0 +1,28 @@ +package org.jetbrains.research.kotoed.web.eventbus.guardian + +import io.vertx.core.Vertx +import io.vertx.ext.web.handler.sockjs.BridgeEvent +import org.jetbrains.research.kotoed.data.api.Code +import org.jetbrains.research.kotoed.database.enums.SubmissionState +import org.jetbrains.research.kotoed.util.scope +import org.jetbrains.research.kotoed.web.auth.isProjectOwner +import org.jetbrains.research.kotoed.web.auth.isSubmissionOwner +import org.jetbrains.research.kotoed.web.eventbus.submissionByIdOrNull + +class ShouldNotRequestLastChecked( + val vertx: Vertx, +) : LoggingBridgeEventFilter() { + override suspend fun checkIsAllowed(be: BridgeEvent): Boolean { + val diffType = be.rawMessage + ?.getJsonObject("body") + ?.getJsonObject("base") + ?.getString("type") + ?: return false + + return diffType != Code.Submission.DiffBaseType.PREVIOUS_CHECKED.toString(); + } + + override fun toString(): String { + return "ShouldBeSubmissionOwner(vertx=$vertx)" + } +} diff --git a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/web/eventbus/guardian/Kotoed.kt b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/web/eventbus/guardian/Kotoed.kt index 98627dc3..c22ce5b6 100644 --- a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/web/eventbus/guardian/Kotoed.kt +++ b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/web/eventbus/guardian/Kotoed.kt @@ -38,6 +38,13 @@ fun ProjectOwnerOrTeacherForFilter(vertx: Vertx, path: String = "project_id"): B fun SubmissionOwnerOrTeacher(vertx: Vertx, path: String = "submission_id"): BridgeEventFilter = ShouldBeSubmissionOwner(vertx, path) or AuthorityRequired(Authority.Teacher) +fun DiffFilter(vertx: Vertx): BridgeEventFilter { + val shouldBeTeacher = AuthorityRequired(Authority.Teacher) + val ownerWithCondition = ShouldBeSubmissionOwner(vertx) and ShouldNotRequestLastChecked(vertx) + val subShouldBeReady = SubmissionReady(vertx) + return (shouldBeTeacher or ownerWithCondition) and subShouldBeReady +} + fun SubmissionOwnerOrTeacherForFilter(vertx: Vertx, path: String = "submission_id"): BridgeEventFilter = ShouldBeSubmissionOwnerForFilter(vertx, path) or AuthorityRequired(Authority.Teacher) @@ -97,8 +104,7 @@ fun kotoedPerAddressFilter(vertx: Vertx): PerAddress { (SubmissionOwnerOrTeacher(vertx) and SubmissionReady(vertx)), Address.Api.Submission.Code.Read to (SubmissionOwnerOrTeacher(vertx) and SubmissionReady(vertx)), - Address.Api.Submission.Code.Diff to - (SubmissionOwnerOrTeacher(vertx) and SubmissionReady(vertx)), + Address.Api.Submission.Code.Diff to DiffFilter(vertx), Address.Api.Submission.Comment.Create to (SubmissionOwnerOrTeacher(vertx) and SubmissionOpen(vertx)), From 7875db4075e06cd7859593d10045fa538bea7f0a Mon Sep 17 00:00:00 2001 From: Kirill Date: Mon, 19 Sep 2022 23:04:02 +0300 Subject: [PATCH 02/11] Remove diff functionality from code list endpoints --- kotoed-js/src/main/ts/code/remote/code.ts | 1 - kotoed-js/src/main/ts/code/util/filetree.tsx | 5 --- .../kotoed/api/SubmissionCodeVerticle.kt | 31 +++++-------------- .../research/kotoed/data/api/Data.kt | 3 +- 4 files changed, 8 insertions(+), 32 deletions(-) diff --git a/kotoed-js/src/main/ts/code/remote/code.ts b/kotoed-js/src/main/ts/code/remote/code.ts index ae26e861..dcaae82e 100644 --- a/kotoed-js/src/main/ts/code/remote/code.ts +++ b/kotoed-js/src/main/ts/code/remote/code.ts @@ -12,7 +12,6 @@ export type FileType = "file" | "directory" export interface File { type: FileType; name: string, - changed: boolean, children?: Array } diff --git a/kotoed-js/src/main/ts/code/util/filetree.tsx b/kotoed-js/src/main/ts/code/util/filetree.tsx index 4a469dd1..ed767133 100644 --- a/kotoed-js/src/main/ts/code/util/filetree.tsx +++ b/kotoed-js/src/main/ts/code/util/filetree.tsx @@ -25,17 +25,12 @@ export function makeFileTreeProps(file: File, idGen: (() => number)|null = null) let id = 0; let idGenF = idGen ? idGen : () => {return ++id;}; - let iconType = (file.changed) ? "changed " : ""; - let nodeClass = (file.changed) ? "pt-tree-node-changed" : ""; - let bpNode: FileNodeProps = { id: idGenF(), isExpanded: false, isSelected: false, label: file.name, hasCaret: file.type === "directory", - className: nodeClass, - iconName: iconType + (file.type == "file" ? IconClasses.DOCUMENT : IconClasses.FOLDER_CLOSE), childNodes: [], data: { kind: "file", diff --git a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt index c851f2be..e8bc9562 100644 --- a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt +++ b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt @@ -152,7 +152,6 @@ class SubmissionCodeVerticle : AbstractKotoedVerticle() { // Feel da powa of Kotlin! private data class MutableCodeTree( private val data: MutableMap = mutableMapOf(), - var changed: Boolean = false ) : MutableMap by data { // it's over 9000! private val fileComparator = compareBy { it.type }.thenBy { it.name } @@ -162,31 +161,28 @@ class SubmissionCodeVerticle : AbstractKotoedVerticle() { FileRecord( type = directory, name = "$name/${children[0].name}", - children = children[0].children, - changed = changed + children = children[0].children ) else this private fun Map.Entry.toFileRecord(): FileRecord = - if (value.isEmpty()) FileRecord(type = file, name = key, changed = value.changed) + if (value.isEmpty()) FileRecord(type = file, name = key) else FileRecord( type = directory, name = key, - children = value.map { it.toFileRecord() }.sortedWith(fileComparator), - changed = value.changed + children = value.map { it.toFileRecord() }.sortedWith(fileComparator) ).squash() fun toFileRecord() = FileRecord( type = directory, name = "", - children = map { it.toFileRecord() }.sortedWith(fileComparator), - changed = changed + children = map { it.toFileRecord() }.sortedWith(fileComparator) ) } - private fun buildCodeTree(files: List, changedFiles: List): FileRecord { + private fun buildCodeTree(files: List): FileRecord { val mutableCodeTree = MutableCodeTree() // this is not overly efficient, but who cares @@ -198,16 +194,6 @@ class SubmissionCodeVerticle : AbstractKotoedVerticle() { } } - for (file in changedFiles) { - val path = file.split('/', '\\') - var current = mutableCodeTree - for (crumb in path) { - current.changed = true - current = current[crumb] ?: break // do not mark removed files or "/dev/null" - current.changed = true - } - } - return mutableCodeTree.toFileRecord() } @@ -232,8 +218,7 @@ class SubmissionCodeVerticle : AbstractKotoedVerticle() { return ListResponse( root = buildCodeTree( - innerResp.files, - emptyList() + innerResp.files ), status = repoInfo.cloneStatus ) @@ -259,11 +244,9 @@ class SubmissionCodeVerticle : AbstractKotoedVerticle() { revision = repoInfo.revision ) ) - val diff = submissionCodeDiff(submission, repoInfo, base) return ListResponse( root = buildCodeTree( - innerResp.files, - diff.contents.flatMap { listOf(it.fromFile, it.toFile) } + innerResp.files ), status = repoInfo.cloneStatus ) diff --git a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/data/api/Data.kt b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/data/api/Data.kt index 316109e8..790e17c7 100644 --- a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/data/api/Data.kt +++ b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/data/api/Data.kt @@ -110,8 +110,7 @@ object Code { data class FileRecord( val type: FileType, val name: String, - val children: List? = null, - val changed: Boolean = false) : Jsonable { + val children: List? = null) : Jsonable { fun toFileSeq(): Sequence = when (type) { FileType.directory -> From da7387cc266691361efac808166afdd94abeb84c Mon Sep 17 00:00:00 2001 From: Kirill Date: Mon, 19 Sep 2022 23:15:15 +0300 Subject: [PATCH 03/11] bugfix --- kotoed-js/src/main/ts/code/reducers.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/kotoed-js/src/main/ts/code/reducers.ts b/kotoed-js/src/main/ts/code/reducers.ts index 3f78b588..ef6ae615 100644 --- a/kotoed-js/src/main/ts/code/reducers.ts +++ b/kotoed-js/src/main/ts/code/reducers.ts @@ -95,6 +95,7 @@ export const fileTreeReducer = (state: FileTreeState = initialFileTreeState, act } else if (isType(action, diffFetch.done)) { let newState = {...state} newState.root = FileNode(applyDiffToFileTree(state.root, action.payload.result)) + return newState; } else if (isType(action, commentAggregatesFetch.done)) { let newState = {...state}; newState.root = addAggregates(newState.root, action.payload.result); From e37d60ae268de9af728f0c26972fc73b00a262ba Mon Sep 17 00:00:00 2001 From: Kirill Date: Tue, 20 Sep 2022 00:56:16 +0300 Subject: [PATCH 04/11] Status bar to display diff range --- kotoed-js/src/main/less/code.less | 14 ++- kotoed-js/src/main/ts/code/actions.ts | 13 +- .../main/ts/code/components/CodeReview.tsx | 118 ++++++++++++------ .../code/containers/CodeReviewContainer.tsx | 6 +- kotoed-js/src/main/ts/code/reducers.ts | 14 +-- kotoed-js/src/main/ts/code/remote/code.ts | 13 +- kotoed-js/src/main/ts/code/state/diff.ts | 4 +- .../kotoed/api/SubmissionCodeVerticle.kt | 78 +++++++----- .../research/kotoed/data/api/Data.kt | 15 ++- 9 files changed, 178 insertions(+), 97 deletions(-) diff --git a/kotoed-js/src/main/less/code.less b/kotoed-js/src/main/less/code.less index a85f2de4..3e676cd2 100644 --- a/kotoed-js/src/main/less/code.less +++ b/kotoed-js/src/main/less/code.less @@ -61,11 +61,16 @@ } #code-review-app { + display: flex; + flex: 1 1 100%; +} + +.code-review-app-rows { flex: 1 1 auto; min-height: 0px; overflow: hidden; display: flex; - flex-flow: row; + flex-flow: column; } .code-review { @@ -76,6 +81,13 @@ border-top: 1px solid #ccc; } +.code-review-status-bar { + flex: 0 0 1.2em; + flex-flow: row; + border-top: 1px solid #ccc; +} + + #code-review-left { display: flex; flex: none; diff --git a/kotoed-js/src/main/ts/code/actions.ts b/kotoed-js/src/main/ts/code/actions.ts index ec62ea51..16b4c755 100644 --- a/kotoed-js/src/main/ts/code/actions.ts +++ b/kotoed-js/src/main/ts/code/actions.ts @@ -152,8 +152,7 @@ export const submissionFetch = actionCreator.async('ROOT_FETCH'); export const fileLoad = actionCreator.async('FILE_LOAD'); -export const fileDiff = actionCreator.async('FILE_DIFF'); -export const diffFetch = actionCreator.async('DIFF_FETCH') +export const diffFetch = actionCreator.async('DIFF_FETCH') // Annotation fetch actions export const annotationsFetch = actionCreator.async('ANNOTATION_FETCH'); @@ -268,10 +267,16 @@ export function fetchRootDirIfNeeded(payload: SubmissionPayload) { }, result: { root, - diff + diff: diff.diff } })) - + dispatch(diffFetch.done({ + params: { + submissionId: payload.submissionId, + diffBase: state.diffState.base + }, + result: diff + })) }; } diff --git a/kotoed-js/src/main/ts/code/components/CodeReview.tsx b/kotoed-js/src/main/ts/code/components/CodeReview.tsx index 73a0cc74..273b5199 100644 --- a/kotoed-js/src/main/ts/code/components/CodeReview.tsx +++ b/kotoed-js/src/main/ts/code/components/CodeReview.tsx @@ -20,12 +20,13 @@ import AggregatesLabel from "../../views/AggregatesLabel"; import {FileForms, ReviewForms} from "../state/forms"; import {ReviewAnnotations} from "../state/annotations"; import {CommentTemplates} from "../remote/templates"; -import {DiffBase, DiffBaseType, FileDiffChange, FileDiffResult} from "../remote/code"; +import {DiffBase, DiffBaseType, FileDiffChange, FileDiffResult, RevisionInfo} from "../remote/code"; import "@fortawesome/fontawesome-free/less/fontawesome.less" import "@fortawesome/fontawesome-free/less/solid.less" import "@fortawesome/fontawesome-free/less/regular.less" import {ChangeEvent} from "react"; +import {DiffState} from "../state/diff"; export interface CodeReviewProps { submissionId: number @@ -63,11 +64,7 @@ export interface CodeReviewProps { forms: ReviewForms } - diff: { - diff: Map - base: DiffBase - loading: boolean - } + diff: DiffState } interface CodeReviewPropsFromRouting { @@ -201,47 +198,90 @@ export default class CodeReview extends renderReview = () => { - return
-
- {this.renderFileTreeVeil()} -
- -
- -
-
- + return
+
+
+ {this.renderFileTreeVeil()} +
+ +
+ +
+
+ +
+
+ {this.renderRightSide()} +
+ {this.renderModal()}
-
- {this.renderRightSide()} -
- {this.renderModal()} + {this.renderStatusBar()}
}; shouldRenderReview = () => this.props.submission && this.props.submission.verificationData.status === "Processed"; + renderStatusBar = () => +
+ {this.renderDiffStatus()} +
+
+ + shrinkRev = (rev: string) => rev.substring(0, 10) + + renderSubLink = (id?: number) => { + if (!id) { + return undefined; + } + + return {" "}(Sub #{id}) + } + + renderSubText = (id?: number) => { + if (!id) { + return undefined; + } + return ` (Sub #${id})` + } + + renderDiffStatus = () => { + if (!this.props.diff.from || !this.props.diff.to) + return undefined; + + if (this.props.diff.from.revision === this.props.diff.to.revision) + return undefined; + + return
+ {"Showing diff "} + {this.shrinkRev(this.props.diff.from.revision)} + {this.renderSubLink(this.props.diff.from.submissionId)} + {" .. "} + {this.shrinkRev(this.props.diff.to.revision)} + {this.renderSubText(this.props.diff.to.submissionId)} +
+ } + renderModal = () => { this.setState({ diff --git a/kotoed-js/src/main/ts/code/containers/CodeReviewContainer.tsx b/kotoed-js/src/main/ts/code/containers/CodeReviewContainer.tsx index 0015a184..f9c961f3 100644 --- a/kotoed-js/src/main/ts/code/containers/CodeReviewContainer.tsx +++ b/kotoed-js/src/main/ts/code/containers/CodeReviewContainer.tsx @@ -61,11 +61,7 @@ const mapStateToProps = function(store: CodeReviewState, forms: { forms: store.formState }, - diff: { - diff: store.diffState.diff, - base: store.diffState.base, - loading: store.diffState.loading - } + diff: {...store.diffState} } }; diff --git a/kotoed-js/src/main/ts/code/reducers.ts b/kotoed-js/src/main/ts/code/reducers.ts index ef6ae615..3e234c98 100644 --- a/kotoed-js/src/main/ts/code/reducers.ts +++ b/kotoed-js/src/main/ts/code/reducers.ts @@ -9,7 +9,7 @@ import { fileSelect, rootFetch, commentAggregatesFetch, aggregatesUpdate, capabilitiesFetch, hiddenCommentsExpand, expandedResetForFile, expandedResetForLine, commentEdit, fileUnselect, expandedResetForLostFound, commentEmphasize, - submissionFetch, annotationsFetch, commentTemplateFetch, fileDiff, diffFetch + submissionFetch, annotationsFetch, commentTemplateFetch, diffFetch } from "./actions"; import { ADD_DELTA, @@ -94,7 +94,7 @@ export const fileTreeReducer = (state: FileTreeState = initialFileTreeState, act return newState; } else if (isType(action, diffFetch.done)) { let newState = {...state} - newState.root = FileNode(applyDiffToFileTree(state.root, action.payload.result)) + newState.root = FileNode(applyDiffToFileTree(state.root, action.payload.result.diff)) return newState; } else if (isType(action, commentAggregatesFetch.done)) { let newState = {...state}; @@ -156,12 +156,6 @@ export const editorReducer = (state: EditorState = defaultEditorState, action: A newState.fileName = action.payload.params.filename; newState.loading = false; return newState; - } else if (isType(action, fileDiff.done)) { - let diff = action.payload.result; - if (diff === undefined) return state; - let newState = {...state}; - newState.diff = diff.changes; - return newState; } return state; }; @@ -188,7 +182,9 @@ export const diffReducer = (state: DiffState = defaultDiffState, action: Action) return { loading: false, base: action.payload.params.diffBase, - diff: fileDiffToMap(action.payload.result) + diff: fileDiffToMap(action.payload.result.diff), + from: action.payload.result.from, + to: action.payload.result.to } } else if (isType(action, diffFetch.started)) { return { diff --git a/kotoed-js/src/main/ts/code/remote/code.ts b/kotoed-js/src/main/ts/code/remote/code.ts index dcaae82e..64d66370 100644 --- a/kotoed-js/src/main/ts/code/remote/code.ts +++ b/kotoed-js/src/main/ts/code/remote/code.ts @@ -1,11 +1,9 @@ import {sleep} from "../../util/common"; import {EventBusError} from "../../util/vertx"; -import {eventBus} from "../../eventBus"; import {ResponseWithStatus, SubmissionIdRequest} from "./common"; import {Kotoed} from "../../util/kotoed-api"; import {sendAsync} from "../../views/components/common"; import {Generated} from "../../util/kotoed-generated"; -import ApiBindingInputs = Generated.ApiBindingInputs; export type FileType = "file" | "directory" @@ -53,8 +51,14 @@ interface FileResponse extends ResponseWithStatus { contents: string } +export interface RevisionInfo { + revision: string, submissionId?: number +} + export interface FileDiffResponse extends ResponseWithStatus { diff: Array + from: RevisionInfo + to: RevisionInfo } @@ -109,15 +113,14 @@ export async function fetchFile(submissionId: number, } export async function fetchDiff(submissionId: number, - base: DiffBase): Promise> { + base: DiffBase): Promise { - let res = await repeatTillReady(() => { + return await repeatTillReady(() => { return sendAsync(Kotoed.Address.Api.Submission.Code.Diff, { submissionId: submissionId, base }); }); - return res.diff; } export async function waitTillReady(submissionId: number): Promise { diff --git a/kotoed-js/src/main/ts/code/state/diff.ts b/kotoed-js/src/main/ts/code/state/diff.ts index 3d55fad4..c809a3ff 100644 --- a/kotoed-js/src/main/ts/code/state/diff.ts +++ b/kotoed-js/src/main/ts/code/state/diff.ts @@ -1,8 +1,10 @@ -import {DiffBase, FileDiffResult} from "../remote/code"; +import {DiffBase, FileDiffResult, RevisionInfo} from "../remote/code"; import {Map} from "immutable"; export interface DiffState { loading: boolean base: DiffBase diff: Map + from?: RevisionInfo + to?: RevisionInfo } diff --git a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt index e8bc9562..1422867b 100644 --- a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt +++ b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt @@ -139,14 +139,38 @@ class SubmissionCodeVerticle : AbstractKotoedVerticle() { suspend fun handleSubmissionCodeDiff(message: SubDiffRequest): SubDiffResponse { val submission: SubmissionRecord = dbFetchAsync(SubmissionRecord().apply { id = message.submissionId }) val repoInfo = getCommitInfo(submission) + val toRevInfo = Code.Submission.RevisionInfo(submission) + when (repoInfo.cloneStatus) { - CloneStatus.pending -> return SubDiffResponse(diff = emptyList(), status = repoInfo.cloneStatus) + CloneStatus.pending -> return SubDiffResponse( + diff = emptyList(), + status = repoInfo.cloneStatus, + from = toRevInfo, + to = toRevInfo + ) CloneStatus.failed -> throw NotFound("Repository not found") else -> { } } - val diff = submissionCodeDiff(submission, repoInfo, message.base) - return SubDiffResponse(diff = diff.contents, status = repoInfo.cloneStatus) + val baseRev = message.base.getBaseRev(submission) + val diff = when (baseRev) { + null -> DiffResponse(listOf()) + else -> sendJsonableAsync( + Address.Code.Diff, + DiffRequest( + uid = repoInfo.repo.uid, + from = baseRev.revision, + to = submission.revision + ) + ) + } + + return SubDiffResponse( + diff = diff.contents, + status = repoInfo.cloneStatus, + from = baseRev ?: toRevInfo, // Makes sense for an empty diff + to = toRevInfo + ) } // Feel da powa of Kotlin! @@ -252,23 +276,6 @@ class SubmissionCodeVerticle : AbstractKotoedVerticle() { ) } - private suspend fun submissionCodeDiff(submission: SubmissionRecord, repoInfo: CommitInfo, - base: SubDiffRequest.DiffBase): DiffResponse { - val baseRev = base.getBaseRev(submission) - - return when (baseRev) { - null -> DiffResponse(listOf()) - else -> sendJsonableAsync( - Address.Code.Diff, - DiffRequest( - uid = repoInfo.repo.uid, - from = baseRev, - to = submission.revision - ) - ) - } - } - @JsonableEventBusConsumerFor(Address.Api.Submission.Code.Date) suspend fun handleSubmissionCodeDate(message: SubReadRequest): BlameResponse { val submission: SubmissionRecord = dbFetchAsync(SubmissionRecord().apply { id = message.submissionId }) @@ -285,14 +292,17 @@ class SubmissionCodeVerticle : AbstractKotoedVerticle() { ) } - private suspend fun SubDiffRequest.DiffBase.getBaseRev(submission: SubmissionRecord): String? = + + private suspend fun SubDiffRequest.DiffBase.getBaseRev(submission: SubmissionRecord): Code.Submission.RevisionInfo? = when (type) { Code.Submission.DiffBaseType.SUBMISSION_ID -> dbFetchAsync(SubmissionRecord().apply { projectId = submission.projectId id = submissionId - })?.revision - Code.Submission.DiffBaseType.PREVIOUS_CHECKED -> submission.getLatestCheckedRev() - Code.Submission.DiffBaseType.PREVIOUS_CLOSED -> submission.getPreviousClosedOrCourseRev() + })?.revision?.let { + Code.Submission.RevisionInfo(it) + } + Code.Submission.DiffBaseType.PREVIOUS_CHECKED -> submission.getPreviousChecked() + Code.Submission.DiffBaseType.PREVIOUS_CLOSED -> submission.getPreviousClosed() Code.Submission.DiffBaseType.COURSE_BASE -> submission.getCourseBaseRev() } private suspend fun SubmissionRecord.getLatestClosedSub(): SubmissionRecord? = @@ -313,7 +323,7 @@ class SubmissionCodeVerticle : AbstractKotoedVerticle() { it.datetime } .firstOrNull() - private suspend fun SubmissionRecord.getCourseBaseRev(): String? = + private suspend fun SubmissionRecord.getCourseBaseRev(): Code.Submission.RevisionInfo? = dbQueryAsync( ComplexDatabaseQuery(ProjectRecord().apply { id = projectId }).join("course") ) @@ -321,10 +331,10 @@ class SubmissionCodeVerticle : AbstractKotoedVerticle() { .getJsonObject("course") .toRecord() .let { - if (it.baseRevision != "") it.baseRevision else null + if (it.baseRevision != "") Code.Submission.RevisionInfo(it.baseRevision) else null } - private suspend fun SubmissionRecord.getLatestCheckedRev(): String? { + private suspend fun SubmissionRecord.getPreviousChecked(): Code.Submission.RevisionInfo? { val latestClosed = getLatestClosedSub() // We consider closed as checked here val q = "project_id == %s " + (latestClosed?.datetime?.let { "and datetime > %s" } ?: "") @@ -360,14 +370,18 @@ class SubmissionCodeVerticle : AbstractKotoedVerticle() { var current = this do { - current = byId[current.parentSubmissionId] ?: return latestClosed?.revision - val tags = tagsById[current.id] ?: return latestClosed?.revision - if (CHECKED in tags) return current.revision + current = byId[current.parentSubmissionId] ?: return latestClosed?.let { + Code.Submission.RevisionInfo(it) + } + val tags = tagsById[current.id] ?: return latestClosed?.let { + Code.Submission.RevisionInfo(it) + } + if (CHECKED in tags) return Code.Submission.RevisionInfo(current) } while(true) } - private suspend fun SubmissionRecord.getPreviousClosedOrCourseRev(): String? = - getLatestClosedSub()?.revision ?: getCourseBaseRev() + private suspend fun SubmissionRecord.getPreviousClosed(): Code.Submission.RevisionInfo? = + getLatestClosedSub()?.let { Code.Submission.RevisionInfo(it) } ?: getCourseBaseRev() companion object { private const val CHECKED = "checked" diff --git a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/data/api/Data.kt b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/data/api/Data.kt index 790e17c7..1b61dfca 100644 --- a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/data/api/Data.kt +++ b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/data/api/Data.kt @@ -12,6 +12,7 @@ import java.util.* import org.jetbrains.research.kotoed.data.buildSystem.BuildCommand import org.jetbrains.research.kotoed.database.tables.records.BuildTemplateRecord +import org.jetbrains.research.kotoed.database.tables.records.SubmissionRecord import org.jetbrains.research.kotoed.util.* enum class VerificationStatus { @@ -93,7 +94,19 @@ object Code { } } } - data class DiffResponse(val diff: List, val status: CloneStatus) : Jsonable + data class DiffResponse( + val diff: List, + val status: CloneStatus, + val from: RevisionInfo, + val to: RevisionInfo) : Jsonable + + data class RevisionInfo(val revision: String, val submissionId: Int? = null): Jsonable { + companion object { + operator fun invoke(revision: String) = RevisionInfo(revision) + operator fun invoke(sub: SubmissionRecord) = RevisionInfo(sub.revision, sub.id) + + } + } enum class DiffBaseType { SUBMISSION_ID, PREVIOUS_CLOSED, PREVIOUS_CHECKED, COURSE_BASE From e38b0f278ab8a308fa22c98014d46222f385cf8b Mon Sep 17 00:00:00 2001 From: Kirill Date: Tue, 20 Sep 2022 01:00:34 +0300 Subject: [PATCH 05/11] Bugfix again --- .../research/kotoed/api/SubmissionCodeVerticle.kt | 4 ++-- kotoed-server/src/main/resources/db.properties | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 kotoed-server/src/main/resources/db.properties diff --git a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt index 1422867b..1f257b73 100644 --- a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt +++ b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt @@ -295,10 +295,10 @@ class SubmissionCodeVerticle : AbstractKotoedVerticle() { private suspend fun SubDiffRequest.DiffBase.getBaseRev(submission: SubmissionRecord): Code.Submission.RevisionInfo? = when (type) { - Code.Submission.DiffBaseType.SUBMISSION_ID -> dbFetchAsync(SubmissionRecord().apply { + Code.Submission.DiffBaseType.SUBMISSION_ID -> dbFindAsync(SubmissionRecord().apply { projectId = submission.projectId id = submissionId - })?.revision?.let { + }).firstOrNull()?.revision?.let { Code.Submission.RevisionInfo(it) } Code.Submission.DiffBaseType.PREVIOUS_CHECKED -> submission.getPreviousChecked() diff --git a/kotoed-server/src/main/resources/db.properties b/kotoed-server/src/main/resources/db.properties new file mode 100644 index 00000000..ef650101 --- /dev/null +++ b/kotoed-server/src/main/resources/db.properties @@ -0,0 +1,7 @@ +db.url=jdbc:postgresql://localhost/kotoed +db.user=kotoed +db.password=kotoed +testdb.url=jdbc:postgresql://localhost/kotoed-test +testdb.user=kotoed +testdb.password=kotoed +flyway.table=schema_version From d03c94aa6961aa481510982da81f430434e8e012 Mon Sep 17 00:00:00 2001 From: Kirill Date: Tue, 20 Sep 2022 22:18:25 +0300 Subject: [PATCH 06/11] Remove Marat's UX joke --- kotoed-js/src/main/ts/projects/create.tsx | 29 +---------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/kotoed-js/src/main/ts/projects/create.tsx b/kotoed-js/src/main/ts/projects/create.tsx index 43ed8ad5..79f9c9d2 100644 --- a/kotoed-js/src/main/ts/projects/create.tsx +++ b/kotoed-js/src/main/ts/projects/create.tsx @@ -23,7 +23,6 @@ interface ProjectCreateState { showModal: boolean remoteError?: string name: string - repoType: string repoUrl: string } @@ -35,7 +34,6 @@ export class ProjectCreate extends ComponentWithLocalErrors) => { - this.setState({ - repoType: changeEvent.target.value as string - }); - }; handleEnter = (event: KeyboardEvent) => event.key === "Enter" && this.handleSubmit(); @@ -158,25 +150,6 @@ export class ProjectCreate extends ComponentWithLocalErrors - - Repo type - - Git - - {" "} - - Mercurial - - Date: Wed, 12 Oct 2022 21:11:51 +0300 Subject: [PATCH 07/11] Persistence for diff mode preference --- kotoed-js/src/main/ts/code/actions.ts | 15 +++- .../main/ts/code/components/CodeReview.tsx | 54 +++++++++----- .../code/containers/CodeReviewContainer.tsx | 5 +- kotoed-js/src/main/ts/code/reducers.ts | 24 +++++++ .../src/main/ts/code/remote/capabilities.ts | 7 +- kotoed-js/src/main/ts/code/remote/code.ts | 11 +++ kotoed-js/src/main/ts/courses/remote.ts | 7 +- kotoed-js/src/main/ts/data/denizen.ts | 14 ++++ kotoed-js/src/main/ts/profile/edit.tsx | 70 +++++++++++++++++-- kotoed-js/src/main/ts/profile/index.tsx | 11 +-- .../research/kotoed/api/DenizenVerticle.kt | 9 ++- .../kotoed/api/SubmissionCodeVerticle.kt | 1 + .../research/kotoed/data/api/Data.kt | 7 +- .../research/kotoed/web/data/CodeReview.kt | 2 +- .../kotoed/web/routers/AuthHelpers.kt | 3 +- .../migration/V93__Diff_mode_preference.sql | 2 + 16 files changed, 194 insertions(+), 48 deletions(-) create mode 100644 kotoed-server/src/main/resources/db/migration/V93__Diff_mode_preference.sql diff --git a/kotoed-js/src/main/ts/code/actions.ts b/kotoed-js/src/main/ts/code/actions.ts index 16b4c755..30ddd63b 100644 --- a/kotoed-js/src/main/ts/code/actions.ts +++ b/kotoed-js/src/main/ts/code/actions.ts @@ -16,7 +16,7 @@ import { File, FileDiffResponse, FileDiffResult, - FileType + FileType, updateDiffPreference } from "./remote/code"; import {FileNotFoundError} from "./errors"; import {push} from "react-router-redux"; @@ -128,6 +128,10 @@ interface DiffBasePayload { diffBase: DiffBase } +interface PersistPayload { + persist: boolean +} + interface DiffResultPayload { diff: FileDiffResult[] } @@ -368,10 +372,17 @@ export function loadFileToEditor(payload: FilePathPayload & SubmissionPayload) { } } -export function updateDiff(payload: SubmissionPayload & DiffBasePayload) { +export function updateDiff(payload: SubmissionPayload & DiffBasePayload & PersistPayload) { return async (dispatch: Dispatch, getState: () => CodeReviewState) => { dispatch(diffFetch.started(payload)) + const state = getState(); + const patchedType = payload.diffBase.type == "SUBMISSION_ID" ? "PREVIOUS_CLOSED" : payload.diffBase.type; + + if (payload.persist) { + updateDiffPreference(state.capabilitiesState.capabilities.principal, patchedType) + } + const diff = await fetchDiff(payload.submissionId, payload.diffBase); dispatch(diffFetch.done({ diff --git a/kotoed-js/src/main/ts/code/components/CodeReview.tsx b/kotoed-js/src/main/ts/code/components/CodeReview.tsx index 273b5199..d295f93b 100644 --- a/kotoed-js/src/main/ts/code/components/CodeReview.tsx +++ b/kotoed-js/src/main/ts/code/components/CodeReview.tsx @@ -1,5 +1,16 @@ import * as React from "react"; -import {Button, Panel, Label, Modal, Form, FormGroup, ControlLabel, FormControl, Radio} from "react-bootstrap"; +import { + Button, + Panel, + Label, + Modal, + Form, + FormGroup, + ControlLabel, + FormControl, + Radio, + SplitButton, MenuItem +} from "react-bootstrap"; import FileReview from "./FileReview"; import FileTree from "./FileTree"; @@ -27,6 +38,7 @@ import "@fortawesome/fontawesome-free/less/solid.less" import "@fortawesome/fontawesome-free/less/regular.less" import {ChangeEvent} from "react"; import {DiffState} from "../state/diff"; +import {Profile} from "../../data/denizen"; export interface CodeReviewProps { submissionId: number @@ -99,7 +111,7 @@ export interface CodeReviewCallbacks { } diff: { - onChangeDiffBase: (submissionId: number, diffBase: DiffBase) => void + onChangeDiffBase: (submissionId: number, diffBase: DiffBase, persist: boolean) => void } } @@ -293,7 +305,7 @@ export default class CodeReview extends Settings - + Diff base } - + onClick={() => this.applyDiffPreference(false)}> + this.applyDiffPreference(true)}>Apply and save + + applyDiffPreference = (persist: boolean) => { + this.props.diff.onChangeDiffBase( + this.props.submission!!.record.id, { + type: this.state.baseChoice.type, + submissionId: this.state.baseChoice.type == "SUBMISSION_ID" ? + parseInt(this.state.baseChoice.submissionId || "0") : + undefined + }, persist) + this.setState({ + showDiffModal: false + }) + } + render() { if (!this.props.submission) { return
, }, diff: { - onChangeDiffBase: (submissionId, diffBase) => { + onChangeDiffBase: (submissionId, diffBase, persist) => { dispatch(updateDiff({ diffBase, - submissionId + submissionId, + persist })) } }, diff --git a/kotoed-js/src/main/ts/code/reducers.ts b/kotoed-js/src/main/ts/code/reducers.ts index 3e234c98..58a9efd8 100644 --- a/kotoed-js/src/main/ts/code/reducers.ts +++ b/kotoed-js/src/main/ts/code/reducers.ts @@ -28,6 +28,7 @@ import {CommentTemplateState} from "./state/templates"; import {CommentTemplates} from "./remote/templates"; import {DiffBase, fetchDiff, FileDiffResult} from "./remote/code"; import {DiffState} from "./state/diff"; +import {DiffModePreference} from "../data/denizen"; const initialFileTreeState: FileTreeState = { root: FileNode({ @@ -198,6 +199,22 @@ export const diffReducer = (state: DiffState = defaultDiffState, action: Action) base: state.base, diff: fileDiffToMap(action.payload.result.diff) } + } else if (isType(action, capabilitiesFetch.done)) { + let diffModePreference: DiffModePreference + + if (!action.payload.result.permissions.tags && + action.payload.result.profile.diffModePreference == "PREVIOUS_CHECKED") { + diffModePreference = "PREVIOUS_CLOSED" + } else { + diffModePreference = action.payload.result.profile.diffModePreference; + } + + return { + ...state, + base: { + type: diffModePreference + } + } } return state } @@ -373,9 +390,16 @@ export const defaultCapabilitiesState: CapabilitiesState = { clean: false, tags: false, klones: false + }, + profile: { + id: 0, + denizenId: "???", + diffModePreference: "PREVIOUS_CLOSED", + oauth: [] } }, loading: true, + }; export const capabilitiesReducer = (state: CapabilitiesState = defaultCapabilitiesState, action: Action) => { diff --git a/kotoed-js/src/main/ts/code/remote/capabilities.ts b/kotoed-js/src/main/ts/code/remote/capabilities.ts index 7a43a8f2..02d4e45b 100644 --- a/kotoed-js/src/main/ts/code/remote/capabilities.ts +++ b/kotoed-js/src/main/ts/code/remote/capabilities.ts @@ -1,16 +1,19 @@ import axios from "axios" import {keysToCamelCase} from "../../util/stringCase"; import {Kotoed} from "../../util/kotoed-api"; -import {DenizenPrincipal} from "../../data/denizen"; +import {DenizenPrincipal, Profile, ProfileInfo} from "../../data/denizen"; import {fetchPermissions, SubmissionPermissions} from "../../submissionDetails/remote"; +import {sendAsync} from "../../views/components/common"; export interface Capabilities { principal: DenizenPrincipal permissions: SubmissionPermissions + profile: ProfileInfo } export async function fetchCapabilities(submissionId: number): Promise { let principalResp = await axios.get(Kotoed.UrlPattern.AuthHelpers.WhoAmI); let permissions = await fetchPermissions(submissionId); - return {principal: keysToCamelCase(principalResp.data), permissions} + let profile = await sendAsync(Kotoed.Address.Api.Denizen.Profile.Read, {id: principalResp.data.id}) + return {principal: keysToCamelCase(principalResp.data), permissions, profile} } diff --git a/kotoed-js/src/main/ts/code/remote/code.ts b/kotoed-js/src/main/ts/code/remote/code.ts index 64d66370..4e68de4a 100644 --- a/kotoed-js/src/main/ts/code/remote/code.ts +++ b/kotoed-js/src/main/ts/code/remote/code.ts @@ -4,6 +4,8 @@ import {ResponseWithStatus, SubmissionIdRequest} from "./common"; import {Kotoed} from "../../util/kotoed-api"; import {sendAsync} from "../../views/components/common"; import {Generated} from "../../util/kotoed-generated"; +import {DenizenPrincipal, DiffModePreference} from "../../data/denizen"; +import Address = Kotoed.Address; export type FileType = "file" | "directory" @@ -130,3 +132,12 @@ export async function waitTillReady(submissionId: number): Promise { }) }); } + +export async function updateDiffPreference(principal: DenizenPrincipal, preference: DiffModePreference) { + return await sendAsync(Address.Api.Denizen.Profile.Update, { + id: principal.id, + denizenId: principal.denizenId, + diffModePreference: preference + }); + +} diff --git a/kotoed-js/src/main/ts/courses/remote.ts b/kotoed-js/src/main/ts/courses/remote.ts index be9a32c2..62007248 100644 --- a/kotoed-js/src/main/ts/courses/remote.ts +++ b/kotoed-js/src/main/ts/courses/remote.ts @@ -7,8 +7,9 @@ import {WithId} from "../data/common"; import {DbRecordWrapper} from "../data/verification"; import {CourseToRead} from "../data/course"; -interface RootPermissions { - createCourse: boolean +export interface RootPermissions { + createCourse: boolean, + tags: boolean } export async function fetchPermissions(): Promise { @@ -22,4 +23,4 @@ export async function fetchCourse(id: number): Promise) }; + bindRadio = (key: K) => (e: ChangeEvent) => { + this.setDenizen({ [key]: e.target.value } as Pick) + }; + onEmailChanged = (e: ChangeEvent) => { this.unsetError("badEmail"); if(e.target.value && !e.target.checkValidity()) this.setError("badEmail"); @@ -200,6 +208,57 @@ export class ProfileComponent extends ComponentWithLocalErrors; }; + renderDiffPreference = () => { + return
+ +
+
+ +
+
+ +
+ {this.props.permissions.tags &&
+ +
} +
+
; + }; + renderBody = () => { return
@@ -220,6 +279,7 @@ export class ProfileComponent extends ComponentWithLocalErrors
Save @@ -267,6 +327,7 @@ export class ProfileComponent extends ComponentWithLocalErrors { @@ -276,9 +337,10 @@ class ProfileWrapper extends React.Component<{}, ProfileWrapperState> { } loadDenizen = async () => { - let profile = + let denizen = await sendAsync(Kotoed.Address.Api.Denizen.Profile.Read, {id: userId}); - this.setState({denizen: profile}) + let permissions = await fetchPermissions(); + this.setState({denizen, permissions}) }; componentDidMount() { @@ -286,8 +348,8 @@ class ProfileWrapper extends React.Component<{}, ProfileWrapperState> { } render() { - return this.state.denizen ? - : ; + return this.state.denizen && this.state.permissions ? + : ; } } diff --git a/kotoed-js/src/main/ts/profile/index.tsx b/kotoed-js/src/main/ts/profile/index.tsx index d4d457cf..6fe82980 100644 --- a/kotoed-js/src/main/ts/profile/index.tsx +++ b/kotoed-js/src/main/ts/profile/index.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import {render} from "react-dom"; import "less/kotoed-bootstrap/bootstrap.less" -import {Denizen, WithDenizen} from "../data/denizen"; +import {Denizen, ProfileInfo, WithDenizen} from "../data/denizen"; import {Kotoed} from "../util/kotoed-api"; import {eventBus} from "../eventBus"; import {sendAsync} from "../views/components/common"; @@ -15,15 +15,6 @@ import SocialButton from "../login/components/SocialButton"; let params = Kotoed.UrlPattern.tryResolve(Kotoed.UrlPattern.Profile.Index, window.location.pathname) || new Map(); let userId = parseInt(params.get("id")) || -1; -interface ProfileInfo { - id: number - denizenId: string - email?: string - oauth: [string, string | null][] - firstName?: string - lastName?: string - group?: string -} interface ProfileComponentProps extends LoadingProperty { denizen?: ProfileInfo diff --git a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/DenizenVerticle.kt b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/DenizenVerticle.kt index 52a60863..0cde3953 100644 --- a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/DenizenVerticle.kt +++ b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/DenizenVerticle.kt @@ -10,6 +10,7 @@ import org.jetbrains.research.kotoed.data.db.textSearch import org.jetbrains.research.kotoed.database.Tables import org.jetbrains.research.kotoed.database.Tables.DENIZEN_TEXT_SEARCH import org.jetbrains.research.kotoed.database.Tables.PROFILE +import org.jetbrains.research.kotoed.database.enums.DiffModePreference import org.jetbrains.research.kotoed.database.tables.records.DenizenRecord import org.jetbrains.research.kotoed.database.tables.records.OauthProfileRecord import org.jetbrains.research.kotoed.database.tables.records.ProfileRecord @@ -51,7 +52,8 @@ class DenizenVerticle: AbstractKotoedVerticle() { firstName = profile?.firstName, lastName = profile?.lastName, group = profile?.groupId, - emailNotifications = profile?.emailNotifications ?: false + emailNotifications = profile?.emailNotifications ?: false, + diffModePreference = profile?.diffModePreference ?: DiffModePreference.PREVIOUS_CLOSED ) } @@ -70,10 +72,11 @@ class DenizenVerticle: AbstractKotoedVerticle() { update.firstName?.let { firstName = it } update.lastName?.let { lastName = it } update.group?.let { groupId = it } - update.emailNotifications.let { emailNotifications = it } + update.emailNotifications?.let { emailNotifications = it } + update.diffModePreference?.let { diffModePreference = it } } - if(profile != null) { + if (profile != null) { dbUpdateAsync(newProf.apply{ id = profile.id}) } else { dbCreateAsync(newProf) diff --git a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt index 1f257b73..b4ca99b5 100644 --- a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt +++ b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt @@ -335,6 +335,7 @@ class SubmissionCodeVerticle : AbstractKotoedVerticle() { } private suspend fun SubmissionRecord.getPreviousChecked(): Code.Submission.RevisionInfo? { + this. val latestClosed = getLatestClosedSub() // We consider closed as checked here val q = "project_id == %s " + (latestClosed?.datetime?.let { "and datetime > %s" } ?: "") diff --git a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/data/api/Data.kt b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/data/api/Data.kt index 1b61dfca..02037f8e 100644 --- a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/data/api/Data.kt +++ b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/data/api/Data.kt @@ -11,6 +11,7 @@ import org.jooq.Record import java.util.* import org.jetbrains.research.kotoed.data.buildSystem.BuildCommand +import org.jetbrains.research.kotoed.database.enums.DiffModePreference import org.jetbrains.research.kotoed.database.tables.records.BuildTemplateRecord import org.jetbrains.research.kotoed.database.tables.records.SubmissionRecord import org.jetbrains.research.kotoed.util.* @@ -219,7 +220,8 @@ data class ProfileInfo( val firstName: String?, val lastName: String?, val group: String?, - val emailNotifications: Boolean + val emailNotifications: Boolean, + val diffModePreference: DiffModePreference ) : Jsonable data class PasswordChangeRequest( @@ -237,7 +239,8 @@ data class ProfileInfoUpdate( val firstName: String?, val lastName: String?, val group: String?, - val emailNotifications: Boolean + val emailNotifications: Boolean?, + val diffModePreference: DiffModePreference? ) : Jsonable enum class SubmissionCodeAnnotationSeverity { error, warning } diff --git a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/web/data/CodeReview.kt b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/web/data/CodeReview.kt index b50ab5a9..69651a36 100644 --- a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/web/data/CodeReview.kt +++ b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/web/data/CodeReview.kt @@ -4,7 +4,7 @@ import io.vertx.core.json.JsonObject import org.jetbrains.research.kotoed.util.Jsonable object Permissions { - data class Root(val createCourse: Boolean = false): Jsonable + data class Root(val createCourse: Boolean = false, val tags: Boolean = false): Jsonable data class Course(val createProject: Boolean = false, val editCourse: Boolean = false, val viewTags: Boolean = false): Jsonable diff --git a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/web/routers/AuthHelpers.kt b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/web/routers/AuthHelpers.kt index 9784a1f2..7a0da66d 100644 --- a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/web/routers/AuthHelpers.kt +++ b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/web/routers/AuthHelpers.kt @@ -43,7 +43,8 @@ suspend fun handleRootPerms(context: RoutingContext) { context.response().end( Permissions.Root( - createCourse = isTeacher + createCourse = isTeacher, + tags = isTeacher ) ) } diff --git a/kotoed-server/src/main/resources/db/migration/V93__Diff_mode_preference.sql b/kotoed-server/src/main/resources/db/migration/V93__Diff_mode_preference.sql new file mode 100644 index 00000000..9d471b53 --- /dev/null +++ b/kotoed-server/src/main/resources/db/migration/V93__Diff_mode_preference.sql @@ -0,0 +1,2 @@ +CREATE TYPE diff_mode_preference AS ENUM ('PREVIOUS_CLOSED', 'PREVIOUS_CHECKED', 'COURSE_BASE'); +ALTER TABLE profile ADD COLUMN diff_mode_preference diff_mode_preference NOT NULL DEFAULT 'PREVIOUS_CLOSED'; From e4b4b7f3ef402640a3d71a2321fb284033166b3f Mon Sep 17 00:00:00 2001 From: Kirill Date: Wed, 12 Oct 2022 21:13:02 +0300 Subject: [PATCH 08/11] oops --- kotoed-server/src/main/resources/db.properties | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 kotoed-server/src/main/resources/db.properties diff --git a/kotoed-server/src/main/resources/db.properties b/kotoed-server/src/main/resources/db.properties deleted file mode 100644 index ef650101..00000000 --- a/kotoed-server/src/main/resources/db.properties +++ /dev/null @@ -1,7 +0,0 @@ -db.url=jdbc:postgresql://localhost/kotoed -db.user=kotoed -db.password=kotoed -testdb.url=jdbc:postgresql://localhost/kotoed-test -testdb.user=kotoed -testdb.password=kotoed -flyway.table=schema_version From 427f0a8fdc3e2856659aa2827da8f0583364af98 Mon Sep 17 00:00:00 2001 From: Kirill Date: Wed, 12 Oct 2022 21:19:15 +0300 Subject: [PATCH 09/11] oops2 --- .../org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt index b4ca99b5..1f257b73 100644 --- a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt +++ b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt @@ -335,7 +335,6 @@ class SubmissionCodeVerticle : AbstractKotoedVerticle() { } private suspend fun SubmissionRecord.getPreviousChecked(): Code.Submission.RevisionInfo? { - this. val latestClosed = getLatestClosedSub() // We consider closed as checked here val q = "project_id == %s " + (latestClosed?.datetime?.let { "and datetime > %s" } ?: "") From 7d087ddb3375d533a9e0ae22251f5434ccc64cf0 Mon Sep 17 00:00:00 2001 From: Kirill Date: Wed, 12 Oct 2022 21:29:35 +0300 Subject: [PATCH 10/11] m --- .../org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt index 1f257b73..76b41739 100644 --- a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt +++ b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt @@ -252,7 +252,6 @@ class SubmissionCodeVerticle : AbstractKotoedVerticle() { suspend fun handleSubmissionCodeList(message: SubListRequest): ListResponse { val submission: SubmissionRecord = dbFetchAsync(SubmissionRecord().apply { id = message.submissionId }) val repoInfo = getCommitInfo(submission) - val base = message.diffBase when (repoInfo.cloneStatus) { CloneStatus.pending -> return ListResponse(root = null, status = repoInfo.cloneStatus) From 5185b98339cbac2c66a8fcf3d51c8d051e054c50 Mon Sep 17 00:00:00 2001 From: Kirill Date: Wed, 26 Oct 2022 21:11:49 +0300 Subject: [PATCH 11/11] Review fixes --- kotoed-js/src/main/ts/code/actions.ts | 2 +- kotoed-js/src/main/ts/code/remote/code.ts | 6 ++---- kotoed-js/src/main/ts/profile/edit.tsx | 1 - .../jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt | 3 --- .../jetbrains/research/kotoed/code/klones/KloneVerticle.kt | 3 +-- .../kotlin/org/jetbrains/research/kotoed/data/api/Data.kt | 2 +- 6 files changed, 5 insertions(+), 12 deletions(-) diff --git a/kotoed-js/src/main/ts/code/actions.ts b/kotoed-js/src/main/ts/code/actions.ts index 30ddd63b..4a12dd1c 100644 --- a/kotoed-js/src/main/ts/code/actions.ts +++ b/kotoed-js/src/main/ts/code/actions.ts @@ -248,7 +248,7 @@ export function fetchRootDirIfNeeded(payload: SubmissionPayload) { submissionId: payload.submissionId })); - const root = await fetchRootDir(payload.submissionId, state.diffState.base); + const root = await fetchRootDir(payload.submissionId); const recursiveSorter = (node: File) => { if (node.children == null) { diff --git a/kotoed-js/src/main/ts/code/remote/code.ts b/kotoed-js/src/main/ts/code/remote/code.ts index 4e68de4a..39a3d303 100644 --- a/kotoed-js/src/main/ts/code/remote/code.ts +++ b/kotoed-js/src/main/ts/code/remote/code.ts @@ -87,12 +87,10 @@ async function repeatTillReady(doRequest: () => Pr } } -export async function fetchRootDir(submissionId: number, - diffBase: DiffBase): Promise { +export async function fetchRootDir(submissionId: number): Promise { let res = await repeatTillReady(() => { return sendAsync(Kotoed.Address.Api.Submission.Code.List, { - submissionId: submissionId, - diffBase + submissionId: submissionId }) }); return res.root!; diff --git a/kotoed-js/src/main/ts/profile/edit.tsx b/kotoed-js/src/main/ts/profile/edit.tsx index e4764e41..6a785924 100644 --- a/kotoed-js/src/main/ts/profile/edit.tsx +++ b/kotoed-js/src/main/ts/profile/edit.tsx @@ -221,7 +221,6 @@ export class ProfileComponent extends ComponentWithLocalErrors Course base revision diff --git a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt index 76b41739..b5ca8c79 100644 --- a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt +++ b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/api/SubmissionCodeVerticle.kt @@ -315,9 +315,6 @@ class SubmissionCodeVerticle : AbstractKotoedVerticle() { .map { it.toRecord() } - .filter { - it.datetime < datetime - } .sortedByDescending { it.datetime } diff --git a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/code/klones/KloneVerticle.kt b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/code/klones/KloneVerticle.kt index 2256983e..13be5645 100644 --- a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/code/klones/KloneVerticle.kt +++ b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/code/klones/KloneVerticle.kt @@ -167,8 +167,7 @@ class KloneVerticle : AbstractKotoedVerticle(), Loggable { val files: Code.ListResponse = sendJsonableAsync( Address.Api.Submission.Code.List, - Code.Submission.ListRequest(sub.getInteger("id"), - Code.Submission.DiffRequest.DiffBase(Code.Submission.DiffBaseType.PREVIOUS_CLOSED)) + Code.Submission.ListRequest(sub.getInteger("id")) ) return handleFiles( diff --git a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/data/api/Data.kt b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/data/api/Data.kt index 02037f8e..9b45c208 100644 --- a/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/data/api/Data.kt +++ b/kotoed-server/src/main/kotlin/org/jetbrains/research/kotoed/data/api/Data.kt @@ -87,7 +87,7 @@ object Code { val fromLine: Int? = null, val toLine: Int? = null) : Jsonable data class ReadResponse(val contents: String, val status: CloneStatus) : Jsonable - data class ListRequest(val submissionId: Int, val diffBase: DiffRequest.DiffBase) : Jsonable + data class ListRequest(val submissionId: Int) : Jsonable data class DiffRequest(val submissionId: Int, val base: DiffBase) : Jsonable { class DiffBase(val type: DiffBaseType, val submissionId: Int? = null) : Jsonable { init {