rocketjob_mission_control 4.3.0 → 6.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/Rakefile +12 -13
  4. data/app/assets/config/manifest.js +3 -0
  5. data/app/assets/javascripts/rocket_job_mission_control/application.js +2 -4
  6. data/app/assets/javascripts/rocket_job_mission_control/nested_fields.js +112 -0
  7. data/app/assets/stylesheets/rocket_job_mission_control/{application.scss → application.css} +9 -9
  8. data/app/assets/stylesheets/rocket_job_mission_control/base.css +420 -0
  9. data/app/assets/stylesheets/rocket_job_mission_control/{callout.scss → callout.css} +50 -52
  10. data/app/assets/stylesheets/rocket_job_mission_control/jobs.css +57 -0
  11. data/app/assets/stylesheets/rocket_job_mission_control/worker_processes.css +7 -0
  12. data/app/controllers/rocket_job_mission_control/application_controller.rb +2 -3
  13. data/app/controllers/rocket_job_mission_control/dirmon_entries_controller.rb +53 -33
  14. data/app/controllers/rocket_job_mission_control/jobs_controller.rb +78 -37
  15. data/app/controllers/rocket_job_mission_control/servers_controller.rb +32 -38
  16. data/app/datatables/rocket_job_mission_control/abstract_datatable.rb +8 -6
  17. data/app/datatables/rocket_job_mission_control/active_workers_datatable.rb +5 -5
  18. data/app/datatables/rocket_job_mission_control/dirmon_entries_datatable.rb +4 -4
  19. data/app/datatables/rocket_job_mission_control/jobs_datatable.rb +55 -62
  20. data/app/datatables/rocket_job_mission_control/servers_datatable.rb +13 -14
  21. data/app/helpers/rocket_job_mission_control/application_helper.rb +70 -52
  22. data/app/helpers/rocket_job_mission_control/dirmon_entries_helper.rb +8 -0
  23. data/app/helpers/rocket_job_mission_control/jobs_helper.rb +80 -13
  24. data/app/helpers/rocket_job_mission_control/pagination_helper.rb +1 -1
  25. data/app/helpers/rocket_job_mission_control/servers_helper.rb +6 -6
  26. data/app/helpers/rocket_job_mission_control/slices_helper.rb +2 -4
  27. data/app/models/rocket_job_mission_control/access_policy.rb +3 -4
  28. data/app/models/rocket_job_mission_control/authorization.rb +3 -2
  29. data/app/models/rocket_job_mission_control/dirmon_sanitizer.rb +68 -0
  30. data/app/models/rocket_job_mission_control/job_sanitizer.rb +48 -16
  31. data/app/models/rocket_job_mission_control/query.rb +2 -5
  32. data/app/views/rocket_job_mission_control/dirmon_entries/_form.html.erb +78 -42
  33. data/app/views/rocket_job_mission_control/dirmon_entries/_input_categories.html.erb +59 -0
  34. data/app/views/rocket_job_mission_control/dirmon_entries/_input_category_fields.html.erb +56 -0
  35. data/app/views/rocket_job_mission_control/dirmon_entries/_output_categories.html.erb +44 -0
  36. data/app/views/rocket_job_mission_control/dirmon_entries/_output_category_fields.html.erb +36 -0
  37. data/app/views/rocket_job_mission_control/dirmon_entries/_status.html.erb +22 -16
  38. data/app/views/rocket_job_mission_control/dirmon_entries/copy.html.erb +4 -0
  39. data/app/views/rocket_job_mission_control/dirmon_entries/edit.html.erb +7 -3
  40. data/app/views/rocket_job_mission_control/dirmon_entries/show.html.erb +8 -5
  41. data/app/views/rocket_job_mission_control/jobs/_attributes.html.erb +89 -0
  42. data/app/views/rocket_job_mission_control/jobs/_dates.html.erb +39 -0
  43. data/app/views/rocket_job_mission_control/jobs/_details.html.erb +86 -0
  44. data/app/views/rocket_job_mission_control/jobs/_exception.html.erb +33 -0
  45. data/app/views/rocket_job_mission_control/jobs/_exceptions.html.erb +1 -1
  46. data/app/views/rocket_job_mission_control/jobs/_input_categories.html.erb +126 -0
  47. data/app/views/rocket_job_mission_control/jobs/_input_category_fields.html.erb +56 -0
  48. data/app/views/rocket_job_mission_control/jobs/_output_categories.html.erb +60 -0
  49. data/app/views/rocket_job_mission_control/jobs/_output_category_fields.html.erb +36 -0
  50. data/app/views/rocket_job_mission_control/jobs/_retryable.html.erb +27 -0
  51. data/app/views/rocket_job_mission_control/jobs/_status.html.erb +21 -49
  52. data/app/views/rocket_job_mission_control/jobs/edit.html.erb +28 -0
  53. data/app/views/rocket_job_mission_control/jobs/edit_slice.html.erb +1 -1
  54. data/app/views/rocket_job_mission_control/jobs/exception.html.erb +1 -1
  55. data/app/views/rocket_job_mission_control/jobs/index.html.erb +24 -18
  56. data/app/views/rocket_job_mission_control/jobs/show.html.erb +32 -58
  57. data/app/views/rocket_job_mission_control/jobs/view_slice.html.erb +4 -4
  58. data/app/views/rocket_job_mission_control/servers/index.html.erb +2 -2
  59. data/config/initializers/assets.rb +5 -8
  60. data/config/locales/en.yml +3 -0
  61. data/config/routes.rb +24 -21
  62. data/lib/rocket_job_mission_control/engine.rb +6 -9
  63. data/lib/rocket_job_mission_control/version.rb +1 -1
  64. data/lib/rocketjob_mission_control.rb +1 -1
  65. data/test/compare_hashes.rb +1 -2
  66. data/test/controllers/rocket_job_mission_control/application_controller_test.rb +13 -21
  67. data/test/controllers/rocket_job_mission_control/dirmon_entries_controller_test.rb +124 -157
  68. data/test/controllers/rocket_job_mission_control/jobs_controller_test.rb +103 -127
  69. data/test/controllers/rocket_job_mission_control/servers_controller_test.rb +66 -103
  70. data/test/helpers/rocket_job_mission_control/application_helper_test.rb +13 -14
  71. data/test/helpers/rocket_job_mission_control/jobs_helper_test.rb +31 -31
  72. data/test/helpers/rocket_job_mission_control/pagination_helper_test.rb +7 -9
  73. data/test/helpers/rocket_job_mission_control/servers_helper_test.rb +15 -15
  74. data/test/helpers/rocket_job_mission_control/slices_helper_test.rb +8 -10
  75. data/test/models/rocket_job_mission_control/dirmon_sanitizer_test.rb +146 -0
  76. data/test/models/rocket_job_mission_control/job_sanitizer_test.rb +47 -42
  77. data/test/models/rocket_job_mission_control/query_test.rb +26 -28
  78. data/test/test_helper.rb +9 -15
  79. data/vendor/assets/fonts/glyphicons-halflings-regular.eot +0 -0
  80. data/vendor/assets/fonts/glyphicons-halflings-regular.svg +288 -0
  81. data/vendor/assets/fonts/glyphicons-halflings-regular.ttf +0 -0
  82. data/vendor/assets/fonts/glyphicons-halflings-regular.woff +0 -0
  83. data/vendor/assets/fonts/glyphicons-halflings-regular.woff2 +0 -0
  84. data/vendor/assets/javascripts/datatables.min.js +881 -0
  85. data/vendor/assets/stylesheets/bootstrap.min.css.erb +6 -0
  86. data/vendor/assets/stylesheets/datatables.min.css +141 -0
  87. metadata +61 -65
  88. data/app/assets/stylesheets/rocket_job_mission_control/base.scss +0 -436
  89. data/app/assets/stylesheets/rocket_job_mission_control/bootstrap_and_overrides.scss +0 -488
  90. data/app/assets/stylesheets/rocket_job_mission_control/jobs.scss +0 -72
  91. data/app/assets/stylesheets/rocket_job_mission_control/worker_processes.scss +0 -9
  92. data/vendor/assets/stylesheets/bootstrap.min.css +0 -6
