diff --git a/packages/zapp/console/src/components/Entities/EntityExecutions.tsx b/packages/zapp/console/src/components/Entities/EntityExecutions.tsx
index da2e701b8..b59bfce88 100644
--- a/packages/zapp/console/src/components/Entities/EntityExecutions.tsx
+++ b/packages/zapp/console/src/components/Entities/EntityExecutions.tsx
@@ -1,5 +1,4 @@
import * as React from 'react';
-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';
@@ -15,8 +14,6 @@ import { executionSortFields } from 'models/Execution/constants';
import { compact } from 'lodash';
import { useOnlyMyExecutionsFilterState } from 'components/Executions/filters/useOnlyMyExecutionsFilterState';
import { executionFilterGenerator } from './generators';
-import { entityStrings } from './constants';
-import t, { patternKey } from './strings';
const useStyles = makeStyles((theme: Theme) => ({
filtersContainer: {
@@ -78,9 +75,6 @@ export const EntityExecutions: React.FC
= ({
return (
<>
-
- {t(patternKey('allExecutionsChartTitle', entityStrings[id.resourceType]))}
-
({
+ header: {
+ marginBottom: theme.spacing(1),
+ },
+ divider: {
+ borderBottom: `1px solid ${theme.palette.divider}`,
+ marginBottom: theme.spacing(1),
+ },
+ rowContainer: {
+ display: 'flex',
+ marginTop: theme.spacing(3),
+ },
+ firstColumnContainer: {
+ width: '60%',
+ marginRight: theme.spacing(3),
+ },
+ secondColumnContainer: {
+ width: '40%',
+ },
+ configs: {
+ listStyleType: 'none',
+ paddingInlineStart: 0,
+ },
+ config: {
+ display: 'flex',
+ },
+ configName: {
+ color: theme.palette.grey[400],
+ marginRight: theme.spacing(2),
+ minWidth: '95px',
+ },
+ configValue: {
+ color: '#333',
+ fontSize: '14px',
+ },
+ headCell: {
+ color: theme.palette.grey[400],
+ },
+ noInputs: {
+ color: theme.palette.grey[400],
+ },
+}));
+
+interface Input {
+ name: string;
+ type?: string;
+ required?: boolean;
+ defaultValue?: string;
+}
+
+/** Fetches and renders the expected & fixed inputs for a given Entity (LaunchPlan) ID */
+export const EntityInputs: React.FC<{
+ id: ResourceIdentifier;
+}> = ({ id }) => {
+ const styles = useStyles();
+
+ const launchPlanState = useLaunchPlans(
+ { project: id.project, domain: id.domain },
+ {
+ limit: 1,
+ filter: [
+ {
+ key: 'launch_plan.name',
+ operation: FilterOperationName.EQ,
+ value: id.name,
+ },
+ ],
+ },
+ );
+
+ const closure = launchPlanState?.value?.length
+ ? launchPlanState.value[0].closure
+ : ({} as LaunchPlanClosure);
+
+ const spec = launchPlanState?.value?.length
+ ? launchPlanState.value[0].spec
+ : ({} as LaunchPlanSpec);
+
+ const expectedInputs = React.useMemo(() => {
+ const results = [] as Input[];
+ Object.keys(closure?.expectedInputs?.parameters ?? {}).forEach((name) => {
+ const parameter = closure?.expectedInputs.parameters[name];
+ if (parameter?.var?.type) {
+ const typeDefinition = getInputDefintionForLiteralType(parameter.var.type);
+ results.push({
+ name,
+ type: formatType(typeDefinition),
+ required: !!parameter.required,
+ defaultValue: parameter.default?.value,
+ });
+ }
+ });
+ return results;
+ }, [closure]);
+
+ const fixedInputs = React.useMemo(() => {
+ const inputsMap = transformLiterals(spec?.fixedInputs?.literals ?? {});
+ return Object.keys(inputsMap).map((name) => ({ name, defaultValue: inputsMap[name] }));
+ }, [spec]);
+
+ const configs = React.useMemo(
+ () => [
+ { name: t('configType'), value: 'single (csv)' },
+ {
+ name: t('configUrl'),
+ value:
+ 'https://raw.githubusercontent.com/jbrownlee/Datasets/master/pima-indians-diabetes.data.csv',
+ },
+ { name: t('configSeed'), value: '7' },
+ { name: t('configTestSplitRatio'), value: '0.33' },
+ ],
+ [],
+ );
+
+ return (
+ <>
+
+ {t('launchPlanLatest')}
+
+
+
+
+
+ {t('expectedInputs')}
+
+ {expectedInputs.length ? (
+
+
+
+
+
+
+ {t('inputsName')}
+
+
+
+
+ {t('inputsType')}
+
+
+
+
+ {t('inputsRequired')}
+
+
+
+
+ {t('inputsDefault')}
+
+
+
+
+
+ {expectedInputs.map(({ name, type, required, defaultValue }) => (
+
+ {name}
+ {type}
+
+ {required ? : ''}
+
+ {defaultValue || '-'}
+
+ ))}
+
+
+
+ ) : (
+
{t('noExpectedInputs')}
+ )}
+
+
+
+ {t('fixedInputs')}
+
+ {fixedInputs.length ? (
+
+
+
+
+
+
+ {t('inputsName')}
+
+
+
+
+ {t('inputsDefault')}
+
+
+
+
+
+ {fixedInputs.map(({ name, defaultValue }) => (
+
+ {name}
+ {defaultValue || '-'}
+
+ ))}
+
+
+
+ ) : (
+
{t('noFixedInputs')}
+ )}
+
+
+ {/*
+
+
+ {t('configuration')}
+
+
+ {configs.map(({ name, value }) => (
+ -
+ {name}:
+ {value}
+
+ ))}
+
+
+
+
*/}
+ >
+ );
+};
diff --git a/packages/zapp/console/src/components/Entities/EntityVersions.tsx b/packages/zapp/console/src/components/Entities/EntityVersions.tsx
index 4509f3090..a9c1339cf 100644
--- a/packages/zapp/console/src/components/Entities/EntityVersions.tsx
+++ b/packages/zapp/console/src/components/Entities/EntityVersions.tsx
@@ -11,7 +11,7 @@ import { isLoadingState } from 'components/hooks/fetchMachine';
import { useEntityVersions } from 'components/hooks/Entity/useEntityVersions';
import { interactiveTextColor } from 'components/Theme/constants';
import { SortDirection } from 'models/AdminEntity/types';
-import { Identifier, ResourceIdentifier } from 'models/Common/types';
+import { Identifier, ResourceIdentifier, ResourceType } from 'models/Common/types';
import { executionSortFields } from 'models/Execution/constants';
import { executionFilterGenerator, versionDetailsUrlGenerator } from './generators';
import { WorkflowVersionsTablePageSize, entityStrings } from './constants';
@@ -20,6 +20,7 @@ import t, { patternKey } from './strings';
const useStyles = makeStyles((theme: Theme) => ({
headerContainer: {
display: 'flex',
+ marginTop: theme.spacing(3),
},
collapseButton: {
marginTop: theme.spacing(-0.5),
@@ -114,7 +115,7 @@ export const EntityVersions: React.FC = ({ id, showAll = fa
) : (
diff --git a/packages/zapp/console/src/components/Entities/VersionDetails/EntityVersionDetailsContainer.tsx b/packages/zapp/console/src/components/Entities/VersionDetails/EntityVersionDetailsContainer.tsx
index 931edeb09..91a274130 100644
--- a/packages/zapp/console/src/components/Entities/VersionDetails/EntityVersionDetailsContainer.tsx
+++ b/packages/zapp/console/src/components/Entities/VersionDetails/EntityVersionDetailsContainer.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import { withRouteParams } from 'components/common/withRouteParams';
-import { ResourceIdentifier } from 'models/Common/types';
+import { ResourceIdentifier, ResourceType } from 'models/Common/types';
import { makeStyles, Theme } from '@material-ui/core/styles';
import { WaitForData } from 'components/common/WaitForData';
import { useProject } from 'components/hooks/useProjects';
@@ -13,7 +13,11 @@ import { typeNameToEntityResource } from '../constants';
import { versionsDetailsSections } from './constants';
import { EntityVersionDetails } from './EntityVersionDetails';
-const useStyles = makeStyles((theme: Theme) => ({
+interface StyleProps {
+ resourceType: ResourceType;
+}
+
+const useStyles = makeStyles((theme: Theme) => ({
verionDetailsContainer: {
marginTop: theme.spacing(2),
display: 'flex',
@@ -39,9 +43,9 @@ const useStyles = makeStyles((theme: Theme) => ({
versionsContainer: {
display: 'flex',
flex: '0 1 auto',
- height: '40%',
+ height: ({ resourceType }) => (resourceType === ResourceType.LAUNCH_PLAN ? '100%' : '40%'),
flexDirection: 'column',
- overflowY: 'scroll',
+ overflowY: 'auto',
},
}));
@@ -81,7 +85,7 @@ const EntityVersionsDetailsContainerImpl: React.FC
diff --git a/packages/zapp/console/src/components/Entities/constants.ts b/packages/zapp/console/src/components/Entities/constants.ts
index efef36a63..6729064c3 100644
--- a/packages/zapp/console/src/components/Entities/constants.ts
+++ b/packages/zapp/console/src/components/Entities/constants.ts
@@ -4,7 +4,7 @@ type EntityStringMap = { [k in ResourceType]: string };
export const entityStrings: EntityStringMap = {
[ResourceType.DATASET]: 'dataset',
- [ResourceType.LAUNCH_PLAN]: 'launch plan',
+ [ResourceType.LAUNCH_PLAN]: 'launch_plan',
[ResourceType.TASK]: 'task',
[ResourceType.UNSPECIFIED]: 'item',
[ResourceType.WORKFLOW]: 'workflow',
@@ -14,7 +14,7 @@ type TypeNameToEntityResourceType = { [key: string]: ResourceType };
export const typeNameToEntityResource: TypeNameToEntityResourceType = {
['dataset']: ResourceType.DATASET,
- ['launch plan']: ResourceType.LAUNCH_PLAN,
+ ['launch_plan']: ResourceType.LAUNCH_PLAN,
['task']: ResourceType.TASK,
['item']: ResourceType.UNSPECIFIED,
['workflow']: ResourceType.WORKFLOW,
@@ -27,15 +27,17 @@ interface EntitySectionsFlags {
schedules?: boolean;
versions?: boolean;
descriptionInputsAndOutputs?: boolean;
+ inputs?: boolean;
}
export const entitySections: { [k in ResourceType]: EntitySectionsFlags } = {
[ResourceType.DATASET]: { description: true },
[ResourceType.LAUNCH_PLAN]: {
- description: true,
executions: true,
- launch: true,
+ launch: false,
+ inputs: true,
schedules: true,
+ versions: true,
},
[ResourceType.TASK]: {
description: true,
diff --git a/packages/zapp/console/src/components/Entities/generators.ts b/packages/zapp/console/src/components/Entities/generators.ts
index f10c41fee..c84aefc8c 100644
--- a/packages/zapp/console/src/components/Entities/generators.ts
+++ b/packages/zapp/console/src/components/Entities/generators.ts
@@ -6,10 +6,30 @@ import { entityStrings } from './constants';
const noFilters = () => [];
export const executionFilterGenerator: {
- [k in ResourceType]: (id: ResourceIdentifier) => FilterOperation[];
+ [k in ResourceType]: (id: ResourceIdentifier, version?: string) => FilterOperation[];
} = {
[ResourceType.DATASET]: noFilters,
- [ResourceType.LAUNCH_PLAN]: noFilters,
+ [ResourceType.LAUNCH_PLAN]: ({ name }, version) =>
+ version
+ ? [
+ {
+ key: 'launch_plan.name',
+ operation: FilterOperationName.EQ,
+ value: name,
+ },
+ {
+ key: 'launch_plan.version',
+ operation: FilterOperationName.EQ,
+ value: version,
+ },
+ ]
+ : [
+ {
+ key: 'launch_plan.name',
+ operation: FilterOperationName.EQ,
+ value: name,
+ },
+ ],
[ResourceType.TASK]: ({ name }) => [
{
key: 'task.name',
@@ -29,6 +49,8 @@ export const executionFilterGenerator: {
const workflowListGenerator = ({ project, domain }: ResourceIdentifier) =>
Routes.ProjectDetails.sections.workflows.makeUrl(project, domain);
+const launchPlanListGenerator = ({ project, domain }: ResourceIdentifier) =>
+ Routes.ProjectDetails.sections.launchPlans.makeUrl(project, domain);
const taskListGenerator = ({ project, domain }: ResourceIdentifier) =>
Routes.ProjectDetails.sections.tasks.makeUrl(project, domain);
const unspecifiedGenerator = ({ project, domain }: ResourceIdentifier | Identifier) => {
@@ -42,7 +64,7 @@ export const backUrlGenerator: {
[k in ResourceType]: (id: ResourceIdentifier) => string;
} = {
[ResourceType.DATASET]: unimplementedGenerator,
- [ResourceType.LAUNCH_PLAN]: unimplementedGenerator,
+ [ResourceType.LAUNCH_PLAN]: launchPlanListGenerator,
[ResourceType.TASK]: taskListGenerator,
[ResourceType.UNSPECIFIED]: unspecifiedGenerator,
[ResourceType.WORKFLOW]: workflowListGenerator,
@@ -50,6 +72,8 @@ export const backUrlGenerator: {
const workflowDetailGenerator = ({ project, domain, name }: ResourceIdentifier) =>
Routes.WorkflowDetails.makeUrl(project, domain, name);
+const launchPlanDetailGenerator = ({ project, domain, name }: ResourceIdentifier) =>
+ Routes.LaunchPlanDetails.makeUrl(project, domain, name);
const taskDetailGenerator = ({ project, domain, name }: ResourceIdentifier) =>
Routes.TaskDetails.makeUrl(project, domain, name);
@@ -57,7 +81,7 @@ export const backToDetailUrlGenerator: {
[k in ResourceType]: (id: ResourceIdentifier) => string;
} = {
[ResourceType.DATASET]: unimplementedGenerator,
- [ResourceType.LAUNCH_PLAN]: unimplementedGenerator,
+ [ResourceType.LAUNCH_PLAN]: launchPlanDetailGenerator,
[ResourceType.TASK]: taskDetailGenerator,
[ResourceType.UNSPECIFIED]: unspecifiedGenerator,
[ResourceType.WORKFLOW]: workflowDetailGenerator,
@@ -79,12 +103,20 @@ const taskVersionDetailsGenerator = ({ project, domain, name, version }: Identif
entityStrings[ResourceType.TASK],
version,
);
+const launchPlanVersionDetailsGenerator = ({ project, domain, name, version }: Identifier) =>
+ Routes.EntityVersionDetails.makeUrl(
+ project,
+ domain,
+ name,
+ entityStrings[ResourceType.LAUNCH_PLAN],
+ version,
+ );
const entityMapVersionDetailsUrl: {
[k in ResourceType]: (id: Identifier) => string;
} = {
[ResourceType.DATASET]: unimplementedGenerator,
- [ResourceType.LAUNCH_PLAN]: unimplementedGenerator,
+ [ResourceType.LAUNCH_PLAN]: launchPlanVersionDetailsGenerator,
[ResourceType.TASK]: taskVersionDetailsGenerator,
[ResourceType.UNSPECIFIED]: unspecifiedGenerator,
[ResourceType.WORKFLOW]: workflowVersopmDetailsGenerator,
diff --git a/packages/zapp/console/src/components/Entities/strings.ts b/packages/zapp/console/src/components/Entities/strings.ts
index e12b28fd3..fd0bb7840 100644
--- a/packages/zapp/console/src/components/Entities/strings.ts
+++ b/packages/zapp/console/src/components/Entities/strings.ts
@@ -11,9 +11,14 @@ const str = {
noSchedules_workflow: 'This workflow has no schedules.',
noSchedules_task: 'This task has no schedules.',
allExecutionsChartTitle_workflow: 'All Executions in the Workflow',
- allExecutionsChartTitle_task: 'All Execuations in the Task',
+ allExecutionsChartTitle_task: 'All Executions in the Task',
+ allExecutionsChartTitle_launch_plan: 'All Executions Using Launch Plan',
versionsTitle_workflow: 'Recent Workflow Versions',
versionsTitle_task: 'Recent Task Versions',
+ versionsTitle_launch_plan: 'Launch Plan Versions',
+ searchName_launch_plan: 'Search Launch Plan Name',
+ searchName_task: 'Search Task Name',
+ searchName_workflow: 'Search Workflow Name',
details_task: 'Task Details',
inputsFieldName: 'Inputs',
outputsFieldName: 'Outputs',
@@ -25,6 +30,20 @@ const str = {
value: 'Value',
basicInformation: 'Basic Information',
description: 'Description',
+ launchPlanLatest: 'Launch Plan Detail (Latest Version)',
+ expectedInputs: 'Expected Inputs',
+ fixedInputs: 'Fixed Inputs',
+ inputsName: 'Name',
+ inputsType: 'Type',
+ inputsRequired: 'Required',
+ inputsDefault: 'Default Value',
+ configuration: 'Configuration',
+ configType: 'type',
+ configUrl: 'url',
+ configSeed: 'seed',
+ configTestSplitRatio: 'test_split_ratio',
+ noExpectedInputs: 'This launch plan has no expected inputs.',
+ noFixedInputs: 'This launch plan has no fixed inputs.',
};
export { patternKey } from '@flyteconsole/locale';
diff --git a/packages/zapp/console/src/components/Executions/Tables/WorkflowVersionRow.tsx b/packages/zapp/console/src/components/Executions/Tables/WorkflowVersionRow.tsx
index 570ea96ba..8914b1475 100644
--- a/packages/zapp/console/src/components/Executions/Tables/WorkflowVersionRow.tsx
+++ b/packages/zapp/console/src/components/Executions/Tables/WorkflowVersionRow.tsx
@@ -5,6 +5,11 @@ import * as React from 'react';
import { ListRowProps } from 'react-virtualized';
import { Workflow } from 'models/Workflow/types';
import TableRow from '@material-ui/core/TableRow';
+import { useWorkflowExecutions } from 'components/hooks/useWorkflowExecutions';
+import { executionSortFields } from 'models/Execution/constants';
+import { SortDirection } from 'models/AdminEntity/types';
+import { executionFilterGenerator } from 'components/Entities/generators';
+import { ResourceIdentifier } from 'models/Common/types';
import { useWorkflowVersionsColumnStyles } from './styles';
import { WorkflowExecutionsTableState, WorkflowVersionColumnDefinition } from './types';
@@ -51,6 +56,31 @@ export const WorkflowVersionRow: React.FC = ({
const versionTableStyles = useWorkflowVersionsColumnStyles();
const styles = useStyles();
+ const sort = {
+ key: executionSortFields.createdAt,
+ direction: SortDirection.DESCENDING,
+ };
+
+ const baseFilters = React.useMemo(
+ () =>
+ workflow.id.resourceType
+ ? executionFilterGenerator[workflow.id.resourceType](
+ workflow.id as ResourceIdentifier,
+ workflow.id.version,
+ )
+ : [],
+ [workflow.id],
+ );
+
+ const executions = useWorkflowExecutions(
+ { domain: workflow.id.domain, project: workflow.id.project, version: workflow.id.version },
+ {
+ sort,
+ filter: baseFilters,
+ limit: 10,
+ },
+ );
+
return (
{versionView && (
@@ -74,6 +104,7 @@ export const WorkflowVersionRow: React.FC = ({
{cellRenderer({
workflow,
state,
+ executions,
})}
))}
diff --git a/packages/zapp/console/src/components/Executions/Tables/constants.ts b/packages/zapp/console/src/components/Executions/Tables/constants.ts
index 9dbcc1145..8e7238444 100644
--- a/packages/zapp/console/src/components/Executions/Tables/constants.ts
+++ b/packages/zapp/console/src/components/Executions/Tables/constants.ts
@@ -29,4 +29,5 @@ export const workflowVersionsTableColumnWidths = {
release: 150,
lastRun: 175,
createdAt: 260,
+ recentRun: 160,
};
diff --git a/packages/zapp/console/src/components/Executions/Tables/styles.ts b/packages/zapp/console/src/components/Executions/Tables/styles.ts
index 4e5709100..d1469db13 100644
--- a/packages/zapp/console/src/components/Executions/Tables/styles.ts
+++ b/packages/zapp/console/src/components/Executions/Tables/styles.ts
@@ -178,4 +178,10 @@ export const useWorkflowVersionsColumnStyles = makeStyles(() => ({
columnCreatedAt: {
flexBasis: workflowVersionsTableColumnWidths.createdAt,
},
+ columnLastRun: {
+ flexBasis: workflowVersionsTableColumnWidths.lastRun,
+ },
+ columnRecentRun: {
+ flexBasis: workflowVersionsTableColumnWidths.recentRun,
+ },
}));
diff --git a/packages/zapp/console/src/components/Executions/Tables/types.ts b/packages/zapp/console/src/components/Executions/Tables/types.ts
index 59ab75b23..49bf992a0 100644
--- a/packages/zapp/console/src/components/Executions/Tables/types.ts
+++ b/packages/zapp/console/src/components/Executions/Tables/types.ts
@@ -1,3 +1,4 @@
+import { PaginatedFetchableData } from 'components/hooks/types';
import { Execution, NodeExecution, NodeExecutionIdentifier } from 'models/Execution/types';
import { Workflow } from 'models/Workflow/types';
@@ -32,6 +33,7 @@ export type WorkflowExecutionColumnDefinition = ColumnDefinition;
}
export type WorkflowVersionColumnDefinition = ColumnDefinition;
diff --git a/packages/zapp/console/src/components/Executions/Tables/useWorkflowVersionsTableColumns.tsx b/packages/zapp/console/src/components/Executions/Tables/useWorkflowVersionsTableColumns.tsx
index aefdfbbb5..f2a750105 100644
--- a/packages/zapp/console/src/components/Executions/Tables/useWorkflowVersionsTableColumns.tsx
+++ b/packages/zapp/console/src/components/Executions/Tables/useWorkflowVersionsTableColumns.tsx
@@ -1,6 +1,9 @@
import { Typography } from '@material-ui/core';
import { formatDateUTC } from 'common/formatters';
-import { timestampToDate } from 'common/utils';
+import { padExecutionPaths, padExecutions, timestampToDate } from 'common/utils';
+import { WaitForData } from 'components/common/WaitForData';
+import ProjectStatusBar from 'components/Project/ProjectStatusBar';
+import * as moment from 'moment';
import * as React from 'react';
import { useWorkflowVersionsColumnStyles } from './styles';
import { WorkflowVersionColumnDefinition } from './types';
@@ -36,6 +39,39 @@ export function useWorkflowVersionsTableColumns(): WorkflowVersionColumnDefiniti
key: 'createdAt',
label: 'time created',
},
+ {
+ cellRenderer: ({ executions }) => {
+ return (
+
+
+ {executions.value.length
+ ? moment(timestampToDate(executions.value[0].closure.createdAt)).fromNow()
+ : ''}
+
+
+ );
+ },
+ className: styles.columnLastRun,
+ key: 'lastExecution',
+ label: 'last execution',
+ },
+ {
+ cellRenderer: ({ executions }) => {
+ return (
+
+ execution.closure.phase) || [],
+ )}
+ paths={padExecutionPaths(executions.value.map((execution) => execution.id) || [])}
+ />
+
+ );
+ },
+ className: styles.columnRecentRun,
+ key: 'recentRun',
+ label: 'recent run',
+ },
],
[styles],
);
diff --git a/packages/zapp/console/src/components/LaunchPlan/LaunchPlanDetails.tsx b/packages/zapp/console/src/components/LaunchPlan/LaunchPlanDetails.tsx
new file mode 100644
index 000000000..c8f87d2c4
--- /dev/null
+++ b/packages/zapp/console/src/components/LaunchPlan/LaunchPlanDetails.tsx
@@ -0,0 +1,33 @@
+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 LaunchPlanDetailsRouteParams {
+ projectId: string;
+ domainId: string;
+ launchPlanName: string;
+}
+export type LaunchPlanDetailsProps = LaunchPlanDetailsRouteParams;
+
+/** The view component for the LaunchPlan landing page */
+export const LaunchPlanDetailsContainer: React.FC = ({
+ projectId,
+ domainId,
+ launchPlanName,
+}) => {
+ const id = React.useMemo(
+ () => ({
+ resourceType: ResourceType.LAUNCH_PLAN,
+ project: projectId,
+ domain: domainId,
+ name: launchPlanName,
+ }),
+ [projectId, domainId, launchPlanName],
+ );
+ return ;
+};
+
+export const LaunchPlanDetails = withRouteParams(
+ LaunchPlanDetailsContainer,
+);
diff --git a/packages/zapp/console/src/components/LaunchPlan/SearchableLaunchPlanNameList.tsx b/packages/zapp/console/src/components/LaunchPlan/SearchableLaunchPlanNameList.tsx
new file mode 100644
index 000000000..e8b7137a5
--- /dev/null
+++ b/packages/zapp/console/src/components/LaunchPlan/SearchableLaunchPlanNameList.tsx
@@ -0,0 +1,159 @@
+import { makeStyles, Theme } from '@material-ui/core/styles';
+import classNames from 'classnames';
+import { useNamedEntityListStyles } from 'components/common/SearchableNamedEntityList';
+import { useCommonStyles } from 'components/common/styles';
+import { separatorColor, primaryTextColor, launchPlanLabelColor } from 'components/Theme/constants';
+import * as React from 'react';
+import { Link } from 'react-router-dom';
+import { Routes } from 'routes/routes';
+import { debounce } from 'lodash';
+import { Typography, FormGroup } from '@material-ui/core';
+import { ResourceType } from 'models/Common/types';
+import { MuiLaunchPlanIcon } from '@flyteconsole/ui-atoms';
+import { LaunchPlanListStructureItem } from './types';
+import { SearchableInput } from '../common/SearchableList';
+import { useSearchableListState } from '../common/useSearchableListState';
+import t, { patternKey } from '../Entities/strings';
+import { entityStrings } from '../Entities/constants';
+
+interface SearchableLaunchPlanNameItemProps {
+ item: LaunchPlanListStructureItem;
+}
+
+interface SearchableLaunchPlanNameListProps {
+ launchPlans: LaunchPlanListStructureItem[];
+}
+
+export const showOnHoverClass = 'showOnHover';
+
+const useStyles = makeStyles((theme: Theme) => ({
+ container: {
+ padding: theme.spacing(2),
+ paddingRight: theme.spacing(5),
+ },
+ filterGroup: {
+ display: 'flex',
+ flexWrap: 'nowrap',
+ flexDirection: 'row',
+ margin: theme.spacing(4, 5, 0, 2),
+ },
+ itemContainer: {
+ padding: theme.spacing(3, 3),
+ border: 'none',
+ borderTop: `1px solid ${separatorColor}`,
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'flex-start',
+ position: 'relative',
+ // All children using the showOnHover class will be hidden until
+ // the mouse enters the container
+ [`& .${showOnHoverClass}`]: {
+ opacity: 0,
+ },
+ [`&:hover .${showOnHoverClass}`]: {
+ opacity: 1,
+ },
+ },
+ itemName: {
+ display: 'flex',
+ fontWeight: 600,
+ color: primaryTextColor,
+ alignItems: 'center',
+ },
+ itemIcon: {
+ marginRight: theme.spacing(2),
+ color: '#636379',
+ },
+ itemRow: {
+ display: 'flex',
+ marginBottom: theme.spacing(1),
+ '&:last-child': {
+ marginBottom: 0,
+ },
+ alignItems: 'center',
+ width: '100%',
+ },
+ itemLabel: {
+ width: 140,
+ fontSize: 14,
+ color: launchPlanLabelColor,
+ },
+ searchInputContainer: {
+ padding: 0,
+ },
+}));
+
+/**
+ * Renders individual searchable launchPlan item
+ * @param item
+ * @returns
+ */
+const SearchableLaunchPlanNameItem: React.FC = React.memo(
+ ({ item }) => {
+ const commonStyles = useCommonStyles();
+ const listStyles = useNamedEntityListStyles();
+ const styles = useStyles();
+ const { id } = item;
+
+ return (
+
+
+
+ );
+ },
+);
+
+/**
+ * Renders a searchable list of LaunchPlan names, with associated descriptions
+ * @param launchPlans
+ * @constructor
+ */
+export const SearchableLaunchPlanNameList: React.FC = ({
+ launchPlans,
+}) => {
+ const styles = useStyles();
+ const [search, setSearch] = React.useState('');
+ const { results, setSearchString } = useSearchableListState({
+ items: launchPlans,
+ propertyGetter: ({ id }) => id.name,
+ });
+
+ const onSearchChange = (event: React.ChangeEvent) => {
+ const searchString = event.target.value;
+ setSearch(searchString);
+ const debouncedSearch = debounce(() => setSearchString(searchString), 1000);
+ debouncedSearch();
+ };
+ const onClear = () => setSearch('');
+
+ return (
+ <>
+
+
+
+
+ {results.map(({ value }) => (
+
+ ))}
+
+ >
+ );
+};
diff --git a/packages/zapp/console/src/components/LaunchPlan/launchPlanQueries.ts b/packages/zapp/console/src/components/LaunchPlan/launchPlanQueries.ts
new file mode 100644
index 000000000..3f9da6cca
--- /dev/null
+++ b/packages/zapp/console/src/components/LaunchPlan/launchPlanQueries.ts
@@ -0,0 +1,25 @@
+import { QueryInput, QueryType } from 'components/data/types';
+import { getLaunchPlan } from 'models/Launch/api';
+import { LaunchPlan, LaunchPlanId } from 'models/Launch/types';
+import { QueryClient } from 'react-query';
+
+export function makeLaunchPlanQuery(
+ queryClient: QueryClient,
+ id: LaunchPlanId,
+): QueryInput {
+ return {
+ queryKey: [QueryType.LaunchPlan, id],
+ queryFn: async () => {
+ const launchPlan = await getLaunchPlan(id);
+
+ return launchPlan;
+ },
+ // `LaunchPlan` objects (individual versions) are immutable and safe to
+ // cache indefinitely once retrieved in full
+ staleTime: Infinity,
+ };
+}
+
+export async function fetchLaunchPlan(queryClient: QueryClient, id: LaunchPlanId) {
+ return queryClient.fetchQuery(makeLaunchPlanQuery(queryClient, id));
+}
diff --git a/packages/zapp/console/src/components/LaunchPlan/types.ts b/packages/zapp/console/src/components/LaunchPlan/types.ts
new file mode 100644
index 000000000..a18b54f4b
--- /dev/null
+++ b/packages/zapp/console/src/components/LaunchPlan/types.ts
@@ -0,0 +1,8 @@
+import { NamedEntityIdentifier } from 'models/Common/types';
+import { NamedEntityState } from 'models/enums';
+
+export type LaunchPlanListStructureItem = {
+ id: NamedEntityIdentifier;
+ description: string;
+ state: NamedEntityState;
+};
diff --git a/packages/zapp/console/src/components/LaunchPlan/useLaunchPlanInfoList.ts b/packages/zapp/console/src/components/LaunchPlan/useLaunchPlanInfoList.ts
new file mode 100644
index 000000000..66ddb52e7
--- /dev/null
+++ b/packages/zapp/console/src/components/LaunchPlan/useLaunchPlanInfoList.ts
@@ -0,0 +1,28 @@
+import { DomainIdentifierScope, ResourceType } from 'models/Common/types';
+import { RequestConfig } from 'models/AdminEntity/types';
+import { usePagination } from 'components/hooks/usePagination';
+import { useAPIContext } from 'components/data/apiContext';
+import { LaunchPlanListStructureItem } from './types';
+
+export const useLaunchPlanInfoList = (scope: DomainIdentifierScope, config?: RequestConfig) => {
+ const { listNamedEntities } = useAPIContext();
+
+ return usePagination(
+ { ...config, fetchArg: scope },
+ async (scope, requestConfig) => {
+ const { entities, ...rest } = await listNamedEntities(
+ { ...scope, resourceType: ResourceType.LAUNCH_PLAN },
+ requestConfig,
+ );
+
+ return {
+ entities: entities.map(({ id, metadata: { description, state } }) => ({
+ id,
+ description,
+ state,
+ })),
+ ...rest,
+ };
+ },
+ );
+};
diff --git a/packages/zapp/console/src/components/Navigation/ProjectNavigation.tsx b/packages/zapp/console/src/components/Navigation/ProjectNavigation.tsx
index b4b75f645..590d631f1 100644
--- a/packages/zapp/console/src/components/Navigation/ProjectNavigation.tsx
+++ b/packages/zapp/console/src/components/Navigation/ProjectNavigation.tsx
@@ -13,6 +13,7 @@ import * as React from 'react';
import { matchPath, NavLink, NavLinkProps } from 'react-router-dom';
import { history } from 'routes/history';
import { Routes } from 'routes/routes';
+import { MuiLaunchPlanIcon } from '@flyteconsole/ui-atoms';
import { ProjectSelector } from './ProjectSelector';
interface ProjectNavigationRouteParams {
@@ -113,6 +114,20 @@ const ProjectNavigationImpl: React.FC = ({
path: Routes.ProjectDetails.sections.tasks.makeUrl(project.value.id, domainId),
text: 'Tasks',
},
+ {
+ icon: MuiLaunchPlanIcon,
+ isActive: (match, location) => {
+ const finalMatch = match
+ ? match
+ : matchPath(location.pathname, {
+ path: Routes.LaunchPlanDetails.path,
+ exact: false,
+ });
+ return !!finalMatch;
+ },
+ path: Routes.ProjectDetails.sections.launchPlans.makeUrl(project.value.id, domainId),
+ text: 'Launch Plans',
+ },
];
return (
diff --git a/packages/zapp/console/src/components/Project/ProjectDetails.tsx b/packages/zapp/console/src/components/Project/ProjectDetails.tsx
index 96f346856..f7cb7277b 100644
--- a/packages/zapp/console/src/components/Project/ProjectDetails.tsx
+++ b/packages/zapp/console/src/components/Project/ProjectDetails.tsx
@@ -11,6 +11,7 @@ import { Routes } from 'routes/routes';
import { ProjectDashboard } from './ProjectDashboard';
import { ProjectTasks } from './ProjectTasks';
import { ProjectWorkflows } from './ProjectWorkflows';
+import { ProjectLaunchPlans } from './ProjectLaunchPlans';
const useStyles = makeStyles((theme: Theme) => ({
tab: {
@@ -30,11 +31,12 @@ const entityTypeToComponent = {
executions: ProjectDashboard,
tasks: ProjectTasks,
workflows: ProjectWorkflows,
+ launchPlans: ProjectLaunchPlans,
};
const ProjectEntitiesByDomain: React.FC<{
project: Project;
- entityType: 'executions' | 'tasks' | 'workflows';
+ entityType: 'executions' | 'tasks' | 'workflows' | 'launchPlans';
}> = ({ entityType, project }) => {
const styles = useStyles();
const { params, setQueryState } = useQueryState<{ domain: string }>();
@@ -71,6 +73,10 @@ const ProjectTasksByDomain: React.FC<{ project: Project }> = ({ project }) => (
);
+const ProjectLaunchPlansByDomain: React.FC<{ project: Project }> = ({ project }) => (
+
+);
+
/** The view component for the Project landing page */
export const ProjectDetailsContainer: React.FC = ({ projectId }) => {
const project = useProject(projectId);
@@ -88,6 +94,9 @@ export const ProjectDetailsContainer: React.FC = ({ p
+
+
+
);
diff --git a/packages/zapp/console/src/components/Project/ProjectLaunchPlans.tsx b/packages/zapp/console/src/components/Project/ProjectLaunchPlans.tsx
new file mode 100644
index 000000000..7fe696802
--- /dev/null
+++ b/packages/zapp/console/src/components/Project/ProjectLaunchPlans.tsx
@@ -0,0 +1,38 @@
+import { WaitForData } from 'components/common/WaitForData';
+import { SearchableLaunchPlanNameList } from 'components/LaunchPlan/SearchableLaunchPlanNameList';
+import { limits } from 'models/AdminEntity/constants';
+import { SortDirection } from 'models/AdminEntity/types';
+import { launchSortFields } from 'models/Launch/constants';
+import * as React from 'react';
+import { useLaunchPlanInfoList } from '../LaunchPlan/useLaunchPlanInfoList';
+
+export interface ProjectLaunchPlansProps {
+ projectId: string;
+ domainId: string;
+}
+
+const DEFAULT_SORT = {
+ direction: SortDirection.ASCENDING,
+ key: launchSortFields.name,
+};
+
+/** A listing of the LaunchPlans registered for a project */
+export const ProjectLaunchPlans: React.FC = ({
+ domainId: domain,
+ projectId: project,
+}) => {
+ const launchPlans = useLaunchPlanInfoList(
+ { domain, project },
+ {
+ limit: limits.NONE,
+ sort: DEFAULT_SORT,
+ filter: [],
+ },
+ );
+
+ return (
+
+
+
+ );
+};
diff --git a/packages/zapp/console/src/components/Theme/constants.ts b/packages/zapp/console/src/components/Theme/constants.ts
index 1e3703329..39d879978 100644
--- a/packages/zapp/console/src/components/Theme/constants.ts
+++ b/packages/zapp/console/src/components/Theme/constants.ts
@@ -45,6 +45,7 @@ export const mutedButtonHoverColor = COLOR_SPECTRUM.gray60.color;
export const errorBackgroundColor = '#FBFBFC';
export const workflowLabelColor = COLOR_SPECTRUM.gray25.color;
+export const launchPlanLabelColor = COLOR_SPECTRUM.gray25.color;
export const statusColors = {
FAILURE: COLOR_SPECTRUM.red20.color,
diff --git a/packages/zapp/console/src/components/Workflow/SearchableWorkflowNameList.tsx b/packages/zapp/console/src/components/Workflow/SearchableWorkflowNameList.tsx
index 2719a501a..5d4da6a19 100644
--- a/packages/zapp/console/src/components/Workflow/SearchableWorkflowNameList.tsx
+++ b/packages/zapp/console/src/components/Workflow/SearchableWorkflowNameList.tsx
@@ -7,9 +7,7 @@ import { separatorColor, primaryTextColor, workflowLabelColor } from 'components
import * as React from 'react';
import { Link } from 'react-router-dom';
import { Routes } from 'routes/routes';
-import { WorkflowExecutionPhase } from 'models/Execution/enums';
import { Shimmer } from 'components/common/Shimmer';
-import { WorkflowExecutionIdentifier } from 'models/Execution/types';
import { debounce } from 'lodash';
import {
IconButton,
@@ -27,6 +25,7 @@ import { NamedEntityState } from 'models/enums';
import { updateWorkflowState } from 'models/Workflow/api';
import { useState } from 'react';
import { useSnackbar } from 'notistack';
+import { padExecutionPaths, padExecutions } from 'common/utils';
import { WorkflowListStructureItem } from './types';
import ProjectStatusBar from '../Project/ProjectStatusBar';
import { workflowNoInputsString } from '../Launch/LaunchForm/constants';
@@ -142,25 +141,6 @@ const useStyles = makeStyles((theme: Theme) => ({
},
}));
-const padExecutions = (items: WorkflowExecutionPhase[]) => {
- if (items.length >= 10) {
- return items.slice(0, 10).reverse();
- }
- const emptyExecutions = new Array(10 - items.length).fill(WorkflowExecutionPhase.QUEUED);
- return [...items, ...emptyExecutions].reverse();
-};
-
-const padExecutionPaths = (items: WorkflowExecutionIdentifier[]) => {
- if (items.length >= 10) {
- return items
- .slice(0, 10)
- .map((id) => Routes.ExecutionDetails.makeUrl(id))
- .reverse();
- }
- const emptyExecutions = new Array(10 - items.length).fill(null);
- return [...items.map((id) => Routes.ExecutionDetails.makeUrl(id)), ...emptyExecutions].reverse();
-};
-
const getArchiveIcon = (isArchived: boolean) =>
isArchived ? : ;
diff --git a/packages/zapp/console/src/components/data/types.ts b/packages/zapp/console/src/components/data/types.ts
index 10db6648d..7b7fbd02d 100644
--- a/packages/zapp/console/src/components/data/types.ts
+++ b/packages/zapp/console/src/components/data/types.ts
@@ -14,6 +14,7 @@ export enum QueryType {
Workflow = 'workflow',
WorkflowExecution = 'workflowExecution',
WorkflowExecutionList = 'workflowExecutionList',
+ LaunchPlan = 'launchPlan',
}
type QueryKeyArray = [QueryType, ...unknown[]];
diff --git a/packages/zapp/console/src/components/hooks/Entity/useEntityVersions.ts b/packages/zapp/console/src/components/hooks/Entity/useEntityVersions.ts
index df2eb9ee3..ecec1862f 100644
--- a/packages/zapp/console/src/components/hooks/Entity/useEntityVersions.ts
+++ b/packages/zapp/console/src/components/hooks/Entity/useEntityVersions.ts
@@ -1,6 +1,5 @@
-import { IdentifierScope, Identifier, ResourceIdentifier } from 'models/Common/types';
+import { IdentifierScope, ResourceIdentifier } from 'models/Common/types';
import { RequestConfig } from 'models/AdminEntity/types';
-import { entityStrings } from 'components/Entities/constants';
import { usePagination } from '../usePagination';
import { EntityType, entityFunctions } from './constants';
diff --git a/packages/zapp/console/src/models/Launch/constants.ts b/packages/zapp/console/src/models/Launch/constants.ts
index f38ef3581..beec28925 100644
--- a/packages/zapp/console/src/models/Launch/constants.ts
+++ b/packages/zapp/console/src/models/Launch/constants.ts
@@ -1,3 +1,3 @@
export const launchSortFields = {
- createdAt: 'created_at',
+ name: 'name',
};
diff --git a/packages/zapp/console/src/routes/ApplicationRouter.tsx b/packages/zapp/console/src/routes/ApplicationRouter.tsx
index 0336daf18..3b84cb7e1 100644
--- a/packages/zapp/console/src/routes/ApplicationRouter.tsx
+++ b/packages/zapp/console/src/routes/ApplicationRouter.tsx
@@ -30,6 +30,11 @@ export const ApplicationRouter: React.FC = () => (
path={Routes.TaskDetails.path}
component={withSideNavigation(components.taskDetails)}
/>
+
+ makeProjectBoundPath(project, `/launchPlans${domain ? `?domain=${domain}` : ''}`),
+ path: `${projectBasePath}/launchPlans`,
+ },
},
};
@@ -62,6 +67,13 @@ export class Routes {
path: `${projectDomainBasePath}/workflows/:workflowName`,
};
+ // LaunchPlans
+ static LaunchPlanDetails = {
+ makeUrl: (project: string, domain: string, launchPlanName: string) =>
+ makeProjectDomainBoundPath(project, domain, `/launchPlans/${launchPlanName}`),
+ path: `${projectDomainBasePath}/launchPlans/:launchPlanName`,
+ };
+
// Entity Version Details
static EntityVersionDetails = {
makeUrl: (