Skip to content

Commit

Permalink
Merge pull request #8782 from jeffibm/workflow-status-list
Browse files Browse the repository at this point in the history
Introducing workflow status list in request's show page

(cherry picked from commit cf29c3a)
  • Loading branch information
Fryguy committed Sep 30, 2023
1 parent ac73614 commit bcf8c71
Show file tree
Hide file tree
Showing 8 changed files with 277 additions and 0 deletions.
4 changes: 4 additions & 0 deletions app/helpers/miq_request_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
65 changes: 65 additions & 0 deletions app/javascript/components/request-workflow-status/data.js
Original file line number Diff line number Diff line change
@@ -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,
};
};
18 changes: 18 additions & 0 deletions app/javascript/components/request-workflow-status/index.jsx
Original file line number Diff line number Diff line change
@@ -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 }) => (
<div className="workflow-states-list-container">
{ ids.map((id) => (
<RequestWorkflowStatusItem recordId={id} key={id} />
))}
</div>
);

RequestWorkflowStatus.propTypes = {
ids: PropTypes.arrayOf(PropTypes.number).isRequired,
};

export default RequestWorkflowStatus;
Original file line number Diff line number Diff line change
@@ -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 (
<Tag type={status.tagType} title={status.text}>
{status.text.toUpperCase()}
</Tag>
);
};

/** Function to render the status of workflow status. */
const renderWorkflowStatus = () => (
<div className="workflow-status-container">
<div className="workflow-status-tag">
{data.responseData && data.responseData.status && renderStatusTag()}
</div>
<div className="workflow-status-label">
<Link href={`/workflow/show/${data.responseData.parentId}/`}>{data.parentName.toString()}</Link>
</div>
<div className="workflow-status-action">
{data.isLoading && <Loading active small withOverlay={false} className="loading" />}
</div>
</div>
);

/** Function to render the notification. */
const renderNotitication = () => (
<div className="workflow-notification-container">
<NotificationMessage type="error" message={data.message} />
</div>
);

/** Function to render the list. */
const renderList = ({ headers, rows }) => (
<MiqDataTable
headers={headers}
rows={rows}
mode="request-workflow-status"
/>
);

return (
<>
{
data.validType && (
<div className="workflow-states-container">
{data.responseData && renderWorkflowStatus()}
{data.message && renderNotitication()}
{data.responseData && data.responseData.status && renderList(data.responseData)}
</div>
)
}
</>
);
};

RequestWorkflowStatusItem.propTypes = {
recordId: PropTypes.number.isRequired,
};

export default RequestWorkflowStatusItem;
2 changes: 2 additions & 0 deletions app/javascript/packs/component-definitions-common.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions app/stylesheet/application-webpack.scss
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@
@import './tree.scss';
@import './toolbar.scss';
@import './widget.scss';
@import './workflows.scss';
@import './cloud-container-projects-dashboard.scss';
34 changes: 34 additions & 0 deletions app/stylesheet/workflows.scss
Original file line number Diff line number Diff line change
@@ -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;
}
}
}

6 changes: 6 additions & 0 deletions app/views/miq_request/_st_prov_show.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -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})

0 comments on commit bcf8c71

Please sign in to comment.