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

archivesspace / archivesspace / 19916213389

04 Dec 2025 03:02AM UTC coverage: 80.708% (+1.4%) from 79.285%
19916213389

Pull #3803

github

661c5a
web-flow
Merge 418b25756 into 87083c526
Pull Request #3803: ANW-1831: Fix 404 console error for PUI resource `show` views

29005 of 35938 relevant lines covered (80.71%)

7935.19 hits per line

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

71.09
/frontend/app/controllers/agents_controller.rb
1
# frozen_string_literal: true
2

3
class AgentsController < ApplicationController
11✔
4
  set_access_control  'view_repository' => [:index, :show],
11✔
5
                      'update_agent_record' => [:new, :edit, :create, :update, :publish, :merge, :merge_selector, :merge_detail, :merge_preview],
6
                      'delete_agent_record' => [:delete],
7
                      'manage_repository' => [:defaults, :update_defaults, :required, :update_required]
8

9
  before_action :assign_types
11✔
10
  before_action :get_required, only: [:new, :create, :required]
11✔
11

12
  include ExportHelper
11✔
13
  include ApplicationHelper
11✔
14

15
  def index
11✔
16
    respond_to do |format|
41✔
17
      format.html do
41✔
18
        @search_data = Search.for_type(session[:repo_id], 'agent', params_for_backend_search.merge({ 'facet[]' => SearchResultData.AGENT_FACETS }))
41✔
19
      end
20
      format.csv do
41✔
21
        search_params = params_for_backend_search.merge({ 'facet[]' => SearchResultData.AGENT_FACETS })
×
22
        search_params['type[]'] = 'agent'
×
23
        uri = "/repositories/#{session[:repo_id]}/search"
×
24
        csv_response(uri, Search.build_filters(search_params), "#{t('agent._plural').downcase}.")
×
25
      end
26
    end
27
  end
28

29
  def current_record
11✔
30
    @agent
×
31
  end
32

33
  def show
11✔
34
    @agent = JSONModel(@agent_type).find(params[:id], find_opts)
13✔
35
  end
36

37
  def new
11✔
38
    @agent = JSONModel(@agent_type).new({ agent_type: @agent_type })._always_valid!
106✔
39
    if user_prefs['default_values']
106✔
40
      defaults = DefaultValues.get @agent_type.to_s
×
41
      @agent.update(defaults.values) if defaults
×
42
    end
43

44
    @required_fields.each_required_subrecord do |property, stub_record|
106✔
45
      @agent[property] << stub_record if @agent[property].empty?
×
46
    end
47

48
    if @agent.names.empty?
106✔
49
      @agent.names = [@name_type.new({ authorized: true, is_display_name: true })._always_valid!]
106✔
50
    end
51

52
    ensure_auth_and_display
106✔
53

54
    set_sort_name_default
106✔
55

56
    render_aspace_partial partial: 'agents/new' if inline?
106✔
57
  end
58

59
  def edit
11✔
60
    @agent = JSONModel(@agent_type).find(params[:id], find_opts)
70✔
61
  end
62

63
  def create
11✔
64
    handle_crud(instance: :agent,
86✔
65
                model: JSONModel(@agent_type),
66
                required_fields: @required_fields,
67
                find_opts: find_opts,
68
                before_hooks: [method(:set_structured_date_type)],
69
                on_invalid: lambda {
70
                  ensure_auth_and_display
4✔
71
                  return render_aspace_partial partial: 'agents/new' if inline?
4✔
72
                  return render action: :new
4✔
73
                },
74
                on_valid: lambda  { |id|
75
                  flash[:success] = t('agent._frontend.messages.created')
39✔
76

77
                  if @agent['is_slug_auto'] == false &&
39✔
78
                     @agent['slug'].nil? &&
79
                     params['agent'] &&
80
                     params['agent']['is_slug_auto'] == '1'
81

82
                    flash[:warning] = t('slug.autogen_disabled')
×
83
                  end
84

85
                  return render json: @agent.to_hash if inline?
39✔
86
                  if params.key?(:plus_one)
39✔
87
                    return redirect_to({ controller: :agents, action: :new, agent_type: @agent_type })
×
88
                  end
89

90
                  redirect_to({ controller: :agents, action: :edit, id: id, agent_type: @agent_type })
39✔
91
                })
