foreman-tasks 3.0.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/js_tests.yml +5 -1
  3. data/app/controllers/foreman_tasks/api/tasks_controller.rb +19 -5
  4. data/app/lib/actions/entry_action.rb +8 -4
  5. data/app/lib/actions/helpers/lock.rb +11 -5
  6. data/app/lib/actions/middleware/keep_current_request_id.rb +4 -1
  7. data/app/lib/actions/middleware/keep_current_user.rb +11 -1
  8. data/app/lib/actions/observable_action.rb +80 -0
  9. data/app/models/foreman_tasks/concerns/action_subject.rb +0 -6
  10. data/app/models/foreman_tasks/link.rb +60 -0
  11. data/app/models/foreman_tasks/lock.rb +30 -128
  12. data/app/models/foreman_tasks/recurring_logic.rb +3 -3
  13. data/app/models/foreman_tasks/task.rb +20 -7
  14. data/app/models/foreman_tasks/task/search.rb +7 -6
  15. data/app/models/setting/foreman_tasks.rb +8 -8
  16. data/app/services/foreman_tasks/dashboard_table_filter.rb +5 -1
  17. data/app/views/foreman_tasks/api/locks/show.json.rabl +4 -0
  18. data/app/views/foreman_tasks/api/tasks/details.json.rabl +5 -3
  19. data/app/views/foreman_tasks/layouts/react.html.erb +1 -2
  20. data/app/views/foreman_tasks/tasks/_lock_card.html.erb +10 -0
  21. data/app/views/foreman_tasks/tasks/dashboard/_latest_tasks_in_error_warning.html.erb +1 -1
  22. data/app/views/foreman_tasks/tasks/dashboard/_tasks_status.html.erb +1 -1
  23. data/app/views/foreman_tasks/tasks/show.html.erb +1 -6
  24. data/db/migrate/20181206123910_create_foreman_tasks_links.foreman_tasks.rb +26 -0
  25. data/db/migrate/20181206124952_migrate_non_exclusive_locks_to_links.foreman_tasks.rb +14 -0
  26. data/db/migrate/20181206131436_drop_old_locks.foreman_tasks.rb +20 -0
  27. data/db/migrate/20181206131627_make_locks_exclusive.foreman_tasks.rb +25 -0
  28. data/foreman-tasks.gemspec +1 -0
  29. data/lib/foreman_tasks/cleaner.rb +10 -0
  30. data/lib/foreman_tasks/engine.rb +5 -2
  31. data/lib/foreman_tasks/tasks/export_tasks.rake +2 -2
  32. data/lib/foreman_tasks/version.rb +1 -1
  33. data/package.json +6 -6
  34. data/test/controllers/api/tasks_controller_test.rb +10 -0
  35. data/test/controllers/tasks_controller_test.rb +1 -1
  36. data/test/unit/actions/action_with_sub_plans_test.rb +5 -2
  37. data/test/unit/cleaner_test.rb +4 -4
  38. data/test/unit/locking_test.rb +85 -0
  39. data/test/unit/recurring_logic_test.rb +6 -0
  40. data/test/unit/task_test.rb +10 -0
  41. data/webpack/ForemanTasks/Components/TaskDetails/Components/Locks.js +2 -2
  42. data/webpack/ForemanTasks/Components/TaskDetails/Components/TaskInfo.js +3 -2
  43. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/Locks.test.js.snap +4 -4
  44. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/TaskInfo.test.js.snap +2 -0
  45. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetails.js +4 -1
  46. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetails.scss +14 -0
  47. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetailsSelectors.js +3 -0
  48. data/webpack/ForemanTasks/Components/TaskDetails/index.js +2 -0
  49. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/ScheduledTasksCard/ScheduledTasksCard.scss +4 -0
  50. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/OtherInfo.js +53 -0
  51. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/OtherInfo.test.js +14 -0
  52. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/StoppedTasksCard.js +27 -19
  53. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/StoppedTasksCard.scss +14 -0
  54. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/StoppedTasksCard.test.js +1 -34
  55. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/{StoppedTasksCardHelper.js → StoppedTasksCardTable.js} +28 -1
  56. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/StoppedTasksCardTable.test.js +54 -0
  57. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/__snapshots__/OtherInfo.test.js.snap +48 -0
  58. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/__snapshots__/StoppedTasksCard.test.js.snap +60 -1367
  59. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/StoppedTasksCard/__snapshots__/StoppedTasksCardTable.test.js.snap +960 -0
  60. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/__snapshots__/TasksCardsGrid.test.js.snap +14 -11
  61. data/webpack/ForemanTasks/Components/TasksDashboard/TasksDashboardConstants.js +2 -0
  62. data/webpack/ForemanTasks/Components/TasksDashboard/TasksDashboardSelectors.js +17 -11
  63. data/webpack/ForemanTasks/Components/TasksDashboard/__tests__/TasksDashboardSelectors.test.js +26 -14
  64. data/webpack/ForemanTasks/Components/TasksDashboard/__tests__/__snapshots__/TasksDashboard.test.js.snap +14 -11
  65. data/webpack/ForemanTasks/Components/TasksDashboard/__tests__/__snapshots__/TasksDashboardSelectors.test.js.snap +38 -22
  66. data/webpack/ForemanTasks/Components/TasksTable/TasksTableHelpers.js +0 -8
  67. data/webpack/ForemanTasks/Components/TasksTable/TasksTablePage.js +13 -4
  68. data/webpack/ForemanTasks/Components/TasksTable/TasksTablePage.scss +4 -13
  69. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTablePage.test.js.snap +2 -4
  70. data/webpack/ForemanTasks/Components/TasksTable/formatters/__test__/__snapshots__/actionNameCellFormatter.test.js.snap +1 -0
  71. data/webpack/ForemanTasks/Components/TasksTable/formatters/actionNameCellFormatter.js +5 -1
  72. metadata +24 -11
  73. data/app/assets/stylesheets/foreman_tasks/tasks.scss +0 -9
  74. data/test/unit/lock_test.rb +0 -22
