foreman-tasks 4.1.6 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby_tests.yml +0 -1
  3. data/app/controllers/foreman_tasks/api/tasks_controller.rb +2 -2
  4. data/app/controllers/foreman_tasks/tasks_controller.rb +7 -6
  5. data/app/graphql/types/recurring_logic.rb +18 -0
  6. data/app/graphql/types/task.rb +25 -0
  7. data/app/graphql/types/triggering.rb +16 -0
  8. data/app/lib/actions/helpers/with_continuous_output.rb +1 -1
  9. data/app/lib/actions/middleware/watch_delegated_proxy_sub_tasks.rb +6 -2
  10. data/app/models/foreman_tasks/recurring_logic.rb +2 -0
  11. data/app/models/foreman_tasks/task/dynflow_task.rb +3 -8
  12. data/app/models/foreman_tasks/task.rb +28 -0
  13. data/app/models/foreman_tasks/triggering.rb +2 -0
  14. data/app/services/foreman_tasks/dashboard_table_filter.rb +56 -0
  15. data/foreman-tasks.gemspec +0 -1
  16. data/lib/foreman_tasks/continuous_output.rb +50 -0
  17. data/lib/foreman_tasks/engine.rb +6 -15
  18. data/lib/foreman_tasks/tasks/export_tasks.rake +46 -90
  19. data/lib/foreman_tasks/version.rb +1 -1
  20. data/lib/foreman_tasks.rb +2 -5
  21. data/test/controllers/api/tasks_controller_test.rb +0 -11
  22. data/test/graphql/queries/recurring_logic_test.rb +28 -0
  23. data/test/graphql/queries/recurring_logics_query_test.rb +30 -0
  24. data/test/graphql/queries/task_query_test.rb +33 -0
  25. data/test/graphql/queries/tasks_query_test.rb +31 -0
  26. data/test/unit/dashboard_table_filter_test.rb +77 -0
  27. data/test/unit/task_test.rb +39 -8
  28. data/webpack/ForemanTasks/Components/TaskActions/TaskActionHelpers.js +4 -11
  29. data/webpack/ForemanTasks/Components/TaskActions/TaskActionHelpers.test.js +5 -27
  30. data/webpack/ForemanTasks/Components/TasksTable/TasksTable.js +0 -8
  31. data/webpack/ForemanTasks/Components/TasksTable/TasksTableActions.js +1 -6
  32. data/webpack/ForemanTasks/Components/TasksTable/TasksTableHelpers.js +1 -2
  33. data/webpack/ForemanTasks/Components/TasksTable/TasksTablePage.js +11 -22
  34. data/webpack/ForemanTasks/Components/TasksTable/TasksTableReducer.js +16 -17
  35. data/webpack/ForemanTasks/Components/TasksTable/TasksTableSelectors.js +0 -3
  36. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTableHelpers.test.js +1 -1
  37. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTableReducer.test.js +1 -3
  38. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTablePage.test.js.snap +2 -12
  39. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTableReducer.test.js.snap +0 -5
  40. data/webpack/ForemanTasks/Components/TasksTable/formatters/__test__/__snapshots__/selectionHeaderCellFormatter.test.js.snap +0 -1
  41. data/webpack/ForemanTasks/Components/TasksTable/formatters/__test__/selectionHeaderCellFormatter.test.js +1 -1
  42. data/webpack/ForemanTasks/Components/TasksTable/formatters/selectionHeaderCellFormatter.js +0 -1
  43. data/webpack/ForemanTasks/Components/TasksTable/index.js +0 -2
  44. metadata +18 -27
  45. data/test/core/unit/dispatcher_test.rb +0 -43
  46. data/test/core/unit/runner_test.rb +0 -116
  47. data/test/core/unit/task_launcher_test.rb +0 -56
  48. data/test/foreman_tasks_core_test_helper.rb +0 -4
  49. data/test/unit/otp_manager_test.rb +0 -77
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 113adfd7bfb8197852703d6bb8ca03e724b355a55723b8a0146c016607bb0024
4
- data.tar.gz: 9117207fc628934e82528eb2238f0bcb010e3f92654b621675485ca4f1a59f9e
3
+ metadata.gz: 3e48966ae6049eb85b3051151b7d15d48beb91a55b3580525fe114a3e045ad0f
4
+ data.tar.gz: 93da446f348f1a27600a79223ba4daab0e5e83417db711919392cfab2eedf57a
5
5
  SHA512:
