Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add plagiarism detection for evaluations #5534

Merged
merged 9 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions app/assets/javascripts/dolos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@
import { i18n } from "i18n/i18n";

const LOADER_ID = "dolos-loader";
const BTN_ID = "dolos-btn";
const DOLOS_URL = "/dolos_reports";

export async function startDolos(url: string): Promise<void> {
export function initDolosBtn(btnID: string, url: string): void {
const btn = document.getElementById(btnID) as HTMLLinkElement;
btn.addEventListener("click", () => startDolos(btn, url));

Check warning on line 12 in app/assets/javascripts/dolos.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/dolos.ts#L10-L12

Added lines #L10 - L12 were not covered by tests
}

export async function startDolos(btn: HTMLLinkElement, url: string): Promise<void> {

Check warning on line 15 in app/assets/javascripts/dolos.ts

View check run for this annotation

Codecov / codecov/patch

app/assets/javascripts/dolos.ts#L15

Added line #L15 was not covered by tests
const loader = document.getElementById(LOADER_ID) as LoadingBar;
loader.show();
const btn = document.getElementById(BTN_ID) as HTMLLinkElement;
btn.classList.add("disabled");

const settings = new FormData();
Expand Down Expand Up @@ -43,7 +46,7 @@
loader.hide();

const newBtn = html`
<a id="${BTN_ID}" class="btn btn-outline with-icon" href="${dolosUrl}" target="_blank">
<a id="${btn.id}" class="btn btn-outline with-icon" href="${dolosUrl}" target="_blank">
<i class="mdi mdi-graph-outline mdi-18"></i> ${i18n.t("js.dolos.view_report")}
</a>
`;
Expand Down
8 changes: 3 additions & 5 deletions app/assets/stylesheets/components/table.css.scss
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,9 @@
.table-resource .actions {
text-align: right;

&.submissions-table {
// by default the size of the element in the col is used as width if width is smaller than that element.
// This width is just here to force everything to the right
width: 1px;
}
// by default the size of the element in the col is used as width if width is smaller than that element.
// This width is just here to force everything to the right
width: 1px;
}

tr.gu-mirror {
Expand Down
11 changes: 9 additions & 2 deletions app/helpers/export_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ class Zipper

attr_reader :users, :item, :errors

CONVERT_TO_BOOL = %w[deadline only_last_submission with_info all with_labels].freeze
SUPPORTED_OPTIONS = %w[deadline filter_students group_by only_last_submission with_info all with_labels].freeze
CONVERT_TO_BOOL = %w[deadline only_last_submission with_info all with_labels evaluation].freeze
SUPPORTED_OPTIONS = %w[deadline filter_students group_by only_last_submission with_info all with_labels evaluation].freeze

# Keywords used:
# :item : A User, Course or Series for which submissions will be exported
Expand All @@ -32,6 +32,7 @@ def initialize(**kwargs)
.to_h { |m| [m.user, m.course_labels] }

@users = @users_labels.keys if users.nil?
@users = @item.evaluation.users if evaluation?
when Course
@list = @item.series if all?
@users_labels = @item.course_memberships
Expand Down Expand Up @@ -92,6 +93,10 @@ def all?
@options[:all].present?
end

def evaluation?
@options[:evaluation].present? && @item.is_a?(Series) && @item.evaluation.present?
end

def zip_filename
@item.is_a?(User) ? "#{@item.full_name.parameterize}.zip" : "#{@item.name.parameterize}.zip"
end
Expand Down Expand Up @@ -240,6 +245,8 @@ def bundle
end

def get_submissions_for_series(series, selected_exercises, users)
return policy_scope(series.evaluation.submissions.where(user_id: users.map(&:id), exercise_id: selected_exercises.map(&:id))) if evaluation?

submissions = policy_scope(Submission).where(user_id: users.map(&:id), exercise_id: selected_exercises.map(&:id), course: series.course_id).includes(:user, :exercise)
submissions = submissions.before_deadline(@options[:deadline]) if deadline?
submissions = submissions.group(:user_id, :exercise_id).most_recent if only_last_submission?
Expand Down
4 changes: 2 additions & 2 deletions app/javascript/packs/dolos.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { startDolos } from "dolos.ts";
import { initDolosBtn } from "dolos.ts";

window.dodona.startDolos = startDolos;
window.dodona.initDolosBtn = initDolosBtn;
1 change: 1 addition & 0 deletions app/models/evaluation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class Evaluation < ApplicationRecord
has_many :users, through: :evaluation_users
has_many :exercises, through: :evaluation_exercises
has_many :score_items, through: :evaluation_exercises
has_many :submissions, through: :feedbacks

has_many :annotated_submissions, -> { distinct }, through: :annotations, source: :submission

Expand Down
27 changes: 22 additions & 5 deletions app/views/evaluations/_exercises_progress_table.html.erb
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
<%= content_for :javascripts do %>
<%= javascript_include_tag 'dolos' %>
<% end %>
<div class="table-scroll-wrapper">
<d-loading-bar id="dolos-loader"></d-loading-bar>
<table class="table activity-table table-resource">
<thead>
<tr>
<th class="status-icon"></th>
<th><%= t "activities.index.activity_title" %></th>
<th class='count'><%= t "evaluations.show.evaluation_progress" %></th>
<th class="status-icon"></th>
<th class="actions"></th>
</tr>
</thead>
<tbody>
Expand All @@ -28,14 +32,27 @@
</span>
</td>

<td>
<td class="actions">
<% if meta[:next_incomplete_feedback].present? %>
<%= link_to meta[:next_incomplete_feedback], title: t('evaluations.show.next_incomplete_feedback'), class: 'btn btn-icon' do %>
<i class="mdi mdi-chevron-right"></i>
<%= link_to meta[:next_incomplete_feedback], title: t('evaluations.show.next_incomplete_feedback'), class: 'btn btn-outline with-icon' do %>
<i class="mdi mdi-message-draw mdi-18"></i>
<%= t(".evaluate") %>
<% end %>
<% else %>
<span><i class="mdi mdi-check colored-correct"></i></span>
<a class="btn btn-outline disabled with-icon">
<i class="mdi mdi-check mdi-18"></i>
<%= t(".evaluated") %>
</a>
<% end %>
<a class="btn btn-outline with-icon" id="dolos-btn-<%= meta[:exercise].id %>">
<i class="mdi mdi-graph-outline mdi-18"></i>
<%= t "submissions.index.detect_plagiarism" %>
</a>
<script>
dodona.ready.then(() =>{
dodona.initDolosBtn('dolos-btn-<%= meta[:exercise].id %>', "<%= series_exports_path(@evaluation.series, token: (@evaluation.series.access_token if @evaluation.series.hidden?), selected_ids: [meta[:exercise].id], evaluation: true) %>");
})
</script>
</td>
</tr>
<% end %>
Expand Down
26 changes: 11 additions & 15 deletions app/views/evaluations/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,17 @@
<h2 class="card-title-text"><%= t('.title', series: @evaluation.series.name) %></h2>
</div>
<div class="card-supporting-text">
<div class="row">
<div class="col-lg-6 col-md-12 order-lg-1">
<div class="callout callout-info">
<h4><%= t ".explanation_title" %></h4>
<% if @evaluation.score_items.empty? %>
<%= t ".explanation_no_grading_html" %>
<% else %>
<%= t ".explanation_yes_grading_html" %>
<% end %>
</div>
</div>
<div class="col-lg-6 col-md-12 order-lg-0">
<p><%= t '.deadline_html', users: @evaluation.users.count, exercises: @evaluation.exercises.count, deadline: l(@evaluation.deadline, format: :submission) %></p>
<%= render partial: 'exercises_progress_table', locals: { metadata: @evaluation.metadata, series: @evaluation.series } %>
</div>
<details>
<summary><%= t ".explanation_title" %></summary>
<% if @evaluation.score_items.empty? %>
<%= t ".explanation_no_grading_html" %>
<% else %>
<%= t ".explanation_yes_grading_html" %>
<% end %>
</details>
<div>
<p><%= t '.deadline_html', users: @evaluation.users.count, exercises: @evaluation.exercises.count, deadline: l(@evaluation.deadline, format: :submission) %></p>
<%= render partial: 'exercises_progress_table', locals: { metadata: @evaluation.metadata, series: @evaluation.series } %>
</div>
</div>
<div class="card-actions card-border">
Expand Down
13 changes: 11 additions & 2 deletions app/views/submissions/index.html.erb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<%= render 'activities/navbar_links' if @activity %>
<%= render 'courses/navbar_links' if @course && !@activity %>
<%= javascript_include_tag 'dolos' %>
<%= content_for :javascripts do %>
<%= javascript_include_tag 'dolos' %>
<% end %>
<div class="row">
<div class="col-12">
<div class="card">
Expand Down Expand Up @@ -43,10 +45,17 @@
<% if current_user&.course_admin?(@course) %>
<%
actions << {icon: 'replay', text: t(".reevaluate_submissions"), confirm: t(".confirm_reevaluate_submissions"), action: mass_rejudge_submissions_path(user_id: @user&.id, activity_id: @activity&.id, course_id: @course&.id, series_id: @series&.id, judge_id: @judge&.id)} if policy(Submission).mass_rejudge?
actions << {icon: 'graph-outline', text: t('.detect_plagiarism'), js: "window.dodona.startDolos(\"#{series_exports_path(@series, token: (@series.access_token if @series.hidden?), selected_ids: [@activity.id])}\")", id: "dolos-btn" } if @series && @activity
options << {label: t('.most_recent'), param: 'most_recent_per_user'} if @activity
options << {label: t('.watch_submissions'), param: 'refresh'}
%>
<% if @series.present? %>
<% actions << {icon: 'graph-outline', text: t('.detect_plagiarism'), id: "dolos-btn" } if @series && @activity %>
<script>
dodona.ready.then(() => {
dodona.initDolosBtn("dolos-btn", "<%= series_exports_path(@series, token: (@series.access_token if @series.hidden?), selected_ids: [@activity.id]) %>");
});
</script>
<% end %>
<% end %>
<%= render partial: 'layouts/searchbar', locals: {actions: actions, options: options, course_labels: @course_labels, statuses: Submission.statuses.keys, refresh_element: "#refresh_element"} %>
<div id="submissions-table-wrapper">
Expand Down
2 changes: 1 addition & 1 deletion config/locales/js/nl.yml
Original file line number Diff line number Diff line change
Expand Up @@ -333,5 +333,5 @@ nl:
draft: Concept
popularity: Populariteit
dolos:
view_report: Plagiaat bekijken
view_report: Rapport bekijken

7 changes: 5 additions & 2 deletions config/locales/views/evaluations/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ en:
evaluation: "Evaluation"
title: "Evaluation for %{series}"
explanation_title: "How do I evaluate a submission?"
explanation_no_grading_html: "<p>In the summary table, you can click on the <i class='mdi mdi-chevron-right mdi-18'></i> icon next to an exercise. You will then be taken to an incompleted submission of a random student. If you wish, you can leave feedback on the code and mark the submission as completed. Note that you can also mark a submission as completed without leaving feedback. To get started, we already marked all exercises without submissions as completed.</p><p>You can find more information in <a href='https://docs.dodona.be/en/guides/teachers/grading/#het-evaluatieoverzicht'>our documentation</a>.</p><p>As a reminder, students will <b>not</b> be able to see the feedback you have given until you click the 'release feedback' button below.</p>"
explanation_yes_grading_html: "<p>In the summary table, you can click on the <i class='mdi mdi-chevron-right mdi-18'></i> icon next to an exercise. You will then be taken to an incomplete submission of a random student. If you wish, you can leave feedback on the code and assign scores using the score items you configured. Note that you can also assign scores without leaving feedback. You can not give scores to exercises without submissions, these are automatically assigned a 0 in the grade overview.</p><p>You can find information in <a href='https://docs.dodona.be/en/guides/teachers/grading/#het-evaluatieoverzicht'>our documentation</a>.</p><p>As a reminder, students will <b>not</b> be able to see the feedback and scores until you click the 'Release feedback' button below.</p>"
explanation_no_grading_html: "<p>In the summary table, you can click on the 'Evaluate' button next to an exercise. You will then be taken to an incompleted submission of a random student. If you wish, you can leave feedback on the code and mark the submission as completed. Note that you can also mark a submission as completed without leaving feedback. To get started, we already marked all exercises without submissions as completed.</p><p>You can find more information in <a href='https://docs.dodona.be/en/guides/teachers/grading/#het-evaluatieoverzicht'>our documentation</a>.</p><p>As a reminder, students will <b>not</b> be able to see the feedback you have given until you click the 'release feedback' button below.</p>"
explanation_yes_grading_html: "<p>In the summary table, you can click on the 'Evaluate' button next to an exercise. You will then be taken to an incomplete submission of a random student. If you wish, you can leave feedback on the code and assign scores using the score items you configured. Note that you can also assign scores without leaving feedback. You can not give scores to exercises without submissions, these are automatically assigned a 0 in the grade overview.</p><p>You can find information in <a href='https://docs.dodona.be/en/guides/teachers/grading/#het-evaluatieoverzicht'>our documentation</a>.</p><p>As a reminder, students will <b>not</b> be able to see the feedback and scores until you click the 'Release feedback' button below.</p>"
release: Release feedback
unrelease: Unrelease feedback
deadline_html: This evaluation of <b>%{users} students</b> contains the submissions of <b>%{exercises} exercises</b> with <b>%{deadline}</b> as a deadline.
Expand Down Expand Up @@ -96,3 +96,6 @@ en:
remove_user_consequences: "If this student already received feedback, that feedback will be deleted. Are you sure you want to remove this student?"
user_progress:
not_submitted: Not submitted
exercises_progress_table:
evaluate: Evaluate
evaluated: Evaluated
7 changes: 5 additions & 2 deletions config/locales/views/evaluations/nl.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ nl:
evaluation: "Evaluatie"
title: "Evaluatie voor %{series}"
explanation_title: "Hoe evalueer ik een oplossing?"
explanation_no_grading_html: "<p>In de samenvattende tabel kan je naast een oefening op <i class='mdi mdi-chevron-right mdi-18'></i> klikken. Je komt dan op een nog te bekijken oplossing van een willekeurige student terecht. Vervolgens kan je indien gewenst feedback op de code geven en de oplossing als afgewerkt markeren. Bemerk dat je ook een oplossing als afgewerkt kan markeren zonder feedback achter te laten. We hebben alvast alle oefeningen zonder ingediende oplossing als afgewerkt gemarkeerd.</p><p>Meer informatie kan je in <a href='https://docs.dodona.be/nl/guides/teachers/grading/#het-evaluatieoverzicht'>onze documentatie</a> vinden.</p><p>Ter herinnering, de studenten krijgen de gegeven feedback <b>niet</b> automatisch te zien, daarvoor dien je hieronder op 'Feedback vrijgeven' te klikken.</p>"
explanation_yes_grading_html: "<p>In de samenvattende tabel kan je naast een oefening op <i class='mdi mdi-chevron-right mdi-18'></i> klikken. Je komt dan op een nog te bekijken oplossing van een willekeurige student terecht. Vervolgens kan je indien gewenst feedback op de code geven en scores toekennen via de scoreonderdelen die je geconfigureerd hebt. Je kan de scores ook invullen zonder feedback achter te laten. Oefeningen zonder ingediende oplossing kan je geen scores geven, deze zullen automatisch een 0 krijgen in het puntenoverzicht.</p><p>Meer informatie kan je in <a href='https://docs.dodona.be/nl/guides/teachers/grading/#het-evaluatieoverzicht'>onze documentatie</a> vinden.</p><p>Ter herinnering, de studenten krijgen de gegeven feedback en scores <b>niet</b> automatisch te zien, daarvoor dien je hieronder op 'Feedback vrijgeven' te klikken.</p>"
explanation_no_grading_html: "<p>In de samenvattende tabel kan je naast een oefening op 'Evalueren' klikken. Je komt dan op een nog te bekijken oplossing van een willekeurige student terecht. Vervolgens kan je indien gewenst feedback op de code geven en de oplossing als afgewerkt markeren. Bemerk dat je ook een oplossing als afgewerkt kan markeren zonder feedback achter te laten. We hebben alvast alle oefeningen zonder ingediende oplossing als afgewerkt gemarkeerd.</p><p>Meer informatie kan je in <a href='https://docs.dodona.be/nl/guides/teachers/grading/#het-evaluatieoverzicht'>onze documentatie</a> vinden.</p><p>Ter herinnering, de studenten krijgen de gegeven feedback <b>niet</b> automatisch te zien, daarvoor dien je hieronder op 'Feedback vrijgeven' te klikken.</p>"
explanation_yes_grading_html: "<p>In de samenvattende tabel kan je naast een oefening op 'Evalueren' klikken. Je komt dan op een nog te bekijken oplossing van een willekeurige student terecht. Vervolgens kan je indien gewenst feedback op de code geven en scores toekennen via de scoreonderdelen die je geconfigureerd hebt. Je kan de scores ook invullen zonder feedback achter te laten. Oefeningen zonder ingediende oplossing kan je geen scores geven, deze zullen automatisch een 0 krijgen in het puntenoverzicht.</p><p>Meer informatie kan je in <a href='https://docs.dodona.be/nl/guides/teachers/grading/#het-evaluatieoverzicht'>onze documentatie</a> vinden.</p><p>Ter herinnering, de studenten krijgen de gegeven feedback en scores <b>niet</b> automatisch te zien, daarvoor dien je hieronder op 'Feedback vrijgeven' te klikken.</p>"
users: "studenten"
release: Feedback vrijgeven
unrelease: Feedback verbergen
Expand Down Expand Up @@ -96,3 +96,6 @@ nl:
remove_user_consequences: "Als deze student al feedback gekregen had, dan zal deze feedback verwijderd worden. Ben je zeker dat je deze student wil verwijderen?"
user_progress:
not_submitted: Niet ingediend
exercises_progress_table:
evaluate: Evalueren
evaluated: Geëvalueerd