@@ -31,12 +31,12 @@ module ForemanTasks
31
31
  if value
32
32
  task.update!(:start_at => next_occurrence_time) if task.start_at < Time.zone.now
33
33
  update(:state => 'active')
34
- else
35
- update(:state => 'disabled')
36
34
  end
37
- else
35
+ elsif value
38
36
  raise RecurringLogicCancelledException
39
37
  end
38
+
39
+ update(:state => 'disabled') unless value
40
40
  end
41
41
 
42
42
  def enabled?
@@ -31,6 +31,7 @@ module ForemanTasks
31
31
  has_many :recurring_logic_task_groups, -> { where :type => 'ForemanTasks::TaskGroups::RecurringLogicTaskGroup' },
32
32
  :through => :task_group_members, :source => :task_group
33
33
  belongs_to :user
34
+ has_many :links, :dependent => :destroy
34
35
 
35
36
  scoped_search :on => :id, :complete_value => false
36
37
  scoped_search :on => :action, :complete_value => false
@@ -46,10 +47,16 @@ module ForemanTasks
46
47
 
47
48
  # Note: the following searches may return duplicates, this is due to
48
49
  # one task maybe having multiple locks (e.g. read/write) for the same resource_id
49
- scoped_search :relation => :locks, :on => :resource_id, :complete_value => false, :rename => 'location_id', :ext_method => :search_by_taxonomy, :only_explicit => true
50
- scoped_search :relation => :locks, :on => :resource_id, :complete_value => false, :rename => 'organization_id', :ext_method => :search_by_taxonomy, :only_explicit => true
51
- scoped_search :relation => :locks, :on => :resource_type, :complete_value => true, :rename => 'resource_type', :ext_method => :search_by_generic_resource, :only_explicit => true
52
- scoped_search :relation => :locks, :on => :resource_id, :complete_value => false, :rename => 'resource_id', :ext_method => :search_by_generic_resource, :only_explicit => true
50
+ scoped_search :relation => :locks, :on => :resource_id, :complete_value => false, :rename => 'locked_location_id', :ext_method => :search_by_taxonomy, :only_explicit => true
51
+ scoped_search :relation => :locks, :on => :resource_id, :complete_value => false, :rename => 'locked_organization_id', :ext_method => :search_by_taxonomy, :only_explicit => true
52
+ scoped_search :relation => :locks, :on => :resource_type, :complete_value => true, :rename => 'locked_resource_type', :ext_method => :search_by_generic_resource, :only_explicit => true
53
+ scoped_search :relation => :locks, :on => :resource_id, :complete_value => false, :rename => 'locked_resource_id', :ext_method => :search_by_generic_resource, :only_explicit => true
54
+
55
+ scoped_search :relation => :links, :on => :resource_id, :complete_value => false, :rename => 'location_id', :ext_method => :search_by_taxonomy, :only_explicit => true
56
+ scoped_search :relation => :links, :on => :resource_id, :complete_value => false, :rename => 'organization_id', :ext_method => :search_by_taxonomy, :only_explicit => true
57
+ scoped_search :relation => :links, :on => :resource_type, :complete_value => true, :rename => 'resource_type', :ext_method => :search_by_generic_resource, :only_explicit => true
58
+ scoped_search :relation => :links, :on => :resource_id, :complete_value => false, :rename => 'resource_id', :ext_method => :search_by_generic_resource, :only_explicit => true
59
+
53
60
  scoped_search :on => :user_id,