@@ -1,42 +1,43 @@
1
1
  module RocketJobMissionControl
2
2
  module ApplicationHelper
3
3
  STATE_ICON_MAP = {
4
- aborted: 'fas fa-stop',
5
- completed: 'fas fa-check',
6
- disabled: 'fas fa-stop',
7
- enabled: 'fas fa-check',
8
- failed: 'fas fa-exclamation-triangle',
9
- paused: 'fas fa-pause',
10
- pending: 'fas fa-inbox',
11
- queued: 'fas fa-inbox',
12
- running: 'fas fa-play',
13
- sleeping: 'fas fa-hourglass',
14
- scheduled: 'fas fa-clock',
15
- starting: 'fas fa-cogs',
16
- stopping: 'fas fa-stop',
17
- zombie: 'fas fa-hourglass'
18
- }
4
+ aborted: "fas fa-stop",
5
+ completed: "fas fa-check",
6
+ disabled: "fas fa-stop",
7
+ enabled: "fas fa-check",
8
+ failed: "fas fa-exclamation-triangle",
9
+ paused: "fas fa-pause",
10
+ pending: "fas fa-inbox",
11
+ queued: "fas fa-inbox",
12
+ running: "fas fa-play",
13
+ sleeping: "fas fa-hourglass",
14
+ scheduled: "fas fa-clock",
15
+ starting: "fas fa-cogs",
16
+ stopping: "fas fa-stop",
17
+ zombie: "fas fa-hourglass"
18
+ }.freeze
19
19
 
