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

pulibrary / pdc_describe / cace366a-ffad-45f1-9b60-678e607fa527

14 May 2024 02:21PM UTC coverage: 60.862% (-35.0%) from 95.908%
cace366a-ffad-45f1-9b60-678e607fa527

push

circleci

jrgriffiniii
wip

1 of 3 new or added lines in 2 files covered. (33.33%)

1194 existing lines in 57 files now uncovered.

2076 of 3411 relevant lines covered (60.86%)

22.71 hits per line

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

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

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

6
# Currently this controller supports Multiple ways to create a work, wizard mode, create dataset, and migrate
7
# The goal is to eventually break some of these workflows into separate contorllers.
8
# For the moment I'm documenting which methods get called by each workflow below.
9
# Note: new, edit and update get called by both the migrate and Non wizard workflows
10
#
11
# Normal mode
12
#  new & file_list -> create -> show & file_list
13
#
14
#  Clicking Edit puts you in wizard mode for some reason :(
15
#
16
# migrate
17
#
18
#  new & file_list -> create -> show & file_list
19
#
20
#  Clicking edit
21
#   edit & file_list -> update -> show & file_list
22
#
23

24
# rubocop:disable Metrics/ClassLength
25
class WorksController < ApplicationController
1✔
26
  include ERB::Util
1✔
27
  around_action :rescue_aasm_error, only: [:approve, :withdraw, :resubmit, :validate, :create]
1✔
28

29
  skip_before_action :authenticate_user!
1✔
30
  before_action :authenticate_user!, unless: :public_request?
1✔
31

32
  def index
1✔
UNCOV
33
    @works = Work.all
×
UNCOV
34
    respond_to do |format|
×
UNCOV
35
      format.html
×
UNCOV
36
      format.rss { render layout: false }
×
37
    end
38
  end
39

40
  # only non wizard mode
41
  def new
1✔
42
    group = Group.find_by(code: params[:group_code]) || current_user.default_group
1✔
43
    @work = Work.new(created_by_user_id: current_user.id, group:)
1✔
44
    @work_decorator = WorkDecorator.new(@work, current_user)
1✔
45
    @form_resource_decorator = FormResourceDecorator.new(@work, current_user)
1✔
46
  end
47

48
  # only non wizard mode
49
  def create
1✔
50
    @work = Work.new(created_by_user_id: current_user.id, group_id: params_group_id, user_entered_doi: params["doi"].present?)
1✔
51
    @work.resource = FormToResourceService.convert(params, @work)
1✔
52
    @work.resource.migrated = migrated?
1✔
53
    if @work.valid?
1✔
54
      @work.draft!(current_user)
1✔
55
      upload_service = WorkUploadsEditService.new(@work, current_user)
1✔
56
      upload_service.update_precurated_file_list(added_files_param, deleted_files_param)
1✔
57
      redirect_to work_url(@work), notice: "Work was successfully created."
1✔
58
    else
UNCOV
59
      @work_decorator = WorkDecorator.new(@work, current_user)
×
UNCOV
60
      @form_resource_decorator = FormResourceDecorator.new(@work, current_user)
×
UNCOV
61
      render :new, status: :unprocessable_entity
×
62
    end
63
  end
64

65
  ##
66
  # Show the information for the dataset with the given id
67
  # When requested as .json, return the internal json resource
68
  def show
1✔
69
    @work = Work.find(params[:id])
11✔
70
    UpdateSnapshotJob.perform_later(work_id: @work.id, last_snapshot_id: work.upload_snapshots.first&.id)
11✔
71
    @work_decorator = WorkDecorator.new(@work, current_user)
11✔
72

73
    respond_to do |format|
11✔
74
      format.html do
11✔
75
        # Ensure that the Work belongs to a Group
76
        group = @work_decorator.group
11✔
77
        raise(Work::InvalidGroupError, "The Work #{@work.id} does not belong to any Group") unless group
11✔
78

79
        @can_curate = current_user.can_admin?(group)
11✔
80
        @work.mark_new_notifications_as_read(current_user.id)
11✔
81
      end
82
      format.json { render json: @work.to_json }
11✔
83
    end
84
  end
85

86
  # only non wizard mode
87
  def file_list
1✔
88
    if params[:id] == "NONE"
