Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/controllers/catalog_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,7 @@ def svc_catalog_provision
ra, st, svc_catalog_provision_finish_submit_endpoint
)
@in_a_form = true
@dialog_locals = options[:dialog_locals]
replace_right_cell(:action => "dialog_provision", :dialog_locals => options[:dialog_locals])
else
# if catalog item has no dialog and provision button was pressed from list view
Expand Down
22 changes: 17 additions & 5 deletions app/controllers/service_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,23 @@ def service_reconfigure
service = find_record_with_rbac(Service, params[:id])
service_template = service.service_template
resource_action = service_template.resource_actions.find_by(:action => 'Reconfigure') if service_template
@dialog_locals = {:resource_action_id => resource_action.id, :target_id => service.id}

@dialog_locals = {
:dialogId => resource_action.dialog.id,
:params => {
:resourceActionId => resource_action.id,
:targetId => service.id,
:targetType => 'service'
},
:urls => {
:apiSubmitEndpoint => "/api/services/#{service.id}",
:apiAction => 'reconfigure',
:cancelEndPoint => '/service/show_list',
:finishSubmitEndpoint => '/miq_request/show_list?typ=service/',
:openUrl => false
}
}

@in_a_form = true
drop_breadcrumb(:name => _("Reconfigure Service \"%{name}\"") % {:name => service.name}, :url => "/service/service_reconfigure/#{service.id}")
end
Expand Down Expand Up @@ -220,10 +236,6 @@ def set_right_cell_vars(action)
partial = "shared/views/retire"
header = _("Set/Remove retirement date for Service")
action = "retire"
when "reconfigure_dialog"
partial = "shared/dialogs/reconfigure_dialog"
header = @right_cell_text
action = nil
when "service_edit"
partial = "service_form"
header = _("Editing Service \"%{name}\"") % {:name => @service.name}
Expand Down
1 change: 1 addition & 0 deletions app/helpers/catalog_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module CatalogHelper
include RequestInfoHelper
include Mixins::AutomationMixin
include OrchestrationTemplateHelper
include OrderServiceHelper

def miq_catalog_resource(resources)
headers = ["", _("Name"), _("Description"), _("Action Order"), _("Provision Order"), _("Action Start"), _("Action Stop"), _("Delay (mins) Start"), _("Delay (mins) Stop")]
Expand Down
43 changes: 42 additions & 1 deletion app/helpers/miq_request_helper.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,53 @@
module MiqRequestHelper
include RequestInfoHelper
include RequestDetailsHelper
include OrderServiceHelper

def row_data(label, value)
{:cells => {:label => label, :value => value}}
end

def request_task_configuration_script_ids(miq_request)
miq_request.miq_request_tasks.map { |task| task.options&.dig(:configuration_script_id) }.compact
miq_request.miq_request_tasks.filter_map { |task| task.options&.dig(:configuration_script_id) }
end

def select_box_options(options)
options.map do |item|
if /Classification::(\d+)/.match?(item)
classification_id = item.match(/Classification::(\d+)/)[1]
classification = Classification.find_by(:id => classification_id)
if classification
{:label => classification.description, :value => classification.id.to_s}
end
else
classification = Classification.find_by(:id => item)
if classification
{:label => classification.description, :value => classification.id.to_s}
end
end
end
end

def dialog_field_values(dialog, wf)

transformed_data = dialog.transform_keys { |key| key.sub('Array::', '') }
transformed_data.transform_values do |value|
if value.to_s.include?("\u001F")
select_box_options(value.split("\u001F"))
elsif value.to_s.include?("::")
model, id = value.split("::")
record = model.constantize.find_by(:id => id)
record ? [{:label => record.description, :value => record.id}] : value
else
value
end
end
end

def service_request_data(request_options, wf)
{
:dialogId => request_options[:workflow_settings][:dialog_id],
:requestDialogOptions => dialog_field_values(request_options[:dialog], wf),
}
end
end
20 changes: 20 additions & 0 deletions app/helpers/order_service_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module OrderServiceHelper
def order_service_data(dialog)
{
:dialogId => dialog[:dialog_id],
:params => {
:resourceActionId => dialog[:resource_action_id],
:targetId => dialog[:target_id],
:targetType => dialog[:target_type],
:realTargetType => dialog[:real_target_type],
},
:urls => {
:apiSubmitEndpoint => dialog[:api_submit_endpoint],
:apiAction => dialog[:api_action],
:cancelEndPoint => dialog[:cancel_endpoint],
:finishSubmitEndpoint => dialog[:finish_submit_endpoint],
:openUrl => dialog[:open_url],
}
}
end
end
84 changes: 84 additions & 0 deletions app/javascript/components/service/DialogFields.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { DIALOG_FIELD_TYPES, ServiceType } from './constants';
import CheckboxField from './dialogFieldItems/CheckboxField';
import DateField from './dialogFieldItems/DateField';
import DateTimeField from './dialogFieldItems/DateTimeField';
import DropDownField from './dialogFieldItems/DropDownField';
import RadioField from './dialogFieldItems/RadioField';
import RefreshField from './RefreshField';
import ServiceContext from './ServiceContext';
import TagField from './dialogFieldItems/TagField';
import TextInputField from './dialogFieldItems/TextInputField';
import TextAreaField from './dialogFieldItems/TextAreaField';