20
20
  def state_icon(state)
21
- STATE_ICON_MAP[state.to_sym] + ' ' + state.to_s
21
+ STATE_ICON_MAP[state.to_sym] + " " + state.to_s
22
22
  end
23
23
 
24
24
  def site_title
25
- 'Rocket Job Mission Control'
25
+ "Rocket Job Mission Control"
26
26
  end
27
27
 
28
28
  def title
29
29
  @page_title ||= params[:controller].to_s.titleize
30
- h(@full_title || [@page_title, site_title].compact.join(' | '))
30
+ h(@full_title || [@page_title, site_title].compact.join(" | "))
31
31
  end
32
32
 
33
33
  def active_page(path)
34
- 'active' if current_page?(path)
34
+ "active" if current_page?(path)
35
35
  end
36
36
 
37
37
  def pretty_print_array_or_hash(arguments)
38
- return arguments unless arguments.kind_of?(Array) || arguments.kind_of?(Hash)
39
- json_string_options = {space: ' ', indent: ' ', array_nl: '<br />', object_nl: '<br />'}
38
+ return arguments unless arguments.is_a?(Array) || arguments.is_a?(Hash)
39
+
40
+ json_string_options = {space: " ", indent: " ", array_nl: "<br />", object_nl: "<br />"}
40
41
  JSON.generate(arguments, json_string_options).html_safe
41
42
  end
42
43
 
@@ -56,54 +57,71 @@ module RocketJobMissionControl
56
57
  end
57
58
 
58
59
  # Returns the editable field as html for use in editing dynamic fields from a Job class.
59
- def editable_field_html(klass, field_name, value, f, include_nil_selectors = false)
60
+ def editable_field_html(klass, field_name, value, f)
60
61
  # When editing a job the values are of the correct type.
61
62
  # When editing a dirmon entry values are strings.
62
63
  field = klass.fields[field_name.to_s]
63
- return unless field && field.type
64
+ return unless field&.type
64
65
 
65
66
  placeholder = field.default_val
66
67
  placeholder = nil if placeholder.is_a?(Proc)
67
68
 
68
69
  case field.type.name
69
- when 'Symbol', 'String', 'Integer'
70
+ when "Integer"
71
+ options = extract_inclusion_values(klass, field_name)
72
+ f.number_field(field_name, in: options, include_blank: false, value: value, class: "form-control", placeholder: placeholder)
73
+ when "String", "Symbol", "Mongoid::StringifiedSymbol"
70
74
  options = extract_inclusion_values(klass, field_name)
71
- str = "[#{field.type.name}]\n".html_safe
72
75
  if options
73
- str + f.select(field_name, options, { include_blank: options.include?(nil) || include_nil_selectors, selected: value }, { class: 'selectize form-control' })
76
+ f.select(field_name, options, {include_blank: options.include?(nil), selected: value}, {class: "selectize form-control"})
74
77
  else