30✔
89
      # This is a special case when we render the file list for a work being created
90
      # (i.e. it does not have an id just yet)
91
      render json: []
1✔
92
    else
93
      @work = Work.find(params[:id])
29✔
94
      render json: @work.file_list
29✔
95
    end
96
  end
97

98
  def resolve_doi
1✔
UNCOV
99
    @work = Work.find_by_doi(params[:doi])
×
UNCOV
100
    redirect_to @work
×
101
  end
102

103
  def resolve_ark
1✔
UNCOV
104
    @work = Work.find_by_ark(params[:ark])
×
UNCOV
105
    redirect_to @work
×
106
  end
107

108
  # GET /works/1/edit
109
  # only non wizard mode
110
  def edit
1✔
111
    @work = Work.find(params[:id])
19✔
112
    @work_decorator = WorkDecorator.new(@work, current_user)
19✔
113
    if validate_modification_permissions(work: @work,
19✔
114
                                         uneditable_message: "Can not update work: #{@work.id} is not editable by #{current_user.uid}",
115
                                         current_state_message: "Can not update work: #{@work.id} is not editable in current state by #{current_user.uid}")
116
      @uploads = @work.uploads
18✔
117
      @form_resource_decorator = FormResourceDecorator.new(@work, current_user)
18✔
118
    end
119
  end
120

121
  # PATCH /works/1
122
  # only non wizard mode
123
  def update
1✔
124
    @work = Work.find(params[:id])
10✔
125
    if validate_modification_permissions(work: @work, uneditable_message: "Can not update work: #{@work.id} is not editable by #{current_user.uid}",
10✔
126
                                         current_state_message: "Can not update work: #{@work.id} is not editable in current state by #{current_user.uid}")
127
      update_work
10✔
128
    end
129
  end
130

131
  def approve
1✔
UNCOV
132
    @work = Work.find(params[:id])
×
UNCOV
133
    @work.approve!(current_user)
×
UNCOV
134
    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."
×
UNCOV
135
    redirect_to work_path(@work)
×
136
  end
137

138
  def withdraw
1✔
UNCOV
139
    @work = Work.find(params[:id])
×
UNCOV
140
    @work.withdraw!(current_user)
×
UNCOV
141
    redirect_to work_path(@work)
×
142
  end
143

144
  def resubmit
1✔
UNCOV
145
    @work = Work.find(params[:id])
×
UNCOV
146
    @work.resubmit!(current_user)
×
UNCOV
147
    redirect_to work_path(@work)
×
148
  end
149

150
  def assign_curator
1✔
UNCOV
151
    work = Work.find(params[:id])
×
UNCOV
152
    work.change_curator(params[:uid], current_user)
×
UNCOV
153
    if work.errors.count > 0
×
UNCOV
154
      render json: { errors: work.errors.map(&:type) }, status: :bad_request
×
155
    else
UNCOV
156
      render json: {}
×
157
    end
158
  rescue => ex
UNCOV
159
    Rails.logger.error("Error changing curator for work: #{work.id}. Exception: #{ex.message}")
×
UNCOV
160
    render json: { errors: ["Cannot save dataset"] }, status: :bad_request
×
161
  end
162

163
  def add_message
1✔
UNCOV
164
    work = Work.find(params[:id])
×
UNCOV
165
    if params["new-message"].present?
×
UNCOV
166
      new_message_param = params["new-message"]
×
UNCOV
167
      sanitized_new_message = html_escape(new_message_param)
×
168

UNCOV
169
      work.add_message(sanitized_new_message, current_user.id)
×
170
    end
UNCOV
171
    redirect_to work_path(id: params[:id])
×
172
  end
173

174
  def add_provenance_note
1✔
UNCOV
175
    work = Work.find(params[:id])
×
UNCOV
176
    if params["new-provenance-note"].present?
×
UNCOV
177
      new_date = params["new-provenance-date"]
×
UNCOV
178
      new_label = params["change_label"]
×
UNCOV
179
      new_note = html_escape(params["new-provenance-note"])
×
180

UNCOV
181
      work.add_provenance_note(new_date, new_note, current_user.id, new_label)
×
182
    end
UNCOV
183
    redirect_to work_path(id: params[:id])
×
184
  end
185

