Skip to content
Merged
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
19 changes: 17 additions & 2 deletions app/javascript/components/miq-data-table/miq-table-cell.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
CellAction, hasIcon, hasImage, hasButton, hasTextInput, hasToggle, hasLink, isObject, isArray, isNumber, decimalCount,
} from './helper';
import { customOnClickHandler } from '../../helpers/custom-click-handler';
import { carbonizeIcon } from '../../menu/icon';

const MiqTableCell = ({
cell, onCellClick, row, truncate,
Expand Down Expand Up @@ -74,6 +75,20 @@ const MiqTableCell = ({
);
};

const returnIcon = (icon, style, styledIconClass, longerTextClass, index = undefined) => {
const extraProps = {};
if (index !== undefined) {
extraProps.key = index.toString();
}
if (icon.startsWith('carbon--')) {
const IconElement = carbonizeIcon(icon);
return (
<IconElement aria-label={icon} className={classNames('icon', 'carbon-icons-style', icon)} style={style} {...extraProps} />
);
}
return (<i className={classNames('fa-lg', 'icon', icon, styledIconClass, longerTextClass)} style={style} {...extraProps} />);
};

/** Function to render icon(s) in cell. */
const renderIcon = (icon, style, showText) => {
const hasBackground = Object.keys(style).includes('background');
Expand All @@ -83,8 +98,8 @@ const MiqTableCell = ({
<div className={cellClass}>
{
typeof (icon) === 'string'
? <i className={classNames('fa-lg', 'icon', icon, styledIconClass, longerTextClass)} style={style} />
: icon.map((i, index) => <i className={classNames('fa-lg', 'icon', i)} key={index.toString()} />)
? returnIcon(icon, style, styledIconClass, longerTextClass)
: icon.map((i, index) => returnIcon(i, style, styledIconClass, longerTextClass, index))
}
{showText && truncateText}
</div>
Expand Down
48 changes: 46 additions & 2 deletions app/javascript/components/request-workflow-status/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,43 @@ const convertDate = (date) => {
return formattedDate.toString();
};

// Converts the duration time in ms and returns a string in format: w days x hours y minutes z seconds
// duration: time in ms
const convertDuration = (duration) => {
const durationString = moment.duration(duration, 'milliseconds').toISOString().split('PT')[1];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm very surprised this doesn't "throw away" the days, months, years component of the time

let startIndex = 0;
let resultString = '';
if (durationString.indexOf('H') >= 0) {
resultString += `${durationString.slice(startIndex, durationString.indexOf('H'))}h `;
startIndex = durationString.indexOf('H') + 1;
}
if (durationString.indexOf('M') >= 0) {
resultString += `${durationString.slice(startIndex, durationString.indexOf('M'))}m `;
startIndex = durationString.indexOf('M') + 1;
}
if (durationString.indexOf('S') >= 0) {
resultString += `${durationString.slice(startIndex, durationString.indexOf('S'))}s`;
startIndex = durationString.indexOf('S') + 1;
}
return resultString;
};

const getItemIcon = (item) => {
if (item.RunnerContext && item.RunnerContext.success) {
return { icon: 'carbon--CheckmarkOutline' };
} if (item.RunnerContext && item.RunnerContext.Error) {
return { icon: 'carbon--MisuseOutline' };
}
return { icon: 'carbon--PlayOutline' };
};

/** Function to get the row data of workflow states table. */
const rowData = ({ StateHistory }) => StateHistory.map((item) => ({
id: item.Guid.toString(),
name: item.Name,
name: { text: item.Name, ...getItemIcon(item) },
enteredTime: convertDate(item.EnteredTime.toString()),
finishedTime: convertDate(item.FinishedTime.toString()),
duration: item.Duration.toFixed(3).toString(),
duration: convertDuration(item.Duration * 1000),
}));

/** Function to return the header, row and status data required for the RequestWorkflowStatus component. */
Expand All @@ -59,6 +89,20 @@ export const workflowStatusData = (response) => {
return undefined;
}
const rows = response.context ? rowData(response.context) : [];
if (response.context && response.context.State) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@agrare I think this code depends on nil-ing out the state when the flow is completed. Is that in yet?

Copy link
Member

@agrare agrare May 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it isn't, started it but ran into some issues because we use context.output after a workflow has completed so need to tweak some stuff to be able to do this in floe.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@GilbertCherrie Since the "final" state might appear twice, I'm concerned this will double up the presentation of it. @agrare Thoughts on how we can avoid presenting the final state twice?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Fryguy @agrare What if I add an ID check where if the current state id is found in an array of all the past ids it won't render the current state? The array might be a little large based on how many past states there is but its one possible solution if we want to handle the fix in the front end.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@agrare If we do a state retry or a state gets re-run in a loop, does each iteration get it's own GUID? If so, then yeah, an ID check might work.

@GilbertCherrie You could also just check the very last state in the StatesHistory and if it's the same as the "current" state then don't show it.

Copy link
Member

@agrare agrare May 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do a state retry or a state gets re-run in a loop, does each iteration get it's own GUID? If so, then yeah, an ID check might work.

Here is an example of the same state retried twice from floe specs:

"StateHistory"=>
    [{"Name"=>"State",
      "Input"=>{"foo"=>{"bar"=>"baz"}, "bar"=>{"baz"=>"foo"}},
      "Guid"=>"63837184-0795-41df-8d23-da034b8b2850",
      "EnteredTime"=>"2024-05-24T12:49:47Z",
      "RunnerContext"=>{"container_ref"=>"container-d"},
      "Output"=>{"Error"=>"States.Timeout"},
      "NextState"=>nil,
      "FinishedTime"=>"2024-05-24T12:49:47Z",
      "Duration"=>0.423689375,
      "RetryCount"=>2,
      "Retrier"=>["States.Timeout"]},
     {"Name"=>"State",
      "Input"=>{"foo"=>{"bar"=>"baz"}, "bar"=>{"baz"=>"foo"}},
      "Guid"=>"63837184-0795-41df-8d23-da034b8b2850",
      "EnteredTime"=>"2024-05-24T12:49:47Z",
      "RunnerContext"=>{"container_ref"=>"container-d"},
      "Output"=>{"Error"=>"States.Timeout"},
      "NextState"=>nil,
      "FinishedTime"=>"2024-05-24T12:49:47Z",
      "Duration"=>0.423689375,
      "RetryCount"=>2,
      "Retrier"=>["States.Timeout"]}]

The State gets a Guid when it is started and a state that is retried multiple times is still the same state so it has the same Guid.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@agrare One more question. When the entire flow is done, does the "current" state have a "finished time"? If so, that might be the simplest check.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes,

   "State"=>
    {"Name"=>"NextState",
     "Input"=>{"foo"=>1, "result"=>{"foo"=>"bar", "bar"=>"baz"}},
     "Guid"=>"81de8e05-8118-49d0-b97e-779904fb07c3",
     "EnteredTime"=>"2024-05-24T14:34:29Z",
     "Output"=>{"foo"=>1, "result"=>{"foo"=>"bar", "bar"=>"baz"}},
     "NextState"=>nil,
     "FinishedTime"=>"2024-05-24T14:34:29Z",
     "Duration"=>0.348082391}

const state = response.context.State;
const currentTime = new Date(); // Date Object for current time
const oldTime = Date.parse(state.EnteredTime); // ms since start time to entered time in UTC
const durationTime = currentTime.getTime() - oldTime; // ms from entered time to current time

rows.push({
id: state.Guid.toString(),
name: { text: state.Name, icon: 'carbon--PlayOutline' },
enteredTime: convertDate(state.EnteredTime.toString()),
finishedTime: '',
duration: convertDuration(durationTime),
});
}
const headers = headerData();
const name = response.name || response.description;
return {
Expand Down
25 changes: 24 additions & 1 deletion app/stylesheet/miq-data-table.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
:root {
--green: #2d7623;
--red: #cc0000;
--yellow: #ec7a08;
}

.tenant-quota-data-table {
.miq-data-table {
margin-bottom: 32px;
Expand Down Expand Up @@ -113,7 +119,7 @@
}

.icon.fa-ruby {
color: #cc0000 !important;
color: var(--red) !important;
}

span {
Expand Down Expand Up @@ -387,3 +393,20 @@ table.miq_preview {
}
}

.request-workflow-status {
.carbon-icons-style {
margin-right: 5px;
}

.carbon--CheckmarkOutline {
color: var(--green);
}

.carbon--MisuseOutline {
color: var(--red);
}

.carbon--PlayOutline {
color: var(--yellow)
}
}