54
61
  :complete_value => true,
55
62
  :rename => 'user.id',
@@ -65,8 +72,8 @@ module ForemanTasks
65
72
  scope :running, -> { where("foreman_tasks_tasks.state NOT IN ('stopped', 'paused')") }
66
73
  scope :for_resource,
67
74
  (lambda do |resource|
68
- joins(:locks).where(:"foreman_tasks_locks.resource_id" => resource.id,
69
- :"foreman_tasks_locks.resource_type" => resource.class.name)
75
+ joins(:links).where(:"foreman_tasks_links.resource_id" => resource.id,
76
+ :"foreman_tasks_links.resource_type" => resource.class.name)
70
77
  end)
71
78
  scope :for_action_types, (->(action_types) { where('foreman_tasks_tasks.label IN (?)', Array(action_types)) })
72
79
 
@@ -82,7 +89,7 @@ module ForemanTasks
82
89
  property :ended_at, ActiveSupport::TimeWithZone, desc: 'Returns date with time the task ended at'
83
90
  end
84
91
  class Jail < Safemode::Jail
85
- allow :started_at, :ended_at, :result, :state, :label, :main_action
92
+ allow :started_at, :ended_at, :result, :state, :label, :main_action, :action_output
86
93
  end
87
94
 
88
95
  def input
@@ -242,6 +249,12 @@ module ForemanTasks
242
249
  parts.join(' ').strip
243
250
  end
244
251
 
252
+ def action_output
253
+ return unless main_action.is_a?(Actions::Helpers::WithContinuousOutput)
254
+ main_action.continuous_output.sort!
255
+ main_action.continuous_output.raw_outputs
256
+ end
257
+
245
258
  protected
246
259
 
247
260
  def generate_id
@@ -5,9 +5,9 @@ module ForemanTasks
5
5
  key = 'resource_type' if key.blank?
6
6
  key_name = connection.quote_column_name(key.sub(/^.*\./, ''))
7
7
  value = value.split(',') if operator.index(/IN/i)
8
- condition = sanitize_sql_for_conditions(["foreman_tasks_locks.#{key_name} #{operator} (?)", value])
8
+ condition = sanitize_sql_for_conditions(["foreman_tasks_links.#{key_name} #{operator} (?)", value])
9
9
 
10
- { :conditions => condition, :joins => :locks }
10
+ { :conditions => condition, :joins => :links }
11
11
  end
12
12
 
13
13
  def search_by_taxonomy(key, operator, value)