186
  # Outputs the Datacite XML representation of the work
187
  def datacite
1✔
UNCOV
188
    work = Work.find(params[:id])
×
UNCOV
189
    render xml: work.to_xml
×
190
  end
191

192
  def datacite_validate
1✔
UNCOV
193
    @errors = []
×
UNCOV
194
    @work = Work.find(params[:id])
×
UNCOV
195
    validator = WorkValidator.new(@work)
×
UNCOV
196
    unless validator.valid_datacite?
×
UNCOV
197
      @errors = @work.errors.full_messages
×
198
    end
199
  end
200

201
  def migrating?
1✔
202
    return @work.resource.migrated if @work&.resource && !params.key?(:migrate)
2✔
203

204
    params[:migrate]
2✔
205
  end
206
  helper_method :migrating?
1✔
207

208
  # Returns the raw BibTex citation information
209
  def bibtex
1✔
UNCOV
210
    work = Work.find(params[:id])
×
UNCOV
211
    creators = work.resource.creators.map { |creator| "#{creator.family_name}, #{creator.given_name}" }
×
UNCOV
212
    citation = DatasetCitation.new(creators, [work.resource.publication_year], work.resource.titles.first.title, work.resource.resource_type, work.resource.publisher, work.resource.doi)
×
UNCOV
213
    bibtex = citation.bibtex
×
UNCOV
214
    send_data bibtex, filename: "#{citation.bibtex_id}.bibtex", type: "text/plain", disposition: "attachment"
×
215
  end
216

217
  # POST /works/1/upload-files (called via Uppy)
218
  def upload_files
1✔
219
    @work = Work.find(params[:id])
×
220
    upload_service = WorkUploadsEditService.new(@work, current_user)
×
221
    upload_service.update_precurated_file_list(params["files"], [])
×
222
  end
223

224
  # Validates that the work is ready to be approved
225
  # GET /works/1/validate
226
  def validate
1✔
227
    @work = Work.find(params[:id])
1✔
228
    @work.complete_submission!(current_user)
1✔
229
    redirect_to user_path(current_user)
1✔
230
  end
231

232
  private
1✔
233

234
    # Extract the Work ID parameter
235
    # @return [String]
236
    def work_id_param
1✔
237
      params[:id]
11✔
238
    end
239

240
    # Find the Work requested by ID
241
    # @return [Work]
242
    def work
1✔
243
      Work.find(work_id_param)
11✔
244
    end
245

246
    # Determine whether or not the request is for the :index action in the RSS
247
    # response format
248
    # This is to enable PDC Discovery to index approved content via the RSS feed
249
    def rss_index_request?
1✔
250
      action_name == "index" && request.format.symbol == :rss
73✔
251
    end
252

253
    # Determine whether or not the request is for the :show action in the JSON
254
    # response format
255
    # @return [Boolean]
256
    def json_show_request?
1✔
257
      action_name == "show" && request.format.symbol == :json
73✔
258
    end
259

260
    # Determine whether or not the requested Work has been approved
261
    # @return [Boolean]
262
    def work_approved?
1✔
UNCOV
263
      work&.state == "approved"
×
264
    end
265

266
    ##
267
    # Public requests are requests that do not require authentication.
268
    # This is to enable PDC Discovery to index approved content via the RSS feed
269
    # and .json calls to individual works without needing to log in as a user.
270
    # Note that only approved works can be fetched for indexing.
271
    def public_request?
1✔
272
      return true if rss_index_request?
73✔
273
      return true if json_show_request? && work_approved?
73✔
274
      false
73✔
275
    end
276

277
    def work_params
1✔
278
      params[:work] || {}
23✔
279
    end
280

281
    def patch_params
1✔
282
      return {} unless params.key?(:patch)
×
283

284
      params[:patch]
×
285
    end
286

287
    def pre_curation_uploads_param
1✔
288
      return if patch_params.nil?
×
289

290
      patch_params[:pre_curation_uploads]
×
291
    end
292

293
    def readme_file_param
1✔
294
      return if patch_params.nil?
×
295

296
      patch_params[:readme_file]
×
297
    end
298

299
    def rescue_aasm_error
1✔
300
      super
2✔
301
    rescue StandardError => generic_error
UNCOV
302
      if action_name == "create"