75
- if field.type.name == 'Integer'
76
- str + f.number_field(field_name, value: value, class: 'form-control', placeholder: placeholder)
77
- else
78
- str + f.text_field(field_name, value: value, class: 'form-control', placeholder: placeholder)
79
- end
78
+ f.text_field(field_name, value: value, class: "form-control", placeholder: placeholder)
80
79
  end
81
- when 'Hash'
80
+ when "Boolean", "Mongoid::Boolean"
81
+ options = extract_inclusion_values(klass, field_name) || [nil, "true", "false"]
82
+ f.select(field_name, options, {include_blank: options.include?(nil), selected: value}, {class: "selectize form-control"})
83
+ when "Hash"
82
84
  "[JSON Hash]\n".html_safe +
83
- f.text_field(field_name, value: value ? value.to_json : '', class: 'form-control', placeholder: '{"key1":"value1", "key2":"value2", "key3":"value3"}')
84
- when 'Array'
85
+ f.text_field(field_name, value: value ? value.to_json : "", class: "form-control", placeholder: '{"key1":"value1", "key2":"value2", "key3":"value3"}')
86
+ when "Array"
85
87
  options = Array(value)
86
- "[Array]\n".html_safe +
87
- f.select(field_name, options_for_select(options, options), { include_hidden: false }, { class: 'selectize form-control', multiple: true })
88
- when 'Mongoid::Boolean'
89
- name = "#{field_name}_true".to_sym
90
- value = value.to_s
91
- str = '<div class="radio-buttons">'.html_safe
92
- str << f.radio_button(field_name, 'true', checked: value == 'true')
93
- str << ' '.html_safe + f.label(name, 'true')
94
- str << ' '.html_safe + f.radio_button(field_name, 'false', checked: value == 'false')
95
- str << ' '.html_safe + f.label(name, 'false')
96
- # Allow this field to be unset (nil).
97
- if include_nil_selectors
98
- str << ' '.html_safe + f.radio_button(field_name, '', checked: value == '')
99
- str << ' '.html_safe + f.label(name, 'nil')
100
- end
101
-
102
- str << '</div>'.html_safe
88
+ f.select(field_name, options_for_select(options, options), {include_hidden: false}, {class: "selectize form-control", multiple: true})
103
89
  else
104
90
  "[#{field.type.name}]".html_safe +
105
- f.text_field(field_name, value: value, class: 'form-control', placeholder: placeholder)
91
+ f.text_field(field_name, value: value, class: "form-control", placeholder: placeholder)
92
+ end
93
+ end
94
+
95
+ # This method creates a link with `data-id` `data-fields` attributes. These attributes are used to create new instances of the nested fields through Javascript.
96
+ def link_to_add_fields(name, f, association, option)
97
+ # Takes an object (@job) and creates a new instance of its associated model (:properties)
98
+ new_object = f.object.send(association).klass.new
99
+
100
+ # Saves the unique ID of the object into a variable.
101
+ # This is needed to ensure the key of the associated array is unique. This is makes parsing the content in the `data-fields` attribute easier through Javascript.
102
+ # We could use another method to achive this.
103
+ id = new_object.object_id
104
+
105
+ # https://api.rubyonrails.org/ fields_for(record_name, record_object = nil, fields_options = {}, &block)
106
+ # record_name = :addresses
107
+ # record_object = new_object
108
+ # fields_options = { child_index: id }
109
+ # child_index` is used to ensure the key of the associated array is unique, and that it matched the value in the `data-id` attribute.
110
+ # `person[addresses_attributes][child_index_value][_destroy]`
111
+ fields = f.fields_for(association, new_object, child_index: id) do |builder|
112
+ # `association.to_s.singularize + "_fields"` ends up evaluating to `address_fields`
113
+ # The render function will then look for `views/people/_address_fields.html.erb`
114
+ # The render function also needs to be passed the value of 'builder', because `views/dirmon_entries/_input_categories.html.erb` needs this to render the form tags.
115
+ render(association.to_s.singularize + "_fields", f: builder)
106
116
  end
