Skip to content

Commit

Permalink
expand
Browse files Browse the repository at this point in the history
  • Loading branch information
MariaAga committed Dec 20, 2024
1 parent 7a10d39 commit f0002f3
Show file tree
Hide file tree
Showing 20 changed files with 1,523 additions and 43 deletions.
66 changes: 66 additions & 0 deletions app/controllers/api/v2/template_invocations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
module Api
module V2
class TemplateInvocationsController < ::Api::V2::BaseController
include RemoteExecutionHelper

before_action :find_job_invocation, :only => %w{template_invocations}

Expand All @@ -23,8 +24,73 @@ def resource_scope
end
end

api :GET, '/hosts/:host_id/job_invocation/:job_invocation_id/show_template_invocation/'
param :host_id, :identifier, :required => true
param :job_invocation_id, :identifier, :required => true
def show_template_invocation_by_host
@host = Host.find(params[:host_id])
@job_invocation = JobInvocation.find(params[:job_invocation_id])
if @host.nil?
render :json => { :error => _('Host not found') }, :status => :bad_request
end
if @job_invocation.nil?
render :json => { :error => _('Job invocation not found') }, :status => :bad_request
end
@template_invocation = @job_invocation.template_invocations.find { |template_inv| template_inv.host_id == @host.id }
@template = TemplateInvocation.find(@template_invocation.id)
if @template_invocation.nil? || @template.nil?
render :json => { :error => _('Template invocation not found') }, :status => :bad_request
end
@template_invocation_task = @template_invocation.run_host_job_task

lines = normalize_line_sets(@template_invocation_task.main_action.live_output)
input_values_with_template_input = @template_invocation.input_values.joins(:template_input).as_json(include: :template_input)
transformed_input_values = input_values_with_template_input.map do |input_value|
template_input = input_value['template_input']
value = template_input["hidden_value"] ? '*' * 5 : input_value["value"]
{
name: input_value['template_input']['name'],
value: value,
}
end
# :permission => :view_foreman_tasks
# :permission => :cancel_job_invocations
auto_refresh = @job_invocation.task.try(:pending?)
finished = @job_invocation.status_label == 'failed' || @job_invocation.status_label == 'succeeded' || @job_invocation.status_label == 'cancelled'
render :json => { :output => lines, :preview => template_invocation_preview(@template_invocation, @host), :input_values => transformed_input_values, :job_invocation_description => @job_invocation.description, :task_id => @template_invocation_task.id, :task_cancellable => @template_invocation_task.cancellable?, :host_name => @host.name, :permissions => {
:view_foreman_tasks => User.current.allowed_to?(:view_foreman_tasks),
:cancel_job_invocations => User.current.allowed_to?(:cancel_job_invocations),
:execute_jobs => User.current.allowed_to?(:create_job_invocations) && (!@host.infrastructure_host? || User.current.can?(:execute_jobs_on_infrastructure_hosts)),

},
:auto_refresh => auto_refresh, :finished => finished}, status: :ok
end

private

def template_invocation_preview(template_invocation, host)
if host.nil?
return {
status: :bad_request,
plain: _('Host not found'),
}
end
if template_invocation.nil?
return {
status: :bad_request,
plain: _('Template invocation not found'),
}
end
renderer = InputTemplateRenderer.new(template_invocation.template, host, template_invocation)
output = load_template_from_task(template_invocation, host) || renderer.preview
if output
{:plain => output}
else
{status: :bad_request,
plain: renderer.error_message }
end
end

def resource_scope_for_template_invocations
@job_invocation.template_invocations
.includes(:host)
Expand Down
1 change: 1 addition & 0 deletions app/helpers/remote_execution_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ def normalize_line_sets(line_sets)
end
previous_line_break = line_set['output'] =~ /\n\Z/
end
puts(line_sets.to_json)
line_sets
end

Expand Down
2 changes: 1 addition & 1 deletion app/views/api/v2/job_invocations/hosts.json.rabl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
collection @hosts

attribute :name, :operatingsystem_id, :operatingsystem_name, :hostgroup_id, :hostgroup_name
attribute :name, :operatingsystem_id, :operatingsystem_name, :hostgroup_id, :hostgroup_name, :id

node :job_status do |host|
@host_statuses[host.id]
Expand Down
3 changes: 3 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
match 'old/job_invocations/new', to: 'job_invocations#new', via: [:get], as: 'form_new_job_invocation'
match 'old/job_invocations/:id/rerun', to: 'job_invocations#rerun', via: [:get, :post], as: 'form_rerun_job_invocation'
match 'experimental/job_invocations_detail/:id', to: 'react#index', :via => [:get], as: 'new_job_invocation_detail'
match 'job_invocations_detail/:id/host_invocation/:host_id', to: 'react#index', :via => [:get], as: 'new_job_invocation_detail_by_host'

