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

benwbrum / fromthepage / 13528664866

25 Feb 2025 06:48PM UTC coverage: 59.626% (-2.2%) from 61.822%
13528664866

Pull #4471

github

web-flow
Merge 279229e31 into 3b7156fe3
Pull Request #4471: Search test

1548 of 3188 branches covered (48.56%)

Branch coverage included in aggregate %.

84 of 457 new or added lines in 18 files covered. (18.38%)

1 existing line in 1 file now uncovered.

7065 of 11257 relevant lines covered (62.76%)

78.7 hits per line

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

62.68
/app/controllers/dashboard_controller.rb
1
# frozen_string_literal: true
2
class DashboardController < ApplicationController
1✔
3

4
  include AddWorkHelper
1✔
5
  include DashboardHelper
1✔
6
  include ElasticSearchable
1✔
7
  include OwnerExporter
1✔
8
  PAGES_PER_SCREEN = 20
1✔
9

10
  before_action :authorized?,
1✔
11
    only: [:owner, :staging, :startproject, :summary]
12

13
  before_action :get_data,
1✔
14
    only: [:owner, :staging, :upload, :new_upload,
15
           :startproject, :empty_work, :create_work, :summary, :exports]
16

17
  before_action :remove_col_id
1✔
18

19
  def dashboard_role
1✔
20
    if user_signed_in?
12✔
21
      if current_user.owner
11✔
22
        redirect_to dashboard_owner_path
7✔
23
      elsif current_user.guest?
4✔
24
        redirect_to guest_dashboard_path
1✔
25
      else
3✔
26
        redirect_to dashboard_watchlist_path
3✔
27
      end
28
    else
1✔
29
      redirect_to guest_dashboard_path
1✔
30
    end
31
  end
32

33
  def index
1✔
34
    if Collection.all.count > 1000
22✔
35
      redirect_to landing_page_path
1✔
36
    else
21✔
37
      redirect_to collections_list_path
21✔
38
    end
39
  end
40

41
  def collections_list(private_only: false)
1✔
42
    if private_only
47✔
43
      cds = []
24✔
44
    else
23✔
45
      public_collections   = Collection.unrestricted.includes(:owner, next_untranscribed_page: :work)
23✔
46
      public_document_sets = DocumentSet.unrestricted.includes(:owner, next_untranscribed_page: :work)
23✔
47

48
      cds = public_collections + public_document_sets
23✔
49
    end
50

51
    if user_signed_in?
47✔
52
      cds |= current_user.all_owner_collections.restricted.includes(:owner, next_untranscribed_page: :work)
45✔
53
      cds |= current_user.document_sets.restricted.includes(:owner, next_untranscribed_page: :work)
45✔
54

55
      cds |= current_user.collection_collaborations.includes(:owner, next_untranscribed_page: :work)
45✔
56
      cds |= current_user.document_set_collaborations.includes(:owner, next_untranscribed_page: :work)
45✔
57
    end
58

59
    @collections_and_document_sets = cds.sort { |a, b| a.slug <=> b.slug }
577✔
60
  end
61

62
  # Owner Dashboard - start project
63
  # other methods in AddWorkHelper
64
  def startproject
1✔
65
    @work = Work.new
31✔
66
    @work.collection = @collection
31✔
67
    @document_upload = DocumentUpload.new
31✔
68
    @document_upload.collection = @collection
31✔
69
    @sc_collections = ScCollection.all
31✔
70
  end
71

72
  def your_hours
1✔
73
    unless user_signed_in?
3✔
74
      redirect_to landing_page_path
1✔
75
      return
1✔
76
    end
77

78
    load_user_hours_data
2✔
79

80
    return unless @start_date_hours > @end_date_hours
2✔
81

82
    flash[:error] = 'Invalid date range. Please make sure the end date is greater than the start date.'
1✔
83
    redirect_to dashboard_your_hours_path
1✔
84
  end
85

86
  def download_hours_letter
1✔
87
    load_user_hours_data
×
88
    @time_duration=params[:time_duration]
