• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

benwbrum / fromthepage / 20727230960

05 Jan 2026 07:46PM UTC coverage: 68.399% (+0.06%) from 68.341%
20727230960

Pull #5059

github

web-flow
Merge 845258e62 into 4ddc0360e
Pull Request #5059: 5033 - Add search in subjects view

2206 of 3737 branches covered (59.03%)

Branch coverage included in aggregate %.

139 of 143 new or added lines in 13 files covered. (97.2%)

162 existing lines in 6 files now uncovered.

9110 of 12807 relevant lines covered (71.13%)

131.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

55.67
/app/controllers/admin_controller.rb
1
class AdminController < ApplicationController
1✔
2
  include ErrorHelper
1✔
3

4
  before_action :authorized?
1✔
5

6
  PAGES_PER_SCREEN = 20
1✔
7

8
  def authorized?
1✔
9
    unless user_signed_in? && current_user.admin
60✔
10
      redirect_to main_app.dashboard_path
7✔
11
    end
12
  end
13

14
  def index
1✔
15
    @users = User.all
17✔
16
    @owners = User.where(owner: true)
17✔
17

18
    transcription_deeds = Deed.where(deed_type: DeedType.transcriptions_or_corrections_no_edits)
17✔
19
    contributor_deeds = Deed.where(deed_type: DeedType.contributor_types)
17✔
20

21
    # Count stats for dashboard
22
    @pages_per_hour         = transcription_deeds.where('created_at between ? and ?', Time.now - 1.hour, Time.now).count
17✔
23
    @contributions_per_hour = contributor_deeds.where('created_at between ? and ?', Time.now - 1.hour, Time.now).count
17✔
24
    @collections_count      = Collection.all.count
17✔
25
    @articles_count         = Article.all.count
17✔
26
    @works_count            = Work.all.count
17✔
27
    @ia_works_count         = IaWork.all.count
17✔
28
    @pages_count            = Page.all.count
17✔
29
    @transcribed_count      = Page.where.not(status: :new).count
17✔
30
    @notes_count            = Note.all.count
17✔
31
    @users_count            = User.all.count
17✔
32
    @owners_count           = User.where(owner: true).count
17✔
33

34
    @transcription_counts = {}
17✔
35
    @contribution_counts = {}
17✔
36
    @activity_project_counts = {}
17✔
37
    @unique_contributor_counts = {}
17✔
38
    @hours_spent_counts = {}
17✔
39
    @week_intervals=[1, 2, 4, 12, 26, 52, 104, 156, 208]
17✔
40
    @week_intervals.each do |weeks_ago|
17✔
41
      start_date = Date.yesterday - weeks_ago.weeks
153✔
42
      end_date = start_date + 1.week
153✔
43
      @transcription_counts[weeks_ago] = transcription_deeds.where('created_at between ? and ?', start_date, end_date).count
153✔
44
      @contribution_counts[weeks_ago] = contributor_deeds.where('created_at between ? and ?', start_date, end_date).count
153✔
45
      @activity_project_counts[weeks_ago] = contributor_deeds.where('created_at between ? and ?', start_date, end_date).distinct.count(:collection_id)
153✔
46
      @unique_contributor_counts[weeks_ago] = contributor_deeds.where('created_at between ? and ?', start_date, end_date).distinct.count(:user_id)
153✔
47
      minutes_spent = AhoyActivitySummary.where('date between ? and ?', start_date, end_date).sum(:minutes)
153✔
48
      @hours_spent_counts[weeks_ago] = minutes_spent.to_f / 60
153✔
49
    end
50

51
    @version = ActiveRecord::Migrator.current_version
17✔
52

53

54
=begin
55
    sql_online =
56
      'SELECT count(DISTINCT user_id) count '+
57
      'FROM interactions '+
58
      'WHERE created_on > date_sub(UTC_TIMESTAMP(), INTERVAL 20 MINUTE) '+
59
      'AND user_id IS NOT NULL'
60

61
    @users_count = Interaction.connection.select_value(sql_online)
62
=end
63
  end
64

65
  def user_list
1✔
66
    if params[:search]
10✔
67
      @users = User.search(params[:search]).order(created_at: :desc).paginate page: params[:page], per_page: PAGES_PER_SCREEN
3✔
68
    else
7✔
69
      @users = User.order(created_at: :desc).paginate page: params[:page], per_page: PAGES_PER_SCREEN
7✔
70
    end
71
  end
72

73
  def edit_user
1✔
74
  end
75

76
  def user_visits
1✔
77
    @visits = @user.visits.order(started_at: :desc).paginate page: params[:page], per_page: PAGES_PER_SCREEN
×
78
  end
79

80
  def visit_actions
1✔
81
    @visit = Visit.find(params[:visit_id])