6
- metadata.gz: 0b8228f8d1ea657cf35106884d1d1b537f0ac910af82c1d74f43a2eb28d2337c6a76cd4f89d927a7731af222453acd6439110b92281a9acbadf1696d1dd55ff4
7
- data.tar.gz: 365c53bf9ce1c3e3e15a9632828f867a83d74ecad2fdd7c1c7ce1b3ecd4d6d6f6775adef6f1e02fb6eff79cf6a6c437663e0a4c8e1cd773c9dded48445138cee
6
+ metadata.gz: 740ff700f75e43154068d3838611dc7c7306d7f5da91e3d2d41a6fce5ea0ccde84bd5f027545f24874b8fb112d9848505ae2a535386ee2f356852695dacd61a2
7
+ data.tar.gz: 8f61c3c868af4b7c3d3173b728858537c72ccf695e60b63971ec19f8a675a6ad9c19ecdd3dedb2549e2eb27b7cde62a2be67ead4d737bb43b774301abb303228
@@ -72,5 +72,4 @@ jobs:
72
72
  - name: Run plugin tests
73
73
  run: |
74
74
  bundle exec rake test:foreman_tasks
75
- bundle exec rake test:foreman_tasks_core
76
75
  bundle exec rake test TEST="test/unit/foreman/access_permissions_test.rb"
@@ -192,7 +192,7 @@ module ForemanTasks
192
192
  params[:order] = "#{ordering_params[:sort_by]} #{ordering_params[:sort_order]}"
193
193
  end
194
194
  params[:order] ||= 'started_at DESC'
195
- @tasks = resource_scope_for_index.order(params[:order].to_s)
195
+ @tasks = DashboardTableFilter.new(resource_scope_for_index, params).scope.order(params[:order].to_s)
196
196
  end
197
197
 
198
198
  def search_options
@@ -260,7 +260,7 @@ module ForemanTasks
260
260
  raise BadRequest,
261
261
  _('Resource search_params requires resource_type and resource_id to be specified')
262
262
  end
