From 450dd604964dd4dc98f776f2448804ec123e8499 Mon Sep 17 00:00:00 2001 From: Randy Schott <1815175+schottra@users.noreply.github.com> Date: Wed, 9 Sep 2020 15:56:10 -0700 Subject: [PATCH 1/2] refactor: make workflow details generic so it can be used for tasks --- .../EntityDescription.tsx} | 20 ++-- src/components/Entities/EntityDetails.tsx | 97 +++++++++++++++++++ .../Entities/EntityDetailsHeader.tsx | 83 ++++++++++++++++ src/components/Entities/EntityExecutions.tsx | 65 +++++++++++++ src/components/Entities/EntitySchedules.tsx | 60 ++++++++++++ src/components/Entities/constants.ts | 52 ++++++++++ .../Entities/executionFilterGenerator.ts | 30 ++++++ .../Task/TaskDetails/TaskDetails.tsx | 33 +++++++ .../WorkflowDetails/WorkflowDetails.tsx | 89 +++-------------- src/models/Common/types.ts | 4 + 10 files changed, 445 insertions(+), 88 deletions(-) rename src/components/{Workflow/WorkflowDetails/WorkflowDescription.tsx => Entities/EntityDescription.tsx} (74%) create mode 100644 src/components/Entities/EntityDetails.tsx create mode 100644 src/components/Entities/EntityDetailsHeader.tsx create mode 100644 src/components/Entities/EntityExecutions.tsx create mode 100644 src/components/Entities/EntitySchedules.tsx create mode 100644 src/components/Entities/constants.ts create mode 100644 src/components/Entities/executionFilterGenerator.ts create mode 100644 src/components/Task/TaskDetails/TaskDetails.tsx diff --git a/src/components/Workflow/WorkflowDetails/WorkflowDescription.tsx b/src/components/Entities/EntityDescription.tsx similarity index 74% rename from src/components/Workflow/WorkflowDetails/WorkflowDescription.tsx rename to src/components/Entities/EntityDescription.tsx index 5eebe942c..33705cc33 100644 --- a/src/components/Workflow/WorkflowDetails/WorkflowDescription.tsx +++ b/src/components/Entities/EntityDescription.tsx @@ -3,10 +3,11 @@ import { makeStyles, Theme } from '@material-ui/core/styles'; import * as classnames from 'classnames'; import { WaitForData } from 'components'; import { useCommonStyles } from 'components/common/styles'; -import { useWorkflowNamedEntity } from 'components/hooks/useNamedEntity'; -import { NamedEntityIdentifier, NamedEntityMetadata } from 'models'; +import { useNamedEntity } from 'components/hooks/useNamedEntity'; +import { NamedEntityMetadata, ResourceIdentifier } from 'models'; import * as React from 'react'; import reactLoadingSkeleton from 'react-loading-skeleton'; +import { noDescriptionStrings } from './constants'; const Skeleton = reactLoadingSkeleton; @@ -16,21 +17,18 @@ const useStyles = makeStyles((theme: Theme) => ({ } })); -const noDescriptionString = 'This workflow has no description.'; - -/** Fetches and renders the description for a given workflow ID */ -export const WorkflowDescription: React.FC<{ - workflowId: NamedEntityIdentifier; -}> = ({ workflowId }) => { +/** Fetches and renders the description for a given Entity (LaunchPlan,Workflow,Task) ID */ +export const EntityDescription: React.FC<{ + id: ResourceIdentifier; +}> = ({ id }) => { const commonStyles = useCommonStyles(); const styles = useStyles(); - const namedEntity = useWorkflowNamedEntity(workflowId); + const namedEntity = useNamedEntity(id); const { metadata = {} as NamedEntityMetadata } = namedEntity.value; const hasDescription = !!metadata.description; return ( <> Description - {hasDescription ? metadata.description - : noDescriptionString} + : noDescriptionStrings[id.resourceType]} diff --git a/src/components/Entities/EntityDetails.tsx b/src/components/Entities/EntityDetails.tsx new file mode 100644 index 000000000..87ba448b9 --- /dev/null +++ b/src/components/Entities/EntityDetails.tsx @@ -0,0 +1,97 @@ +import { Dialog } from '@material-ui/core'; +import { makeStyles, Theme } from '@material-ui/core/styles'; +import { contentMarginGridUnits } from 'common/layout'; +import { WaitForData } from 'components/common'; +import { EntityDescription } from 'components/Entities/EntityDescription'; +import { useProject } from 'components/hooks'; +import { LaunchWorkflowForm } from 'components/Launch/LaunchWorkflowForm/LaunchWorkflowForm'; +import { ResourceIdentifier } from 'models'; +import * as React from 'react'; +import { entitySections } from './constants'; +import { EntityDetailsHeader } from './EntityDetailsHeader'; +import { EntityExecutions } from './EntityExecutions'; +import { EntitySchedules } from './EntitySchedules'; + +const useStyles = makeStyles((theme: Theme) => ({ + metadataContainer: { + display: 'flex', + marginBottom: theme.spacing(5), + marginTop: theme.spacing(2), + width: '100%' + }, + descriptionContainer: { + flex: '2 1 auto', + marginRight: theme.spacing(2) + }, + executionsContainer: { + display: 'flex', + flex: '1 1 auto', + flexDirection: 'column', + margin: `0 -${theme.spacing(contentMarginGridUnits)}px` + }, + schedulesContainer: { + flex: '1 2 auto', + marginRight: theme.spacing(30) + } +})); + +export interface EntityDetailsProps { + id: ResourceIdentifier; +} + +/** 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). + */ +export const EntityDetails: React.FC = ({ id }) => { + const sections = entitySections[id.resourceType]; + const project = useProject(id.project); + const styles = useStyles(); + const [showLaunchForm, setShowLaunchForm] = React.useState(false); + const onLaunch = () => setShowLaunchForm(true); + const onCancelLaunch = () => setShowLaunchForm(false); + + return ( + <> + + +
+ {!!sections.description ? ( +
+ +
+ ) : null} + {!!sections.schedules ? ( +
+ +
+ ) : null} +
+ {!!sections.executions ? ( +
+ +
+ ) : null} + {/* TODO: LaunchWorkflowForm needs to be made generic */} + {!!sections.launch ? ( + + + + ) : null} +
+ + ); +}; diff --git a/src/components/Entities/EntityDetailsHeader.tsx b/src/components/Entities/EntityDetailsHeader.tsx new file mode 100644 index 000000000..8c5ec1307 --- /dev/null +++ b/src/components/Entities/EntityDetailsHeader.tsx @@ -0,0 +1,83 @@ +import { Button } from '@material-ui/core'; +import { makeStyles, Theme } from '@material-ui/core/styles'; +import ArrowBack from '@material-ui/icons/ArrowBack'; +import * as classnames from 'classnames'; +import { useCommonStyles } from 'components/common/styles'; +import { Project, ResourceIdentifier } from 'models'; +import { getProjectDomain } from 'models/Project/utils'; +import * as React from 'react'; +import { Link } from 'react-router-dom'; +import { Routes } from 'routes'; +import { launchStrings } from './constants'; + +const useStyles = makeStyles((theme: Theme) => ({ + actionsContainer: {}, + headerContainer: { + alignItems: 'center', + display: 'flex', + height: theme.spacing(5), + justifyContent: 'space-between', + marginTop: theme.spacing(2), + width: '100%' + }, + headerText: { + margin: `0 ${theme.spacing(1)}px` + }, + headerTextContainer: { + display: 'flex', + flex: '1 0 auto' + } +})); + +interface EntityDetailsHeaderProps { + project: Project; + id: ResourceIdentifier; + launchable?: boolean; + onClickLaunch?(): void; +} + +/** Renders the workflow name and actions shown on the workflow details page */ +export const EntityDetailsHeader: React.FC = ({ + id, + onClickLaunch, + launchable = false, + project +}) => { + const styles = useStyles(); + const commonStyles = useCommonStyles(); + const domain = getProjectDomain(project, id.domain); + const headerText = `${domain.name} / ${id.name}`; + return ( +
+
+ + + + {headerText} +
+
+ {launchable ? ( + + ) : null} +
+
+ ); +}; diff --git a/src/components/Entities/EntityExecutions.tsx b/src/components/Entities/EntityExecutions.tsx new file mode 100644 index 000000000..9289c5db9 --- /dev/null +++ b/src/components/Entities/EntityExecutions.tsx @@ -0,0 +1,65 @@ +import { Typography } from '@material-ui/core'; +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 { WorkflowExecutionsTable as ExecutionsTable } from 'components/Executions/Tables/WorkflowExecutionsTable'; +import { useWorkflowExecutions as useExecutions } from 'components/hooks'; +import { ResourceIdentifier } from 'models'; +import { SortDirection } from 'models/AdminEntity'; +import { executionSortFields } from 'models/Execution'; +import * as React from 'react'; +import { executionFilterGenerator } from './executionFilterGenerator'; + +const useStyles = makeStyles((theme: Theme) => ({ + filtersContainer: { + borderTop: `1px solid ${theme.palette.divider}` + }, + header: { + marginBottom: theme.spacing(1), + marginLeft: theme.spacing(contentMarginGridUnits) + } +})); + +export interface EntityExecutionsProps { + id: ResourceIdentifier; +} + +/** The tab/page content for viewing a workflow's executions */ +export const EntityExecutions: React.FC = ({ id }) => { + const { domain, project, resourceType } = id; + const styles = useStyles(); + const filtersState = useExecutionFiltersState(); + const sort = { + key: executionSortFields.createdAt, + direction: SortDirection.DESCENDING + }; + + const baseFilters = React.useMemo( + () => executionFilterGenerator[resourceType](id), + [id] + ); + + const executions = useExecutions( + { domain, project }, + { + sort, + filter: [...baseFilters, ...filtersState.appliedFilters] + } + ); + + return ( + <> + + Executions + +
+ +
+ + + + + ); +}; diff --git a/src/components/Entities/EntitySchedules.tsx b/src/components/Entities/EntitySchedules.tsx new file mode 100644 index 000000000..42a52854e --- /dev/null +++ b/src/components/Entities/EntitySchedules.tsx @@ -0,0 +1,60 @@ +import { Typography } from '@material-ui/core'; +import { makeStyles, Theme } from '@material-ui/core/styles'; +import { getScheduleFrequencyString } from 'common/formatters'; +import { WaitForData } from 'components/common'; +import { useCommonStyles } from 'components/common/styles'; +import { useWorkflowSchedules } from 'components/hooks'; +import { LaunchPlan, ResourceIdentifier } from 'models'; +import * as React from 'react'; +import { noSchedulesStrings } from './constants'; + +const useStyles = makeStyles((theme: Theme) => ({ + schedulesContainer: { + marginTop: theme.spacing(1) + } +})); + +const RenderSchedules: React.FC<{ + launchPlans: LaunchPlan[]; +}> = ({ launchPlans }) => { + const commonStyles = useCommonStyles(); + return ( +
    + {launchPlans.map((launchPlan, idx) => { + const { schedule } = launchPlan.spec.entityMetadata; + const frequencyString = getScheduleFrequencyString(schedule); + return
  • {frequencyString}
  • ; + })} +
+ ); +}; + +// TODO: This only supports workflows at the moment. +export const EntitySchedules: React.FC<{ + id: ResourceIdentifier; +}> = ({ id }) => { + const styles = useStyles(); + const commonStyles = useCommonStyles(); + const scheduledLaunchPlans = useWorkflowSchedules(id); + return ( + <> + + Schedules +
+ {scheduledLaunchPlans.value.length > 0 ? ( + + ) : ( + + {noSchedulesStrings[id.resourceType]} + + )} +
+
+ + ); +}; diff --git a/src/components/Entities/constants.ts b/src/components/Entities/constants.ts new file mode 100644 index 000000000..e0fddc1a0 --- /dev/null +++ b/src/components/Entities/constants.ts @@ -0,0 +1,52 @@ +import { mapValues, startCase } from 'lodash'; +import { ResourceType } from 'models'; + +type EntityStringMap = { [k in ResourceType]: string }; + +export const entityStrings: EntityStringMap = { + [ResourceType.DATASET]: 'dataset', + [ResourceType.LAUNCH_PLAN]: 'launch plan', + [ResourceType.TASK]: 'task', + [ResourceType.UNSPECIFIED]: 'item', + [ResourceType.WORKFLOW]: 'workflow' +}; + +export const noDescriptionStrings: EntityStringMap = mapValues( + entityStrings, + typeString => `This ${typeString} has no description.` +); + +export const noSchedulesStrings: EntityStringMap = mapValues( + entityStrings, + typeString => `This ${typeString} has no schedules.` +); + +export const launchStrings: EntityStringMap = mapValues( + entityStrings, + typeString => `Launch ${startCase(typeString)}` +); + +export interface EntitySectionsFlags { + description?: boolean; + executions?: boolean; + launch?: boolean; + schedules?: boolean; +} + +export const entitySections: { [k in ResourceType]: EntitySectionsFlags } = { + [ResourceType.DATASET]: { description: true }, + [ResourceType.LAUNCH_PLAN]: { + description: true, + executions: true, + launch: true, + schedules: true + }, + [ResourceType.TASK]: { description: true, executions: true, launch: true }, + [ResourceType.UNSPECIFIED]: { description: true }, + [ResourceType.WORKFLOW]: { + description: true, + executions: true, + launch: true, + schedules: true + } +}; diff --git a/src/components/Entities/executionFilterGenerator.ts b/src/components/Entities/executionFilterGenerator.ts new file mode 100644 index 000000000..f862d6666 --- /dev/null +++ b/src/components/Entities/executionFilterGenerator.ts @@ -0,0 +1,30 @@ +import { + FilterOperation, + FilterOperationName, + ResourceIdentifier, + ResourceType +} from 'models'; + +const noFilters = () => []; + +export const executionFilterGenerator: { + [k in ResourceType]: (id: ResourceIdentifier) => FilterOperation[]; +} = { + [ResourceType.DATASET]: noFilters, + [ResourceType.LAUNCH_PLAN]: noFilters, + [ResourceType.TASK]: ({ name }) => [ + { + key: 'launch_plan.name', + operation: FilterOperationName.EQ, + value: name + } + ], + [ResourceType.UNSPECIFIED]: noFilters, + [ResourceType.WORKFLOW]: ({ name }) => [ + { + key: 'workflow.name', + operation: FilterOperationName.EQ, + value: name + } + ] +}; diff --git a/src/components/Task/TaskDetails/TaskDetails.tsx b/src/components/Task/TaskDetails/TaskDetails.tsx new file mode 100644 index 000000000..88cac9310 --- /dev/null +++ b/src/components/Task/TaskDetails/TaskDetails.tsx @@ -0,0 +1,33 @@ +import { withRouteParams } from 'components/common'; +import { EntityDetails } from 'components/Entities/EntityDetails'; +import { ResourceIdentifier, ResourceType } from 'models'; +import * as React from 'react'; + +export interface TaskDetailsRouteParams { + projectId: string; + domainId: string; + taskName: string; +} +export type TaskDetailsProps = TaskDetailsRouteParams; + +/** The view component for the Task landing page */ +export const TaskDetailsContainer: React.FC = ({ + projectId, + domainId, + taskName +}) => { + const id = React.useMemo( + () => ({ + resourceType: ResourceType.TASK, + project: projectId, + domain: domainId, + name: taskName + }), + [projectId, domainId, taskName] + ); + return ; +}; + +export const TaskDetails = withRouteParams( + TaskDetailsContainer +); diff --git a/src/components/Workflow/WorkflowDetails/WorkflowDetails.tsx b/src/components/Workflow/WorkflowDetails/WorkflowDetails.tsx index f8a42a7a1..9adc228b3 100644 --- a/src/components/Workflow/WorkflowDetails/WorkflowDetails.tsx +++ b/src/components/Workflow/WorkflowDetails/WorkflowDetails.tsx @@ -1,37 +1,7 @@ -import { Dialog } from '@material-ui/core'; -import { makeStyles, Theme } from '@material-ui/core/styles'; -import { contentMarginGridUnits } from 'common/layout'; -import { WaitForData, withRouteParams } from 'components/common'; -import { useProject } from 'components/hooks'; -import { LaunchWorkflowForm } from 'components/Launch/LaunchWorkflowForm/LaunchWorkflowForm'; +import { withRouteParams } from 'components/common'; +import { EntityDetails } from 'components/Entities/EntityDetails'; +import { ResourceIdentifier, ResourceType } from 'models'; import * as React from 'react'; -import { WorkflowDescription } from './WorkflowDescription'; -import { WorkflowDetailsHeader } from './WorkflowDetailsHeader'; -import { WorkflowExecutions } from './WorkflowExecutions'; -import { WorkflowSchedules } from './WorkflowSchedules'; - -const useStyles = makeStyles((theme: Theme) => ({ - metadataContainer: { - display: 'flex', - marginBottom: theme.spacing(5), - marginTop: theme.spacing(2), - width: '100%' - }, - descriptionContainer: { - flex: '2 1 auto', - marginRight: theme.spacing(2) - }, - executionsContainer: { - display: 'flex', - flex: '1 1 auto', - flexDirection: 'column', - margin: `0 -${theme.spacing(contentMarginGridUnits)}px` - }, - schedulesContainer: { - flex: '1 2 auto', - marginRight: theme.spacing(30) - } -})); export interface WorkflowDetailsRouteParams { projectId: string; @@ -46,51 +16,16 @@ export const WorkflowDetailsContainer: React.FC = ({ domainId, workflowName }) => { - const project = useProject(projectId); - const styles = useStyles(); - const [showLaunchForm, setShowLaunchForm] = React.useState(false); - const onLaunch = () => setShowLaunchForm(true); - const onCancelLaunch = () => setShowLaunchForm(false); - - const workflowId = { - project: projectId, - domain: domainId, - name: workflowName - }; - return ( - <> - - -
-
- -
-
- -
-
-
- -
- - - -
- + const id = React.useMemo( + () => ({ + resourceType: ResourceType.WORKFLOW, + project: projectId, + domain: domainId, + name: workflowName + }), + [projectId, domainId, workflowName] ); + return ; }; export const WorkflowDetails = withRouteParams( diff --git a/src/models/Common/types.ts b/src/models/Common/types.ts index 92765a352..2b29ebd4b 100644 --- a/src/models/Common/types.ts +++ b/src/models/Common/types.ts @@ -20,6 +20,10 @@ export interface Identifier extends Core.IIdentifier { export interface NamedEntityIdentifier extends RequiredNonNullable {} +export interface ResourceIdentifier extends NamedEntityIdentifier { + resourceType: Core.ResourceType; +} + export interface NamedEntityMetadata extends RequiredNonNullable {} From d02feba1238f49160d25ec39488a20d1e854b1bb Mon Sep 17 00:00:00 2001 From: Randy Schott <1815175+schottra@users.noreply.github.com> Date: Wed, 9 Sep 2020 16:11:55 -0700 Subject: [PATCH 2/2] chore: cleanup and moving tests over --- src/components/Entities/EntitySchedules.tsx | 5 +- src/components/Entities/constants.ts | 2 + .../test/EntitySchedules.test.tsx} | 25 +++--- .../Task/{TaskDetails => }/TaskDetails.tsx | 0 .../{WorkflowDetails => }/WorkflowDetails.tsx | 0 .../WorkflowDetails/WorkflowDetailsHeader.tsx | 87 ------------------- .../WorkflowDetails/WorkflowExecutions.tsx | 67 -------------- .../WorkflowDetails/WorkflowLaunchPlans.tsx | 40 --------- .../WorkflowDetails/WorkflowSchedules.tsx | 56 ------------ .../WorkflowDetails/WorkflowVersions.tsx | 41 --------- .../Workflow/WorkflowDetails/index.ts | 2 - src/routes/components.ts | 3 +- 12 files changed, 19 insertions(+), 309 deletions(-) rename src/components/{Workflow/WorkflowDetails/test/WorkflowSchedules.spec.tsx => Entities/test/EntitySchedules.test.tsx} (80%) rename src/components/Task/{TaskDetails => }/TaskDetails.tsx (100%) rename src/components/Workflow/{WorkflowDetails => }/WorkflowDetails.tsx (100%) delete mode 100644 src/components/Workflow/WorkflowDetails/WorkflowDetailsHeader.tsx delete mode 100644 src/components/Workflow/WorkflowDetails/WorkflowExecutions.tsx delete mode 100644 src/components/Workflow/WorkflowDetails/WorkflowLaunchPlans.tsx delete mode 100644 src/components/Workflow/WorkflowDetails/WorkflowSchedules.tsx delete mode 100644 src/components/Workflow/WorkflowDetails/WorkflowVersions.tsx delete mode 100644 src/components/Workflow/WorkflowDetails/index.ts diff --git a/src/components/Entities/EntitySchedules.tsx b/src/components/Entities/EntitySchedules.tsx index 42a52854e..3b04b4852 100644 --- a/src/components/Entities/EntitySchedules.tsx +++ b/src/components/Entities/EntitySchedules.tsx @@ -6,7 +6,7 @@ import { useCommonStyles } from 'components/common/styles'; import { useWorkflowSchedules } from 'components/hooks'; import { LaunchPlan, ResourceIdentifier } from 'models'; import * as React from 'react'; -import { noSchedulesStrings } from './constants'; +import { noSchedulesStrings, schedulesHeader } from './constants'; const useStyles = makeStyles((theme: Theme) => ({ schedulesContainer: { @@ -29,7 +29,6 @@ const RenderSchedules: React.FC<{ ); }; -// TODO: This only supports workflows at the moment. export const EntitySchedules: React.FC<{ id: ResourceIdentifier; }> = ({ id }) => { @@ -39,7 +38,7 @@ export const EntitySchedules: React.FC<{ return ( <> - Schedules + {schedulesHeader}
{scheduledLaunchPlans.value.length > 0 ? ( `This ${typeString} has no description.` ); +export const schedulesHeader = 'Schedules'; + export const noSchedulesStrings: EntityStringMap = mapValues( entityStrings, typeString => `This ${typeString} has no schedules.` diff --git a/src/components/Workflow/WorkflowDetails/test/WorkflowSchedules.spec.tsx b/src/components/Entities/test/EntitySchedules.test.tsx similarity index 80% rename from src/components/Workflow/WorkflowDetails/test/WorkflowSchedules.spec.tsx rename to src/components/Entities/test/EntitySchedules.test.tsx index 13f0a1ee3..9e9066cc6 100644 --- a/src/components/Workflow/WorkflowDetails/test/WorkflowSchedules.spec.tsx +++ b/src/components/Entities/test/EntitySchedules.test.tsx @@ -1,22 +1,24 @@ -import { act, render } from '@testing-library/react'; +import { render, waitFor } from '@testing-library/react'; import { createMockLaunchPlan, mockLaunchPlanSchedules } from 'models/__mocks__/launchPlanData'; import { FilterOperation, FilterOperationName } from 'models/AdminEntity/types'; -import { NamedEntityIdentifier } from 'models/Common/types'; +import { ResourceIdentifier, ResourceType } from 'models/Common/types'; import { listLaunchPlans } from 'models/Launch/api'; import { LaunchPlan, LaunchPlanState } from 'models/Launch/types'; import * as React from 'react'; -import { WorkflowSchedules } from '../WorkflowSchedules'; +import { schedulesHeader } from '../constants'; +import { EntitySchedules } from '../EntitySchedules'; jest.mock('models/Launch/api'); -describe('WorkflowSchedules', () => { +describe('EntitySchedules', () => { const mockListLaunchPlans = listLaunchPlans as jest.Mock< ReturnType >; - const workflowId: NamedEntityIdentifier = { + const id: ResourceIdentifier = { + resourceType: ResourceType.WORKFLOW, project: 'project', domain: 'domain', name: 'name' @@ -24,10 +26,9 @@ describe('WorkflowSchedules', () => { let launchPlans: LaunchPlan[]; const renderSchedules = async () => { - await act(() => { - render(); - return new Promise(resolve => setTimeout(resolve, 0)); - }); + const result = render(); + await waitFor(() => result.getByText(schedulesHeader)); + return result; }; beforeEach(() => { @@ -63,17 +64,17 @@ describe('WorkflowSchedules', () => { { key: 'workflow.name', operation: FilterOperationName.EQ, - value: workflowId.name + value: id.name }, { key: 'workflow.domain', operation: FilterOperationName.EQ, - value: workflowId.domain + value: id.domain }, { key: 'workflow.project', operation: FilterOperationName.EQ, - value: workflowId.project + value: id.project } ]; await renderSchedules(); diff --git a/src/components/Task/TaskDetails/TaskDetails.tsx b/src/components/Task/TaskDetails.tsx similarity index 100% rename from src/components/Task/TaskDetails/TaskDetails.tsx rename to src/components/Task/TaskDetails.tsx diff --git a/src/components/Workflow/WorkflowDetails/WorkflowDetails.tsx b/src/components/Workflow/WorkflowDetails.tsx similarity index 100% rename from src/components/Workflow/WorkflowDetails/WorkflowDetails.tsx rename to src/components/Workflow/WorkflowDetails.tsx diff --git a/src/components/Workflow/WorkflowDetails/WorkflowDetailsHeader.tsx b/src/components/Workflow/WorkflowDetails/WorkflowDetailsHeader.tsx deleted file mode 100644 index 58a0de973..000000000 --- a/src/components/Workflow/WorkflowDetails/WorkflowDetailsHeader.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { Button } from '@material-ui/core'; -import { makeStyles, Theme } from '@material-ui/core/styles'; -import ArrowBack from '@material-ui/icons/ArrowBack'; -import * as classnames from 'classnames'; -import { useCommonStyles } from 'components/common/styles'; -import { Project } from 'models'; -import { getProjectDomain } from 'models/Project/utils'; -import * as React from 'react'; -import { Link } from 'react-router-dom'; -import { Routes } from 'routes'; - -const useStyles = makeStyles((theme: Theme) => ({ - actionsContainer: {}, - headerContainer: { - alignItems: 'center', - display: 'flex', - height: theme.spacing(5), - justifyContent: 'space-between', - marginTop: theme.spacing(2), - width: '100%' - }, - headerText: { - margin: `0 ${theme.spacing(1)}px` - }, - headerTextContainer: { - display: 'flex', - flex: '1 0 auto' - } -})); - -export interface WorkflowDetailsRouteParams { - projectId: string; - domainId: string; - workflowName: string; -} -export type WorkflowDetailsProps = WorkflowDetailsRouteParams; - -interface WorkflowDetailsHeaderProps { - domainId: string; - project: Project; - workflowName: string; - onClickLaunch(): void; -} - -/** Renders the workflow name and actions shown on the workflow details page */ -export const WorkflowDetailsHeader: React.FC = ({ - domainId, - onClickLaunch, - project, - workflowName -}) => { - const styles = useStyles(); - const commonStyles = useCommonStyles(); - const domain = getProjectDomain(project, domainId); - const headerText = `${domain.name} / ${workflowName}`; - return ( -
-
- - - - {headerText} -
-
- -
-
- ); -}; diff --git a/src/components/Workflow/WorkflowDetails/WorkflowExecutions.tsx b/src/components/Workflow/WorkflowDetails/WorkflowExecutions.tsx deleted file mode 100644 index 458d4857f..000000000 --- a/src/components/Workflow/WorkflowDetails/WorkflowExecutions.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { Typography } from '@material-ui/core'; -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 } from 'components/Executions/filters/useExecutionFiltersState'; -import { WorkflowExecutionsTable } from 'components/Executions/Tables/WorkflowExecutionsTable'; -import { useWorkflowExecutions } from 'components/hooks'; -import { NamedEntityIdentifier } from 'models'; -import { FilterOperationName, SortDirection } from 'models/AdminEntity'; -import { executionSortFields } from 'models/Execution'; -import * as React from 'react'; - -const useStyles = makeStyles((theme: Theme) => ({ - filtersContainer: { - borderTop: `1px solid ${theme.palette.divider}` - }, - header: { - marginBottom: theme.spacing(1), - marginLeft: theme.spacing(contentMarginGridUnits) - } -})); - -export interface WorkflowExecutionsProps { - workflowId: NamedEntityIdentifier; -} - -/** The tab/page content for viewing a workflow's executions */ -export const WorkflowExecutions: React.FC = ({ - workflowId: { project, domain, name } -}) => { - const styles = useStyles(); - const filtersState = useWorkflowExecutionFiltersState(); - const sort = { - key: executionSortFields.createdAt, - direction: SortDirection.DESCENDING - }; - - const executions = useWorkflowExecutions( - { domain, project }, - { - sort, - filter: [ - { - key: 'workflow.name', - operation: FilterOperationName.EQ, - value: name - }, - ...filtersState.appliedFilters - ] - } - ); - - return ( - <> - - Executions - -
- -
- - - - - ); -}; diff --git a/src/components/Workflow/WorkflowDetails/WorkflowLaunchPlans.tsx b/src/components/Workflow/WorkflowDetails/WorkflowLaunchPlans.tsx deleted file mode 100644 index 1dbee7131..000000000 --- a/src/components/Workflow/WorkflowDetails/WorkflowLaunchPlans.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import * as React from 'react'; - -import { WaitForData, withRouteParams } from 'components/common'; -import { useLaunchPlans } from 'components/hooks'; -import { LaunchPlansTable } from 'components/Launch/LaunchPlansTable'; - -import { launchSortFields, SortDirection } from 'models'; - -export interface WorkflowLaunchPlansRouteParams { - projectId: string; - domainId: string; - workflowName: string; -} - -/** The tab/page content for viewing a workflow's launch plans */ -export const WorkflowLaunchPlansContainer: React.FC = ({ - projectId: project, - domainId: domain, - workflowName: name -}) => { - const launchPlans = useLaunchPlans( - { domain, name, project }, - { - sort: { - direction: SortDirection.DESCENDING, - key: launchSortFields.createdAt - } - } - ); - - return ( - - - - ); -}; - -export const WorkflowLaunchPlans = withRouteParams< - WorkflowLaunchPlansRouteParams ->(WorkflowLaunchPlansContainer); diff --git a/src/components/Workflow/WorkflowDetails/WorkflowSchedules.tsx b/src/components/Workflow/WorkflowDetails/WorkflowSchedules.tsx deleted file mode 100644 index b41b9f15b..000000000 --- a/src/components/Workflow/WorkflowDetails/WorkflowSchedules.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { Typography } from '@material-ui/core'; -import { makeStyles, Theme } from '@material-ui/core/styles'; -import { getScheduleFrequencyString } from 'common/formatters'; -import { WaitForData } from 'components/common'; -import { useCommonStyles } from 'components/common/styles'; -import { useWorkflowSchedules } from 'components/hooks'; -import { LaunchPlan, NamedEntityIdentifier } from 'models'; -import * as React from 'react'; - -const useStyles = makeStyles((theme: Theme) => ({ - schedulesContainer: { - marginTop: theme.spacing(1) - } -})); - -const noSchedulesString = 'This workflow has no schedules.'; - -const RenderSchedules: React.FC<{ launchPlans: LaunchPlan[] }> = ({ - launchPlans -}) => { - const commonStyles = useCommonStyles(); - if (launchPlans.length === 0) { - return ( - - {noSchedulesString} - - ); - } - - return ( -
    - {launchPlans.map((launchPlan, idx) => { - const { schedule } = launchPlan.spec.entityMetadata; - const frequencyString = getScheduleFrequencyString(schedule); - return
  • {frequencyString}
  • ; - })} -
- ); -}; - -export const WorkflowSchedules: React.FC<{ - workflowId: NamedEntityIdentifier; -}> = ({ workflowId }) => { - const styles = useStyles(); - const scheduledLaunchPlans = useWorkflowSchedules(workflowId); - return ( - <> - - Schedules -
- -
-
- - ); -}; diff --git a/src/components/Workflow/WorkflowDetails/WorkflowVersions.tsx b/src/components/Workflow/WorkflowDetails/WorkflowVersions.tsx deleted file mode 100644 index f527f8603..000000000 --- a/src/components/Workflow/WorkflowDetails/WorkflowVersions.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import * as React from 'react'; - -import { WaitForData, withRouteParams } from 'components/common'; -import { useWorkflows } from 'components/hooks'; - -import { SortDirection, workflowSortFields } from 'models'; - -import { WorkflowsTable } from '../WorkflowsTable'; - -export interface WorkflowVersionsRouteParams { - projectId: string; - domainId: string; - workflowName: string; -} - -/** The tab/page content for viewing a workflow's versions */ -export const WorkflowVersionsContainer: React.FC = ({ - projectId: project, - domainId: domain, - workflowName: name -}) => { - const workflows = useWorkflows( - { domain, name, project }, - { - sort: { - direction: SortDirection.DESCENDING, - key: workflowSortFields.createdAt - } - } - ); - - return ( - - - - ); -}; - -export const WorkflowVersions = withRouteParams( - WorkflowVersionsContainer -); diff --git a/src/components/Workflow/WorkflowDetails/index.ts b/src/components/Workflow/WorkflowDetails/index.ts deleted file mode 100644 index 0df67e8db..000000000 --- a/src/components/Workflow/WorkflowDetails/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './WorkflowDetails'; -export * from './WorkflowExecutions'; diff --git a/src/routes/components.ts b/src/routes/components.ts index 8509e8406..487d90333 100644 --- a/src/routes/components.ts +++ b/src/routes/components.ts @@ -3,7 +3,8 @@ import { TaskExecutionDetails } from 'components/Executions/TaskExecutionDetails import { NotFound } from 'components/NotFound'; import { ProjectDetails } from 'components/Project'; import { SelectProject } from 'components/SelectProject'; -import { WorkflowDetails, WorkflowVersionDetails } from 'components/Workflow'; +import { WorkflowVersionDetails } from 'components/Workflow'; +import { WorkflowDetails } from 'components/Workflow/WorkflowDetails'; /** Indexes the components for each defined route. These are done separately to avoid circular references * in components which include the Routes dictionary