117
+
118
+ # This renders a simple link, but passes information into `data` attributes.
119
+ # This info can be named anything we want, but in this case we chose `data-id:` and `data-fields:`.
120
+ # The `id:` is from `new_object.object_id`.
121
+ # The `fields:` are rendered from the `fields` blocks.
122
+ # We use `gsub("\n", "")` to remove anywhite space from the rendered partial.
123
+ # The `id:` value needs to match the value used in `child_index: id`.
124
+ link_to(name, '#', class: "add_fields btn btn-#{option}", data: { id: id, fields: fields.gsub("\n", "") })
107
125
  end
108
126
  end
109
127
  end
@@ -3,5 +3,13 @@ module RocketJobMissionControl
3
3
  def dirmon_counts_by_state(state)
4
4
  RocketJob::DirmonEntry.counts_by_state.fetch(state.downcase.to_sym, 0)
5
5
  end
6
+
7
+
8
+ def dirmon_entry_find_category(categories, category_name = :main)
9
+ return unless categories
10
+
11
+ categories.each { |category| return category if category_name == (category["name"] || :main).to_sym }
12
+ nil
13
+ end
6
14
  end
7
15
  end
@@ -1,5 +1,23 @@
1
1
  module RocketJobMissionControl
2
2
  module JobsHelper
3
+ # The fields that RJMC already displays, all other will be rendered under the custom section.
4
+ DISPLAYED_FIELDS = %w[
5
+ _id _type
6
+ completed_at created_at cron_schedule
7
+ description destroy_on_complete download_encryption
8
+ exception expires_at
9
+ failed_at_list failure_count
10
+ input_categories
11
+ log_level
12
+ output_categories output_path
13
+ percent_complete priority
14
+ record_count retry_limit run_at
15
+ started_at state statistics sub_state
16
+ throttle_group throttle_running_workers
17
+ upload_file_name
18
+ worker_name
19
+ ]
20
+
3
21
  def job_icon(job)
4
22
  state = job_state(job)
5
23
  state_icon(state)
@@ -15,52 +33,101 @@ module RocketJobMissionControl
15
33
  end
16
34
  end
17
35
 
36
+ def job_state_name(job)
37
+ job_state(@job).to_s.camelcase
38
+ end
39
+
40
+ def job_time(time)
41
+ return "" unless time
42
+ time.in_time_zone(Time.zone)
43
+ end
44
+
45
+ def job_state_time(job)
46
+ return job_time(job.run_at) if job.scheduled?
47
+
48
+ job_time(job.completed_at || job.started_at || job.created_at )
49
+ # job_time(job.running? ? job.started_at : job.completed_at)
50
+ end
51
+
52
+ def job_estimated_time_left(job)
53
+ if job.record_count && job.running? && job.record_count.positive?
54
+ percent = job.percent_complete
55
+ if percent >= 5
56
+ secs = job.seconds.to_f
57
+ RocketJob.seconds_as_duration((((secs / percent) * 100) - secs))
58
+ end
59
+ end
60
+ end
61
+
62
+ def job_records_per_hour(job)
63
+ return unless job.completed?
64
+
65
+ secs = job.seconds.to_f
66
+ ((job.record_count.to_f / secs) * 60 * 60).round if job.record_count&.positive? && (secs > 0.0)
67
+ end
68
+
69
+ def job_custom_fields(job)
70
+ attrs = job.attributes.dup
71
+ DISPLAYED_FIELDS.each { |key| attrs.delete(key) }
72
+ # Convert time zones for any custom time fields
73
+ attrs.keys { |key| attrs[key] = attrs[key].in_time_zone(Time.zone) if attrs[key].is_a?(Time) }
74
+ attrs
75
+ end
76
+
18
77
  def job_states
19
78
  @job_states ||= RocketJob::Job.aasm.states.map { |state| state.name.to_s }
20
79
  end
21
80
 
22
81
  def job_states_with_scheduled
23
- @job_states_with_scheduled ||= ['scheduled'] + job_states
82
+ @job_states_with_scheduled ||= ["scheduled"] + job_states
24
83
  end
25
84
 
26
85
  def job_counts_by_state(state)
27
86
  @job_counts ||= begin
28
- counts = RocketJob::Job.counts_by_state
29
- counts[:queued] = counts[:queued_now] || 0
30
- counts
31
- end
87
+ counts = RocketJob::Job.counts_by_state
88
+ counts[:queued] = counts[:queued_now] || 0
89
+ counts
90
+ end
32
91
  @job_counts.fetch(state.downcase.to_sym, 0)