263
- scope.joins(:links).where(foreman_tasks_links:
263
+ scope.joins(:locks).where(foreman_tasks_locks:
264
264
  { resource_type: search_params[:resource_type],
265
265
  resource_id: search_params[:resource_id] })
266
266
  when 'task'
@@ -72,15 +72,17 @@ module ForemanTasks
72
72
  def unlock
73
73
  task = find_dynflow_task
74
74
  if task.paused?
75
- force_unlock(task)
75
+ task.state = :stopped
76
+ task.save!
77
+ render json: { statusText: 'OK' }
76
78
  else
77
79
  render json: {}, status: :bad_request
78
80
  end
79
81
  end
80
82
 
81
- def force_unlock(task = find_dynflow_task)
83
+ def force_unlock
84
+ task = find_dynflow_task
82
85
  task.state = :stopped
83
- task.locks.destroy_all
84
86
  task.save!
85
87
  render json: { statusText: 'OK' }
86
88
  end
@@ -125,9 +127,8 @@ module ForemanTasks
125
127
  end
126
128
 
127
129
  def filter(scope, paginate: true)
128
- search = current_taxonomy_search
129
- search = [search, params[:search]].select(&:present?).join(' AND ')
130
- scope = scope.search_for(search, order: params[:order])
130
+ scope = DashboardTableFilter.new(scope, params).scope
131
+ scope = scope.search_for(search_query, order: params[:order])
131
132
  scope = scope.paginate(page: params[:page], per_page: params[:per_page]) if paginate
132
133
  scope.distinct
133
134
  end
@@ -0,0 +1,18 @@
1
+ module Types
2
+ class RecurringLogic < Types::BaseObject
3
+ description 'A Recurring Logic'
4
+ model_class ::ForemanTasks::RecurringLogic
5
+
6
+ global_id_field :id
7
+ field :cron_line, String
8
+ field :end_time, GraphQL::Types::ISO8601DateTime
9
+ field :max_iteration, Integer
10
+ field :iteration, Integer
11
+ field :state, String
12
+ belongs_to :triggering, Types::Triggering
13
+
14
+ def self.graphql_definition
15
+ super.tap { |type| type.instance_variable_set(:@name, 'ForemanTasks::RecurringLogic') }
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,25 @@
1
+ module Types
2
+ class Task < Types::BaseObject
3
+ description 'A Task'
4
+ model_class ::ForemanTasks::Task
5
+
6
+ global_id_field :id
7
+ field :type, String
8
+ field :label, String
9
+ field :started_at, GraphQL::Types::ISO8601DateTime
10
+ field :ended_at, GraphQL::Types::ISO8601DateTime
11
+ field :state, String
12
+ field :result, String
13
+ field :external_id, String
14
+ field :parent_task_id, String
15
+ field :start_at, GraphQL::Types::ISO8601DateTime
16
+ field :start_before, GraphQL::Types::ISO8601DateTime
17
+ field :action, String
18
+ field :user_id, Integer
19
+ field :state_updated_at, GraphQL::Types::ISO8601DateTime
20
+
21
+ def self.graphql_definition
22
+ super.tap { |type| type.instance_variable_set(:@name, 'ForemanTasks::Task') }
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,16 @@
1
+ module Types
2
+ class Triggering < Types::BaseObject
3
+ description 'A Task Triggering'
4
+ model_class ::ForemanTasks::Triggering
5
+
6
+ global_id_field :id
7
+ field :mode, String
8
+ field :start_at, GraphQL::Types::ISO8601DateTime
9
+ field :start_before, GraphQL::Types::ISO8601DateTime
10
+ field :recurring_logic, Types::RecurringLogic
11
+
12
+ def self.graphql_definition
13
+ super.tap { |type| type.instance_variable_set(:@name, 'ForemanTasks::Triggering') }
14
+ end
15
+ end
16
+ end
@@ -8,7 +8,7 @@ module Actions
8
8
  end
9
9
 
10
10
  def continuous_output
11
- continuous_output = ::ForemanTasksCore::ContinuousOutput.new
11
+ continuous_output = ::ForemanTasks::ContinuousOutput.new
12
12
  continuous_output_providers.each do |continous_output_provider|
13
13
  continous_output_provider.fill_continuous_output(continuous_output)
14
14
  end
@@ -20,7 +20,9 @@ module Actions
20
20
  private
21
21
 
22
22
  def set_clock
23
- action.plan_event(CheckOnProxyActions, POLL_INTERVAL, optional: true)
23
+ action.world.clock.ping action.send(:suspended_action),
24
+ POLL_INTERVAL,
25
+ CheckOnProxyActions
24
26
  end
25
27
 
26
28
  def check_triggered
@@ -42,7 +44,9 @@ module Actions
42
44
 
43
45
  def notify(event, tasks)
44
46
  tasks.each do |task|
45
- action.plan_event(event, execution_plan_id: task.execution_plan_id, step_id: task.step_id)
47
+ action.world.event task.execution_plan_id,
48
+ task.step_id,
49
+ event
46
50
  end
47
51
  end
48
52
 
@@ -4,6 +4,8 @@ module ForemanTasks
4
4
  class RecurringLogic < ApplicationRecord
5
5
  include Authorizable
6
6
 
7
+ graphql_type '::Types::RecurringLogic'
8
+
7
9
  belongs_to :task_group
8
10
  belongs_to :triggering
9
11
 
@@ -140,17 +140,12 @@ module ForemanTasks
140
140
 
141
141
  def main_action
142
142
  return @main_action if defined?(@main_action)
143
-
144
- @main_action = execution_plan && execution_plan.root_plan_step.try(:action, execution_plan)
145
143
  if active_job?
146
144
  job_data = active_job_data
147
- begin
148
- @main_action = active_job_action(job_data['job_class'], job_data['arguments'])
149
- rescue => e
150
- Foreman::Logging.exception("Failed to load ActiveJob for task #{id}", e, :logger => 'foreman-tasks')
151
- end
145
+ @main_action = active_job_action(job_data['job_class'], job_data['arguments'])
146
+ else
147
+ @main_action = execution_plan && execution_plan.root_plan_step.try(:action, execution_plan)
152
148
  end
153
- @main_action
154
149
  end
155
150
 
156
151
  # The class for ActiveJob jobs in Dynflow, JobWrapper is not expected to
@@ -5,6 +5,8 @@ module ForemanTasks
5
5
  include Authorizable
6
6
  extend Search
7
7
 
8
+ graphql_type '::Types::Task'
9
+
8
10
  def check_permissions_after_save
9
11
  # there's no create_tasks permission, tasks are created as a result of internal actions, in such case we
10
12
  # don't do authorization, that should have been performed on wrapping action level
@@ -255,6 +257,32 @@ module ForemanTasks
255
257
  main_action.continuous_output.raw_outputs
256
258
  end
257
259
 
260
+ def self.latest_tasks_by_resource_ids(label, resource_type, resource_ids)
261
+ tasks = arel_table
262
+ links = ForemanTasks::Link.arel_table
263
+ started_at = tasks[:started_at]
264
+ resource_id = links[:resource_id]
265
+
266
+ base_combined_table = tasks
267
+ .join(links).on(tasks[:id].eq(links[:task_id]))
268
+ .where(tasks[:label].eq(label)
269
+ .and(links[:resource_type].eq(resource_type))
270
+ .and(links[:resource_id].in(resource_ids)))
271
+
272
+ grouped = base_combined_table.project(
273
+ started_at.maximum.as('started_at_max'),
274
+ resource_id
275
+ ).group(resource_id).order(resource_id).as('grouped')
276
+
277
+ max_per_resource_id = tasks
278
+ .join(links).on(tasks[:id].eq(links[:task_id]))
279
+ .join(grouped).on(grouped[:started_at_max].eq(started_at).and(grouped[:resource_id].eq(resource_id)))
280
+ .distinct
281
+ .project(tasks[Arel.star], grouped[:resource_id])
282
+
283
+ find_by_sql(max_per_resource_id.to_sql).index_by(&:resource_id)
284
+ end
285
+
258
286
  protected
259
287
 
260
288
  def generate_id
@@ -5,6 +5,8 @@ module ForemanTasks
5
5
  :end_time].freeze