/** Function to render fields based on type */
const renderFieldContent = (field, dialogFields) => {
switch (dialogFields[field.name].type) {
case DIALOG_FIELD_TYPES.checkBox:
return <CheckboxField field={field} />;
case DIALOG_FIELD_TYPES.date:
return <DateField field={field} />;
case DIALOG_FIELD_TYPES.dateTime:
return <DateTimeField field={field} />;
case DIALOG_FIELD_TYPES.dropDown:
return <DropDownField field={field} />;
case DIALOG_FIELD_TYPES.radio:
return <RadioField field={field} />;
case DIALOG_FIELD_TYPES.tag:
return <TagField field={field} />;
case DIALOG_FIELD_TYPES.textBox:
return <TextInputField field={field} />;
case DIALOG_FIELD_TYPES.textArea:
return <TextAreaField field={field} />;
default:
return <>{__('Field not supported')}</>;
}
};

/** Function to render a field. */
const renderFieldItem = (field, data) => {
const isRefreshing = data.fieldsToRefresh.includes(field.name);
return (
<div
className={classNames('section-field-row', isRefreshing && 'field-refresh-in-progress')}
key={field.id.toString()}
id={`section-field-row-${field.name}`}
>
<div className="field-item">
{
renderFieldContent(field, data.dialogFields)
}
</div>
<RefreshField field={field} />
</div>
);
};

/** Component to render the Fields in the Service/DialogTabs/DialogGroups component */
const DialogFields = ({ dialogFields }) => {
const { data } = useContext(ServiceContext);
const visible = (field) => {
if (data.serviceType === ServiceType.dialog) {
return true;
}
return field.visible;
};

return (
<>
{
dialogFields.map((field) => (
visible(field) ? renderFieldItem(field, data) : <span key={field.id.toString()} />
))
}
</>
);
};

DialogFields.propTypes = {
dialogFields: PropTypes.arrayOf(PropTypes.any).isRequired,
};

export default DialogFields;
34 changes: 34 additions & 0 deletions app/javascript/components/service/DialogGroups.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';
import PropTypes from 'prop-types';
import { TooltipIcon } from 'carbon-components-react';
import DialogFields from './DialogFields';

/** Component to render the Groups in the Service/DialogTabs component */
const DialogGroups = ({ dialogGroups }) => {
const itemLabel = ({ label, description }) => (description
? <TooltipIcon direction="right" tooltipText={description}>{label}</TooltipIcon>
: label);

return (
<>
{
dialogGroups.map((item) => (
<div className="section" key={item.id.toString()}>
<div className="section-label">
{itemLabel(item)}
</div>
<div className="section-fields">
<DialogFields dialogFields={item.dialog_fields} />
</div>
</div>
))
}
</>
);
};

DialogGroups.propTypes = {
dialogGroups: PropTypes.arrayOf(PropTypes.any).isRequired,
};

export default DialogGroups;
40 changes: 40 additions & 0 deletions app/javascript/components/service/DialogTabs.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React, { useContext } from 'react';
import { Tabs, Tab, Loading } from 'carbon-components-react';
import DialogGroups from './DialogGroups';
import ServiceContext from './ServiceContext';
import { extractDialogTabs } from './helper';

/** Component to render the Tabs in the Service component */
const DialogTabs = () => {
const { data } = useContext(ServiceContext);
const dialogTabs = extractDialogTabs(data.apiResponse);

const tabLabel = (tab, tabIndex) => {
const { fieldsToRefresh, groupFieldsByTab } = data;
const refreshInProgress = fieldsToRefresh.some((field) => groupFieldsByTab[tabIndex].includes(field));
return refreshInProgress
? (
<div className="tab-label">
{tab.label}
<Loading active small withOverlay={false} className="loading" />
</div>
)
: tab.label;
};

return (
<Tabs className="miq_custom_tabs">
{
dialogTabs.map((tab, tabIndex) => (
<Tab key={tab.id.toString()} label={tabLabel(tab, tabIndex)}>
<div className="tabs">
<DialogGroups dialogGroups={tab.dialog_groups} />
</div>
</Tab>
))
}
</Tabs>
);
};

export default DialogTabs;
62 changes: 62 additions & 0 deletions app/javascript/components/service/RefreshField.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import { Button, Loading } from 'carbon-components-react';
import { Renew16 } from '@carbon/icons-react';
import ServiceContext from './ServiceContext';
import ServiceValidator from './ServiceValidator';
import { defaultFieldValue } from './helper';
import { fieldProperties } from './helper.field';

/** Function to reset the dialogField data when the field refresh button is clicked. */
const resetDialogField = (dialogFields, field) => {
const { value, valid } = ServiceValidator.validateField({ field, value: defaultFieldValue(field).defaultValue });
dialogFields[field.name] = { ...dialogFields[field.name], value, valid };
return { ...dialogFields };
};

const RefreshField = ({ field }) => {
const { data, setData } = useContext(ServiceContext);
const { isDisabled } = fieldProperties(field, data);

const { fieldsToRefresh } = data;
const inProgress = fieldsToRefresh.includes(field.name);
return (
<div className="refresh-field-item">
{
!!(field.dynamic && field.show_refresh_button) && !inProgress && (
<Button
hasIconOnly
disabled={isDisabled}
className="refresh-field-button"
onClick={() => {
setData({
...data,
fieldsToRefresh: [field.name],
dialogFields: resetDialogField(data.dialogFields, field),
});
}}
iconDescription={__(`Refresh ${field.label}`)}
tooltipAlignment="start"
tooltipPosition="left"
renderIcon={Renew16}
/>
)
}
{
inProgress && <Loading active small withOverlay={false} className="loading" />
}
</div>
);
};

RefreshField.propTypes = {
field: PropTypes.shape({
label: PropTypes.string,
dynamic: PropTypes.bool,
show_refresh_button: PropTypes.bool,
dialog_field_responders: PropTypes.arrayOf(PropTypes.string),
name: PropTypes.string,
}).isRequired,
};

export default RefreshField;
Loading
Loading