33
92
  end
34
93
 
35
- def job_action_link(action, path, http_method=:get)
94
+ def job_action_link(action, path, http_method = :get)
36
95
  link_to(
37
96
  action,
38
97
  path,
39
98
  method: http_method,
40
99
  title: "#{action} job",
41
- class: 'btn btn-default',
42
- data: {confirm: t(:confirm, scope: [:job, :action], action: action)}
100
+ class: "btn btn-default btn-group",
101
+ role: "group",
102
+ data: { confirm: t(:confirm, scope: %i[job action], action: action) }
43
103
  )
44
104
  end
45
105
 
46
- def job_action_links_for_show(action, path, http_method=:get)
106
+ def job_action_links_for_show(action, path, http_method = :get)
47
107
  link_to(
48
108
  action,
49
109
  path,
50
110
  method: http_method,
51
111
  title: "#{action} job",
52
- class: 'btn btn-primary',
53
- data: {confirm: t(:confirm, scope: [:job, :action], action: action)}
112
+ class: "btn btn-default btn-group",
113
+ role: "group",
114
+ data: { confirm: t(:confirm, scope: %i[job action], action: action) }
54
115
  )
55
116
  end
56
117
 
57
118
  def job_selected_class(job, selected_job)
58
119
  if selected_job.present? && job.id == selected_job.id
59
- 'selected'
120
+ "selected"
60
121
  else
61
- ''
122
+ ""
62
123
  end
63
124
  end
64
125
 
126
+ def job_find_category(categories, category_name = :main)
127
+ return unless categories
128
+
129
+ categories.each { |category| return category if category_name == (category["name"] || :main).to_sym }
130
+ nil
131
+ end
65
132
  end
66
133
  end
@@ -1,7 +1,7 @@
1
1
  module RocketJobMissionControl
2
2
  module PaginationHelper
3
3
  def page_nav_disabled_class(current_position, boundary)
4
- current_position.to_i == boundary.to_i ? 'disabled' : ''
4
+ current_position.to_i == boundary.to_i ? "disabled" : ""
5
5
  end
6
6
  end
7
7
  end
@@ -7,7 +7,7 @@ module RocketJobMissionControl
7
7
  def server_icon(server)
8
8
  state =
9
9
  if server.zombie?
10
- 'zombie'
10
+ "zombie"
11
11
  else
12
12
  server.state
13
13
  end
@@ -16,14 +16,14 @@ module RocketJobMissionControl
16
16
 
17
17
  def server_card_class(server)
18
18
  if server.zombie?
19
- 'callout-zombie'
19
+ "callout-zombie"
20
20
  else
21
21
  map = {
22
- running: 'callout-success',
23
- paused: 'callout-warning',
24
- stopping: 'callout-alert',
22
+ running: "callout-success",
23
+ paused: "callout-warning",
24
+ stopping: "callout-alert"
25
25
  }
26
- map[server.state] || 'callout-info'
26
+ map[server.state] || "callout-info"
27
27
  end
28
28
  end
29
29
  end
@@ -1,9 +1,7 @@
1
1
  module RocketJobMissionControl
2
2
  module SlicesHelper
3
-
4
- def display_slice_info(slice, encrypted=false)
5
- encrypted ? 'encrypted' : pretty_print_array_or_hash(slice.to_a)
3
+ def display_slice_info(slice, encrypted = false)
4
+ encrypted ? "encrypted" : pretty_print_array_or_hash(slice.to_a)
6
5
  end
7
-
8
6
  end
9
7
  end
@@ -17,7 +17,7 @@ module RocketJobMissionControl
17
17
 
18
18
  # Stop, Pause, Resume, Destroy (force stop) Rocket Job Servers
19
19
  role :operator, {operator: true} do
20
- can %i[stop kill pause resume destroy update_all], RocketJob::Server
20
+ can %i[stop kill pause resume destroy destroy_zombies thread_dump], RocketJob::Server
21
21
  end
22
22
 
23
23
  # Pause, Resume, Retry, Abort, Edit Jobs
