diff --git a/Gemfile b/Gemfile
--- a/Gemfile
+++ b/Gemfile
@@ -41,6 +41,8 @@
gem 'jquery-ui-sass-rails'
gem 'jquery-timepicker-addon-rails'
+#syntax highlighter
+gem 'rouge'
gem "haml"
gem "mail"
diff --git a/Gemfile.lock b/Gemfile.lock
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -104,6 +104,7 @@
rdiscount (2.1.7.1)
rdoc (3.12.2)
json (~> 1.4)
+ rouge (1.6.2)
rspec-collection_matchers (1.0.0)
rspec-expectations (>= 2.99.0.beta1)
rspec-core (2.99.2)
@@ -158,6 +159,7 @@
prototype-rails
rails (= 3.2.19)
rdiscount
+ rouge
rspec-rails (~> 2.0)
sass-rails (~> 3.2.3)
test-unit
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
@@ -6,7 +6,12 @@
def admin_authorization
return false unless authenticate
user = User.find(session[:user_id], :include => ['roles'])
- redirect_to :controller => 'main', :action => 'login' unless user.admin?
+ unless user.admin?
+ flash[:notice] = 'You are not authorized to view the page you requested'
+ redirect_to :controller => 'main', :action => 'login' unless user.admin?
+ return false
+ end
+ return true
end
def authorization_by_roles(allowed_roles)
@@ -23,6 +28,10 @@
def authenticate
unless session[:user_id]
+ flash[:notice] = 'You need to login'
+ if GraderConfiguration[SINGLE_USER_MODE_CONF_KEY]
+ flash[:notice] = 'You need to login but you cannot log in at this time'
+ end
redirect_to :controller => 'main', :action => 'login'
return false
end
diff --git a/app/controllers/graders_controller.rb b/app/controllers/graders_controller.rb
--- a/app/controllers/graders_controller.rb
+++ b/app/controllers/graders_controller.rb
@@ -1,6 +1,15 @@
class GradersController < ApplicationController
- before_filter :admin_authorization
+ before_filter :admin_authorization, except: [ :submission ]
+ before_filter(only: [:submission]) {
+ return false unless authenticate
+
+ if GraderConfiguration["right.user_view_submission"]
+ return true;
+ end
+
+ admin_authorization
+ }
verify :method => :post, :only => ['clear_all',
'start_exam',
@@ -63,6 +72,18 @@
def submission
@submission = Submission.find(params[:id])
+ formatter = Rouge::Formatters::HTML.new(css_class: 'highlight', line_numbers: true )
+ lexer = case @submission.language.name
+ when "c" then Rouge::Lexers::C.new
+ when "cpp" then Rouge::Lexers::Cpp.new
+ when "pas" then Rouge::Lexers::Pas.new
+ when "ruby" then Rouge::Lexers::Ruby.new
+ when "python" then Rouge::Lexers::Python.new
+ when "java" then Rouge::Lexers::Java.new
+ end
+ @formatted_code = formatter.format(lexer.lex(@submission.source))
+ @css_style = Rouge::Themes::ThankfulEyes.render(scope: '.highlight')
+
end
# various grader controls
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
@@ -1,4 +1,16 @@
class ReportController < ApplicationController
+
+ before_filter :admin_authorization, only: [:login_stat,:submission_stat]
+ before_filter(only: [:problem_hof]) { |c|
+ return false unless authenticate
+
+ if GraderConfiguration["right.user_view_submission"]
+ return true;
+ end
+
+ admin_authorization
+ }
+
def login_stat
@logins = Array.new
@@ -69,4 +81,108 @@
end
end
end
+
+ def problem_hof
+ # gen problem list
+ @user = User.find(session[:user_id])
+ @problems = @user.available_problems
+
+ # get selected problems or the default
+ if params[:id]
+ begin
+ @problem = Problem.available.find(params[:id])
+ rescue
+ redirect_to action: :problem_hof
+ flash[:notice] = 'Error: submissions for that problem are not viewable.'
+ return
+ end
+ end
+
+ if @problem
+ #aggregrate by language
+ @by_lang = {}
+ Submission.where(problem_id: @problem.id).find_each do |sub|
+ lang = Language.find_by_id(sub.language_id)
+ next unless lang
+ next unless sub.points >= @problem.full_score
+
+ #initialize
+ unless @by_lang.has_key?(lang.pretty_name)
+ @by_lang[lang.pretty_name] = {
+ runtime: { avail: false, value: 2**30-1 },
+ memory: { avail: false, value: 2**30-1 },
+ length: { avail: false, value: 2**30-1 },
+ first: { avail: false, value: DateTime.new(3000,1,1) }
+ }
+ end
+
+ if sub.max_runtime and sub.max_runtime < @by_lang[lang.pretty_name][:runtime][:value]
+ @by_lang[lang.pretty_name][:runtime] = {
+ avail: true,
+ user_id: sub.user_id,
+ value: sub.max_runtime,
+ sub_id: sub.id
+ }
+ end
+
+ if sub.peak_memory and sub.peak_memory < @by_lang[lang.pretty_name][:memory][:value]
+ @by_lang[lang.pretty_name][:memory] = {
+ avail: true,
+ user_id: sub.user_id,
+ value: sub.peak_memory,
+ sub_id: sub.id
+ }
+ end
+
+ if sub.submitted_at and sub.submitted_at < @by_lang[lang.pretty_name][:first][:value] and
+ !sub.user.admin?
+ @by_lang[lang.pretty_name][:first] = {
+ avail: true,
+ user_id: sub.user_id,
+ value: sub.submitted_at,
+ sub_id: sub.id
+ }
+ end
+
+ if @by_lang[lang.pretty_name][:length][:value] > sub.effective_code_length
+ @by_lang[lang.pretty_name][:length] = {
+ avail: true,
+ user_id: sub.user_id,
+ value: sub.effective_code_length,
+ sub_id: sub.id
+ }
+ end
+ end
+
+ #process user_id
+ @by_lang.each do |lang,prop|
+ prop.each do |k,v|
+ v[:user] = User.exists?(v[:user_id]) ? User.find(v[:user_id]).full_name : "(NULL)"
+ end
+ end
+
+ #sum into best
+ if @by_lang and @by_lang.first
+ @best = @by_lang.first[1]
+ @by_lang.each do |lang,prop|
+ if @best[:runtime][:value] >= prop[:runtime][:value]
+ @best[:runtime] = prop[:runtime]
+ @best[:runtime][:lang] = lang
+ end
+ if @best[:memory][:value] >= prop[:memory][:value]
+ @best[:memory] = prop[:memory]
+ @best[:memory][:lang] = lang
+ end
+ if @best[:length][:value] >= prop[:length][:value]
+ @best[:length] = prop[:length]
+ @best[:length][:lang] = lang
+ end
+ if @best[:first][:value] >= prop[:first][:value]
+ @best[:first] = prop[:first]
+ @best[:first][:lang] = lang
+ end
+ end
+ end
+ end
+ end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -30,6 +30,10 @@
append_to menu_items, "[#{I18n.t 'menu.submissions'}]", 'main', 'submission'
append_to menu_items, "[#{I18n.t 'menu.test'}]", 'test', 'index'
end
+
+ if GraderConfiguration['right.user_hall_of_fame']
+ append_to menu_items, "[#{I18n.t 'menu.hall_of_fame'}]", 'report', 'problem_hof'
+ end
append_to menu_items, "[#{I18n.t 'menu.help'}]", 'main', 'help'
if GraderConfiguration['system.user_setting_enabled']
diff --git a/app/views/graders/submission.html.haml b/app/views/graders/submission.html.haml
--- a/app/views/graders/submission.html.haml
+++ b/app/views/graders/submission.html.haml
@@ -1,8 +1,11 @@
+%style{type: "text/css"}
+ = @css_style
+
%h1= "Submission: #{@submission.id}"
%p
User:
- = "#{@submission.user.login}"
+ = "(#{@submission.user.login}) #{@submission.user.full_name}"
%br/
Problem:
- if @submission.problem!=nil
@@ -13,10 +16,12 @@
= "Number: #{@submission.number}"
%br/
= "Submitted at: #{format_short_time(@submission.submitted_at)}"
+ %br/
+ = "Points : #{@submission.points}/#{@submission.problem.full_score}"
+ %br/
+ = "Comment : #{@submission.grader_comment}"
%b Source code (first 10kb)
-%div{:style => "border: 1px solid black; background: lightgrey"}
- - if @submission.source
- %pre
- =h truncate @submission.source, :length => 10240
+//%div.highlight{:style => "border: 1px solid black;"}
+=@formatted_code.html_safe
diff --git a/app/views/report/_all_time_hof.html.haml b/app/views/report/_all_time_hof.html.haml
new file mode 100644
--- /dev/null
+++ b/app/views/report/_all_time_hof.html.haml
@@ -0,0 +1,8 @@
+%h2 Paid in Full
+User with highest number of problem solved
+
+%h2 Polymaths
+User with highest number of problems each solved by more than 1 languages.
+
+%h2 Icebreakers
+If you solve the problem before 95% of your friends, you are an icebreaker.
diff --git a/app/views/report/_report_menu.html.haml b/app/views/report/_report_menu.html.haml
--- a/app/views/report/_report_menu.html.haml
+++ b/app/views/report/_report_menu.html.haml
@@ -2,5 +2,6 @@
.task-menu
Reports
%br/
+ = link_to '[Hall of Fame]', :action => 'problem_hof'
= link_to '[Submission]', :action => 'submission_stat'
= link_to '[Login]', :action => 'login_stat'
diff --git a/app/views/report/_task_hof.html.haml b/app/views/report/_task_hof.html.haml
new file mode 100644
--- /dev/null
+++ b/app/views/report/_task_hof.html.haml
@@ -0,0 +1,62 @@
+:css
+ .hof_user { color: orangered; font-style: italic; }
+ .hof_language { color: green; font-style: italic; }
+ .hof_value { color: deeppink;font-style: italic; }
+
+%h2 Overall
+
+- if @best
+ %b Best Runtime:
+ by #{@best[:runtime][:user]} using #{@best[:runtime][:lang]} with #{@best[:runtime][:value] * 1000} milliseconds at submission
+ = link_to("#" + @best[:runtime][:sub_id].to_s, controller: 'graders', action: 'submission', id:@best[:runtime][:sub_id])
+ %br/
+
+ %b Best Memory Usage:
+ by #{@best[:memory][:user]} using #{@best[:memory][:lang]} with #{@best[:memory][:value]} kbytes at submission
+ = link_to("#" + @best[:memory][:sub_id].to_s, controller: 'graders' , action: 'submission', id:@best[:memory][:sub_id])
+ %br/
+
+ %b Shortest Code:
+ by #{@best[:length][:user]} using #{@best[:length][:lang]} with #{@best[:length][:value]} bytes at submission
+ = link_to("#" + @best[:length][:sub_id].to_s, controller: 'graders' , action: 'submission', id: @best[:length][:sub_id])
+ %br/
+
+ %b First solver:
+ #{@best[:first][:user]} is the first solver using #{@best[:first][:lang]} on #{@best[:first][:value]} at submission
+ = link_to("#" + @best[:first][:sub_id].to_s, controller: 'graders' , action: 'submission', id: @best[:first][:sub_id])
+ %br/
+
+
+ %p
+ This counts only for submission with 100% score
+ Right now, java is excluded from memory usage competition. (Because it always uses 2GB memory...)
+
+ %h2 By language
+
+ %table.info
+ %thead
+ %tr.info-head
+ %th Language
+ %th Best runtime (ms)
+ %th Best memory (kbytes)
+ %th Shortest Code (bytes)
+ %th First solver
+ %tbody
+ - @by_lang.each do |lang,value|
+ %tr{class: cycle('info-even','info-odd')}
+ %td= lang
+ %td
+ = "#{value[:runtime][:user]} (#{(value[:runtime][:value] * 1000).to_i} @"
+ = "#{link_to("#" + value[:runtime][:sub_id].to_s, controller: 'graders' , action: 'submission', id: value[:runtime][:sub_id])} )".html_safe
+ %td
+ = "#{value[:memory][:user]} (#{value[:memory][:value]} @"
+ = "#{link_to("#" + value[:memory][:sub_id].to_s, controller: 'graders' , action: 'submission', id: value[:memory][:sub_id])} )".html_safe
+ %td
+ = "#{value[:length][:user]} (#{value[:length][:value]} @"
+ = "#{link_to("#" + value[:length][:sub_id].to_s, controller: 'graders' , action: 'submission', id: value[:length][:sub_id])} )".html_safe
+ %td
+ = "#{value[:first][:user]} (#{value[:first][:value]} @"
+ = "#{link_to("#" + value[:first][:sub_id].to_s, controller: 'graders' , action: 'submission', id: value[:first][:sub_id])} )".html_safe
+
+- else
+ %h3 No submissions
diff --git a/app/views/report/login_stat.html.haml b/app/views/report/login_stat.html.haml
--- a/app/views/report/login_stat.html.haml
+++ b/app/views/report/login_stat.html.haml
@@ -7,10 +7,8 @@
$('#until_datetime').datetimepicker({ showButtonPanel: true, dateFormat: "yy-mm-dd", controlType: "slider"} );
});
-
%h1 Login status
-
=render partial: 'report_menu'
=render partial: 'date_range', locals: {param_text: 'Login date range:', title: 'Query login stat in the range' }
diff --git a/app/views/report/problem_hof.html.haml b/app/views/report/problem_hof.html.haml
new file mode 100644
--- /dev/null
+++ b/app/views/report/problem_hof.html.haml
@@ -0,0 +1,22 @@
+
+/- if params[:id]
+/ %h1 Tasks Hall of Fame
+/ = link_to('[back to All-Time Hall of Fame]', action: 'problem_hof', id: nil )
+/- else
+/ %h1 All-Time Hall of Fame
+
+
+%h1 Tasks Hall of Fame
+.task-menu
+ Tasks
+ %br/
+ - @problems.each do |prob|
+ = link_to( "[#{prob.name}]", {id: prob.id})
+
+- unless params[:id]
+ /=render partial: 'all_time_hof'
+ Please select a problem.
+- else
+ =render partial: 'task_hof'
+
+
diff --git a/app/views/report/submission_stat.html.haml b/app/views/report/submission_stat.html.haml
--- a/app/views/report/submission_stat.html.haml
+++ b/app/views/report/submission_stat.html.haml
@@ -7,12 +7,8 @@
$('#until_datetime').datetimepicker({ showButtonPanel: true, dateFormat: "yy-mm-dd", controlType: "slider"} );
});
-
%h1 Login status
-
-
-
=render partial: 'report_menu'
=render partial: 'date_range', locals: {param_text: 'Submission date range:', title: 'Query submission stat in the range' }
diff --git a/config/application.rb.SAMPLE b/config/application.rb.SAMPLE
--- a/config/application.rb.SAMPLE
+++ b/config/application.rb.SAMPLE
@@ -59,6 +59,6 @@
# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0'
- config.assets.precompile += ['announcement_refresh.js','effects.js','site_update.js','graders.css','problems.css']
+ config.assets.precompile += ['announcement_refresh.js','effects.js','site_update.js','graders.css','problems.css','new.js']
end
end
diff --git a/config/locales/en.yml b/config/locales/en.yml
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -20,6 +20,7 @@
tasks: 'Tasks'
submissions: 'Submissions'
test: 'Test Interface'
+ hall_of_fame: 'Hall of Fame'
help: 'Help'
settings: 'Settings'
log_out: 'Log out'
diff --git a/config/locales/th.yml b/config/locales/th.yml
--- a/config/locales/th.yml
+++ b/config/locales/th.yml
@@ -20,6 +20,7 @@
tasks: 'โจทย์'
submissions: 'โปรแกรมที่ส่ง'
test: 'ทดสอบโปรแกรม'
+ hall_of_fame: 'หอเกียรติยศ'
help: 'ความช่วยเหลือ'
settings: 'เปลี่ยนรหัสผ่าน'
log_out: 'ออกจากระบบ'
diff --git a/db/migrate/20140823031747_add_more_detail_to_submission.rb b/db/migrate/20140823031747_add_more_detail_to_submission.rb
new file mode 100644
--- /dev/null
+++ b/db/migrate/20140823031747_add_more_detail_to_submission.rb
@@ -0,0 +1,7 @@
+class AddMoreDetailToSubmission < ActiveRecord::Migration
+ def change
+ add_column :submissions, :max_runtime, :float
+ add_column :submissions, :peak_memory, :integer
+ add_column :submissions, :effective_code_length, :integer
+ end
+end
diff --git a/db/seeds.rb b/db/seeds.rb
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -6,60 +6,67 @@
:default_value => 'false',
:description => 'Only admins can log in to the system when running under single user mode.'
},
-
+
{
:key => 'ui.front.title',
:value_type => 'string',
:default_value => 'Grader'
},
-
+
{
:key => 'ui.front.welcome_message',
:value_type => 'string',
:default_value => 'Welcome!'
},
-
+
{
:key => 'ui.show_score',
:value_type => 'boolean',
:default_value => 'true'
},
-
+
{
:key => 'contest.time_limit',
:value_type => 'string',
:default_value => 'unlimited',
:description => 'Time limit in format hh:mm, or "unlimited" for contests with no time limits. This config is CACHED. Restart the server before the change can take effect.'
},
-
+
{
:key => 'system.mode',
:value_type => 'string',
:default_value => 'standard',
:description => 'Current modes are "standard", "contest", "indv-contest", and "analysis".'
},
-
+
{
:key => 'contest.name',
:value_type => 'string',
:default_value => 'Grader',
:description => 'This name will be shown on the user header bar.'
},
-
+
{
:key => 'contest.multisites',
:value_type => 'boolean',
:default_value => 'false',
:description => 'If the server is in contest mode and this option is true, on the log in of the admin a menu for site selections is shown.'
},
-
+
{
- :key => 'system.online_registration',
+ :key => 'right.user_hall_of_fame',
:value_type => 'boolean',
:default_value => 'false',
- :description => 'This option enables online registration.'
+ :description => 'If true, any user can access hall of fame page.'
},
-
+
+ {
+ :key => 'right.user_view_submission',
+ :value_type => 'boolean',
+ :default_value => 'false',
+ :description => 'If true, any user can view submissions of every one.'
+ },
+
# If Configuration['system.online_registration'] is true, the
# system allows online registration, and will use these
# information for sending confirmation emails.
@@ -68,26 +75,33 @@
:value_type => 'string',
:default_value => 'smtp.somehost.com'
},
-
+
{
:key => 'system.online_registration.from',
:value_type => 'string',
:default_value => 'your.email@address'
},
-
+
{
:key => 'system.admin_email',
:value_type => 'string',
:default_value => 'admin@admin.email'
},
-
+
{
:key => 'system.user_setting_enabled',
:value_type => 'boolean',
:default_value => 'true',
:description => 'If this option is true, users can change their settings'
},
-
+
+ {
+ :key => 'system.user_setting_enabled',
+ :value_type => 'boolean',
+ :default_value => 'true',
+ :description => 'If this option is true, users can change their settings'
+ }
+
# If Configuration['contest.test_request.early_timeout'] is true
# the user will not be able to use test request at 30 minutes
# before the contest ends.
@@ -115,7 +129,7 @@
:default_value => 'none',
:description => "New user will be assigned to this contest automatically, if it exists. Set to 'none' if there is no default contest."
}
-
+
]
@@ -194,5 +208,16 @@
seed_root
end
+def seed_more_languages
+ Language.delete_all
+ Language.create( name: 'c', pretty_name: 'C', ext: 'c', common_ext: 'c' )
+ Language.create( name: 'cpp', pretty_name: 'C++', ext: 'cpp', common_ext: 'cpp,cc' )
+ Language.create( name: 'pas', pretty_name: 'Pascal', ext: 'pas', common_ext: 'pas' )
+ Language.create( name: 'ruby', pretty_name: 'Ruby', ext: 'rb', common_ext: 'rb' )
+ Language.create( name: 'python', pretty_name: 'Python', ext: 'py', common_ext: 'py' )
+ Language.create( name: 'java', pretty_name: 'Java', ext: 'java', common_ext: 'java' )
+end
+
seed_config
seed_users_and_roles
+seed_more_languages