×
89
    markdown_text = generate_markdown_text
×
90

91
    # write the string to a temp directory
92
    temp_dir = File.join(Rails.root, 'public', 'printable')
×
93
    Dir.mkdir(temp_dir) unless Dir.exist? temp_dir
×
94

95
    time_stub = Time.now.gmtime.iso8601.gsub(/\D/,'')
×
96
    temp_dir = File.join(temp_dir, time_stub)
×
97
    Dir.mkdir(temp_dir) unless Dir.exist? temp_dir
×
98

99
    file_stub = "letter_#{time_stub}"
×
100
    md_file = File.join(temp_dir, "#{file_stub}.md")
×
101
    output_file = File.join(temp_dir, "#{file_stub}.pdf")
×
102

103
    generate_pdf(md_file, output_file, markdown_text)
×
104

105
    send_generated_pdf(output_file)
×
106
  end
107

108
  # Owner Dashboard - list of works
109
  def owner
1✔
110
    collections = current_user.all_owner_collections
69✔
111
    @active_collections = @collections.select { |c| c.active? }
219✔
112
    @inactive_collections = @collections.select { |c| !c.active? }
219✔
113
    # Needs to be active collections first, then inactive collections
114
    @collections = @active_collections + @inactive_collections
69✔
115
  end
116

117
  # Owner Summary Statistics - statistics for all owned collections
118
  def summary
1✔
119
    start_d = params[:start_date]
5✔
120
    end_d = params[:end_date]
5✔
121

122
    max_date = 1.day.ago
5✔
123

124
    # Give a week fo data if there are no dates
125
    @start_date = start_d&.to_datetime&.beginning_of_day || 1.week.ago.beginning_of_day
5✔
126
    @end_date = end_d&.to_datetime&.end_of_day || max_date
5✔
127
    @end_date = max_date if max_date < @end_date
5!
128

129
    @statistics_object = current_user
5✔
130
    @subjects_disabled = @statistics_object.collections.all?(&:subjects_disabled)
5✔
131

132
    # Stats
133
    owner_collections = current_user.all_owner_collections.map{ |c| c.id }
12✔
134
    contributor_ids_for_dates = AhoyActivitySummary
5✔
135
        .where(collection_id: owner_collections)
136
        .where('date BETWEEN ? AND ?', @start_date, @end_date).distinct.pluck(:user_id)
137

138
    @contributors = User.where(id: contributor_ids_for_dates).order(:display_name)
5✔
139

140
    @activity = AhoyActivitySummary
5✔
141
        .where(collection_id: owner_collections)
142
        .where('date BETWEEN ? AND ?', @start_date, @end_date)
143
        .group(:user_id)
144
        .sum(:minutes)
145
  end
146

147
  # Collaborator Dashboard - watchlist
148
  def watchlist
1✔
149
    works = Work.joins(:deeds).where(deeds: { user_id: current_user.id }).distinct
24✔
150
    recent_collections = Collection.joins(:deeds).where(deeds: { user_id: current_user.id }).where('deeds.created_at > ?', Time.now-2.days).distinct.order_by_recent_activity.limit(5)
24✔
151
    collections = Collection.where(id: current_user.ahoy_activity_summaries.pluck(:collection_id)).distinct.order_by_recent_activity.limit(5)
24✔
152
    document_sets = DocumentSet.joins(works: :deeds).where(works: { id: works.ids }).order('deeds.created_at DESC').distinct.limit(5)
24✔
153
    collections_list(private_only: true) # assigns @collections_and_document_sets for private collections only
24✔
154
    @collections = (collections + recent_collections + document_sets)
24✔
155
               .uniq
156
               .sort_by do |collection|
157
                 if collection.is_a?(Collection)
36✔
158
                   collection.created_on
27✔
159
                 elsif collection.is_a?(DocumentSet)
9!
160
                   collection.created_at
9✔
161
                 end
162
               end
163
               .reverse
164
               .take(10)
165
  end
166

167
  def exports
1✔
168
    @bulk_exports = current_user.bulk_exports.order('id DESC').paginate :page => params[:page], :per_page => PAGES_PER_SCREEN
