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

r880:31e27f513ce9 - - 9 files changed: 173 inserted, 142 deleted

@@ -55,192 +55,195
55 @problem.full_score = 100
55 @problem.full_score = 100
56 @problem.available = false
56 @problem.available = false
57 @problem.test_allowed = true
57 @problem.test_allowed = true
58 @problem.output_only = false
58 @problem.output_only = false
59 @problem.date_added = Time.new
59 @problem.date_added = Time.new
60 if @problem.save
60 if @problem.save
61 flash[:notice] = 'Problem was successfully created.'
61 flash[:notice] = 'Problem was successfully created.'
62 redirect_to action: :index
62 redirect_to action: :index
63 else
63 else
64 flash[:notice] = 'Error saving problem'
64 flash[:notice] = 'Error saving problem'
65 redirect_to action: :index
65 redirect_to action: :index
66 end
66 end
67 end
67 end
68
68
69 def edit
69 def edit
70 @description = @problem.description
70 @description = @problem.description
71 end
71 end
72
72
73 def update
73 def update
74 if problem_params[:statement] && problem_params[:statement].content_type != 'application/pdf'
74 if problem_params[:statement] && problem_params[:statement].content_type != 'application/pdf'
75 flash[:error] = 'Error: Uploaded file is not PDF'
75 flash[:error] = 'Error: Uploaded file is not PDF'
76 render :action => 'edit'
76 render :action => 'edit'
77 return
77 return
78 end
78 end
79 if @problem.update(problem_params)
79 if @problem.update(problem_params)
80 flash[:notice] = 'Problem was successfully updated. '
80 flash[:notice] = 'Problem was successfully updated. '
81 flash[:notice] += 'A new statement PDF is uploaded' if problem_params[:statement]
81 flash[:notice] += 'A new statement PDF is uploaded' if problem_params[:statement]
82 @problem.save
82 @problem.save
83 redirect_to edit_problem_path(@problem)
83 redirect_to edit_problem_path(@problem)
84 else
84 else
85 render :action => 'edit'
85 render :action => 'edit'
86 end
86 end
87 end
87 end
88
88
89 def destroy
89 def destroy
90 @problem.destroy
90 @problem.destroy
91 redirect_to action: :index
91 redirect_to action: :index
92 end
92 end
93
93
94 def toggle
94 def toggle
95 @problem.update(available: !(@problem.available) )
95 @problem.update(available: !(@problem.available) )
96 respond_to do |format|
96 respond_to do |format|
97 format.js { }
97 format.js { }
98 end
98 end
99 end
99 end
100
100
101 def toggle_test
101 def toggle_test
102 @problem.update(test_allowed: !(@problem.test_allowed?) )
102 @problem.update(test_allowed: !(@problem.test_allowed?) )
103 respond_to do |format|
103 respond_to do |format|
104 format.js { }
104 format.js { }
105 end
105 end
106 end
106 end
107
107
108 def toggle_view_testcase
108 def toggle_view_testcase
109 @problem.update(view_testcase: !(@problem.view_testcase?) )
109 @problem.update(view_testcase: !(@problem.view_testcase?) )
110 respond_to do |format|
110 respond_to do |format|
111 format.js { }
111 format.js { }
112 end
112 end
113 end
113 end
114
114
115 def turn_all_off
115 def turn_all_off
116 Problem.available.all.each do |problem|
116 Problem.available.all.each do |problem|
117 problem.available = false
117 problem.available = false
118 problem.save
118 problem.save
119 end
119 end
120 redirect_to action: :index
120 redirect_to action: :index
121 end
121 end
122
122
123 def turn_all_on
123 def turn_all_on
124 Problem.where.not(available: true).each do |problem|
124 Problem.where.not(available: true).each do |problem|
125 problem.available = true
125 problem.available = true
126 problem.save
126 problem.save
127 end
127 end
128 redirect_to action: :index
128 redirect_to action: :index
129 end
129 end
130
130
131 def stat
131 def stat
132 unless @problem.available or session[:admin]
132 unless @problem.available or session[:admin]
133 redirect_to :controller => 'main', :action => 'list'
133 redirect_to :controller => 'main', :action => 'list'
134 return
134 return
135 end
135 end
136 @submissions = Submission.includes(:user).includes(:language).where(problem_id: params[:id]).order(:user_id,:id)
136 @submissions = Submission.includes(:user).includes(:language).where(problem_id: params[:id]).order(:user_id,:id)
137
137
138 #stat summary
138 #stat summary
139 range =65
139 range =65
140 @histogram = { data: Array.new(range,0), summary: {} }
140 @histogram = { data: Array.new(range,0), summary: {} }
141 user = Hash.new(0)
141 user = Hash.new(0)
142 @submissions.find_each do |sub|
142 @submissions.find_each do |sub|
143 d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
143 d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
144 @histogram[:data][d.to_i] += 1 if d < range
144 @histogram[:data][d.to_i] += 1 if d < range
145 user[sub.user_id] = [user[sub.user_id], ((sub.try(:points) || 0) >= @problem.full_score) ? 1 : 0].max
145 user[sub.user_id] = [user[sub.user_id], ((sub.try(:points) || 0) >= @problem.full_score) ? 1 : 0].max
146 end
146 end
147 @histogram[:summary][:max] = [@histogram[:data].max,1].max
147 @histogram[:summary][:max] = [@histogram[:data].max,1].max
148
148
149 @summary = { attempt: user.count, solve: 0 }
149 @summary = { attempt: user.count, solve: 0 }
150 user.each_value { |v| @summary[:solve] += 1 if v == 1 }
150 user.each_value { |v| @summary[:solve] += 1 if v == 1 }
151 +
152 + #for new graph
153 + @chart_dataset = @problem.get_jschart_history.to_json.html_safe
151 end
154 end
152
155
153 def manage
156 def manage
154 @problems = Problem.order(date_added: :desc)
157 @problems = Problem.order(date_added: :desc)
155 end
158 end
156
159
157 def do_manage
160 def do_manage
158 if params.has_key? 'change_date_added' and params[:date_added].strip.empty? == false
161 if params.has_key? 'change_date_added' and params[:date_added].strip.empty? == false
159 change_date_added
162 change_date_added
160 elsif params.has_key? 'add_to_contest'
163 elsif params.has_key? 'add_to_contest'
161 add_to_contest
164 add_to_contest
162 elsif params.has_key? 'enable_problem'
165 elsif params.has_key? 'enable_problem'
163 set_available(true)
166 set_available(true)
164 elsif params.has_key? 'disable_problem'
167 elsif params.has_key? 'disable_problem'
165 set_available(false)
168 set_available(false)
166 elsif params.has_key? 'add_group'
169 elsif params.has_key? 'add_group'
167 group = Group.find(params[:group_id])
170 group = Group.find(params[:group_id])
168 ok = []
171 ok = []
169 failed = []
172 failed = []
170 get_problems_from_params.each do |p|
173 get_problems_from_params.each do |p|
171 begin
174 begin
172 group.problems << p
175 group.problems << p
173 ok << p.full_name
176 ok << p.full_name
174 rescue => e
177 rescue => e
175 failed << p.full_name
178 failed << p.full_name
176 end
179 end
177 end
180 end
178 flash[:success] = "The following problems are added to the group #{group.name}: " + ok.join(', ') if ok.count > 0
181 flash[:success] = "The following problems are added to the group #{group.name}: " + ok.join(', ') if ok.count > 0
179 flash[:alert] = "The following problems are already in the group #{group.name}: " + failed.join(', ') if failed.count > 0
182 flash[:alert] = "The following problems are already in the group #{group.name}: " + failed.join(', ') if failed.count > 0
180 elsif params.has_key? 'add_tags'
183 elsif params.has_key? 'add_tags'
181 get_problems_from_params.each do |p|
184 get_problems_from_params.each do |p|
182 p.tag_ids += params[:tag_ids]
185 p.tag_ids += params[:tag_ids]
183 end
186 end
184 end
187 end
185
188
186 redirect_to :action => 'manage'
189 redirect_to :action => 'manage'
187 end
190 end
188
191
189 def import
192 def import
190 @allow_test_pair_import = allow_test_pair_import?
193 @allow_test_pair_import = allow_test_pair_import?
191 end
194 end
192
195
193 def do_import
196 def do_import
194 old_problem = Problem.find_by_name(params[:name])
197 old_problem = Problem.find_by_name(params[:name])
195 if !allow_test_pair_import? and params.has_key? :import_to_db
198 if !allow_test_pair_import? and params.has_key? :import_to_db
196 params.delete :import_to_db
199 params.delete :import_to_db
197 end
200 end
198 @problem, import_log = Problem.create_from_import_form_params(params,
201 @problem, import_log = Problem.create_from_import_form_params(params,
199 old_problem)
202 old_problem)
200
203
201 if !@problem.errors.empty?
204 if !@problem.errors.empty?
202 render :action => 'import' and return
205 render :action => 'import' and return
203 end
206 end
204
207
205 if old_problem!=nil
208 if old_problem!=nil
206 flash[:notice] = "The test data has been replaced for problem #{@problem.name}"
209 flash[:notice] = "The test data has been replaced for problem #{@problem.name}"
207 end
210 end
208 @log = import_log
211 @log = import_log
209 end
212 end
210
213
211 def remove_contest
214 def remove_contest
212 problem = Problem.find(params[:id])
215 problem = Problem.find(params[:id])
213 contest = Contest.find(params[:contest_id])
216 contest = Contest.find(params[:contest_id])
214 if problem!=nil and contest!=nil
217 if problem!=nil and contest!=nil
215 problem.contests.delete(contest)
218 problem.contests.delete(contest)
216 end
219 end
217 redirect_to :action => 'manage'
220 redirect_to :action => 'manage'
218 end
221 end
219
222
220 ##################################
223 ##################################
221 protected
224 protected
222
225
223 def allow_test_pair_import?
226 def allow_test_pair_import?
224 if defined? ALLOW_TEST_PAIR_IMPORT
227 if defined? ALLOW_TEST_PAIR_IMPORT
225 return ALLOW_TEST_PAIR_IMPORT
228 return ALLOW_TEST_PAIR_IMPORT
226 else
229 else
227 return false
230 return false
228 end
231 end
229 end
232 end
230
233
231 def change_date_added
234 def change_date_added
232 problems = get_problems_from_params
235 problems = get_problems_from_params
233 date = Date.parse(params[:date_added])
236 date = Date.parse(params[:date_added])
234 problems.each do |p|
237 problems.each do |p|
235 p.date_added = date
238 p.date_added = date
236 p.save
239 p.save
237 end
240 end
238 end
241 end
239
242
240 def add_to_contest
243 def add_to_contest
241 problems = get_problems_from_params
244 problems = get_problems_from_params
242 contest = Contest.find(params[:contest][:id])
245 contest = Contest.find(params[:contest][:id])
243 if contest!=nil and contest.enabled
246 if contest!=nil and contest.enabled
244 problems.each do |p|
247 problems.each do |p|
245 p.contests << contest
248 p.contests << contest
246 end
249 end
@@ -155,202 +155,203
155 ip: x
155 ip: x
156 }
156 }
157 end
157 end
158 end
158 end
159
159
160 def login_detail_query
160 def login_detail_query
161 @logins = Array.new
161 @logins = Array.new
162
162
163 date_and_time = '%Y-%m-%d %H:%M'
163 date_and_time = '%Y-%m-%d %H:%M'
164 begin
164 begin
165 md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
165 md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
166 @since_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
166 @since_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
167 rescue
167 rescue
168 @since_time = Time.zone.now
168 @since_time = Time.zone.now
169 end
169 end
170 begin
170 begin
171 md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
171 md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
172 @until_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
172 @until_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
173 rescue
173 rescue
174 @until_time = DateTime.new(3000,1,1)
174 @until_time = DateTime.new(3000,1,1)
175 end
175 end
176
176
177 @logins = Login.includes(:user).where("logins.created_at >= ? AND logins.created_at <= ?",@since_time, @until_time)
177 @logins = Login.includes(:user).where("logins.created_at >= ? AND logins.created_at <= ?",@since_time, @until_time)
178 case params[:users]
178 case params[:users]
179 when 'enabled'
179 when 'enabled'
180 @logins = @logins.where(users: {enabled: true})
180 @logins = @logins.where(users: {enabled: true})
181 when 'group'
181 when 'group'
182 @logins = @logins.joins(user: :groups).where(user: {groups: {id: params[:groups]}}) if params[:groups]
182 @logins = @logins.joins(user: :groups).where(user: {groups: {id: params[:groups]}}) if params[:groups]
183 end
183 end
184 end
184 end
185
185
186 def submission
186 def submission
187 end
187 end
188
188
189 def submission_query
189 def submission_query
190 @submissions = Submission
190 @submissions = Submission
191 .includes(:problem).includes(:user).includes(:language)
191 .includes(:problem).includes(:user).includes(:language)
192
192
193 case params[:users]
193 case params[:users]
194 when 'enabled'
194 when 'enabled'
195 @submissions = @submissions.where(users: {enabled: true})
195 @submissions = @submissions.where(users: {enabled: true})
196 when 'group'
196 when 'group'
197 @submissions = @submissions.joins(user: :groups).where(user: {groups: {id: params[:groups]}}) if params[:groups]
197 @submissions = @submissions.joins(user: :groups).where(user: {groups: {id: params[:groups]}}) if params[:groups]
198 end
198 end
199
199
200 case params[:problems]
200 case params[:problems]
201 when 'enabled'
201 when 'enabled'
202 @submissions = @submissions.where(problems: {available: true})
202 @submissions = @submissions.where(problems: {available: true})
203 when 'selected'
203 when 'selected'
204 @submissions = @submissions.where(problem_id: params[:problem_id])
204 @submissions = @submissions.where(problem_id: params[:problem_id])
205 end
205 end
206
206
207 #set default
207 #set default
208 params[:since_datetime] = Date.today.to_s if params[:since_datetime].blank?
208 params[:since_datetime] = Date.today.to_s if params[:since_datetime].blank?
209
209
210 @submissions, @recordsTotal, @recordsFiltered = process_query_record( @submissions,
210 @submissions, @recordsTotal, @recordsFiltered = process_query_record( @submissions,
211 global_search: ['user.login','user.full_name','problem.name','problem.full_name','points'],
211 global_search: ['user.login','user.full_name','problem.name','problem.full_name','points'],
212 date_filter: 'submitted_at',
212 date_filter: 'submitted_at',
213 date_param_since: 'since_datetime',
213 date_param_since: 'since_datetime',
214 date_param_until: 'until_datetime',
214 date_param_until: 'until_datetime',
215 hard_limit: 100_000
215 hard_limit: 100_000
216 )
216 )
217 end
217 end
218
218
219 def login
219 def login
220 end
220 end
221
221
222 def problem_hof
222 def problem_hof
223 # gen problem list
223 # gen problem list
224 @user = User.find(session[:user_id])
224 @user = User.find(session[:user_id])
225 @problems = @user.available_problems
225 @problems = @user.available_problems
226
226
227 # get selected problems or the default
227 # get selected problems or the default
228 if params[:id]
228 if params[:id]
229 begin
229 begin
230 @problem = Problem.available.find(params[:id])
230 @problem = Problem.available.find(params[:id])
231 rescue
231 rescue
232 redirect_to action: :problem_hof
232 redirect_to action: :problem_hof
233 flash[:notice] = 'Error: submissions for that problem are not viewable.'
233 flash[:notice] = 'Error: submissions for that problem are not viewable.'
234 return
234 return
235 end
235 end
236 end
236 end
237
237
238 return unless @problem
238 return unless @problem
239
239
240 #model submisssion
240 #model submisssion
241 @model_subs = Submission.where(problem: @problem,tag: Submission.tags[:model])
241 @model_subs = Submission.where(problem: @problem,tag: Submission.tags[:model])
242
242
243
243
244 #calculate best submission
244 #calculate best submission
245 @by_lang = {} #aggregrate by language
245 @by_lang = {} #aggregrate by language
246
246
247 range =65
247 range =65
248 #@histogram = { data: Array.new(range,0), summary: {} }
248 #@histogram = { data: Array.new(range,0), summary: {} }
249 @summary = {count: 0, solve: 0, attempt: 0}
249 @summary = {count: 0, solve: 0, attempt: 0}
250 user = Hash.new(0)
250 user = Hash.new(0)
251 - Submission.where(problem_id: @problem.id).find_each do |sub|
251 + Submission.where(problem_id: @problem.id).includes(:language).each do |sub|
252 #histogram
252 #histogram
253 d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
253 d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
254 #@histogram[:data][d.to_i] += 1 if d < range
254 #@histogram[:data][d.to_i] += 1 if d < range
255
255
256 next unless sub.points
256 next unless sub.points
257 @summary[:count] += 1
257 @summary[:count] += 1
258 user[sub.user_id] = [user[sub.user_id], (sub.points >= @problem.full_score) ? 1 : 0].max
258 user[sub.user_id] = [user[sub.user_id], (sub.points >= @problem.full_score) ? 1 : 0].max
259
259
260 - lang = Language.find_by_id(sub.language_id)
260 + #lang = Language.find_by_id(sub.language_id)
261 + lang = sub.language
261 next unless lang
262 next unless lang
262 next unless sub.points >= @problem.full_score
263 next unless sub.points >= @problem.full_score
263
264
264 #initialize
265 #initialize
265 unless @by_lang.has_key?(lang.pretty_name)
266 unless @by_lang.has_key?(lang.pretty_name)
266 @by_lang[lang.pretty_name] = {
267 @by_lang[lang.pretty_name] = {
267 runtime: { avail: false, value: 2**30-1 },
268 runtime: { avail: false, value: 2**30-1 },
268 memory: { avail: false, value: 2**30-1 },
269 memory: { avail: false, value: 2**30-1 },
269 length: { avail: false, value: 2**30-1 },
270 length: { avail: false, value: 2**30-1 },
270 first: { avail: false, value: DateTime.new(3000,1,1) }
271 first: { avail: false, value: DateTime.new(3000,1,1) }
271 }
272 }
272 end
273 end
273
274
274 if sub.max_runtime and sub.max_runtime < @by_lang[lang.pretty_name][:runtime][:value]
275 if sub.max_runtime and sub.max_runtime < @by_lang[lang.pretty_name][:runtime][:value]
275 @by_lang[lang.pretty_name][:runtime] = { avail: true, user_id: sub.user_id, value: sub.max_runtime, sub_id: sub.id }
276 @by_lang[lang.pretty_name][:runtime] = { avail: true, user_id: sub.user_id, value: sub.max_runtime, sub_id: sub.id }
276 end
277 end
277
278
278 if sub.peak_memory and sub.peak_memory < @by_lang[lang.pretty_name][:memory][:value]
279 if sub.peak_memory and sub.peak_memory < @by_lang[lang.pretty_name][:memory][:value]
279 @by_lang[lang.pretty_name][:memory] = { avail: true, user_id: sub.user_id, value: sub.peak_memory, sub_id: sub.id }
280 @by_lang[lang.pretty_name][:memory] = { avail: true, user_id: sub.user_id, value: sub.peak_memory, sub_id: sub.id }
280 end
281 end
281
282
282 if sub.submitted_at and sub.submitted_at < @by_lang[lang.pretty_name][:first][:value] and sub.user and
283 if sub.submitted_at and sub.submitted_at < @by_lang[lang.pretty_name][:first][:value] and sub.user and
283 !sub.user.admin?
284 !sub.user.admin?
284 @by_lang[lang.pretty_name][:first] = { avail: true, user_id: sub.user_id, value: sub.submitted_at, sub_id: sub.id }
285 @by_lang[lang.pretty_name][:first] = { avail: true, user_id: sub.user_id, value: sub.submitted_at, sub_id: sub.id }
285 end
286 end
286
287
287 if @by_lang[lang.pretty_name][:length][:value] > sub.effective_code_length
288 if @by_lang[lang.pretty_name][:length][:value] > sub.effective_code_length
288 @by_lang[lang.pretty_name][:length] = { avail: true, user_id: sub.user_id, value: sub.effective_code_length, sub_id: sub.id }
289 @by_lang[lang.pretty_name][:length] = { avail: true, user_id: sub.user_id, value: sub.effective_code_length, sub_id: sub.id }
289 end
290 end
290 end
291 end
291
292
292 #process user_id
293 #process user_id
293 @by_lang.each do |lang,prop|
294 @by_lang.each do |lang,prop|
294 prop.each do |k,v|
295 prop.each do |k,v|
295 v[:user] = User.exists?(v[:user_id]) ? User.find(v[:user_id]).full_name : "(NULL)"
296 v[:user] = User.exists?(v[:user_id]) ? User.find(v[:user_id]).full_name : "(NULL)"
296 end
297 end
297 end
298 end
298
299
299 #sum into best
300 #sum into best
300 if @by_lang and @by_lang.first
301 if @by_lang and @by_lang.first
301 @best = @by_lang.first[1].clone
302 @best = @by_lang.first[1].clone
302 @by_lang.each do |lang,prop|
303 @by_lang.each do |lang,prop|
303 if @best[:runtime][:value] >= prop[:runtime][:value]
304 if @best[:runtime][:value] >= prop[:runtime][:value]
304 @best[:runtime] = prop[:runtime]
305 @best[:runtime] = prop[:runtime]
305 @best[:runtime][:lang] = lang
306 @best[:runtime][:lang] = lang
306 end
307 end
307 if @best[:memory][:value] >= prop[:memory][:value]
308 if @best[:memory][:value] >= prop[:memory][:value]
308 @best[:memory] = prop[:memory]
309 @best[:memory] = prop[:memory]
309 @best[:memory][:lang] = lang
310 @best[:memory][:lang] = lang
310 end
311 end
311 if @best[:length][:value] >= prop[:length][:value]
312 if @best[:length][:value] >= prop[:length][:value]
312 @best[:length] = prop[:length]
313 @best[:length] = prop[:length]
313 @best[:length][:lang] = lang
314 @best[:length][:lang] = lang
314 end
315 end
315 if @best[:first][:value] >= prop[:first][:value]
316 if @best[:first][:value] >= prop[:first][:value]
316 @best[:first] = prop[:first]
317 @best[:first] = prop[:first]
317 @best[:first][:lang] = lang
318 @best[:first][:lang] = lang
318 end
319 end
319 end
320 end
320 end
321 end
321
322
322 #@histogram[:summary][:max] = [@histogram[:data].max,1].max
323 #@histogram[:summary][:max] = [@histogram[:data].max,1].max
323 @summary[:attempt] = user.count
324 @summary[:attempt] = user.count
324 user.each_value { |v| @summary[:solve] += 1 if v == 1 }
325 user.each_value { |v| @summary[:solve] += 1 if v == 1 }
325
326
326
327
327 #for new graph
328 #for new graph
328 @chart_dataset = @problem.get_jschart_history.to_json.html_safe
329 @chart_dataset = @problem.get_jschart_history.to_json.html_safe
329 end
330 end
330
331
331 def stuck #report struggling user,problem
332 def stuck #report struggling user,problem
332 # init
333 # init
333 user,problem = nil
334 user,problem = nil
334 solve = true
335 solve = true
335 tries = 0
336 tries = 0
336 @struggle = Array.new
337 @struggle = Array.new
337 record = {}
338 record = {}
338 Submission.includes(:problem,:user).order(:problem_id,:user_id).find_each do |sub|
339 Submission.includes(:problem,:user).order(:problem_id,:user_id).find_each do |sub|
339 next unless sub.problem and sub.user
340 next unless sub.problem and sub.user
340 if user != sub.user_id or problem != sub.problem_id
341 if user != sub.user_id or problem != sub.problem_id
341 @struggle << { user: record[:user], problem: record[:problem], tries: tries } unless solve
342 @struggle << { user: record[:user], problem: record[:problem], tries: tries } unless solve
342 record = {user: sub.user, problem: sub.problem}
343 record = {user: sub.user, problem: sub.problem}
343 user,problem = sub.user_id, sub.problem_id
344 user,problem = sub.user_id, sub.problem_id
344 solve = false
345 solve = false
345 tries = 0
346 tries = 0
346 end
347 end
347 if sub.points >= sub.problem.full_score
348 if sub.points >= sub.problem.full_score
348 solve = true
349 solve = true
349 else
350 else
350 tries += 1
351 tries += 1
351 end
352 end
352 end
353 end
353 @struggle.sort!{|a,b| b[:tries] <=> a[:tries] }
354 @struggle.sort!{|a,b| b[:tries] <=> a[:tries] }
354 @struggle = @struggle[0..50]
355 @struggle = @struggle[0..50]
355 end
356 end
356
357
@@ -37,201 +37,195
37 @user = current_user;
37 @user = current_user;
38 end
38 end
39 end
39 end
40
40
41 def chg_passwd
41 def chg_passwd
42 user = User.find(session[:user_id])
42 user = User.find(session[:user_id])
43 user.password = params[:password]
43 user.password = params[:password]
44 user.password_confirmation = params[:password_confirmation]
44 user.password_confirmation = params[:password_confirmation]
45 if user.save
45 if user.save
46 flash[:notice] = 'password changed'
46 flash[:notice] = 'password changed'
47 else
47 else
48 flash[:notice] = 'Error: password changing failed'
48 flash[:notice] = 'Error: password changing failed'
49 end
49 end
50 redirect_to :action => 'profile'
50 redirect_to :action => 'profile'
51 end
51 end
52
52
53 def chg_default_language
53 def chg_default_language
54 user = User.find(session[:user_id])
54 user = User.find(session[:user_id])
55 user.default_language = params[:default_language]
55 user.default_language = params[:default_language]
56 if user.save
56 if user.save
57 flash[:notice] = 'default language changed'
57 flash[:notice] = 'default language changed'
58 else
58 else
59 flash[:notice] = 'Error: default language changing failed'
59 flash[:notice] = 'Error: default language changing failed'
60 end
60 end
61 redirect_to :action => 'profile'
61 redirect_to :action => 'profile'
62 end
62 end
63
63
64 def new
64 def new
65 @user = User.new
65 @user = User.new
66 render :action => 'new', :layout => 'empty'
66 render :action => 'new', :layout => 'empty'
67 end
67 end
68
68
69 def register
69 def register
70 if(params[:cancel])
70 if(params[:cancel])
71 redirect_to :controller => 'main', :action => 'login'
71 redirect_to :controller => 'main', :action => 'login'
72 return
72 return
73 end
73 end
74 @user = User.new(user_params)
74 @user = User.new(user_params)
75 @user.password_confirmation = @user.password = User.random_password
75 @user.password_confirmation = @user.password = User.random_password
76 @user.activated = false
76 @user.activated = false
77 if (@user.valid?) and (@user.save)
77 if (@user.valid?) and (@user.save)
78 if send_confirmation_email(@user)
78 if send_confirmation_email(@user)
79 render :action => 'new_splash', :layout => 'empty'
79 render :action => 'new_splash', :layout => 'empty'
80 else
80 else
81 @admin_email = GraderConfiguration['system.admin_email']
81 @admin_email = GraderConfiguration['system.admin_email']
82 render :action => 'email_error', :layout => 'empty'
82 render :action => 'email_error', :layout => 'empty'
83 end
83 end
84 else
84 else
85 @user.errors.add(:base,"Email cannot be blank") if @user.email==''
85 @user.errors.add(:base,"Email cannot be blank") if @user.email==''
86 render :action => 'new', :layout => 'empty'
86 render :action => 'new', :layout => 'empty'
87 end
87 end
88 end
88 end
89
89
90 def confirm
90 def confirm
91 login = params[:login]
91 login = params[:login]
92 key = params[:activation]
92 key = params[:activation]
93 @user = User.find_by_login(login)
93 @user = User.find_by_login(login)
94 if (@user) and (@user.verify_activation_key(key))
94 if (@user) and (@user.verify_activation_key(key))
95 if @user.valid? # check uniquenss of email
95 if @user.valid? # check uniquenss of email
96 @user.activated = true
96 @user.activated = true
97 @user.save
97 @user.save
98 @result = :successful
98 @result = :successful
99 else
99 else
100 @result = :email_used
100 @result = :email_used
101 end
101 end
102 else
102 else
103 @result = :failed
103 @result = :failed
104 end
104 end
105 render :action => 'confirm', :layout => 'empty'
105 render :action => 'confirm', :layout => 'empty'
106 end
106 end
107
107
108 def forget
108 def forget
109 render :action => 'forget', :layout => 'empty'
109 render :action => 'forget', :layout => 'empty'
110 end
110 end
111
111
112 def retrieve_password
112 def retrieve_password
113 email = params[:email]
113 email = params[:email]
114 user = User.find_by_email(email)
114 user = User.find_by_email(email)
115 if user
115 if user
116 last_updated_time = user.updated_at || user.created_at || (Time.now.gmtime - 1.hour)
116 last_updated_time = user.updated_at || user.created_at || (Time.now.gmtime - 1.hour)
117 if last_updated_time > Time.now.gmtime - 5.minutes
117 if last_updated_time > Time.now.gmtime - 5.minutes
118 flash[:notice] = 'The account has recently created or new password has recently been requested. Please wait for 5 minutes'
118 flash[:notice] = 'The account has recently created or new password has recently been requested. Please wait for 5 minutes'
119 else
119 else
120 user.password = user.password_confirmation = User.random_password
120 user.password = user.password_confirmation = User.random_password
121 user.save
121 user.save
122 send_new_password_email(user)
122 send_new_password_email(user)
123 flash[:notice] = 'New password has been mailed to you.'
123 flash[:notice] = 'New password has been mailed to you.'
124 end
124 end
125 else
125 else
126 flash[:notice] = I18n.t 'registration.password_retrieval.no_email'
126 flash[:notice] = I18n.t 'registration.password_retrieval.no_email'
127 end
127 end
128 redirect_to :action => 'forget'
128 redirect_to :action => 'forget'
129 end
129 end
130
130
131 def stat
131 def stat
132 @user = User.find(params[:id])
132 @user = User.find(params[:id])
133 - @submission = Submission.joins(:problem).where(user_id: params[:id])
133 + @submission = Submission.joins(:problem).includes(:problem).includes(:language).where(user_id: params[:id])
134 @submission = @submission.where('problems.available = true') unless current_user.admin?
134 @submission = @submission.where('problems.available = true') unless current_user.admin?
135
135
136 - range = 120
137 - @histogram = { data: Array.new(range,0), summary: {} }
138 @summary = {count: 0, solve: 0, attempt: 0}
136 @summary = {count: 0, solve: 0, attempt: 0}
139 problem = Hash.new(0)
137 problem = Hash.new(0)
140
138
141 @submission.find_each do |sub|
139 @submission.find_each do |sub|
142 - #histogram
143 - d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
144 - @histogram[:data][d.to_i] += 1 if d < range
145 -
146 @summary[:count] += 1
140 @summary[:count] += 1
147 next unless sub.problem
141 next unless sub.problem
148 problem[sub.problem] = [problem[sub.problem], ( (sub.try(:points) || 0) >= sub.problem.full_score) ? 1 : 0].max
142 problem[sub.problem] = [problem[sub.problem], ( (sub.try(:points) || 0) >= sub.problem.full_score) ? 1 : 0].max
149 end
143 end
150
144
151 - @histogram[:summary][:max] = [@histogram[:data].max,1].max
152 @summary[:attempt] = problem.count
145 @summary[:attempt] = problem.count
153 problem.each_value { |v| @summary[:solve] += 1 if v == 1 }
146 problem.each_value { |v| @summary[:solve] += 1 if v == 1 }
147 + @chart_dataset = @user.get_jschart_user_sub_history.to_json.html_safe
154 end
148 end
155
149
156 def toggle_activate
150 def toggle_activate
157 @user = User.find(params[:id])
151 @user = User.find(params[:id])
158 @user.update_attributes( activated: !@user.activated? )
152 @user.update_attributes( activated: !@user.activated? )
159 respond_to do |format|
153 respond_to do |format|
160 format.js { render partial: 'toggle_button',
154 format.js { render partial: 'toggle_button',
161 locals: {button_id: "#toggle_activate_user_#{@user.id}",button_on: @user.activated? } }
155 locals: {button_id: "#toggle_activate_user_#{@user.id}",button_on: @user.activated? } }
162 end
156 end
163 end
157 end
164
158
165 def toggle_enable
159 def toggle_enable
166 @user = User.find(params[:id])
160 @user = User.find(params[:id])
167 @user.update_attributes( enabled: !@user.enabled? )
161 @user.update_attributes( enabled: !@user.enabled? )
168 respond_to do |format|
162 respond_to do |format|
169 format.js { render partial: 'toggle_button',
163 format.js { render partial: 'toggle_button',
170 locals: {button_id: "#toggle_enable_user_#{@user.id}",button_on: @user.enabled? } }
164 locals: {button_id: "#toggle_enable_user_#{@user.id}",button_on: @user.enabled? } }
171 end
165 end
172 end
166 end
173
167
174 protected
168 protected
175
169
176 def verify_online_registration
170 def verify_online_registration
177 if !GraderConfiguration['system.online_registration']
171 if !GraderConfiguration['system.online_registration']
178 redirect_to :controller => 'main', :action => 'login'
172 redirect_to :controller => 'main', :action => 'login'
179 end
173 end
180 end
174 end
181
175
182 def send_confirmation_email(user)
176 def send_confirmation_email(user)
183 contest_name = GraderConfiguration['contest.name']
177 contest_name = GraderConfiguration['contest.name']
184 activation_url = url_for(:action => 'confirm',
178 activation_url = url_for(:action => 'confirm',
185 :login => user.login,
179 :login => user.login,
186 :activation => user.activation_key)
180 :activation => user.activation_key)
187 home_url = url_for(:controller => 'main', :action => 'index')
181 home_url = url_for(:controller => 'main', :action => 'index')
188 mail_subject = "[#{contest_name}] Confirmation"
182 mail_subject = "[#{contest_name}] Confirmation"
189 mail_body = t('registration.email_body', {
183 mail_body = t('registration.email_body', {
190 :full_name => user.full_name,
184 :full_name => user.full_name,
191 :contest_name => contest_name,
185 :contest_name => contest_name,
192 :login => user.login,
186 :login => user.login,
193 :password => user.password,
187 :password => user.password,
194 :activation_url => activation_url,
188 :activation_url => activation_url,
195 :admin_email => GraderConfiguration['system.admin_email']
189 :admin_email => GraderConfiguration['system.admin_email']
196 })
190 })
197
191
198 logger.info mail_body
192 logger.info mail_body
199
193
200 send_mail(user.email, mail_subject, mail_body)
194 send_mail(user.email, mail_subject, mail_body)
201 end
195 end
202
196
203 def send_new_password_email(user)
197 def send_new_password_email(user)
204 contest_name = GraderConfiguration['contest.name']
198 contest_name = GraderConfiguration['contest.name']
205 mail_subject = "[#{contest_name}] Password recovery"
199 mail_subject = "[#{contest_name}] Password recovery"
206 mail_body = t('registration.password_retrieval.email_body', {
200 mail_body = t('registration.password_retrieval.email_body', {
207 :full_name => user.full_name,
201 :full_name => user.full_name,
208 :contest_name => contest_name,
202 :contest_name => contest_name,
209 :login => user.login,
203 :login => user.login,
210 :password => user.password,
204 :password => user.password,
211 :admin_email => GraderConfiguration['system.admin_email']
205 :admin_email => GraderConfiguration['system.admin_email']
212 })
206 })
213
207
214 logger.info mail_body
208 logger.info mail_body
215
209
216 send_mail(user.email, mail_subject, mail_body)
210 send_mail(user.email, mail_subject, mail_body)
217 end
211 end
218
212
219 # allow viewing of regular user profile only when options allow so
213 # allow viewing of regular user profile only when options allow so
220 # only admins can view admins profile
214 # only admins can view admins profile
221 def profile_authorization
215 def profile_authorization
222 #if view admins' profile, allow only admin
216 #if view admins' profile, allow only admin
223 return false unless(params[:id])
217 return false unless(params[:id])
224 user = User.find(params[:id])
218 user = User.find(params[:id])
225 return false unless user
219 return false unless user
226 return admin_authorization if user.admin?
220 return admin_authorization if user.admin?
227 return true if GraderConfiguration["right.user_view_submission"]
221 return true if GraderConfiguration["right.user_view_submission"]
228
222
229 #finally, we allow only admin
223 #finally, we allow only admin
230 admin_authorization
224 admin_authorization
231 end
225 end
232
226
233 private
227 private
234 def user_params
228 def user_params
235 params.require(:user).permit(:login, :full_name, :email)
229 params.require(:user).permit(:login, :full_name, :email)
236 end
230 end
237 end
231 end
@@ -221,192 +221,207
221 end
221 end
222 return false
222 return false
223 end
223 end
224
224
225 def available_problems_group_by_contests
225 def available_problems_group_by_contests
226 contest_problems = []
226 contest_problems = []
227 pin = {}
227 pin = {}
228 contests.enabled.each do |contest|
228 contests.enabled.each do |contest|
229 available_problems = contest.problems.available
229 available_problems = contest.problems.available
230 contest_problems << {
230 contest_problems << {
231 :contest => contest,
231 :contest => contest,
232 :problems => available_problems
232 :problems => available_problems
233 }
233 }
234 available_problems.each {|p| pin[p.id] = true}
234 available_problems.each {|p| pin[p.id] = true}
235 end
235 end
236 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
236 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
237 contest_problems << {
237 contest_problems << {
238 :contest => nil,
238 :contest => nil,
239 :problems => other_avaiable_problems
239 :problems => other_avaiable_problems
240 }
240 }
241 return contest_problems
241 return contest_problems
242 end
242 end
243
243
244 def solve_all_available_problems?
244 def solve_all_available_problems?
245 available_problems.each do |p|
245 available_problems.each do |p|
246 u = self
246 u = self
247 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
247 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
248 return false if !p or !sub or sub.points < p.full_score
248 return false if !p or !sub or sub.points < p.full_score
249 end
249 end
250 return true
250 return true
251 end
251 end
252
252
253 #get a list of available problem
253 #get a list of available problem
254 def available_problems
254 def available_problems
255 # first, we check if this is normal mode
255 # first, we check if this is normal mode
256 if not GraderConfiguration.multicontests?
256 if not GraderConfiguration.multicontests?
257
257
258 #if this is a normal mode
258 #if this is a normal mode
259 #we show problem based on problem_group, if the config said so
259 #we show problem based on problem_group, if the config said so
260 if GraderConfiguration.use_problem_group?
260 if GraderConfiguration.use_problem_group?
261 return available_problems_in_group
261 return available_problems_in_group
262 else
262 else
263 return Problem.available_problems
263 return Problem.available_problems
264 end
264 end
265 else
265 else
266 #this is multi contest mode
266 #this is multi contest mode
267 contest_problems = []
267 contest_problems = []
268 pin = {}
268 pin = {}
269 contests.enabled.each do |contest|
269 contests.enabled.each do |contest|
270 contest.problems.available.each do |problem|
270 contest.problems.available.each do |problem|
271 if not pin.has_key? problem.id
271 if not pin.has_key? problem.id
272 contest_problems << problem
272 contest_problems << problem
273 end
273 end
274 pin[problem.id] = true
274 pin[problem.id] = true
275 end
275 end
276 end
276 end
277 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
277 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
278 return contest_problems + other_avaiable_problems
278 return contest_problems + other_avaiable_problems
279 end
279 end
280 end
280 end
281
281
282 # new feature, get list of available problem in all enabled group that the user belongs to
282 # new feature, get list of available problem in all enabled group that the user belongs to
283 def available_problems_in_group
283 def available_problems_in_group
284 problem = []
284 problem = []
285 self.groups.where(enabled: true).each do |group|
285 self.groups.where(enabled: true).each do |group|
286 group.problems.where(available: true).each { |p| problem << p }
286 group.problems.where(available: true).each { |p| problem << p }
287 end
287 end
288 problem.uniq!
288 problem.uniq!
289 if problem
289 if problem
290 problem.sort! do |a,b|
290 problem.sort! do |a,b|
291 case
291 case
292 when a.date_added < b.date_added
292 when a.date_added < b.date_added
293 1
293 1
294 when a.date_added > b.date_added
294 when a.date_added > b.date_added
295 -1
295 -1
296 else
296 else
297 a.name <=> b.name
297 a.name <=> b.name
298 end
298 end
299 end
299 end
300 return problem
300 return problem
301 else
301 else
302 return []
302 return []
303 end
303 end
304 end
304 end
305
305
306 #check if the user has the right to view that problem
306 #check if the user has the right to view that problem
307 #this also consider group based problem policy
307 #this also consider group based problem policy
308 def can_view_problem?(problem)
308 def can_view_problem?(problem)
309 return true if admin?
309 return true if admin?
310 return available_problems.include? problem
310 return available_problems.include? problem
311 end
311 end
312
312
313 def self.clear_last_login
313 def self.clear_last_login
314 User.update_all(:last_ip => nil)
314 User.update_all(:last_ip => nil)
315 end
315 end
316
316
317 + def get_jschart_user_sub_history
318 + start = 4.month.ago.beginning_of_day
319 + start_date = start.to_date
320 + count = Submission.where(user: self).where('submitted_at >= ?', start).group('DATE(submitted_at)').count
321 + i = 0
322 + label = []
323 + value = []
324 + while (start_date + i < Time.zone.now.to_date)
325 + label << (start_date+i).strftime("%d-%b")
326 + value << (count[start_date+i] || 0)
327 + i+=1
328 + end
329 + return {labels: label,datasets: [label:'sub',data: value, backgroundColor: 'rgba(54, 162, 235, 0.2)', borderColor: 'rgb(75, 192, 192)']}
330 + end
331 +
317 #create multiple user, one per lines of input
332 #create multiple user, one per lines of input
318 def self.create_from_list(lines)
333 def self.create_from_list(lines)
319 error_logins = []
334 error_logins = []
320 first_error = nil
335 first_error = nil
321 created_users = []
336 created_users = []
322
337
323 lines.split("\n").each do |line|
338 lines.split("\n").each do |line|
324 #split with large limit, this will cause consecutive ',' to be result in a blank
339 #split with large limit, this will cause consecutive ',' to be result in a blank
325 items = line.chomp.split(',',1000)
340 items = line.chomp.split(',',1000)
326 if items.length>=2
341 if items.length>=2
327 login = items[0]
342 login = items[0]
328 full_name = items[1]
343 full_name = items[1]
329 remark =''
344 remark =''
330 user_alias = ''
345 user_alias = ''
331
346
332 added_random_password = false
347 added_random_password = false
333 added_password = false
348 added_password = false
334
349
335 #given password?
350 #given password?
336 if items.length >= 3
351 if items.length >= 3
337 if items[2].chomp(" ").length > 0
352 if items[2].chomp(" ").length > 0
338 password = items[2].chomp(" ")
353 password = items[2].chomp(" ")
339 added_password = true
354 added_password = true
340 end
355 end
341 else
356 else
342 password = random_password
357 password = random_password
343 added_random_password=true;
358 added_random_password=true;
344 end
359 end
345
360
346 #given alias?
361 #given alias?
347 if items.length>= 4 and items[3].chomp(" ").length > 0;
362 if items.length>= 4 and items[3].chomp(" ").length > 0;
348 user_alias = items[3].chomp(" ")
363 user_alias = items[3].chomp(" ")
349 else
364 else
350 user_alias = login
365 user_alias = login
351 end
366 end
352
367
353 #given remark?
368 #given remark?
354 has_remark = false
369 has_remark = false
355 if items.length>=5
370 if items.length>=5
356 remark = items[4].strip;
371 remark = items[4].strip;
357 has_remark = true
372 has_remark = true
358 end
373 end
359
374
360 user = User.find_by_login(login)
375 user = User.find_by_login(login)
361 if (user)
376 if (user)
362 user.full_name = full_name
377 user.full_name = full_name
363 user.remark = remark if has_remark
378 user.remark = remark if has_remark
364 user.password = password if added_password || added_random_password
379 user.password = password if added_password || added_random_password
365 else
380 else
366 #create a random password if none are given
381 #create a random password if none are given
367 password = random_password unless password
382 password = random_password unless password
368 user = User.new({:login => login,
383 user = User.new({:login => login,
369 :full_name => full_name,
384 :full_name => full_name,
370 :password => password,
385 :password => password,
371 :password_confirmation => password,
386 :password_confirmation => password,
372 :alias => user_alias,
387 :alias => user_alias,
373 :remark => remark})
388 :remark => remark})
374 end
389 end
375 user.activated = true
390 user.activated = true
376
391
377 if user.save
392 if user.save
378 created_users << user
393 created_users << user
379 else
394 else
380 error_logins << "'#{login}'"
395 error_logins << "'#{login}'"
381 first_error = user.errors.full_messages.to_sentence unless first_error
396 first_error = user.errors.full_messages.to_sentence unless first_error
382 end
397 end
383 end
398 end
384 end
399 end
385
400
386 return {error_logins: error_logins, first_error: first_error, created_users: created_users}
401 return {error_logins: error_logins, first_error: first_error, created_users: created_users}
387
402
388 end
403 end
389
404
390 def self.find_non_admin_with_prefix(prefix='')
405 def self.find_non_admin_with_prefix(prefix='')
391 users = User.all
406 users = User.all
392 return users.find_all { |u| !(u.admin?) and u.login.index(prefix)==0 }
407 return users.find_all { |u| !(u.admin?) and u.login.index(prefix)==0 }
393 end
408 end
394
409
395 protected
410 protected
396 def encrypt_new_password
411 def encrypt_new_password
397 return if password.blank?
412 return if password.blank?
398 self.salt = (10+rand(90)).to_s
413 self.salt = (10+rand(90)).to_s
399 self.hashed_password = User.encrypt(self.password,self.salt)
414 self.hashed_password = User.encrypt(self.password,self.salt)
400 end
415 end
401
416
402 def assign_default_site
417 def assign_default_site
403 # have to catch error when migrating (because self.site is not available).
418 # have to catch error when migrating (because self.site is not available).
404 begin
419 begin
405 if self.site==nil
420 if self.site==nil
406 self.site = Site.find_by_name('default')
421 self.site = Site.find_by_name('default')
407 if self.site==nil
422 if self.site==nil
408 self.site = Site.find(1) # when 'default has be renamed'
423 self.site = Site.find(1) # when 'default has be renamed'
409 end
424 end
410 end
425 end
411 rescue
426 rescue
412 end
427 end
@@ -1,33 +1,33
1 - if submission.nil?
1 - if submission.nil?
2 = "-"
2 = "-"
3 - else
3 - else
4 - if local_assigns[:show_id]
4 - if local_assigns[:show_id]
5 .row
5 .row
6 - .col-3.text-secondary
6 + .col-3.fw-bold
7 - Sub ID:
7 + Sub ID
8 - %strong.col-9= submission.id
8 + .col-9.text-secondary-x= submission.id
9 - unless submission.graded_at
9 - unless submission.graded_at
10 .row
10 .row
11 - .col-3.text-secondary= t 'main.submitted_at'
11 + .col-3.fw-bold= t 'main.submitted_at'
12 - %strong.col-9= format_full_time_ago(submission.submitted_at.localtime)
12 + .col-9.text-secondary-x= format_full_time_ago(submission.submitted_at.localtime)
13 - else
13 - else
14 .row
14 .row
15 - .col-3.text-secondary= t 'main.graded_at'
15 + .col-3.fw-bold= t 'main.graded_at'
16 - %strong.col-9= format_full_time_ago(submission.graded_at.localtime)
16 + .col-9.text-secondary-x= format_full_time_ago(submission.graded_at.localtime)
17 - if GraderConfiguration['ui.show_score']
17 - if GraderConfiguration['ui.show_score']
18 .row
18 .row
19 - .col-3.text-secondary=t 'main.score'
19 + .col-3.fw-bold=t 'main.score'
20 - %strong.col-9
20 + .col-9.text-secondary-x
21 = (submission.points*100/submission.problem.full_score).to_i
21 = (submission.points*100/submission.problem.full_score).to_i
22 %tt.grader-comment
22 %tt.grader-comment
23 = " [#{submission.grader_comment}]"
23 = " [#{submission.grader_comment}]"
24 - if local_assigns[:show_button]
24 - if local_assigns[:show_button]
25 - if submission.graded_at
25 - if submission.graded_at
26 - if GraderConfiguration.show_grading_result
26 - if GraderConfiguration.show_grading_result
27 = link_to '[detailed result]', :action => 'result', :id => submission.id
27 = link_to '[detailed result]', :action => 'result', :id => submission.id
28 = link_to "#{t 'main.cmp_msg'}", compiler_msg_submission_path(submission), {popup: true,remote: true,class: 'btn btn-sm btn-info'}
28 = link_to "#{t 'main.cmp_msg'}", compiler_msg_submission_path(submission), {popup: true,remote: true,class: 'btn btn-sm btn-info'}
29 = link_to "#{t 'main.src_link'}",download_submission_path(submission), class: 'btn btn-sm btn-info'
29 = link_to "#{t 'main.src_link'}",download_submission_path(submission), class: 'btn btn-sm btn-info'
30 = link_to "#{t 'main.submissions_link'}", problem_submissions_path(submission.problem.id), class: 'btn btn-sm btn-info'
30 = link_to "#{t 'main.submissions_link'}", problem_submissions_path(submission.problem.id), class: 'btn btn-sm btn-info'
31 - if GraderConfiguration.show_testcase
31 - if GraderConfiguration.show_testcase
32 = link_to "testcases", show_problem_testcases_path(submission.problem.id), class: 'btn btn-sm btn-info'
32 = link_to "testcases", show_problem_testcases_path(submission.problem.id), class: 'btn btn-sm btn-info'
33
33
@@ -1,119 +1,129
1 - content_for :head do
1 - content_for :head do
2 = stylesheet_link_tag 'problems'
2 = stylesheet_link_tag 'problems'
3 = javascript_include_tag 'local_jquery'
3 = javascript_include_tag 'local_jquery'
4
4
5 - :javascript
6 - $(document).ready( function() {
7 - function shiftclick(start,stop,value) {
8 - $('tr input').each( function(id,input) {
9 - var $input=$(input);
10 - var iid=parseInt($input.attr('id').split('-')[2]);
11 - if(iid>=start&&iid<=stop){
12 - $input.prop('checked',value)
13 - }
14 - });
15 - }
16 -
17 - $('tr input').click( function(e) {
18 - if (e.shiftKey) {
19 - stop = parseInt($(this).attr('id').split('-')[2]);
20 - var orig_stop = stop
21 - if (typeof start !== 'undefined') {
22 - if (start > stop) {
23 - var tmp = start;
24 - start = stop;
25 - stop = tmp;
26 - }
27 - shiftclick(start,stop,$(this).is(':checked') )
28 - }
29 - start = orig_stop
30 - } else {
31 - start = parseInt($(this).attr('id').split('-')[2]);
32 - }
33 - });
34 - });
35
5
36
6
37 %h1 Manage problems
7 %h1 Manage problems
38
8
39 %p= link_to '[Back to problem list]', problems_path
9 %p= link_to '[Back to problem list]', problems_path
40
10
41 - = form_tag :action=>'do_manage' do
11 + = form_with url: do_manage_problems_path do |f|
42 - .panel.panel-primary
12 + .card.border-primary.mb-2
43 - .panel-heading
13 + .card-header.text-bg-primary.border-primary
44 Action
14 Action
45 - .panel-body
15 + .card-body
46 .submit-box
16 .submit-box
47 What do you want to do to the selected problem?
17 What do you want to do to the selected problem?
48 %br/
18 %br/
49 (You can shift-click to select a range of problems)
19 (You can shift-click to select a range of problems)
20 + .row
21 + .col-md-auto
22 + = f.check_box :change_date_added, class: 'form-check-input'
23 + .col-md-auto
24 + .form-check-label
25 + Change "Date added" to
26 + .col-md-auto
27 +
50 %ul.form-inline
28 %ul.form-inline
51 %li
29 %li
52 Change "Date added" to
30 Change "Date added" to
53 .input-group.date
31 .input-group.date
54 = text_field_tag :date_added, class: 'form-control'
32 = text_field_tag :date_added, class: 'form-control'
55 %span.input-group-addon
33 %span.input-group-addon
56 %span.glyphicon.glyphicon-calendar
34 %span.glyphicon.glyphicon-calendar
57 -# = select_date Date.current, :prefix => 'date_added'
35 -# = select_date Date.current, :prefix => 'date_added'
58 &nbsp;&nbsp;&nbsp;
36 &nbsp;&nbsp;&nbsp;
59 = submit_tag 'Change', :name => 'change_date_added', class: 'btn btn-primary btn-sm'
37 = submit_tag 'Change', :name => 'change_date_added', class: 'btn btn-primary btn-sm'
60 %li
38 %li
61 Set "Available" to
39 Set "Available" to
62 = submit_tag 'True', :name => 'enable_problem', class: 'btn btn-primary btn-sm'
40 = submit_tag 'True', :name => 'enable_problem', class: 'btn btn-primary btn-sm'
63 = submit_tag 'False', :name => 'disable_problem', class: 'btn btn-primary btn-sm'
41 = submit_tag 'False', :name => 'disable_problem', class: 'btn btn-primary btn-sm'
64
42
65 - if GraderConfiguration.multicontests?
43 - if GraderConfiguration.multicontests?
66 %li
44 %li
67 Add selected problems to contest
45 Add selected problems to contest
68 = select("contest","id",Contest.all.collect {|c| [c.title, c.id]})
46 = select("contest","id",Contest.all.collect {|c| [c.title, c.id]})
69 = submit_tag 'Add', :name => 'add_to_contest', class: 'btn btn-primary btn-sm'
47 = submit_tag 'Add', :name => 'add_to_contest', class: 'btn btn-primary btn-sm'
70 %li
48 %li
71 Add selected problems to user group
49 Add selected problems to user group
72 = select_tag "group_id", options_from_collection_for_select( Group.all, 'id','name',params[:group_name]), id: 'group_name',class: 'select2'
50 = 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-primary'
51 = submit_tag 'Add', name: 'add_group', class: 'btn btn-primary'
74 %li
52 %li
75 Add the following tags to the selected problems
53 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"}
54 = 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'
55 = submit_tag 'Add', name: 'add_tags', class: 'btn btn-primary'
78
56
79 %table.table.table-hover.datatable
57 %table.table.table-hover.datatable
80 %thead
58 %thead
81 %tr{style: "text-align: left;"}
59 %tr{style: "text-align: left;"}
82 %th= check_box_tag 'select_all'
60 %th= check_box_tag 'select_all'
83 %th Name
61 %th Name
84 %th Full name
62 %th Full name
85 %th Tags
63 %th Tags
86 %th Available
64 %th Available
87 %th Date added
65 %th Date added
88 - if GraderConfiguration.multicontests?
66 - if GraderConfiguration.multicontests?
89 %th Contests
67 %th Contests
90
68
91 %tbody
69 %tbody
92 - num = 0
70 - num = 0
93 - for problem in @problems
71 - for problem in @problems
94 - num += 1
72 - num += 1
95 %tr{:id => "row-prob-#{problem.id}", :name=> "prob-#{problem.id}"}
73 %tr{:id => "row-prob-#{problem.id}", :name=> "prob-#{problem.id}"}
96 %td= check_box_tag "prob-#{problem.id}-#{num}"
74 %td= check_box_tag "prob-#{problem.id}-#{num}"
97 %td= problem.name
75 %td= problem.name
98 %td= problem.full_name
76 %td= problem.full_name
99 %td
77 %td
100 - problem.tags.each do |t|
78 - problem.tags.each do |t|
101 %span.label.label-default= t.name
79 %span.label.label-default= t.name
102 %td= problem.available
80 %td= problem.available
103 %td= problem.date_added
81 %td= problem.date_added
104 - if GraderConfiguration.multicontests?
82 - if GraderConfiguration.multicontests?
105 %td
83 %td
106 - problem.contests.each do |contest|
84 - problem.contests.each do |contest|
107 = "(#{contest.name} [#{link_to 'x', :action => 'remove_contest', :id => problem.id, :contest_id => contest.id }])"
85 = "(#{contest.name} [#{link_to 'x', :action => 'remove_contest', :id => problem.id, :contest_id => contest.id }])"
108
86
109 :javascript
87 :javascript
88 +
89 + $(document).on('import-map-loaded', function() {
90 + function shiftclick(start,stop,value) {
91 + $('tr input').each( function(id,input) {
92 + var $input=$(input);
93 + var iid=parseInt($input.attr('id').split('-')[2]);
94 + if(iid>=start&&iid<=stop){
95 + $input.prop('checked',value)
96 + }
97 + });
98 + }
99 +
100 + $('tr input').click( function(e) {
101 + if (e.shiftKey) {
102 + stop = parseInt($(this).attr('id').split('-')[2]);
103 + var orig_stop = stop
104 + if (typeof start !== 'undefined') {
105 + if (start > stop) {
106 + var tmp = start;
107 + start = stop;
108 + stop = tmp;
109 + }
110 + shiftclick(start,stop,$(this).is(':checked') )
111 + }
112 + start = orig_stop
113 + } else {
114 + start = parseInt($(this).attr('id').split('-')[2]);
115 + }
116 + });
117 +
110 $('.input-group.date').datetimepicker({
118 $('.input-group.date').datetimepicker({
111 format: 'DD/MMM/YYYY',
119 format: 'DD/MMM/YYYY',
112 showTodayButton: true,
120 showTodayButton: true,
113 locale: 'en',
121 locale: 'en',
114 widgetPositioning: {horizontal: 'auto', vertical: 'bottom'},
122 widgetPositioning: {horizontal: 'auto', vertical: 'bottom'},
115
123
116 });
124 });
125 +
117 $('.datatable').DataTable({
126 $('.datatable').DataTable({
118 paging: false
127 paging: false
119 });
128 });
129 + });
@@ -1,63 +1,90
1 :css
1 :css
2 .fix-width {
2 .fix-width {
3 font-family: "Consolas, Monaco, Droid Sans Mono,Mono, Monospace,Courier"
3 font-family: "Consolas, Monaco, Droid Sans Mono,Mono, Monospace,Courier"
4 }
4 }
5
5
6 %h1 Problem stat: #{@problem.name}
6 %h1 Problem stat: #{@problem.name}
7 - %h2 Overview
8
7
8 + .row.mb-3
9 + .col-md-8
10 + .card
11 + .card-body
12 + %h2.card-title Submission History
13 + %canvas#chart{height: '50px'}
14 +
15 + .col-md-4
16 + .card
17 + .card-body
18 + %h2.card-title General Info
9 .row
19 .row
10 - .col-md-2
20 + .col-sm-6
11 - %strong Name:
21 + Name
12 - .col-md-10
22 + .col-sm-6
13 = @problem.full_name #in_place_editor_field :problem, :full_name, {}, :rows=>1
23 = @problem.full_name #in_place_editor_field :problem, :full_name, {}, :rows=>1
14 = link_to_description_if_any "[#{t 'main.problem_desc'}] <span class='glyphicon glyphicon-file'></span>".html_safe, @problem
24 = link_to_description_if_any "[#{t 'main.problem_desc'}] <span class='glyphicon glyphicon-file'></span>".html_safe, @problem
15 .row
25 .row
16 - .col-md-2.strong
26 + .col-sm-6
17 - %strong Submissions:
27 + Subs
18 - .col-md-10
28 + .col-sm-6
19 = @submissions.count
29 = @submissions.count
20 .row
30 .row
21 - .col-md-2.strong
31 + .col-sm-6
22 - %strong Solved/Attemped User
32 + Solved/Attempted User
23 - .col-md-10
33 + .col-sm-6
24 #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%)
34 #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%)
25
35
26 -
27 - %h2 Submissions Count
28 - = render partial: 'application/bar_graph', locals: { histogram: @histogram }
29 -
30 %h2 Submissions
36 %h2 Submissions
31 - if @submissions and @submissions.count > 0
37 - if @submissions and @submissions.count > 0
32 %table#main_table.table.table-condensed.table-striped
38 %table#main_table.table.table-condensed.table-striped
33 %thead
39 %thead
34 %tr
40 %tr
35 %th ID
41 %th ID
36 %th Login
42 %th Login
37 %th Name
43 %th Name
38 %th Submitted_at
44 %th Submitted_at
39 %th language
45 %th language
40 %th Points
46 %th Points
41 %th comment
47 %th comment
42 %th IP
48 %th IP
43 %tbody
49 %tbody
44 - row_odd,curr = true,''
50 - row_odd,curr = true,''
45 - @submissions.each do |sub|
51 - @submissions.each do |sub|
46 - next unless sub.user
52 - next unless sub.user
47 - row_odd,curr = !row_odd, sub.user if curr != sub.user
53 - row_odd,curr = !row_odd, sub.user if curr != sub.user
48 %tr
54 %tr
49 %td= link_to sub.id, submission_path(sub)
55 %td= link_to sub.id, submission_path(sub)
50 %td= link_to sub.user.login, stat_user_path(sub.user)
56 %td= link_to sub.user.login, stat_user_path(sub.user)
51 %td= sub.user.full_name
57 %td= sub.user.full_name
52 %td{data: {order: sub.submitted_at}}= time_ago_in_words(sub.submitted_at) + " ago"
58 %td{data: {order: sub.submitted_at}}= time_ago_in_words(sub.submitted_at) + " ago"
53 %td= sub.language.name
59 %td= sub.language.name
54 %td= sub.points
60 %td= sub.points
55 %td.fix-width= sub.grader_comment
61 %td.fix-width= sub.grader_comment
56 %td= sub.ip_address
62 %td= sub.ip_address
57 - else
63 - else
58 No submission
64 No submission
59
65
60 :javascript
66 :javascript
67 + $(document).on('import-map-loaded',(e) => {
68 + //init datatable
61 $("#main_table").DataTable({
69 $("#main_table").DataTable({
62 paging: false
70 paging: false
63 });
71 });
72 +
73 + //history graph
74 + data = #{@chart_dataset}
75 + config = {
76 + type: 'bar',
77 + data: data,
78 + options: {
79 + plugins: {
80 + legend: {
81 + display: false
82 + },
83 + },
84 + }
85 + }
86 + Chart.defaults.font.size = 15
87 + //Chart.defaults.font.family = 'Sarabun Light'
88 + chart = new Chart($('#chart'),config)
89 + });
90 +
@@ -1,70 +1,95
1 - - content_for :header do
2 - = javascript_include_tag 'local_jquery'
3 -
4 - :javascript
5 - $(function () {
6 - $('#submission_table').tablesorter({widgets: ['zebra']});
7 - });
8
1
9 :css
2 :css
10 .fix-width {
3 .fix-width {
11 font-family: Droid Sans Mono,Consolas, monospace, mono, Courier New, Courier;
4 font-family: Droid Sans Mono,Consolas, monospace, mono, Courier New, Courier;
12 }
5 }
13
6
14 - %h1= @user.full_name
7 + %h1 User stats
8 + %h5.text-secondary= @user.login
9 +
10 + .row.my-3
11 + .col-md-8
12 + .card
13 + .card-body
14 + %h2.card-title Sub Info
15 + %canvas#chart{height: '50px'}
15
16
16 - <b>Login:</b> #{@user.login} <br/>
17 + .col-md-4
17 - <b>Full name:</b> #{@user.full_name} <br />
18 + .card
19 + .card-body
20 + %h2.card-title General Info
21 + .row
22 + .col-sm-6.fw-bold
23 + Login
24 + .col-sm-6
25 + = @user.login
26 + .row
27 + .col-sm-6.fw-bold
28 + Full name
29 + .col-sm-6
30 + = @user.full_name
31 + .row
32 + .col-sm-6.fw-bold
33 + Subs
34 + .col-sm-6
35 + = @summary[:count]
36 + .row
37 + .col-sm-6.fw-bold
38 + Solved/Attempted Problem
39 + .col-sm-6
40 + #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%)
18
41
19
42
20 - %h2 Problem Stat
43 + %table#main_table.table.table-striped
21 - %table.info
22 - %thead
23 - %tr.info-head
24 - %th Stat
25 - %th Value
26 - %tbody
27 - %tr{class: cycle('info-even','info-odd')}
28 - %td.info_param Submissions
29 - %td= @summary[:count]
30 - %tr{class: cycle('info-even','info-odd')}
31 - %td.info_param Solved/Attempted Problem
32 - %td #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%)
33 -
34 - %h2 Submission History
35 -
36 - =render partial: 'application/bar_graph', locals: {histogram: @histogram, param: {bar_width: 7}}
37 -
38 -
39 - %table#submission_table.table.table-striped
40 %thead
44 %thead
41 %tr
45 %tr
42 %th ID
46 %th ID
43 %th Problem code
47 %th Problem code
44 %th Problem full name
48 %th Problem full name
45 %th Language
49 %th Language
46 %th Submitted at
50 %th Submitted at
47 %th Result
51 %th Result
48 %th Score
52 %th Score
49 - if session[:admin]
53 - if session[:admin]
50 %th IP
54 %th IP
51 %tbody
55 %tbody
52 - @submission.each do |s|
56 - @submission.each do |s|
53 - next unless s.problem
57 - next unless s.problem
54 %tr
58 %tr
55 %td= link_to s.id, submission_path(s)
59 %td= link_to s.id, submission_path(s)
56 %td= link_to s.problem.name, stat_problem_path(s.problem)
60 %td= link_to s.problem.name, stat_problem_path(s.problem)
57 %td= s.problem.full_name
61 %td= s.problem.full_name
58 %td= s.language.pretty_name
62 %td= s.language.pretty_name
59 %td #{s.submitted_at.strftime('%Y-%m-%d %H:%M')} (#{time_ago_in_words(s.submitted_at)} ago)
63 %td #{s.submitted_at.strftime('%Y-%m-%d %H:%M')} (#{time_ago_in_words(s.submitted_at)} ago)
60 %td.fix-width= s.grader_comment
64 %td.fix-width= s.grader_comment
61 %td= ( s.try(:points) ? (s.points*100/s.problem.full_score) : '' )
65 %td= ( s.try(:points) ? (s.points*100/s.problem.full_score) : '' )
62 - if session[:admin]
66 - if session[:admin]
63 %td= s.ip_address
67 %td= s.ip_address
64
68
65
69
66
70
67 :javascript
71 :javascript
68 - $("#submission_table").DataTable({
72 + $(document).on('import-map-loaded',(e) => {
73 + //init datatable
74 + $("#main_table").DataTable({
69 paging: false
75 paging: false
70 });
76 });
77 +
78 + //history graph
79 + data = #{@chart_dataset}
80 + config = {
81 + type: 'bar',
82 + data: data,
83 + options: {
84 + plugins: {
85 + legend: {
86 + display: false
87 + },
88 + },
89 + }
90 + }
91 + Chart.defaults.font.size = 15
92 + //Chart.defaults.font.family = 'Sarabun Light'
93 + chart = new Chart($('#chart'),config)
94 + });
95 +
deleted file
You need to be logged in to leave comments. Login now