Class: LeadsController

Inherits:
ApplicationController show all
Includes:
DataControllerConfiguration::ProjectDataControllerConfiguration
Defined in:
app/controllers/leads_controller.rb

Constant Summary

Constants included from ProjectsHelper

ProjectsHelper::CLASSIFIER, ProjectsHelper::CLASSIFIER_ANNOTATION

Instance Method Summary collapse

Methods included from DataControllerConfiguration::ProjectDataControllerConfiguration

#annotator_params

Methods inherited from ApplicationController

#set_time_zone

Methods included from RedirectHelper

#destroy_redirect

Methods included from RequestType

#json_request?

Methods included from LogRecent

#log_user_recent_route

Methods included from Cookies

#digest_cookie, #digested_cookie_exists?

Methods included from Whitelist

#whitelist_constantize

Methods included from ProjectsHelper

#cumulative_gb_per_year, #cumulative_projects_created_per_year, #document_cumulative_gb_per_year, #document_gb_per_year, #gb_per_year, #image_cumulative_gb_per_year, #image_gb_per_year, #invalid_object, #project_classification, #project_initials, #project_link, #project_login_link, #project_matches, #project_tag, #projects_list, #projects_search_form, #sound_cumulative_gb_per_year, #sound_gb_per_year, #taxonworks_classification, #week_in_review_graphs

Methods included from Api::Intercept

#intercept_api

Methods included from TokenAuthentication

#intercept_project, #intercept_user, #intercept_user_or_project, #project_token_authenticate, #token_authenticate

Instance Method Details

#add_children ⇒ Object

POST /leads/1/add_children.json



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'app/controllers/leads_controller.rb', line 158

def add_children
  num_to_add = params[:num_to_add]
  if num_to_add < 1 || num_to_add > 2
    @lead.errors.add(:add_children,
      "request must be for 1 or 2, was '#{num_to_add}'"
    )
    render json: @lead.errors, status: :unprocessable_content
    return
  end

  # currently infers num_to_add
  @lead.add_children(sessions_current_project_id, sessions_current_user_id)
  expand_lead
  render action: :show, status: :created, location: @lead
end

#api_eliminated_otus ⇒ Object



394
395
396
397
398
399
400
401
# File 'app/controllers/leads_controller.rb', line 394

def api_eliminated_otus
  lead = Lead.find(params[:id])

  @otus = lead.eliminated_otus

  render '/leads/api/v1/otus'

end

#api_index ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'app/controllers/leads_controller.rb', line 52

def api_index
  # Default excludes virtual (simple) keys so existing API consumers keep
  # the shape they expect. Callers that want simple keys pass is_virtual=true;
  # is_virtual=all returns both.
  is_virtual =
    if params[:is_virtual].to_s == 'all'
      :all
    elsif params[:is_virtual].present?
      ActiveRecord::Type::Boolean.new.cast(params[:is_virtual])
    else
      false
    end

  @leads = Lead
    .roots_with_data(sessions_current_project_id, true, is_virtual:)
    .where(is_public: true)
    .page(params[:page]).per(params[:per])

  render '/leads/api/v1/index'
end

#api_key ⇒ Object



428
429
430
431
# File 'app/controllers/leads_controller.rb', line 428

def api_key
  @lead = Lead.where(project_id: sessions_current_project_id, is_public: true).find(params.require(:id))
  render '/leads/api/v1/key'
end

#api_remaining_otus ⇒ Object



385
386
387
388
389
390
391
392
# File 'app/controllers/leads_controller.rb', line 385

def api_remaining_otus
  lead = Lead.find(params[:id])

  @otus = lead.remaining_otus

  render '/leads/api/v1/otus'

end

#autocomplete ⇒ Object



403
404
405
406
407
408
# File 'app/controllers/leads_controller.rb', line 403

def autocomplete
  @leads = ::Queries::Lead::Autocomplete.new(
    params.require(:term),
    project_id: sessions_current_project_id,
  ).autocomplete
end

#batch_create_lead_items ⇒ Object

Creates a new key populated with otus from params.



434
435
436
437
438
439
440
441
442
443
444
445
# File 'app/controllers/leads_controller.rb', line 434

def batch_create_lead_items
  @lead = Lead.create!(
    params.require(:lead).permit(:text, :observation_matrix_id)
  )
  @lead.batch_populate_lead_items(params[:otu_query],
    sessions_current_project_id, sessions_current_user_id
  )

  @lead.add_children(sessions_current_project_id, sessions_current_user_id)

  render json: @lead
end

#cite_key_bootstrap ⇒ Object

GET /leads/cite_key_bootstrap.json Accepts either an explicit otu_ids[] list or a nested otu_query (a Queries::Otu::Filter param hash). Returns the MRCA OTU (parent taxon) for the resulting OTU set and the embedded OTU details so the cite_key task can prefill Step 1.



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'app/controllers/leads_controller.rb', line 78