6
6
  attr_accessor(*PARAMS)
7
7
 
8
+ graphql_type '::Types::Triggering'
9
+
8
10
  before_save do
9
11
  if future?
10
12
  parse_start_at!
@@ -0,0 +1,56 @@
1
+ module ForemanTasks
2
+ # narrows the scope for the tasks table based on params coming from tasks dashboard
3
+ #
4
+ # Supported filters:
5
+ #
6
+ # * :result
7
+ # * :state
8
+ # * :time_horizon - expected format of Hxy, where the xy is the time horizon in hours we're interested in
9
+ # :time_mode can be set to 'recent' to filter the recent tasks, or 'older' (default) to filter earlier ones
10
+ class DashboardTableFilter
11
+ def initialize(scope, params)
12
+ @scope = scope
13
+ @params = params
14
+ end
15
+
16
+ def scope
17
+ @new_scope = @scope
18
+ scope_by(:result)
19
+ scope_by(:state)
20
+ scope_by_time
21
+ @new_scope
22
+ end
23
+
24
+ private
25
+
26
+ def scope_by(field)
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
32
+ end
33
+
34
+ def scope_by_time
35
+ return if @params[:time_horizon].blank?
36
+ hours = if @params[:time_horizon].casecmp('week') == 0
37
+ 24 * 7
38
+ else
39
+ @params[:time_horizon][/\AH(\d{1,2})$/i, 1]
40
+ end
41
+
42
+ unless hours
43
+ raise Foreman::Exception, 'Unexpected format of time: should be in form of "H24" or equal to "week"'
44
+ end
45
+ timestamp = Time.now.utc - hours.to_i.hours
46
+ case @params[:time_mode]
47
+ when 'recent'
48
+ operator = '>'
49
+ else
50
+ operator = '<'
51
+ search_suffix = 'OR state_updated_at IS NULL'
52
+ end
53
+ @new_scope = @new_scope.where("state_updated_at #{operator} ? #{search_suffix}", timestamp)
54
+ end
55
+ end
56
+ end
@@ -29,7 +29,6 @@ same resource. It also optionally provides Dynflow infrastructure for using it f
29
29
  s.extra_rdoc_files = Dir['README*', 'LICENSE']
