diff --git a/app/helpers/miq_request_helper.rb b/app/helpers/miq_request_helper.rb
index 9501712458c..418ebf678fc 100644
--- a/app/helpers/miq_request_helper.rb
+++ b/app/helpers/miq_request_helper.rb
@@ -5,4 +5,8 @@ module MiqRequestHelper
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
+ end
end
diff --git a/app/javascript/components/request-workflow-status/data.js b/app/javascript/components/request-workflow-status/data.js
new file mode 100644
index 00000000000..88978ecf793
--- /dev/null
+++ b/app/javascript/components/request-workflow-status/data.js
@@ -0,0 +1,65 @@
+/** Types of workflow state status */
+export const workflowStateTypes = {
+ success: { text: 'success', tagType: 'green' },
+ error: { text: 'error', tagType: 'red' },
+ failed: { text: 'failed', tagType: 'gray' },
+ pending: { text: 'pending', tagType: 'gray' },
+};
+
+/** Function to get the header data of workflow states table. */
+const headerData = () => ([
+ {
+ key: 'name',
+ header: __('Name'),
+ },
+ {
+ key: 'enteredTime',
+ header: __('Entered Time'),
+ },
+ {
+ key: 'finishedTime',
+ header: __('Finished Time'),
+ },
+ {
+ key: 'duration',
+ header: __('Duration'),
+ },
+]);
+
+// const convertDate = (date) => `${moment(date).format('MM/DD/YYYY')} ${moment(date).format('h:mm:ss A')}`;
+const convertDate = (date) => {
+ const utcDate = new Date(date);
+ const year = utcDate.getUTCFullYear();
+ const month = (utcDate.getUTCMonth() + 1).toString().padStart(2, '0');
+ const day = utcDate.getUTCDate().toString().padStart(2, '0');
+ const hours = utcDate.getUTCHours();
+ const minutes = utcDate.getUTCMinutes().toString().padStart(2, '0');
+ const period = hours < 12 ? 'AM' : 'PM';
+ const hours12 = hours % 12 || 12; // Convert 0 to 12 for 12:00 AM
+
+ const formattedDate = `${month}/${day}/${year} ${hours12}:${minutes} ${period}`;
+ return formattedDate.toString();
+};
+
+/** Function to get the row data of workflow states table. */
+const rowData = ({ StateHistory }) => StateHistory.map((item) => ({
+ id: item.Guid.toString(),
+ name: item.Name,
+ enteredTime: convertDate(item.EnteredTime.toString()),
+ finishedTime: convertDate(item.FinishedTime.toString()),
+ duration: item.Duration.toFixed(3).toString(),
+}));
+
+/** Function to return the header, row and status data required for the RequestWorkflowStatus component. */
+export const workflowStatusData = (response) => {
+ const type = 'ManageIQ::Providers::Workflows::AutomationManager::WorkflowInstance';
+ if (response.type !== type) {
+ return undefined;
+ }
+ const rows = response.context ? rowData(response.context) : [];
+ const headers = headerData();
+ const name = response.name || response.description;
+ return {
+ headers, rows, status: response.status, name, parentId: response.parent_id, id: response.id, type: response.type,
+ };
+};
diff --git a/app/javascript/components/request-workflow-status/index.jsx b/app/javascript/components/request-workflow-status/index.jsx
new file mode 100644
index 00000000000..3b1ba4768f0
--- /dev/null
+++ b/app/javascript/components/request-workflow-status/index.jsx
@@ -0,0 +1,18 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import RequestWorkflowStatusItem from './request-workflow-status-item';
+
+/** Component to render the Workflow status in /miq_request/show/#{id} page */
+const RequestWorkflowStatus = ({ ids }) => (
+
+ { ids.map((id) => (
+
+ ))}
+
+);
+
+RequestWorkflowStatus.propTypes = {
+ ids: PropTypes.arrayOf(PropTypes.number).isRequired,
+};
+
+export default RequestWorkflowStatus;
diff --git a/app/javascript/components/request-workflow-status/request-workflow-status-item.jsx b/app/javascript/components/request-workflow-status/request-workflow-status-item.jsx
new file mode 100644
index 00000000000..bda8b785457
--- /dev/null
+++ b/app/javascript/components/request-workflow-status/request-workflow-status-item.jsx
@@ -0,0 +1,147 @@
+import React, { useEffect, useState, useRef } from 'react';
+import {
+ Tag, Loading, Link,
+} from 'carbon-components-react';
+import PropTypes from 'prop-types';
+import { workflowStatusData, workflowStateTypes } from './data';
+import MiqDataTable from '../miq-data-table';
+import NotificationMessage from '../notification-message';
+
+/** Component to render the Workflow status in /miq_request/show/#{id} page */
+const RequestWorkflowStatusItem = ({ recordId }) => {
+ const RELOAD = 2000; // Time interval to reload the RequestWorkflowStatus component.
+ const reloadLimit = 5; // This is to handle the Auto refresh issue causing the the server to burn out with multiple requests.
+ const reloadCount = useRef(0);
+
+ const [data, setData] = useState(
+ {
+ isLoading: true,
+ responseData: undefined,
+ message: undefined,
+ list: [],
+ parentName: undefined,
+ validType: false,
+ }
+ );
+
+ /** Function to get the Workflow */
+ const getWorkflow = async() => {
+ reloadCount.current += 1;
+ const url = `/api/configuration_scripts/${recordId}`;
+ API.get(url, { skipErrors: [404, 400, 500] })
+ .then((response) => {
+ const responseData = workflowStatusData(response);
+ if (responseData) {
+ API.get(`/api/configuration_script_payloads/${responseData.parentId}`).then((response2) => {
+ if (response.context) {
+ setData({
+ ...data,
+ responseData,
+ isLoading: false,
+ parentName: response2.name,
+ validType: true,
+ message: responseData && responseData.status === workflowStateTypes.error.text
+ ? __('An error has occurred with this workflow') : undefined,
+ });
+ } else {
+ setData({
+ ...data,
+ responseData,
+ isLoading: false,
+ parentName: response2.name,
+ validType: true,
+ message: sprintf(__('Context is not available for "%s"'), response.name),
+ });
+ }
+ });
+ } else {
+ setData({
+ ...data,
+ validType: false,
+ responseData: undefined,
+ isLoading: false,
+ });
+ }
+ });
+ };
+
+ /** Logic to reload the component every so often (RELOAD). */
+ useEffect(() => {
+ const omitStatus = [workflowStateTypes.success.text, workflowStateTypes.error.text];
+ if (reloadCount.current <= reloadLimit && data.responseData && data.responseData.status && !omitStatus.includes(data.responseData.status)) {
+ const interval = setInterval(() => {
+ setData({ ...data, isLoading: true });
+ getWorkflow();
+ }, RELOAD);
+ return () => clearInterval(interval); // This represents the unmount function, in which you need to clear your interval to prevent memory leaks.
+ }
+ return undefined;
+ }, [data.responseData]);
+
+ useEffect(() => {
+ if (recordId) {
+ getWorkflow();
+ }
+ }, [recordId]);
+
+ /** Function to render the status of workflow. */
+ const renderStatusTag = () => {
+ const status = workflowStateTypes[data.responseData.status];
+ return (
+
+ {status.text.toUpperCase()}
+
+ );
+ };
+
+ /** Function to render the status of workflow status. */
+ const renderWorkflowStatus = () => (
+
+
+ {data.responseData && data.responseData.status && renderStatusTag()}
+
+
+ {data.parentName.toString()}
+
+
+ {data.isLoading && }
+
+
+ );
+
+ /** Function to render the notification. */
+ const renderNotitication = () => (
+
+
+
+ );
+
+ /** Function to render the list. */
+ const renderList = ({ headers, rows }) => (
+
+ );
+
+ return (
+ <>
+ {
+ data.validType && (
+
+ {data.responseData && renderWorkflowStatus()}
+ {data.message && renderNotitication()}
+ {data.responseData && data.responseData.status && renderList(data.responseData)}
+
+ )
+ }
+ >
+ );
+};
+
+RequestWorkflowStatusItem.propTypes = {
+ recordId: PropTypes.number.isRequired,
+};
+
+export default RequestWorkflowStatusItem;
diff --git a/app/javascript/packs/component-definitions-common.js b/app/javascript/packs/component-definitions-common.js
index 526816abcfb..691b545147b 100644
--- a/app/javascript/packs/component-definitions-common.js
+++ b/app/javascript/packs/component-definitions-common.js
@@ -102,6 +102,7 @@ import ReportDataTable from '../components/data-tables/report-data-table/report-
import RetirementForm from '../components/retirement-form';
import RoleList from '../components/data-tables/role-list';
import RequestsTable from '../components/data-tables/requests-table';
+import RequestWorkflowStatus from '../components/request-workflow-status';
import RoutersForm from '../components/routers-form';
import ServiceDialogFromForm from '../components/service-dialog-from-form/service-dialog-from';
import ServiceDetailStdout from '../components/service-detail-stdout';
@@ -269,6 +270,7 @@ ManageIQ.component.addReact('ReportList', ReportList);
ManageIQ.component.addReact('RetirementForm', RetirementForm);
ManageIQ.component.addReact('RoleList', RoleList);
ManageIQ.component.addReact('RequestsTable', RequestsTable);
+ManageIQ.component.addReact('RequestWorkflowStatus', RequestWorkflowStatus);
ManageIQ.component.addReact('RoutersForm', RoutersForm);
ManageIQ.component.addReact('SearchBar', SearchBar);
ManageIQ.component.addReact('ServiceDialogFromForm', ServiceDialogFromForm);
diff --git a/app/stylesheet/application-webpack.scss b/app/stylesheet/application-webpack.scss
index 44a952e4c4b..7d01d704c5e 100644
--- a/app/stylesheet/application-webpack.scss
+++ b/app/stylesheet/application-webpack.scss
@@ -26,4 +26,5 @@
@import './tree.scss';
@import './toolbar.scss';
@import './widget.scss';
+@import './workflows.scss';
@import './cloud-container-projects-dashboard.scss';
diff --git a/app/stylesheet/workflows.scss b/app/stylesheet/workflows.scss
new file mode 100644
index 00000000000..8d709ee1667
--- /dev/null
+++ b/app/stylesheet/workflows.scss
@@ -0,0 +1,34 @@
+.workflow-states-list-container {
+ margin-bottom: 20px;
+
+ .workflow-states-container {
+ padding: 10px;
+ border: 1px solid lightgray;
+ border-bottom: 0;
+
+ &:last-child {
+ border-bottom: 1px solid lightgray;
+ }
+
+ .workflow-status-container {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+
+ .workflow-status-label {
+ font-weight:bold;
+ font-size: 14px;
+ margin-right: 5px;
+ }
+
+ .workflow-status-tag {
+ margin-right: 5px;
+ }
+ }
+
+ .workflow-notification-container {
+ margin-top: 10px;
+ }
+ }
+}
+
diff --git a/app/views/miq_request/_st_prov_show.html.haml b/app/views/miq_request/_st_prov_show.html.haml
index d8abdd5c773..b5bda77229f 100644
--- a/app/views/miq_request/_st_prov_show.html.haml
+++ b/app/views/miq_request/_st_prov_show.html.haml
@@ -30,3 +30,9 @@
= render :partial => "miq_request/request_dialog_details",
:locals => {:wf => wf, :field => field}
%hr
+
+ - record_ids = request_task_configuration_script_ids(@miq_request)
+ - if record_ids.any?
+ %h3
+ = _("Workflow States")
+ = react('RequestWorkflowStatus', {:ids => record_ids})