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

benwbrum / fromthepage / 18534844741

15 Oct 2025 03:53PM UTC coverage: 58.252% (-5.3%) from 63.509%
18534844741

push

github

web-flow
Fixes #4973 by treating blank lines in spreadsheet cells as double-lb (#4977)

* Fixes #4973 by treating blank lines in spreadsheet cells as double-lb

* Fix hash style

* 4976 - Fix broken specs

---------

Co-authored-by: WillNigel23 <wcjdesus@up.edu.ph>

1907 of 3913 branches covered (48.73%)

Branch coverage included in aggregate %.

6 of 6 new or added lines in 1 file covered. (100.0%)

73 existing lines in 3 files now uncovered.

8473 of 13906 relevant lines covered (60.93%)

191.32 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
2✔
2
  include ErrorHelper
2✔
3

4
  before_action :authorized?
2✔
5

6
  PAGES_PER_SCREEN = 20
2✔
7

8
  def authorized?
2✔
9
    unless user_signed_in? && current_user.admin
106✔
10
      redirect_to main_app.dashboard_path
6✔
11
    end
12
  end
13

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

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

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

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

51
    @version = ActiveRecord::Migrator.current_version
34✔
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
2✔
66
    if params[:search]
20✔
67
      @users = User.search(params[:search]).order(created_at: :desc).paginate page: params[:page], per_page: PAGES_PER_SCREEN
6✔
68
    else
14✔
69
      @users = User.order(created_at: :desc).paginate page: params[:page], per_page: PAGES_PER_SCREEN
14✔
70
    end
71
  end
72

73
  def edit_user
2✔
74
  end
75

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

80
  def visit_actions
2✔
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
2✔
86
    @visit = Visit.find(params[:visit_id])
×
87
  end
88

89
  def update_user
2✔
90
    owner = @user.owner
2✔
91
    if @user.update(user_params)
2✔
92
      if owner == false && @user.owner == true
2!
93
        if SMTP_ENABLED
2!
94
          begin
2✔
95
            text = PageBlock.find_by(view: 'new_owner').html
2✔
96
            UserMailer.new_owner(@user, text).deliver!
2✔
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')
2✔
104
      if owner
2!
105
        ajax_redirect_to action: 'owner_list'
×
106
      else
2✔
107
        ajax_redirect_to action: 'user_list'
2✔
108
      end
109

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

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

122
  def expunge_confirmation
2✔
123
  end
124

125
  def expunge_user
2✔
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

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

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

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

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

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

175
  def autoflag
2✔
176
    flash[:notice] = t('.flag_message')
×
177

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

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

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

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

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

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

208
  def collection_list
2✔
209
    @collections = Collection.order(:title)
×
210
  end
211

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

217
  def article_list
2✔
218
    @collections = Collection.all
×
219
  end
220

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

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

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

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

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

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

258

259
    flash[:notice] = t('.admin_settings_updated')
2✔
260

261
    redirect_to action: 'settings'
2✔
262
  end
263

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

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

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

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

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

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

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

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

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

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

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

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

340
  def delete_tag
2✔
341
    tag = Tag.find params[:tag_id]
×
342
    tag.destroy
×
343

344
    redirect_to action: 'tag_list'
×
345
  end
346

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

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

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

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

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

UNCOV
374
    source_tag.destroy!
×
375

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

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

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

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

391

392
  def user_params
2✔
393
    params.require(:user).permit(:real_name, :login, :email, :account_type, :start_date, :paid_date, :user, :owner)
2✔
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