diff --git a/packages/zapp/console/src/components/Executions/ExecutionDetails/Timeline/ChartHeader.tsx b/packages/zapp/console/src/components/Executions/ExecutionDetails/Timeline/ChartHeader.tsx new file mode 100644 index 000000000..1fafd08c8 --- /dev/null +++ b/packages/zapp/console/src/components/Executions/ExecutionDetails/Timeline/ChartHeader.tsx @@ -0,0 +1,68 @@ +import * as React from 'react'; +import * as moment from 'moment-timezone'; +import makeStyles from '@material-ui/core/styles/makeStyles'; +import { COLOR_SPECTRUM } from 'components/Theme/colorSpectrum'; +import { useScaleContext } from './scaleContext'; +import { TimeZone } from './helpers'; + +interface StyleProps { + chartWidth: number; + labelInterval: number; +} + +const useStyles = makeStyles((_theme) => ({ + chartHeader: (props: StyleProps) => ({ + height: 41, + display: 'flex', + alignItems: 'center', + width: `${props.chartWidth}px`, + }), + taskDurationsLabelItem: (props: StyleProps) => ({ + fontSize: 12, + fontFamily: 'Open Sans', + fontWeight: 'bold', + color: COLOR_SPECTRUM.gray40.color, + paddingLeft: 10, + width: `${props.labelInterval}px`, + }), +})); + +interface HeaderProps extends StyleProps { + chartTimezone: string; + totalDurationSec: number; + startedAt: Date; +} + +export const ChartHeader = (props: HeaderProps) => { + const styles = useStyles(props); + + const { chartInterval: chartTimeInterval, setMaxValue } = useScaleContext(); + const { startedAt, chartTimezone, totalDurationSec } = props; + + React.useEffect(() => { + setMaxValue(props.totalDurationSec); + }, [props.totalDurationSec, setMaxValue]); + + const labels = React.useMemo(() => { + const len = Math.ceil(totalDurationSec / chartTimeInterval); + const lbs = len > 0 ? new Array(len).fill('') : []; + return lbs.map((_, idx) => { + const time = moment.utc(new Date(startedAt.getTime() + idx * chartTimeInterval * 1000)); + return chartTimezone === TimeZone.UTC + ? time.format('hh:mm:ss A') + : time.local().format('hh:mm:ss A'); + }); + }, [chartTimezone, startedAt, chartTimeInterval, totalDurationSec]); + + return ( +
+ {labels.map((label) => { + return ( +
+ {label} +
+ ); + })} +
+ ); +}; diff --git a/packages/zapp/console/src/components/Executions/ExecutionDetails/Timeline/ExecutionTimeline.tsx b/packages/zapp/console/src/components/Executions/ExecutionDetails/Timeline/ExecutionTimeline.tsx index 9d686a6f9..14af5b17e 100644 --- a/packages/zapp/console/src/components/Executions/ExecutionDetails/Timeline/ExecutionTimeline.tsx +++ b/packages/zapp/console/src/components/Executions/ExecutionDetails/Timeline/ExecutionTimeline.tsx @@ -13,7 +13,7 @@ import { createRef, useContext, useEffect, useRef, useState } from 'react'; import { NodeExecutionsByIdContext } from 'components/Executions/contexts'; import { checkForDynamicExecutions } from 'components/common/utils'; import { convertToPlainNodes } from './helpers'; -import { ChartHeader } from './chartHeader'; +import { ChartHeader } from './ChartHeader'; import { useScaleContext } from './scaleContext'; import { TaskNames } from './TaskNames'; import { getChartDurationData } from './TimelineChart/chartData'; diff --git a/packages/zapp/console/src/components/Executions/ExecutionDetails/Timeline/TaskNames.tsx b/packages/zapp/console/src/components/Executions/ExecutionDetails/Timeline/TaskNames.tsx new file mode 100644 index 000000000..a2bcd4187 --- /dev/null +++ b/packages/zapp/console/src/components/Executions/ExecutionDetails/Timeline/TaskNames.tsx @@ -0,0 +1,99 @@ +import * as React from 'react'; +import { makeStyles, Theme, Typography } from '@material-ui/core'; + +import { RowExpander } from 'components/Executions/Tables/RowExpander'; +import { getNodeTemplateName } from 'components/WorkflowGraph/utils'; +import { dNode } from 'models/Graph/types'; +import { NodeExecutionName } from './NodeExecutionName'; +import { NodeExecutionsTimelineContext } from './context'; + +const useStyles = makeStyles((theme: Theme) => ({ + taskNamesList: { + overflowY: 'scroll', + flex: 1, + }, + namesContainer: { + display: 'flex', + flexDirection: 'row', + alignItems: 'flex-start', + justifyContent: 'left', + padding: '0 10px', + height: 56, + width: 256, + borderBottom: `1px solid ${theme.palette.divider}`, + whiteSpace: 'nowrap', + }, + namesContainerExpander: { + display: 'flex', + marginTop: 'auto', + marginBottom: 'auto', + }, + namesContainerBody: { + display: 'flex', + flexDirection: 'column', + alignItems: 'flex-start', + justifyContent: 'center', + whiteSpace: 'nowrap', + height: '100%', + overflow: 'hidden', + }, + displayName: { + marginTop: 4, + textOverflow: 'ellipsis', + width: '100%', + overflow: 'hidden', + }, + leaf: { + width: 30, + }, +})); + +interface TaskNamesProps { + nodes: dNode[]; + onScroll: () => void; + onToggle: (id: string, scopeId: string, level: number) => void; +} + +export const TaskNames = React.forwardRef((props, ref) => { + const state = React.useContext(NodeExecutionsTimelineContext); + const { nodes, onScroll, onToggle } = props; + const styles = useStyles(); + + return ( +
+ {nodes.map((node) => { + const templateName = getNodeTemplateName(node); + const nodeLevel = node?.level ?? 0; + return ( +
+
+ {node.nodes?.length ? ( + onToggle(node.id, node.scopedId, nodeLevel)} + /> + ) : ( +
+ )} +
+ +
+ + + {templateName} + +
+
+ ); + })} +
+ ); +}); diff --git a/packages/zapp/console/src/components/WorkflowGraph/utils.ts b/packages/zapp/console/src/components/WorkflowGraph/utils.ts index afc31968f..79b2e342b 100644 --- a/packages/zapp/console/src/components/WorkflowGraph/utils.ts +++ b/packages/zapp/console/src/components/WorkflowGraph/utils.ts @@ -4,6 +4,7 @@ import { CompiledWorkflow, Workflow } from 'models/Workflow/types'; import { CompiledNode, TaskNode } from 'models/Node/types'; import { CompiledTask, TaskTemplate } from 'models/Task/types'; import { dTypes, dNode } from 'models/Graph/types'; +import _ from 'lodash'; import { transformerWorkflowToDag } from './transformerWorkflowToDag'; /** * TODO FC#393: these are dupes for testing, remove once tests fixed @@ -35,17 +36,7 @@ export function isExpanded(node: any) { * @returns boolean */ export const checkIfObjectsAreSame = (a, b) => { - // if one of the objects is null (undefined), objects can't be the same - if ((!a || !b) && a != b) { - return false; - } - - for (const k in a) { - if (a[k] != b[k]) { - return false; - } - } - return true; + return _.isEqual(a, b); }; /** @@ -115,11 +106,12 @@ export const getNodeTypeFromCompiledNode = (node: CompiledNode): dTypes => { }; export const getSubWorkflowFromId = (id, workflow) => { + const _ = require('lodash'); const { subWorkflows } = workflow; /* Find current matching entitity from subWorkflows */ for (const k in subWorkflows) { const subWorkflowId = subWorkflows[k].template.id; - if (checkIfObjectsAreSame(subWorkflowId, id)) { + if (_.isEqual(subWorkflowId, id)) { return subWorkflows[k]; } } @@ -127,11 +119,12 @@ export const getSubWorkflowFromId = (id, workflow) => { }; export const getTaskTypeFromCompiledNode = (taskNode: TaskNode, tasks: CompiledTask[]) => { + const _ = require('lodash'); for (let i = 0; i < tasks.length; i++) { const compiledTask: CompiledTask = tasks[i]; const taskTemplate: TaskTemplate = compiledTask.template; const templateId: Identifier = taskTemplate.id; - if (checkIfObjectsAreSame(templateId, taskNode.referenceId)) { + if (_.isEqual(templateId, taskNode.referenceId)) { return compiledTask; } }