Class: Tasks::FieldOccurrences::InaturalistImportController

Inherits:
ApplicationController
  • Object
show all
Includes:
TaskControllerConfiguration
Defined in:
app/controllers/tasks/field_occurrences/inaturalist_import_controller.rb

Constant Summary collapse

IMPORT_LIMIT =
50
FIND_LIMIT =
200

Instance Method Summary collapse

Methods included from TaskControllerConfiguration

#set_is_task_controller

Instance Method Details

#check_for_existing ⇒ Object

GET /tasks/field_occurrences/inaturalist_import/check_for_existing.json



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'app/controllers/tasks/field_occurrences/inaturalist_import_controller.rb', line 59

def check_for_existing
  uuids = params[:uuids] || []
  existing_fo_by_uuid = existing_field_occurrences_for_uuids(uuids)
  fo_data = fetch_field_occurrence_data(existing_fo_by_uuid)

  found = existing_fo_by_uuid.map { |uuid, fo_id|
    fo = fo_data[fo_id]
    {
      uuid:,
      field_occurrence_id: fo_id,
      global_id: fo[:global_id],
      determination_label: fo[:determination_label],
      image_count: fo[:image_count],
      sound_count: fo[:sound_count]
    }
  }

  render json: { found: }
end

#existing_field_occurrences_for(results) ⇒ Object (private)



117
118
119
# File 'app/controllers/tasks/field_occurrences/inaturalist_import_controller.rb', line 117

def existing_field_occurrences_for(results)
  existing_field_occurrences_for_uuids(results.filter_map { |r| r['uuid'] })
end

#existing_field_occurrences_for_uuids(uuids) ⇒ Object (private)



121
122
123
# File 'app/controllers/tasks/field_occurrences/inaturalist_import_controller.rb', line 121

def existing_field_occurrences_for_uuids(uuids)
  FieldOccurrence.by_inat_uuids(uuids, project_id: sessions_current_project_id)
end

#fetch_field_occurrence_data(existing_fo_by_uuid) ⇒ Object (private)



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'app/controllers/tasks/field_occurrences/inaturalist_import_controller.rb', line 167

def fetch_field_occurrence_data(existing_fo_by_uuid)
  return {} if existing_fo_by_uuid.empty?

  FieldOccurrence
    .where(id: existing_fo_by_uuid.values)
    .includes(:depictions, :conveyances, taxon_determinations: { otu: :taxon_name })
    .each_with_object({}) do |fo, h|
      h[fo.id] = {
        global_id: fo.to_global_id.to_s,
        image_count: fo.depictions.size,
        sound_count: fo.conveyances.size,
        # current determination (lowest position; the preloaded association is
        # unordered)
        determination_label: helpers.otu_tag(
            fo.taxon_determinations.min_by(&:position).otu
          )
      }
    end
end

#fetch_inat_results(observation_ids) ⇒ Object (private)



110
111
112
113
114
115
# File 'app/controllers/tasks/field_occurrences/inaturalist_import_controller.rb', line 110

def fetch_inat_results(observation_ids)
  ::Vendor::Nasturtium.by_observation_ids(observation_ids)
rescue Timeout::Error
  render json: { error: 'The iNaturalist API did not respond in time. Please try again.' }, status: :service_unavailable
  nil
end

#find ⇒ Object

GET /tasks/field_occurrences/inaturalist_import/find.json



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'app/controllers/tasks/field_occurrences/inaturalist_import_controller.rb', line 13

def find
  observation_ids = params[:observation_ids] || []
  if observation_ids.size > FIND_LIMIT
    render json: { error: "Maximum #{FIND_LIMIT} observations per submission." }, status: :unprocessable_entity
    return
  end

  results = fetch_inat_results(observation_ids)
  return unless results

  existing_fo_by_uuid = existing_field_occurrences_for(results)
  fo_data = fetch_field_occurrence_data(existing_fo_by_uuid)
  use_community_taxon = params[:use_community_taxon] != false

  summary = helpers.inaturalist_find_summary(
      results, existing_fo_by_uuid, fo_data:, use_community_taxon:
    ) + not_found_rows(observation_ids, results)

  render json: { summary: }
end

#import ⇒ Object

POST /tasks/field_occurrences/inaturalist_import/import.json



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'app/controllers/tasks/field_occurrences/inaturalist_import_controller.rb', line 35

def import
  observation_ids = params[:observation_ids] || []
  if observation_ids.size > IMPORT_LIMIT
    render json: { error: "Maximum #{IMPORT_LIMIT} observations per submission." }, status: :unprocessable_entity
    return
  end

  results = fetch_inat_results(observation_ids)
  return unless results

  existing_fo_by_uuid = existing_field_occurrences_for(results)
  fo_data = fetch_field_occurrence_data(existing_fo_by_uuid)
  opts = import_options

  queue_import(results, existing_fo_by_uuid, opts)

  summary = helpers.inaturalist_import_summary(
      results, existing_fo_by_uuid, fo_data:, **opts
    ) + not_found_rows(observation_ids, results)

  render json: { summary: }
end

#import_options ⇒ Object (private)



131
132
133
134
135
136
137
138
# File 'app/controllers/tasks/field_occurrences/inaturalist_import_controller.rb', line 131

def import_options
  {
    use_community_taxon: params[:use_community_taxon] != false,
    import_images: params[:import_images] == true,
    import_sounds: params[:import_sounds] == true,
    taxon_determination: import_taxon_determination
  }
end

#import_taxon_determination ⇒ Hash? (private)

A determination to give every imported observation, if any.

Returns:

  • (Hash, nil)


142
143
144
145
146
147
148
149
150
151
152
# File 'app/controllers/tasks/field_occurrences/inaturalist_import_controller.rb', line 142

def import_taxon_determination
  return nil if params[:taxon_determination].blank?

  params.require(:taxon_determination).permit(
    :otu_id, :year_made, :month_made, :day_made,
    roles_attributes: [
      :type, :organization_id, :person_id, :position,
      person_attributes: [:last_name, :first_name, :suffix, :prefix]
    ]
  ).to_h
end

#index ⇒ Object



9
10
# File 'app/controllers/tasks/field_occurrences/inaturalist_import_controller.rb', line 9

def index
end

#not_found_rows(observation_ids, results) ⇒ Object (private)



125
126
127
128
129
# File 'app/controllers/tasks/field_occurrences/inaturalist_import_controller.rb', line 125

def not_found_rows(observation_ids, results)
  (observation_ids.to_set - results.map { |r| r['id'].to_s }.to_set).map { |id|
     { observation_id: id, status: 'not_found' }
  }
end

#queue_import(results, existing_fo_by_uuid, opts) ⇒ Object (private)



154
155
156
157
158
159
160
161
162
163
164
165
# File 'app/controllers/tasks/field_occurrences/inaturalist_import_controller.rb', line 154

def queue_import(results, existing_fo_by_uuid, opts)
  new_results = results.reject { |r| existing_fo_by_uuid.key?(r['uuid']) }
  return if new_results.empty?

  InaturalistImportJob.perform_later(
    results: new_results,
    project_id: sessions_current_project_id,
    user_id: sessions_current_user_id,
    match_otu_by_name: params[:match_otu_by_name] == true,
    **opts
  )
end

#recent ⇒ Object

GET /tasks/field_occurrences/inaturalist_import/recent.json field_occurrence_id - optionally restrict to these (e.g. to refresh a row)



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'app/controllers/tasks/field_occurrences/inaturalist_import_controller.rb', line 81

def recent
  fos = FieldOccurrence
    .joins(:identifiers)
    .where(
      project_id: sessions_current_project_id,
      identifiers: { type: 'Identifier::Global::Uuid::InaturalistObservation' }
    )
  if params[:field_occurrence_id].present?
    fos = fos.where(id: params[:field_occurrence_id])
  end
  fos = fos
    .order(created_at: :desc)
    .page(params[:page])
    .per(params.fetch(:per_page, 10).to_i.clamp(1, 100))
    .includes(
      :collecting_event, :identifiers, :depictions, :conveyances,
      taxon_determinations: { otu: :taxon_name }
    )

  assign_pagination(fos)
  render json: {
    field_occurrences: fos.map { |fo|
      helpers.serialize_inat_field_occurrence(fo)
    }
  }
end