Skip to content

Commit

Permalink
Adjusting code fragment selector and plagiarism code box to display t…
Browse files Browse the repository at this point in the history
…otal coverage in the left panel (suspected code).
  • Loading branch information
krulis-martin committed Dec 31, 2023
1 parent 03a4167 commit 0fa1927
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 116 deletions.
58 changes: 44 additions & 14 deletions src/components/Solutions/PlagiarismCodeBox/PlagiarismCodeBox.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ import cfsStyles from '../../helpers/CodeFragmentSelector/CodeFragmentSelector.l

const linesCount = content => (content.match(/\n/g) || '').length + 1;

const mergeRemainingFragments = (fragments, remainingFragments) => {
Object.keys(remainingFragments).forEach(fileIdx => {
remainingFragments[fileIdx].forEach(f => {
fragments.push({ ...f, doubleClickData: fileIdx });
});
});
};

class PlagiarismCodeBox extends Component {
// Generate content for <pre> element that holds line numbers (based on size of the two compared contents).
content1Ref = null;
Expand All @@ -46,16 +54,19 @@ class PlagiarismCodeBox extends Component {

// Get fragments from the plagiarism record and split them to 2 lists (left half and right half)
fragmentsRef = null;
remainingFragmentsRef = null;
splitFragmentsCache = null;

splitFragments = fragments => {
if (this.fragmentsRef !== fragments) {
preprocessFragments = (fragments, remainingFragments) => {
if (this.fragmentsRef !== fragments || this.remainingFragmentsRef !== remainingFragments) {
this.splitFragmentsCache = [[], []];
fragments.forEach(([f0, f1]) => {
this.splitFragmentsCache[0].push(f0);
this.splitFragmentsCache[1].push(f1);
fragments.forEach(([f0, f1], idx) => {
this.splitFragmentsCache[0].push({ ...f0, clickData: idx });
this.splitFragmentsCache[1].push({ ...f1, clickData: idx });
});
mergeRemainingFragments(this.splitFragmentsCache[0], remainingFragments);
this.fragmentsRef = fragments;
this.remainingFragmentsRef = remainingFragments;
}
return this.splitFragmentsCache;
};
Expand All @@ -75,13 +86,26 @@ class PlagiarismCodeBox extends Component {
}
}

selectFragment = selectedFragment => this.setState({ selectedFragment });
fragmentClickHandler = data => {
const selectedFragment =
this.state.selectedFragment !== null
? data[data.indexOf(this.state.selectedFragment) + 1] || null
: data.length > 0
? data[0]
: null;

this.setState({ selectedFragment });
};

fragmentDoubleClickHandler = data => {
// console.log(data);
};

_selectRelFragment = (ev, rel) => {
const count = this.props.selectedPlagiarismFile.fragments.length;
if (count > 0) {
const next = this.state.selectedFragment !== null ? this.state.selectedFragment + rel : 0;
this.selectFragment(next >= 0 && next < count ? next : null);
this.fragmentClickHandler(next >= 0 && next < count ? next : null);
}

if (window) {
Expand Down Expand Up @@ -124,8 +148,10 @@ class PlagiarismCodeBox extends Component {
download = null,
fileContentsSelector,
selectedPlagiarismFile,
remainingFragments,
similarity = null,
selectPlagiarismFile = null,
// selectPlagiarismFile = null,
openSelectFileDialog = null,
links: { SOLUTION_DETAIL_URI_FACTORY, GROUP_STUDENTS_URI_FACTORY },
} = this.props;

Expand Down Expand Up @@ -214,7 +240,7 @@ class PlagiarismCodeBox extends Component {
/>
)}

{selectPlagiarismFile ? (
{openSelectFileDialog ? (
<OverlayTrigger
placement="bottom"
overlay={
Expand All @@ -225,7 +251,7 @@ class PlagiarismCodeBox extends Component {
/>
</Tooltip>
}>
<CodeCompareIcon className="text-primary ml-4 mr-3" onClick={selectPlagiarismFile} />
<CodeCompareIcon className="text-primary ml-4 mr-3" onClick={openSelectFileDialog} />
</OverlayTrigger>
) : (
<CodeCompareIcon className="text-muted ml-4 mr-3" />
Expand Down Expand Up @@ -341,17 +367,19 @@ class PlagiarismCodeBox extends Component {
<div className={this.state.fullWidth ? styles.fullWidth : ''}>
<CodeFragmentSelector
content={content.content}
fragments={this.splitFragments(selectedPlagiarismFile.fragments)[0]}
fragments={this.preprocessFragments(selectedPlagiarismFile.fragments, remainingFragments)[0]}
selected={this.state.selectedFragment}
setSelected={this.selectFragment}
clickHandler={this.fragmentClickHandler}
doubleClickHandler={this.fragmentDoubleClickHandler}
/>
</div>
<div className={this.state.fullWidth ? styles.fullWidth : ''}>
<CodeFragmentSelector
content={secondContent.content}
fragments={this.splitFragments(selectedPlagiarismFile.fragments)[1]}
fragments={this.preprocessFragments(selectedPlagiarismFile.fragments, remainingFragments)[1]}
selected={this.state.selectedFragment}
setSelected={this.selectFragment}
clickHandler={this.fragmentClickHandler}
doubleClickHandler={this.fragmentDoubleClickHandler}
/>
</div>
</div>
Expand Down Expand Up @@ -381,7 +409,9 @@ PlagiarismCodeBox.propTypes = {
fileContentsSelector: PropTypes.func,
selectedPlagiarismFile: PropTypes.object.isRequired,
similarity: PropTypes.number,
remainingFragments: PropTypes.object,
selectPlagiarismFile: PropTypes.func,
openSelectFileDialog: PropTypes.func,
links: PropTypes.object,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,30 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { Modal, Table } from 'react-bootstrap';
import { defaultMemoize } from 'reselect';

import PlagiarismCodeBox from '../PlagiarismCodeBox';
import SourceCodeBox from '../SourceCodeBox';
import DateTime from '../../widgets/DateTime';
import Button from '../../widgets/TheButton';
import Box from '../../widgets/Box';
import ResourceRenderer from '../../helpers/ResourceRenderer';
import GroupsNameContainer from '../../../containers/GroupsNameContainer';
import { CloseIcon, CodeFileIcon } from '../../icons';
import { CloseIcon, CodeFileIcon, LoadingIcon } from '../../icons';

/**
* Construct an object { fileIdx => fragmentsArray } where the fragments array holds only
* references to the left (target) part. Selected file fragments are excluded.
*/
const getRemainingFragments = defaultMemoize((files, selected) => {
const res = {};
files.forEach((file, idx) => {
if (idx !== selected) {
res[idx] = file.fragments.map(f => f[0]);
}
});
return Object.keys(res).length > 0 ? res : null;
});

class PlagiarismCodeBoxWithSelector extends Component {
state = { selectedFile: 0, dialogOpen: false };
Expand All @@ -33,71 +50,96 @@ class PlagiarismCodeBoxWithSelector extends Component {

render() {
const { file, solutionId, selectedPlagiarismSource = null, download, fileContentsSelector } = this.props;
const sourceContents =
selectedPlagiarismSource &&
selectedPlagiarismSource.files.map(file => file && fileContentsSelector(file.solutionFile.id, file.fileEntry));

return selectedPlagiarismSource ? (
<>
<PlagiarismCodeBox
{...file}
solutionId={solutionId}
download={download}
fileContentsSelector={fileContentsSelector}
selectedPlagiarismFile={selectedPlagiarismSource.files[this.state.selectedFile]}
selectPlagiarismFile={selectedPlagiarismSource.files.length > 1 ? this.openDialog : null}
similarity={selectedPlagiarismSource.similarity}
/>
{selectedPlagiarismSource.files.length > 1 && (
<Modal show={this.state.dialogOpen} backdrop="static" onHide={this.closeDialog} size="xl">
<Modal.Header closeButton>
<Modal.Title>
<FormattedMessage
id="app.solutionPlagiarisms.selectPlagiarismFileModal.title"
defaultMessage="Select one of the possible source files to be compared"
/>
</Modal.Title>
</Modal.Header>
<Modal.Body className="p-0">
<Table hover className="m-0">
<tbody>
{selectedPlagiarismSource.files.map((file, idx) => (
<tr
key={file.id}
className={this.state.selectedFile === idx ? 'table-primary' : 'clickable'}
onClick={this.state.selectedFile !== idx ? () => this.selectFile(idx) : null}>
<td className="text-nowrap shrink-col">
<CodeFileIcon className="text-muted" gapLeft gapRight />
</td>
<td>
<code>
{file.solutionFile.name}
{file.fileEntry ? `/${file.fileEntry}` : ''}
</code>
</td>
<td>
<FormattedMessage
id="app.solutionPlagiarisms.selectPlagiarismFileModal.fromSolution"
defaultMessage="solution"
/>{' '}
<strong>#{file.solution.attemptIndex}</strong>
</td>
<td>
(<DateTime unixts={file.solution.createdAt} />)
</td>
<td>
<GroupsNameContainer groupId={file.groupId} fullName admins />
</td>
</tr>
))}
</tbody>
</Table>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={this.closeDialog}>
<CloseIcon gapRight />
<FormattedMessage id="generic.close" defaultMessage="Close" />
</Button>
</Modal.Footer>
</Modal>
<ResourceRenderer
key={file.id}
resource={sourceContents}
returnAsArray
loading={
<Box
key={`${file.id}-loading`}
title={
<>
<LoadingIcon gapRight />
<code>{file.name}</code>
</>
}
noPadding
/>
}>
{() => (
<>
<PlagiarismCodeBox
{...file}
solutionId={solutionId}
download={download}
fileContentsSelector={fileContentsSelector}
selectedPlagiarismFile={selectedPlagiarismSource.files[this.state.selectedFile]}
selectPlagiarismFile={selectedPlagiarismSource.files.length > 1 ? this.selectFile : null}
openSelectFileDialog={selectedPlagiarismSource.files.length > 1 ? this.openDialog : null}
similarity={selectedPlagiarismSource.similarity}
remainingFragments={getRemainingFragments(selectedPlagiarismSource.files, this.state.selectedFile)}
/>
{selectedPlagiarismSource.files.length > 1 && (
<Modal show={this.state.dialogOpen} backdrop="static" onHide={this.closeDialog} size="xl">
<Modal.Header closeButton>
<Modal.Title>
<FormattedMessage
id="app.solutionPlagiarisms.selectPlagiarismFileModal.title"
defaultMessage="Select one of the possible source files to be compared"
/>
</Modal.Title>
</Modal.Header>
<Modal.Body className="p-0">
<Table hover className="m-0">
<tbody>
{selectedPlagiarismSource.files.map((file, idx) => (
<tr
key={file.id}
className={this.state.selectedFile === idx ? 'table-primary' : 'clickable'}
onClick={this.state.selectedFile !== idx ? () => this.selectFile(idx) : null}>
<td className="text-nowrap shrink-col">
<CodeFileIcon className="text-muted" gapLeft gapRight />
</td>
<td>
<code>
{file.solutionFile.name}
{file.fileEntry ? `/${file.fileEntry}` : ''}
</code>
</td>
<td>
<FormattedMessage
id="app.solutionPlagiarisms.selectPlagiarismFileModal.fromSolution"
defaultMessage="solution"
/>{' '}
<strong>#{file.solution.attemptIndex}</strong>
</td>
<td>
(<DateTime unixts={file.solution.createdAt} />)
</td>
<td className="small">
<GroupsNameContainer groupId={file.groupId} admins />
</td>
</tr>
))}
</tbody>
</Table>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={this.closeDialog}>
<CloseIcon gapRight />
<FormattedMessage id="generic.close" defaultMessage="Close" />
</Button>
</Modal.Footer>
</Modal>
)}
</>
)}
</>
</ResourceRenderer>
) : (
<SourceCodeBox
{...file}
Expand Down
Loading

0 comments on commit 0fa1927

Please sign in to comment.