@@ -15,12 +15,13 @@ module ForemanTasks
15
15
  resource_type = key == 'location_id' ? 'Location' : 'Organization'
16
16
 
17
17
  joins = <<-SQL
18
- LEFT JOIN foreman_tasks_locks AS foreman_tasks_locks_taxonomy#{uniq_suffix}
19
- ON (foreman_tasks_locks_taxonomy#{uniq_suffix}.task_id = foreman_tasks_tasks.id AND
20
- foreman_tasks_locks_taxonomy#{uniq_suffix}.resource_type = '#{resource_type}')
18
+ LEFT JOIN foreman_tasks_links AS foreman_tasks_links_taxonomy#{uniq_suffix}
19
+ ON (foreman_tasks_links_taxonomy#{uniq_suffix}.task_id = foreman_tasks_tasks.id AND
20
+ foreman_tasks_links_taxonomy#{uniq_suffix}.resource_type = '#{resource_type}')
21
21
  SQL
22
22
  # Select only those tasks which either have the correct taxonomy or are not related to any
23
- sql = "foreman_tasks_locks_taxonomy#{uniq_suffix}.resource_id #{operator} ? OR foreman_tasks_locks_taxonomy#{uniq_suffix}.resource_id IS NULL"
23
+ sql = "foreman_tasks_links_taxonomy#{uniq_suffix}.resource_id #{operator} (?) OR foreman_tasks_links_taxonomy#{uniq_suffix}.resource_id IS NULL"
24
+ value = value.split(',') if operator.index(/IN/i)
24
25
  { :conditions => sanitize_sql_for_conditions([sql, value]), :joins => joins }
25
26
  end
26
27
 
@@ -1,18 +1,18 @@
1
1
  class Setting::ForemanTasks < Setting
2
2
  def self.default_settings
3
3
  [
4
- set('foreman_tasks_sync_task_timeout', N_('Number of seconds to wait for synchronous task to finish.'), 120),
5
- set('dynflow_enable_console', N_('Enable the dynflow console (/foreman_tasks/dynflow) for debugging'), true),
6
- set('dynflow_console_require_auth', N_('Require user to be authenticated as user with admin rights when accessing dynflow console'), true),
7
- set('foreman_tasks_proxy_action_retry_count', N_('Number of attempts to start a task on the smart proxy before failing'), 4),
8
- set('foreman_tasks_proxy_action_retry_interval', N_('Time in seconds between retries'), 15),
9
- set('foreman_tasks_proxy_batch_trigger', N_('Allow triggering tasks on the smart proxy in batches'), true),
10
- set('foreman_tasks_proxy_batch_size', N_('Number of tasks which should be sent to the smart proxy in one request, if foreman_tasks_proxy_batch_trigger is enabled'), 100),
4
+ set('foreman_tasks_sync_task_timeout', N_('Number of seconds to wait for synchronous task to finish.'), 120, N_('Sync task timeout')),
5
+ set('dynflow_enable_console', N_('Enable the dynflow console (/foreman_tasks/dynflow) for debugging'), true, N_('Enable dynflow console')),
6
+ set('dynflow_console_require_auth', N_('Require user to be authenticated as user with admin rights when accessing dynflow console'), true, N_('Require auth for dynflow console')),
7
+ set('foreman_tasks_proxy_action_retry_count', N_('Number of attempts to start a task on the smart proxy before failing'), 4, N_('Proxy action retry count')),
8
+ set('foreman_tasks_proxy_action_retry_interval', N_('Time in seconds between retries'), 15, N_('Proxy action retry interval')),
9
+ set('foreman_tasks_proxy_batch_trigger', N_('Allow triggering tasks on the smart proxy in batches'), true, N_('Allow proxy batch tasks')),
10
+ set('foreman_tasks_proxy_batch_size', N_('Number of tasks which should be sent to the smart proxy in one request, if foreman_tasks_proxy_batch_trigger is enabled'), 100, N_('Proxy tasks batch size')),
11
11
  set('foreman_tasks_troubleshooting_url',
12
12
  N_('Url pointing to the task troubleshooting documentation. '\
13
13
  'It should contain %{label} placeholder, that will be replaced with normalized task label '\
14
14
  '(restricted to only alphanumeric characters)). %{version} placeholder is also available.'),
15
- nil),
15
+ nil, N_('Tasks troubleshooting URL')),
16
16
  set('foreman_tasks_polling_multiplier',
17
17
  N_('Polling multiplier which is used to multiply the default polling intervals. '\
18
18
  'This can be used to prevent polling too frequently for long running tasks.'),
@@ -24,7 +24,11 @@ module ForemanTasks
24
24
  private
25
25
 
26
26
  def scope_by(field)
27
- @new_scope = @new_scope.where(field => @params[field]) if @params[field].present?
27
+ if (field == :result) && (@params[field] == 'other')
28
+ @new_scope = @new_scope.where(:result => ['cancelled', 'pending'])
29
+ elsif @params[field].present?
30
+ @new_scope = @new_scope.where(field => @params[field])
31
+ end
28
32
  end
29
33
 
30
34
  def scope_by_time
@@ -0,0 +1,4 @@
1
+ object @lock
2
+
3
+ attributes :name, :resource_type, :resource_id
4
+ node(:exclusive) { !locals[:link] }
@@ -9,10 +9,12 @@ node(:failed_steps) { @task.input_output_failed_steps }
9
9
  node(:running_steps) { @task.input_output_running_steps }
10
10
  node(:help) { troubleshooting_info_text }
11
11
  node(:has_sub_tasks) { @task.sub_tasks.any? }
12
+
12
13
  node(:locks) do
13
- @task.locks.map do |lock|
14
- { name: lock.name, exclusive: lock.exclusive, resource_type: lock.resource_type, resource_id: lock.resource_id }
15
- end
14
+ @task.locks.map { |lock| partial('foreman_tasks/api/locks/show', :object => lock) }
15
+ end
16
+ node(:links) do
17
+ @task.links.map { |link| partial('foreman_tasks/api/locks/show', :object => link, :locals => { :link => true }) }
16
18
  end
17
19
  node(:username_path) { username_link_task(@task.owner, @task.username) }
18
20
  node(:dynflow_enable_console) { Setting['dynflow_enable_console'] }
@@ -9,7 +9,6 @@
9
9
  <%= notifications %>
10
10
  <div id="organization-id" data-id="<%= Organization.current.id if Organization.current %>" ></div>
11
11
  <div id="user-id" data-id="<%= User.current.id if User.current %>" ></div>
12
- <div id="foremanTasksReactRoot"></div>
12
+ <%= react_component('ForemanTasks') %>
13
13
  <% end %>
14
14
  <%= render file: "layouts/base" %>
15
- <%= mount_react_component('ForemanTasks', '#foremanTasksReactRoot') %>
@@ -0,0 +1,10 @@
1
+ <div class="col-xs-6 col-sm-4 col-md-4">
2
+ <div class="card-pf card-pf-accented card-pf-aggregate-status">
3
+ <h2 class="card-pf-title">
4
+ <span class="fa <%= lock.is_a?(::ForemanTasks::Lock) ? 'fa-lock' : 'fa-unlock-alt' %>"></span> <%= lock.resource_type %>
5
+ </h2>
6
+ <div class="card-pf-body">
7
+ <%= format('id:%s', lock.resource_id) %><br>
8
+ </div>
9
+ </div>
10
+ </div>
@@ -11,7 +11,7 @@
11
11
  <td class="ellipsis"><%= link_to task.humanized[:action], defined?(main_app) ? main_app.foreman_tasks_task_path(task.id) : foreman_tasks_task_path(task.id) %></td>
12
12
  <td><%= task.state %></td>
13
13
  <td><%= task.result %></td>
14
- <td><%= task.started_at ? (_(date_time_relative(task.started_at))) : _('N/A') %></td>
14
+ <td><%= task.started_at ? date_time_relative(task.started_at) : _('N/A') %></td>
15
15
  </tr>
16
16
  <% end %>
17
17
  </table>
@@ -11,7 +11,7 @@
11
11
  <td><%= result.state %></td>
12
12
  <td><%= result.result %></td>
13
13
  <td><%= link_to result.count, main_app.foreman_tasks_tasks_path(:search => "state=#{result.state}&result=#{result.result}") %></td>
14
- <td><%= result.started_at ? (_(date_time_relative(result.started_at))) : _('N/A') %></td>
14
+ <td><%= result.started_at ? date_time_relative(result.started_at) : _('N/A') %></td>
15
15
  </tr>
16
16
  <% end %>
17
17
  </table>
@@ -15,9 +15,4 @@
15
15
  switcher_item_url: foreman_tasks_task_path(:id => ':id')
16
16
  ) %>
17
17
 
18
-
19
- <div class="task-details" id="foremanTaskDetails">
20
- </div>
21
-
22
-
23
- <%= mount_react_component('TaskDetails', '#foremanTaskDetails') %>
18
+ <%= react_component('TaskDetails') %>
@@ -0,0 +1,26 @@
1
+ class CreateForemanTasksLinks < ActiveRecord::Migration[4.2]
2
+ def change
3
+ # rubocop:disable Rails/CreateTableWithTimestamps
4
+ create_table :foreman_tasks_links do |t|
5
+ task_id_options = { :index => true, :null => true }
6
+ if on_postgresql?
7
+ t.uuid :task_id, task_id_options
8
+ else
9
+ t.string :task_id, task_id_options
10
+ end
11
+ t.string :resource_type
12
+ t.integer :resource_id
13
+ end
14
+ # rubocop:enable Rails/CreateTableWithTimestamps
15
+
16
+ add_index :foreman_tasks_links, [:resource_type, :resource_id]
17
+ add_index :foreman_tasks_links, [:task_id, :resource_type, :resource_id],
18
+ :unique => true, :name => 'foreman_tasks_links_unique_index'
19
+ end
20
+
21
+ private
22
+
23
+ def on_postgresql?
24
+ ActiveRecord::Base.connection.adapter_name.casecmp('postgresql').zero?
25
+ end
26
+ end
@@ -0,0 +1,14 @@
1
+ class MigrateNonExclusiveLocksToLinks < ActiveRecord::Migration[5.0]
2
+ def up
3
+ execute <<-SQL
4
+ INSERT INTO foreman_tasks_links(task_id, resource_type, resource_id)
5
+ SELECT DISTINCT locks.task_id, locks.resource_type, locks.resource_id
6
+ FROM foreman_tasks_locks AS locks
7
+ LEFT JOIN foreman_tasks_links AS links
8
+ ON links.task_id = locks.task_id
9
+ AND links.resource_type = locks.resource_type
10
+ AND links.resource_id = locks.resource_id
11
+ WHERE locks.exclusive = FALSE AND links.task_id IS NULL;
12
+ SQL
13
+ end
14
+ end
@@ -0,0 +1,20 @@
1
+ class DropOldLocks < ActiveRecord::Migration[5.0]
2
+ BATCH_SIZE = 10_000
3
+
4
+ # Delete all locks which are exclusive or have a stopped task or are orphaned
5
+ def up
6
+ scope = ForemanTasks::Lock.left_outer_joins(:task)
7
+ scope = scope.where(:exclusive => false)
8
+ .or(scope.where("#{ForemanTasks::Task.table_name}.state" => ['stopped', nil]))
9
+ scope.limit(BATCH_SIZE).delete_all while scope.any?
10
+
11
+ # For each group of locks, where each lock has the same task_id, resource_type and resource_id
12
+ # return the highest id within the group, if there is more than 1 lock in the group
13
+ scope = ForemanTasks::Lock.select("MAX(id) as id")
14
+ .group(:task_id, :resource_type, :resource_id)
15
+ .having("count(*) > 1")
16
+
17
+ # Make sure there is at most one lock per task and resource
18
+ ForemanTasks::Lock.where(:id => scope.limit(BATCH_SIZE)).delete_all while scope.any?
19
+ end
20
+ end
@@ -0,0 +1,25 @@
1
+ class MakeLocksExclusive < ActiveRecord::Migration[5.0]
2
+ BATCH_SIZE = 2
3
+
4
+ def up
5
+ change_table :foreman_tasks_locks do |t|
6
+ t.remove :exclusive
7
+ t.remove :name
8
+ t.remove_index :name => 'index_foreman_tasks_locks_on_resource_type_and_resource_id'
9
+ t.index [:resource_type, :resource_id], :unique => true
10
+ end
11
+ end
12
+
13
+ def down
14
+ change_table :foreman_tasks_locks do |t|
15
+ t.boolean :exclusive, index: true
16
+ t.string :name, index: true
17
+ t.remove_index :name => 'index_foreman_tasks_locks_on_resource_type_and_resource_id'
18
+ t.index [:resource_type, :resource_id]
19
+ end
20
+
21
+ scope = ForemanTasks::Lock.where(:name => nil)
22
+ while scope.limit(BATCH_SIZE).update_all(:name => 'lock') == BATCH_SIZE; end
23
+ change_column_null(:foreman_tasks_locks, :name, false)
24
+ end
25
+ end
@@ -7,6 +7,7 @@ require "foreman_tasks/version"
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "foreman-tasks"
9
9
  s.version = ForemanTasks::VERSION
10
+ s.license = 'GPL-3.0'
10
11
  s.authors = ["Ivan Nečas"]
11
12
  s.email = ["inecas@redhat.com"]
12
13
  s.homepage = "https://github.com/theforeman/foreman-tasks"
@@ -103,6 +103,7 @@ module ForemanTasks
103
103
  end
104
104
  end
105
105
  delete_orphaned_locks
106
+ delete_orphaned_links
106
107
  delete_orphaned_dynflow_tasks
107
108
  end
108
109
 
@@ -156,6 +157,15 @@ module ForemanTasks
156
157
  end
157
158
  end
158
159
 
160
+ def delete_orphaned_links
161
+ orphaned_links = ForemanTasks::Link.left_outer_joins(:task).where(:'foreman_tasks_tasks.id' => nil)
162
+ with_noop(orphaned_links, 'orphaned task links') do |source, name|
163
+ with_batches(source, name) do |chunk|
164
+ ForemanTasks::Link.where(id: chunk.pluck(:id)).delete_all
165
+ end
166
+ end
167
+ end
168
+
159
169
  def delete_orphaned_dynflow_tasks
160
170
  with_noop(orphaned_dynflow_tasks, 'orphaned execution plans') do |source, name|
161
171
  with_batches(source, name) do |chunk|
@@ -34,7 +34,7 @@ module ForemanTasks
34
34
 
35
35
  initializer 'foreman_tasks.register_plugin', :before => :finisher_hook do |_app|
36
36
  Foreman::Plugin.register :"foreman-tasks" do
37
- requires_foreman '>= 2.2.0'
37
+ requires_foreman '>= 2.4.0'
38
38
  divider :top_menu, :parent => :monitor_menu, :last => true, :caption => N_('Foreman Tasks')
39
39
  menu :top_menu, :tasks,
40
40
  :url_hash => { :controller => 'foreman_tasks/tasks', :action => :index },
@@ -76,6 +76,9 @@ module ForemanTasks
76
76
 
77
77
  widget 'foreman_tasks/tasks/dashboard/tasks_status', :sizex => 6, :sizey => 1, :name => N_('Task Status')
78
78
  widget 'foreman_tasks/tasks/dashboard/latest_tasks_in_error_warning', :sizex => 6, :sizey => 1, :name => N_('Latest Warning/Error Tasks')
79
+
80
+ ForemanTasks.dynflow.eager_load_actions!
81
+ extend_observable_events(::Dynflow::Action.descendants.select { |klass| klass <= ::Actions::ObservableAction }.map(&:namespaced_event_names))
79
82
  end
80
83
  end
81
84
 
@@ -108,7 +111,7 @@ module ForemanTasks
108
111
  ForemanTasks.dynflow.require!
109
112
  ::ForemanTasks.dynflow.config.on_init(false) do |world|
110
113
  world.middleware.use Actions::Middleware::KeepCurrentTaxonomies
111
- world.middleware.use Actions::Middleware::KeepCurrentUser
114
+ world.middleware.use Actions::Middleware::KeepCurrentUser, :before => ::Dynflow::Middleware::Common::Transaction
112
115
  world.middleware.use Actions::Middleware::KeepCurrentTimezone
113
116
  world.middleware.use Actions::Middleware::KeepCurrentRequestID
114
117
  end
@@ -252,7 +252,7 @@ namespace :foreman_tasks do
252
252
  renderer = TaskRender.new
253
253
  total = tasks.count
254
254
 
255
- tasks.each_with_index do |task, count|
255
+ tasks.find_each.with_index do |task, count|
256
256
  File.open(File.join(tmp_dir, "#{task.id}.html"), 'w') { |file| file.write(PageHelper.pagify(renderer.render_task(task))) }
257
257
  puts "#{count + 1}/#{total}"
258
258
  end
@@ -264,7 +264,7 @@ namespace :foreman_tasks do
264
264
  elsif format == 'csv'
265
265
  CSV.open(export_filename, 'wb') do |csv|
266
266
  csv << %w[id state type label result parent_task_id started_at ended_at]
267
- tasks.each do |task|
267
+ tasks.find_each do |task|
268
268
  csv << [task.id, task.state, task.type, task.label, task.result,
269
269
  task.parent_task_id, task.started_at, task.ended_at]
270
270
  end
@@ -1,3 +1,3 @@
1
1
  module ForemanTasks
2
- VERSION = '3.0.0'.freeze
2
+ VERSION = '4.0.0'.freeze
3
3
  end
data/package.json CHANGED
@@ -23,7 +23,7 @@
23
23
  "url": "http://projects.theforeman.org/projects/foreman-tasks/issues"
24
24
  },
25
25
  "peerDependencies": {
26
- "@theforeman/vendor": ">= 4.0.2"
26
+ "@theforeman/vendor": ">= 6.0.0"
27
27
  },
28
28
  "dependencies": {
29
29
  "c3": "^0.4.11",
@@ -32,11 +32,11 @@
32
32
  },
33
33
  "devDependencies": {
34
34
  "@babel/core": "^7.7.0",
35
- "@theforeman/builder": "^4.0.2",
36
- "@theforeman/eslint-plugin-foreman": "4.0.5",
37
- "@theforeman/stories": "^4.0.2",
38
- "@theforeman/test": "^4.0.2",
39
- "@theforeman/vendor-dev": "^4.0.2",
35
+ "@theforeman/builder": "^6.0.0",
36
+ "@theforeman/eslint-plugin-foreman": "6.0.0",
37
+ "@theforeman/stories": "^6.0.0",
38
+ "@theforeman/test": "^6.0.0",
39
+ "@theforeman/vendor-dev": "^6.0.0",
40
40
  "babel-eslint": "^10.0.3",
41
41
  "eslint": "^6.7.2",
42
42
  "jed": "^1.1.1",
@@ -45,6 +45,16 @@ module ForemanTasks
45
45
  end
46
46
  end
47
47
 
48
+ describe 'POST /api/tasks/bulk_search' do
49
+ it 'searching for a task' do
50
+ task = FactoryBot.create(:dynflow_task, :user_create_task)
51
+ post :bulk_search, params: { :searches => [{ :type => "task", :task_id => task.id, :search_id => "1" }] }
52
+ assert_response :success
53
+ data = JSON.parse(response.body)
54
+ _(data[0]['results'][0]['id']).must_equal task.id
55
+ end
56
+ end
57
+
48
58
  describe 'GET /api/tasks/show' do
49
59
  it 'searches for task' do
50
60
  task = FactoryBot.create(:dynflow_task, :user_create_task)