resources :job_invocations, :only => [:create, :show, :index] do
collection do
Expand Down Expand Up @@ -98,6 +99,8 @@
get 'hosts/:id/available_remote_execution_features', to: 'remote_execution_features#available_remote_execution_features'

resources :remote_execution_features, :only => [:show, :index, :update]

get 'show_template_invocation_by_host/:host_id/job_invocation/:job_invocation_id', to: 'template_invocations#show_template_invocation_by_host'
end
end
end
14 changes: 14 additions & 0 deletions webpack/JobInvocationDetail/JobInvocationConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ export const JOB_INVOCATION_HOSTS = 'JOB_INVOCATION_HOSTS';
export const currentPermissionsUrl = foremanUrl(
'/api/v2/permissions/current_permissions'
);
export const GET_TEMPLATE_INVOCATION = 'GET_TEMPLATE_INVOCATION';
export const showTemplateInvocationUrl = (hostID, jobID) =>
`/api/show_template_invocation_by_host/${hostID}/job_invocation/${jobID}`;

export const templateInvocationPageUrl = (hostID, jobID) =>
`/job_invocations_detail/${jobID}/host_invocation/${hostID}`;

export const jobInvocationDetailsUrl = id =>
`/experimental/job_invocations_detail/${id}`;

export const STATUS = {
PENDING: 'pending',
Expand Down Expand Up @@ -65,6 +74,11 @@ const Columns = () => {
const hostDetailsPageUrl = useForemanHostDetailsPageUrl();

return {
expand: {
title: '',
weight: 0,
wrapper: () => null,
},
name: {
title: __('Name'),
wrapper: ({ name }) => (
Expand Down
62 changes: 62 additions & 0 deletions webpack/JobInvocationDetail/JobInvocationDetail.scss
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,65 @@
height: $chart_size;
}
}

.template-invocation {
&.output-in-table-view {
div.invocation-output {
overflow: auto;
max-height: 25em;
}
}
div.invocation-output {
// overflow: auto;
display: block;
padding: 9.5px;
margin: 0 0 10px;
font-size: 12px;
word-break: break-all;
word-wrap: break-word;
color: rgba(255, 255, 255, 1);
background-color: rgba(47, 47, 47, 1);
border: 1px solid #000000;
border-radius: 0px;
font-family: Menlo, Monaco, Consolas, monospace;

div.printable {
min-height: 50px;
}

div.line.stderr,
div.line.error,
div.line.debug {
color: red;
}

div.line span.counter {
float: left;
clear: left;
}

div.line div.content {
position: relative;
margin-left: 50px;
white-space: pre-wrap;
}

a {
color: #ffffff;
}

a.scroll-link{
position: relative;
bottom: 10px;
float: right;
}
}

.template-invocation-preview {
margin-top: 10px;
}

.pf-c-toggle-group {
margin-bottom: 10px;
}
}
135 changes: 94 additions & 41 deletions webpack/JobInvocationDetail/JobInvocationHostTable.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/* eslint-disable camelcase */
import PropTypes from 'prop-types';
import React, { useMemo, useEffect } from 'react';
import React, { useMemo, useEffect, useState } from 'react';
import { Icon } from 'patternfly-react';
import { translate as __ } from 'foremanReact/common/I18n';
import { FormattedMessage } from 'react-intl';
import { Tr, Td } from '@patternfly/react-table';
import { Tr, Td, Tbody, ExpandableRowContent } from '@patternfly/react-table';
import {
Title,
EmptyState,
Expand All @@ -26,6 +26,8 @@ import Columns, {
JOB_INVOCATION_HOSTS,
STATUS_UPPERCASE,
} from './JobInvocationConstants';
import { TemplateInvocation } from './TemplateInvocation';
import { OpenAlInvocations, PopupAlert } from './OpenAlInvocations';

const JobInvocationHostTable = ({ id, targeting, finished, autoRefresh }) => {
const columns = Columns();
Expand Down Expand Up @@ -153,48 +155,99 @@ const JobInvocationHostTable = ({ id, targeting, finished, autoRefresh }) => {
</Tr>
);

const { results = [] } = response;
const [expandedHost, setExpandedHost] = useState([]);
const isHostExpanded = host => expandedHost.includes(host);
const setHostExpanded = (host, isExpanding = true) =>
setExpandedHost(prevExpanded => {
const otherExpandedHosts = prevExpanded.filter(h => h !== host);
return isExpanding ? [...otherExpandedHosts, host] : otherExpandedHosts;
});
const [showAlert, setShowAlert] = useState(false);
return (
<TableIndexPage
apiUrl=""
apiOptions={apiOptions}
customSearchProps={memoDefaultSearchProps}
controller="hosts"
creatable={false}
replacementResponse={combinedResponse}
updateSearchQuery={updateSearchQuery}
>
<Table
ouiaId="job-invocation-hosts-table"
columns={columns}
customEmptyState={
status === STATUS_UPPERCASE.RESOLVED && !response?.results?.length
? customEmptyState
: null
}
params={params}
setParams={setParamsAndAPI}
itemCount={response?.subtotal}
results={response?.results}
url=""
refreshData={() => {}}
errorMessage={
status === STATUS_UPPERCASE.ERROR && response?.message
? response.message
: null
<>
{showAlert && <PopupAlert setShowAlert={setShowAlert} />}
<TableIndexPage
apiUrl=""
apiOptions={apiOptions}
customSearchProps={memoDefaultSearchProps}
controller="hosts"
creatable={false}
replacementResponse={combinedResponse}
updateSearchQuery={updateSearchQuery}
customToolbarItems={
<OpenAlInvocations
setShowAlert={setShowAlert}
results={results}
id={id}
/>
}
isPending={status === STATUS_UPPERCASE.PENDING}
isDeleteable={false}
bottomPagination={bottomPagination}
>
{response?.results?.map((result, rowIndex) => (
<Tr key={rowIndex} ouiaId={`table-row-${rowIndex}`}>
{columnNamesKeys.map(k => (
<Td key={k}>{columns[k].wrapper(result)}</Td>
))}
</Tr>
))}
</Table>
</TableIndexPage>
<Table
ouiaId="job-invocation-hosts-table"
columns={columns}
customEmptyState={
status === STATUS_UPPERCASE.RESOLVED && !results?.length
? customEmptyState
: null
}
params={params}
setParams={setParamsAndAPI}
itemCount={response?.subtotal}
results={results}
url=""
refreshData={() => {}}
errorMessage={
status === STATUS_UPPERCASE.ERROR && response?.message
? response.message
: null
}
isPending={status === STATUS_UPPERCASE.PENDING}
isDeleteable={false}
bottomPagination={bottomPagination}
childrenOutsideTbody
>
{results?.map((result, rowIndex) => (
<Tbody key={rowIndex}>
<Tr ouiaId={`table-row-${rowIndex}`}>
<Td
expand={{
rowIndex,
isExpanded: isHostExpanded(result.id),
onToggle: () =>
setHostExpanded(result.id, !isHostExpanded(result.id)),
expandId: 'host-expandable',
}}
/>
{columnNamesKeys.slice(1).map(k => (
<Td key={k}>{columns[k].wrapper(result)}</Td>
))}
</Tr>
<Tr
isExpanded={isHostExpanded(result.id)}
ouiaId="table-row-expanded-sections"
>
<Td
dataLabel={`${result.id}-expandable-content`}
colSpan={columnNamesKeys.length + 1}
>
<ExpandableRowContent>
{result.job_status === 'cancelled' ||
result.job_status === 'N/A' ? (
<div>
{__('A task for this host has not been started')}
</div>
) : (
<TemplateInvocation hostID={result.id} jobID={id} />
)}
</ExpandableRowContent>
</Td>
</Tr>
</Tbody>
))}
</Table>
</TableIndexPage>
</>
);
};

Expand Down
10 changes: 9 additions & 1 deletion webpack/JobInvocationDetail/JobInvocationSelectors.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
/* eslint-disable camelcase */
import { selectAPIResponse } from 'foremanReact/redux/API/APISelectors';
import { JOB_INVOCATION_KEY, GET_TASK } from './JobInvocationConstants';
import {
JOB_INVOCATION_KEY,
GET_TASK,
GET_TEMPLATE_INVOCATION,
} from './JobInvocationConstants';

export const selectItems = state =>
selectAPIResponse(state, JOB_INVOCATION_KEY);
Expand All @@ -8,3 +13,6 @@ export const selectTask = state => selectAPIResponse(state, GET_TASK);

export const selectTaskCancelable = state =>
selectTask(state).available_actions?.cancellable || false;

export const selectTemplateInvocation = state =>
selectAPIResponse(state, GET_TEMPLATE_INVOCATION);
Loading

0 comments on commit f0002f3

Please sign in to comment.