Description:
- login report - fix bugs on pagination on user manage
Commit status:
[Not Reviewed]
References:
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r792:6ae18fbc4b91 - - 14 files changed: 255 inserted, 76 deleted

@@ -0,0 +1,130
1 + - content_for :header do
2 + = javascript_include_tag 'local_jquery'
3 +
4 + %h1 Logins detail
5 +
6 + .row
7 + .col-md-4
8 + .alert.alert-info
9 + %ul
10 + %li You have to click refresh when changing the filter above
11 + %li Detail tab shows each logins separately
12 + %li Summary tab shows logins summary of each user
13 + .col-md-4
14 + = render partial: 'shared/date_filter'
15 + .col-md-4
16 + = render partial: 'shared/user_select'
17 +
18 + .row.form-group
19 + .col-sm-12
20 + %ul.nav.nav-tabs
21 + %li.active
22 + %a{href: '#detail', data: {toggle: :tab}} Detail
23 + %li
24 + %a{href: '#summary', data: {toggle: :tab}} Summary
25 + .row
26 + .col-sm-12
27 + .tab-content
28 + .tab-pane.active#detail
29 + %table#detail-table.table.table-hover.table-condense.datatable{style: 'width: 100%'}
30 + .tab-pane#summary
31 + %table#summary-table.table.table-hover.table-condense.datatable{style: 'width: 100%'}
32 +
33 +
34 +
35 + :javascript
36 + $(function() {
37 + detail_table = $('#detail-table').DataTable({
38 + dom: "<'row'<'col-sm-3'B><'col-sm-3'l><'col-sm-6'f>>" + "<'row'<'col-sm-12'tr>>" + "<'row'<'col-sm-5'i><'col-sm-7'p>>",
39 + autoWidth: true,
40 + buttons: [
41 + {
42 + text: 'Refresh',
43 + action: (e,dt,node,config) => {
44 + detail_table.clear().draw()
45 + detail_table.ajax.reload( () => { detail_table.columns.adjust().draw() } )
46 + summary_table.clear().draw()
47 + summary_table.ajax.reload( () => { summary_table.columns.adjust().draw() } )
48 + }
49 + },
50 + 'copy',
51 + {
52 + extend: 'excel',
53 + title: 'Login detail',
54 + }
55 + ],
56 + columns: [
57 + {title: 'User', data: 'login_text'},
58 + {title: 'Time', data: 'created_at'},
59 + {title: 'IP', data: 'ip_address'},
60 + ],
61 + ajax: {
62 + url: '#{login_detail_query_report_path}',
63 + type: 'POST',
64 + data: (d) => {
65 + d.since_datetime = $('#since_datetime').val()
66 + d.until_datetime = $('#until_datetime').val()
67 + d.users = $("input[name='users']:checked").val()
68 + d.groups = $("#group_id").select2('val')
69 + },
70 + dataType: 'json',
71 + beforeSend: (request) => {
72 + request.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));
73 + },
74 + }, //end ajax
75 + pageLength: 25,
76 + processing: true,
77 + });
78 +
79 + summary_table = $('#summary-table').DataTable({
80 + dom: "<'row'<'col-sm-3'B><'col-sm-3'l><'col-sm-6'f>>" + "<'row'<'col-sm-12'tr>>" + "<'row'<'col-sm-5'i><'col-sm-7'p>>",
81 + autoWidth: true,
82 + buttons: [
83 + {
84 + text: 'Refresh',
85 + action: (e,dt,node,config) => {
86 + summary_table.clear().draw()
87 + summary_table.ajax.reload( () => { summary_table.columns.adjust().draw() } )
88 + detail_table.clear().draw()
89 + detail_table.ajax.reload( () => { detail_table.columns.adjust().draw() } )
90 + }
91 + },
92 + 'copy',
93 + {
94 + extend: 'excel',
95 + title: 'Login summary',
96 + }
97 + ],
98 + columns: [
99 + {title: 'User', data: 'login_text'},
100 + {title: 'Login Count', data: 'count'},
101 + {title: 'Earliest', data: 'earliest'},
102 + {title: 'Latest', data: 'latest'},
103 + {title: 'IP', data: 'ip_address'},
104 + ],
105 + ajax: {
106 + url: '#{login_summary_query_report_path}',
107 + type: 'POST',
108 + data: (d) => {
109 + d.since_datetime = $('#since_datetime').val()
110 + d.until_datetime = $('#until_datetime').val()
111 + d.users = $("input[name='users']:checked").val()
112 + d.groups = $("#group_id").select2('val')
113 + },
114 + dataType: 'json',
115 + beforeSend: (request) => {
116 + request.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));
117 + },
118 + }, //end ajax
119 + pageLength: 25,
120 + processing: true,
121 + });
122 +
123 + $('.input-group.date').datetimepicker({
124 + format: 'YYYY-MM-DD HH:mm',
125 + showTodayButton: true,
126 + locale: 'en',
127 + widgetPositioning: {horizontal: 'auto', vertical: 'bottom'},
128 + defaultDate: moment()
129 + });
130 + });
@@ -0,0 +1,10
1 + json.draw params['draw']&.to_i
2 + json.recordsTotal @recordsTotal
3 + json.recordsFiltered @recordsFiltered
4 + json.data do
5 + json.array! @logins do |login|
6 + json.login_text login.user ? "<a href='#{stat_user_path(login.user_id)}'>(#{login.user.login})</a> #{login.user.full_name}" : '-- deletec user --'
7 + json.created_at login.created_at.strftime('%Y-%m-%d %H:%M')
8 + json.ip_address login.ip_address
9 + end
10 + end
@@ -0,0 +1,12
1 + json.draw params['draw']&.to_i
2 + json.recordsTotal @recordsTotal
3 + json.recordsFiltered @recordsFiltered
4 + json.data do
5 + json.array! @users do |user|
6 + json.login_text "<a href='#{stat_user_path(user[:id])}'>(#{user[:login]})</a> #{user[:full_name]}"
7 + json.count user[:count]
8 + json.earliest user[:min].strftime('%Y-%m-%d %H:%M')
9 + json.latest user[:max].strftime('%Y-%m-%d %H:%M')
10 + json.ip_address user[:ip].join('<br/>')
11 + end
12 + end
@@ -0,0 +1,5
1 + class AddIndexToLogin < ActiveRecord::Migration[5.2]
2 + def change
3 + add_index :logins, :user_id
4 + end
5 + end
@@ -1,13 +1,15
1 1 require 'csv'
2 2
3 3 class ReportController < ApplicationController
4 4
5 5 before_action :check_valid_login
6 6
7 - before_action :admin_authorization, only: [:login_stat,:submission, :submission_query, :stuck, :cheat_report, :cheat_scruntinize, :show_max_score, :current_score]
7 + before_action :admin_authorization, only: [:login_stat,:submission, :submission_query,
8 + :login, :login_detail_query, :login_summary_query,
9 + :stuck, :cheat_report, :cheat_scruntinize, :show_max_score, :current_score]
8 10
9 11 before_action(only: [:problem_hof]) { |c|
10 12 return false unless check_valid_login
11 13
12 14 admin_authorization unless GraderConfiguration["right.user_view_submission"]
13 15 }
@@ -101,13 +103,59
101 103 else
102 104 render template: 'user_admin/user_stat'
103 105 end
104 106
105 107 end
106 108
107 - def login_stat
109 + def login
110 + end
111 +
112 + def login_summary_query
113 + @users = Array.new
114 +
115 + date_and_time = '%Y-%m-%d %H:%M'
116 + begin
117 + md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
118 + @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)
119 + rescue
120 + @since_time = DateTime.new(1000,1,1)
121 + end
122 + begin
123 + md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
124 + @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)
125 + rescue
126 + @until_time = DateTime.new(3000,1,1)
127 + end
128 +
129 + record = User
130 + .left_outer_joins(:logins).group('users.id')
131 + .where("logins.created_at >= ? AND logins.created_at <= ?",@since_time, @until_time)
132 + case params[:users]
133 + when 'enabled'
134 + record = record.where(enabled: true)
135 + when 'group'
136 + record = record.joins(:groups).where(groups: {id: params[:groups]}) if params[:groups]
137 + end
138 +
139 + record = record.pluck("users.id,users.login,users.full_name,count(logins.created_at),min(logins.created_at),max(logins.created_at)")
140 + record.each do |user|
141 + x = Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
142 + user[0],@since_time,@until_time)
143 + .pluck(:ip_address).uniq
144 + @users << { id: user[0],
145 + login: user[1],
146 + full_name: user[2],
147 + count: user[3],
148 + min: user[4],
149 + max: user[5],
150 + ip: x
151 + }
152 + end
153 + end
154 +
155 + def login_detail_query
108 156 @logins = Array.new
109 157
110 158 date_and_time = '%Y-%m-%d %H:%M'
111 159 begin
112 160 md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
113 161 @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)
@@ -118,49 +166,40
118 166 md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
119 167 @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)
120 168 rescue
121 169 @until_time = DateTime.new(3000,1,1)
122 170 end
123 171
124 - User.all.each do |user|
125 - @logins << { id: user.id,
126 - login: user.login,
127 - full_name: user.full_name,
128 - count: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
129 - user.id,@since_time,@until_time)
130 - .count(:id),
131 - min: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
132 - user.id,@since_time,@until_time)
133 - .minimum(:created_at),
134 - max: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
135 - user.id,@since_time,@until_time)
136 - .maximum(:created_at),
137 - ip: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
138 - user.id,@since_time,@until_time)
139 - .select(:ip_address).uniq
140 -
141 - }
172 + @logins = Login.includes(:user).where("logins.created_at >= ? AND logins.created_at <= ?",@since_time, @until_time)
173 + case params[:users]
174 + when 'enabled'
175 + @logins = @logins.where(users: {enabled: true})
176 + when 'group'
177 + @logins = @logins.joins(user: :groups).where(user: {groups: {id: params[:groups]}}) if params[:groups]
142 178 end
143 179 end
144 180
145 181 def submission
146 182 end
147 183
148 184 def submission_query
149 185 @submissions = Submission
150 186 .includes(:problem).includes(:user).includes(:language)
151 187
152 - if params[:problem]
153 - @submission = @submission.where(problem_id: params[:problem])
188 + case params[:users]
189 + when 'enabled'
190 + @submissions = @submissions.where(users: {enabled: true})
191 + when 'group'
192 + @submissions = @submissions.joins(user: :groups).where(user: {groups: {id: params[:groups]}}) if params[:groups]
154 193 end
155 194
156 - case params[:users]
195 + case params[:problems]
157 196 when 'enabled'
158 - @submissions = @submissions.where('user.enabled': true)
159 - when 'group'
160 - @submissions = @submissions.joins(user: :groups).where(user: {groups: {id: params[:groups]}}) if params[:groups]
197 + @submissions = @submissions.where(problems: {available: true})
198 + when 'selected'
199 + @submissions = @submissions.where(problem_id: params[:problem_id])
161 200 end
162 201
163 202 #set default
164 203 params[:since_datetime] = Date.today.to_s if params[:since_datetime].blank?
165 204
166 205 @submissions, @recordsTotal, @recordsFiltered = process_query_record( @submissions,
@@ -169,12 +208,15
169 208 date_param_since: 'since_datetime',
170 209 date_param_until: 'until_datetime',
171 210 hard_limit: 100_000
172 211 )
173 212 end
174 213
214 + def login
215 + end
216 +
175 217 def problem_hof
176 218 # gen problem list
177 219 @user = User.find(session[:user_id])
178 220 @problems = @user.available_problems
179 221
180 222 # get selected problems or the default
@@ -5,19 +5,12
5 5 include MailHelperMethods
6 6
7 7 before_action :admin_authorization
8 8
9 9 def index
10 10 @user_count = User.count
11 - if params[:page] == 'all'
12 - @users = User.all
13 - @paginated = false
14 - else
15 - @users = User.paginate :page => params[:page]
16 - @paginated = true
17 - end
18 11 @users = User.all
19 12 @hidden_columns = ['hashed_password', 'salt', 'created_at', 'updated_at']
20 13 @contests = Contest.enabled
21 14 end
22 15
23 16 def active
@@ -19,12 +19,14
19 19 :foreign_key => "sender_id"
20 20
21 21 has_many :replied_messages, -> { order(created_at: :desc) },
22 22 :class_name => "Message",
23 23 :foreign_key => "receiver_id"
24 24
25 + has_many :logins
26 +
25 27 has_one :contest_stat, :class_name => "UserContestStat", :dependent => :destroy
26 28
27 29 belongs_to :site
28 30 belongs_to :country
29 31
30 32 has_and_belongs_to_many :contests, -> { order(:name)}
@@ -62,12 +62,13
62 62 Report
63 63 %span.caret
64 64 %ul.dropdown-menu
65 65 = add_menu( 'Current Score', 'report', 'current_score')
66 66 = add_menu( 'Score Report', 'report', 'max_score')
67 67 = add_menu( 'Submission Report', 'report', 'submission')
68 + = add_menu( 'Login Report', 'report', 'login')
68 69 - if (ungraded = Submission.where('graded_at is null').where('submitted_at < ?', 1.minutes.ago).count) > 0
69 70 =link_to "#{ungraded} backlogs!",
70 71 grader_list_path,
71 72 class: 'navbar-btn btn btn-default btn-warning', data: {toggle: 'tooltip'},title: 'Number of ungraded submission'
72 73
73 74 %ul.nav.navbar-nav.navbar-right
@@ -33,13 +33,17
33 33 text: 'Refresh',
34 34 action: (e,dt,node,config) => {
35 35 submission_table.clear().draw()
36 36 submission_table.ajax.reload( () => { submission_table.columns.adjust().draw() } )
37 37 }
38 38 },
39 - 'copy', 'excel'
39 + 'copy',
40 + {
41 + extend: 'excel',
42 + title: 'Submission detail'
43 + }
40 44 ],
41 45 columns: [
42 46 {title: 'Sub ID', data: 'id'},
43 47 {title: 'User', data: 'user.login'},
44 48 {title: 'Problem', data: 'problem.long_name'},
45 49 {title: 'Language', data: 'language.pretty_name'},
@@ -52,14 +56,15
52 56 url: '#{submission_query_report_path}',
53 57 type: 'POST',
54 58 data: (d) => {
55 59 d.since_datetime = $('#since_datetime').val()
56 60 d.until_datetime = $('#until_datetime').val()
57 61 d.users = $("input[name='users']:checked").val()
58 - d.problems = $("#problem_id").select2('val')
59 62 d.groups = $("#group_id").select2('val')
63 + d.problems = $("input[name='problems']:checked").val()
64 + d.problem_id = $("#problem_id").select2('val')
60 65 },
61 66 dataType: 'json',
62 67 beforeSend: (request) => {
63 68 request.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));
64 69 },
65 70 }, //end ajax
@@ -1,10 +1,19
1 1 .panel.panel-primary
2 2 .panel-heading
3 3 Problems
4 4 .panel-body
5 - %p
6 - Select problem(s) to be included in the report
7 - = label_tag :problem_id, "Problems"
5 + .radio
6 + %label
7 + = radio_button_tag 'problems', 'all', (params[:users] == "all")
8 + All problems
9 + .radio
10 + %label
11 + = radio_button_tag 'problems', 'enabled', (params[:users] == "enabled"), checked: true
12 + Only problems with available = "yes"
13 + .radio
14 + %label
15 + = radio_button_tag 'problems', 'selected', (params[:users] == "group")
16 + Only these selected problems
8 17 = select_tag 'problem_id[]',
9 18 options_for_select(Problem.all.collect {|p| ["[#{p.name}] #{p.full_name}", p.id]},params[:problem_id]),
10 19 { id: :problem_id, class: 'select2 form-control', multiple: "true" }
@@ -5,13 +5,13
5 5 .radio
6 6 %label
7 7 = radio_button_tag 'users', 'all', (params[:users] == "all")
8 8 All users
9 9 .radio
10 10 %label
11 - = radio_button_tag 'users', 'enabled', (params[:users] == "enabled")
11 + = radio_button_tag 'users', 'enabled', (params[:users] == "enabled"), checked: true
12 12 Only enabled users
13 13 .radio
14 14 %label
15 15 = radio_button_tag 'users', 'group', (params[:users] == "group")
16 16 Only these groups
17 17 = select_tag 'group_id[]',
@@ -151,12 +151,17
151 151 get 'cheat_report'
152 152 post 'cheat_report'
153 153 get 'cheat_scruntinize'
154 154 post 'cheat_scruntinize'
155 155 get 'submission'
156 156 post 'submission_query'
157 + get 'login_stat'
158 + post 'login_stat'
159 + get 'login'
160 + post 'login_summary_query'
161 + post 'login_detail_query'
157 162 end
158 163 #get 'report/current_score', to: 'report#current_score', as: 'report_current_score'
159 164 #get 'report/problem_hof(/:id)', to: 'report#problem_hof', as: 'report_problem_hof'
160 165 #get "report/login"
161 166 #get 'report/max_score', to: 'report#max_score', as: 'report_max_score'
162 167 #post 'report/show_max_score', to: 'report#show_max_score', as: 'report_show_max_score'
@@ -7,13 +7,13
7 7 # system, you should be using db:schema:load, not running all the migrations
8 8 # from scratch. The latter is a flawed and unsustainable approach (the more migrations
9 9 # you'll amass, the slower it'll run and the greater likelihood for issues).
10 10 #
11 11 # It's strongly recommended that you check this file into your version control system.
12 12
13 - ActiveRecord::Schema.define(version: 2020_04_04_142959) do
13 + ActiveRecord::Schema.define(version: 2020_04_05_112919) do
14 14
15 15 create_table "announcements", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
16 16 t.string "author"
17 17 t.text "body", limit: 16777215
18 18 t.boolean "published"
19 19 t.datetime "created_at", null: false
@@ -112,12 +112,13
112 112
113 113 create_table "logins", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t|
114 114 t.integer "user_id"
115 115 t.string "ip_address"
116 116 t.datetime "created_at", null: false
117 117 t.datetime "updated_at", null: false
118 + t.index ["user_id"], name: "index_logins_on_user_id"
118 119 end
119 120
120 121 create_table "messages", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
121 122 t.integer "sender_id"
122 123 t.integer "receiver_id"
123 124 t.integer "replying_message_id"
deleted file
You need to be logged in to leave comments. Login now