def cite_key_bootstrap
  if params[:otu_ids].present?
    otu_ids = Array(params[:otu_ids]).flatten.compact.map(&:to_i).uniq
    scope = ::Otu.where(project_id: sessions_current_project_id, id: otu_ids)
  elsif params[:otu_query].present?
    scope = ::Queries::Otu::Filter.new(params[:otu_query]).all
      .where(project_id: sessions_current_project_id)
  else
    scope = ::Otu.none
  end

  cap = 2000
  total = scope.count
  otus = scope.includes(:taxon_name).limit(cap).to_a
  taxon_name_ids = otus.map(&:taxon_name_id).compact.uniq

  mrca_taxon = ::TaxonName.mrca(taxon_name_ids)
  parent_otu = mrca_taxon &&
    ::Otu.where(project_id: sessions_current_project_id, taxon_name_id: mrca_taxon.id).first

  render json: {
    parent_otu: parent_otu && otu_payload(parent_otu),
    otus: otus.map { |o| otu_payload(o) },
    total: total,
    truncated: total > cap
  }
end

#create ⇒ Object

POST /leads POST /leads.json



144
145
146
147
148
149
150
151
152
153
154
155
# File 'app/controllers/leads_controller.rb', line 144

def create
  @lead = Lead.new(lead_params)
  respond_to do |format|
    if @lead.save
      new_couplet unless @lead.is_virtual
      expand_lead
      format.json { render action: :show, status: :created, location: @lead }
    else
      format.json { render json: @lead.errors, status: :unprocessable_content}
    end
  end
end

#delete_children ⇒ Object

For deleting all children at a particular level of the key where at most one of the children has its own children; the grandchildren, if any, are reparented.



259
260
261
262
263
264
265
266
267
268
269
270
# File 'app/controllers/leads_controller.rb', line 259

def delete_children
  respond_to do |format|
    if @lead.destroy_children
      format.json { head :no_content }
    else
      @lead.errors.add(:delete, 'failed!')
      format.json {
        render json: @lead.errors, status: :unprocessable_content
      }
    end
  end
end

#depictions ⇒ Object

POST /leads/:id/depictions.json



323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'app/controllers/leads_controller.rb', line 323

def depictions
  image_ids = params.permit(image_ids: [])[:image_ids] || []
  created = 0

  render json: { success: true, created: } if image_ids.empty?

  Depiction.transaction do
    image_ids.each do |image_id|
      depiction = Depiction.find_or_initialize_by(
        depiction_object: @lead,
        image_id: image_id
      )
      next unless depiction.new_record?

      unless depiction.save
        render json: depiction.errors, status: :unprocessable_content
        raise ActiveRecord::Rollback
      end
      created += 1
    end
  end

  render json: { success: true, created: created }
end

#destroy ⇒ Object

DELETE /leads/1 DELETE /leads/1.json



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'app/controllers/leads_controller.rb', line 194

def destroy
  if @lead.parent_id
    respond_to do |format|
      flash[:error] = 'Delete aborted - you can only delete on root nodes.'
      format.html { redirect_back(fallback_location: (request.referer || root_path)) }
      format.json { head :no_content, status: :unprocessable_content }
    end
    return
  end

  begin
    @lead.transaction_nuke
    respond_to do |format|
      flash[:notice] = 'Key was successfully destroyed.'
      format.html { destroy_redirect @lead }
      format.json { head :no_content }
    end
  rescue ActiveRecord::RecordInvalid
    respond_to do |format|
      flash[:error] = 'Delete failed!'
      format.html { redirect_back(fallback_location: (request.referer || root_path)) }
      format.json { render json: @lead.errors, status: :unprocessable_content }
    end
  end
end

#destroy_children ⇒ Object

For destroying all children at a particular level of the key where none of the children have their own children.



242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'app/controllers/leads_controller.rb', line 242

def destroy_children
  if @lead.parent_id.present?
    if @lead.destroy_children
      head :no_content
    else
      @lead.errors.add(:delete, 'failed!')
      render json: @lead.errors, status: :unprocessable_content
    end
  else
    @lead.errors.add(:destroy, "failed - can't delete the only couplet.")
    render json: @lead.errors, status: :unprocessable_content
  end
end

#destroy_simple_lead ⇒ Object