2✔
169
  end
170

171
  # Collaborator Dashboard - activity
172
  def editor
1✔
173
    @user = current_user
×
174
  end
175

176
  # Guest Dashboard - activity
177
  def guest
1✔
178
    @collections = Collection.order_by_recent_activity.unrestricted.distinct.limit(5)
1✔
179
  end
180

181
  def landing_page
1✔
182
    if ELASTIC_ENABLED
6!
NEW
183
      search_page = (params[:page] || 1).to_i
×
184

NEW
185
      page_size = 10
×
NEW
186
      @breadcrumb_scope={site: true}
×
187

NEW
188
      query_config = {}
×
NEW
189
      if params[:org]
×
NEW
190
        org_user = User.find_by(slug: params[:org])
×
191

NEW
192
        if org_user.present?
×
193
          query_config = {
NEW
194
            type: 'org',
×
195
            org_id: org_user[:id]
196
          }
NEW
197
          @org_filter = org_user
×
198
        end
×
NEW
199
      elsif params[:mode] and params[:slug]
×
NEW
200
        if params[:mode] == 'collection'
×
NEW
201
          coll = Collection.find_by(slug: params[:slug])
×
202

NEW
203
          if coll.present?
×
204
            query_config = {
NEW
205
              type: 'collection',
×
206
              coll_id: coll[:id]
207
            }
NEW
208
            @collection_filter = coll
×
209
          end
×
NEW
210
        elsif params[:mode] == 'docset'
×
NEW
211
          docset = DocumentSet.find_by(slug: params[:slug])
×
212

NEW
213
          if docset.present?
×
214
            query_config = {
NEW
215
              type: 'docset',
×
216
              docset_id: docset[:id]
217
            }
NEW
218
            @docset_filter = docset
×
219
          end
×
NEW
220
        elsif params[:mode] == 'work'
×
NEW
221
          work = Work.find_by(slug: params[:slug])
×
222

NEW
223
          if work.present?
×
224
            query_config = {
NEW
225
              type: 'work',
×
226
              work_id: work[:id]
227
            }
NEW
228
            @work_filter = work;
×
229
          end
230
        end
231
      end
232

NEW
233
      search_data = elastic_search_results(
×
234
        params[:search],
235
        search_page,
236
        page_size,
237
        params[:filter],
238
        query_config
239
      )
240

NEW
241
      if search_data
×
NEW
242
        inflated_results = search_data[:inflated]
×
NEW
243
        @full_count = search_data[:full_count] # Used by All tab
×
NEW
244
        @type_counts = search_data[:type_counts]
×
245

246
        # Used for pagination, currently capped at 10k
247
        #
248
        # TODO: ES requires a scroll/search_after query for result sets larger
249
        #       than 10k.
250
        #
251
        #       To setup support we just need to add a composite tiebreaker field
252
        #       to the schemas
NEW
253
        @filtered_count = [ 10000, search_data[:filtered_count] ].min
×
254

255
        # Inspired by display controller search
NEW
256
        @search_string = "\"#{params[:search] || ""}\""
×
257

NEW
258
        @search_results = WillPaginate::Collection.create(
×
259
          search_page,
260
          page_size,
261
          @filtered_count) do |pager|
NEW
262
            pager.replace(inflated_results)
×
263
          end
264
      end
265
    else
6✔
266
      if params[:search]
6✔
267
        @search_results = search_results(params[:search])&.paginate(page: params[:page], per_page: PAGES_PER_SCREEN)
3!
268
        @search_results_map = {}
3✔
269
        @search_results.each do |result|
3✔
270
          @search_results_map[result.owner_user_id] ||= []
2✔
271
          @search_results_map[result.owner_user_id] << result
2✔
272
        end
273
      end
274
    end
275
    users = User.owners
6✔
276
      .joins(:collections)
277
      .left_outer_joins(:document_sets)
278

279
    org_owners = users.findaproject_orgs.with_owner_works