92
  end
93

94
  def update
11✔
95
    handle_crud(instance: :agent,
22✔
96
                model: JSONModel(@agent_type),
97
                obj: JSONModel(@agent_type).find(params[:id], find_opts),
98
                before_hooks: [method(:set_structured_date_type)],
99
                on_invalid: lambda {
100
                  if @agent.names.empty?
3✔
101
                    @agent.names = [@name_type.new._always_valid!]
×
102
                  end
103

104
                  return render action: :edit
3✔
105
                },
106
                on_valid: lambda  { |id|
107
                  flash[:success] = t('agent._frontend.messages.updated')
8✔
108
                  if @agent['is_slug_auto'] == false &&
8✔
109
                     @agent['slug'].nil? &&
110
                     params['agent'] &&
111
                     params['agent']['is_slug_auto'] == '1'
112

113
                    flash[:warning] = t('slug.autogen_disabled')
×
114
                  end
115

116
                  redirect_to controller: :agents, action: :edit, id: id, agent_type: @agent_type
8✔
117
                })
118
  end
119

120
  def delete
11✔
121
    agent = JSONModel(@agent_type).find(params[:id])
2✔
122

123
    if agent.key?('is_repo_agent')
2✔
124
      flash[:error] = t('errors.cannot_delete_repository_agent')
1✔
125
      redirect_to(controller: :agents, action: :show, id: params[:id])
1✔
126
      return
1✔
127
    end
128

129
    begin
130
      agent.delete
1✔
131
    rescue ConflictException => e
132
      flash[:error] = t('agent._frontend.messages.delete_conflict', error: t("errors.#{e.conflicts}", default: e.message))
×
133
      redirect_to(controller: :agents, action: :show, id: params[:id])
×
134
      return
×
135
    end
136

137
    flash[:success] = t('agent._frontend.messages.deleted')
1✔
138
    redirect_to(controller: :agents, action: :index, deleted_uri: agent.uri)
1✔
139
  end
140

141
  def publish
11✔
142
    agent = JSONModel(@agent_type).find(params[:id])
1✔
143

144
    response = JSONModel::HTTP.post_form("#{agent.uri}/publish")
1✔
145

146
    if response.code == '200'
1✔
147
      flash[:success] = t('agent._frontend.messages.published', agent_title: clean_mixed_content(agent.display_name['sort_name']))
1✔
148
    else
149
      flash[:error] = ASUtils.json_parse(response.body)['error'].to_s
×
150
    end
151

152
    redirect_to request.referer
1✔
153
  end
154

155
  def defaults
11✔
156
    defaults = DefaultValues.get params['agent_type']
×
157

158
    @agent = JSONModel(@agent_type).new({ agent_type: @agent_type })._always_valid!
×
159

160
    @agent.update(defaults.form_values) if defaults
×
161

162
    render 'defaults'
×
163
  end
164

165
  def update_defaults
11✔
166
    DefaultValues.from_hash({
×
167
                              'record_type' => @agent_type.to_s,
168
                              'lock_version' => params['agent'].delete('lock_version'),
169
                              'defaults' => cleanup_params_for_schema(
170
                                params['agent'],
171
                                JSONModel(@agent_type).schema
172
                              )
173
                            }).save
174

175
    flash[:success] = t('default_values.messages.defaults_updated')
×
176
    redirect_to controller: :agents, action: :defaults
×
177
  rescue Exception => e
178
    flash[:error] = e.message
