-
Notifications
You must be signed in to change notification settings - Fork 72
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
46 changed files
with
1,404 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# frozen_string_literal: true | ||
|
||
class API::CodingTestSubmissionsController < API::BaseController | ||
def create | ||
cts = CodingTestSubmission.new(coding_test_submission_params) | ||
cts.user = current_user | ||
|
||
if cts.save | ||
head :ok | ||
else | ||
render json: { errors: cts.errors }, status: :unprocessable_entity | ||
end | ||
end | ||
|
||
private | ||
|
||
def coding_test_submission_params | ||
params.require(:coding_test_submission).permit( | ||
:coding_test_id, | ||
:code | ||
) | ||
end | ||
end |
19 changes: 19 additions & 0 deletions
19
app/controllers/coding_tests/coding_test_submissions_controller.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# frozen_string_literal: true | ||
|
||
class CodingTests::CodingTestSubmissionsController < ApplicationController | ||
before_action :set_coding_test | ||
|
||
def index | ||
@coding_test_submissions = @coding_test.coding_test_submissions.page(params[:page]) | ||
end | ||
|
||
def show | ||
@coding_test_submission = @coding_test.coding_test_submissions.find(params[:id]) | ||
end | ||
|
||
private | ||
|
||
def set_coding_test | ||
@coding_test = CodingTest.find(params[:coding_test_id]) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# frozen_string_literal: true | ||
|
||
class CodingTestsController < ApplicationController | ||
def show | ||
@coding_test = CodingTest.find(params[:id]) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
# frozen_string_literal: true | ||
|
||
class Mentor::CodingTestsController < ApplicationController | ||
before_action :require_admin_or_mentor_login, only: %i[index new create edit update] | ||
before_action :set_coding_test, only: %i[show edit update destroy] | ||
|
||
def index | ||
@coding_tests = CodingTest.joins(:practice).order('practices.id, coding_tests.position') | ||
end | ||
|
||
def show; end | ||
|
||
def new | ||
@coding_test = CodingTest.new(user: current_user) | ||
end | ||
|
||
def edit; end | ||
|
||
def create | ||
@coding_test = CodingTest.new(coding_test_params) | ||
if @coding_test.save | ||
redirect_to @coding_test, notice: 'コーディング問題を作成しました。' | ||
else | ||
render :new | ||
end | ||
end | ||
|
||
def update | ||
if @coding_test.update(coding_test_params) | ||
redirect_to @coding_test, notice: 'コーディング問題を更新しました。' | ||
else | ||
render :edit | ||
end | ||
end | ||
|
||
def destroy | ||
@coding_test.destroy! | ||
redirect_to mentor_coding_tests_path, notice: 'コーディング問題を削除しました。' | ||
end | ||
|
||
private | ||
|
||
def coding_test_params | ||
params.require(:coding_test).permit( | ||
:title, | ||
:description, | ||
:practice_id, | ||
:user_id, | ||
:language, | ||
coding_test_cases_attributes: %i[id input output _destroy] | ||
) | ||
end | ||
|
||
def set_coding_test | ||
@coding_test = CodingTest.find(params[:id]) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { OnBrowserJudge } from './onbrowserjudge.js' | ||
import ace from 'ace-builds' | ||
import 'ace-builds/webpack-resolver' | ||
import 'ace-builds/src-noconflict/mode-javascript' | ||
import 'ace-builds/src-noconflict/mode-ruby' | ||
import 'ace-builds/src-noconflict/theme-github' | ||
import Bootcamp from 'bootcamp' | ||
|
||
document.addEventListener('DOMContentLoaded', () => { | ||
const id = 'code_editor' | ||
const element = document.getElementById(id) | ||
if (!element) { | ||
return null | ||
} | ||
|
||
const codingTestId = element.dataset.codingTestId | ||
const practiceId = element.dataset.practiceId | ||
const language = element.dataset.language | ||
const editor = ace.edit(id) | ||
|
||
editor.session.setMode(`ace/mode/${language}`) | ||
editor.setTheme('ace/theme/github') | ||
|
||
OnBrowserJudge.workerFile = `../${language}.js` | ||
OnBrowserJudge.getProgram = () => editor.getValue() | ||
OnBrowserJudge.dict.ready = "提出" | ||
OnBrowserJudge.dict.running = "停止" | ||
OnBrowserJudge.dict.preparation = "準備中" | ||
OnBrowserJudge.dict.case_name = "テストケース名" | ||
OnBrowserJudge.dict.status = "結果" | ||
OnBrowserJudge.dict.AC = "正解" | ||
OnBrowserJudge.dict.WA = "不正解" | ||
OnBrowserJudge.dict.RE = "エラー" | ||
OnBrowserJudge.dict.TLE = "時間超過" | ||
OnBrowserJudge.dict.WJ = "ジャッジ待ち" | ||
OnBrowserJudge.timeLimit = 2001 | ||
OnBrowserJudge.process = (program, _casename, _input) => program | ||
OnBrowserJudge.assertEqual = (expected, actual) => { | ||
console.log(`expected: ${expected}, actual: ${actual}`) | ||
console.log(expected === actual.trimEnd()) | ||
return expected === actual.trimEnd() | ||
} | ||
OnBrowserJudge.congratulations = async () => { | ||
alert("正解!") | ||
|
||
const params = { | ||
coding_test_submission: { | ||
coding_test_id: codingTestId, | ||
code: editor.getValue() | ||
} | ||
} | ||
|
||
try { | ||
const response = await Bootcamp.post('/api/coding_test_submissions', params) | ||
if (response.ok) { | ||
location.href = `/practices/${practiceId}` | ||
} else { | ||
console.warn('提出に失敗しました。') | ||
} | ||
} catch (error) { | ||
console.error(error) | ||
} | ||
} | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
const OnBrowserJudge = { | ||
dict: { | ||
ready: "▶Run", | ||
running: "■Stop", | ||
preparation: "In preparation", | ||
case_name: "Case Name", | ||
status: "Status", | ||
AC: "AC", | ||
WA: "WA", | ||
RE: "RE", | ||
CE: "CE", | ||
IE: "IE", | ||
TLE: "TLE", | ||
MLE: "MLE", | ||
OLE: "OLE", | ||
WJ: "WJ" | ||
}, | ||
|
||
timeLimit: 2000, | ||
|
||
initialData: null, | ||
|
||
congratulations: () => {}, | ||
|
||
process: (program, _casename, _input) => program, | ||
|
||
assertEqual: (expected, actual) => expected === actual.trimEnd(), | ||
|
||
|
||
status: "preparation", | ||
|
||
updateStatus: function(status) { | ||
this.status = status | ||
const button = document.getElementById("run") | ||
button.disabled = status === "preparation" | ||
button.innerHTML = this.dict[status] | ||
}, | ||
|
||
runButtonPressed: function() { | ||
switch (this.status) { | ||
case "ready": | ||
this.run() | ||
break | ||
case "running": | ||
this.stop() | ||
break | ||
} | ||
}, | ||
|
||
worker: null, | ||
|
||
timer: null, | ||
|
||
workerEvent: function(e) { | ||
let d = null | ||
switch (e.data[0]) { | ||
case "init": | ||
this.worker.postMessage(["init", this.initialData]) | ||
break | ||
case "ready": | ||
this.updateStatus("ready") | ||
break | ||
case "executed": | ||
d = e.data[1] | ||
this.executed(d.testCase, d.output, d.error, d.errorMessage, d.execTime) | ||
break | ||
} | ||
}, | ||
|
||
loadWorker: function(path) { | ||
const baseURL = window.location.href.replaceAll("\\", "/").replace(/\/[^/]*$/, "/") | ||
const array = [`importScripts("${baseURL}${path}");`] | ||
const blob = new Blob(array, { type: "text/javascript" }) | ||
const url = window.URL.createObjectURL(blob) | ||
return new Worker(url) | ||
}, | ||
|
||
resetWorker: function() { | ||
if (this.worker) this.worker.terminate() | ||
this.worker = this.loadWorker(this.workerFile) | ||
this.worker.addEventListener("message", event => { this.workerEvent(event) }, false) | ||
}, | ||
|
||
run: async function() { | ||
if (this.status !== "ready") return | ||
this.updateStatus("running") | ||
const autocopy = document.getElementById("autocopy") | ||
if (!autocopy || autocopy.checked) this.copyProgram() | ||
this.initializeResult() | ||
this.restTests = Array.from(this.tests) | ||
this.allPassed = true | ||
this.program = this.getProgram() | ||
this.nextTest() | ||
}, | ||
|
||
nextTest: function() { | ||
const testCase = this.restTests.shift() | ||
const input = document.getElementById(`${testCase}_input`).innerText.trim() | ||
const program = this.process(this.getProgram(), testCase, input) | ||
this.timer = setTimeout(() => this.tle(testCase), this.timeLimit * 2) | ||
this.worker.postMessage(["execute", { testCase, program, input }]) | ||
}, | ||
|
||
tle: function(testCase) { | ||
this.updateResult(testCase, "TLE", '', '', '', this.timeLimit * 2) | ||
this.stop() | ||
}, | ||
|
||
executed: function(testCase, output, error, errorMessage, execTime) { | ||
clearTimeout(this.timer) | ||
let result = "AC" | ||
if (error !== 0) { | ||
result = error === 1 ? "CE" : "RE" | ||
} else { | ||
if (execTime > this.timeLimit) result = "TLE" | ||
const expected = document.getElementById(`${testCase}_output`).innerText.trim() | ||
if (! this.assertEqual(expected, output)) result = "WA" | ||
} | ||
|
||
console.log('output:', output); | ||
console.log('error:', error); | ||
console.log('errorMessage:', errorMessage); | ||
|
||
this.updateResult(testCase, result, output, error, errorMessage, execTime) | ||
if (result !== "AC") this.allPassed = false | ||
if (this.restTests.length === 0) { | ||
if (this.allPassed) setTimeout(this.congratulations, 20) | ||
this.updateStatus("ready") | ||
} else { | ||
this.nextTest() | ||
} | ||
}, | ||
|
||
initializeResult: function() { | ||
document.getElementById("result").innerHTML = ` | ||
<thead><tr> | ||
<th>${this.dict.case_name}</th> | ||
<th>出力</th> | ||
<th>エラー</th> | ||
<th>${this.dict.status}</th> | ||
</tr></thead>` | ||
|
||
for(const testCase of OnBrowserJudge.tests) { | ||
const tr = document.createElement("tr") | ||
tr.innerHTML = ` | ||
<td id="${testCase}">${testCase}</td> | ||
<td id="${testCase}_std_output"><pre><code></code></pre></td></td> | ||
<td id="${testCase}_std_error"><pre><code></code></pre></td> | ||
<td id="${testCase}_status"><span class="status wj">${this.dict.WJ}</span></td>` | ||
document.getElementById("result").appendChild(tr) | ||
} | ||
document.getElementById("result").scrollIntoView({ behavior: "smooth" }) | ||
}, | ||
|
||
updateResult: function(testCase, result, output, _error, errorMessage, _execTime) { | ||
document.getElementById(`${testCase}_std_output`).innerHTML = `<pre><code>${output}</code></pre>` | ||
document.getElementById(`${testCase}_std_error`).innerHTML = `<pre><code>${errorMessage}</code></pre>` | ||
const span = `<span class="status ${result.toLowerCase()}` + | ||
`" title="${result}">${this.dict[result]}</span>` | ||
document.getElementById(`${testCase}_status`).innerHTML = span | ||
}, | ||
|
||
stop: function() { | ||
window.clearTimeout(this.timer) | ||
Array.from(document.getElementsByClassName("wj")).forEach((elm) => { | ||
elm.innerText = "" | ||
}) | ||
|
||
this.updateStatus("preparation") | ||
this.resetWorker() | ||
}, | ||
|
||
copyProgram: function() { | ||
navigator.clipboard.writeText(this.getProgram()) | ||
} | ||
} | ||
|
||
|
||
window.addEventListener("DOMContentLoaded", () => { | ||
const editor = document.getElementById('code_editor') | ||
if (!editor) return null | ||
|
||
function getTestNames() { | ||
return Array.from(document.getElementsByTagName("pre")).map(elm => | ||
elm.id | ||
).filter(id => | ||
id.match(/_input$/) && document.getElementById(id.replace(/_input$/, "_output")) | ||
).map(id => | ||
id.replace(/_input$/, "") | ||
) | ||
} | ||
OnBrowserJudge.tests = getTestNames() | ||
|
||
function trimAllSampleCases() { | ||
const samples = document.getElementsByClassName("sample") | ||
for (const elm of samples) elm.innerText = elm.innerText.trim() | ||
} | ||
trimAllSampleCases() | ||
|
||
OnBrowserJudge.updateStatus("preparation") | ||
OnBrowserJudge.resetWorker() | ||
document.getElementById("run").onclick = () => OnBrowserJudge.runButtonPressed() | ||
}) | ||
|
||
export { OnBrowserJudge } |
Oops, something went wrong.