6✔
280
    individual_owners = users.findaproject_individuals.with_owner_works
6✔
281
    @owners = users.where(id: org_owners.select(:id)).or(users.where(id: individual_owners.select(:id))).distinct
6✔
282
           .order(Arel.sql("COALESCE(NULLIF(display_name, ''), login) ASC"))
283
    @collections = Collection.where(owner_user_id: users.select(:id)).unrestricted
6✔
284

285
    @new_projects = Collection.includes(:owner)
6✔
286
                      .order('created_on DESC')
287
                      .unrestricted.where("LOWER(title) NOT LIKE 'test%'")
288
                      .distinct
289
                      .limit(10)
290

291
    @new_document_sets = DocumentSet.includes(:owner)
6✔
292
                            .order('created_at DESC')
293
                            .unrestricted.where("LOWER(title) NOT LIKE 'test%'")
294
                            .distinct
295
                            .limit(10)
296
    respond_to do |format|
6✔
297
      format.html do
6✔
298
        @new_projects = (@new_projects + @new_document_sets).sort do |a, b|
3✔
299
          a_date = a.is_a?(Collection) ? a.created_on : a.created_at
29!
300
          b_date = b.is_a?(Collection) ? b.created_on : b.created_at
29✔
301
          b_date <=> a_date
29✔
302
        end
303

304
        @tag_map = Tag.featured_tags.group(:ai_text).count
3✔
305
      end
306

307
      format.turbo_stream
6✔
308
    end
309
                  
310
  end
311

312
  def browse_tag
1✔
313
    @tag = Tag.find_by(ai_text: params[:ai_text])
1✔
314
    tag_collections = @tag.collections
1✔
315
    collections = tag_collections.includes(:owner, { works: :work_statistic })
1✔
316
                                 .unrestricted
317
                                 .has_intro_block
318
                                 .has_picture
319
                                 .not_empty
320

321
    document_sets = DocumentSet.includes(:owner, :collection, { works: :work_statistic })
1✔
322
                               .where(collection: { id: tag_collections.select(:id) })
323
                               .unrestricted
324
                               .has_intro_block
325
                               .not_empty
326

327
    @collections = collections + document_sets
1✔
328
  end
329

330
  def collaborator_time_export
1✔
331
    start_date = params[:start_date]
×
332
    end_date = params[:end_date]
×
333

334
    start_date = start_date.to_date
×
335
    end_date = end_date.to_date
×
336

337
    dates = (start_date..end_date)
×
338

339
    headers = [
×
340
      "Username",
341
      "Email",
342
    ]
343

344
    headers += dates.map{|d| d.strftime("%b %d, %Y")}
×
345

346
    # Get Row Data (Users)
347
    owner_collections = current_user.all_owner_collections.map{ |c| c.id }
×
348

349
    contributor_ids_for_dates = AhoyActivitySummary
×
350
      .where(collection_id: owner_collections)
351
      .where('date BETWEEN ? AND ?', start_date, end_date).distinct.pluck(:user_id)
352

353
    contributors = User.where(id: contributor_ids_for_dates).order(:display_name)
×
354

355
    csv = CSV.generate(:headers => true) do |records|
×
356
      records << headers
×
357
      contributors.each do |user|
×
358
        row = [user.display_name, user.email]
×
359

360
        activity = AhoyActivitySummary
×
361
          .where(user_id: user.id)
362
          .where(collection_id: owner_collections)
363
          .where('date BETWEEN ? AND ?', start_date, end_date)
364
          .group(:date)
365
          .sum(:minutes)
366
          .transform_keys{ |k| k.to_date }
×
367

368
        user_activity = dates.map{ |d| activity[d.to_date] || 0 }
×
369

370
        row += user_activity
×
371

372
        records << row
×
373
      end
374
    end
375

376
    send_data( csv,
×
377
              :filename => "#{start_date.strftime('%Y-%m%b-%d')}-#{end_date.strftime('%Y-%m%b-%d')}_activity_summary.csv",
378
              :type => "application/csv")
379
  end
380

381
  private
1✔
382