×
179
    redirect_to controller: :agents, action: :defaults
×
180
  end
181

182
  def required
11✔
183
    @agent = JSONModel(@agent_type).new({ agent_type: @agent_type })._always_valid!
21✔
184
    # we are pretending this is an agent form but it's really a RequiredFields form
185
    @agent.lock_version = @required_fields.lock_version
21✔
186
    render 'required'
21✔
187
  end
188

189
  def update_required
11✔
190
    processed_params = cleanup_params_for_schema(
×
191
      params['agent'],
192
      JSONModel(@agent_type).schema
193
    )
194
    subrecord_requirements = []
×
195

196
    processed_params.each do |key, defn|
×
197
      next unless defn.is_a?(Array) && defn.size == 1
×
198
      # we aren't interested in booleans
199
      defn[0].reject! {|k, v| [false, true].include? (v) }
×
200
      subrecord_requirements << {
×
201
        property: key,
202
        record_type: defn[0]['jsonmodel_type'],
203
        required: (defn[0]['required'] == 'true'),
×
204
        required_fields: defn[0].keys.reject { |k| ['jsonmodel_type', 'required'].include?(k) }
×
205
      }
206
    end
207
    RequiredFields.from_hash({
×
208
                               'lock_version' => processed_params['lock_version'],
209
                               'record_type' => @agent_type.to_s,
210
                               'subrecord_requirements' => subrecord_requirements}).save
211
    flash[:success] = t('required_fields.messages.required_fields_updated')
×
212
    redirect_to controller: :agents, action: :required
×
213
  rescue Exception => e
214
    flash[:error] = e.message
×
215
    redirect_to controller: :agents, action: :required
×
216
  end
217

218
  def merge
11✔
219
    merge_list = params[:record_uris]
×
220
    merge_destination = merge_list[0]
×
221
    merge_list.shift
×
222
    merge_candidates = merge_list
×
223
    merge_destination_type = JSONModel.parse_reference(merge_destination)[:type]
×
224
    handle_merge(merge_candidates,
×
225
                 merge_destination,
226
                 'agent',
227
                 { agent_type: merge_destination_type })
228
  end
229

230
  def merge_selector
11✔
231
    @agent = JSONModel(@agent_type).find(params[:id], find_opts)
8✔
232

233
    if params[:refs].is_a?(Array)
8✔
234
      flash[:error] = t('errors.merge_too_many_merge_candidates')
×
235
      redirect_to({ action: :show, id: params[:id] })
×
236
      return
×
237
    end
238

239
    merge_candidate_details = JSONModel.parse_reference(params[:refs])
8✔
240
    @merge_candidate_type = merge_candidate_details[:type].to_sym
8✔
241
    if @merge_candidate_type != @agent_type
8✔
242
      flash[:error] = t('errors.merge_different_types')
×
243
      redirect_to({ action: :show, id: params[:id] })
×
244
      return
×
245
    end
246

247
    @merge_candidate = JSONModel(@merge_candidate_type).find(merge_candidate_details[:id], find_opts)
8✔
248
    if @agent.key?('is_user') || @merge_candidate.key?('is_user')
8✔
249
      flash[:error] = t('errors.merge_denied_for_system_user')
×
250
      redirect_to({ action: :show, id: params[:id] })
×
251
      return
×
252
    end
253

254
    relationship_uris = @merge_candidate['related_agents'] ? @merge_candidate['related_agents'].map {|ra| ra['ref']} : []
10✔
255
    if relationship_uris.include?(@agent['uri'])
8✔
256
      flash[:error] = t('errors.merge_denied_relationship')
2✔
257
      redirect_to({ action: :show, id: params[:id] })
2✔
258
      return
2✔
259
    end
260

261
    if !user_can?('view_agent_contact_record') && (@agent.agent_contacts.any? || @merge_candidate.agent_contacts.any?)
6✔
262
      flash[:error] = t('errors.merge_restricted_contact_details')
