From 6fff87e40007fd15faae634eb6402045c067dd2c Mon Sep 17 00:00:00 2001 From: csirius <85753828+csirius@users.noreply.github.com> Date: Tue, 31 Aug 2021 13:01:51 -0400 Subject: [PATCH] feat: add workflow versions table (#193) Signed-off-by: csirius --- src/common/constants.ts | 1 + src/components/Entities/EntityDetails.tsx | 11 +++ src/components/Entities/EntityVersions.tsx | 87 ++++++++++++++++++ src/components/Entities/constants.ts | 6 +- .../Executions/Tables/WorkflowVersionRow.tsx | 66 ++++++++++++++ .../Tables/WorkflowVersionsTable.tsx | 63 +++++++++++++ src/components/Executions/Tables/constants.ts | 7 ++ src/components/Executions/Tables/styles.ts | 23 ++++- src/components/Executions/Tables/types.ts | 10 ++ .../useWorkflowVersionsTableColumns.tsx | 91 +++++++++++++++++++ src/components/hooks/useWorkflowVersions.ts | 20 ++++ 11 files changed, 383 insertions(+), 2 deletions(-) create mode 100644 src/components/Entities/EntityVersions.tsx create mode 100644 src/components/Executions/Tables/WorkflowVersionRow.tsx create mode 100644 src/components/Executions/Tables/WorkflowVersionsTable.tsx create mode 100644 src/components/Executions/Tables/useWorkflowVersionsTableColumns.tsx create mode 100644 src/components/hooks/useWorkflowVersions.ts diff --git a/src/common/constants.ts b/src/common/constants.ts index ed8fab02e8..e4c34b05cd 100644 --- a/src/common/constants.ts +++ b/src/common/constants.ts @@ -7,6 +7,7 @@ export const dashedValueString = '----'; export const noDescriptionString = '(No description)'; export const noneString = '(none)'; export const noExecutionsFoundString = 'No executions found.'; +export const noWorkflowVersionsFoundString = 'No workflow versions found.'; export const zeroSecondsString = '0s'; export enum KeyCodes { diff --git a/src/components/Entities/EntityDetails.tsx b/src/components/Entities/EntityDetails.tsx index 4978773b0c..4c4fee4212 100644 --- a/src/components/Entities/EntityDetails.tsx +++ b/src/components/Entities/EntityDetails.tsx @@ -11,6 +11,7 @@ import { entitySections } from './constants'; import { EntityDetailsHeader } from './EntityDetailsHeader'; import { EntityExecutions } from './EntityExecutions'; import { EntitySchedules } from './EntitySchedules'; +import { EntityVersions } from './EntityVersions'; const useStyles = makeStyles((theme: Theme) => ({ metadataContainer: { @@ -29,6 +30,11 @@ const useStyles = makeStyles((theme: Theme) => ({ flexDirection: 'column', margin: `0 -${theme.spacing(contentMarginGridUnits)}px` }, + versionsContainer: { + display: 'flex', + flex: '1 1 auto', + flexDirection: 'column' + }, schedulesContainer: { flex: '1 2 auto', marginRight: theme.spacing(30) @@ -79,6 +85,11 @@ export const EntityDetails: React.FC = ({ id }) => { ) : null} + {sections.versions ? ( +
+ +
+ ) : null} {sections.executions ? (
diff --git a/src/components/Entities/EntityVersions.tsx b/src/components/Entities/EntityVersions.tsx new file mode 100644 index 0000000000..43229a3fce --- /dev/null +++ b/src/components/Entities/EntityVersions.tsx @@ -0,0 +1,87 @@ +import { Typography } from '@material-ui/core'; +import { makeStyles, Theme } from '@material-ui/core/styles'; +import { contentMarginGridUnits } from 'common/layout'; +import { WaitForData } from 'components/common/WaitForData'; +import { useWorkflowExecutionFiltersState } from 'components/Executions/filters/useExecutionFiltersState'; +import { WorkflowVersionsTable } from 'components/Executions/Tables/WorkflowVersionsTable'; +import { isLoadingState } from 'components/hooks/fetchMachine'; +import { useWorkflowVersions } from 'components/hooks/useWorkflowVersions'; +import { interactiveTextColor } from 'components/Theme/constants'; +import { SortDirection } from 'models/AdminEntity/types'; +import { ResourceIdentifier } from 'models/Common/types'; +import { executionSortFields } from 'models/Execution/constants'; +import * as React from 'react'; +import { executionFilterGenerator } from './generators'; +import { WorkflowVersionsTablePageSize } from './constants'; + +const useStyles = makeStyles((theme: Theme) => ({ + headerContainer: { + display: 'flex', + justifyContent: 'space-between', + marginLeft: theme.spacing(contentMarginGridUnits), + marginRight: theme.spacing(contentMarginGridUnits) + }, + header: { + marginBottom: theme.spacing(1) + }, + viewAll: { + color: interactiveTextColor, + cursor: 'pointer' + } +})); + +export interface EntityVersionsProps { + id: ResourceIdentifier; +} + +/** + * The tab/page content for viewing a workflow's versions. + * @param id + */ +export const EntityVersions: React.FC = ({ id }) => { + const { domain, project, resourceType } = id; + const styles = useStyles(); + const filtersState = useWorkflowExecutionFiltersState(); + const sort = { + key: executionSortFields.createdAt, + direction: SortDirection.DESCENDING + }; + + const baseFilters = React.useMemo( + () => executionFilterGenerator[resourceType](id), + [id] + ); + + const versions = useWorkflowVersions( + { domain, project }, + { + sort, + filter: [...baseFilters, ...filtersState.appliedFilters], + limit: WorkflowVersionsTablePageSize + } + ); + + /** Don't render component until finish fetching user profile */ + if (filtersState.filters[4].status !== 'LOADED') { + return null; + } + + return ( + <> +
+ + Recent Workflow Versions + + + View All + +
+ + + + + ); +}; diff --git a/src/components/Entities/constants.ts b/src/components/Entities/constants.ts index 7020293b24..ac84c2b6eb 100644 --- a/src/components/Entities/constants.ts +++ b/src/components/Entities/constants.ts @@ -33,6 +33,7 @@ export interface EntitySectionsFlags { executions?: boolean; launch?: boolean; schedules?: boolean; + versions?: boolean; } export const entitySections: { [k in ResourceType]: EntitySectionsFlags } = { @@ -49,6 +50,9 @@ export const entitySections: { [k in ResourceType]: EntitySectionsFlags } = { description: true, executions: true, launch: true, - schedules: true + schedules: true, + versions: true } }; + +export const WorkflowVersionsTablePageSize = 5; diff --git a/src/components/Executions/Tables/WorkflowVersionRow.tsx b/src/components/Executions/Tables/WorkflowVersionRow.tsx new file mode 100644 index 0000000000..f85cdc3b66 --- /dev/null +++ b/src/components/Executions/Tables/WorkflowVersionRow.tsx @@ -0,0 +1,66 @@ +import { makeStyles, Theme } from '@material-ui/core'; +import classnames from 'classnames'; +import * as React from 'react'; +import { ListRowProps } from 'react-virtualized'; +import { Workflow } from 'models/Workflow/types'; +import { useExecutionTableStyles } from './styles'; +import { + WorkflowExecutionsTableState, + WorkflowVersionColumnDefinition +} from './types'; + +const useStyles = makeStyles((theme: Theme) => ({ + row: { + paddingLeft: theme.spacing(2) + } +})); + +export interface WorkflowVersionRowProps extends Partial { + columns: WorkflowVersionColumnDefinition[]; + workflow: Workflow; + state: WorkflowExecutionsTableState; +} + +/** + * Renders a single `Workflow` record as a row. Designed to be used as a child + * of `WorkflowVersionsTable`. + * @param columns + * @param workflow + * @param state + * @param style + * @constructor + */ +export const WorkflowVersionRow: React.FC = ({ + columns, + workflow, + state, + style +}) => { + const tableStyles = useExecutionTableStyles(); + const styles = useStyles(); + + return ( +
+
+ {columns.map(({ className, key: columnKey, cellRenderer }) => ( +
+ {cellRenderer({ + workflow, + state + })} +
+ ))} +
+
+ ); +}; diff --git a/src/components/Executions/Tables/WorkflowVersionsTable.tsx b/src/components/Executions/Tables/WorkflowVersionsTable.tsx new file mode 100644 index 0000000000..ad814c2b30 --- /dev/null +++ b/src/components/Executions/Tables/WorkflowVersionsTable.tsx @@ -0,0 +1,63 @@ +import * as classnames from 'classnames'; +import { noWorkflowVersionsFoundString } from 'common/constants'; +import { useCommonStyles } from 'components/common/styles'; +import { ListProps } from 'components/common/types'; +import { DataList, DataListRef } from 'components/Tables/DataList'; +import { Workflow } from 'models/Workflow/types'; +import * as React from 'react'; +import { ListRowRenderer } from 'react-virtualized'; +import { ExecutionsTableHeader } from './ExecutionsTableHeader'; +import { useExecutionTableStyles } from './styles'; +import { useWorkflowExecutionsTableState } from './useWorkflowExecutionTableState'; +import { useWorkflowVersionsTableColumns } from './useWorkflowVersionsTableColumns'; +import { WorkflowVersionRow } from './WorkflowVersionRow'; + +/** + * Renders a table of WorkflowVersion records. + * @param props + * @constructor + */ +export const WorkflowVersionsTable: React.FC> = props => { + const { value: workflows } = props; + const state = useWorkflowExecutionsTableState(); + const commonStyles = useCommonStyles(); + const tableStyles = useExecutionTableStyles(); + const listRef = React.useRef(null); + + const columns = useWorkflowVersionsTableColumns(); + + const retry = () => props.fetch(); + + // Custom renderer to allow us to append error content to workflow versions which + // are in a failed state + const rowRenderer: ListRowRenderer = rowProps => { + const workflow = workflows[rowProps.index]; + return ( + + ); + }; + + return ( +
+ + +
+ ); +}; diff --git a/src/components/Executions/Tables/constants.ts b/src/components/Executions/Tables/constants.ts index 3853b92d01..29f1c2f4fa 100644 --- a/src/components/Executions/Tables/constants.ts +++ b/src/components/Executions/Tables/constants.ts @@ -21,3 +21,10 @@ export const titleStrings = { expandRow: 'Expand row', groupName: 'Group name' }; + +export const workflowVersionsTableColumnWidths = { + name: 380, + release: 150, + lastRun: 175, + createdAt: 260 +}; diff --git a/src/components/Executions/Tables/styles.ts b/src/components/Executions/Tables/styles.ts index c3ce21d78c..98ddc035fa 100644 --- a/src/components/Executions/Tables/styles.ts +++ b/src/components/Executions/Tables/styles.ts @@ -10,7 +10,8 @@ import { } from 'components/Theme/constants'; import { nodeExecutionsTableColumnWidths, - workflowExecutionsTableColumnWidths + workflowExecutionsTableColumnWidths, + workflowVersionsTableColumnWidths } from './constants'; export const selectedClassName = 'selected'; @@ -188,3 +189,23 @@ export const useWorkflowExecutionsColumnStyles = makeStyles((theme: Theme) => ({ textAlign: 'left' } })); + +/** Style overrides specific to columns in `WorkflowVersionsTable`. */ +export const useWorkflowVersionsColumnStyles = makeStyles((theme: Theme) => ({ + columnName: { + flexBasis: workflowVersionsTableColumnWidths.name, + whiteSpace: 'normal' + }, + columnRelease: { + flexBasis: workflowVersionsTableColumnWidths.release + }, + columnCreatedAt: { + flexBasis: workflowVersionsTableColumnWidths.createdAt + }, + columnLastRun: { + flexBasis: workflowVersionsTableColumnWidths.lastRun + }, + columnRecentRun: { + flexGrow: 1 + } +})); diff --git a/src/components/Executions/Tables/types.ts b/src/components/Executions/Tables/types.ts index ca1ce016d7..35ebf5ea4b 100644 --- a/src/components/Executions/Tables/types.ts +++ b/src/components/Executions/Tables/types.ts @@ -3,6 +3,7 @@ import { NodeExecution, NodeExecutionIdentifier } from 'models/Execution/types'; +import { Workflow } from 'models/Workflow/types'; export interface WorkflowExecutionsTableState { selectedIOExecution: Execution | null; @@ -37,3 +38,12 @@ export interface WorkflowExecutionCellRendererData { export type WorkflowExecutionColumnDefinition = ColumnDefinition< WorkflowExecutionCellRendererData >; + +export interface WorkflowVersionCellRendererData { + workflow: Workflow; + state: WorkflowExecutionsTableState; +} + +export type WorkflowVersionColumnDefinition = ColumnDefinition< + WorkflowVersionCellRendererData +>; diff --git a/src/components/Executions/Tables/useWorkflowVersionsTableColumns.tsx b/src/components/Executions/Tables/useWorkflowVersionsTableColumns.tsx new file mode 100644 index 0000000000..9460654480 --- /dev/null +++ b/src/components/Executions/Tables/useWorkflowVersionsTableColumns.tsx @@ -0,0 +1,91 @@ +import { Typography } from '@material-ui/core'; +import { formatDateUTC } from 'common/formatters'; +import { timestampToDate } from 'common/utils'; +import { useCommonStyles } from 'components/common/styles'; +import * as React from 'react'; +import { useWorkflowVersionsColumnStyles } from './styles'; +import { WorkflowVersionColumnDefinition } from './types'; + +/** + * Returns a memoized list of column definitions to use when rendering a + * `WorkflowVersionRow`. Memoization is based on common/column style objects + * and any fields in the incoming `WorkflowExecutionColumnOptions` object. + */ +export function useWorkflowVersionsTableColumns(): WorkflowVersionColumnDefinition[] { + const styles = useWorkflowVersionsColumnStyles(); + const commonStyles = useCommonStyles(); + return React.useMemo( + () => [ + { + cellRenderer: ({ + workflow: { + id: { version } + } + }) => {version}, + className: styles.columnName, + key: 'name', + label: 'version id' + }, + // { + // cellRenderer: ({ workflow: { closure } }) => { + // return ( + // + // 1.8.1 + // + // ); + // }, + // className: styles.columnRelease, + // key: 'release', + // label: 'Release' + // }, + { + cellRenderer: ({ workflow: { closure } }) => { + if (!closure?.createdAt) { + return ''; + } + const createdAtDate = timestampToDate(closure.createdAt); + return ( + + {formatDateUTC(createdAtDate)} + + ); + }, + className: styles.columnCreatedAt, + key: 'createdAt', + label: 'time created' + } + // { + // cellRenderer: ({ workflow }) => { + // // const timing = getWorkflowExecutionTimingMS(workflow); + // // return ( + // // + // // {timing !== null + // // ? millisecondsToHMS(timing.duration) + // // : ''} + // // + // // ); + // return NA hours ago + // }, + // className: styles.columnLastRun, + // key: 'duration', + // label: 'last execution' + // }, + // { + // cellRenderer: ({ workflow, state }) => { + // return ( + // + // View Inputs & Outputs + // + // ); + // }, + // className: styles.columnRecentRun, + // key: 'inputsOutputs', + // label: 'recent run' + // } + ], + [styles, commonStyles] + ); +} diff --git a/src/components/hooks/useWorkflowVersions.ts b/src/components/hooks/useWorkflowVersions.ts new file mode 100644 index 0000000000..d6e7b5e50b --- /dev/null +++ b/src/components/hooks/useWorkflowVersions.ts @@ -0,0 +1,20 @@ +import { IdentifierScope } from 'models/Common/types'; +import { RequestConfig } from 'models/AdminEntity/types'; +import { listWorkflows } from 'models/Workflow/api'; +import { Workflow } from 'models/Workflow/types'; +import { usePagination } from './usePagination'; + +/** + * A hook for fetching a paginated list of workflow versions. + * @param scope + * @param config + */ +export function useWorkflowVersions( + scope: IdentifierScope, + config: RequestConfig +) { + return usePagination( + { ...config, cacheItems: true, fetchArg: scope }, + listWorkflows + ); +}