DELETE /leads/1/destroy_simple_lead.json Simple (virtual) keys are flat by construction and are managed by the cite_key task. This action lets that task destroy either a species-child leaf or the entire simple key from the same endpoint. It refuses anything that isn't virtual so it can't be repurposed to remove a lead from an ordinary dichotomous couplet (which #destroy still guards against).



278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'app/controllers/leads_controller.rb', line 278

def destroy_simple_lead
  unless @lead.is_virtual
    render json: { errors: { base: ['Only virtual (simple key) leads can be destroyed here.'] } },
      status: :unprocessable_content
    return
  end

  begin
    @lead.destroy!
    head :no_content
  rescue ActiveRecord::RecordNotDestroyed, ActiveRecord::RecordInvalid
    render json: @lead.errors, status: :unprocessable_content
  end
end

#destroy_subtree ⇒ Object

A separate action from destroy to be called in different contexts: this one can be called on any lead, not just root. POST /leads/1/destroy_subtree.json



223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'app/controllers/leads_controller.rb', line 223

def destroy_subtree
  parent = @lead.parent
  begin
    Lead.destroy_lead_and_descendants(
      @lead, sessions_current_project_id, sessions_current_user_id
    )
  rescue TaxonWorks::Error => e
    parent.errors.add(:error, e)
    render json: parent.errors, status: :unprocessable_content
    return
  end

  @lead = parent.reload
  expand_lead
  render :show, status: :ok, location: @lead
end

#download ⇒ Object

GET /leads/download



420
421
422
423
424
425
426
# File 'app/controllers/leads_controller.rb', line 420

def download
  send_data Export::CSV.generate_csv(
      Lead.where(project_id: sessions_current_project_id)
    ),
    type: 'text',
    filename: "leads_#{DateTime.now}.tsv"
end

#duplicate ⇒ Object

POST /leads/1/duplicate.html



294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'app/controllers/leads_controller.rb', line 294

def duplicate
  respond_to do |format|
    format.html {
      if !@lead.parent_id
        if @lead.dupe
          flash[:notice] = 'Key cloned.'
        else
          flash[:error] = @lead.errors.full_messages.join('; ')
        end
      else
        flash[:error] = 'Clone aborted - you can only clone on a root node.'
      end

      redirect_to action: :list
    }
  end
end

#edit ⇒ Object

GET /leads/1/edit



134
135
136
137
138
139
140
# File 'app/controllers/leads_controller.rb', line 134

def edit
  if @lead.is_virtual
    redirect_to cite_key_task_path(lead_id: @lead.id)
  else
    redirect_to new_lead_task_path(lead_id: @lead.id)
  end
end

#eliminated_otus ⇒ Object



376
377
378
379
380
381
382
383
# File 'app/controllers/leads_controller.rb', line 376

def eliminated_otus
  lead = Lead.find(params[:id])

  @otus = lead.eliminated_otus

  render '/leads/otus'

end

#expand_lead ⇒ Object (private)



498
499
500
501
# File 'app/controllers/leads_controller.rb', line 498

def expand_lead
  @children = @lead.children
  @lead_item_otus = @lead.apportioned_lead_item_otus
end

#index ⇒ Object

GET /leads GET /leads.json



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'app/controllers/leads_controller.rb', line 13

def index
  respond_to do |format|
    format.html {
      one_week_ago = Time.now.utc.to_date - 7
      @recent_objects = Lead
        .roots_with_data(sessions_current_project_id, false, is_virtual: :all)
        .where('key_updated_at > ?', one_week_ago)
        .reorder(key_updated_at: :desc)
        .limit(10)

      render '/shared/data/all/index'
    }
    format.json {
      is_virtual =
        if params[:is_virtual].to_s == 'all'
          :all
        elsif params[:is_virtual].present?
          ActiveRecord::Type::Boolean.new.cast(params[:is_virtual])
        else
          nil
        end

      @leads = Lead.roots_with_data(
        sessions_current_project_id,
        !!params[:load_root_otus],
        is_virtual:
      )

      @leads = @leads.where(id: params[:id]) if params[:id].present?

      if params[:recent].present? && ActiveRecord::Type::Boolean.new.cast(params[:recent])
        @leads = @leads.reorder('key_updated_at DESC NULLS LAST')
      end

      @leads = @leads.page(params[:page]).per(params[:per])
    }
  end
end

#insert_couplet ⇒ Object

POST /leads/1/insert_couplet.json



175
176
177
178
# File 'app/controllers/leads_controller.rb', line 175

def insert_couplet
  @lead.insert_couplet
  head :no_content
end

#insert_key ⇒ Object

POST /leads/1/insert_key.json?key_to_insert=:id



313
314
315
316
317
318
319
320
# File 'app/controllers/leads_controller.rb', line 313

def insert_key
  if !@lead.insert_key(params[:key_to_insert])
    render json: @lead.errors, status: :unprocessable_content
    return
  end

  head :no_content
end

#lead_params ⇒ Object (private)



486
487
488
489
490
491
492
# File 'app/controllers/leads_controller.rb', line 486

def lead_params()
  params.require(:lead).permit(
    :parent_id,
    :otu_id, :text, :origin_label, :description, :redirect_id,
    :link_out, :link_out_text, :is_public, :is_virtual, :position
  )
end

#list ⇒ Object



106
107
108
109
110
# File 'app/controllers/leads_controller.rb', line 106

def list
  @leads = Lead
    .roots_with_data(sessions_current_project_id, false, is_virtual: :all)
    .page(params[:page])
end

#new ⇒ Object

GET /leads/new



129
130
131
# File 'app/controllers/leads_controller.rb', line 129

def new
  redirect_to new_lead_task_path
end

#new_couplet ⇒ Object (private)



503
504
505
506
507
# File 'app/controllers/leads_controller.rb', line 503

def new_couplet
  2.times do
    @lead.children.create!
  end
end

#otu_payload(otu) ⇒ Hash (private)

Returns the OTU + taxon_name shape the cite_key task expects.

Returns:

  • (Hash) —

    the OTU + taxon_name shape the cite_key task expects



466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
# File 'app/controllers/leads_controller.rb', line 466

def otu_payload(otu)
  tn = otu.taxon_name
  {
    id: otu.id,
    object_tag: helpers.object_tag(otu),
    taxon_name: tn && {
      id: tn.id,
      cached_html: tn.cached_html,
      cached_author_year: tn.cached_author_year,
      cached_nomenclature_date: tn.cached_nomenclature_date,
      cached_is_valid: tn.cached_is_valid,
      cached_misspelling: tn.cached_misspelling
    }
  }
end

#otus ⇒ Object

GET /leads/1/otus.json



363
364
365
# File 'app/controllers/leads_controller.rb', line 363

def otus
  @otus = Otu.associated_with_key(@lead)
end

#redirect_option_texts ⇒ Object

GET /leads/1/redirect_option_texts.json



113
114
115
# File 'app/controllers/leads_controller.rb', line 113

def redirect_option_texts
  @texts = @lead.redirect_options(sessions_current_project_id)
end

#remaining_otus ⇒ Object



367
368
369
370
371
372
373
374
# File 'app/controllers/leads_controller.rb', line 367

def remaining_otus
  lead = Lead.find(params[:id])

  @otus = lead.remaining_otus

  render '/leads/otus'

end

#reorder_children ⇒ Object

PATCH /leads/1/reorder_children.json



349
350
351
352
353
354
355
356
357
358
359
360
# File 'app/controllers/leads_controller.rb', line 349

def reorder_children
  begin
    @lead.reorder_children(reorder_params[:reorder_list])
  rescue TaxonWorks::Error => e
    @lead.errors.add(:reorder_failed, e.to_s)
    render json: @lead.errors, status: :unprocessable_content
    return
  end

  @leads = @lead.reload.children
  @lead_item_otus = @lead.apportioned_lead_item_otus
end

#reorder_params ⇒ Object (private)



494
495
496
# File 'app/controllers/leads_controller.rb', line 494

def reorder_params
  params.permit(reorder_list: [])
end

#reset_lead_items ⇒ Object



447
448
449
450
451
# File 'app/controllers/leads_controller.rb', line 447

def reset_lead_items
  @lead.reset_lead_items

  head :no_content
end

#search ⇒ Object



410
411
412
413
414
415
416
417
# File 'app/controllers/leads_controller.rb', line 410

def search
  if params[:id].blank?
    redirect_to(lead_path,
                alert: 'You must select an item from the list with a click or tab press before clicking show.')
  else
    redirect_to lead_path(params[:id])
  end
end

#set_lead ⇒ Object (private)



482
483
484
# File 'app/controllers/leads_controller.rb', line 482

def set_lead
  @lead = Lead.find(params[:id])
end

#set_observation_matrix ⇒ Object



453
454
455
456
457
458
459
460
461
# File 'app/controllers/leads_controller.rb', line 453

def set_observation_matrix
  if params[:observation_matrix_id].nil?
    @lead.errors.add(:observation_matrix_id, 'is required.')
    render json: @lead.errors, status: :unprocessable_content
  end

  @lead.update!(observation_matrix_id: params[:observation_matrix_id])
  head :no_content
end

#show ⇒ Object

GET /leads/1 GET /leads/1.json



119
120
121
122
123
124
125
126
# File 'app/controllers/leads_controller.rb', line 119

def show
  if @lead.children.present?
    expand_lead
  else
    @children = nil
    @lead_item_otus = @lead.apportioned_lead_item_otus
  end
end

#update ⇒ Object

PATCH/PUT /leads/1.json



181
182
183
184
185
186
187
188
189
190
# File 'app/controllers/leads_controller.rb', line 181

def update
  begin
    @lead.update!(lead_params)
    @lead_item_otus = @lead.parent_id ?
      @lead.parent.apportioned_lead_item_otus :
      { parent: [], children: [] }
  rescue ActiveRecord::RecordInvalid
    render json: @lead.errors, status: :unprocessable_content
  end
end