×
82
    @actions = @visit.ahoy_events.order(time: :asc).paginate page: params[:page], per_page: 500
×
83
  end
84

85
  def visit_deeds
1✔
86
    @visit = Visit.find(params[:visit_id])
×
87
  end
88

89
  def update_user
1✔
90
    owner = @user.owner
1✔
91
    if @user.update(user_params)
1✔
92
      if owner == false && @user.owner == true
1!
93
        if SMTP_ENABLED
1!
94
          begin
1✔
95
            text = PageBlock.find_by(view: 'new_owner').html
1✔
96
            UserMailer.new_owner(@user, text).deliver!
1✔
97
          rescue StandardError => e
98
            log_smtp_error(e, current_user)
×
99
          end
100
        end
101
      end
102

103
      flash[:notice] = t('.user_profile_updated')
1✔
104
      if owner
1!
105
        ajax_redirect_to action: 'owner_list'
×
106
      else
1✔
107
        ajax_redirect_to action: 'user_list'
1✔
108
      end
109

110
    else
×
111
      render action: 'edit_user'
×
112
    end
113
  end
114

115
  def delete_user
1✔
116
    @user.soft_delete
1✔
117
    # @user.destroy
118
    flash[:notice] = t('.user_profile_deleted')
1✔
119
    redirect_to action: 'user_list'
1✔
120
  end
121

122
  def expunge_confirmation
1✔
123
  end
124

125
  def expunge_user
1✔
126
    @user.expunge
×
127
    flash[:notice] = t('.user_expunged', user: @user.display_name)
×
128
    if params[:flag_id]
×
129
      ajax_redirect_to action: 'revert_flag', flag_id: params[:flag_id]
×
130
    else
×
131
      ajax_redirect_to action: 'user_list'  # what if we came from the flag list?  TODO
×
132
    end
133
  end
134

135
  def flag_list
1✔
136
    @flags = Flag.where(status: Flag::Status::UNCONFIRMED).order(content_at: :desc).paginate page: params[:page], per_page: PAGES_PER_SCREEN
2✔
137
  end
138

139
  def revert_flag
1✔
140
    # find the flag
UNCOV
141
    flag = Flag.find(params[:flag_id])
×
142
    # revert the content
UNCOV
143
    flag.revert_content!
×
144
    # redirect to flag list at the appropriate page
UNCOV
145
    redirect_to action: 'flag_list', page: params[:page]
×
146
  end
147

148
  def ok_flag
1✔
149
    # find the flag
UNCOV
150
    flag = Flag.find(params[:flag_id])
×
151
    # revert the content
UNCOV
152
    flag.mark_ok!
×
153
    # redirect to flag list at the appropriate page
UNCOV
154
    redirect_to action: 'flag_list', page: params[:page]
×
155
  end
156

157
  def ok_user
1✔
158
    flag = Flag.find(params[:flag_id])
×
159
    flag.ok_user
×
UNCOV
160
    redirect_to action: 'flag_list', page: params[:page]
×
161
  end
162

163
  def tail_logfile
1✔
164
    @lines = params[:lines].to_i
1✔
165
    if @lines == 0
1!
166
      @lines=5000
1✔
167
    end
168
    development_logfile = "#{Rails.root}/log/development.log"
1✔
169
    production_logfile = "#{Rails.root}/log/production.log"
1✔
170
    @dev_tail = `tail -#{@lines} #{development_logfile}`
1✔
171
    @prod_tail = `tail -#{@lines} #{production_logfile}`
1✔
172
  end
173

174
  def autoflag
1✔
UNCOV
175
    flash[:notice] = t('.flag_message')
×
176

177
    cmd = 'rake fromthepage:flag_abuse &'
×
178
    logger.info(cmd)
×
UNCOV
179
    system(cmd)
×
180

UNCOV
181
    redirect_to action: 'flag_list', page: params[:page]
×
182
  end
183

184
  def uploads
1✔
185
    @document_uploads = DocumentUpload.order('id DESC').paginate page: params[:page], per_page: PAGES_PER_SCREEN
1✔
186
  end
187

188
  def delete_upload
1✔
189
    @document_upload = DocumentUpload.find(params[:id])
1✔
190
    @document_upload.destroy
1✔
191
    flash[:notice] = t('.uploaded_document_deleted')
1✔
192
    redirect_to action: 'uploads'
1✔
193
  end
194

195
  def process_upload
1✔
196
    @document_upload = DocumentUpload.find(params[:id])
×
197
    @document_upload.submit_process
×
198
    flash[:notice] = t('.uploaded_document_queued')
×
UNCOV
199
    redirect_to action: 'uploads'
×
200
  end
201

202
  def view_processing_log