30
30
 
31
31
  s.add_dependency "dynflow", '>= 1.2.3'
32
- s.add_dependency "foreman-tasks-core"
33
32
  s.add_dependency "get_process_mem" # for memory polling
34
33
  s.add_dependency "parse-cron", '~> 0.1.4'
35
34
  s.add_dependency "sinatra" # for Dynflow web console
@@ -0,0 +1,50 @@
1
+ module ForemanTasks
2
+ class ContinuousOutput
3
+ attr_accessor :raw_outputs
4
+
5
+ def initialize(raw_outputs = [])
6
+ @raw_outputs = []
7
+ raw_outputs.each { |raw_output| add_raw_output(raw_output) }
8
+ end
9
+
10
+ def add_raw_output(raw_output)
11
+ missing_args = %w[output_type output timestamp] - raw_output.keys
12
+ unless missing_args.empty?
13
+ raise ArgumentError, "Missing args for raw output: #{missing_args.inspect}"
14
+ end
15
+ @raw_outputs << raw_output
16
+ end
17
+
18
+ def empty?
19
+ @raw_outputs.empty?
20
+ end
21
+
22
+ def last_timestamp
23
+ return if @raw_outputs.empty?
24
+ @raw_outputs.last.fetch('timestamp')
25
+ end
26
+
27
+ def sort!
28
+ @raw_outputs.sort_by! { |record| record['timestamp'].to_f }
29
+ end
30
+
31
+ def humanize
32
+ sort!
33
+ raw_outputs.map { |output| output['output'] }.join("\n")
34
+ end
35
+
36
+ def add_exception(context, exception, timestamp = Time.now.getlocal)
37
+ add_output(context + ": #{exception.class} - #{exception.message}", 'debug', timestamp)
38
+ end
39
+
40
+ def add_output(*args)
41
+ add_raw_output(self.class.format_output(*args))
42
+ end
43
+
44
+ def self.format_output(message, type = 'debug', timestamp = Time.now.getlocal)
45
+ { 'output_type' => type,
46
+ 'output' => message,
47
+ 'timestamp' => timestamp.to_f }
48
+ end
49
+ end
50
+ end
@@ -1,4 +1,3 @@
1
- require 'foreman_tasks_core'
2
1
  require 'fast_gettext'
3
2
  require 'gettext_i18n_rails'
4
3
 
@@ -34,7 +33,7 @@ module ForemanTasks
34
33
 
35
34
  initializer 'foreman_tasks.register_plugin', :before => :finisher_hook do |_app|
36
35
  Foreman::Plugin.register :"foreman-tasks" do
37
- requires_foreman '>= 2.4.0'
36
+ requires_foreman '>= 2.6.0'
38
37
  divider :top_menu, :parent => :monitor_menu, :last => true, :caption => N_('Foreman Tasks')
39
38
  menu :top_menu, :tasks,
40
39
  :url_hash => { :controller => 'foreman_tasks/tasks', :action => :index },