383
  def authorized?
1✔
384
    return if user_signed_in? && current_user.owner
109✔
385

386
    redirect_to dashboard_path
4✔
387
  end
388

389
  def document_upload_params
1✔
390
    params.require(:document_upload).permit(:document_upload, :file, :preserve_titles, :ocr, :collection_id)
4✔
391
  end
392

393
  def load_user_hours_data
1✔
394
    if params['start_date'].present? && params['end_date'].present?
2✔
395
      @start_date_hours = Date.parse(params['start_date'])
1✔
396
      @end_date_hours = Date.parse(params['end_date'])
1✔
397
    else
1✔
398
      @start_date_hours = 7.days.ago.to_date
1✔
399
      @end_date_hours = Date.today
1✔
400
    end
401
    @time_duration = time_spent_in_date_range(current_user.id, @start_date_hours, @end_date_hours)
2✔
402

403
    raw = Deed.where(user_id: current_user.id, created_at: [@start_date_hours..@end_date_hours]).pluck(:collection_id, :page_id).uniq
2✔
404
    @collection_id_to_page_count = raw.select { |collection_id, page_id| !page_id.nil? }.map{ |collection_id, page_id| collection_id }.tally
2✔
405
    @user_collections = Collection.find(@collection_id_to_page_count.keys).sort{|a,b| a.owner.display_name <=> b.owner.display_name}
2✔
406
  end
407

408
  def generate_markdown_text
1✔
409
    <<~MARKDOWN
×
410
      ![](app/assets/images/logo.png){width=300px style='display: block; margin-left: 300px auto;'}
411
      &nbsp; &nbsp;
412
      \n#{generated_format_date(Time.now.to_date)}\n
413
      &nbsp; &nbsp;
414
      \n#{I18n.t('dashboard.hours_letter.to_whom_it_may_concern')}\n
415
      #{I18n.t('dashboard.hours_letter.certification_text', user_name: current_user.real_name, time_duration: @time_duration, start_date: generated_format_date(@start_date_hours), end_date: generated_format_date(@end_date_hours))}\n
416
      #{I18n.t('dashboard.hours_letter.worked_on_collections', user_name: current_user.real_name)}\n
417
      #{I18n.t('dashboard.hours_letter.institutions_header')}
418
      #{I18n.t('dashboard.hours_letter.institutions_separator')}
419
      #{generate_collection_rows(@user_collections)}
420
      #{I18n.t('dashboard.hours_letter.volunteer_text', user_display_name: current_user.display_name)}\n
421
      |
422
      #{I18n.t('dashboard.hours_letter.regards_text')}\n
423
      |
424
      | Sara Brumfield
425
      | Partner, FromThePage
426
    MARKDOWN
427
  end
428

429
  def generate_collection_rows(user_collections)
1✔
430
    user_collections.map do |collection|
×
431
      "| #{collection.owner.display_name} | #{collection.title} | #{@collection_id_to_page_count[collection.id]} |"
×
432
    end.join("\n")
433
  end
434

435
  def generated_format_date(date)
1✔
436
    formatted_date = date.strftime("%B %d, %Y")
×
437
  end
438

439
  def generate_pdf(input_path, output_path, markdown_text)
1✔
440
    File.write(input_path, markdown_text)
×
441

442
    system("pandoc #{input_path} -s --pdf-engine=xelatex -o #{output_path}")
×
443
  end
444

445
  def send_generated_pdf(output_path)
1✔
446
    # spew the output to the browser
447
    send_data(File.read(output_path),
×
448
      filename: File.basename("letter.pdf"),
449
      :content_type => "application/pdf")
450
    cookies['download_finished'] = 'true'
×
451
  end
452

453
  def search_results(search_key)
1✔
454
    return nil if search_key.nil?
3!
455

456
    collections_query = Collection.search(search_key).unrestricted.includes(:owner)
3✔
457
    document_sets_query = DocumentSet.search(search_key).unrestricted.includes(:owner)
3✔
458

459
    collections_query + document_sets_query
3✔
460
  end
461

462

463
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