From 9920e0a568a500c23f46519890d7f29753b67479 Mon Sep 17 00:00:00 2001 From: csirius <85753828+csirius@users.noreply.github.com> Date: Thu, 16 Sep 2021 13:25:36 -0400 Subject: [PATCH] Feat/version details (#198) * feat: add workflow versions table Signed-off-by: csirius * feat: workflow version details page Signed-off-by: csirius --- src/components/Entities/EntityDetails.tsx | 51 ++++++++++++------- src/components/Entities/EntityVersions.tsx | 49 +++++++++++++----- .../Tables/ExecutionsTableHeader.tsx | 13 ++++- .../Executions/Tables/WorkflowVersionRow.tsx | 14 ++++- .../Tables/WorkflowVersionsTable.tsx | 39 ++++++++++++-- src/components/Executions/Tables/styles.ts | 3 ++ .../Workflow/WorkflowVersionDetails.tsx | 38 ++++++++++++++ src/routes/ApplicationRouter.tsx | 7 +++ src/routes/components.ts | 4 +- src/routes/routes.ts | 16 ++++++ 10 files changed, 198 insertions(+), 36 deletions(-) create mode 100644 src/components/Workflow/WorkflowVersionDetails.tsx diff --git a/src/components/Entities/EntityDetails.tsx b/src/components/Entities/EntityDetails.tsx index 61ead99409..5977f1e4e1 100644 --- a/src/components/Entities/EntityDetails.tsx +++ b/src/components/Entities/EntityDetails.tsx @@ -12,6 +12,7 @@ import { EntityDetailsHeader } from './EntityDetailsHeader'; import { EntityExecutions } from './EntityExecutions'; import { EntitySchedules } from './EntitySchedules'; import { EntityVersions } from './EntityVersions'; +import classNames from 'classnames'; const useStyles = makeStyles((theme: Theme) => ({ metadataContainer: { @@ -35,6 +36,9 @@ const useStyles = makeStyles((theme: Theme) => ({ flexDirection: 'column', height: theme.spacing(45) }, + versionView: { + flex: '1 1 auto' + }, schedulesContainer: { flex: '1 2 auto', marginRight: theme.spacing(30) @@ -43,6 +47,7 @@ const useStyles = makeStyles((theme: Theme) => ({ export interface EntityDetailsProps { id: ResourceIdentifier; + versionView?: boolean; } function getLaunchProps(id: ResourceIdentifier) { @@ -53,11 +58,17 @@ function getLaunchProps(id: ResourceIdentifier) { return { workflowId: id }; } -/** A view which optionally renders description, schedules, executions, and a +/** + * A view which optionally renders description, schedules, executions, and a * launch button/form for a given entity. Note: not all components are suitable * for use with all entities (not all entities have schedules, for example). + * @param id + * @param versionView */ -export const EntityDetails: React.FC = ({ id }) => { +export const EntityDetails: React.FC = ({ + id, + versionView = false +}) => { const sections = entitySections[id.resourceType]; const project = useProject(id.project); const styles = useStyles(); @@ -73,24 +84,30 @@ export const EntityDetails: React.FC = ({ id }) => { launchable={!!sections.launch} onClickLaunch={onLaunch} /> -
- {sections.description ? ( -
- -
- ) : null} - {sections.schedules ? ( -
- -
- ) : null} -
+ {!versionView && ( +
+ {sections.description ? ( +
+ +
+ ) : null} + {sections.schedules ? ( +
+ +
+ ) : null} +
+ )} {sections.versions ? ( -
- +
+
) : null} - {sections.executions ? ( + {sections.executions && !versionView ? (
diff --git a/src/components/Entities/EntityVersions.tsx b/src/components/Entities/EntityVersions.tsx index 43229a3fce..7a4359f982 100644 --- a/src/components/Entities/EntityVersions.tsx +++ b/src/components/Entities/EntityVersions.tsx @@ -1,4 +1,4 @@ -import { Typography } from '@material-ui/core'; +import Typography from '@material-ui/core/Typography'; import { makeStyles, Theme } from '@material-ui/core/styles'; import { contentMarginGridUnits } from 'common/layout'; import { WaitForData } from 'components/common/WaitForData'; @@ -10,6 +10,8 @@ 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 { Routes } from 'routes/routes'; +import { history } from 'routes/history'; import * as React from 'react'; import { executionFilterGenerator } from './generators'; import { WorkflowVersionsTablePageSize } from './constants'; @@ -32,14 +34,19 @@ const useStyles = makeStyles((theme: Theme) => ({ export interface EntityVersionsProps { id: ResourceIdentifier; + versionView?: boolean; } /** * The tab/page content for viewing a workflow's versions. * @param id + * @param versionView */ -export const EntityVersions: React.FC = ({ id }) => { - const { domain, project, resourceType } = id; +export const EntityVersions: React.FC = ({ + id, + versionView = false +}) => { + const { domain, project, resourceType, name } = id; const styles = useStyles(); const filtersState = useWorkflowExecutionFiltersState(); const sort = { @@ -57,10 +64,21 @@ export const EntityVersions: React.FC = ({ id }) => { { sort, filter: [...baseFilters, ...filtersState.appliedFilters], - limit: WorkflowVersionsTablePageSize + limit: versionView ? 100 : WorkflowVersionsTablePageSize } ); + const handleViewAll = React.useCallback(() => { + history.push( + Routes.WorkflowVersionDetails.makeUrl( + project, + domain, + name, + versions.value[0].id.version ?? '' + ) + ); + }, [project, domain, name, versions]); + /** Don't render component until finish fetching user profile */ if (filtersState.filters[4].status !== 'LOADED') { return null; @@ -68,18 +86,25 @@ export const EntityVersions: React.FC = ({ id }) => { return ( <> -
- - Recent Workflow Versions - - - View All - -
+ {!versionView && ( +
+ + Recent Workflow Versions + + + View All + +
+ )} diff --git a/src/components/Executions/Tables/ExecutionsTableHeader.tsx b/src/components/Executions/Tables/ExecutionsTableHeader.tsx index 25e7be5408..5ab87106de 100644 --- a/src/components/Executions/Tables/ExecutionsTableHeader.tsx +++ b/src/components/Executions/Tables/ExecutionsTableHeader.tsx @@ -9,7 +9,8 @@ import { ColumnDefinition } from './types'; export const ExecutionsTableHeader: React.FC<{ columns: ColumnDefinition[]; scrollbarPadding?: number; -}> = ({ columns, scrollbarPadding = 0 }) => { + versionView?: boolean; +}> = ({ columns, scrollbarPadding = 0, versionView = false }) => { const tableStyles = useExecutionTableStyles(); const scrollbarSpacer = scrollbarPadding > 0 ? ( @@ -17,6 +18,16 @@ export const ExecutionsTableHeader: React.FC<{ ) : null; return (
+ {versionView && ( +
+   +
+ )} {columns.map(({ key, label, className }) => { const labelContent = isFunction(label) ? ( React.createElement(label) diff --git a/src/components/Executions/Tables/WorkflowVersionRow.tsx b/src/components/Executions/Tables/WorkflowVersionRow.tsx index f85cdc3b66..b20eeb0cdd 100644 --- a/src/components/Executions/Tables/WorkflowVersionRow.tsx +++ b/src/components/Executions/Tables/WorkflowVersionRow.tsx @@ -1,4 +1,5 @@ import { makeStyles, Theme } from '@material-ui/core'; +import Radio from '@material-ui/core/Radio'; import classnames from 'classnames'; import * as React from 'react'; import { ListRowProps } from 'react-virtualized'; @@ -11,7 +12,8 @@ import { const useStyles = makeStyles((theme: Theme) => ({ row: { - paddingLeft: theme.spacing(2) + paddingLeft: theme.spacing(2), + cursor: 'pointer' } })); @@ -19,6 +21,9 @@ export interface WorkflowVersionRowProps extends Partial { columns: WorkflowVersionColumnDefinition[]; workflow: Workflow; state: WorkflowExecutionsTableState; + onClick: (() => void) | undefined; + versionView?: boolean; + isChecked?: boolean; } /** @@ -34,7 +39,10 @@ export const WorkflowVersionRow: React.FC = ({ columns, workflow, state, - style + style, + onClick, + versionView = false, + isChecked = false }) => { const tableStyles = useExecutionTableStyles(); const styles = useStyles(); @@ -47,8 +55,10 @@ export const WorkflowVersionRow: React.FC = ({ tableStyles.borderBottom )} style={style} + onClick={onClick} >
+ {versionView && } {columns.map(({ className, key: columnKey, cellRenderer }) => (
{ + versionView?: boolean; +} + +interface WorkflowVersionRouteParams { + workflowVersion: string; +} /** * Renders a table of WorkflowVersion records. * @param props * @constructor */ -export const WorkflowVersionsTable: React.FC> = props => { - const { value: workflows } = props; +export const WorkflowVersionsTable: React.FC = props => { + const { value: workflows, versionView } = props; const state = useWorkflowExecutionsTableState(); const commonStyles = useCommonStyles(); const tableStyles = useExecutionTableStyles(); const listRef = React.useRef(null); + const { workflowVersion } = useParams(); const columns = useWorkflowVersionsTableColumns(); const retry = () => props.fetch(); + const handleClickRow = React.useCallback( + ({ project, name, domain, version }: Identifier) => () => { + history.push( + Routes.WorkflowVersionDetails.makeUrl( + project, + domain, + name, + version + ) + ); + }, + [] + ); + // Custom renderer to allow us to append error content to workflow versions which // are in a failed state const rowRenderer: ListRowRenderer = rowProps => { @@ -38,6 +65,9 @@ export const WorkflowVersionsTable: React.FC> = props => { columns={columns} workflow={workflow} state={state} + onClick={versionView ? handleClickRow(workflow.id) : undefined} + versionView={versionView} + isChecked={workflowVersion === workflow.id.version} /> ); }; @@ -49,7 +79,10 @@ export const WorkflowVersionsTable: React.FC> = props => { commonStyles.flexFill )} > - + ({ marginLeft: theme.spacing(2) } }, + headerColumnVersion: { + width: theme.spacing(4) + }, headerColumnName: { fontSize: smallFontSize, fontWeight: 'bold', diff --git a/src/components/Workflow/WorkflowVersionDetails.tsx b/src/components/Workflow/WorkflowVersionDetails.tsx new file mode 100644 index 0000000000..297909b76d --- /dev/null +++ b/src/components/Workflow/WorkflowVersionDetails.tsx @@ -0,0 +1,38 @@ +import { withRouteParams } from 'components/common/withRouteParams'; +import { EntityDetails } from 'components/Entities/EntityDetails'; +import { ResourceIdentifier, ResourceType } from 'models/Common/types'; +import * as React from 'react'; + +export interface WorkflowVersionDetailsRouteParams { + projectId: string; + domainId: string; + workflowName: string; +} +export type WorkflowDetailsProps = WorkflowVersionDetailsRouteParams; + +/** + * The view component for the Workflow Versions page + * @param projectId + * @param domainId + * @param workflowName + */ +export const WorkflowVersionDetailsContainer: React.FC = ({ + projectId, + domainId, + workflowName +}) => { + const id = React.useMemo( + () => ({ + resourceType: ResourceType.WORKFLOW, + project: projectId, + domain: domainId, + name: workflowName + }), + [projectId, domainId, workflowName] + ); + return ; +}; + +export const WorkflowVersionDetails = withRouteParams< + WorkflowVersionDetailsRouteParams +>(WorkflowVersionDetailsContainer); diff --git a/src/routes/ApplicationRouter.tsx b/src/routes/ApplicationRouter.tsx index fecfc7a52a..052e796290 100644 --- a/src/routes/ApplicationRouter.tsx +++ b/src/routes/ApplicationRouter.tsx @@ -44,9 +44,16 @@ export const ApplicationRouter: React.FC = () => ( component={withSideNavigation(components.taskDetails)} /> + + makeProjectDomainBoundPath( + project, + domain, + `/workflows/${workflowName}/version/${version}` + ), + path: `${projectDomainBasePath}/workflows/:workflowName/version/:workflowVersion` + }; + // Tasks static TaskDetails = { makeUrl: (project: string, domain: string, taskName: string) =>