@@ -66,6 +65,11 @@ module ForemanTasks
66
65
 
67
66
  add_all_permissions_to_default_roles
68
67
 
68
+ register_graphql_query_field :task, '::Types::Task', :record_field
69
+ register_graphql_query_field :tasks, '::Types::Task', :collection_field
70
+ register_graphql_query_field :recurring_logic, '::Types::RecurringLogic', :record_field
71
+ register_graphql_query_field :recurring_logics, '::Types::RecurringLogic', :collection_field
72
+
69
73
  logger :dynflow, :enabled => true
70
74
  logger :action, :enabled => true
71
75
 
@@ -116,19 +120,6 @@ module ForemanTasks
116
120
  world.middleware.use Actions::Middleware::KeepCurrentRequestID
117
121
  world.middleware.use ::Actions::Middleware::LoadSettingValues if Gem::Version.new(::SETTINGS[:version]) >= Gem::Version.new('2.5')
118
122
  end
119
-
120
- ::ForemanTasks.dynflow.config.on_init do |world|
121
- ForemanTasksCore.dynflow_setup(world)
122
- end
123
- end
124
-
125
- initializer 'foreman_tasks.set_core_settings' do
126
- ForemanTasksCore::SettingsLoader.settings_registry.each_key do |settings_keys|
127
- settings = settings_keys.inject({}) do |h, settings_key|
128
- h.merge(SETTINGS[settings_key] || {})
129
- end
130
- ForemanTasksCore::SettingsLoader.setup_settings(settings_keys.first, settings)
131
- end
132
123
  end
133
124
 
134
125
  # to enable async Foreman operations using Dynflow
@@ -12,7 +12,7 @@ namespace :foreman_tasks do
12
12
 
13
13
  * TASK_SEARCH : scoped search filter (example: 'label = "Actions::Foreman::Host::ImportFacts"')
14
14
  * TASK_FILE : file to export to
15
- * TASK_FORMAT : format to use for the export (either html, html-dir or csv)
15
+ * TASK_FORMAT : format to use for the export (either html or csv)
16
16
  * TASK_DAYS : number of days to go back
17
17
 
18
18
  If TASK_SEARCH is not defined, it defaults to all tasks in the past 7 days and
@@ -185,27 +185,23 @@ namespace :foreman_tasks do
185
185
  end
186
186
 
187
187
  class PageHelper
188
- def self.pagify(io, template = nil)
189
- io.write <<~HTML
190
- <html>
191
- <head>
192
- <title>Dynflow Console</title>
193
- <script src="jquery.js"></script>
194
- <link rel="stylesheet" type="text/css" href="bootstrap.css">
195
- <link rel="stylesheet" type="text/css" href="application.css">
196
- <script src="bootstrap.js"></script>
197
- <script src="run_prettify.js"></script>
198
- <script src="application.js"></script>
199
- </head>
200
- <body>
188
+ def self.pagify(template)
189
+ pre = <<-HTML
190
+ <html>
191
+ <head>
192
+ <title>Dynflow Console</title>
193
+ <script src="jquery.js"></script>
194
+ <link rel="stylesheet" type="text/css" href="bootstrap.css">
195
+ <link rel="stylesheet" type="text/css" href="application.css">
196
+ <script src="bootstrap.js"></script>
197
+ <script src="run_prettify.js"></script>
198
+ <script src="application.js"></script>
199
+ </head>
200
+ <body>
201
+ #{template}
202
+ <body>
203
+ </html>
201
204
  HTML
202
- if block_given?
203
- yield io
204
- else
205
- io.write template
206
- end
207
- ensure
208
- io.write '</body></html>'
209
205
  end
210
206
 
211
207
  def self.copy_assets(tmp_dir)
@@ -220,64 +216,13 @@ namespace :foreman_tasks do
220
216
  end
221
217
  end
222
218
 
