Description:
merge
Commit status:
[Not Reviewed]
References:
merge java
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r701:f26733606a76 - - 21 files changed: 104 inserted, 29 deleted

@@ -76,49 +76,49
76 76 @submission.source_filename = "live_edit.#{language.ext}"
77 77 @submission.language = language
78 78 end
79 79
80 80 @submission.submitted_at = Time.new.gmtime
81 81 @submission.ip_address = request.remote_ip
82 82
83 83 if GraderConfiguration.time_limit_mode? and user.contest_finished?
84 84 @submission.errors.add(:base,"The contest is over.")
85 85 prepare_list_information
86 86 render :action => 'list' and return
87 87 end
88 88
89 89 if @submission.valid?(@current_user)
90 90 if @submission.save == false
91 91 flash[:notice] = 'Error saving your submission'
92 92 elsif Task.create(:submission_id => @submission.id,
93 93 :status => Task::STATUS_INQUEUE) == false
94 94 flash[:notice] = 'Error adding your submission to task queue'
95 95 end
96 96 else
97 97 prepare_list_information
98 98 render :action => 'list' and return
99 99 end
100 - redirect_to :action => 'list'
100 + redirect_to edit_submission_path(@submission)
101 101 end
102 102
103 103 def source
104 104 submission = Submission.find(params[:id])
105 105 if ((submission.user_id == session[:user_id]) and
106 106 (submission.problem != nil) and
107 107 (submission.problem.available))
108 108 send_data(submission.source,
109 109 {:filename => submission.download_filename,
110 110 :type => 'text/plain'})
111 111 else
112 112 flash[:notice] = 'Error viewing source'
113 113 redirect_to :action => 'list'
114 114 end
115 115 end
116 116
117 117 def compiler_msg
118 118 @submission = Submission.find(params[:id])
119 119 if @submission.user_id == session[:user_id]
120 120 render :action => 'compiler_msg', :layout => 'empty'
121 121 else
122 122 flash[:notice] = 'Error viewing source'
123 123 redirect_to :action => 'list'
124 124 end
@@ -144,92 +144,96
144 144 end
145 145
146 146 def turn_all_off
147 147 Problem.available.all.each do |problem|
148 148 problem.available = false
149 149 problem.save
150 150 end
151 151 redirect_to action: :index
152 152 end
153 153
154 154 def turn_all_on
155 155 Problem.where.not(available: true).each do |problem|
156 156 problem.available = true
157 157 problem.save
158 158 end
159 159 redirect_to action: :index
160 160 end
161 161
162 162 def stat
163 163 @problem = Problem.find(params[:id])
164 164 unless @problem.available or session[:admin]
165 165 redirect_to :controller => 'main', :action => 'list'
166 166 return
167 167 end
168 - @submissions = Submission.includes(:user).where(problem_id: params[:id]).order(:user_id,:id)
168 + @submissions = Submission.includes(:user).includes(:language).where(problem_id: params[:id]).order(:user_id,:id)
169 169
170 170 #stat summary
171 171 range =65
172 172 @histogram = { data: Array.new(range,0), summary: {} }
173 173 user = Hash.new(0)
174 174 @submissions.find_each do |sub|
175 175 d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
176 176 @histogram[:data][d.to_i] += 1 if d < range
177 177 user[sub.user_id] = [user[sub.user_id], ((sub.try(:points) || 0) >= @problem.full_score) ? 1 : 0].max
178 178 end
179 179 @histogram[:summary][:max] = [@histogram[:data].max,1].max
180 180
181 181 @summary = { attempt: user.count, solve: 0 }
182 182 user.each_value { |v| @summary[:solve] += 1 if v == 1 }
183 183 end
184 184
185 185 def manage
186 186 @problems = Problem.order(date_added: :desc)
187 187 end
188 188
189 189 def do_manage
190 190 if params.has_key? 'change_date_added'
191 191 change_date_added
192 192 elsif params.has_key? 'add_to_contest'
193 193 add_to_contest
194 194 elsif params.has_key? 'enable_problem'
195 195 set_available(true)
196 196 elsif params.has_key? 'disable_problem'
197 197 set_available(false)
198 198 elsif params.has_key? 'add_group'
199 199 group = Group.find(params[:group_id])
200 200 ok = []
201 201 failed = []
202 202 get_problems_from_params.each do |p|
203 203 begin
204 204 group.problems << p
205 205 ok << p.full_name
206 206 rescue => e
207 207 failed << p.full_name
208 208 end
209 209 end
210 210 flash[:success] = "The following problems are added to the group #{group.name}: " + ok.join(', ') if ok.count > 0
211 211 flash[:alert] = "The following problems are already in the group #{group.name}: " + failed.join(', ') if failed.count > 0
212 + elsif params.has_key? 'add_tags'
213 + get_problems_from_params.each do |p|
214 + p.tag_ids += params[:tag_ids]
215 + end
212 216 end
213 217
214 218 redirect_to :action => 'manage'
215 219 end
216 220
217 221 def import
218 222 @allow_test_pair_import = allow_test_pair_import?
219 223 end
220 224
221 225 def do_import
222 226 old_problem = Problem.find_by_name(params[:name])
223 227 if !allow_test_pair_import? and params.has_key? :import_to_db
224 228 params.delete :import_to_db
225 229 end
226 230 @problem, import_log = Problem.create_from_import_form_params(params,
227 231 old_problem)
228 232
229 233 if !@problem.errors.empty?
230 234 render :action => 'import' and return
231 235 end
232 236
233 237 if old_problem!=nil
234 238 flash[:notice] = "The test data has been replaced for problem #{@problem.name}"
235 239 end
@@ -279,28 +283,28
279 283 problems = get_problems_from_params
280 284 problems.each do |p|
281 285 p.available = avail
282 286 p.save
283 287 end
284 288 end
285 289
286 290 def get_problems_from_params
287 291 problems = []
288 292 params.keys.each do |k|
289 293 if k.index('prob-')==0
290 294 name, id, order = k.split('-')
291 295 problems << Problem.find(id)
292 296 end
293 297 end
294 298 problems
295 299 end
296 300
297 301 def get_problems_stat
298 302 end
299 303
300 304 private
301 305
302 306 def problem_params
303 - params.require(:problem).permit(:name, :full_name, :full_score, :date_added, :available, :test_allowed,:output_only, :url, :description)
307 + params.require(:problem).permit(:name, :full_name, :full_score, :date_added, :available, :test_allowed,:output_only, :url, :description, tag_ids:[])
304 308 end
305 309
306 310 end
@@ -31,48 +31,50
31 31 end
32 32
33 33 def show_max_score
34 34 #process parameters
35 35 #problems
36 36 @problems = []
37 37 if params[:problem_id]
38 38 params[:problem_id].each do |id|
39 39 next unless id.strip != ""
40 40 pid = Problem.find_by_id(id.to_i)
41 41 @problems << pid if pid
42 42 end
43 43 end
44 44
45 45 #users
46 46 @users = if params[:user] == "all" then
47 47 User.includes(:contests).includes(:contest_stat)
48 48 else
49 49 User.includes(:contests).includes(:contest_stat).where(enabled: true)
50 50 end
51 51
52 52 #set up range from param
53 53 @since_id = params.fetch(:from_id, 0).to_i
54 54 @until_id = params.fetch(:to_id, 0).to_i
55 + @since_id = nil if @since_id == 0
56 + @until_id = nil if @until_id == 0
55 57
56 58 #calculate the routine
57 59 @scorearray = calculate_max_score(@problems, @users, @since_id, @until_id)
58 60
59 61 #rencer accordingly
60 62 if params[:button] == 'download' then
61 63 csv = gen_csv_from_scorearray(@scorearray,@problems)
62 64 send_data csv, filename: 'max_score.csv'
63 65 else
64 66 #render template: 'user_admin/user_stat'
65 67 render 'max_score'
66 68 end
67 69
68 70 end
69 71
70 72 def score
71 73 if params[:commit] == 'download csv'
72 74 @problems = Problem.all
73 75 else
74 76 @problems = Problem.available_problems
75 77 end
76 78 @users = User.includes(:contests, :contest_stat).where(enabled: true)
77 79 @scorearray = Array.new
78 80 @users.each do |u|
@@ -20,39 +20,41
20 20 end
21 21
22 22 # POST /tags
23 23 def create
24 24 @tag = Tag.new(tag_params)
25 25
26 26 if @tag.save
27 27 redirect_to @tag, notice: 'Tag was successfully created.'
28 28 else
29 29 render :new
30 30 end
31 31 end
32 32
33 33 # PATCH/PUT /tags/1
34 34 def update
35 35 if @tag.update(tag_params)
36 36 redirect_to @tag, notice: 'Tag was successfully updated.'
37 37 else
38 38 render :edit
39 39 end
40 40 end
41 41
42 42 # DELETE /tags/1
43 43 def destroy
44 + #remove any association
45 + ProblemTag.where(tag_id: @tag.id).destroy_all
44 46 @tag.destroy
45 47 redirect_to tags_url, notice: 'Tag was successfully destroyed.'
46 48 end
47 49
48 50 private
49 51 # Use callbacks to share common setup or constraints between actions.
50 52 def set_tag
51 53 @tag = Tag.find(params[:id])
52 54 end
53 55
54 56 # Only allow a trusted parameter "white list" through.
55 57 def tag_params
56 58 params.require(:tag).permit(:name, :description, :public)
57 59 end
58 60 end
@@ -64,63 +64,66
64 64 append_to menu_items, "[#{I18n.t 'menu.test'}]", 'test', 'index'
65 65 end
66 66
67 67 if GraderConfiguration['right.user_hall_of_fame']
68 68 append_to menu_items, "[#{I18n.t 'menu.hall_of_fame'}]", 'report', 'problem_hof'
69 69 end
70 70 append_to menu_items, "[#{I18n.t 'menu.help'}]", 'main', 'help'
71 71
72 72 if GraderConfiguration['system.user_setting_enabled']
73 73 append_to menu_items, "[#{I18n.t 'menu.settings'}]", 'users', 'index'
74 74 end
75 75 append_to menu_items, "[#{I18n.t 'menu.log_out'}]", 'main', 'login'
76 76
77 77 menu_items.html_safe
78 78 end
79 79
80 80 def append_to(option,label, controller, action)
81 81 option << ' ' if option!=''
82 82 option << link_to_unless_current(label,
83 83 :controller => controller,
84 84 :action => action)
85 85 end
86 86
87 87 def format_short_time(time)
88 - now = Time.now.gmtime
88 + now = Time.zone.now
89 89 st = ''
90 - if (time.yday != now.yday) or
91 - (time.year != now.year)
92 - st = time.strftime("%x ")
90 + if (time.yday != now.yday) or (time.year != now.year)
91 + st = time.strftime("%d/%m/%y ")
93 92 end
94 93 st + time.strftime("%X")
95 94 end
96 95
97 96 def format_short_duration(duration)
98 97 return '' if duration==nil
99 98 d = duration.to_f
100 99 return Time.at(d).gmtime.strftime("%X")
101 100 end
102 101
102 + def format_full_time_ago(time)
103 + st = time_ago_in_words(time) + ' ago (' + format_short_time(time) + ')'
104 + end
105 +
103 106 def read_textfile(fname,max_size=2048)
104 107 begin
105 108 File.open(fname).read(max_size)
106 109 rescue
107 110 nil
108 111 end
109 112 end
110 113
111 114 def toggle_button(on,toggle_url,id, option={})
112 115 btn_size = option[:size] || 'btn-xs'
113 116 link_to (on ? "Yes" : "No"), toggle_url,
114 117 {class: "btn btn-block #{btn_size} btn-#{on ? 'success' : 'default'} ajax-toggle",
115 118 id: id,
116 119 data: {remote: true, method: 'get'}}
117 120 end
118 121
119 122 def get_ace_mode(language)
120 123 # return ace mode string from Language
121 124
122 125 case language.pretty_name
123 126 when 'Pascal'
124 127 'ace/mode/pascal'
125 128 when 'C++','C'
126 129 'ace/mode/c_cpp'
@@ -13,50 +13,50
13 13 validate :must_have_valid_problem
14 14 validate :must_specify_language
15 15
16 16 has_one :task
17 17
18 18 before_save :assign_latest_number_if_new_recond
19 19
20 20 def self.find_last_by_user_and_problem(user_id, problem_id)
21 21 where("user_id = ? AND problem_id = ?",user_id,problem_id).last
22 22 end
23 23
24 24 def self.find_all_last_by_problem(problem_id)
25 25 # need to put in SQL command, maybe there's a better way
26 26 Submission.includes(:user).find_by_sql("SELECT * FROM submissions " +
27 27 "WHERE id = " +
28 28 "(SELECT MAX(id) FROM submissions AS subs " +
29 29 "WHERE subs.user_id = submissions.user_id AND " +
30 30 "problem_id = " + problem_id.to_s + " " +
31 31 "GROUP BY user_id) " +
32 32 "ORDER BY user_id")
33 33 end
34 34
35 35 def self.find_in_range_by_user_and_problem(user_id, problem_id,since_id,until_id)
36 36 records = Submission.where(problem_id: problem_id,user_id: user_id)
37 - records = records.where('id >= ?',since_id) if since_id > 0
38 - records = records.where('id <= ?',until_id) if until_id > 0
37 + records = records.where('id >= ?',since_id) if since_id and since_id > 0
38 + records = records.where('id <= ?',until_id) if until_id and until_id > 0
39 39 records.all
40 40 end
41 41
42 42 def self.find_last_for_all_available_problems(user_id)
43 43 submissions = Array.new
44 44 problems = Problem.available_problems
45 45 problems.each do |problem|
46 46 sub = Submission.find_last_by_user_and_problem(user_id, problem.id)
47 47 submissions << sub if sub!=nil
48 48 end
49 49 submissions
50 50 end
51 51
52 52 def self.find_by_user_problem_number(user_id, problem_id, number)
53 53 where("user_id = ? AND problem_id = ? AND number = ?",user_id,problem_id,number).first
54 54 end
55 55
56 56 def self.find_all_by_user_problem(user_id, problem_id)
57 57 where("user_id = ? AND problem_id = ?",user_id,problem_id)
58 58 end
59 59
60 60 def download_filename
61 61 if self.problem.output_only
62 62 return self.source_filename
@@ -1,26 +1,28
1 -
2 1 - if submission.nil?
3 2 = "-"
4 3 - else
4 + %strong= "Submission ID:"
5 + = submission.id
6 + %br
5 7 - unless submission.graded_at
6 - = t 'main.submitted_at'
7 - = format_short_time(submission.submitted_at.localtime)
8 + %strong= t 'main.submitted_at:'
9 + = format_full_time_ago(submission.submitted_at.localtime)
8 10 - else
9 - %strong= t 'main.graded_at'
10 - = "#{format_short_time(submission.graded_at.localtime)} "
11 + %strong= t 'main.graded_at:'
12 + = format_full_time_ago(submission.graded_at.localtime)
11 13 %br
12 14 - if GraderConfiguration['ui.show_score']
13 15 %strong=t 'main.score'
14 16 = "#{(submission.points*100/submission.problem.full_score).to_i} "
15 17 = " ["
16 18 %tt
17 19 = submission.grader_comment
18 20 = "]"
19 21 %br
20 22 %strong View:
21 23 - if GraderConfiguration.show_grading_result
22 24 = link_to '[detailed result]', :action => 'result', :id => submission.id
23 - = link_to "#{t 'main.cmp_msg'}", {:action => 'compiler_msg', :id => submission.id}, {popup: true,class: 'btn btn-xs btn-info'}
25 + = link_to "#{t 'main.cmp_msg'}", {:action => 'compiler_msg', :id => submission.id}, {popup: true,class: 'btn btn-xs btn-info'} if submission.graded_at
24 26 = link_to "#{t 'main.src_link'}", download_submission_path(submission.id), class: 'btn btn-xs btn-info'
25 27 = link_to "#{t 'main.submissions_link'}", problem_submissions_path(problem_id), class: 'btn btn-xs btn-info'
26 28
@@ -25,48 +25,49
25 25 - if GraderConfiguration['right.user_hall_of_fame']
26 26 = add_menu("#{I18n.t 'menu.hall_of_fame'}", 'report', 'problem_hof')
27 27 / display MODE button (with countdown in contest mode)
28 28 - if GraderConfiguration.analysis_mode?
29 29 %div.navbar-btn.btn.btn-success#countdown= "ANALYSIS MODE"
30 30 - elsif GraderConfiguration.time_limit_mode?
31 31 - if @current_user.contest_finished?
32 32 %div.navbar-btn.btn.btn-danger#countdown= "Contest is over"
33 33 - elsif !@current_user.contest_started?
34 34 %div.navbar-btn.btn.btn-primary#countdown= (t 'title_bar.contest_not_started')
35 35 - else
36 36 %div.navbar-btn.btn.btn-primary#countdown asdf
37 37 :javascript
38 38 $("#countdown").countdown({until: "+#{@current_user.contest_time_left.to_i}s", layout: 'Time left: {hnn}:{mnn}:{snn}'});
39 39 / admin section
40 40 - if (@current_user!=nil) and (session[:admin])
41 41 / management
42 42 %li.dropdown
43 43 %a.dropdown-toggle{href: '#', data: {toggle:'dropdown'}, aria: {haspopup:"true", expanded:"false"}, role: "button"}
44 44 Manage
45 45 %span.caret
46 46 %ul.dropdown-menu
47 47 = add_menu( 'Announcements', 'announcements', 'index')
48 48 = add_menu( 'Problems', 'problems', 'index')
49 + = add_menu( 'Tags', 'tags', 'index')
49 50 = add_menu( 'Users', 'user_admin', 'index')
50 51 = add_menu( 'User Groups', 'groups', 'index')
51 52 = add_menu( 'Graders', 'graders', 'list')
52 53 = add_menu( 'Message ', 'messages', 'console')
53 54 %li.divider{role: 'separator'}
54 55 = add_menu( 'System config', 'configurations', 'index')
55 56 %li.divider{role: 'separator'}
56 57 = add_menu( 'Sites', 'sites', 'index')
57 58 = add_menu( 'Contests', 'contest_management', 'index')
58 59 / report
59 60 %li.dropdown
60 61 %a.dropdown-toggle{href: '#', data: {toggle:'dropdown'}, aria: {haspopup:"true", expanded:"false"}, role: "button"}
61 62 Report
62 63 %span.caret
63 64 %ul.dropdown-menu
64 65 = add_menu( 'Current Score', 'report', 'current_score')
65 66 = add_menu( 'Score Report', 'report', 'max_score')
66 67 = add_menu( 'Report', 'report', 'multiple_login')
67 68 - if (ungraded = Submission.where('graded_at is null').where('submitted_at < ?', 1.minutes.ago).count) > 0
68 69 =link_to "#{ungraded} backlogs!",
69 70 grader_list_path,
70 71 class: 'navbar-btn btn btn-default btn-warning', data: {toggle: 'tooltip'},title: 'Number of ungraded submission'
71 72
72 73 %ul.nav.navbar-nav.navbar-right
@@ -1,38 +1,41
1 1 = error_messages_for 'problem'
2 2 / [form:problem]
3 3 .form-group
4 4 %label{:for => "problem_name"} Name
5 5 = text_field 'problem', 'name', class: 'form-control'
6 6 %small
7 7 Do not directly edit the problem name, unless you know what you are doing. If you want to change the name, use the name change button in the problem management menu instead.
8 8 .form-group
9 9 %label{:for => "problem_full_name"} Full name
10 10 = text_field 'problem', 'full_name', class: 'form-control'
11 11 .form-group
12 12 %label{:for => "problem_full_score"} Full score
13 13 = text_field 'problem', 'full_score', class: 'form-control'
14 14 .form-group
15 + %label{:for => "problem_full_score"} Tags
16 + = collection_select(:problem, :tag_ids, Tag.all, :id, :name, {}, {multiple: true, class: 'form-control select2'})
17 + .form-group
15 18 %label{:for => "problem_date_added"} Date added
16 19 = date_select 'problem', 'date_added', class: 'form-control'
17 20 - # TODO: these should be put in model Problem, but I can't think of
18 21 - # nice default values for them. These values look fine only
19 22 - # in this case (of lazily adding new problems).
20 23 - @problem.available = true if @problem!=nil and @problem.available==nil
21 24 - @problem.test_allowed = true if @problem!=nil and @problem.test_allowed==nil
22 25 - @problem.output_only = false if @problem!=nil and @problem.output_only==nil
23 26 .checkbox
24 27 %label{:for => "problem_available"}
25 28 = check_box :problem, :available
26 29 Available?
27 30 .checkbox
28 31 %label{:for => "problem_test_allowed"}
29 32 = check_box :problem, :test_allowed
30 33 Test allowed?
31 34 .checkbox
32 35 %label{:for => "problem_output_only"}
33 36 = check_box :problem, :output_only
34 37 Output only?
35 38 = error_messages_for 'description'
36 39 .form-group
37 40 %label{:for => "description_body"} Description
38 41 %br/
@@ -1,60 +1,65
1 1 - content_for :head do
2 2 = stylesheet_link_tag 'problems'
3 3 %h1 Problems
4 4 %p
5 5 = link_to 'Import problems', {:action => 'import'}, class: 'btn btn-success btn-sm'
6 6 = link_to 'New problem', new_problem_path, class: 'btn btn-success btn-sm'
7 7 = link_to 'Bulk Manage', { action: 'manage'}, class: 'btn btn-info btn-sm'
8 8 = link_to 'Turn off all problems', {:action => 'turn_all_off'}, class: 'btn btn-default btn-sm'
9 9 = link_to 'Turn on all problems', {:action => 'turn_all_on'}, class: 'btn btn-default btn-sm'
10 10 .submitbox
11 11 = form_tag :action => 'quick_create' do
12 12 %b Quick New:
13 13 %label{:for => "problem_name"} Name
14 14 = text_field 'problem', 'name'
15 15 |
16 16 %label{:for => "problem_full_name"} Full name
17 17 = text_field 'problem', 'full_name'
18 18 = submit_tag "Create"
19 19 %table.table.table-condense.table-hover
20 20 %thead
21 21 %th Name
22 22 %th Full name
23 23 %th.text-right Full score
24 + %th Tags
24 25 %th
25 26 Submit
26 27 %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Admin can always submit to any problem' } [?]
27 28 %th Date added
28 29 %th.text-center
29 30 Avail?
30 31 %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Let user submits to this problem?' } [?]
31 32 %th.text-center
32 33 View Data?
33 34 %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Let user view the testcase of this problem?' } [?]
34 35 %th.text-center
35 36 Test?
36 37 %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Let user uses test interface on this problem?' } [?]
37 38 - if GraderConfiguration.multicontests?
38 39 %th Contests
39 40 - for problem in @problems
40 41 %tr{:class => "#{(problem.available) ? "success" : "danger"}", :id => "prob-#{problem.id}", :name => "prob-#{problem.id}"}
41 42 - @problem=problem
42 43 %td= problem.name #in_place_editor_field :problem, :name, {}, :rows=>1
43 44 %td
44 45 = problem.full_name #in_place_editor_field :problem, :full_name, {}, :rows=>1
45 46 = link_to_description_if_any "[#{t 'main.problem_desc'}] <span class='glyphicon glyphicon-file'></span>".html_safe, problem
46 47 %td.text-right= problem.full_score #in_place_editor_field :problem, :full_score, {}, :rows=>1
48 + %td
49 + - problem.tags.each do |t|
50 + - #%button.btn.btn-default.btn-xs= t.name
51 + %span.label.label-default= t.name
47 52 %td= link_to "Submit", direct_edit_problem_submissions_path(problem,@current_user.id), class: 'btn btn-xs btn-primary'
48 53 %td= problem.date_added
49 54 %td= toggle_button(@problem.available?, toggle_problem_path(@problem), "problem-avail-#{@problem.id}")
50 55 %td= toggle_button(@problem.view_testcase?, toggle_view_testcase_problem_path(@problem), "problem-view-testcase-#{@problem.id}")
51 56 %td= toggle_button(@problem.test_allowed?, toggle_test_problem_path(@problem), "problem-test-#{@problem.id}")
52 57 - if GraderConfiguration.multicontests?
53 58 %td
54 59 = problem.contests.collect { |c| c.name }.join(', ')
55 60 %td= link_to 'Stat', {:action => 'stat', :id => problem.id}, class: 'btn btn-info btn-xs btn-block'
56 61 %td= link_to 'Show', {:action => 'show', :id => problem}, class: 'btn btn-info btn-xs btn-block'
57 62 %td= link_to 'Edit', {:action => 'edit', :id => problem}, class: 'btn btn-info btn-xs btn-block'
58 63 %td= link_to 'Destroy', { :action => 'destroy', :id => problem }, :confirm => 'Are you sure?', :method => :delete, class: 'btn btn-danger btn-xs btn-block'
59 64 %br/
60 65 = link_to '[New problem]', :action => 'new'
@@ -28,80 +28,91
28 28 }
29 29 start = orig_stop
30 30 } else {
31 31 start = parseInt($(this).attr('id').split('-')[2]);
32 32 }
33 33 });
34 34 });
35 35
36 36
37 37 %h1 Manage problems
38 38
39 39 %p= link_to '[Back to problem list]', problems_path
40 40
41 41 = form_tag :action=>'do_manage' do
42 42 .panel.panel-primary
43 43 .panel-heading
44 44 Action
45 45 .panel-body
46 46 .submit-box
47 47 What do you want to do to the selected problem?
48 48 %br/
49 49 (You can shift-click to select a range of problems)
50 50 %ul.form-inline
51 51 %li
52 - Change date added to
52 + Change "Date added" to
53 53 .input-group.date
54 54 = text_field_tag :date_added, class: 'form-control'
55 55 %span.input-group-addon
56 56 %span.glyphicon.glyphicon-calendar
57 57 -# = select_date Date.current, :prefix => 'date_added'
58 58 &nbsp;&nbsp;&nbsp;
59 59 = submit_tag 'Change', :name => 'change_date_added', class: 'btn btn-primary btn-sm'
60 60 %li
61 - Set available to
61 + Set "Available" to
62 62 = submit_tag 'True', :name => 'enable_problem', class: 'btn btn-primary btn-sm'
63 63 = submit_tag 'False', :name => 'disable_problem', class: 'btn btn-primary btn-sm'
64 64
65 65 - if GraderConfiguration.multicontests?
66 66 %li
67 - Add to
67 + Add selected problems to contest
68 68 = select("contest","id",Contest.all.collect {|c| [c.title, c.id]})
69 69 = submit_tag 'Add', :name => 'add_to_contest', class: 'btn btn-primary btn-sm'
70 70 %li
71 - Add problems to group
71 + Add selected problems to user group
72 72 = select_tag "group_id", options_from_collection_for_select( Group.all, 'id','name',params[:group_name]), id: 'group_name',class: 'select2'
73 - = submit_tag 'Add', name: 'add_group', class: 'btn btn-default'
73 + = submit_tag 'Add', name: 'add_group', class: 'btn btn-primary'
74 + %li
75 + Add the following tags to the selected problems
76 + = select_tag "tag_ids", options_from_collection_for_select( Tag.all, 'id','name'), id: 'tags_name',class: 'select2', multiple: true, data: {placeholder: 'Select tags by clicking', width: "200px"}
77 + = submit_tag 'Add', name: 'add_tags', class: 'btn btn-primary'
74 78
75 -
76 - %table.table.table-hover
79 + %table.table.table-hover.datatable
80 + %thead
77 81 %tr{style: "text-align: left;"}
78 82 %th= check_box_tag 'select_all'
79 83 %th Name
80 84 %th Full name
85 + %th Tags
81 86 %th Available
82 87 %th Date added
83 88 - if GraderConfiguration.multicontests?
84 89 %th Contests
85 90
91 + %tbody
86 92 - num = 0
87 93 - for problem in @problems
88 94 - num += 1
89 95 %tr{:id => "row-prob-#{problem.id}", :name=> "prob-#{problem.id}"}
90 96 %td= check_box_tag "prob-#{problem.id}-#{num}"
91 97 %td= problem.name
92 98 %td= problem.full_name
99 + %td
100 + - problem.tags.each do |t|
101 + %span.label.label-default= t.name
93 102 %td= problem.available
94 103 %td= problem.date_added
95 104 - if GraderConfiguration.multicontests?
96 105 %td
97 106 - problem.contests.each do |contest|
98 107 = "(#{contest.name} [#{link_to 'x', :action => 'remove_contest', :id => problem.id, :contest_id => contest.id }])"
99 108
100 109 :javascript
101 110 $('.input-group.date').datetimepicker({
102 111 format: 'DD/MMM/YYYY',
103 112 showTodayButton: true,
104 113 widgetPositioning: {horizontal: 'auto', vertical: 'bottom'},
105 114
106 115 });
107 -
116 + $('.datatable').DataTable({
117 + paging: false
118 + });
@@ -11,47 +11,49
11 11 %thead
12 12 %tr.info-head
13 13 %th Stat
14 14 %th Value
15 15 %tbody
16 16 %tr{class: cycle('info-even','info-odd')}
17 17 %td Submissions
18 18 %td= @submissions.count
19 19 %tr{class: cycle('info-even','info-odd')}
20 20 %td Solved/Attempted User
21 21 %td #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%)
22 22
23 23 %h2 Submissions Count
24 24 = render partial: 'application/bar_graph', locals: { histogram: @histogram }
25 25
26 26 %h2 Submissions
27 27 - if @submissions and @submissions.count > 0
28 28 %table#main_table.table.table-condensed.table-striped
29 29 %thead
30 30 %tr
31 31 %th ID
32 32 %th Login
33 33 %th Name
34 34 %th Submitted_at
35 + %th language
35 36 %th Points
36 37 %th comment
37 38 %th IP
38 39 %tbody
39 40 - row_odd,curr = true,''
40 41 - @submissions.each do |sub|
41 42 - next unless sub.user
42 43 - row_odd,curr = !row_odd, sub.user if curr != sub.user
43 44 %tr
44 45 %td= link_to sub.id, submission_path(sub)
45 46 %td= link_to sub.user.login, stat_user_path(sub.user)
46 47 %td= sub.user.full_name
47 48 %td{data: {order: sub.submitted_at}}= time_ago_in_words(sub.submitted_at) + " ago"
49 + %td= sub.language.name
48 50 %td= sub.points
49 51 %td.fix-width= sub.grader_comment
50 52 %td= sub.ip_address
51 53 - else
52 54 No submission
53 55
54 56 :javascript
55 57 $("#main_table").DataTable({
56 58 paging: false
57 59 });
@@ -1,34 +1,69
1 1 %table.table.sortable.table-striped.table-bordered.table-condensed
2 2 %thead
3 3 %tr
4 4 %th Login
5 5 %th Name
6 6 / %th Activated?
7 7 / %th Logged_in
8 8 / %th Contest(s)
9 9 %th Remark
10 10 - @problems.each do |p|
11 11 %th.text-right= p.name.gsub('_',' ')
12 12 %th.text-right Total
13 13 %th.text-right Passed
14 14 %tbody
15 + - sum = Array.new(@scorearray[0].count,0)
16 + - nonzero = Array.new(@scorearray[0].count,0)
17 + - full = Array.new(@scorearray[0].count,0)
15 18 - @scorearray.each do |sc|
16 19 %tr
17 20 - total,num_passed = 0,0
18 21 - sc.each_index do |i|
19 22 - if i == 0
20 23 %td= link_to sc[i].login, stat_user_path(sc[i])
21 24 %td= sc[i].full_name
22 25 / %td= sc[i].activated
23 26 / %td= sc[i].try(:contest_stat).try(:started_at) ? 'yes' : 'no'
24 27 / %td= sc[i].contests.collect {|c| c.name}.join(', ')
25 28 %td= sc[i].remark
26 29 - else
27 30 %td.text-right= sc[i][0]
28 31 - total += sc[i][0]
29 32 - num_passed += 1 if sc[i][1]
33 + - sum[i] += sc[i][0]
34 + - nonzero[i] += 1 if sc[i][0] > 0
35 + - full[i] += 1 if sc[i][1]
30 36 %td.text-right= total
31 37 %td.text-right= num_passed
38 + %tfoot
39 + %tr
40 + %td Summation
41 + %td
42 + %td
43 + - sum.each.with_index do |s,i|
44 + - next if i == 0
45 + %td.text-right= number_with_delimiter(s)
46 + %td
47 + %td
48 + %tr
49 + %td partial solver
50 + %td
51 + %td
52 + - nonzero.each.with_index do |s,i|
53 + - next if i == 0
54 + %td.text-right= number_with_delimiter(s)
55 + %td
56 + %td
57 + %tr
58 + %td Full solver
59 + %td
60 + %td
61 + - full.each.with_index do |s,i|
62 + - next if i == 0
63 + %td.text-right= number_with_delimiter(s)
64 + %td
65 + %td
66 +
32 67
33 68 :javascript
34 69 $.bootstrapSortable(true,'reversed')
@@ -12,38 +12,38
12 12 = label_tag :problem_id, "Problems"
13 13 = select_tag 'problem_id[]',
14 14 options_for_select(Problem.all.collect {|p| ["[#{p.name}] #{p.full_name}", p.id]},params[:problem_id]),
15 15 { class: 'select2 form-control', multiple: "true" }
16 16 .col-md-4
17 17 .panel.panel-primary
18 18 .panel-heading
19 19 Submission range
20 20 .panel-body
21 21 %p
22 22 Input minimum and maximum range of submission ID that should be included. A blank value for min and max means -1 and infinity, respectively.
23 23 .form-group
24 24 = label_tag :from, "Min"
25 25 = text_field_tag 'from_id', @since_id, class: "form-control"
26 26 .form-group
27 27 = label_tag :from, "Max"
28 28 = text_field_tag 'to_id', @until_id, class: "form-control"
29 29 .col-md-4
30 30 .panel.panel-primary
31 31 .panel-heading
32 32 Users
33 33 .panel-body
34 34 .radio
35 35 %label
36 - = radio_button_tag 'users', 'all', true
36 + = radio_button_tag 'users', 'all', (params[:users] == "all")
37 37 All users
38 38 .radio
39 39 %label
40 - = radio_button_tag 'users', 'enabled'
40 + = radio_button_tag 'users', 'enabled', (params[:users] == "enabled")
41 41 Only enabled users
42 42 .row
43 43 .col-md-12
44 44 = button_tag 'Show', class: "btn btn-primary btn-large", value: "show"
45 45 = button_tag 'Download CSV', class: "btn btn-primary btn-large", value: "download"
46 46
47 47 - if @scorearray
48 48 %h2 Result
49 49 =render "score_table"
@@ -10,26 +10,27
10 10 = f.label :password, "Password"
11 11 = f.text_field :password, class: 'form-control'
12 12 .form-group.field
13 13 = f.label :started, "Started"
14 14 = f.check_box :started, class: 'form-control'
15 15 .form-group.field
16 16 = f.label :start_time, "Start time"
17 17 -# = f.datetime_select :start_time, :include_blank => true
18 18 .input-group.date
19 19 = f.text_field :start_time, class:'form-control' , value: (@site.start_time ? @site.start_time.strftime('%d/%b/%Y %H:%M') : '')
20 20 %span.input-group-addon
21 21 %span.glyphicon.glyphicon-calendar
22 22 .actions
23 23 = f.submit "Update", class: 'btn btn-primary'
24 24 .col-md-8
25 25
26 26 = link_to 'Show', @site
27 27 |
28 28 = link_to 'Back', sites_path
29 29
30 30
31 31 :javascript
32 32 $('.input-group.date').datetimepicker({
33 33 format: 'DD/MMM/YYYY HH:mm',
34 + showTodayButton: true,
34 35 });
35 36
@@ -6,53 +6,54
6 6 .row
7 7 .col-md-12
8 8 .alert.alert-info
9 9 Write your code in the following box, choose language, and click submit button when finished
10 10 .row
11 11 .col-md-8
12 12 %div#editor{style: 'height: 500px; border-radius: 7px; font-size: 14px;'}
13 13 .col-md-4
14 14 - # submission form
15 15 = form_tag({controller: :main, :action => 'submit'}, :multipart => true, class: 'form') do
16 16
17 17 = hidden_field_tag 'editor_text', @source
18 18 = hidden_field_tag 'submission[problem_id]', @problem.id
19 19 .form-group
20 20 = label_tag "Task:"
21 21 = text_field_tag 'asdf', "#{@problem.long_name}", class: 'form-control', disabled: true
22 22
23 23 .form-group
24 24 = label_tag 'Language'
25 25 = select_tag 'language_id', options_from_collection_for_select(Language.all, 'id', 'pretty_name', @lang_id || Language.find_by_pretty_name("Python").id || Language.first.id), class: 'form-control select', style: "width: 100px"
26 26 .form-group
27 27 = submit_tag 'Submit', class: 'btn btn-success', id: 'live_submit',
28 28 data: {confirm: "Submitting this source code for task #{@problem.long_name}?"}
29 29 - # latest submission status
30 - .panel.panel-info
30 + .panel{class: (@submission && @submission.graded_at) ? "panel-info" : "panel-warning"}
31 31 .panel-heading
32 32 Latest Submission Status
33 33 = link_to "Refresh",get_latest_submission_status_submissions_path(@submission.user,@problem), class: "btn btn-default btn-sm", remote: true if @submission
34 34 .panel-body
35 + %div#latest_status
35 36 - if @submission
36 37 = render :partial => 'submission_short',
37 38 :locals => {submission: @submission, problem_name: @problem.name, problem_id: @problem.id }
38 39 .row
39 40 .col-md-12
40 41 %h2 Console
41 42 %textarea#console{style: 'height: 100%; width: 100%;background-color:#000;color:#fff;font-family: consolas, monaco, "Droid Sans Mono";',rows: 20}
42 43
43 44 :javascript
44 45 $(document).ready(function() {
45 46 e = ace.edit("editor")
46 47 e.setValue($("#text_sourcecode").val());
47 48 e.gotoLine(1);
48 49 $("#language_id").trigger('change');
49 50 brython();
50 51 });
51 52
52 53
53 54 %script#__main__{type:'text/python3'}
54 55 :plain
55 56 import sys
56 57 import traceback
57 58
58 59 from browser import document as doc
@@ -1,2 +1,2
1 1 :plain
2 - $("#latest_status").html("#{j render({partial: 'submission_short', locals: {submission: @submission, problem_name: @problem.name}})}")
2 + $("#latest_status").html("#{j render({partial: 'submission_short', locals: {submission: @submission, problem_name: @problem.name, problem_id: @problem.id}})}")
@@ -44,29 +44,29
44 44
45 45 # Use SQL instead of Active Record's schema dumper when creating the database.
46 46 # This is necessary if your schema can't be completely dumped by the schema dumper,
47 47 # like if you have constraints or database-specific column types
48 48 # config.active_record.schema_format = :sql
49 49
50 50 # Enable the asset pipeline
51 51 config.assets.enabled = true
52 52
53 53 # Version of your assets, change this if you want to expire all your assets
54 54 config.assets.version = '1.0'
55 55
56 56 # ---------------- IMPORTANT ----------------------
57 57 # If we deploy the app into a subdir name "grader", be sure to do "rake assets:precompile RAILS_RELATIVE_URL_ROOT=/grader"
58 58 # moreover, using the following line instead also known to works
59 59 #config.action_controller.relative_url_root = '/grader'
60 60
61 61 #font path
62 62 config.assets.paths << "#{Rails}/vendor/assets/fonts"
63 63
64 64 config.assets.precompile += ['announcement_refresh.js','effects.js','site_update.js']
65 65 config.assets.precompile += ['local_jquery.js','tablesorter-theme.cafe.css']
66 66 %w( announcements submissions configurations contests contest_management graders heartbeat
67 67 login main messages problems report site sites sources tasks
68 - test user_admin users ).each do |controller|
68 + test user_admin users testcases).each do |controller|
69 69 config.assets.precompile += ["#{controller}.js", "#{controller}.css"]
70 70 end
71 71 end
72 72 end
@@ -1,23 +1,23
1 1 # Be sure to restart your server when you modify this file.
2 2
3 3 # Version of your assets, change this if you want to expire all your assets.
4 4 Rails.application.config.assets.version = '1.0'
5 5
6 6 # Add additional assets to the asset load path.
7 7 # Rails.application.config.assets.paths << Emoji.images_path
8 8 # Add Yarn node_modules folder to the asset load path.
9 9 Rails.application.config.assets.paths << Rails.root.join('node_modules')
10 10 Rails.application.config.assets.paths << Rails.root.join('vendor/assets/fonts')
11 11
12 12 # Precompile additional assets.
13 13 # application.js, application.css, and all non-JS/CSS in the app/assets
14 14 # folder are already added.
15 15 # Rails.application.config.assets.precompile += %w( admin.js admin.css )
16 16
17 17 Rails.application.config.assets.precompile += ['announcement_refresh.js','effects.js','site_update.js']
18 18 Rails.application.config.assets.precompile += ['local_jquery.js','tablesorter-theme.cafe.css']
19 19 %w( announcements submissions configurations contests contest_management graders heartbeat
20 20 login main messages problems report site sites sources tasks groups
21 - test user_admin users tags).each do |controller|
21 + test user_admin users tags testcases).each do |controller|
22 22 Rails.application.config.assets.precompile += ["#{controller}.js", "#{controller}.css"]
23 23 end
@@ -12,48 +12,49
12 12 File.join GRADER_ROOT_DIR, "raw"
13 13 end
14 14
15 15 def self.call_grader(params)
16 16 if GraderScript.grader_control_enabled?
17 17 cmd = File.join(GRADER_ROOT_DIR, "scripts/grader") + " " + params
18 18 system(cmd)
19 19 end
20 20 end
21 21
22 22 def self.stop_grader(pid)
23 23 GraderScript.call_grader "stop #{pid}"
24 24 end
25 25
26 26 def self.stop_graders(pids)
27 27 pid_str = (pids.map { |process| process.pid.to_s }).join ' '
28 28 GraderScript.call_grader "stop #{pid_str}"
29 29 end
30 30
31 31 def self.start_grader(env)
32 32 GraderScript.call_grader "#{env} queue --err-log &"
33 33 GraderScript.call_grader "#{env} test_request -err-log &"
34 34 end
35 35
36 + #call the import problem script
36 37 def self.call_import_problem(problem_name,
37 38 problem_dir,
38 39 time_limit=1,
39 40 memory_limit=32,
40 41 checker_name='text')
41 42 if GraderScript.grader_control_enabled?
42 43 cur_dir = `pwd`.chomp
43 44 Dir.chdir(GRADER_ROOT_DIR)
44 45
45 46 script_name = File.join(GRADER_ROOT_DIR, "scripts/import_problem")
46 47 cmd = "#{script_name} #{problem_name} #{problem_dir} #{checker_name}" +
47 48 " -t #{time_limit} -m #{memory_limit}"
48 49
49 50 output = `#{cmd}`
50 51
51 52 Dir.chdir(cur_dir)
52 53
53 54 return "import CMD: #{cmd}\n" + output
54 55 end
55 56 return ''
56 57 end
57 58
58 59 def self.call_import_testcase(problem_name)
59 60 if GraderScript.grader_control_enabled?
@@ -1,78 +1,80
1 1 require 'tmpdir'
2 2
3 3 class TestdataImporter
4 4
5 5 attr :log_msg
6 6
7 7 def initialize(problem)
8 8 @problem = problem
9 9 end
10 10
11 + #Create or update problem according to the parameter
11 12 def import_from_file(tempfile,
12 13 time_limit,
13 14 memory_limit,
14 15 checker_name='text',
15 16 import_to_db=false)
16 17
17 18 dirname = extract(tempfile)
18 19 return false if not dirname
19 20 if not import_to_db
20 21 @log_msg = GraderScript.call_import_problem(@problem.name,
21 22 dirname,
22 23 time_limit,
23 24 memory_limit,
24 25 checker_name)
25 26 else
26 27 # Import test data to test pairs.
27 28
28 29 @problem.test_pairs.clear
29 30 if import_test_pairs(dirname)
30 31 test_pair_count = TestPair.count :conditions => "problem_id = #{@problem.id}"
31 32 @log_msg = "Importing test pair successful. (#{test_pair_count} test pairs imported)"
32 33 else
33 34 @log_msg = "Importing test pair failed. (0 test pairs imported)"
34 35 end
35 36 end
36 37
37 38 @log_msg << import_problem_description(dirname)
38 39 @log_msg << import_problem_pdf(dirname)
39 40 @log_msg << import_full_score(dirname)
40 41
41 42 #import test data
42 43 @log_msg << GraderScript.call_import_testcase(@problem.name)
43 44
44 45 return true
45 46 end
46 47
47 48 protected
48 49
49 50 def self.long_ext(filename)
50 51 i = filename.index('.')
51 52 len = filename.length
52 53 return filename.slice(i..len)
53 54 end
54 55
56 + # extract an archive file located at +tempfile+ to the +raw_dir+
55 57 def extract(tempfile)
56 58 testdata_filename = save_testdata_file(tempfile)
57 59 ext = TestdataImporter.long_ext(tempfile.original_filename)
58 60
59 61 extract_dir = File.join(GraderScript.raw_dir, @problem.name)
60 62 if File.exists? extract_dir
61 63 backup_count = 0
62 64 begin
63 65 backup_count += 1
64 66 backup_dirname = "#{extract_dir}.backup.#{backup_count}"
65 67 end while File.exists? backup_dirname
66 68 File.rename(extract_dir, backup_dirname)
67 69 end
68 70 Dir.mkdir extract_dir
69 71
70 72 if ext=='.tar.gz' or ext=='.tgz'
71 73 cmd = "tar -zxvf #{testdata_filename} -C #{extract_dir}"
72 74 elsif ext=='.tar'
73 75 cmd = "tar -xvf #{testdata_filename} -C #{extract_dir}"
74 76 elsif ext=='.zip'
75 77 cmd = "unzip -o #{testdata_filename} -d #{extract_dir}"
76 78 else
77 79 return nil
78 80 end
You need to be logged in to leave comments. Login now