263
      redirect_to({ action: :show, id: params[:id] })
264
      return
8✔
265
    end
7✔
266

7✔
267
    render '_merge_selector'
7✔
268
  end
7✔
269

7✔
270
  def merge_detail
3✔
271
    merge_destination_uri = JSONModel(@agent_type).uri_for(params[:id])
272
    merge_candidate_uri = params['merge_candidate_uri']
273
    request = JSONModel(:merge_request_detail).new
7✔
274
    request.merge_destination = { 'ref' => merge_destination_uri }
7✔
275
    request.merge_candidates = Array.wrap({ 'ref' => merge_candidate_uri })
276

7✔
277
    # the backend is expecting to know how the user may have re-ordered subrecords in the merge interface. This information is encoded in the params, but will be stripped out when we clean them up unless we add them as a pseudo schema attribute.
7✔
278
    # add_position_to_agents_merge does exactly this.
4✔
279
    agent_params_with_position = add_position_to_agents_merge_params(params['agent'])
4✔
280
    request.selections = cleanup_params_for_schema(agent_params_with_position, JSONModel(@agent_type).schema)
281

4✔
282
    uri = "#{JSONModel::HTTP.backend_url}/merge_requests/agent_detail"
×
283
    if params['dry_run']
×
284
      uri += '?dry_run=true'
285
      response = JSONModel::HTTP.post_json(URI(uri), request.to_json)
4✔
286
      merge_response = ASUtils.json_parse(response.body)
287

4✔
288
      @agent = JSONModel(@agent_type).from_hash(merge_response['result'], find_opts)
4✔
289
      render_aspace_partial partial: 'agents/merge_preview', locals: { object: @agent }
290
    else
291
      begin
3✔
292
        response = JSONModel::HTTP.post_json(URI(uri), request.to_json)
293

3✔
294
        flash[:success] = t('agent._frontend.messages.merged')
1✔
295
        resolver = Resolver.new(request.merge_destination['ref'])
1✔
296
        redirect_to(resolver.view_uri)
297
      rescue ValidationException => e
2✔
298
        flash[:error] = e.errors.to_s
2✔
299
        redirect_to({ action: :show, id: params[:id] }.merge(extra_params))
2✔
300
      rescue ConflictException => e
301
        flash[:error] = t('errors.merge_conflict', message: e.conflicts)
302
        redirect_to({ action: :show, id: params[:id] }.merge(extra_params))
303
      rescue RecordNotFound => e
304
        flash[:error] = t('errors.error_404')
8✔
305
        redirect_to({ action: :show, id: params[:id] }.merge(extra_params))
306
      end
8✔
307
    end
275✔
308
  end
309

310
  private
11✔
311

169✔
312
  def name_type_for_agent_type(agent_type)
3✔
313
    JSONModel(agent_type).type_of('names/items')
7✔
314
  end
8✔
315

312✔
316
  def get_required
3✔
317
    @required_fields = RequiredFields.get @agent_type.to_s
276✔
318
  end
275✔
319

275✔
320
  def assign_types
3✔
321
    return unless params.key? 'agent_type'
11✔
322

8✔
323
    params['agent_type'] = "agent_#{params['agent_type']}" if params['agent_type'] !~ /^agent_/
61✔
324
    @agent_type = :"#{params[:agent_type]}"
15✔
325
    @name_type = name_type_for_agent_type(@agent_type)
15✔
326
  end
327

328
  def set_structured_date_type(agent_hash)
3✔
329
    agent_hash['dates_of_existence']&.each do |label|
×
330
      if label['structured_date_single']
331
        label['date_type_structured'] = 'single'
332
      elsif label['structured_date_range']
54✔
333
        label['date_type_structured'] = 'range'
55✔
334
      else
4✔
335
        label['date_type_structured'] = 'Add or update either a single or ranged date subrecord to set'
4✔
336
      end
