Skip to content

Commit

Permalink
Merge pull request #5420 from dodona-edu/feat/dolos-export
Browse files Browse the repository at this point in the history
Add dolos plagiarism detection
  • Loading branch information
jorg-vr authored May 13, 2024
2 parents 5dc46d8 + c9f7960 commit 7c4a181
Show file tree
Hide file tree
Showing 15 changed files with 117 additions and 10 deletions.
2 changes: 1 addition & 1 deletion app/assets/javascripts/components/search/loading_bar.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { html, TemplateResult } from "lit";
import { html, PropertyValues, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators.js";
import { search } from "search";
import { DodonaElement } from "components/meta/dodona_element";
Expand Down
4 changes: 3 additions & 1 deletion app/assets/javascripts/components/search/search_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ export type SearchAction = {
action?: string,
js?: string,
confirm?: string,
icon: string
icon: string,
id?: string
};

/**
Expand Down Expand Up @@ -77,6 +78,7 @@ export class SearchActions extends DodonaElement {
return this.filteredActions.map(action => html`
<a class="btn btn-outline with-icon ml-2"
@click=${() => this.performAction(action)}
id=${action.id}
>
<i class='mdi mdi-${action.icon} mdi-18'></i>
${action.text}
Expand Down
52 changes: 52 additions & 0 deletions app/assets/javascripts/dolos.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { LoadingBar } from "components/search/loading_bar";
import { exportData, prepareExport } from "export";
import { fetch } from "utilities";
import { html, render } from "lit";
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> {
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();
settings.append("with_info", "true");
settings.append("only_last_submission", "true");
settings.append("group_by", "user");

const exportDataUrl = await prepareExport(url, settings);
const download = await exportData(exportDataUrl);

const dolosResponse = await fetch(DOLOS_URL, {
method: "POST",
body: JSON.stringify({ export_id: download.id }),
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
}
});

if (!dolosResponse.ok) {
alert("An error occurred while creating the plagiarism report.");
loader.hide();
return;
}

const json = await dolosResponse.json();
const dolosUrl = json.html_url;
window.open(dolosUrl, "_blank");
loader.hide();

const newBtn = html`
<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>
`;
render(newBtn, btn.parentElement, { renderBefore: btn });
btn.remove();
}
19 changes: 13 additions & 6 deletions app/assets/javascripts/export.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { i18n } from "i18n/i18n";
import { fetch } from "utilities";
import { Collapse } from "bootstrap";

function initSelection(): void {
Expand Down Expand Up @@ -104,7 +105,7 @@ function initSelection(): void {
});

const exportDataUrl = await prepareExport(form.action, data);
const downloadUrl = await exportLocation(exportDataUrl);
const downloadUrl = (await exportData(exportDataUrl)).url;

// Update the stepper content
downloadingPanel.querySelector(".stepper-part").innerHTML = i18n.t("js.export.ready_html", { url: downloadUrl });
Expand Down Expand Up @@ -134,18 +135,24 @@ async function prepareExport(url: string, data: FormData): Promise<string> {
return json.url;
}

type ExportData = {
ready: boolean;
url: string;
id: number;
};

/**
* Returns the url of the blob to download when the download is ready.
* Returns the data of the blob to download when the download is ready.
* @param url The URL of the download endpoint
*/
async function exportLocation(url: string): Promise<string> {
async function exportData(url: string): Promise<ExportData> {
const response = await fetch(url);
const data = await response.json();
if (!data.ready) {
await new Promise(resolve => setTimeout(resolve, 1000));
return await exportLocation(url);
return await exportData(url);
}
return data.url;
return data;
}

export { initSelection, prepareExport, exportLocation };
export { initSelection, prepareExport, exportData };
6 changes: 6 additions & 0 deletions app/assets/javascripts/i18n/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,9 @@
"date_before": "before",
"date_on": "on",
"description_languages": "Language of the description",
"dolos": {
"view_report": "Open plagiarism report"
},
"draft": "Draft",
"dropdown": {
"multi": {
Expand Down Expand Up @@ -718,6 +721,9 @@
"date_before": "voor",
"date_on": "op",
"description_languages": "Taal van de beschrijving",
"dolos": {
"view_report": "Rapport bekijken"
},
"draft": "Concept",
"dropdown": {
"multi": {
Expand Down
22 changes: 22 additions & 0 deletions app/controllers/dolos_reports_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
class DolosReportsController < ApplicationController
def create
export = Export.find(params[:export_id])

authorize export, :show?
return head :unprocessable_entity unless export.finished?

export.archive.open do |file|
response = HTTParty.post(
'https://dolos.ugent.be/api/reports',
body: {
dataset: {
zipfile: file,
name: export.archive.filename
}
}
)

render json: response
end
end
end
3 changes: 3 additions & 0 deletions app/javascript/packs/dolos.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { startDolos } from "dolos.ts";

window.dodona.startDolos = startDolos;
1 change: 1 addition & 0 deletions app/views/exports/show.json.jbuilder
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
json.id @export.id
json.ready @export.finished?
json.url rails_blob_path(@export.archive, disposition: 'attachment') if @export.finished? && @export.archive.attached?
3 changes: 3 additions & 0 deletions app/views/submissions/index.html.erb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<%= render 'activities/navbar_links' if @activity %>
<%= render 'courses/navbar_links' if @course && !@activity %>
<%= javascript_include_tag 'dolos' %>
<div class="row">
<div class="col-12">
<div class="card">
Expand Down Expand Up @@ -33,6 +34,7 @@
</div>
<% end %>
</div>
<d-loading-bar id="dolos-loader"></d-loading-bar>
<div class="card-supporting-text" id="refresh_element">
<%
actions = []
Expand All @@ -41,6 +43,7 @@
<% 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'}
%>
Expand Down
6 changes: 4 additions & 2 deletions config/initializers/content_security_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
policy.connect_src :self,
'https://cdn.jsdelivr.net/pyodide/',
'https://*.googleapis.com',
'http://localhost:3035', 'ws://localhost:3035'
'http://localhost:3035', 'ws://localhost:3035',
'https://dolos.ugent.be/api/reports'
else
policy.connect_src :self,
'https://cdn.jsdelivr.net/pyodide/',
'https://cdn.jsdelivr.net/npm/mathjax@3/'
'https://cdn.jsdelivr.net/npm/mathjax@3/',
'https://dolos.ugent.be/api/reports'
end

policy.font_src :self, 'https://fonts.gstatic.com',
Expand Down
2 changes: 2 additions & 0 deletions config/locales/js/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -332,3 +332,5 @@ en:
ready_html: Export is ready. The download should start automatically. If not, click <a href="%{url}">this link</a> to start the download.
draft: Draft
popularity: Popularity
dolos:
view_report: Open plagiarism report
3 changes: 3 additions & 0 deletions config/locales/js/nl.yml
Original file line number Diff line number Diff line change
Expand Up @@ -332,3 +332,6 @@ nl:
ready_html: De export is klaar. De download start normaal automatisch. Als dit niet gebeurt, klik dan <a href="%{url}">hier</a>.
draft: Concept
popularity: Populariteit
dolos:
view_report: Plagiaat bekijken

1 change: 1 addition & 0 deletions config/locales/views/submissions/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ en:
most_recent: Most recent submissions per user
watch_submissions: Automatically reload submissions
reevaluate_submissions: Retest submissions
detect_plagiarism: Detect plagiarism
confirm_reevaluate_submissions: Are you sure you want to retest these submissions?
reevaluating_submissions:
one: Re-evaluating 1 submission
Expand Down
1 change: 1 addition & 0 deletions config/locales/views/submissions/nl.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ nl:
watch_submissions: Oplossingen automatisch herladen
most_recent: Meest recente oplossing per gebruiker
reevaluate_submissions: Oplossingen hertesten
detect_plagiarism: Plagiaat detecteren
confirm_reevaluate_submissions: Ben je zeker dat je deze oplossingen wil hertesten?
reevaluating_submissions:
one: 1 oplossing wordt geherevalueerd
Expand Down
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,8 @@
end

get 'inputServiceWorker.js', to: 'activities#input_service_worker', as: 'input_service_worker'

resources :dolos_reports, only: %i[create]
end

# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
Expand Down

0 comments on commit 7c4a181

Please sign in to comment.