diff --git a/src/locales/cs.json b/src/locales/cs.json index 26f6e87b8..c5bff10ab 100644 --- a/src/locales/cs.json +++ b/src/locales/cs.json @@ -161,6 +161,7 @@ "app.assignmentStats.noSolutions": "Momentálně zde nejsou žádná odevzdaná řešení.", "app.assignmentStats.onlyBestSolutionsCheckbox": "Pouze nejlepší řešení", "app.assignmentStats.pendingReviews": "V tuto chvíli {count, plural, one {je otevřena} =2 {jsou otevřeny} =3 {jsou otevřeny} =4 {jsou otevřeny} other {je otevřeno}} {count} {count, plural, one {revize} =2 {revize} =3 {revize} =4 {revize} other {revizí}} ze všech řešení vybrané úlohy. Nezapomeňte, že autoři úloh vidí vaše komentáře zdrojových kódů až poté, co jsou příslušné revize uzavřeny.", + "app.assignmentStats.plagiarismsDetected": "V seznamu {count, plural, one {je zobrazeno} =2 {jsou zobrazena} =3 {jsou zobrazena} =4 {jsou zobrazena} other {je zobrazeno}} {count} řešení, {count, plural, one {ke kterému} other {ke kterým}} byla nelezena podobná řešení. V takových případech se může jednat o plagiáty.", "app.assignmentStats.title": "Všechna řešení zadané úlohy", "app.assignments.deadline": "Termín odevzdání", "app.assignments.deleteAllButton": "Smazat", @@ -1664,6 +1665,7 @@ "app.solution.submittedAt": "Odevzdáno", "app.solution.submittedBeforeFirstDeadline": "Řešení bylo odevzdáno včas před termínem", "app.solution.submittedBeforeSecondDeadline": "Řešení bylo odevzdáno po prvním termínu, ale stále ještě před druhým termínem", + "app.solution.suspectedPlagiarismWarning": "Byla detekována podobná řešení, toto řešení by mohlo být plagiátem. Detialy naleznete na stránce 'Podobnosti'.", "app.solution.title": "Řešení", "app.solutionArchiveInfoBox.description": "Všechny soubory v ZIPu", "app.solutionDetail.comments.additionalSwitchNote": "(autor tohoto řešení a vedoucí této skupiny)", diff --git a/src/locales/en.json b/src/locales/en.json index 8fd7194d8..97ff0a6fb 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -161,6 +161,7 @@ "app.assignmentStats.noSolutions": "There are currently no submitted solutions.", "app.assignmentStats.onlyBestSolutionsCheckbox": "Best solutions only", "app.assignmentStats.pendingReviews": "There {count, plural, one {is} other {are}} {count} pending {count, plural, one {review} other {reviews}} among the solutions of the selected assignment. Remember that the review comments are visible to the author after a review is closed.", + "app.assignmentStats.plagiarismsDetected": "There {count, plural, one {is} other {are}} {count} {count, plural, one {solution} other {solutions}} with detected similarities. Such solutions may be plagiarisms.", "app.assignmentStats.title": "All Submissions of The Assignment", "app.assignments.deadline": "Deadline", "app.assignments.deleteAllButton": "Delete", @@ -1664,6 +1665,7 @@ "app.solution.submittedAt": "Submitted at", "app.solution.submittedBeforeFirstDeadline": "The solution was submitted before the deadline", "app.solution.submittedBeforeSecondDeadline": "The solution was submitted after the first but still before the second deadline", + "app.solution.suspectedPlagiarismWarning": "Similar solutions have been detected, the solution is suspected of being a plagiarism. Details can be found on 'Similarities' page.", "app.solution.title": "The Solution", "app.solutionArchiveInfoBox.description": "All files in a ZIP archive", "app.solutionDetail.comments.additionalSwitchNote": "(author of the solution and supervisors of this group)", diff --git a/src/pages/AssignmentStats/AssignmentStats.js b/src/pages/AssignmentStats/AssignmentStats.js index df4a7a03e..43d768c53 100644 --- a/src/pages/AssignmentStats/AssignmentStats.js +++ b/src/pages/AssignmentStats/AssignmentStats.js @@ -21,6 +21,7 @@ import Icon, { DetailIcon, CodeFileIcon, LoadingIcon, + PlagiarismIcon, ResultsIcon, UserIcon, } from '../../components/icons'; @@ -262,6 +263,13 @@ const getPendingReviewSolutions = defaultMemoize(assignmentSolutions => .filter(solution => solution && solution.review && solution.review.startedAt && !solution.review.closedAt) ); +const getPlagiarisms = defaultMemoize(assignmentSolutions => + assignmentSolutions + .toArray() + .map(getJsData) + .filter(solution => solution && solution.plagiarism) +); + const localStorageStateKey = 'AssignmentStats.state'; class AssignmentStats extends Component { @@ -351,6 +359,7 @@ class AssignmentStats extends Component { } = this.props; const pendingReviews = getPendingReviewSolutions(assignmentSolutions); + const plagiarisms = getPlagiarisms(assignmentSolutions); return ( + {plagiarisms && plagiarisms.length > 0 && ( + }> + + + )} + {pendingReviews && pendingReviews.length > 0 && ( diff --git a/src/pages/GroupUserSolutions/GroupUserSolutions.js b/src/pages/GroupUserSolutions/GroupUserSolutions.js index 282179f78..fd0d3aafd 100644 --- a/src/pages/GroupUserSolutions/GroupUserSolutions.js +++ b/src/pages/GroupUserSolutions/GroupUserSolutions.js @@ -13,7 +13,14 @@ import ReviewSolutionContainer from '../../containers/ReviewSolutionContainer'; import Page from '../../components/layout/Page'; import { GroupNavigation } from '../../components/layout/Navigation'; -import Icon, { AssignmentIcon, DetailIcon, CodeFileIcon, LoadingIcon, UserIcon } from '../../components/icons'; +import Icon, { + AssignmentIcon, + DetailIcon, + CodeFileIcon, + LoadingIcon, + PlagiarismIcon, + UserIcon, +} from '../../components/icons'; import SolutionTableRowIcons from '../../components/Assignments/SolutionsTable/SolutionTableRowIcons'; import Points from '../../components/Assignments/SolutionsTable/Points'; import SolutionsTable from '../../components/Assignments/SolutionsTable'; @@ -302,6 +309,25 @@ const getPendingReviewSolutions = defaultMemoize((assignments, getAssignmentSolu : [] ); +const getPlagiarisms = defaultMemoize((assignments, getAssignmentSolutions) => + assignments + ? assignments + .toArray() + .map(getJsData) + .filter(identity) + .reduce( + (acc, assignment) => [ + ...acc, + ...getAssignmentSolutions(assignment.id) + .toArray() + .map(getJsData) + .filter(solution => solution && solution.plagiarism), + ], + [] + ) + : [] +); + const localStorageStateKey = 'GroupUserSolutions.state'; class GroupUserSolutions extends Component { @@ -379,6 +405,7 @@ class GroupUserSolutions extends Component { } = this.props; const pendingReviews = getPendingReviewSolutions(assignments, getAssignmentSolutions); + const plagiarisms = getPlagiarisms(assignments, getAssignmentSolutions); return ( + {plagiarisms && plagiarisms.length > 0 && ( + }> + + + )} + {pendingReviews && pendingReviews.length > 0 && ( diff --git a/src/pages/Solution/Solution.js b/src/pages/Solution/Solution.js index 5ea120c52..9d10097c0 100644 --- a/src/pages/Solution/Solution.js +++ b/src/pages/Solution/Solution.js @@ -45,7 +45,7 @@ import { assignmentSubmissionScoreConfigSelector } from '../../redux/selectors/e import { registerSolutionVisit } from '../../components/Solutions/RecentlyVisited/functions'; import { hasPermissions } from '../../helpers/common'; -import { SolutionResultsIcon, WarningIcon } from '../../components/icons'; +import { PlagiarismIcon, SolutionResultsIcon, WarningIcon } from '../../components/icons'; const assignmentHasRuntime = defaultMemoize( (assignment, runtimeId) => @@ -117,7 +117,7 @@ class Solution extends Component { userId={solution.authorId} groupId={assignment.groupId} attemptIndex={solution.attemptIndex} - plagiarism={Boolean(solution.plagiarism)} + plagiarism={Boolean(solution.plagiarism) && hasPermissions(solution, 'viewDetectedPlagiarisms')} canViewSolutions={hasPermissions(assignment, 'viewAssignmentSolutions')} canViewExercise={ hasPermissions( @@ -128,6 +128,15 @@ class Solution extends Component { canViewUserProfile={hasPermissions(assignment, 'viewAssignmentSolutions')} /> + {solution.plagiarism && hasPermissions(solution, 'viewDetectedPlagiarisms') && ( + }> + + + )} + {(hasPermissions(solution, 'setFlag') || hasPermissions(solution, 'review') || hasPermissions(assignment, 'resubmitSubmissions')) && ( diff --git a/src/pages/SolutionPlagiarisms/SolutionPlagiarisms.js b/src/pages/SolutionPlagiarisms/SolutionPlagiarisms.js index 502026c76..ccc14af0b 100644 --- a/src/pages/SolutionPlagiarisms/SolutionPlagiarisms.js +++ b/src/pages/SolutionPlagiarisms/SolutionPlagiarisms.js @@ -175,7 +175,7 @@ class SolutionPlagiarisms extends Component { userId={solution.authorId} groupId={assignment.groupId} attemptIndex={solution.attemptIndex} - plagiarism={Boolean(solution.plagiarism)} + plagiarism={Boolean(solution.plagiarism) && hasPermissions(solution, 'viewDetectedPlagiarisms')} canViewSolutions={hasPermissions(assignment, 'viewAssignmentSolutions')} canViewExercise={ hasPermissions( diff --git a/src/pages/SolutionSourceCodes/SolutionSourceCodes.js b/src/pages/SolutionSourceCodes/SolutionSourceCodes.js index bab55a155..6c0603fe8 100644 --- a/src/pages/SolutionSourceCodes/SolutionSourceCodes.js +++ b/src/pages/SolutionSourceCodes/SolutionSourceCodes.js @@ -16,6 +16,7 @@ import Button, { TheButtonGroup } from '../../components/widgets/TheButton'; import { CircleIcon, CodeCompareIcon, + PlagiarismIcon, RefreshIcon, SolutionResultsIcon, StopIcon, @@ -255,7 +256,7 @@ class SolutionSourceCodes extends Component { userId={solution.authorId} groupId={assignment.groupId} attemptIndex={solution.attemptIndex} - plagiarism={Boolean(solution.plagiarism)} + plagiarism={Boolean(solution.plagiarism) && hasPermissions(solution, 'viewDetectedPlagiarisms')} canViewSolutions={hasPermissions(assignment, 'viewAssignmentSolutions')} canViewExercise={ hasPermissions( @@ -272,6 +273,7 @@ class SolutionSourceCodes extends Component { ) : null } /> + {diffMode && ( <>

@@ -290,7 +292,9 @@ class SolutionSourceCodes extends Component { userId={secondSolution.authorId} groupId={secondAssignment.groupId} attemptIndex={secondSolution.attemptIndex} - plagiarism={Boolean(secondSolution.plagiarism)} + plagiarism={ + Boolean(secondSolution.plagiarism) && hasPermissions(secondSolution, 'viewDetectedPlagiarisms') + } canViewSolutions={hasPermissions(secondAssignment, 'viewAssignmentSolutions')} canViewExercise={ hasPermissions( @@ -310,6 +314,15 @@ class SolutionSourceCodes extends Component { {isSupervisorRole(effectiveRole) && ( <> + {solution.plagiarism && hasPermissions(solution, 'viewDetectedPlagiarisms') && ( + }> + + + )} + {!diffMode && (