diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -39,6 +39,17 @@ return true end + #admin always count as every roles + def role_authorization(roles) + return false unless check_valid_login + user = User.find(session[:user_id]) + return true if user.admin? + roles.each do |r| + return true if user.has_role?(r) + end + unauthorized_redirect + end + def authorization_by_roles(allowed_roles) return false unless check_valid_login unless @current_user.roles.detect { |role| allowed_roles.member?(role.name) } diff --git a/app/controllers/main_controller.rb b/app/controllers/main_controller.rb --- a/app/controllers/main_controller.rb +++ b/app/controllers/main_controller.rb @@ -71,7 +71,7 @@ end @submission.submitted_at = Time.new.gmtime - @submission.ip_address = request.remote_ip + @submission.ip_address = cookies.encrypted[:uuid] if @current_user.admin? == false && GraderConfiguration.time_limit_mode? && @current_user.contest_finished? @submission.errors.add(:base,"The contest is over.") diff --git a/app/controllers/report_controller.rb b/app/controllers/report_controller.rb --- a/app/controllers/report_controller.rb +++ b/app/controllers/report_controller.rb @@ -234,16 +234,21 @@ return unless @problem + #model submisssion + @model_subs = Submission.where(problem: @problem,tag: Submission.tags[:model]) + + + #calculate best submission @by_lang = {} #aggregrate by language range =65 - @histogram = { data: Array.new(range,0), summary: {} } + #@histogram = { data: Array.new(range,0), summary: {} } @summary = {count: 0, solve: 0, attempt: 0} user = Hash.new(0) Submission.where(problem_id: @problem.id).find_each do |sub| #histogram d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60 - @histogram[:data][d.to_i] += 1 if d < range + #@histogram[:data][d.to_i] += 1 if d < range next unless sub.points @summary[:count] += 1 @@ -311,9 +316,13 @@ end end - @histogram[:summary][:max] = [@histogram[:data].max,1].max + #@histogram[:summary][:max] = [@histogram[:data].max,1].max @summary[:attempt] = user.count user.each_value { |v| @summary[:solve] += 1 if v == 1 } + + + #for new graph + @chart_dataset = @problem.get_jschart_history.to_json.html_safe end def stuck #report struggling user,problem diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -1,7 +1,8 @@ class SubmissionsController < ApplicationController + before_action :set_submission, only: [:show,:download,:compiler_msg,:rejudge,:set_tag, :edit] before_action :check_valid_login before_action :submission_authorization, only: [:show, :download, :edit] - before_action :admin_authorization, only: [:rejudge] + before_action only: [:rejudge, :set_tag] do role_authorization([:ta]) end # GET /submissions # GET /submissions.json @@ -27,8 +28,6 @@ # GET /submissions/1 # GET /submissions/1.json def show - @submission = Submission.find(params[:id]) - #log the viewing user = User.find(session[:user_id]) SubmissionViewLog.create(user_id: session[:user_id],submission_id: @submission.id) unless user.admin? @@ -37,12 +36,10 @@ end def download - @submission = Submission.find(params[:id]) send_data(@submission.source, {:filename => @submission.download_filename, :type => 'text/plain'}) end def compiler_msg - @submission = Submission.find(params[:id]) respond_to do |format| format.js end @@ -65,7 +62,6 @@ # GET /submissions/1/edit def edit - @submission = Submission.find(params[:id]) @source = @submission.source.to_s @problem = @submission.problem @lang_id = @submission.language.id @@ -82,7 +78,6 @@ # GET /submissions/:id/rejudge def rejudge - @submission = Submission.find(params[:id]) @task = @submission.task @task.status_inqueue! if @task respond_to do |format| @@ -90,6 +85,11 @@ end end + def set_tag + @submission.update(tag: params[:tag]) + redirect_to @submission + end + protected def submission_authorization @@ -106,6 +106,10 @@ unauthorized_redirect return false end + + def set_submission + @submission = Submission.find(params[:id]) + end end diff --git a/app/models/problem.rb b/app/models/problem.rb --- a/app/models/problem.rb +++ b/app/models/problem.rb @@ -24,6 +24,28 @@ DEFAULT_TIME_LIMIT = 1 DEFAULT_MEMORY_LIMIT = 32 + def get_jschart_history + start = 4.month.ago.beginning_of_day + start_date = start.to_date + count = Submission.where(problem: self).where('submitted_at >= ?', start).group('DATE(submitted_at)').count + i = 0 + label = [] + value = [] + while (start_date + i < Time.zone.now.to_date) + if (start_date+i).day == 1 + #label << (start_date+i).strftime("%d %b %Y") + #label << (start_date+i).strftime("%d") + else + #label << ' ' + #label << (start_date+i).strftime("%d") + end + label << (start_date+i).strftime("%d-%b") + value << (count[start_date+i] || 0) + i+=1 + end + return {labels: label,datasets: [label:'sub',data: value, backgroundColor: 'rgba(54, 162, 235, 0.2)', borderColor: 'rgb(75, 192, 192)']} + end + def self.available_problems available.order(date_added: :desc).order(:name) #Problem.available.all(:order => "date_added DESC, name ASC") diff --git a/app/models/submission.rb b/app/models/submission.rb --- a/app/models/submission.rb +++ b/app/models/submission.rb @@ -1,5 +1,7 @@ class Submission < ActiveRecord::Base + enum tag: {default: 0, model: 1}, _prefix: true + belongs_to :language belongs_to :problem belongs_to :user @@ -24,12 +26,12 @@ def self.find_all_last_by_problem(problem_id) # need to put in SQL command, maybe there's a better way Submission.includes(:user).find_by_sql("SELECT * FROM submissions " + - "WHERE id = " + - "(SELECT MAX(id) FROM submissions AS subs " + - "WHERE subs.user_id = submissions.user_id AND " + - "problem_id = " + problem_id.to_s + " " + - "GROUP BY user_id) " + - "ORDER BY user_id") + "WHERE id = " + + "(SELECT MAX(id) FROM submissions AS subs " + + "WHERE subs.user_id = submissions.user_id AND " + + "problem_id = " + problem_id.to_s + " " + + "GROUP BY user_id) " + + "ORDER BY user_id") end def self.find_in_range_by_user_and_problem(user_id, problem_id,since_id,until_id) @@ -75,12 +77,12 @@ i = 0 source.each_line do |s| if s =~ option - words = s.split - return words[1] + words = s.split + return words[1] end i = i + 1 if i==10 - return nil + return nil end end return nil diff --git a/app/views/report/_task_hof.html.haml b/app/views/report/_task_hof.html.haml --- a/app/views/report/_task_hof.html.haml +++ b/app/views/report/_task_hof.html.haml @@ -24,113 +24,131 @@ } -.container +.container-fluid + .row + .col-md-8 + .card + .card-body + %h2.card-title Submission History + %canvas#chart{height: '50px'} + + .col-md-4 + .card + .card-body + %h2.card-title General Info + .row + .col-sm-6 + Subs + .col-sm-6 + = @summary[:count] + .row + .col-sm-6 + Solved/Attempted User + .col-sm-6 + #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%) .row .col-md-4 - %h2 Overall Stat - %table.table.table-hover - %thead - %tr - %th - %th - %tbody - %tr - %td.info_param Submissions - %td= @summary[:count] - %tr - %td.info_param Solved/Attempted User - %td #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%) - - if @best - %tr - %td.info_param Best Runtime - %td - by #{link_to @best[:runtime][:user], stat_user_path(@best[:runtime][:user_id])} - %br - using #{@best[:runtime][:lang]} - %br - with #{@best[:runtime][:value] * 1000} milliseconds - %br - at submission - = link_to "#" + @best[:runtime][:sub_id].to_s, submission_path(@best[:runtime][:sub_id]) - - %tr - %td.info_param - Best Memory Usage - %sup{ id: "xmem_remark", - style: "position:relative; color: blue;", - data: {toggle: 'tooltip', placement: 'top', animation: 'false', delay: 20}, - title: "This counts only for submission with 100% score. Right now, java is excluded from memory usage competition. (Because it always uses 2GB memory...)"} - [?] - %td - by #{link_to @best[:memory][:user], stat_user_path(@best[:memory][:user_id])} - %br - using #{@best[:memory][:lang]} - %br - with #{number_with_delimiter(@best[:memory][:value])} kbytes - %br - at submission - = link_to "#" + @best[:memory][:sub_id].to_s, submission_path(@best[:memory][:sub_id]) - - %tr - %td.info_param Shortest Code - %td - by #{link_to @best[:length][:user], stat_user_path(@best[:length][:user_id])} - %br - using #{@best[:length][:lang]} - %br - with #{@best[:length][:value]} bytes - %br - at submission - = link_to "#" + @best[:length][:sub_id].to_s, submission_path(@best[:length][:sub_id]) - - %tr - %td.info_param First solver - %td - - if @best[:first][:user] != '(NULL)' - #{link_to @best[:first][:user], stat_user_path(@best[:first][:user_id])} is the first solver - %br - using #{@best[:first][:lang]} - %br - on #{@best[:first][:value]} - %br - at submission - = link_to "#" + @best[:first][:sub_id].to_s, submission_path( @best[:first][:sub_id]) - - else - no first solver - .col-md-8 - - if @best - %h2 By Language + .card + .card-body + %h2.card-title Model submission %table.table.table-hover %thead %tr - %th Language - %th Best runtime (ms) - %th Best memory (kbytes) - %th Shortest Code (bytes) - %th First solver + %th #Sub + %th Author %tbody - - @by_lang.each do |lang,value| + - @model_subs.each do |sub| %tr - %td= lang + %td= link_to "##{sub.id}", submission_path(sub) + %td= sub.user.full_name + .col-md-8 + - if @best + .card + .card-body + %h2.card-title Top Submissions + %table.table.table-hover + %thead + %tr + %th Language + %th Best runtime (ms) + %th Best memory (kbytes) + %th Shortest Code (bytes) + %th First solver + %tbody + %tr.bg-warning %td - = link_to value[:runtime][:user], stat_user_path(value[:runtime][:user_id]) + Overall + %td + by #{link_to @best[:runtime][:user], stat_user_path(@best[:runtime][:user_id])} + %br + using #{@best[:runtime][:lang]} + %br + with #{@best[:runtime][:value] * 1000} milliseconds + %br= link_to "#" + @best[:runtime][:sub_id].to_s, submission_path(@best[:runtime][:sub_id]) + %td + by #{link_to @best[:memory][:user], stat_user_path(@best[:memory][:user_id])} %br - = "#{(value[:runtime][:value] * 1000).to_i} @" - = link_to "#" + value[:runtime][:sub_id].to_s, submission_path( value[:runtime][:sub_id]) + using #{@best[:memory][:lang]} + %br + with #{number_with_delimiter(@best[:memory][:value])} kbytes + %br= link_to "#" + @best[:memory][:sub_id].to_s, submission_path(@best[:memory][:sub_id]) + %td + by #{link_to @best[:length][:user], stat_user_path(@best[:length][:user_id])} + %br + using #{@best[:length][:lang]} + %br + with #{@best[:length][:value]} bytes + %br= link_to "#" + @best[:length][:sub_id].to_s, submission_path(@best[:length][:sub_id]) %td - = link_to value[:memory][:user], stat_user_path( value[:memory][:user_id]) - %br - = "#{number_with_delimiter(value[:memory][:value])} @" - = link_to "#" + value[:memory][:sub_id].to_s, submission_path(value[:memory][:sub_id]) - %td - = link_to value[:length][:user], stat_user_path(value[:length][:user_id]) - %br - = "#{value[:length][:value]} @" - = link_to "#" + value[:length][:sub_id].to_s, submission_path(value[:length][:sub_id]) - %td - - if value[:first][:user] != '(NULL)' #TODO: i know... this is wrong... - = link_to value[:first][:user], stat_user_path(value[:first][:user_id]) + - if @best[:first][:user] != '(NULL)' + #{link_to @best[:first][:user], stat_user_path(@best[:first][:user_id])} is the first solver + %br + using #{@best[:first][:lang]} + %br + on #{@best[:first][:value]} + %br= link_to "#" + @best[:first][:sub_id].to_s, submission_path( @best[:first][:sub_id]) + - else + no first solver + - @by_lang.each do |lang,value| + %tr + %td= lang + %td + = link_to value[:runtime][:user], stat_user_path(value[:runtime][:user_id]) + %br + = "#{(value[:runtime][:value] * 1000).to_i} @" + = link_to "#" + value[:runtime][:sub_id].to_s, submission_path( value[:runtime][:sub_id]) + %td + = link_to value[:memory][:user], stat_user_path( value[:memory][:user_id]) + %br + = "#{number_with_delimiter(value[:memory][:value])} @" + = link_to "#" + value[:memory][:sub_id].to_s, submission_path(value[:memory][:sub_id]) + %td + = link_to value[:length][:user], stat_user_path(value[:length][:user_id]) %br - = "#{value[:first][:value]} @" - = link_to "#" + value[:first][:sub_id].to_s, submission_path( value[:first][:sub_id]) + = "#{value[:length][:value]} @" + = link_to "#" + value[:length][:sub_id].to_s, submission_path(value[:length][:sub_id]) + %td + - if value[:first][:user] != '(NULL)' #TODO: i know... this is wrong... + = link_to value[:first][:user], stat_user_path(value[:first][:user_id]) + %br + = "#{value[:first][:value]} @" + = link_to "#" + value[:first][:sub_id].to_s, submission_path( value[:first][:sub_id]) +%script{src:"https://cdn.jsdelivr.net/npm/chart.js"} +:javascript + data = #{@chart_dataset} + config = { + type: 'bar', + data: data, + options: { + plugins: { + legend: { + display: false + }, + }, + } + } + Chart.defaults.font.size = 15 + //Chart.defaults.font.family = 'Sarabun Light' + chart = new Chart($('#chart'),config) + diff --git a/app/views/report/problem_hof.html.haml b/app/views/report/problem_hof.html.haml --- a/app/views/report/problem_hof.html.haml +++ b/app/views/report/problem_hof.html.haml @@ -23,7 +23,7 @@ Please select a problem. - else %h1 [#{Problem.find(params[:id]).name}] #{Problem.find(params[:id]).full_name} - %h2 Submission History - =render partial: 'application/bar_graph', locals: { histogram: @histogram } + -# %h2 Submission History + -# =render partial: 'application/bar_graph', locals: { histogram: @histogram } =render partial: 'task_hof' diff --git a/app/views/submissions/show.html.haml b/app/views/submissions/show.html.haml --- a/app/views/submissions/show.html.haml +++ b/app/views/submissions/show.html.haml @@ -89,11 +89,6 @@ %td %button.btn.btn-info.btn-xs{type: 'button', data: {toggle: 'modal', target: '#compiler'}} view - - if session[:admin] - %tr - %td.text-right - %strong IP - %td #{@submission.ip_address} %tr %td.text-right %strong Grading Task Status @@ -101,6 +96,23 @@ = @task.status_str if @task - if session[:admin] = link_to "rejudge", rejudge_submission_path, data: {remote: true}, class: 'btn btn-info btn-xs' + - if session[:admin] + %tr + %td.text-right + %strong IP + %td #{@submission.ip_address} + %tr + %td.text-right + %strong Model solution + %td + - if @submission.tag_model? + YES + - if session[:admin] + = link_to "remove model status", set_tag_submission_path(@submission, tag: :default), class: 'btn btn-warning btn-xs' + - else + No + - if session[:admin] + = link_to "set as model solution", set_tag_submission_path(@submission, tag: :model), class: 'btn btn-success btn-xs' .modal.fade#compiler{tabindex: -1,role: 'dialog'} diff --git a/config/routes.rb b/config/routes.rb --- a/config/routes.rb +++ b/config/routes.rb @@ -94,6 +94,7 @@ get 'download' get 'compiler_msg' get 'rejudge' + get 'set_tag' end collection do get 'prob/:problem_id', to: 'submissions#index', as: 'problem' diff --git a/db/migrate/20220204080936_add_type_to_submission.rb b/db/migrate/20220204080936_add_type_to_submission.rb new file mode 100644 --- /dev/null +++ b/db/migrate/20220204080936_add_type_to_submission.rb @@ -0,0 +1,6 @@ +class AddTypeToSubmission < ActiveRecord::Migration[7.0] + def change + add_column :submissions, :tag, :integer, default: 0 + add_column :problems, :difficulty, :integer + end +end