1✔
203
    @document_upload = DocumentUpload.find(params[:id])
×
UNCOV
204
    render content_type: 'text/plain', plain: `cat #{@document_upload.log_file}`, layout: false
×
205
  end
206

207
  def collection_list
1✔
UNCOV
208
    @collections = Collection.order(:title)
×
209
  end
210

211
  def work_list
1✔
212
    @collections = Collection.order(:title).paginate(page: params[:page], per_page: 10)
×
UNCOV
213
    @works = Work.order(:title)
×
214
  end
215

216
  def article_list
1✔
UNCOV
217
    @collections = Collection.all
×
218
  end
219

220
  def page_list
1✔
UNCOV
221
    @pages = Page.order(:title).paginate(page: params[:page], per_page: PAGES_PER_SCREEN)
×
222
  end
223

224
  def settings
1✔
225
    @email_text = PageBlock.find_by(view: 'new_owner').html
3✔
226
    @flag_denylist = PageBlock.find_by(view: 'flag_denylist').html
3✔
227
    @flag_allowlist = (PageBlock.where(view: 'flag_allowlist').first ? PageBlock.where(view: 'flag_allowlist').first.html : '')
3!
228
    @email_denylist = (PageBlock.where(view: 'email_denylist').first ? PageBlock.where(view: 'email_denylist').first.html : '')
3✔
229
  end
230

231
  def update
1✔
232
    # need the original email text to update
233
    block = PageBlock.find_by(view: 'new_owner')
1✔
234
    if params[:admin][:welcome_text] != block.html
1!
235
      block.html = params[:admin][:welcome_text]
1✔
236
      block.save!
1✔
237
    end
238

239
    block = PageBlock.find_by(view: 'flag_denylist')
1✔
240
    if params[:admin][:flag_denylist] != block.html
1!
241
      block.html = params[:admin][:flag_denylist]
1✔
242
      block.save!
1✔
243
    end
244

245
    block = PageBlock.where(view: 'flag_allowlist').first || PageBlock.new(view: 'flag_allowlist', controller: 'admin')
1✔
246
    if params[:admin][:flag_allowlist] != block.html
1!
247
      block.html = params[:admin][:flag_allowlist]
1✔
248
      block.save!
1✔
249
    end
250

251
    block = PageBlock.where(view: 'email_denylist').first || PageBlock.new(view: 'email_denylist')
1✔
252
    if params[:admin][:email_denylist] != block.html
1!
253
      block.html = params[:admin][:email_denylist]
1✔
254
      block.save!
1✔
255
    end
256

257

258
    flash[:notice] = t('.admin_settings_updated')
1✔
259

260
    redirect_to action: 'settings'
1✔
261
  end
262

263
  def owner_list
1✔
264
    @collections = Collection.all
7✔
265
    # @owners = User.where(owner: true).order(paid_date: :desc).paginate(:page => params[:page], :per_page => PAGES_PER_SCREEN)
266
    if params[:search]
7!
UNCOV
267
      @owners = User.search(params[:search]).where(owner: true).order(paid_date: :desc).paginate(page: params[:page], per_page: PAGES_PER_SCREEN)
✔
268
    elsif params[:sort]
7✔
269
      sort = params[:sort]
3✔
270
      dir = params[:dir].upcase
3✔
271
      @owners = User.where(owner: true).order("#{sort} #{dir}").paginate(page: params[:page], per_page: PAGES_PER_SCREEN)
3✔
272
    else
4✔
273
      @owners = User.where(owner: true).order(created_at: :desc).paginate(page: params[:page], per_page: PAGES_PER_SCREEN)
4✔
274
    end
275
  end
276

277
  def downgrade
1✔
278
    u = User.find(params[:user_id])
1✔
279
    u.downgrade
1✔
280
    redirect_back fallback_location: { action: 'user_list' }, notice: t('.user_downgraded_successfully')
1✔
281
  end
282

283
  def moderation
1✔
UNCOV
284
    @collections = Collection.where(messageboards_enabled: true)
×
285
  end
286

287
  def searches
1✔
UNCOV
288
    searches = case params[:filter]
×
289
    when 'nonowner'
×
UNCOV
290
                 SearchAttempt.where(owner: false)
×
291
    when 'findaproject'
×
UNCOV
292
                 SearchAttempt.where(search_type: 'findaproject')
×
293
    when 'collectionwork'
×
UNCOV
294
                 SearchAttempt.where.not(search_type: 'findaproject')
×
295
    when 'collection'
×
UNCOV
296
                 SearchAttempt.where(search_type: 'collection')
×
297
    when 'collection-title'
×
UNCOV
298
                 SearchAttempt.where(search_type: 'collection-title')
×
299
    when 'work'
