Skip to content

Commit

Permalink
feat: update node list view and add executions bar chart (flyteorg#252)
Browse files Browse the repository at this point in the history
* feat: update node list view and add executions bar chart

Signed-off-by: csirius <[email protected]>

* fix: unit testing coverage

Signed-off-by: csirius <[email protected]>

* fix: remove unused vars

Signed-off-by: csirius <[email protected]>

* fix: show 100 executions for bar chart

Signed-off-by: csirius <[email protected]>

* fix: navigate to execution detail when  bar chart item is clicked

Signed-off-by: csirius <[email protected]>
  • Loading branch information
govalt authored Dec 15, 2021
1 parent 7912790 commit cca633e
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 31 deletions.
2 changes: 1 addition & 1 deletion src/components/Entities/EntityExecutions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const EntityExecutions: React.FC<EntityExecutionsProps> = ({

const baseFilters = React.useMemo(
() => executionFilterGenerator[resourceType](id),
[id]
[id, resourceType]
);

const executions = useWorkflowExecutions(
Expand Down
13 changes: 7 additions & 6 deletions src/components/Entities/EntityExecutionsBarChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@ import { useWorkflowExecutionFiltersState } from 'components/Executions/filters/
import { useWorkflowExecutions } from 'components/hooks/useWorkflowExecutions';
import { SortDirection } from 'models/AdminEntity/types';
import { ResourceIdentifier } from 'models/Common/types';
import { Execution, WorkflowExecutionIdentifier } from 'models/Execution/types';
import { Execution } from 'models/Execution/types';
import { executionSortFields } from 'models/Execution/constants';
import { Routes } from 'routes/routes';
import { history } from 'routes/history';
import { executionFilterGenerator } from './generators';
import {
getWorkflowExecutionPhaseConstants,
Expand All @@ -36,7 +34,10 @@ export interface EntityExecutionsBarChartProps {
chartIds: string[];
}

const getExecutionTimeData = (executions: Execution[], fillSize = 100) => {
export const getExecutionTimeData = (
executions: Execution[],
fillSize = 100
) => {
const newExecutions = [...executions].reverse().map(execution => {
const duration = getWorkflowExecutionTimingMS(execution)?.duration || 1;
return {
Expand Down Expand Up @@ -66,14 +67,14 @@ const getExecutionTimeData = (executions: Execution[], fillSize = 100) => {
}
return new Array(fillSize - newExecutions.length)
.fill(0)
.map(_ => ({
.map(() => ({
value: 1,
color: '#e5e5e5'
}))
.concat(newExecutions);
};

const getStartExecutionTime = (executions: Execution[]) => {
export const getStartExecutionTime = (executions: Execution[]) => {
if (executions.length === 0) {
return '';
}
Expand Down
1 change: 1 addition & 0 deletions src/components/Executions/Tables/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const nodeExecutionsTableColumnWidths = {
duration: 100,
logs: 225,
type: 144,
nodeId: 144,
name: 380,
phase: 150,
startedAt: 200
Expand Down
47 changes: 31 additions & 16 deletions src/components/Executions/Tables/nodeExecutionColumns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,28 +31,25 @@ const NodeExecutionName: React.FC<NodeExecutionCellRendererData> = ({
const detailsQuery = useNodeExecutionDetails(execution);
const commonStyles = useCommonStyles();
const styles = useColumnStyles();
const nodeId = execution.id.nodeId;

const isSelected =
state.selectedExecution != null &&
isEqual(execution.id, state.selectedExecution);

const renderReadableName = ({
displayId,
displayName
}: NodeExecutionDetails) => {
const renderReadableName = ({ displayName }: NodeExecutionDetails) => {
const truncatedName = displayName?.split('.').pop() || '';
const readableName = isSelected ? (
<Typography
variant="body1"
className={styles.selectedExecutionName}
>
{displayId || nodeId}
{truncatedName}
</Typography>
) : (
<SelectNodeExecutionLink
className={commonStyles.primaryLink}
execution={execution}
linkText={displayId || nodeId}
linkText={truncatedName || ''}
state={state}
/>
);
Expand All @@ -75,12 +72,24 @@ const NodeExecutionName: React.FC<NodeExecutionCellRendererData> = ({
);
};

const NodeExecutionDisplayId: React.FC<NodeExecutionCellRendererData> = ({
execution
}) => {
const detailsQuery = useNodeExecutionDetails(execution);
const extractDisplayId = ({ displayId }: NodeExecutionDetails) =>
displayId || execution.id.nodeId;
return <WaitForQuery query={detailsQuery}>{extractDisplayId}</WaitForQuery>;
};

const NodeExecutionDisplayType: React.FC<NodeExecutionCellRendererData> = ({
execution
}) => {
const detailsQuery = useNodeExecutionDetails(execution);
const extractDisplayType = ({ displayType }: NodeExecutionDetails) =>
displayType;
const extractDisplayType = ({ displayType }: NodeExecutionDetails) => (
<Typography color="textSecondary">
{displayType || execution.id.nodeId}
</Typography>
);
return (
<WaitForQuery query={detailsQuery}>{extractDisplayType}</WaitForQuery>
);
Expand Down Expand Up @@ -108,7 +117,19 @@ export function generateColumns(
cellRenderer: props => <NodeExecutionName {...props} />,
className: styles.columnName,
key: 'name',
label: 'node'
label: 'task name'
},
{
cellRenderer: props => <NodeExecutionDisplayId {...props} />,
className: styles.columnNodeId,
key: 'nodeId',
label: 'node id'
},
{
cellRenderer: props => <NodeExecutionDisplayType {...props} />,
className: styles.columnType,
key: 'type',
label: 'type'
},
{
cellRenderer: ({
Expand All @@ -133,12 +154,6 @@ export function generateColumns(
key: 'phase',
label: 'status'
},
{
cellRenderer: props => <NodeExecutionDisplayType {...props} />,
className: styles.columnType,
key: 'type',
label: 'type'
},
{
cellRenderer: ({ execution: { closure } }) => {
const { startedAt } = closure;
Expand Down
6 changes: 5 additions & 1 deletion src/components/Executions/Tables/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,12 @@ export const useColumnStyles = makeStyles((theme: Theme) => ({
marginLeft: theme.spacing(nameColumnLeftMarginGridWidth)
}
},
columnNodeId: {
flexBasis: nodeExecutionsTableColumnWidths.nodeId
},
columnType: {
flexBasis: nodeExecutionsTableColumnWidths.type
flexBasis: nodeExecutionsTableColumnWidths.type,
textTransform: 'capitalize'
},
columnStatus: {
display: 'flex',
Expand Down
21 changes: 17 additions & 4 deletions src/components/Executions/Tables/test/NodeExecutionsTable.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,13 @@ describe('NodeExecutionsTable', () => {
const shouldUpdateFn = (nodeExecutions: NodeExecution[]) =>
nodeExecutions.some(ne => !nodeExecutionIsTerminal(ne));

const selectNode = async (container: HTMLElement, nodeId: string) => {
const selectNode = async (
container: HTMLElement,
truncatedName: string,
nodeId: string
) => {
const nodeNameAnchor = await waitFor(() =>
getByText(container, nodeId)
getByText(container, truncatedName)
);
fireEvent.click(nodeNameAnchor);
// Wait for Details Panel to render and then for the nodeId header
Expand Down Expand Up @@ -500,10 +504,13 @@ describe('NodeExecutionsTable', () => {
it('should render updated state if selected nodeExecution object changes', async () => {
nodeExecution.closure.phase = NodeExecutionPhase.RUNNING;
updateNodeExecutions([nodeExecution]);
const truncatedName =
fixture.tasks.python.id.name.split('.').pop() || '';
// Render table, click first node
const { container } = renderTable();
const detailsPanel = await selectNode(
container,
truncatedName,
nodeExecution.id.nodeId
);
expect(getByText(detailsPanel, 'Running')).toBeInTheDocument();
Expand Down Expand Up @@ -535,8 +542,14 @@ describe('NodeExecutionsTable', () => {
dynamicTaskNameEl,
'listitem'
);
await expandParentNode(dynamicRowEl);
await selectNode(container, childNodeExecution.id.nodeId);
const parentNodeEl = await expandParentNode(dynamicRowEl);
const truncatedName =
fixture.tasks.python.id.name.split('.').pop() || '';
await selectNode(
parentNodeEl[0],
truncatedName,
childNodeExecution.id.nodeId
);

// Wait for Details Panel to render and then for the nodeId header
const detailsPanel = await waitFor(() =>
Expand Down
62 changes: 60 additions & 2 deletions src/components/Project/ProjectExecutions.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { makeStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import { makeStyles, Theme } from '@material-ui/core/styles';
import { getCacheKey } from 'components/Cache/utils';
import { ErrorBoundary } from 'components/common/ErrorBoundary';
import { LargeLoadingSpinner } from 'components/common/LoadingSpinner';
Expand All @@ -13,12 +14,35 @@ import { Execution } from 'models/Execution/types';
import * as React from 'react';
import { useInfiniteQuery } from 'react-query';
import { failedToLoadExecutionsString } from './constants';
import { BarChart } from 'components/common/BarChart';
import {
getExecutionTimeData,
getStartExecutionTime
} from 'components/Entities/EntityExecutionsBarChart';
import classNames from 'classnames';
import { useWorkflowExecutions } from 'components/hooks/useWorkflowExecutions';
import { WaitForData } from 'components/common/WaitForData';
import { history } from 'routes/history';
import { Routes } from 'routes/routes';

const useStyles = makeStyles(() => ({
const useStyles = makeStyles((theme: Theme) => ({
container: {
display: 'flex',
flex: '1 1 auto',
flexDirection: 'column'
},
header: {
paddingBottom: theme.spacing(1),
paddingLeft: theme.spacing(1),
borderBottom: `1px solid ${theme.palette.divider}`
},
marginTop: {
marginTop: theme.spacing(2)
},
chartContainer: {
paddingLeft: theme.spacing(1),
paddingRight: theme.spacing(3),
paddingTop: theme.spacing(1)
}
}));
export interface ProjectExecutionsProps {
Expand Down Expand Up @@ -73,6 +97,19 @@ export const ProjectExecutions: React.FC<ProjectExecutionsProps> = ({
[query.data?.pages]
);

const handleBarChartItemClick = React.useCallback(item => {
history.push(Routes.ExecutionDetails.makeUrl(item.metadata));
}, []);

const last100Executions = useWorkflowExecutions(
{ domain, project },
{
sort: defaultSort,
filter: filtersState.appliedFilters,
limit: 100
}
);

const fetch = React.useCallback(() => query.fetchNextPage(), [query]);

const content = query.isLoadingError ? (
Expand All @@ -99,6 +136,27 @@ export const ProjectExecutions: React.FC<ProjectExecutionsProps> = ({
if (filtersState.filters[4].status === 'LOADED') {
return (
<div className={styles.container}>
<Typography
className={classNames(styles.header, styles.marginTop)}
variant="h6"
>
Last 100 Executions in the Project
</Typography>
<div className={styles.chartContainer}>
<WaitForData {...last100Executions}>
<BarChart
chartIds={[]}
data={getExecutionTimeData(last100Executions.value)}
startDate={getStartExecutionTime(
last100Executions.value
)}
onClickItem={handleBarChartItemClick}
/>
</WaitForData>
</div>
<Typography className={styles.header} variant="h6">
All Executions in the Project
</Typography>
<ExecutionFilters {...filtersState} />
<ErrorBoundary>{content}</ErrorBoundary>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/common/BarChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ const useStyles = makeStyles((theme: Theme) => ({
flex: 1,
display: 'flex',
flexDirection: 'column',
justifyContent: 'flex-end',
alignItems: 'center',
'&:last-child': {
marginRight: 0
}
},
itemBar: {
borderRadius: 2,
flex: 1,
marginRight: theme.spacing(0.25),
minHeight: theme.spacing(0.75),
cursor: 'pointer',
Expand Down

0 comments on commit cca633e

Please sign in to comment.