×
UNCOV
303
        handle_error_for_create(generic_error)
×
304
      else
305
        redirect_to root_url, notice: "We apologize, an error was encountered: #{generic_error.message}. Please contact the PDC Describe administrators."
×
306
      end
307
    end
308

309
    def handle_error_for_create(generic_error)
1✔
UNCOV
310
      if @work.persisted?
×
311
        Honeybadger.notify("Failed to create the new Dataset #{@work.id}: #{generic_error.message}")
×
312
        @form_resource_decorator = FormResourceDecorator.new(@work, current_user)
×
313
        redirect_to edit_work_url(id: @work.id), notice: "Failed to create the new Dataset #{@work.id}: #{generic_error.message}", params:
×
314
      else
UNCOV
315
        Honeybadger.notify("Failed to create a new Dataset #{@work.id}: #{generic_error.message}")
×
UNCOV
316
        new_params = {}
×
UNCOV
317
        new_params[:wizard] = wizard_mode? if wizard_mode?
×
UNCOV
318
        new_params[:migrate] = migrating? if migrating?
×
UNCOV
319
        @form_resource_decorator = FormResourceDecorator.new(@work, current_user)
×
UNCOV
320
        redirect_to new_work_url(params: new_params), notice: "Failed to create a new Dataset: #{generic_error.message}", params: new_params
×
321
      end
322
    end
323

324
    def redirect_aasm_error(transition_error_message)
1✔
UNCOV
325
      if @work.persisted?
×
UNCOV
326
        redirect_to edit_work_url(id: @work.id), notice: transition_error_message, params:
×
327
      else
328
        new_params = {}
×
329
        new_params[:wizard] = wizard_mode? if wizard_mode?
×
330
        new_params[:migrate] = migrating? if migrating?
×
331
        @form_resource_decorator = FormResourceDecorator.new(@work, current_user)
×
332
        redirect_to new_work_url(params: new_params), notice: transition_error_message, params: new_params
×
333
      end
334
    end
335

336
    def error_action
1✔
337
      @form_resource_decorator = FormResourceDecorator.new(@work, current_user)
×
338
      if action_name == "create"
×
339
        :new
×
340
      elsif action_name == "validate"
×
341
        :edit
×
342
      elsif action_name == "new_submission"
×
343
        :new_submission
×
344
      else
345
        @work_decorator = WorkDecorator.new(@work, current_user)
×
346
        :show
×
347
      end
348
    end
349

350
    def wizard_mode?
1✔
UNCOV
351
      params[:wizard] == "true"
×
352
    end
353

354
    def update_work
1✔
355
      upload_service = WorkUploadsEditService.new(@work, current_user)
10✔
356
      if @work.approved?
10✔
UNCOV
357
        upload_keys = deleted_files_param || []
×
UNCOV
358
        deleted_uploads = upload_service.find_post_curation_uploads(upload_keys:)
×
359

UNCOV
360
        return head(:forbidden) unless deleted_uploads.empty?
×
361
      else
362
        @work = upload_service.update_precurated_file_list(added_files_param, deleted_files_param)
10✔
363
      end
364

365
      process_updates
10✔
366
    end
367

368
    def added_files_param
1✔
369
      Array(work_params[:pre_curation_uploads_added])
11✔
370
    end
371

372
    def deleted_files_param
1✔
373
      deleted_count = (work_params["deleted_files_count"] || "0").to_i
11✔
374
      (1..deleted_count).map { |i| work_params["deleted_file_#{i}"] }.select(&:present?)
12✔
375
    end
376

377
    def process_updates
1✔
378
      if WorkCompareService.update_work(work: @work, update_params:, current_user:)
10✔
379
        redirect_to work_url(@work), notice: "Work was successfully updated."
10✔
380
      else
381
        # This is needed for rendering HTML views with validation errors
UNCOV
382
        @uploads = @work.uploads
×
UNCOV
383
        @form_resource_decorator = FormResourceDecorator.new(@work, current_user)
×
384

UNCOV
385
        render :edit, status: :unprocessable_entity
×
386
      end
387
    end
388

389
    def migrated?
1✔
390
      return false unless params.key?(:submit)
1✔
391

392
      params[:submit] == "Migrate"
1✔
393
    end
394
end
395
# 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