From 15a0e10f2f11c4a353b2dc7e6116e4a0fbbaf2af Mon Sep 17 00:00:00 2001 From: Randy Schott <1815175+schottra@users.noreply.github.com> Date: Mon, 9 Nov 2020 11:10:54 -0800 Subject: [PATCH 1/5] feat: checkpoint for adding role input --- .../Launch/LaunchForm/CollectionInput.tsx | 11 +- .../Launch/LaunchForm/RoleInput.tsx | 137 ++++++++++++++++++ .../Launch/LaunchForm/SimpleInput.tsx | 19 +-- .../Launch/LaunchForm/StructInput.tsx | 11 +- src/components/Launch/LaunchForm/constants.ts | 29 +++- src/components/Launch/LaunchForm/handlers.ts | 17 +++ .../Launch/LaunchForm/launchMachine.ts | 2 + src/components/Launch/LaunchForm/types.ts | 8 +- .../Launch/LaunchForm/useFormInputsState.ts | 7 +- .../LaunchForm/useLaunchTaskFormState.ts | 5 +- src/models/Execution/api.ts | 3 + 11 files changed, 211 insertions(+), 38 deletions(-) create mode 100644 src/components/Launch/LaunchForm/RoleInput.tsx create mode 100644 src/components/Launch/LaunchForm/handlers.ts diff --git a/src/components/Launch/LaunchForm/CollectionInput.tsx b/src/components/Launch/LaunchForm/CollectionInput.tsx index f2732ea33..5eb66a2d8 100644 --- a/src/components/Launch/LaunchForm/CollectionInput.tsx +++ b/src/components/Launch/LaunchForm/CollectionInput.tsx @@ -1,15 +1,10 @@ import { TextField } from '@material-ui/core'; import * as React from 'react'; -import { InputChangeHandler, InputProps, InputType } from './types'; +import { makeStringChangeHandler } from './handlers'; +import { InputProps, InputType } from './types'; import { UnsupportedInput } from './UnsupportedInput'; import { getLaunchInputId } from './utils'; -function stringChangeHandler(onChange: InputChangeHandler) { - return ({ target: { value } }: React.ChangeEvent) => { - onChange(value); - }; -} - /** Handles rendering of the input component for a Collection of SimpleType values*/ export const CollectionInput: React.FC = props => { const { @@ -49,7 +44,7 @@ export const CollectionInput: React.FC = props => { fullWidth={true} label={label} multiline={true} - onChange={stringChangeHandler(onChange)} + onChange={makeStringChangeHandler(onChange)} rowsMax={8} value={value} variant="outlined" diff --git a/src/components/Launch/LaunchForm/RoleInput.tsx b/src/components/Launch/LaunchForm/RoleInput.tsx new file mode 100644 index 000000000..852b194d0 --- /dev/null +++ b/src/components/Launch/LaunchForm/RoleInput.tsx @@ -0,0 +1,137 @@ +import { + FormControl, + FormControlLabel, + FormLabel, + Radio, + RadioGroup, + TextField, + Typography +} from '@material-ui/core'; +import { NewTargetLink } from 'components/common/NewTargetLink'; +import { useDebouncedValue } from 'components/hooks/useDebouncedValue'; +import { Admin } from 'flyteidl'; +import * as React from 'react'; +import { launchInputDebouncDelay, roleTypes } from './constants'; +import { makeStringChangeHandler } from './handlers'; +import { RoleTypeValue } from './types'; + +const roleHeader = 'Role'; +const roleDescription = 'Enter a role to assume for this execution.'; +const roleDocLinkText = 'See source for more information.'; +const roleDocLinkUrl = + 'https://github.com/lyft/flyteidl/blob/3789005a1372221eba28fa20d8386e44b32388f5/protos/flyteidl/admin/common.proto#L241'; +const roleValueLabel = 'value'; +const roleTypeLabel = 'type'; +const roleInputId = 'launch-auth-role'; + +export interface RoleInputProps { + error?: string; + roleType: string; + roleString?: string; + onChangeRoleString(newValue: string): void; + onChangeRoleType(newValue: string): void; +} + +function getDefaultRoleTypeValue(): RoleTypeValue { + return roleTypes.iamRole.value; +} + +interface RoleInputState extends RoleInputProps { + getValue(): Admin.IAuthRole; + validate(): boolean; +} + +export function useRoleInputState(): RoleInputState { + const [error, setError] = React.useState(); + const [roleString, setRoleString] = React.useState(); + const [roleType, setRoleType] = React.useState( + getDefaultRoleTypeValue + ); + + const validationValue = useDebouncedValue( + roleString, + launchInputDebouncDelay + ); + + const getValue = () => ({ [roleType]: roleString }); + const validate = () => { + if (roleString == null || roleString.length === 0) { + setError('Value is required'); + return false; + } + setError(undefined); + return true; + }; + + React.useEffect(() => { + validate(); + }, [validationValue]); + + return { + error, + getValue, + roleType, + roleString, + validate, + onChangeRoleString: setRoleString, + onChangeRoleType: (value: string) => setRoleType(value as RoleTypeValue) + }; +} + +const RoleDescription = () => ( + <> + + {roleDescription} + + {roleDocLinkText} + + + +); + +export const RoleInput: React.FC = ({ + error, + roleType, + roleString, + onChangeRoleString, + onChangeRoleType +}) => { + const hasError = !!error; + const helperText = hasError ? error : undefined; + return ( +
+
+ {roleHeader} + +
+ + {roleTypeLabel} + + {Object.values(roleTypes).map(({ label, value }) => ( + } + label={label} + /> + ))} + + + +
+ ); +}; diff --git a/src/components/Launch/LaunchForm/SimpleInput.tsx b/src/components/Launch/LaunchForm/SimpleInput.tsx index 058f9fbf5..2248c323e 100644 --- a/src/components/Launch/LaunchForm/SimpleInput.tsx +++ b/src/components/Launch/LaunchForm/SimpleInput.tsx @@ -7,22 +7,11 @@ import { } from '@material-ui/core'; import * as React from 'react'; import { DatetimeInput } from './DatetimeInput'; -import { InputChangeHandler, InputProps, InputType } from './types'; +import { makeStringChangeHandler, makeSwitchChangeHandler } from './handlers'; +import { InputProps, InputType } from './types'; import { UnsupportedInput } from './UnsupportedInput'; import { getLaunchInputId } from './utils'; -function switchChangeHandler(onChange: InputChangeHandler) { - return ({ target: { checked } }: React.ChangeEvent) => { - onChange(checked); - }; -} - -function stringChangeHandler(onChange: InputChangeHandler) { - return ({ target: { value } }: React.ChangeEvent) => { - onChange(value); - }; -} - /** Handles rendering of the input component for any primitive-type input */ export const SimpleInput: React.FC = props => { const { @@ -44,7 +33,7 @@ export const SimpleInput: React.FC = props => { } @@ -67,7 +56,7 @@ export const SimpleInput: React.FC = props => { helperText={helperText} fullWidth={true} label={label} - onChange={stringChangeHandler(onChange)} + onChange={makeStringChangeHandler(onChange)} value={value} variant="outlined" /> diff --git a/src/components/Launch/LaunchForm/StructInput.tsx b/src/components/Launch/LaunchForm/StructInput.tsx index 0f8327554..7ec8ffe49 100644 --- a/src/components/Launch/LaunchForm/StructInput.tsx +++ b/src/components/Launch/LaunchForm/StructInput.tsx @@ -1,14 +1,9 @@ import { TextField } from '@material-ui/core'; import * as React from 'react'; -import { InputChangeHandler, InputProps } from './types'; +import { makeStringChangeHandler } from './handlers'; +import { InputProps } from './types'; import { getLaunchInputId } from './utils'; -function stringChangeHandler(onChange: InputChangeHandler) { - return ({ target: { value } }: React.ChangeEvent) => { - onChange(value); - }; -} - /** Handles rendering of the input component for a Struct */ export const StructInput: React.FC = props => { const { @@ -29,7 +24,7 @@ export const StructInput: React.FC = props => { fullWidth={true} label={label} multiline={true} - onChange={stringChangeHandler(onChange)} + onChange={makeStringChangeHandler(onChange)} rowsMax={8} value={value} variant="outlined" diff --git a/src/components/Launch/LaunchForm/constants.ts b/src/components/Launch/LaunchForm/constants.ts index 398a79761..4822716c2 100644 --- a/src/components/Launch/LaunchForm/constants.ts +++ b/src/components/Launch/LaunchForm/constants.ts @@ -1,5 +1,5 @@ import { BlobDimensionality, SimpleType } from 'models'; -import { BlobValue, InputType } from './types'; +import { BlobValue, InputType, ParsedInput, RoleType } from './types'; export const launchPlansTableRowHeight = 40; export const launchPlansTableColumnWidths = { @@ -16,6 +16,7 @@ export const schedulesTableColumnsWidths = { export const formStrings = { cancel: 'Cancel', inputs: 'Inputs', + role: 'Role', submit: 'Launch', taskVersion: 'Task Version', title: 'Create New Execution', @@ -23,6 +24,30 @@ export const formStrings = { launchPlan: 'Launch Plan' }; +export const roleInputDefinition = { + description: + 'Role to assume for this execution (ex. IAM role or Kubernetes Service Account)', + label: 'role', + name: '__authRole', + required: true, + typeDefinition: { + type: InputType.String, + literalType: { simple: SimpleType.STRING } + } +}; + +type RoleTypesKey = 'iamRole' | 'k8sServiceAccount'; +export const roleTypes: { [k in RoleTypesKey]: RoleType } = { + iamRole: { + label: 'IAM Role', + value: 'assumableIamRole' + }, + k8sServiceAccount: { + label: 'Kubernetes Service Account', + value: 'kubernetesServiceAccount' + } +}; + /** Maps any valid InputType enum to a display string */ export const typeLabels: { [k in InputType]: string } = { [InputType.Binary]: 'binary', @@ -61,6 +86,8 @@ export const defaultBlobValue: BlobValue = { dimensionality: BlobDimensionality.SINGLE }; +export const launchInputDebouncDelay = 500; + export const requiredInputSuffix = '*'; export const cannotLaunchWorkflowString = 'Workflow cannot be launched'; export const cannotLaunchTaskString = 'Task cannot be launched'; diff --git a/src/components/Launch/LaunchForm/handlers.ts b/src/components/Launch/LaunchForm/handlers.ts new file mode 100644 index 000000000..e6dbf2d43 --- /dev/null +++ b/src/components/Launch/LaunchForm/handlers.ts @@ -0,0 +1,17 @@ +import * as React from 'react'; +import { InputChangeHandler } from './types'; + +export function makeSwitchChangeHandler(onChange: InputChangeHandler) { + return ({ target: { checked } }: React.ChangeEvent) => { + onChange(checked); + }; +} + +type StringChangeHandler = (value: string) => void; +export function makeStringChangeHandler( + onChange: InputChangeHandler | StringChangeHandler +) { + return ({ target: { value } }: React.ChangeEvent) => { + onChange(value); + }; +} diff --git a/src/components/Launch/LaunchForm/launchMachine.ts b/src/components/Launch/LaunchForm/launchMachine.ts index 23557c56b..d7bc60047 100644 --- a/src/components/Launch/LaunchForm/launchMachine.ts +++ b/src/components/Launch/LaunchForm/launchMachine.ts @@ -1,3 +1,4 @@ +import { Admin } from 'flyteidl'; import { Identifier, LaunchPlan, @@ -83,6 +84,7 @@ export interface WorkflowLaunchContext extends BaseLaunchContext { } export interface TaskLaunchContext extends BaseLaunchContext { + authRole?: Admin.IAuthRole; preferredTaskId?: Identifier; taskVersion?: Identifier; taskVersionOptions?: Task[]; diff --git a/src/components/Launch/LaunchForm/types.ts b/src/components/Launch/LaunchForm/types.ts index 35f8f2f23..0a15578c3 100644 --- a/src/components/Launch/LaunchForm/types.ts +++ b/src/components/Launch/LaunchForm/types.ts @@ -1,4 +1,4 @@ -import { Core } from 'flyteidl'; +import { Admin, Core } from 'flyteidl'; import { BlobDimensionality, Identifier, @@ -192,3 +192,9 @@ export interface ParsedInput /** Provides an initial value for the input, which can be changed by the user. */ initialValue?: Core.ILiteral; } + +export type RoleTypeValue = keyof Admin.IAuthRole; +export interface RoleType { + label: string; + value: RoleTypeValue; +} diff --git a/src/components/Launch/LaunchForm/useFormInputsState.ts b/src/components/Launch/LaunchForm/useFormInputsState.ts index 414eb7200..79689e682 100644 --- a/src/components/Launch/LaunchForm/useFormInputsState.ts +++ b/src/components/Launch/LaunchForm/useFormInputsState.ts @@ -1,6 +1,7 @@ import { useDebouncedValue } from 'components/hooks/useDebouncedValue'; import { Core } from 'flyteidl'; -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useState } from 'react'; +import { launchInputDebouncDelay } from './constants'; import { defaultValueForInputType, literalToInputValue, @@ -10,8 +11,6 @@ import { useInputValueCacheContext } from './inputValueCache'; import { InputProps, InputValue, ParsedInput } from './types'; import { convertFormInputsToLiterals, createInputCacheKey } from './utils'; -const debounceDelay = 500; - interface FormInputState extends InputProps { validate(): boolean; } @@ -42,7 +41,7 @@ function useFormInputState(parsedInput: ParsedInput): FormInputState { }); const [error, setError] = useState(); - const validationValue = useDebouncedValue(value, debounceDelay); + const validationValue = useDebouncedValue(value, launchInputDebouncDelay); const validate = () => { try { diff --git a/src/components/Launch/LaunchForm/useLaunchTaskFormState.ts b/src/components/Launch/LaunchForm/useLaunchTaskFormState.ts index 2338dd6b6..918a7ec5b 100644 --- a/src/components/Launch/LaunchForm/useLaunchTaskFormState.ts +++ b/src/components/Launch/LaunchForm/useLaunchTaskFormState.ts @@ -96,11 +96,14 @@ async function loadInputs( async function submit( { createWorkflowExecution }: APIContextValue, formInputsRef: RefObject, - { referenceExecutionId, taskVersion }: TaskLaunchContext + { authRole, referenceExecutionId, taskVersion }: TaskLaunchContext ) { if (!taskVersion) { throw new Error('Attempting to launch with no Task version'); } + if (!authRole) { + throw new Error('Attempting to launch without specifying an authRole'); + } if (formInputsRef.current === null) { throw new Error('Unexpected empty form inputs ref'); } diff --git a/src/models/Execution/api.ts b/src/models/Execution/api.ts index 0786ae03e..e8dda112b 100644 --- a/src/models/Execution/api.ts +++ b/src/models/Execution/api.ts @@ -85,6 +85,7 @@ export const getExecutionData = ( ); export interface CreateWorkflowExecutionArguments { + authRole?: Admin.IAuthRole; domain: string; inputs: Core.ILiteralMap; launchPlanId: Identifier; @@ -96,6 +97,7 @@ export interface CreateWorkflowExecutionArguments { */ export const createWorkflowExecution = ( { + authRole, domain, inputs, launchPlanId: launchPlan, @@ -113,6 +115,7 @@ export const createWorkflowExecution = ( project, domain, spec: { + authRole, inputs, launchPlan, metadata: { From cb322616adf21496b5c4a36d521f0582717f109c Mon Sep 17 00:00:00 2001 From: Randy Schott <1815175+schottra@users.noreply.github.com> Date: Fri, 13 Nov 2020 11:12:08 -0800 Subject: [PATCH 2/5] feat: adds role input for single task execution --- .../Launch/LaunchForm/LaunchFormInputs.tsx | 55 +++++--- .../{RoleInput.tsx => LaunchRoleInput.tsx} | 118 +++++++++++------- .../Launch/LaunchForm/LaunchTaskForm.tsx | 9 ++ .../Launch/LaunchForm/NoInputsNeeded.tsx | 34 +++++ src/components/Launch/LaunchForm/constants.ts | 9 ++ .../Launch/LaunchForm/launchMachine.ts | 1 - src/components/Launch/LaunchForm/services.ts | 5 +- src/components/Launch/LaunchForm/styles.ts | 4 + .../LaunchForm/test/LaunchTaskForm.test.tsx | 16 +++ src/components/Launch/LaunchForm/types.ts | 8 ++ .../LaunchForm/useLaunchTaskFormState.ts | 50 ++++++-- .../LaunchForm/useLaunchWorkflowFormState.ts | 3 + src/components/Launch/LaunchForm/utils.ts | 16 +++ 13 files changed, 251 insertions(+), 77 deletions(-) rename src/components/Launch/LaunchForm/{RoleInput.tsx => LaunchRoleInput.tsx} (50%) create mode 100644 src/components/Launch/LaunchForm/NoInputsNeeded.tsx diff --git a/src/components/Launch/LaunchForm/LaunchFormInputs.tsx b/src/components/Launch/LaunchForm/LaunchFormInputs.tsx index ed9ee9636..bc216b3b3 100644 --- a/src/components/Launch/LaunchForm/LaunchFormInputs.tsx +++ b/src/components/Launch/LaunchForm/LaunchFormInputs.tsx @@ -1,8 +1,10 @@ +import { Typography } from '@material-ui/core'; import * as React from 'react'; import { BlobInput } from './BlobInput'; import { CollectionInput } from './CollectionInput'; import { formStrings } from './constants'; import { LaunchState } from './launchMachine'; +import { NoInputsNeeded } from './NoInputsNeeded'; import { SimpleInput } from './SimpleInput'; import { StructInput } from './StructInput'; import { useStyles } from './styles'; @@ -15,6 +17,11 @@ import { import { UnsupportedInput } from './UnsupportedInput'; import { UnsupportedRequiredInputsError } from './UnsupportedRequiredInputsError'; import { useFormInputsState } from './useFormInputsState'; +import { isEnterInputsState } from './utils'; + +const inputsHeader = 'Inputs'; +const inputsDescription = + 'Enter input values below. Items marked with an asterisk(*) are required.'; function getComponentForInput(input: InputProps, showErrors: boolean) { const props = { ...input, error: showErrors ? input.error : undefined }; @@ -39,6 +46,29 @@ export interface LaunchFormInputsProps { variant: 'workflow' | 'task'; } +const RenderFormInputs: React.FC<{ + inputs: InputProps[]; + showErrors: boolean; + variant: LaunchFormInputsProps['variant']; +}> = ({ inputs, showErrors, variant }) => { + const styles = useStyles(); + return inputs.length === 0 ? ( + + ) : ( + <> +
+ {inputsHeader} + {inputsDescription} +
+ {inputs.map(input => ( +
+ {getComponentForInput(input, showErrors)} +
+ ))} + + ); +}; + export const LaunchFormInputsImpl: React.RefForwardingComponent< LaunchFormInputsRef, LaunchFormInputsProps @@ -55,18 +85,7 @@ export const LaunchFormInputsImpl: React.RefForwardingComponent< validate })); - const showInputs = [ - LaunchState.UNSUPPORTED_INPUTS, - LaunchState.ENTER_INPUTS, - LaunchState.VALIDATING_INPUTS, - LaunchState.INVALID_INPUTS, - LaunchState.SUBMIT_VALIDATING, - LaunchState.SUBMITTING, - LaunchState.SUBMIT_FAILED, - LaunchState.SUBMIT_SUCCEEDED - ].some(state.matches); - - return showInputs ? ( + return isEnterInputsState(state) ? (
{state.matches(LaunchState.UNSUPPORTED_INPUTS) ? ( ) : ( - <> - {inputs.map(input => ( -
- {getComponentForInput(input, showErrors)} -
- ))} - + )}
) : null; diff --git a/src/components/Launch/LaunchForm/RoleInput.tsx b/src/components/Launch/LaunchForm/LaunchRoleInput.tsx similarity index 50% rename from src/components/Launch/LaunchForm/RoleInput.tsx rename to src/components/Launch/LaunchForm/LaunchRoleInput.tsx index 852b194d0..b7fc8f4cd 100644 --- a/src/components/Launch/LaunchForm/RoleInput.tsx +++ b/src/components/Launch/LaunchForm/LaunchRoleInput.tsx @@ -13,38 +13,39 @@ import { Admin } from 'flyteidl'; import * as React from 'react'; import { launchInputDebouncDelay, roleTypes } from './constants'; import { makeStringChangeHandler } from './handlers'; -import { RoleTypeValue } from './types'; +import { useStyles } from './styles'; +import { LaunchRoleInputRef, RoleType, RoleTypeValue } from './types'; const roleHeader = 'Role'; -const roleDescription = 'Enter a role to assume for this execution.'; -const roleDocLinkText = 'See source for more information.'; +const iamHelperText = 'Enter a string to use as the role value'; const roleDocLinkUrl = 'https://github.com/lyft/flyteidl/blob/3789005a1372221eba28fa20d8386e44b32388f5/protos/flyteidl/admin/common.proto#L241'; -const roleValueLabel = 'value'; +const roleValueLabel = 'role value'; const roleTypeLabel = 'type'; const roleInputId = 'launch-auth-role'; -export interface RoleInputProps { - error?: string; - roleType: string; - roleString?: string; - onChangeRoleString(newValue: string): void; - onChangeRoleType(newValue: string): void; +function getDefaultRoleTypeValue(): RoleType { + return roleTypes.iamRole; } -function getDefaultRoleTypeValue(): RoleTypeValue { - return roleTypes.iamRole.value; +export interface LaunchRoleInputProps { + showErrors: boolean; } -interface RoleInputState extends RoleInputProps { +interface LaunchRoleInputState { + error?: string; + roleType: RoleType; + roleString?: string; getValue(): Admin.IAuthRole; + onChangeRoleString(newValue: string): void; + onChangeRoleType(newValue: string): void; validate(): boolean; } -export function useRoleInputState(): RoleInputState { +export function useRoleInputState(): LaunchRoleInputState { const [error, setError] = React.useState(); const [roleString, setRoleString] = React.useState(); - const [roleType, setRoleType] = React.useState( + const [roleType, setRoleType] = React.useState( getDefaultRoleTypeValue ); @@ -53,7 +54,7 @@ export function useRoleInputState(): RoleInputState { launchInputDebouncDelay ); - const getValue = () => ({ [roleType]: roleString }); + const getValue = () => ({ [roleType.value]: roleString }); const validate = () => { if (roleString == null || roleString.length === 0) { setError('Value is required'); @@ -63,6 +64,16 @@ export function useRoleInputState(): RoleInputState { return true; }; + const onChangeRoleType = (value: string) => { + const newRoleType = Object.values(roleTypes).find( + ({ value: roleTypeValue }) => value === roleTypeValue + ); + if (newRoleType === undefined) { + throw new Error(`Unexpected role type value: ${value}`); + } + setRoleType(newRoleType); + }; + React.useEffect(() => { validate(); }, [validationValue]); @@ -70,37 +81,54 @@ export function useRoleInputState(): RoleInputState { return { error, getValue, + onChangeRoleType, roleType, roleString, validate, - onChangeRoleString: setRoleString, - onChangeRoleType: (value: string) => setRoleType(value as RoleTypeValue) + onChangeRoleString: setRoleString }; } const RoleDescription = () => ( <> - {roleDescription} - - {roleDocLinkText} + Enter a + +  role  + to assume for this execution. ); -export const RoleInput: React.FC = ({ - error, - roleType, - roleString, - onChangeRoleString, - onChangeRoleType -}) => { - const hasError = !!error; - const helperText = hasError ? error : undefined; +export const LaunchRoleInputImpl: React.RefForwardingComponent< + LaunchRoleInputRef, + LaunchRoleInputProps +> = ({ showErrors }, ref) => { + const styles = useStyles(); + const { + error, + getValue, + roleType, + roleString, + onChangeRoleString, + onChangeRoleType, + validate + } = useRoleInputState(); + const hasError = showErrors && !!error; + const helperText = hasError ? error : roleType.helperText; + + React.useImperativeHandle(ref, () => ({ + getValue, + validate + })); + + // TODO: Select appropriate helper text/label constants based on the selected + // role type and use them in the text field. + return (
-
+
{roleHeader}
@@ -109,7 +137,8 @@ export const RoleInput: React.FC = ({ {Object.values(roleTypes).map(({ label, value }) => ( @@ -122,16 +151,21 @@ export const RoleInput: React.FC = ({ ))} - +
+ +
); }; + +/** Renders controls for selecting an AuthRole type and inputting a value for it. */ +export const LaunchRoleInput = React.forwardRef(LaunchRoleInputImpl); diff --git a/src/components/Launch/LaunchForm/LaunchTaskForm.tsx b/src/components/Launch/LaunchForm/LaunchTaskForm.tsx index 12f52dd7d..aae888032 100644 --- a/src/components/Launch/LaunchForm/LaunchTaskForm.tsx +++ b/src/components/Launch/LaunchForm/LaunchTaskForm.tsx @@ -6,6 +6,7 @@ import { LaunchFormActions } from './LaunchFormActions'; import { LaunchFormHeader } from './LaunchFormHeader'; import { LaunchFormInputs } from './LaunchFormInputs'; import { LaunchState } from './launchMachine'; +import { LaunchRoleInput } from './LaunchRoleInput'; import { SearchableSelector } from './SearchableSelector'; import { useStyles } from './styles'; import { @@ -14,11 +15,13 @@ import { LaunchTaskFormProps } from './types'; import { useLaunchTaskFormState } from './useLaunchTaskFormState'; +import { isEnterInputsState } from './utils'; /** Renders the form for initiating a Launch request based on a Task */ export const LaunchTaskForm: React.FC = props => { const { formInputsRef, + roleInputRef, state, service, taskSourceSelectorState @@ -66,6 +69,12 @@ export const LaunchTaskForm: React.FC = props => { /> ) : null} + {isEnterInputsState(baseState) ? ( + + ) : null} ({ + root: { + marginBottom: theme.spacing(1), + marginTop: theme.spacing(1) + } +})); + +export interface NoInputsProps { + variant: 'workflow' | 'task'; +} +/** An informational message to be shown if a Workflow or Task does not need any + * input values. + */ +export const NoInputsNeeded: React.FC = ({ variant }) => { + const commonStyles = useCommonStyles(); + return ( + + {variant === 'workflow' + ? workflowNoInputsString + : taskNoInputsString} + + ); +}; diff --git a/src/components/Launch/LaunchForm/constants.ts b/src/components/Launch/LaunchForm/constants.ts index 4822716c2..c4de51b62 100644 --- a/src/components/Launch/LaunchForm/constants.ts +++ b/src/components/Launch/LaunchForm/constants.ts @@ -39,10 +39,14 @@ export const roleInputDefinition = { type RoleTypesKey = 'iamRole' | 'k8sServiceAccount'; export const roleTypes: { [k in RoleTypesKey]: RoleType } = { iamRole: { + helperText: 'example: arn:aws:iam::12345678:role/defaultrole', + inputLabel: 'role urn', label: 'IAM Role', value: 'assumableIamRole' }, k8sServiceAccount: { + helperText: 'example: default-service-account', + inputLabel: 'service account name', label: 'Kubernetes Service Account', value: 'kubernetesServiceAccount' } @@ -91,7 +95,12 @@ export const launchInputDebouncDelay = 500; export const requiredInputSuffix = '*'; export const cannotLaunchWorkflowString = 'Workflow cannot be launched'; export const cannotLaunchTaskString = 'Task cannot be launched'; +export const workflowNoInputsString = + 'This workflow does not accept any inputs.'; +export const taskNoInputsString = 'This task does not accept any inputs.'; export const workflowUnsupportedRequiredInputsString = `This Workflow version contains one or more required inputs which are not supported by Flyte Console and do not have default values specified in the Workflow definition or the selected Launch Plan.\n\nYou can launch this Workflow version with the Flyte CLI or by selecting a Launch Plan which provides values for the unsupported inputs.\n\nThe required inputs are :`; export const taskUnsupportedRequiredInputsString = `This Task version contains one or more required inputs which are not supported by Flyte Console.\n\nYou can launch this Task version with the Flyte CLI instead.\n\nThe required inputs are :`; export const blobUriHelperText = '(required) location of the data'; export const blobFormatHelperText = '(optional) csv, parquet, etc...'; +export const correctInputErrors = + 'Some inputs have errors. Please correct them before submitting.'; diff --git a/src/components/Launch/LaunchForm/launchMachine.ts b/src/components/Launch/LaunchForm/launchMachine.ts index d7bc60047..cf84a3555 100644 --- a/src/components/Launch/LaunchForm/launchMachine.ts +++ b/src/components/Launch/LaunchForm/launchMachine.ts @@ -84,7 +84,6 @@ export interface WorkflowLaunchContext extends BaseLaunchContext { } export interface TaskLaunchContext extends BaseLaunchContext { - authRole?: Admin.IAuthRole; preferredTaskId?: Identifier; taskVersion?: Identifier; taskVersionOptions?: Task[]; diff --git a/src/components/Launch/LaunchForm/services.ts b/src/components/Launch/LaunchForm/services.ts index 248c80710..7f9d867ea 100644 --- a/src/components/Launch/LaunchForm/services.ts +++ b/src/components/Launch/LaunchForm/services.ts @@ -1,4 +1,5 @@ import { RefObject } from 'react'; +import { correctInputErrors } from './constants'; import { WorkflowLaunchContext } from './launchMachine'; import { LaunchFormInputsRef } from './types'; @@ -11,8 +12,6 @@ export async function validate( } if (!formInputsRef.current.validate()) { - throw new Error( - 'Some inputs have errors. Please correct them before submitting.' - ); + throw new Error(correctInputErrors); } } diff --git a/src/components/Launch/LaunchForm/styles.ts b/src/components/Launch/LaunchForm/styles.ts index 50780d960..b0d566d92 100644 --- a/src/components/Launch/LaunchForm/styles.ts +++ b/src/components/Launch/LaunchForm/styles.ts @@ -24,5 +24,9 @@ export const useStyles = makeStyles((theme: Theme) => ({ display: 'flex', flexDirection: 'column', width: '100%' + }, + sectionHeader: { + marginBottom: theme.spacing(1), + marginTop: theme.spacing(1) } })); diff --git a/src/components/Launch/LaunchForm/test/LaunchTaskForm.test.tsx b/src/components/Launch/LaunchForm/test/LaunchTaskForm.test.tsx index aec7362f9..30a9b27d0 100644 --- a/src/components/Launch/LaunchForm/test/LaunchTaskForm.test.tsx +++ b/src/components/Launch/LaunchForm/test/LaunchTaskForm.test.tsx @@ -357,6 +357,20 @@ describe('LaunchForm: Task', () => { ); }); + it('should render info message when no inputs are needed', async () => {}); + + describe('Auth Role', () => { + it('should require a value', async () => {}); + + it('should show correct help text and label based on role type selection', async () => {}); + + it('should correctly construct an IAM role', async () => {}); + + it('should correctly construct a k8s service account role', async () => {}); + + it('should not submit with input errors and a valid value for role', async () => {}); + }); + describe('Input Values', () => { it('Should send false for untouched toggles', async () => { let inputs: Core.ILiteralMap = {}; @@ -547,6 +561,8 @@ describe('LaunchForm: Task', () => { ) ); }); + + it('should use provided authRole parameter', async () => {}); }); describe('With Unsupported Required Inputs', () => { diff --git a/src/components/Launch/LaunchForm/types.ts b/src/components/Launch/LaunchForm/types.ts index 0a15578c3..2be0de4f8 100644 --- a/src/components/Launch/LaunchForm/types.ts +++ b/src/components/Launch/LaunchForm/types.ts @@ -81,6 +81,10 @@ export interface LaunchFormInputsRef { getValues(): Record; validate(): boolean; } +export interface LaunchRoleInputRef { + getValue(): Admin.IAuthRole; + validate(): boolean; +} export interface WorkflowSourceSelectorState { launchPlanSelectorOptions: SearchableSelectorOption[]; @@ -107,6 +111,7 @@ export interface TaskSourceSelectorState { export interface LaunchWorkflowFormState { formInputsRef: React.RefObject; + onSubmit(): void; state: State< WorkflowLaunchContext, WorkflowLaunchEvent, @@ -124,6 +129,7 @@ export interface LaunchWorkflowFormState { export interface LaunchTaskFormState { formInputsRef: React.RefObject; + roleInputRef: React.RefObject; state: State; service: Interpreter< TaskLaunchContext, @@ -195,6 +201,8 @@ export interface ParsedInput export type RoleTypeValue = keyof Admin.IAuthRole; export interface RoleType { + helperText: string; + inputLabel: string; label: string; value: RoleTypeValue; } diff --git a/src/components/Launch/LaunchForm/useLaunchTaskFormState.ts b/src/components/Launch/LaunchForm/useLaunchTaskFormState.ts index 918a7ec5b..98c1222db 100644 --- a/src/components/Launch/LaunchForm/useLaunchTaskFormState.ts +++ b/src/components/Launch/LaunchForm/useLaunchTaskFormState.ts @@ -11,17 +11,21 @@ import { WorkflowExecutionIdentifier } from 'models'; import { RefObject, useEffect, useMemo, useRef } from 'react'; +import { correctInputErrors } from './constants'; import { getInputsForTask } from './getInputs'; import { + BaseLaunchContext, LaunchState, TaskLaunchContext, TaskLaunchEvent, taskLaunchMachine, TaskLaunchTypestate } from './launchMachine'; -import { validate } from './services'; +import { useRoleInputState } from './LaunchRoleInput'; +import { validate as baseValidate } from './services'; import { LaunchFormInputsRef, + LaunchRoleInputRef, LaunchTaskFormProps, LaunchTaskFormState, ParsedInput @@ -93,25 +97,44 @@ async function loadInputs( }; } +async function validate( + formInputsRef: RefObject, + roleInputRef: RefObject, + context: any +) { + if (roleInputRef.current === null) { + throw new Error('Unexpected empty role input ref'); + } + + if (!roleInputRef.current.validate()) { + throw new Error(correctInputErrors); + } + baseValidate(formInputsRef, context); +} + async function submit( { createWorkflowExecution }: APIContextValue, formInputsRef: RefObject, - { authRole, referenceExecutionId, taskVersion }: TaskLaunchContext + roleInputRef: RefObject, + { referenceExecutionId, taskVersion }: TaskLaunchContext ) { if (!taskVersion) { throw new Error('Attempting to launch with no Task version'); } - if (!authRole) { - throw new Error('Attempting to launch without specifying an authRole'); - } if (formInputsRef.current === null) { throw new Error('Unexpected empty form inputs ref'); } + if (roleInputRef.current === null) { + throw new Error('Unexpected empty role input ref'); + } + + const authRole = roleInputRef.current.getValue(); const literals = formInputsRef.current.getValues(); const launchPlanId = taskVersion; const { domain, project } = taskVersion; const response = await createWorkflowExecution({ + authRole, domain, launchPlanId, project, @@ -128,13 +151,14 @@ async function submit( function getServices( apiContext: APIContextValue, - formInputsRef: RefObject + formInputsRef: RefObject, + roleInputRef: RefObject ) { return { loadTaskVersions: partial(loadTaskVersions, apiContext), loadInputs: partial(loadInputs, apiContext), - submit: partial(submit, apiContext, formInputsRef), - validate: partial(validate, formInputsRef) + submit: partial(submit, apiContext, formInputsRef, roleInputRef), + validate: partial(validate, formInputsRef, roleInputRef) }; } @@ -155,11 +179,12 @@ export function useLaunchTaskFormState({ const apiContext = useAPIContext(); const formInputsRef = useRef(null); + const roleInputRef = useRef(null); - const services = useMemo(() => getServices(apiContext, formInputsRef), [ - apiContext, - formInputsRef - ]); + const services = useMemo( + () => getServices(apiContext, formInputsRef, roleInputRef), + [apiContext, formInputsRef, roleInputRef] + ); const [state, sendEvent, service] = useMachine< TaskLaunchContext, @@ -225,6 +250,7 @@ export function useLaunchTaskFormState({ return { formInputsRef, + roleInputRef, state, service, taskSourceSelectorState diff --git a/src/components/Launch/LaunchForm/useLaunchWorkflowFormState.ts b/src/components/Launch/LaunchForm/useLaunchWorkflowFormState.ts index 4c58246f7..a849c8ae1 100644 --- a/src/components/Launch/LaunchForm/useLaunchWorkflowFormState.ts +++ b/src/components/Launch/LaunchForm/useLaunchWorkflowFormState.ts @@ -269,6 +269,8 @@ export function useLaunchWorkflowFormState({ }); }; + const onSubmit = () => sendEvent({ type: 'SUBMIT' }); + const workflowSourceSelectorState = useWorkflowSourceSelectorState({ launchPlan, launchPlanOptions, @@ -356,6 +358,7 @@ export function useLaunchWorkflowFormState({ return { formInputsRef, + onSubmit, state, service, workflowSourceSelectorState diff --git a/src/components/Launch/LaunchForm/utils.ts b/src/components/Launch/LaunchForm/utils.ts index 6f24fd675..3761402e8 100644 --- a/src/components/Launch/LaunchForm/utils.ts +++ b/src/components/Launch/LaunchForm/utils.ts @@ -13,8 +13,10 @@ import * as moment from 'moment'; import { simpleTypeToInputType, typeLabels } from './constants'; import { inputToLiteral } from './inputHelpers/inputHelpers'; import { typeIsSupported } from './inputHelpers/utils'; +import { LaunchState } from './launchMachine'; import { SearchableSelectorOption } from './SearchableSelector'; import { + BaseInterpretedLaunchState, BlobValue, InputProps, InputType, @@ -195,3 +197,17 @@ export function getUnsupportedRequiredInputs( export function isBlobValue(value: unknown): value is BlobValue { return isObject(value); } + +/** Determines if a given launch machine state is one in which a user can provide input values. */ +export function isEnterInputsState(state: BaseInterpretedLaunchState): boolean { + return [ + LaunchState.UNSUPPORTED_INPUTS, + LaunchState.ENTER_INPUTS, + LaunchState.VALIDATING_INPUTS, + LaunchState.INVALID_INPUTS, + LaunchState.SUBMIT_VALIDATING, + LaunchState.SUBMITTING, + LaunchState.SUBMIT_FAILED, + LaunchState.SUBMIT_SUCCEEDED + ].some(state.matches); +} From 9a6034bbe658a454dd6e3ccb34fbcc3e58fe8a03 Mon Sep 17 00:00:00 2001 From: Randy Schott <1815175+schottra@users.noreply.github.com> Date: Fri, 13 Nov 2020 13:06:22 -0800 Subject: [PATCH 3/5] test: add tests for role inputs and no inputs case --- .../Launch/LaunchForm/LaunchFormInputs.tsx | 8 +- .../Launch/LaunchForm/LaunchRoleInput.tsx | 52 +++-- src/components/Launch/LaunchForm/constants.ts | 2 + .../LaunchForm/test/LaunchTaskForm.test.tsx | 177 ++++++++++++++++-- .../test/LaunchWorkflowForm.test.tsx | 32 +++- .../Launch/LaunchForm/test/constants.ts | 5 + .../LaunchForm/useLaunchTaskFormState.ts | 2 +- 7 files changed, 244 insertions(+), 34 deletions(-) diff --git a/src/components/Launch/LaunchForm/LaunchFormInputs.tsx b/src/components/Launch/LaunchForm/LaunchFormInputs.tsx index bc216b3b3..88b3d4345 100644 --- a/src/components/Launch/LaunchForm/LaunchFormInputs.tsx +++ b/src/components/Launch/LaunchForm/LaunchFormInputs.tsx @@ -2,7 +2,7 @@ import { Typography } from '@material-ui/core'; import * as React from 'react'; import { BlobInput } from './BlobInput'; import { CollectionInput } from './CollectionInput'; -import { formStrings } from './constants'; +import { formStrings, inputsDescription } from './constants'; import { LaunchState } from './launchMachine'; import { NoInputsNeeded } from './NoInputsNeeded'; import { SimpleInput } from './SimpleInput'; @@ -19,10 +19,6 @@ import { UnsupportedRequiredInputsError } from './UnsupportedRequiredInputsError import { useFormInputsState } from './useFormInputsState'; import { isEnterInputsState } from './utils'; -const inputsHeader = 'Inputs'; -const inputsDescription = - 'Enter input values below. Items marked with an asterisk(*) are required.'; - function getComponentForInput(input: InputProps, showErrors: boolean) { const props = { ...input, error: showErrors ? input.error : undefined }; switch (input.typeDefinition.type) { @@ -57,7 +53,7 @@ const RenderFormInputs: React.FC<{ ) : ( <>
- {inputsHeader} + {formStrings.inputs} {inputsDescription}
{inputs.map(input => ( diff --git a/src/components/Launch/LaunchForm/LaunchRoleInput.tsx b/src/components/Launch/LaunchForm/LaunchRoleInput.tsx index b7fc8f4cd..f081a70e5 100644 --- a/src/components/Launch/LaunchForm/LaunchRoleInput.tsx +++ b/src/components/Launch/LaunchForm/LaunchRoleInput.tsx @@ -7,12 +7,15 @@ import { TextField, Typography } from '@material-ui/core'; +import { log } from 'common/log'; +import { getCacheKey } from 'components/Cache'; import { NewTargetLink } from 'components/common/NewTargetLink'; import { useDebouncedValue } from 'components/hooks/useDebouncedValue'; import { Admin } from 'flyteidl'; import * as React from 'react'; import { launchInputDebouncDelay, roleTypes } from './constants'; import { makeStringChangeHandler } from './handlers'; +import { useInputValueCacheContext } from './inputValueCache'; import { useStyles } from './styles'; import { LaunchRoleInputRef, RoleType, RoleTypeValue } from './types'; @@ -42,13 +45,37 @@ interface LaunchRoleInputState { validate(): boolean; } +function getRoleTypeByValue(value: string): RoleType | undefined { + return Object.values(roleTypes).find( + ({ value: roleTypeValue }) => value === roleTypeValue + ); +} + +const roleTypeCacheKey = '__roleType'; +const roleStringCacheKey = '__roleString'; + export function useRoleInputState(): LaunchRoleInputState { + const inputValueCache = useInputValueCacheContext(); + const [error, setError] = React.useState(); - const [roleString, setRoleString] = React.useState(); - const [roleType, setRoleType] = React.useState( - getDefaultRoleTypeValue + const [roleString, setRoleString] = React.useState( + inputValueCache.has(roleStringCacheKey) + ? `${inputValueCache.get(roleStringCacheKey)}` + : '' ); + const [roleType, setRoleType] = React.useState(() => { + let roleType: RoleType | undefined; + if (inputValueCache.has(roleTypeCacheKey)) { + const cachedValue = `${inputValueCache.get(roleTypeCacheKey)}`; + roleType = getRoleTypeByValue(cachedValue); + if (roleType === undefined) { + log.error(`Unexepected cached role type: ${cachedValue}`); + } + } + return roleType ?? getDefaultRoleTypeValue(); + }); + const validationValue = useDebouncedValue( roleString, launchInputDebouncDelay @@ -64,13 +91,17 @@ export function useRoleInputState(): LaunchRoleInputState { return true; }; + const onChangeRoleString = (value: string) => { + inputValueCache.set(roleStringCacheKey, value); + setRoleString(value); + }; + const onChangeRoleType = (value: string) => { - const newRoleType = Object.values(roleTypes).find( - ({ value: roleTypeValue }) => value === roleTypeValue - ); + const newRoleType = getRoleTypeByValue(value); if (newRoleType === undefined) { throw new Error(`Unexpected role type value: ${value}`); } + inputValueCache.set(roleTypeCacheKey, value); setRoleType(newRoleType); }; @@ -81,11 +112,11 @@ export function useRoleInputState(): LaunchRoleInputState { return { error, getValue, + onChangeRoleString, onChangeRoleType, roleType, roleString, - validate, - onChangeRoleString: setRoleString + validate }; } @@ -110,7 +141,7 @@ export const LaunchRoleInputImpl: React.RefForwardingComponent< error, getValue, roleType, - roleString, + roleString = '', onChangeRoleString, onChangeRoleType, validate @@ -123,9 +154,6 @@ export const LaunchRoleInputImpl: React.RefForwardingComponent< validate })); - // TODO: Select appropriate helper text/label constants based on the selected - // role type and use them in the text field. - return (
diff --git a/src/components/Launch/LaunchForm/constants.ts b/src/components/Launch/LaunchForm/constants.ts index c4de51b62..149b43831 100644 --- a/src/components/Launch/LaunchForm/constants.ts +++ b/src/components/Launch/LaunchForm/constants.ts @@ -95,6 +95,8 @@ export const launchInputDebouncDelay = 500; export const requiredInputSuffix = '*'; export const cannotLaunchWorkflowString = 'Workflow cannot be launched'; export const cannotLaunchTaskString = 'Task cannot be launched'; +export const inputsDescription = + 'Enter input values below. Items marked with an asterisk(*) are required.'; export const workflowNoInputsString = 'This workflow does not accept any inputs.'; export const taskNoInputsString = 'This task does not accept any inputs.'; diff --git a/src/components/Launch/LaunchForm/test/LaunchTaskForm.test.tsx b/src/components/Launch/LaunchForm/test/LaunchTaskForm.test.tsx index 30a9b27d0..828bc9e8f 100644 --- a/src/components/Launch/LaunchForm/test/LaunchTaskForm.test.tsx +++ b/src/components/Launch/LaunchForm/test/LaunchTaskForm.test.tsx @@ -1,6 +1,5 @@ import { ThemeProvider } from '@material-ui/styles'; import { - act, fireEvent, getAllByRole, getByLabelText, @@ -36,7 +35,10 @@ import { import { cannotLaunchTaskString, formStrings, - requiredInputSuffix + inputsDescription, + requiredInputSuffix, + roleTypes, + taskNoInputsString } from '../constants'; import { LaunchForm } from '../LaunchForm'; import { LaunchFormProps, TaskInitialLaunchParameters } from '../types'; @@ -45,7 +47,9 @@ import { binaryInputName, booleanInputName, floatInputName, + iamRoleString, integerInputName, + k8sServiceAccountString, stringInputName } from './constants'; import { createMockObjects } from './utils'; @@ -139,7 +143,7 @@ describe('LaunchForm: Task', () => { return buttons[0]; }; - const fillInputs = (container: HTMLElement) => { + const fillInputs = async (container: HTMLElement) => { fireEvent.change( getByLabelText(container, stringInputName, { exact: false @@ -158,8 +162,43 @@ describe('LaunchForm: Task', () => { }), { target: { value: '1.5' } } ); + fireEvent.click(getByLabelText(container, roleTypes.iamRole.label)); + const roleInput = await waitFor(() => + getByLabelText(container, roleTypes.iamRole.inputLabel, { + exact: false + }) + ); + fireEvent.change(roleInput, { target: { value: iamRoleString } }); }; + describe('With No Inputs', () => { + beforeEach(() => { + variables = {}; + createMocks(); + }); + + it('should render info message', async () => { + const { container, getByText } = renderForm(); + const submitButton = await waitFor(() => + getSubmitButton(container) + ); + await waitFor(() => expect(submitButton).toBeEnabled()); + + expect(getByText(taskNoInputsString)).toBeInTheDocument(); + }); + + it('should not render inputs header/description', async () => { + const { container, queryByText } = renderForm(); + const submitButton = await waitFor(() => + getSubmitButton(container) + ); + await waitFor(() => expect(submitButton).toBeEnabled()); + + expect(queryByText(formStrings.inputs)).toBeNull(); + expect(queryByText(inputsDescription)).toBeNull(); + }); + }); + describe('With Simple Inputs', () => { beforeEach(() => { const { @@ -255,7 +294,7 @@ describe('LaunchForm: Task', () => { exact: false }) ); - fillInputs(container); + await fillInputs(container); const submitButton = getSubmitButton(container); fireEvent.change(integerInput, { target: { value: 'abc' } }); fireEvent.click(submitButton); @@ -337,7 +376,7 @@ describe('LaunchForm: Task', () => { queryByText } = renderForm(); await waitFor(() => getByTitle(formStrings.inputs)); - fillInputs(container); + await fillInputs(container); fireEvent.click(getSubmitButton(container)); await waitFor(() => @@ -357,18 +396,126 @@ describe('LaunchForm: Task', () => { ); }); - it('should render info message when no inputs are needed', async () => {}); - describe('Auth Role', () => { - it('should require a value', async () => {}); + it('should require a value', async () => { + const { container, getByLabelText } = renderForm(); + const roleInput = await waitFor(() => + getByLabelText(roleTypes.iamRole.inputLabel, { + exact: false + }) + ); + + fireEvent.click(getSubmitButton(container)); + await waitFor(() => expect(roleInput).toBeInvalid()); + }); + + Object.entries(roleTypes).forEach( + ([key, { label, inputLabel, helperText }]) => { + it(`should show correct label and helper text for ${key}`, async () => { + const { getByLabelText, getByText } = renderForm(); + const roleRadioSelector = await waitFor(() => + getByLabelText(label) + ); + fireEvent.click(roleRadioSelector); + await waitFor(() => + getByLabelText(inputLabel, { exact: false }) + ); + expect(getByText(helperText)).toBeInTheDocument(); + }); + + it('should preserve role value for ${key} when changing task version', async () => { + const { getByLabelText, getByTitle } = renderForm(); - it('should show correct help text and label based on role type selection', async () => {}); + // We expect both the radio selection and text value to be preserved + const roleRadioSelector = await waitFor(() => + getByLabelText(label) + ); + fireEvent.click(roleRadioSelector); - it('should correctly construct an IAM role', async () => {}); + expect(roleRadioSelector).toBeChecked(); + + const roleInput = await waitFor(() => + getByLabelText(inputLabel, { + exact: false + }) + ); + fireEvent.change(roleInput, { + target: { value: 'roleInputStringValue' } + }); + + // Click the expander for the task version, select the second item + const taskVersionDiv = getByTitle( + formStrings.taskVersion + ); + const expander = getByRole(taskVersionDiv, 'button'); + fireEvent.click(expander); + const items = await waitFor(() => + getAllByRole(taskVersionDiv, 'menuitem') + ); + fireEvent.click(items[1]); + await waitFor(() => getByTitle(formStrings.inputs)); + + expect(getByLabelText(label)).toBeChecked(); + expect( + getByLabelText(inputLabel, { + exact: false + }) + ).toHaveValue('roleInputStringValue'); + }); + } + ); + + it('should correctly construct an IAM role', async () => { + const { container, getByLabelText } = renderForm(); + const { label, inputLabel, value } = roleTypes.iamRole; + const radioSelector = await waitFor(() => + getByLabelText(label) + ); + await fillInputs(container); + fireEvent.click(radioSelector); + const input = await waitFor(() => + getByLabelText(inputLabel, { exact: false }) + ); + fireEvent.change(input, { target: { value: iamRoleString } }); + + fireEvent.click(getSubmitButton(container)); + await waitFor(() => + expect(mockCreateWorkflowExecution).toHaveBeenCalledWith( + expect.objectContaining({ + authRole: { [value]: iamRoleString } + }) + ) + ); + }); - it('should correctly construct a k8s service account role', async () => {}); + it('should correctly construct a k8s service account role', async () => { + const { container, getByLabelText } = renderForm(); + const { + label, + inputLabel, + value + } = roleTypes.k8sServiceAccount; + const radioSelector = await waitFor(() => + getByLabelText(label) + ); + await fillInputs(container); + fireEvent.click(radioSelector); + const input = await waitFor(() => + getByLabelText(inputLabel, { exact: false }) + ); + fireEvent.change(input, { + target: { value: k8sServiceAccountString } + }); - it('should not submit with input errors and a valid value for role', async () => {}); + fireEvent.click(getSubmitButton(container)); + await waitFor(() => + expect(mockCreateWorkflowExecution).toHaveBeenCalledWith( + expect.objectContaining({ + authRole: { [value]: k8sServiceAccountString } + }) + ) + ); + }); }); describe('Input Values', () => { @@ -385,7 +532,7 @@ describe('LaunchForm: Task', () => { const { container, getByTitle } = renderForm(); await waitFor(() => getByTitle(formStrings.inputs)); - fillInputs(container); + await fillInputs(container); fireEvent.click(getSubmitButton(container)); await waitFor(() => @@ -562,7 +709,9 @@ describe('LaunchForm: Task', () => { ); }); - it('should use provided authRole parameter', async () => {}); + it('should use provided authRole parameter', async () => { + // todo + }); }); describe('With Unsupported Required Inputs', () => { diff --git a/src/components/Launch/LaunchForm/test/LaunchWorkflowForm.test.tsx b/src/components/Launch/LaunchForm/test/LaunchWorkflowForm.test.tsx index a9a6f5c77..cc3a84c53 100644 --- a/src/components/Launch/LaunchForm/test/LaunchWorkflowForm.test.tsx +++ b/src/components/Launch/LaunchForm/test/LaunchWorkflowForm.test.tsx @@ -39,7 +39,9 @@ import { import { cannotLaunchWorkflowString, formStrings, - requiredInputSuffix + inputsDescription, + requiredInputSuffix, + workflowNoInputsString } from '../constants'; import { LaunchForm } from '../LaunchForm'; import { LaunchFormProps, WorkflowInitialLaunchParameters } from '../types'; @@ -184,6 +186,34 @@ describe('LaunchForm: Workflow', () => { return buttons[0]; }; + describe('With No Inputs', () => { + beforeEach(() => { + variables = {}; + createMocks(); + }); + + it('should render info message', async () => { + const { container, getByText } = renderForm(); + const submitButton = await waitFor(() => + getSubmitButton(container) + ); + await waitFor(() => expect(submitButton).toBeEnabled()); + + expect(getByText(workflowNoInputsString)).toBeInTheDocument(); + }); + + it('should not render inputs header/description', async () => { + const { container, queryByText } = renderForm(); + const submitButton = await waitFor(() => + getSubmitButton(container) + ); + await waitFor(() => expect(submitButton).toBeEnabled()); + + expect(queryByText(formStrings.inputs)).toBeNull(); + expect(queryByText(inputsDescription)).toBeNull(); + }); + }); + describe('With Simple Inputs', () => { beforeEach(() => { variables = cloneDeep(mockSimpleVariables); diff --git a/src/components/Launch/LaunchForm/test/constants.ts b/src/components/Launch/LaunchForm/test/constants.ts index a4f8ab0ae..be2d0e9db 100644 --- a/src/components/Launch/LaunchForm/test/constants.ts +++ b/src/components/Launch/LaunchForm/test/constants.ts @@ -1,3 +1,5 @@ +import { roleTypes } from '../constants'; + export const booleanInputName = 'simpleBoolean'; export const stringInputName = 'simpleString'; export const stringNoLabelName = 'stringNoLabel'; @@ -7,3 +9,6 @@ export const datetimeInputName = 'simpleDatetime'; export const integerInputName = 'simpleInteger'; export const binaryInputName = 'simpleBinary'; export const errorInputName = 'simpleError'; + +export const iamRoleString = 'arn:aws:iam::12345678:role/defaultrole'; +export const k8sServiceAccountString = 'default-service-account'; diff --git a/src/components/Launch/LaunchForm/useLaunchTaskFormState.ts b/src/components/Launch/LaunchForm/useLaunchTaskFormState.ts index 98c1222db..f73b428d5 100644 --- a/src/components/Launch/LaunchForm/useLaunchTaskFormState.ts +++ b/src/components/Launch/LaunchForm/useLaunchTaskFormState.ts @@ -109,7 +109,7 @@ async function validate( if (!roleInputRef.current.validate()) { throw new Error(correctInputErrors); } - baseValidate(formInputsRef, context); + return baseValidate(formInputsRef, context); } async function submit( From 47ad020bae19dff83b2e2a323a22b944cc2d3059 Mon Sep 17 00:00:00 2001 From: Randy Schott <1815175+schottra@users.noreply.github.com> Date: Fri, 13 Nov 2020 14:41:39 -0800 Subject: [PATCH 4/5] fix: pass authRole when relaunching single task executions --- .../RelaunchExecutionForm.tsx | 4 +- .../test/RelaunchExecutionForm.test.tsx | 17 ++ .../Launch/LaunchForm/LaunchFormInputs.tsx | 1 - .../Launch/LaunchForm/LaunchRoleInput.tsx | 84 ++++++--- .../Launch/LaunchForm/LaunchTaskForm.tsx | 1 + .../Launch/LaunchForm/launchMachine.ts | 1 + .../LaunchForm/test/LaunchTaskForm.test.tsx | 178 +++++++++++++----- src/components/Launch/LaunchForm/types.ts | 1 + .../LaunchForm/useLaunchTaskFormState.ts | 4 +- src/models/Execution/types.ts | 1 + 10 files changed, 209 insertions(+), 83 deletions(-) diff --git a/src/components/Executions/ExecutionDetails/RelaunchExecutionForm.tsx b/src/components/Executions/ExecutionDetails/RelaunchExecutionForm.tsx index ed9ce53ff..2ebd9a5e0 100644 --- a/src/components/Executions/ExecutionDetails/RelaunchExecutionForm.tsx +++ b/src/components/Executions/ExecutionDetails/RelaunchExecutionForm.tsx @@ -62,7 +62,7 @@ function useRelaunchTaskFormState({ execution }: RelaunchExecutionFormProps) { defaultValue: {} as TaskInitialLaunchParameters, doFetch: async execution => { const { - spec: { launchPlan: taskId } + spec: { authRole, launchPlan: taskId } } = execution; const task = await apiContext.getTask(taskId); const inputDefinitions = getTaskInputs(task); @@ -73,7 +73,7 @@ function useRelaunchTaskFormState({ execution }: RelaunchExecutionFormProps) { }, apiContext ); - return { values, taskId }; + return { authRole, values, taskId }; } }, execution diff --git a/src/components/Executions/ExecutionDetails/test/RelaunchExecutionForm.test.tsx b/src/components/Executions/ExecutionDetails/test/RelaunchExecutionForm.test.tsx index 32dcee7b3..ef26530f3 100644 --- a/src/components/Executions/ExecutionDetails/test/RelaunchExecutionForm.test.tsx +++ b/src/components/Executions/ExecutionDetails/test/RelaunchExecutionForm.test.tsx @@ -12,6 +12,7 @@ import { createInputCacheKey, getInputDefintionForLiteralType } from 'components/Launch/LaunchForm/utils'; +import { Admin } from 'flyteidl'; import { Execution, ExecutionData, @@ -189,8 +190,13 @@ describe('RelaunchExecutionForm', () => { describe('with single task execution', () => { let values: LiteralValueMap; + let authRole: Admin.IAuthRole; beforeEach(() => { + authRole = { + assumableIamRole: 'arn:aws:iam::12345678:role/defaultrole' + }; execution.spec.launchPlan.resourceType = ResourceType.TASK; + execution.spec.authRole = { ...authRole }; taskInputDefinitions = { taskSimpleString: mockSimpleVariables.simpleString, taskSimpleInteger: mockSimpleVariables.simpleInteger @@ -222,6 +228,17 @@ describe('RelaunchExecutionForm', () => { }); }); + it('passes authRole from original execution', async () => { + const { getByText } = renderForm(); + await waitFor(() => getByText(mockContentString)); + + checkLaunchFormProps({ + initialParameters: expect.objectContaining({ + authRole + }) + }); + }); + it('maps execution input values to workflow inputs', async () => { const { getByText } = renderForm(); await waitFor(() => getByText(mockContentString)); diff --git a/src/components/Launch/LaunchForm/LaunchFormInputs.tsx b/src/components/Launch/LaunchForm/LaunchFormInputs.tsx index 88b3d4345..6dd6fb344 100644 --- a/src/components/Launch/LaunchForm/LaunchFormInputs.tsx +++ b/src/components/Launch/LaunchForm/LaunchFormInputs.tsx @@ -75,7 +75,6 @@ export const LaunchFormInputsImpl: React.RefForwardingComponent< showErrors } = state.context; const { getValues, inputs, validate } = useFormInputsState(parsedInputs); - const styles = useStyles(); React.useImperativeHandle(ref, () => ({ getValues, validate diff --git a/src/components/Launch/LaunchForm/LaunchRoleInput.tsx b/src/components/Launch/LaunchForm/LaunchRoleInput.tsx index f081a70e5..98c10995b 100644 --- a/src/components/Launch/LaunchForm/LaunchRoleInput.tsx +++ b/src/components/Launch/LaunchForm/LaunchRoleInput.tsx @@ -8,7 +8,6 @@ import { Typography } from '@material-ui/core'; import { log } from 'common/log'; -import { getCacheKey } from 'components/Cache'; import { NewTargetLink } from 'components/common/NewTargetLink'; import { useDebouncedValue } from 'components/hooks/useDebouncedValue'; import { Admin } from 'flyteidl'; @@ -17,21 +16,17 @@ import { launchInputDebouncDelay, roleTypes } from './constants'; import { makeStringChangeHandler } from './handlers'; import { useInputValueCacheContext } from './inputValueCache'; import { useStyles } from './styles'; -import { LaunchRoleInputRef, RoleType, RoleTypeValue } from './types'; +import { InputValueMap, LaunchRoleInputRef, RoleType } from './types'; const roleHeader = 'Role'; -const iamHelperText = 'Enter a string to use as the role value'; const roleDocLinkUrl = 'https://github.com/lyft/flyteidl/blob/3789005a1372221eba28fa20d8386e44b32388f5/protos/flyteidl/admin/common.proto#L241'; -const roleValueLabel = 'role value'; const roleTypeLabel = 'type'; const roleInputId = 'launch-auth-role'; - -function getDefaultRoleTypeValue(): RoleType { - return roleTypes.iamRole; -} +const defaultRoleTypeValue = roleTypes.iamRole; export interface LaunchRoleInputProps { + initialValue?: Admin.IAuthRole; showErrors: boolean; } @@ -54,27 +49,64 @@ function getRoleTypeByValue(value: string): RoleType | undefined { const roleTypeCacheKey = '__roleType'; const roleStringCacheKey = '__roleString'; -export function useRoleInputState(): LaunchRoleInputState { +interface AuthRoleInitialValues { + roleType: RoleType; + roleString: string; +} + +function getInitialValues( + cache: InputValueMap, + initialValue?: Admin.IAuthRole +): AuthRoleInitialValues { + let roleType: RoleType | undefined; + let roleString: string | undefined; + + // Prefer cached value first, since that is user input + if (cache.has(roleTypeCacheKey)) { + const cachedValue = `${cache.get(roleTypeCacheKey)}`; + roleType = getRoleTypeByValue(cachedValue); + if (roleType === undefined) { + log.error(`Unexepected cached role type: ${cachedValue}`); + } + } + if (cache.has(roleStringCacheKey)) { + roleString = cache.get(roleStringCacheKey)?.toString(); + } + + // After trying cache, check for an initial value and populate either + // field from the initial value if no cached value was passed. + if (initialValue != null) { + const initialRoleType = Object.values(roleTypes).find( + rt => initialValue[rt.value] != null + ); + if (initialRoleType != null && roleType == null) { + roleType = initialRoleType; + } + if (initialRoleType != null && roleString == null) { + roleString = initialValue[initialRoleType.value]?.toString(); + } + } + + return { + roleType: roleType ?? defaultRoleTypeValue, + roleString: roleString ?? '' + }; +} + +export function useRoleInputState( + props: LaunchRoleInputProps +): LaunchRoleInputState { const inputValueCache = useInputValueCacheContext(); + const initialValues = getInitialValues(inputValueCache, props.initialValue); const [error, setError] = React.useState(); const [roleString, setRoleString] = React.useState( - inputValueCache.has(roleStringCacheKey) - ? `${inputValueCache.get(roleStringCacheKey)}` - : '' + initialValues.roleString ); - const [roleType, setRoleType] = React.useState(() => { - let roleType: RoleType | undefined; - if (inputValueCache.has(roleTypeCacheKey)) { - const cachedValue = `${inputValueCache.get(roleTypeCacheKey)}`; - roleType = getRoleTypeByValue(cachedValue); - if (roleType === undefined) { - log.error(`Unexepected cached role type: ${cachedValue}`); - } - } - return roleType ?? getDefaultRoleTypeValue(); - }); + const [roleType, setRoleType] = React.useState( + initialValues.roleType + ); const validationValue = useDebouncedValue( roleString, @@ -135,7 +167,7 @@ const RoleDescription = () => ( export const LaunchRoleInputImpl: React.RefForwardingComponent< LaunchRoleInputRef, LaunchRoleInputProps -> = ({ showErrors }, ref) => { +> = (props, ref) => { const styles = useStyles(); const { error, @@ -145,8 +177,8 @@ export const LaunchRoleInputImpl: React.RefForwardingComponent< onChangeRoleString, onChangeRoleType, validate - } = useRoleInputState(); - const hasError = showErrors && !!error; + } = useRoleInputState(props); + const hasError = props.showErrors && !!error; const helperText = hasError ? error : roleType.helperText; React.useImperativeHandle(ref, () => ({ diff --git a/src/components/Launch/LaunchForm/LaunchTaskForm.tsx b/src/components/Launch/LaunchForm/LaunchTaskForm.tsx index aae888032..7647584a4 100644 --- a/src/components/Launch/LaunchForm/LaunchTaskForm.tsx +++ b/src/components/Launch/LaunchForm/LaunchTaskForm.tsx @@ -71,6 +71,7 @@ export const LaunchTaskForm: React.FC = props => { ) : null} {isEnterInputsState(baseState) ? ( diff --git a/src/components/Launch/LaunchForm/launchMachine.ts b/src/components/Launch/LaunchForm/launchMachine.ts index cf84a3555..a60b15a8b 100644 --- a/src/components/Launch/LaunchForm/launchMachine.ts +++ b/src/components/Launch/LaunchForm/launchMachine.ts @@ -84,6 +84,7 @@ export interface WorkflowLaunchContext extends BaseLaunchContext { } export interface TaskLaunchContext extends BaseLaunchContext { + defaultAuthRole?: Admin.IAuthRole; preferredTaskId?: Identifier; taskVersion?: Identifier; taskVersionOptions?: Task[]; diff --git a/src/components/Launch/LaunchForm/test/LaunchTaskForm.test.tsx b/src/components/Launch/LaunchForm/test/LaunchTaskForm.test.tsx index 828bc9e8f..c4b7aad8e 100644 --- a/src/components/Launch/LaunchForm/test/LaunchTaskForm.test.tsx +++ b/src/components/Launch/LaunchForm/test/LaunchTaskForm.test.tsx @@ -199,7 +199,7 @@ describe('LaunchForm: Task', () => { }); }); - describe('With Simple Inputs', () => { + describe('With Inputs', () => { beforeEach(() => { const { simpleString, @@ -410,57 +410,135 @@ describe('LaunchForm: Task', () => { }); Object.entries(roleTypes).forEach( - ([key, { label, inputLabel, helperText }]) => { - it(`should show correct label and helper text for ${key}`, async () => { - const { getByLabelText, getByText } = renderForm(); - const roleRadioSelector = await waitFor(() => - getByLabelText(label) - ); - fireEvent.click(roleRadioSelector); - await waitFor(() => - getByLabelText(inputLabel, { exact: false }) - ); - expect(getByText(helperText)).toBeInTheDocument(); - }); - - it('should preserve role value for ${key} when changing task version', async () => { - const { getByLabelText, getByTitle } = renderForm(); - - // We expect both the radio selection and text value to be preserved - const roleRadioSelector = await waitFor(() => - getByLabelText(label) - ); - fireEvent.click(roleRadioSelector); + ([key, { label, inputLabel, helperText, value }]) => { + describe(`for role type ${key}`, () => { + it('should show correct label and helper text', async () => { + const { getByLabelText, getByText } = renderForm(); + const roleRadioSelector = await waitFor(() => + getByLabelText(label) + ); + fireEvent.click(roleRadioSelector); + await waitFor(() => + getByLabelText(inputLabel, { exact: false }) + ); + expect(getByText(helperText)).toBeInTheDocument(); + }); - expect(roleRadioSelector).toBeChecked(); + it('should preserve role value when changing task version', async () => { + const { getByLabelText, getByTitle } = renderForm(); + + // We expect both the radio selection and text value to be preserved + const roleRadioSelector = await waitFor(() => + getByLabelText(label) + ); + fireEvent.click(roleRadioSelector); + + expect(roleRadioSelector).toBeChecked(); + + const roleInput = await waitFor(() => + getByLabelText(inputLabel, { + exact: false + }) + ); + fireEvent.change(roleInput, { + target: { value: 'roleInputStringValue' } + }); + + // Click the expander for the task version, select the second item + const taskVersionDiv = getByTitle( + formStrings.taskVersion + ); + const expander = getByRole( + taskVersionDiv, + 'button' + ); + fireEvent.click(expander); + const items = await waitFor(() => + getAllByRole(taskVersionDiv, 'menuitem') + ); + fireEvent.click(items[1]); + await waitFor(() => getByTitle(formStrings.inputs)); + + expect(getByLabelText(label)).toBeChecked(); + expect( + getByLabelText(inputLabel, { + exact: false + }) + ).toHaveValue('roleInputStringValue'); + }); - const roleInput = await waitFor(() => - getByLabelText(inputLabel, { - exact: false - }) - ); - fireEvent.change(roleInput, { - target: { value: 'roleInputStringValue' } + it(`should use initial values when provided`, async () => { + const initialParameters: TaskInitialLaunchParameters = { + authRole: { + [value]: 'roleStringValue' + } + }; + const { getByLabelText } = renderForm({ + initialParameters + }); + await waitFor(() => + expect(getByLabelText(label)).toBeChecked() + ); + await waitFor(() => + expect( + getByLabelText(inputLabel, { exact: false }) + ).toHaveValue('roleStringValue') + ); }); - // Click the expander for the task version, select the second item - const taskVersionDiv = getByTitle( - formStrings.taskVersion - ); - const expander = getByRole(taskVersionDiv, 'button'); - fireEvent.click(expander); - const items = await waitFor(() => - getAllByRole(taskVersionDiv, 'menuitem') - ); - fireEvent.click(items[1]); - await waitFor(() => getByTitle(formStrings.inputs)); - - expect(getByLabelText(label)).toBeChecked(); - expect( - getByLabelText(inputLabel, { - exact: false - }) - ).toHaveValue('roleInputStringValue'); + it(`should prefer cached values over initial values when changing task versions`, async () => { + const initialRoleTypeValue = Object.values( + roleTypes + ).find(rt => rt.value !== value)?.value; + // Set the role and string initial values to something different than what we will input + const initialParameters: TaskInitialLaunchParameters = { + authRole: { + [initialRoleTypeValue!]: 'initialRoleStringValue' + } + }; + const { getByLabelText, getByTitle } = renderForm({ + initialParameters + }); + + // We expect both the radio selection and text value to be preserved + const roleRadioSelector = await waitFor(() => + getByLabelText(label) + ); + fireEvent.click(roleRadioSelector); + + expect(roleRadioSelector).toBeChecked(); + + const roleInput = await waitFor(() => + getByLabelText(inputLabel, { + exact: false + }) + ); + fireEvent.change(roleInput, { + target: { value: 'roleInputStringValue' } + }); + + // Click the expander for the task version, select the second item + const taskVersionDiv = getByTitle( + formStrings.taskVersion + ); + const expander = getByRole( + taskVersionDiv, + 'button' + ); + fireEvent.click(expander); + const items = await waitFor(() => + getAllByRole(taskVersionDiv, 'menuitem') + ); + fireEvent.click(items[1]); + await waitFor(() => getByTitle(formStrings.inputs)); + + expect(getByLabelText(label)).toBeChecked(); + expect( + getByLabelText(inputLabel, { + exact: false + }) + ).toHaveValue('roleInputStringValue'); + }); }); } ); @@ -708,10 +786,6 @@ describe('LaunchForm: Task', () => { ) ); }); - - it('should use provided authRole parameter', async () => { - // todo - }); }); describe('With Unsupported Required Inputs', () => { diff --git a/src/components/Launch/LaunchForm/types.ts b/src/components/Launch/LaunchForm/types.ts index 2be0de4f8..10c543c9d 100644 --- a/src/components/Launch/LaunchForm/types.ts +++ b/src/components/Launch/LaunchForm/types.ts @@ -64,6 +64,7 @@ export interface LaunchWorkflowFormProps extends BaseLaunchFormProps { export interface TaskInitialLaunchParameters extends BaseInitialLaunchParameters { taskId?: Identifier; + authRole?: Admin.IAuthRole; } export interface LaunchTaskFormProps extends BaseLaunchFormProps { taskId: NamedEntityIdentifier; diff --git a/src/components/Launch/LaunchForm/useLaunchTaskFormState.ts b/src/components/Launch/LaunchForm/useLaunchTaskFormState.ts index f73b428d5..a3a0db62d 100644 --- a/src/components/Launch/LaunchForm/useLaunchTaskFormState.ts +++ b/src/components/Launch/LaunchForm/useLaunchTaskFormState.ts @@ -14,14 +14,12 @@ import { RefObject, useEffect, useMemo, useRef } from 'react'; import { correctInputErrors } from './constants'; import { getInputsForTask } from './getInputs'; import { - BaseLaunchContext, LaunchState, TaskLaunchContext, TaskLaunchEvent, taskLaunchMachine, TaskLaunchTypestate } from './launchMachine'; -import { useRoleInputState } from './LaunchRoleInput'; import { validate as baseValidate } from './services'; import { LaunchFormInputsRef, @@ -173,6 +171,7 @@ export function useLaunchTaskFormState({ // These values will be used to auto-select items from the task // version/launch plan drop downs. const { + authRole: defaultAuthRole, taskId: preferredTaskId, values: defaultInputValues } = initialParameters; @@ -194,6 +193,7 @@ export function useLaunchTaskFormState({ ...defaultStateMachineConfig, services, context: { + defaultAuthRole, defaultInputValues, preferredTaskId, referenceExecutionId, diff --git a/src/models/Execution/types.ts b/src/models/Execution/types.ts index 0ad9046f6..41a85f98f 100644 --- a/src/models/Execution/types.ts +++ b/src/models/Execution/types.ts @@ -47,6 +47,7 @@ export interface ExecutionMetadata extends Admin.IExecutionMetadata { } export interface ExecutionSpec extends Admin.IExecutionSpec { + authRole?: Admin.IAuthRole; inputs: LiteralMap; launchPlan: Identifier; metadata: ExecutionMetadata; From 146fa0fae43c6fce177c681cdddd5f4b90cdcd77 Mon Sep 17 00:00:00 2001 From: Randy Schott <1815175+schottra@users.noreply.github.com> Date: Fri, 13 Nov 2020 15:24:58 -0800 Subject: [PATCH 5/5] chore: remove unused code --- src/components/Launch/LaunchForm/constants.ts | 12 ------------ src/components/Launch/LaunchForm/types.ts | 1 - .../Launch/LaunchForm/useLaunchWorkflowFormState.ts | 3 --- 3 files changed, 16 deletions(-) diff --git a/src/components/Launch/LaunchForm/constants.ts b/src/components/Launch/LaunchForm/constants.ts index 149b43831..70b8701cc 100644 --- a/src/components/Launch/LaunchForm/constants.ts +++ b/src/components/Launch/LaunchForm/constants.ts @@ -24,18 +24,6 @@ export const formStrings = { launchPlan: 'Launch Plan' }; -export const roleInputDefinition = { - description: - 'Role to assume for this execution (ex. IAM role or Kubernetes Service Account)', - label: 'role', - name: '__authRole', - required: true, - typeDefinition: { - type: InputType.String, - literalType: { simple: SimpleType.STRING } - } -}; - type RoleTypesKey = 'iamRole' | 'k8sServiceAccount'; export const roleTypes: { [k in RoleTypesKey]: RoleType } = { iamRole: { diff --git a/src/components/Launch/LaunchForm/types.ts b/src/components/Launch/LaunchForm/types.ts index 10c543c9d..ebae78309 100644 --- a/src/components/Launch/LaunchForm/types.ts +++ b/src/components/Launch/LaunchForm/types.ts @@ -112,7 +112,6 @@ export interface TaskSourceSelectorState { export interface LaunchWorkflowFormState { formInputsRef: React.RefObject; - onSubmit(): void; state: State< WorkflowLaunchContext, WorkflowLaunchEvent, diff --git a/src/components/Launch/LaunchForm/useLaunchWorkflowFormState.ts b/src/components/Launch/LaunchForm/useLaunchWorkflowFormState.ts index a849c8ae1..4c58246f7 100644 --- a/src/components/Launch/LaunchForm/useLaunchWorkflowFormState.ts +++ b/src/components/Launch/LaunchForm/useLaunchWorkflowFormState.ts @@ -269,8 +269,6 @@ export function useLaunchWorkflowFormState({ }); }; - const onSubmit = () => sendEvent({ type: 'SUBMIT' }); - const workflowSourceSelectorState = useWorkflowSourceSelectorState({ launchPlan, launchPlanOptions, @@ -358,7 +356,6 @@ export function useLaunchWorkflowFormState({ return { formInputsRef, - onSubmit, state, service, workflowSourceSelectorState