diff --git a/app/controllers/api/v2/job_invocations_controller.rb b/app/controllers/api/v2/job_invocations_controller.rb index c14b8c158..0bf640111 100644 --- a/app/controllers/api/v2/job_invocations_controller.rb +++ b/app/controllers/api/v2/job_invocations_controller.rb @@ -19,13 +19,16 @@ def index api :GET, '/job_invocations/:id', N_('Show job invocation') param :id, :identifier, :required => true - param :host_status, :bool, required: false, desc: N_('Show Job status for the hosts') + param :include_hosts, :bool, :required => false, :default_value => true, :desc => N_('Include hosts and template invocations in the response. Defaults to true for backwards compatibility. Pass false to skip serializing all hosts.') + param :host_status, :bool, :required => false, :default_value => false, :desc => N_('Show job status for each host, only applicable when include_hosts is true') def show - set_hosts_and_template_invocations + @pattern_template_invocations = @job_invocation.pattern_template_invocations.includes(:input_values) @job_organization = Taxonomy.find_by(id: @job_invocation.task.input[:current_organization_id]) @job_location = Taxonomy.find_by(id: @job_invocation.task.input[:current_location_id]) - if params[:host_status] == 'true' - set_statuses_and_smart_proxies + + if include_hosts? + set_hosts_and_template_invocations + set_statuses_and_smart_proxies if Foreman::Cast.to_bool(params[:host_status]) end end @@ -84,13 +87,14 @@ def show api :POST, '/job_invocations/', N_('Create a job invocation') param_group :job_invocation, :as => :create + param :include_hosts, :bool, :required => false, :default_value => true, :desc => N_('Include hosts and template invocations in the response. Defaults to true for backwards compatibility. Pass false to skip serializing all hosts.') def create composer = JobInvocationComposer.from_api_params( job_invocation_params ) composer.trigger! @job_invocation = composer.job_invocation - @hosts = @job_invocation.targeting.hosts + set_hosts_and_template_invocations if include_hosts? process_response @job_invocation rescue JobInvocationComposer::JobTemplateNotFound, JobInvocationComposer::FeatureNotFound => e not_found(error: { message: e.message }) @@ -298,7 +302,6 @@ def parent_scope end def set_hosts_and_template_invocations - @pattern_template_invocations = @job_invocation.pattern_template_invocations.includes(:input_values) @hosts = @job_invocation.targeting.hosts.authorized(:view_hosts, Host) if params[:search].present? @@ -310,6 +313,10 @@ def set_hosts_and_template_invocations .includes(:input_values) end + def include_hosts? + params[:include_hosts].nil? || Foreman::Cast.to_bool(params[:include_hosts]) + end + def set_statuses_and_smart_proxies template_invocations = @template_invocations.where(host_id: @hosts.select(:id)) .includes(:run_host_job_task).to_a diff --git a/app/views/api/v2/job_invocations/main.json.rabl b/app/views/api/v2/job_invocations/main.json.rabl index 20cf4ac0e..3e4111a44 100644 --- a/app/views/api/v2/job_invocations/main.json.rabl +++ b/app/views/api/v2/job_invocations/main.json.rabl @@ -19,12 +19,14 @@ child :targeting do attributes :bookmark_id, :bookmark_name, :search_query, :targeting_type, :user_id, :status, :status_label, :randomized_ordering - child @hosts => :hosts do - extends 'api/v2/hosts/base' + if @hosts + child @hosts => :hosts do + extends 'api/v2/hosts/base' - if params[:host_status] == 'true' - node :job_status do |host| - @host_statuses[host.id] + if @host_statuses + node :job_status do |host| + @host_statuses[host.id] + end end end end @@ -34,12 +36,14 @@ child :task do attributes :id, :state, :started_at end -child @template_invocations do - attributes :template_id, :template_name, :host_id - child :input_values do - attributes :template_input_name, :template_input_id - node :value do |iv| - iv.template_input.respond_to?(:hidden_value) && iv.template_input.hidden_value? ? '*' * 5 : iv.value +if @template_invocations + child @template_invocations => :template_invocations do + attributes :template_id, :template_name, :host_id + child :input_values do + attributes :template_input_name, :template_input_id + node :value do |iv| + iv.template_input.respond_to?(:hidden_value) && iv.template_input.hidden_value? ? '*' * 5 : iv.value + end end end end diff --git a/test/functional/api/v2/job_invocations_controller_test.rb b/test/functional/api/v2/job_invocations_controller_test.rb index 6c5fea2ee..2b9d84e2b 100644 --- a/test/functional/api/v2/job_invocations_controller_test.rb +++ b/test/functional/api/v2/job_invocations_controller_test.rb @@ -27,7 +27,10 @@ class JobInvocationsControllerTest < ActionController::TestCase template = ActiveSupport::JSON.decode(@response.body) assert_not_empty template assert_equal template['job_category'], @invocation.job_category - assert_not_empty template['targeting']['hosts'] + assert_not_nil template['targeting'] + assert_not_empty template['pattern_template_invocations'] + assert_not_nil template['targeting']['hosts'] + assert_not_nil template['template_invocations'] end test 'should get invocation detail when taxonomies are set' do @@ -36,9 +39,17 @@ class JobInvocationsControllerTest < ActionController::TestCase assert_response :success end + test 'should exclude hosts when include_hosts=false' do + get :show, params: { :id => @invocation.id, :include_hosts => false } + assert_response :success + result = ActiveSupport::JSON.decode(@response.body) + assert_nil result['targeting']['hosts'] + assert_nil result['template_invocations'] + end + test 'should see only permitted hosts' do - @user = FactoryBot.create(:user, admin: false) - @invocation.task.update(user: @user) + @user = FactoryBot.create(:user, :admin => false) + @invocation.task.update(:user => @user) setup_user('view', 'job_invocations', nil, @user) setup_user('view', 'hosts', 'name ~ nope.example.com', @user) @@ -47,6 +58,29 @@ class JobInvocationsControllerTest < ActionController::TestCase response = ActiveSupport::JSON.decode(@response.body) assert_equal response['targeting']['hosts'], [] end + + test 'should ignore host_status when include_hosts=false' do + get :show, params: { :id => @invocation.id, :include_hosts => false, :host_status => true } + assert_response :success + result = ActiveSupport::JSON.decode(@response.body) + assert_nil result['targeting']['hosts'] + assert_nil result['template_invocations'] + end + + test 'should include job_status per host when host_status=true' do + invocation = FactoryBot.create(:job_invocation, :with_template, :with_task) + invocation.template_invocations << FactoryBot.create(:template_invocation, :with_task, :with_host, :job_invocation => invocation) + invocation.job_category = invocation.pattern_template_invocations.first.template.job_category + invocation.targeting.hosts = invocation.template_invocations.map(&:host) + invocation.save! + + get :show, params: { :id => invocation.id, :include_hosts => true, :host_status => true } + assert_response :success + result = ActiveSupport::JSON.decode(@response.body) + hosts = result['targeting']['hosts'] + assert_not_empty hosts + assert hosts.all? { |h| h.key?('job_status') }, 'Expected job_status to be present on each host' + end end context 'creation' do @@ -66,6 +100,20 @@ class JobInvocationsControllerTest < ActionController::TestCase assert_response :success end + test 'should include hosts in create response by default' do + post :create, params: { job_invocation: @attrs } + invocation = ActiveSupport::JSON.decode(@response.body) + assert_not_nil invocation['targeting']['hosts'] + assert_response :success + end + + test 'should exclude hosts from create response when include_hosts=false' do + post :create, params: { job_invocation: @attrs, include_hosts: false } + invocation = ActiveSupport::JSON.decode(@response.body) + assert_nil invocation['targeting']['hosts'] + assert_response :success + end + test 'should create with description format overridden' do @attrs[:description_format] = 'format' post :create, params: { job_invocation: @attrs } diff --git a/webpack/JobInvocationDetail/JobInvocationActions.js b/webpack/JobInvocationDetail/JobInvocationActions.js index a237a527b..131696068 100644 --- a/webpack/JobInvocationDetail/JobInvocationActions.js +++ b/webpack/JobInvocationDetail/JobInvocationActions.js @@ -19,7 +19,7 @@ export const getJobInvocation = url => dispatch => { const fetchData = withInterval( get({ key: JOB_INVOCATION_KEY, - params: { include_permissions: true }, + params: { include_permissions: true, include_hosts: false }, url, handleError: () => { dispatch(stopInterval(JOB_INVOCATION_KEY)); @@ -44,6 +44,7 @@ export const updateJob = jobId => dispatch => { APIActions.get({ url, key: UPDATE_JOB, + params: { include_hosts: false }, }) ); };