4✔
337
    end
338
    agent_hash['names']&.each do |name|
×
339
      next unless name['use_dates']
340
      name['use_dates'].each do |label|
×
341
        if label['structured_date_single']
342
          label['date_type_structured'] = 'single'
343
        elsif label['structured_date_range']
344
          label['date_type_structured'] = 'range'
345
        else
54✔
346
          label['date_type_structured'] = 'Add or update either a single or ranged date subrecord to set'
2✔
347
        end
348
      end
349
    end
350

351
    agent_hash['related_agents']&.each do |rel|
352
      if rel['dates']
×
353
        if rel['dates']['structured_date_single']
354
          rel['dates']['date_type_structured'] = 'single'
355
        elsif rel['dates']['structured_date_range']
356
          rel['dates']['date_type_structured'] = 'range'
357
        else
358
          rel['dates']['date_type_structured'] = 'Add or update either a single or ranged date subrecord to set'
8✔
359
        end
109✔
360
      end
109✔
361
    end
109✔
362
  end
363

364
  def ensure_auth_and_display
3✔
365
    if @agent.names.length == 1
1✔
366
      @agent.names[0]['authorized'] = true
1✔
367
      @agent.names[0]['is_display_name'] = true
1✔
368
    elsif @agent.names.length > 1
369
      authorized = false
×
370
      display = false
×
371
      @agent.names.each do |name|
372
        authorized = true if name['authorized'] == true
373
        display = true if name['is_display_name'] == true
374
      end
8✔
375
      @agent.names[0]['authorized'] = true unless authorized
105✔
376
      @agent.names[0]['is_display_name'] = true unless display
105✔
377
    end
378
  end
379

380
  def set_sort_name_default
3✔
381
    @agent.names.each do |name|
1✔
382
      name['sort_name_auto_generate'] = true
1✔
383
    end
384
  end
385

386
  # agent_merge_params looks like this:
387
  # < ActionController::Parameters {
388
  # "lock_version" => "3", "agent_record_identifiers" => {
389
  #  "0" => {
390
  #    "lock_version" => "0"
391
  #  }, "1" => {
392
  #    "lock_version" => "0"
393
  #  }
394
  # }, "agent_record_controls" => {
395
  #  "0" => {
396
  #    "lock_version" => "0"
397
  #  }
398
  # }, "names" => {
399
  #  "1" => {
400
  #    "lock_version" => "0", "replace" => "REPLACE"
401
  #  }, "0" => {
402
  #    "lock_version" => "0"
403
  #  }, "2" => {
404
  #    "lock_version" => "0"
405
  #  }
406
  # }
407
  # This method takes the integer hash keys that represent the original position of the subrecord doing the replacing and adds it as an attribute called "position" under the record type. The result looks like this:
408
  # "names" => [{
409
  #   "lock_version" => "0",
410
  #   "replace" => "REPLACE",
411
  #   "authorized" => false,
412
  #   "position" => 1,
413
  #   "is_display_name" => false,
414
  #   "sort_name_auto_generate" => false
415
  # }, {
416
  #   "lock_version" => "0",
417
  #   "authorized" => false,
418
  #   "position" => 0,
419
  #   "is_display_name" => false,
420
  #   "sort_name_auto_generate" => false
421
  # }, {
422
  #   "lock_version" => "0",
8✔
423
  #   "authorized" => false,
7✔
424
  #   "position" => 2,
83✔
425
  #   "is_display_name" => false,
426
  #   "sort_name_auto_generate" => false
76✔
427
  # }]
92✔
428
  def add_position_to_agents_merge_params(agent_merge_params)
3✔
429
    agent_merge_params.each do |_param_key, param_value|
430
      next unless param_value.respond_to?(:each)
431

432
      param_value.each do |key, value|
433
        value['position'] = key if value
434
      end
435
    end
436
  end
437
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

© 2025 Coveralls, Inc