223
- def self.generate_with_index(io)
224
- io.write '<div><table class="table">'
225
- yield io
226
- ensure
227
- io.write '</table></div>'
228
- end
229
-
230
- def self.generate_index_entry(io, task)
231
- io << <<~HTML
232
- <tr>
233
- <td><a href=\"#{task.id}.html\">#{task.label}</a></td>
234
- <td>#{task.started_at}</td>
235
- <td>#{task.state}</td>
236
- <td>#{task.result}</td>
237
- </tr>
238
- HTML
239
- end
240
- end
241
-
242
- def csv_export(export_filename, tasks)
243
- CSV.open(export_filename, 'wb') do |csv|
244
- csv << %w[id state type label result parent_task_id started_at ended_at]
245
- tasks.find_each do |task|
246
- csv << [task.id, task.state, task.type, task.label, task.result,
247
- task.parent_task_id, task.started_at, task.ended_at]
219
+ def self.generate_index(tasks)
220
+ html = '<div><table class="table">'
221
+ tasks.order('started_at desc').all.each do |task|
222
+ html << "<tr><td><a href=\"#{task.id}.html\">#{task.label}</a></td><td>#{task.started_at}</td>\
223
+ <td>#{task.state}</td><td>#{task.result}</td></tr>"
248
224
  end
249
- end
250
- end
251
-
252
- def html_export(workdir, tasks)
253
- PageHelper.copy_assets(workdir)
254
-
255
- renderer = TaskRender.new
256
- total = tasks.count
257
- index = File.open(File.join(workdir, 'index.html'), 'w')
258
-
259
- File.open(File.join(workdir, 'index.html'), 'w') do |index|
260
- PageHelper.pagify(index) do |io|
261
- PageHelper.generate_with_index(io) do |index|
262
- tasks.find_each.each_with_index do |task, count|
263
- File.open(File.join(workdir, "#{task.id}.html"), 'w') { |file| PageHelper.pagify(file, renderer.render_task(task)) }
264
- PageHelper.generate_index_entry(index, task)
265
- puts "#{count + 1}/#{total}"
266
- end
267
- end
268
- end
269
- end
270
- end
271
-
272
- def generate_filename(format)
273
- base = "/tmp/task-export-#{Time.now.to_i}"
274
- case format
275
- when 'html'
276
- base + '.tar.gz'
277
- when 'csv'
278
- base + '.csv'
279
- when 'html-dir'
280
- base
225
+ html << '</table></div>'
281
226
  end
282
227
  end
283
228
 
@@ -294,25 +239,36 @@ namespace :foreman_tasks do
294
239
  end
295
240
 
296
241
  format = ENV['TASK_FORMAT'] || 'html'
297
- export_filename = ENV['TASK_FILE'] || generate_filename(format)
242
+ export_filename = ENV['TASK_FILE'] || "/tmp/task-export-#{Time.now.to_i}.#{format == 'csv' ? 'csv' : 'tar.gz'}"
298
243
 
299
- tasks = ForemanTasks::Task.search_for(filter).order(:started_at => :desc)
244
+ tasks = ForemanTasks::Task.search_for(filter)
300
245
 
301
246
  puts _("Exporting all tasks matching filter #{filter}")
302
247
  puts _("Gathering #{tasks.count} tasks.")
303
- case format
304
- when 'html'
248
+ if format == 'html'
305
249
  Dir.mktmpdir('task-export') do |tmp_dir|
306
- html_export(tmp_dir, tasks)
250
+ PageHelper.copy_assets(tmp_dir)
251
+
252
+ renderer = TaskRender.new
253
+ total = tasks.count
254
+
255
+ tasks.find_each.with_index do |task, count|
256
+ File.open(File.join(tmp_dir, "#{task.id}.html"), 'w') { |file| file.write(PageHelper.pagify(renderer.render_task(task))) }
257
+ puts "#{count + 1}/#{total}"
258
+ end
259
+
260
+ File.open(File.join(tmp_dir, 'index.html'), 'w') { |file| file.write(PageHelper.pagify(PageHelper.generate_index(tasks))) }
261
+
307
262
  system("tar", "czf", export_filename, tmp_dir)
308
263
  end
309
- when 'html-dir'
310
- FileUtils.mkdir_p(export_filename)
311
- html_export(export_filename, tasks)
312
- when 'csv'
313
- csv_export(export_filename, tasks)
314
- else
315
- raise "Unkonwn export format '#{format}'"
264
+ elsif format == 'csv'
265
+ CSV.open(export_filename, 'wb') do |csv|
266
+ csv << %w[id state type label result parent_task_id started_at ended_at]
267
+ tasks.find_each do |task|
268
+ csv << [task.id, task.state, task.type, task.label, task.result,
269
+ task.parent_task_id, task.started_at, task.ended_at]
270
+ end
271
+ end
316
272
  end
317
273
 
318
274
  puts "Created #{export_filename}"
@@ -1,3 +1,3 @@
1
1
  module ForemanTasks
2
- VERSION = '4.1.6'.freeze
2
+ VERSION = '5.0.0'.freeze
3
3
  end
data/lib/foreman_tasks.rb CHANGED
@@ -6,17 +6,14 @@ require 'foreman_tasks/dynflow/configuration'
6
6
  require 'foreman_tasks/triggers'
7
7
  require 'foreman_tasks/authorizer_ext'
8
8
  require 'foreman_tasks/cleaner'
9
+ require 'foreman_tasks/continuous_output'
9
10
 
10
11
  module ForemanTasks
11
12
  extend Algebrick::TypeCheck
12
13
  extend Algebrick::Matching
13
14
 
14
15
  def self.dynflow
15
- @dynflow ||= begin
16
- world = ForemanTasks::Dynflow.new(nil, ForemanTasks::Dynflow::Configuration.new)
17
- ForemanTasksCore.dynflow_setup(world) if defined?(ForemanTasksCore)
18
- world
19
- end
16
+ @dynflow ||= ForemanTasks::Dynflow.new(nil, ForemanTasks::Dynflow::Configuration.new)
20
17
  end
21
18
 
22
19
  def self.trigger(action, *args, &block)
@@ -68,17 +68,6 @@ module ForemanTasks
68
68
  data = JSON.parse(response.body)
69
69
  _(data[0]['results'][0]['id']).must_equal task.id
70
70
  end
71
-
72
- it 'can search for a specific resource' do
73
- org = FactoryBot.create(:organization)
74
- task = FactoryBot.create(:task_with_links, resource_id: org.id, resource_type: 'Organization')
75
-
76
- post :bulk_search, params: { :searches => [{ :type => 'resource', :resource_id => org.id, :resource_type => 'Organization' }] }
77
-
78
- assert_response :success
79
- data = JSON.parse(response.body)
80
- _(data[0]['results'][0]['id']).must_equal task.id
81
- end
82
71
  end
83
72
 
84
73
  describe 'GET /api/tasks/show' do
@@ -0,0 +1,28 @@
1
+ require 'foreman_tasks_test_helper'
2
+
3
+ module Queries
4
+ class RecurringLogicTest < GraphQLQueryTestCase
5
+ let(:query) do
6
+ <<-GRAPHQL
7
+ query($id: String!) {
8
+ recurringLogic(id: $id) {
9
+ id
10
+ cronLine
11
+ }
12
+ }
13
+ GRAPHQL
14
+ end
15
+
16
+ let(:cron_line) { '5 4 3 2 1' }
17
+ let(:recurring_logic) { FactoryBot.create(:recurring_logic, :cron_line => cron_line) }
18
+ let(:global_id) { Foreman::GlobalId.for(recurring_logic) }
19
+ let(:variables) { { id: global_id } }
20
+ let(:data) { result['data']['recurringLogic'] }
21
+
22
+ test "should fetch recurring logic" do
23
+ assert_empty result['errors']
24
+ assert_equal global_id, data['id']
25
+ assert_equal cron_line, data['cronLine']
26
+ end
27
+ end
28
+ end