@@ -25,9 +25,9 @@ module RocketJobMissionControl
25
25
  can %i[edit pause resume retry abort fail update run_now], RocketJob::Job
26
26
  end
27
27
 
28
- # Create, Destroy, Enable, Disable, Edit Dirmon Entries
28
+ # Create, Destroy, Enable, Disable, Edit, Copy, Replicate Dirmon Entries
29
29
  role :dirmon, {dirmon: true} do
30
- can %i[create enable disable update edit], RocketJob::DirmonEntry
30
+ can %i[create enable disable update edit copy replicate], RocketJob::DirmonEntry
31
31
  end
32
32
 
33
33
  # A User can only edit their own jobs
@@ -47,4 +47,3 @@ module RocketJobMissionControl
47
47
  end
48
48
  end
49
49
  end
50
-
@@ -1,12 +1,13 @@
1
1
  module RocketJobMissionControl
2
2
  class Authorization
3
- ROLES = %i[admin editor operator manager dirmon user view]
3
+ ROLES = %i[admin editor operator manager dirmon user view].freeze
4
4
  attr_accessor *ROLES
5
5
  attr_accessor :login
6
6
 
7
7
  def initialize(roles: [], login: nil)
8
8
  @login = login
9
9
  return if roles.blank?
10
+
10
11
  invalid_roles = roles - ROLES
11
12
  raise(ArgumentError, "Invalid Roles Supplied: #{invalid_roles.inspect}") unless invalid_roles.empty?
12
13
 
@@ -19,4 +20,4 @@ module RocketJobMissionControl
19
20
  roles.each { |role| public_send("#{role}=", true) }
20
21
  end
21
22
  end
22
- end
23
+ end
@@ -0,0 +1,68 @@
1
+ module RocketJobMissionControl
2
+ module DirmonSanitizer
3
+ DIRMON_FIELDS = %i(archive_directory job_class_name name pattern).freeze
4
+
5
+ def self.sanitize(params, job_class, target)
6
+ permissible_params = {}
7
+
8
+ DIRMON_FIELDS.each do |field_name|
9
+ value = params[field_name]
10
+ next if value.blank?
11
+
12
+ permissible_params[field_name] = value
13
+ end
14
+
15
+ if params.key?(:properties)
16
+ properties = JobSanitizer.sanitize(params[:properties], job_class, target, false)
17
+ permissible_params[:properties] = properties unless properties.blank?
18
+ end
19
+
20
+ permissible_params
21
+ end
22
+
23
+ # Returns [Hash] the difference between the supplied params and those already set in the job itself
24
+ def self.diff_properties(sanitized_properties, dirmon_entry)
25
+ default_job = dirmon_entry.job_class.new
26
+ updated_job = dirmon_entry.job_class.from_properties(sanitized_properties)
27
+ properties = {}
28
+ sanitized_properties&.each_pair do |name, value|
29
+ if name == :input_categories
30
+ categories = []
31
+ value.each do |category_properties|
32
+ category_name = category_properties[:name].to_sym
33
+ props = diff_category(category_properties, updated_job.input_category(category_name), default_job.input_category(category_name))
34
+ categories << props unless props.empty?
35
+ end
36
+ properties[:input_categories] = categories unless categories.empty?
37
+ elsif name == :output_categories
38
+ categories = []
39
+ value.each do |category_properties|
40
+ category_name = category_properties[:name].to_sym
41
+ props = diff_category(category_properties, updated_job.output_category(category_name), default_job.output_category(category_name))
42
+ categories << props unless props.empty?
43
+ end
44
+ properties[:output_categories] = categories unless categories.empty?
45
+ else
46
+ properties[name] = value unless default_job.public_send(name) == updated_job.public_send(name)
47
+ end
48
+ end
49
+ properties
50
+ end
51
+
52
+ def self.diff_category(properties, updated_category, default_category)
53
+ diff = {}
54
+ name = nil
55
+ properties&.each_pair do |key, value|
56
+ if key == :name
57
+ name = value
58
+ next
59
+ end
60
+ next if updated_category.public_send(key) == default_category.public_send(key)
61
+
62
+ diff[key] = value
63
+ end
64
+ diff[:name] = name unless diff.empty?
65
+ diff
66
+ end
67
+ end
68
+ end