Skip to content

Commit

Permalink
feat: adds project-level executions view (#135)
Browse files Browse the repository at this point in the history
* feat: adds project-level executions view

* refactor: make table compatible with refresh logic from queryies

* fix: rendering error when changing filters

* chore: docs

* test: adding test for createPaginationQuery

* refactor: split executions table to make project executions testable

* chore: docs
  • Loading branch information
schottra authored Jan 9, 2021
1 parent 392783a commit d8daf6c
Show file tree
Hide file tree
Showing 38 changed files with 715 additions and 309 deletions.
7 changes: 4 additions & 3 deletions src/components/Entities/EntityExecutions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { makeStyles, Theme } from '@material-ui/core/styles';
import { contentMarginGridUnits } from 'common/layout';
import { WaitForData } from 'components/common';
import { ExecutionFilters } from 'components/Executions/ExecutionFilters';
import { useWorkflowExecutionFiltersState as useExecutionFiltersState } from 'components/Executions/filters/useExecutionFiltersState';
import { useWorkflowExecutionFiltersState } from 'components/Executions/filters/useExecutionFiltersState';
import { WorkflowExecutionsTable as ExecutionsTable } from 'components/Executions/Tables/WorkflowExecutionsTable';
import { useWorkflowExecutions as useExecutions } from 'components/hooks';
import { isLoadingState } from 'components/hooks/fetchMachine';
import { ResourceIdentifier } from 'models';
import { SortDirection } from 'models/AdminEntity';
import { executionSortFields } from 'models/Execution';
Expand All @@ -30,7 +31,7 @@ export interface EntityExecutionsProps {
export const EntityExecutions: React.FC<EntityExecutionsProps> = ({ id }) => {
const { domain, project, resourceType } = id;
const styles = useStyles();
const filtersState = useExecutionFiltersState();
const filtersState = useWorkflowExecutionFiltersState();
const sort = {
key: executionSortFields.createdAt,
direction: SortDirection.DESCENDING
Expand Down Expand Up @@ -58,7 +59,7 @@ export const EntityExecutions: React.FC<EntityExecutionsProps> = ({ id }) => {
<ExecutionFilters {...filtersState} />
</div>
<WaitForData {...executions}>
<ExecutionsTable {...executions} />
<ExecutionsTable {...executions} isFetching={isLoadingState(executions.state)} />
</WaitForData>
</>
);
Expand Down
70 changes: 70 additions & 0 deletions src/components/Executions/Tables/WorkflowExecutionRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { makeStyles, Theme } from "@material-ui/core";
import classnames from "classnames";
import { Execution } from "models";
import * as React from "react";
import { ListRowProps } from "react-virtualized";
import { ExpandableExecutionError } from "./ExpandableExecutionError";
import { useExecutionTableStyles } from "./styles";
import { WorkflowExecutionColumnDefinition, WorkflowExecutionsTableState } from "./types";

const useStyles = makeStyles((theme: Theme) => ({
row: {
paddingLeft: theme.spacing(2)
}
}));

export interface WorkflowExecutionRowProps extends Partial<ListRowProps> {
columns: WorkflowExecutionColumnDefinition[];
errorExpanded?: boolean;
execution: Execution;
onExpandCollapseError?(expanded: boolean): void;
state: WorkflowExecutionsTableState;
}

/** Renders a single `Execution` record as a row. Designed to be used as a child
* of `WorkflowExecutionTable`.
*/
export const WorkflowExecutionRow: React.FC<WorkflowExecutionRowProps> = ({
columns,
errorExpanded,
execution,
onExpandCollapseError,
state,
style
}) => {
const { error } = execution.closure;
const tableStyles = useExecutionTableStyles();
const styles = useStyles();

return (
<div
className={classnames(
tableStyles.row,
styles.row,
tableStyles.borderBottom
)}
style={style}
>
<div className={tableStyles.rowColumns}>
{columns.map(({ className, key: columnKey, cellRenderer }) => (
<div
key={columnKey}
className={classnames(tableStyles.rowColumn, className)}
>
{cellRenderer({
execution,
state
})}
</div>
))}
</div>
{error ? (
<ExpandableExecutionError
onExpandCollapse={onExpandCollapseError}
initialExpansionState={errorExpanded}
error={error}
/>
) : null}
</div>
);
};
227 changes: 8 additions & 219 deletions src/components/Executions/Tables/WorkflowExecutionsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,240 +1,33 @@
import { Typography } from '@material-ui/core';
import Link from '@material-ui/core/Link';
import { makeStyles, Theme } from '@material-ui/core/styles';
import * as classnames from 'classnames';
import { noExecutionsFoundString } from 'common/constants';
import {
dateFromNow,
formatDateLocalTimezone,
formatDateUTC,
millisecondsToHMS
} from 'common/formatters';
import { timestampToDate } from 'common/utils';
import { DataList, DataListRef } from 'components';
import { getCacheKey } from 'components/Cache';
import { ListProps } from 'components/common';
import { useCommonStyles } from 'components/common/styles';
import { Execution } from 'models';
import { WorkflowExecutionPhase } from 'models/Execution/enums';
import * as React from 'react';
import { ListRowProps, ListRowRenderer } from 'react-virtualized';
import { ListRowRenderer } from 'react-virtualized';
import { ExecutionInputsOutputsModal } from '../ExecutionInputsOutputsModal';
import { ExecutionStatusBadge } from '../ExecutionStatusBadge';
import { getWorkflowExecutionTimingMS } from '../utils';
import { workflowExecutionsTableColumnWidths } from './constants';
import { ExecutionsTableHeader } from './ExecutionsTableHeader';
import { ExpandableExecutionError } from './ExpandableExecutionError';
import { useExecutionTableStyles } from './styles';
import { ColumnDefinition } from './types';
import { WorkflowExecutionLink } from './WorkflowExecutionLink';
import { useWorkflowExecutionsTableColumns } from './useWorkflowExecutionsTableColumns';
import { useWorkflowExecutionsTableState } from './useWorkflowExecutionTableState';
import { WorkflowExecutionRow } from './WorkflowExecutionRow';

const useStyles = makeStyles((theme: Theme) => ({
cellName: {
paddingLeft: theme.spacing(1)
},
columnName: {
flexBasis: workflowExecutionsTableColumnWidths.name
},
columnLastRun: {
flexBasis: workflowExecutionsTableColumnWidths.lastRun
},
columnStatus: {
flexBasis: workflowExecutionsTableColumnWidths.phase
},
columnStartedAt: {
flexBasis: workflowExecutionsTableColumnWidths.startedAt
},
columnDuration: {
flexBasis: workflowExecutionsTableColumnWidths.duration,
textAlign: 'right'
},
columnInputsOutputs: {
flexGrow: 1,
flexBasis: workflowExecutionsTableColumnWidths.inputsOutputs,
marginLeft: theme.spacing(2),
marginRight: theme.spacing(2),
textAlign: 'left'
},
row: {
paddingLeft: theme.spacing(2)
}
}));

function useWorkflowExecutionsTableState() {
const [
selectedIOExecution,
setSelectedIOExecution
] = React.useState<Execution | null>(null);
return {
selectedIOExecution,
setSelectedIOExecution
};
}

interface WorkflowExecutionCellRendererData {
execution: Execution;
state: ReturnType<typeof useWorkflowExecutionsTableState>;
export interface WorkflowExecutionsTableProps extends ListProps<Execution> {
showWorkflowName?: boolean;
}
type WorkflowExecutionColumnDefinition = ColumnDefinition<
WorkflowExecutionCellRendererData
>;

function generateColumns(
styles: ReturnType<typeof useStyles>,
tableStyles: ReturnType<typeof useExecutionTableStyles>
): WorkflowExecutionColumnDefinition[] {
return [
{
cellRenderer: ({
execution: {
id,
closure: { startedAt }
}
}) => (
<>
<WorkflowExecutionLink id={id} />
<Typography variant="subtitle1" color="textSecondary">
{startedAt
? `Last run ${dateFromNow(
timestampToDate(startedAt)
)}`
: ''}
</Typography>
</>
),
className: styles.columnName,
key: 'name',
label: 'execution id'
},
{
cellRenderer: ({
execution: {
closure: { phase = WorkflowExecutionPhase.UNDEFINED }
}
}) => <ExecutionStatusBadge phase={phase} type="workflow" />,
className: styles.columnStatus,
key: 'phase',
label: 'status'
},
{
cellRenderer: ({ execution: { closure } }) => {
const { startedAt } = closure;
if (!startedAt) {
return '';
}
const startedAtDate = timestampToDate(startedAt);
return (
<>
<Typography variant="body1">
{formatDateUTC(startedAtDate)}
</Typography>
<Typography variant="subtitle1" color="textSecondary">
{formatDateLocalTimezone(startedAtDate)}
</Typography>
</>
);
},
className: styles.columnStartedAt,
key: 'startedAt',
label: 'start time'
},
{
cellRenderer: ({ execution }) => {
const timing = getWorkflowExecutionTimingMS(execution);
return (
<Typography variant="body1">
{timing !== null
? millisecondsToHMS(timing.duration)
: ''}
</Typography>
);
},
className: styles.columnDuration,
key: 'duration',
label: 'duration'
},
{
cellRenderer: ({ execution, state }) => {
const onClick = () => state.setSelectedIOExecution(execution);
return (
<Link component="button" onClick={onClick} variant="body1">
View Inputs &amp; Outputs
</Link>
);
},
className: styles.columnInputsOutputs,
key: 'inputsOutputs',
label: ''
}
];
}

interface WorkflowExecutionRowProps extends ListRowProps {
columns: WorkflowExecutionColumnDefinition[];
errorExpanded: boolean;
execution: Execution;
onExpandCollapseError(expanded: boolean): void;
state: ReturnType<typeof useWorkflowExecutionsTableState>;
}

const WorkflowExecutionRow: React.FC<WorkflowExecutionRowProps> = ({
columns,
errorExpanded,
execution,
onExpandCollapseError,
state,
style
}) => {
const { error } = execution.closure;
const tableStyles = useExecutionTableStyles();
const styles = useStyles();

return (
<div
className={classnames(
tableStyles.row,
styles.row,
tableStyles.borderBottom
)}
style={style}
>
<div className={tableStyles.rowColumns}>
{columns.map(({ className, key: columnKey, cellRenderer }) => (
<div
key={columnKey}
className={classnames(tableStyles.rowColumn, className)}
>
{cellRenderer({
execution,
state
})}
</div>
))}
</div>
{error ? (
<ExpandableExecutionError
onExpandCollapse={onExpandCollapseError}
initialExpansionState={errorExpanded}
error={error}
/>
) : null}
</div>
);
};

export type WorkflowExecutionsTableProps = ListProps<Execution>

/** Renders a table of WorkflowExecution records. Executions with errors will
* have an expanadable container rendered as part of the table row.
*/
export const WorkflowExecutionsTable: React.FC<WorkflowExecutionsTableProps> = props => {
const executions = props.value;
const { value: executions, showWorkflowName = false } = props;
const [expandedErrors, setExpandedErrors] = React.useState<
Dictionary<boolean>
>({});
const state = useWorkflowExecutionsTableState();
const commonStyles = useCommonStyles();
const styles = useStyles();
const tableStyles = useExecutionTableStyles();
const listRef = React.useRef<DataListRef>(null);

Expand All @@ -243,11 +36,7 @@ export const WorkflowExecutionsTable: React.FC<WorkflowExecutionsTableProps> = p
setExpandedErrors({});
}, [executions]);

// Memoizing columns so they won't be re-generated unless the styles change
const columns = React.useMemo(() => generateColumns(styles, tableStyles), [
styles,
commonStyles
]);
const columns = useWorkflowExecutionsTableColumns({ showWorkflowName });

const retry = () => props.fetch();
const onCloseIOModal = () => state.setSelectedIOExecution(null);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import * as classnames from 'classnames';
import { useCommonStyles } from 'components/common/styles';
import * as React from 'react';
import { ExecutionsTableHeader } from '../ExecutionsTableHeader';
import { useExecutionTableStyles } from '../styles';
import { useWorkflowExecutionsTableColumns } from '../useWorkflowExecutionsTableColumns';
import { useWorkflowExecutionsTableState } from '../useWorkflowExecutionTableState';
import { WorkflowExecutionRow } from '../WorkflowExecutionRow';
import { WorkflowExecutionsTableProps } from '../WorkflowExecutionsTable';

/** Mocked, simpler version of WorkflowExecutionsTable which does not use a DataList since
* that will not work in a test environment.
*/
export const WorkflowExecutionsTable: React.FC<WorkflowExecutionsTableProps> = props => {
const { value: executions, showWorkflowName = false } = props;
const state = useWorkflowExecutionsTableState();
const commonStyles = useCommonStyles();
const tableStyles = useExecutionTableStyles();
const columns = useWorkflowExecutionsTableColumns({ showWorkflowName });

return (
<div
className={classnames(
tableStyles.tableContainer,
commonStyles.flexFill
)}
>
<ExecutionsTableHeader columns={columns} />
{executions.map(execution => <WorkflowExecutionRow key={execution.id.name} execution={execution} columns={columns} state={state} /> )}
</div>
);
};
Loading

0 comments on commit d8daf6c

Please sign in to comment.