×
UNCOV
300
                 SearchAttempt.where(search_type: 'work')
×
301
    else
×
UNCOV
302
                 SearchAttempt.all
×
303
    end
304

UNCOV
305
    @searches = searches.order('id DESC').paginate(page: params[:page], per_page: PAGES_PER_SCREEN)
×
306

UNCOV
307
    this_week = SearchAttempt.where('created_at > ?', 1.week.ago)
×
308

309
    unless this_week.empty?
×
310
      by_visit = this_week.joins(:visit).group('visits.id')
×
311
      total_days = 7.0
×
312
      total_searches = this_week.count.nonzero? || 1
×
UNCOV
313
      total_visits = by_visit.size.nonzero? || 1
×
314

315
      @find_a_project_searches_per_day = (this_week.where(search_type: 'findaproject').count / total_days).round(2)
×
UNCOV
316
      @collection_work_searches_per_day = (this_week.where.not(search_type: 'findaproject').count / total_days).round(2)
×
317

UNCOV
318
      @find_a_project_average_hits = (this_week.where(search_type: 'findaproject').average(:hits) || 0).to_f
×
319
                                                                                                       .round(2)
UNCOV
320
      @collection_work_average_hits = (this_week.where.not(search_type: 'findaproject').average(:hits) || 0).to_f
×
321
                                                                                                            .round(2)
322

323
      @clickthrough_rate = ((this_week.where('clicks > 0').count.to_f / total_searches) * 100).round(1)
×
UNCOV
324
      @clickthrough_rate_visit = ((by_visit.sum(:clicks).values.count(&:positive?).to_f / total_visits) * 100).round(1)
×
325

326
      @contribution_rate = ((this_week.where('contributions > 0').count.to_f / total_searches) * 100).round(1)
×
UNCOV
327
      @contribution_rate_visit = ((by_visit.sum(:contributions).values.count(&:positive?).to_f / total_visits) * 100)
×
328
                                 .round(1)
329
    end
330

331
    start_d = params[:start_date]
×
332
    end_d = params[:end_date]
×
333
    max_date = 1.day.ago.end_of_day
×
334
    @start_date = start_d&.to_datetime&.beginning_of_day || 1.week.ago.beginning_of_day
×
335
    @end_date = end_d&.to_datetime&.end_of_day || max_date
×
UNCOV
336
    @end_date = max_date if max_date < @end_date
×
337
  end
338

339
  def delete_tag
1✔
340
    tag = Tag.find params[:tag_id]
×
UNCOV
341
    tag.destroy
×
342

UNCOV
343
    redirect_to action: 'tag_list'
×
344
  end
345

346
  def show_tag
1✔
347
    @tag = Tag.find params[:tag_id]
×
348
    @collections = @tag.collections.order(:title)
×
349
    @possible_duplicates = []
×
350
    clean_text = @tag.ai_text.gsub(/^\W*/, '').gsub(/\W*$/, '')
×
351
    Tag.where("regexp_replace(upper(ai_text), '[^A-Z0-9]', '') like regexp_replace(upper('%#{clean_text}%'), '[^A-Z0-9]', '')").each do |t|
×
UNCOV
352
      @possible_duplicates << t unless t == @tag
×
353
    end
354
  end
355

356
  def edit_tag
1✔
UNCOV
357
    @tag = Tag.find params[:tag_id]
×
358
  end
359

360
  def update_tag
1✔
361
    @tag = Tag.find params[:tag_id]
×
362
    @tag.update(tag_params)
×
UNCOV
363
    redirect_to action: 'tag_list'
×
364
  end
365

366
  def merge_tag
1✔
367
    target_tag = Tag.find params[:target_tag_id]
×
UNCOV
368
    source_tag = Tag.find params[:source_tag_id]
×
369

370
    target_tag.collections << source_tag.collections
×
UNCOV
371
    target_tag.save
×
372

UNCOV
373
    source_tag.destroy!
×
374

UNCOV
375
    flash[:notice] = t('.tags_merged', target_tag: target_tag.ai_text, source_tag: source_tag.ai_text)
×
376

UNCOV
377
    redirect_to admin_tags_show_path(target_tag.id)
×
378
  end
379

380
  def tag_list
1✔
381
    @tag_to_count_map = Tag.joins(:collections_tags).group(:id).count
×
UNCOV
382
    @tags = Tag.all.order(:canonical, :ai_text)
×
383
  end
384

385
  private
1✔
386

387
  def tag_params
1✔
388
    params.require(:tag).permit(:ai_text, :canonical, :tag_type)
×
389
  end
390

391

392
  def user_params
1✔
393
    params.require(:user).permit(:real_name, :login, :email, :account_type, :start_date, :paid_date, :user, :owner)
1✔
394
  end
395
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc