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

pulibrary / pdc_describe / 34fcfd04-4a59-4c24-91de-15a8ef14d8a4

18 Mar 2024 03:56PM UTC coverage: 95.979% (-0.3%) from 96.233%
34fcfd04-4a59-4c24-91de-15a8ef14d8a4

push

circleci

jrgriffiniii
wip

19 of 19 new or added lines in 2 files covered. (100.0%)

9 existing lines in 4 files now uncovered.

3151 of 3283 relevant lines covered (95.98%)

223.34 hits per line

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

94.44
/app/controllers/works_controller.rb
1
# frozen_string_literal: true
2

3
require "nokogiri"
1✔
4
require "open-uri"
1✔
5

6
# rubocop:disable Metrics/ClassLength
7
class WorksController < ApplicationController
1✔
8
  include ERB::Util
1✔
9
  around_action :rescue_aasm_error, only: [:approve, :withdraw, :resubmit, :validate, :create, :new_submission]
1✔
10

11
  skip_before_action :authenticate_user!
1✔
12
  before_action :authenticate_user!, unless: :public_request?
1✔
13

14
  def index
1✔
15
    @works = Work.all
4✔
16
    respond_to do |format|
4✔
17
      format.html
4✔
18
      format.rss { render layout: false }
6✔
19
    end
20
  end
21

22
  # Renders the "step 0" information page before creating a new dataset
23
  def new
1✔
24
    group = Group.find_by(code: params[:group_code]) || current_user.default_group
49✔
25
    @work = Work.new(created_by_user_id: current_user.id, group:)
49✔
26
    @form_resource_decorator = FormResourceDecorator.new(@work, current_user)
49✔
27
    if wizard_mode?
49✔
28
      render "new_submission"
23✔
29
    end
30
  end
31

32
  def create
1✔
33
    @work = Work.new(created_by_user_id: current_user.id, group_id: params_group_id, user_entered_doi: params["doi"].present?)
32✔
34
    @work.resource = FormToResourceService.convert(params, @work)
32✔
35
    @work.resource.migrated = migrated?
32✔
36
    if @work.valid?
32✔
37
      @work.draft!(current_user)
28✔
38
      upload_service = WorkUploadsEditService.new(@work, current_user)
27✔
39
      upload_service.update_precurated_file_list(added_files_param, deleted_files_param)
27✔
40
      redirect_to work_url(@work), notice: "Work was successfully created."
27✔
41
    else
42
      @form_resource_decorator = FormResourceDecorator.new(@work, current_user)
4✔
43
      render :new, status: :unprocessable_entity
4✔
44
    end
45
  end
46

47
  # Creates the new dataset
48
  def new_submission
1✔
49
    default_group_id = current_user.default_group.id
19✔
50
    @work = Work.new(created_by_user_id: current_user.id, group_id: default_group_id)
19✔
51
    @work.resource = FormToResourceService.convert(params, @work)
19✔
52
    @work.draft!(current_user)
19✔
53
    redirect_to edit_work_path(@work, wizard: true)
18✔
54
  end
55

56
  ##
57
  # Show the information for the dataset with the given id
58
  # When requested as .json, return the internal json resource
59
  def show
1✔
60
    @work = Work.find(params[:id])
88✔
61
    UpdateSnapshotJob.perform_later(work_id: @work.id, last_snapshot_id: work.upload_snapshots.first&.id)
88✔
62
    @work_decorator = WorkDecorator.new(@work, current_user)
88✔
63

64
    respond_to do |format|
88✔
65
      format.html do
88✔
66
        # Ensure that the Work belongs to a Group
67
        group = @work_decorator.group
85✔
68
        raise(Work::InvalidGroupError, "The Work #{@work.id} does not belong to any Group") unless group
85✔
69

70
        @can_curate = current_user.can_admin?(group)
84✔
71
        @work.mark_new_notifications_as_read(current_user.id)
84✔
72
      end
73
      format.json { render json: @work.to_json }
91✔
74
    end
75
  end
76

77
  def file_list
1✔
78
    if params[:id] == "NONE"
178✔
79
      # This is a special case when we render the file list for a work being created
80
      # (i.e. it does not have an id just yet)
81
      render json: []
29✔
82
    else
83
      @work = Work.find(params[:id])
149✔
84
      render json: @work.file_list
149✔
85
    end
86
  end
87

88
  def resolve_doi
1✔
89
    @work = Work.find_by_doi(params[:doi])
3✔
90
    redirect_to @work
2✔
91
  end
92

93
  def resolve_ark
1✔
94
    @work = Work.find_by_ark(params[:ark])
3✔
95
    redirect_to @work
2✔
96
  end
97

98
  # GET /works/1/edit
99
  def edit
1✔
100
    @work = Work.find(params[:id])
106✔
101
    if current_user && @work.editable_by?(current_user)
106✔
102
      if @work.approved? && !@work.administered_by?(current_user)
104✔
103
        Honeybadger.notify("Can not edit work: #{@work.id} is approved but #{current_user.uid} is not admin")
2✔
104
        redirect_to root_path, notice: I18n.t("works.uneditable.approved")
2✔
105
      else
106
        @uploads = @work.uploads
102✔
107
        @wizard_mode = wizard_mode?
102✔
108
        @form_resource_decorator = FormResourceDecorator.new(@work, current_user)
102✔
109
        render "edit"
102✔
110
      end
111
    else
112
      Honeybadger.notify("Can not edit work: #{@work.id} is not editable by #{current_user.uid}")
2✔
113
      redirect_to root_path, notice: I18n.t("works.uneditable.privs")
2✔
114
    end
115
  end
116

117
  def update
1✔
118
    @work = Work.find(params[:id])
102✔
119
    if current_user.blank? || !@work.editable_by?(current_user)
102✔
120
      Honeybadger.notify("Can not update work: #{@work.id} is not editable by #{current_user.uid}")
1✔
121
      redirect_to root_path, notice: I18n.t("works.uneditable.privs")
1✔
122
    elsif !@work.editable_in_current_state?(current_user)
101✔
123
      Honeybadger.notify("Can not update work: #{@work.id} is not editable in current state by #{current_user.uid}")
1✔
124
      redirect_to root_path, notice: I18n.t("works.uneditable.approved")
1✔
125
    else
126
      update_work
100✔
127
    end
128
  end
129

130
  # Prompt to select how to submit their files
131
  def attachment_select
1✔
132
    @work = Work.find(params[:id])
2✔
133
    @wizard_mode = true
2✔
134
  end
135

136
  # User selected a specific way to submit their files
137
  def attachment_selected
1✔
138
    @work = Work.find(params[:id])
3✔
139
    @wizard_mode = true
3✔
140
    @work.files_location = params["attachment_type"]
3✔
141
    @work.save!
3✔
142

143
    # create a directory for the work if the curator will need to move files by hand
144
    @work.s3_query_service.create_directory if @work.files_location != "file_upload"
3✔
145

146
    next_url = case @work.files_location
3✔
147
               when "file_upload"
148
                 work_file_upload_url(@work)
1✔
149
               when "file_cluster"
150
                 work_file_cluster_url(@work)
1✔
151
               else
152
                 work_file_other_url(@work)
1✔
153
               end
154
    redirect_to next_url
3✔
155
  end
156

157
  # Allow user to upload files directly
158
  def file_upload
1✔
159
    @work = Work.find(params[:id])
3✔
160
  end
161

162
  def file_uploaded
1✔
163
    @work = Work.find(params[:id])
4✔
164
    files = pre_curation_uploads_param || []
4✔
165
    if files.count > 0
4✔
166
      upload_service = WorkUploadsEditService.new(@work, current_user)
3✔
167
      @work = upload_service.update_precurated_file_list(files, [])
3✔
168
      @work.save!
2✔
169
      @work.reload_snapshots
2✔
170
    end
171
    redirect_to(work_review_path)
3✔
172
  rescue StandardError => active_storage_error
173
    Rails.logger.error("Failed to attach the file uploads for the work #{@work.doi}: #{active_storage_error}")
1✔
174
    flash[:notice] = "Failed to attach the file uploads for the work #{@work.doi}: #{active_storage_error}. Please contact rdss@princeton.edu for assistance."
1✔
175

176
    redirect_to work_file_upload_path(@work)
1✔
177
  end
178

179
  # Allow user to indicate where their files are located in the PUL Research Cluster
180
  def file_cluster
1✔
181
    @work = Work.find(params[:id])
1✔
182
  end
183

184
  # Allow user to indicate where their files are located in the WWW
185
  def file_other
1✔
186
    @work = Work.find(params[:id])
1✔
187
  end
188

189
  def review
1✔
190
    @work = Work.find(params[:id])
2✔
191
    if request.method == "POST"
2✔
192
      @work.location_notes = params["location_notes"]
1✔
193
      @work.save!
1✔
194
    end
195
  end
196

197
  def validate
1✔
198
    @work = Work.find(params[:id])
26✔
199
    @work.submission_notes = params["submission_notes"]
26✔
200
    @uploads = @work.uploads
26✔
201
    @wizard_mode = true
26✔
202
    @work.complete_submission!(current_user)
26✔
203
    redirect_to user_url(current_user)
25✔
204
  end
205

206
  def approve
1✔
207
    @work = Work.find(params[:id])
14✔
208
    @work.approve!(current_user)
14✔
209
    flash[:notice] = "Your files are being moved to the post-curation bucket in the background. Depending on the file sizes this may take some time."
6✔
210
    redirect_to work_path(@work)
6✔
211
  end
212

213
  def withdraw
1✔
214
    @work = Work.find(params[:id])
2✔
215
    @work.withdraw!(current_user)
2✔
216
    redirect_to work_path(@work)
1✔
217
  end
218

219
  def resubmit
1✔
220
    @work = Work.find(params[:id])
2✔
221
    @work.resubmit!(current_user)
2✔
222
    redirect_to work_path(@work)
1✔
223
  end
224

225
  def assign_curator
1✔
226
    work = Work.find(params[:id])
5✔
227
    work.change_curator(params[:uid], current_user)
5✔
228
    if work.errors.count > 0
4✔
229
      render json: { errors: work.errors.map(&:type) }, status: :bad_request
1✔
230
    else
231
      render json: {}
3✔
232
    end
233
  rescue => ex
234
    Rails.logger.error("Error changing curator for work: #{work.id}. Exception: #{ex.message}")
1✔
235
    render json: { errors: ["Cannot save dataset"] }, status: :bad_request
1✔
236
  end
237

238
  def add_message
1✔
239
    work = Work.find(params[:id])
6✔
240
    if params["new-message"].present?
6✔
241
      new_message_param = params["new-message"]
6✔
242
      sanitized_new_message = html_escape(new_message_param)
6✔
243

244
      work.add_message(sanitized_new_message, current_user.id)
6✔
245
    end
246
    redirect_to work_path(id: params[:id])
6✔
247
  end
248

249
  def add_provenance_note
1✔
250
    work = Work.find(params[:id])
2✔
251
    if params["new-provenance-note"].present?
2✔
252
      new_date = params["new-provenance-date"]
2✔
253
      new_label = params["change_label"]
2✔
254
      new_note = html_escape(params["new-provenance-note"])
2✔
255

256
      work.add_provenance_note(new_date, new_note, current_user.id, new_label)
2✔
257
    end
258
    redirect_to work_path(id: params[:id])
2✔
259
  end
260

261
  # Outputs the Datacite XML representation of the work
262
  def datacite
1✔
263
    work = Work.find(params[:id])
2✔
264
    render xml: work.to_xml
2✔
265
  end
266

267
  def datacite_validate
1✔
268
    @errors = []
3✔
269
    @work = Work.find(params[:id])
3✔
270
    validator = WorkValidator.new(@work)
3✔
271
    unless validator.valid_datacite?
3✔
272
      @errors = @work.errors.full_messages
2✔
273
    end
274
  end
275

276
  def readme_select
1✔
277
    @work = Work.find(params[:id])
3✔
278
    readme = Readme.new(@work, current_user)
3✔
279
    @readme = readme.file_name
3✔
280
    @wizard = true
3✔
281
  end
282

283
  def readme_uploaded
1✔
284
    @work = Work.find(params[:id])
3✔
285
    @wizard = true
3✔
286
    readme = Readme.new(@work, current_user)
3✔
287
    readme_error = readme.attach(readme_file_param)
3✔
288
    if readme_error.nil?
3✔
289
      redirect_to work_attachment_select_url(@work)
2✔
290
    else
291
      flash[:notice] = readme_error
1✔
292
      redirect_to work_readme_select_url(@work)
1✔
293
    end
294
  end
295

296
  def migrating?
1✔
297
    return @work.resource.migrated if @work&.resource && !params.key?(:migrate)
61✔
298

299
    params[:migrate]
44✔
300
  end
301
  helper_method :migrating?
1✔
302

303
  def doi_mutable?
1✔
304
    return true unless !@work.nil? && @work.persisted?
149✔
305

306
    !@work.approved?
93✔
307
  end
308
  helper_method :doi_mutable?
1✔
309

310
  # Returns the raw BibTex citation information
311
  def bibtex
1✔
312
    work = Work.find(params[:id])
1✔
313
    creators = work.resource.creators.map { |creator| "#{creator.family_name}, #{creator.given_name}" }
15✔
314
    citation = DatasetCitation.new(creators, [work.resource.publication_year], work.resource.titles.first.title, work.resource.resource_type, work.resource.publisher, work.resource.doi)
1✔
315
    bibtex = citation.bibtex
1✔
316
    send_data bibtex, filename: "#{citation.bibtex_id}.bibtex", type: "text/plain", disposition: "attachment"
1✔
317
  end
318

319
  private
1✔
320

321
    # Extract the Work ID parameter
322
    # @return [String]
323
    def work_id_param
1✔
324
      params[:id]
92✔
325
    end
326

327
    # Find the Work requested by ID
328
    # @return [Work]
329
    def work
1✔
330
      Work.find(work_id_param)
92✔
331
    end
332

333
    # Determine whether or not the request is for the :index action in the RSS
334
    # response format
335
    # This is to enable PDC Discovery to index approved content via the RSS feed
336
    def rss_index_request?
1✔
337
      action_name == "index" && request.format.symbol == :rss
675✔
338
    end
339

340
    # Determine whether or not the request is for the :show action in the JSON
341
    # response format
342
    # @return [Boolean]
343
    def json_show_request?
1✔
344
      action_name == "show" && request.format.symbol == :json
673✔
345
    end
346

347
    # Determine whether or not the requested Work has been approved
348
    # @return [Boolean]
349
    def work_approved?
1✔
350
      work&.state == "approved"
4✔
351
    end
352

353
    ##
354
    # Public requests are requests that do not require authentication.
355
    # This is to enable PDC Discovery to index approved content via the RSS feed
356
    # and .json calls to individual works without needing to log in as a user.
357
    # Note that only approved works can be fetched for indexing.
358
    def public_request?
1✔
359
      return true if rss_index_request?
675✔
360
      return true if json_show_request? && work_approved?
673✔
361
      false
671✔
362
    end
363

364
    def work_params
1✔
365
      params[:work] || {}
261✔
366
    end
367

368
    def patch_params
1✔
369
      return {} unless params.key?(:patch)
14✔
370

371
      params[:patch]
12✔
372
    end
373

374
    def pre_curation_uploads_param
1✔
375
      return if patch_params.nil?
4✔
376

377
      patch_params[:pre_curation_uploads]
4✔
378
    end
379

380
    def readme_file_param
1✔
381
      return if patch_params.nil?
3✔
382

383
      patch_params[:readme_file]
3✔
384
    end
385

386
    # rubocop:disable Metrics/AbcSize
387
    # rubocop:disable Metrics/BlockNesting
388
    # rubocop:disable Metrics/CyclomaticComplexity
389
    # rubocop:disable Metrics/MethodLength
390
    # rubocop:disable Metrics/PerceivedComplexity
391
    def rescue_aasm_error
1✔
392
      yield
95✔
393
    rescue AASM::InvalidTransition => error
394
      message = error.message
12✔
395
      if @work.errors.count > 0
12✔
396
        message = @work.errors.to_a.join(", ")
7✔
397
      end
398
      message.chop! if message.last == "."
12✔
399
      Honeybadger.notify("Invalid #{@work.current_transition}: #{error.message} errors: #{message}")
12✔
400
      transition_error_message = "We apologize, the following errors were encountered: #{message}. Please contact the PDC Describe administrators for any assistance."
12✔
401
      @errors = [transition_error_message]
12✔
402

403
      if @work.persisted?
12✔
404
        redirect_to edit_work_url(id: @work.id), notice: transition_error_message, params:
11✔
405
      else
406
        new_params = {}
1✔
407
        new_params[:wizard] = wizard_mode? if wizard_mode?
1✔
408
        new_params[:migrate] = migrating? if migrating?
1✔
409
        @form_resource_decorator = FormResourceDecorator.new(@work, current_user)
1✔
410
        redirect_to new_work_url(params: new_params), notice: transition_error_message, params: new_params
1✔
411
      end
412
    rescue StandardError => generic_error
413
      if action_name == "create"
1✔
414
        if @work.persisted?
1✔
415
          Honeybadger.notify("Failed to create the new Dataset #{@work.id}: #{generic_error.message}")
×
416
          @form_resource_decorator = FormResourceDecorator.new(@work, current_user)
×
417
          redirect_to edit_work_url(id: @work.id), notice: "Failed to create the new Dataset #{@work.id}: #{generic_error.message}", params:
×
418
        else
419
          Honeybadger.notify("Failed to create a new Dataset #{@work.id}: #{generic_error.message}")
1✔
420
          new_params = {}
1✔
421
          new_params[:wizard] = wizard_mode? if wizard_mode?
1✔
422
          new_params[:migrate] = migrating? if migrating?
1✔
423
          @form_resource_decorator = FormResourceDecorator.new(@work, current_user)
1✔
424
          redirect_to new_work_url(params: new_params), notice: "Failed to create a new Dataset: #{generic_error.message}", params: new_params
1✔
425
        end
426
      else
427
        redirect_to root_url, notice: "We apologize, an error was encountered: #{generic_error.message}. Please contact the PDC Describe administrators."
×
428
      end
429
    end
430
    # rubocop:enable Metrics/PerceivedComplexity
431
    # rubocop:enable Metrics/MethodLength
432
    # rubocop:enable Metrics/CyclomaticComplexity
433
    # rubocop:enable Metrics/BlockNesting
434
    # rubocop:enable Metrics/AbcSize
435

436
    def error_action
1✔
437
      @form_resource_decorator = FormResourceDecorator.new(@work, current_user)
×
438
      if action_name == "create"
×
439
        :new
×
440
      elsif action_name == "validate"
×
441
        :edit
×
442
      elsif action_name == "new_submission"
×
443
        :new_submission
×
444
      else
445
        @work_decorator = WorkDecorator.new(@work, current_user)
×
446
        :show
×
447
      end
448
    end
449

450
    def wizard_mode?
1✔
451
      params[:wizard] == "true"
256✔
452
    end
453

454
    def update_work
1✔
455
      @wizard_mode = wizard_mode?
100✔
456
      upload_service = WorkUploadsEditService.new(@work, current_user)
100✔
457
      if @work.approved?
100✔
458
        upload_keys = deleted_files_param || []
5✔
459
        deleted_uploads = upload_service.find_post_curation_uploads(upload_keys:)
5✔
460

461
        return head(:forbidden) unless deleted_uploads.empty?
5✔
462
      else
463
        @work = upload_service.update_precurated_file_list(added_files_param, deleted_files_param)
95✔
464
      end
465

466
      process_updates
99✔
467
    end
468

469
    def embargo_date_param
1✔
470
      params["embargo-date"]
201✔
471
    end
472

473
    def embargo_date
1✔
474
      return nil if embargo_date_param.blank?
195✔
475

476
      Date.parse(embargo_date_param)
4✔
477
    rescue Date::Error
478
      Rails.logger.error("Failed to parse the embargo date #{embargo_date_param} for Work #{@work.id}")
2✔
479
      nil
2✔
480
    end
481

482
    def update_params
1✔
483
      {
484
        group_id: params_group_id,
195✔
485
        embargo_date:,
486
        resource: FormToResourceService.convert(params, @work)
487
      }
488
    end
489

490
    def added_files_param
1✔
491
      Array(work_params[:pre_curation_uploads_added])
122✔
492
    end
493

494
    def deleted_files_param
1✔
495
      deleted_count = (work_params["deleted_files_count"] || "0").to_i
127✔
496
      (1..deleted_count).map { |i| work_params["deleted_file_#{i}"] }.select(&:present?)
139✔
497
    end
498

499
    def process_updates
1✔
500
      resource_before = @work.resource
99✔
501
      if @work.update(update_params)
99✔
502
        resource_compare = WorkCompareService.new(resource_before, update_params[:resource])
96✔
UNCOV
503
        @work.log_changes(resource_compare, current_user.id)
×
504

UNCOV
505
        if @wizard_mode
×
UNCOV
506
          redirect_to work_readme_select_url(@work)
×
507
        else
UNCOV
508
          redirect_to work_url(@work), notice: "Work was successfully updated."
×
509
        end
510
      else
511
        # This is needed for rendering HTML views with validation errors
512
        @uploads = @work.uploads
3✔
513
        @wizard_mode = wizard_mode?
3✔
514
        @form_resource_decorator = FormResourceDecorator.new(@work, current_user)
3✔
515

516
        render :edit, status: :unprocessable_entity
3✔
517
      end
518
    end
519

520
    def params_group_id
1✔
521
      # Do not allow a nil for the group id
522
      @params_group_id ||= begin
227✔
523
        group_id = params[:group_id]
131✔
524
        if group_id.blank?
131✔
525
          group_id = current_user.default_group.id
9✔
526
          Honeybadger.notify("We got a nil group as part of the parameters #{params} #{request}")
9✔
527
        end
528
        group_id
131✔
529
      end
530
    end
531

532
    def migrated?
1✔
533
      return false unless params.key?(:submit)
32✔
534

535
      params[:submit] == "Migrate"
28✔
536
    end
537
end
538
# rubocop:enable Metrics/ClassLength
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

© 2025 Coveralls, Inc