Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds Launch Plan selector #3

Merged
merged 5 commits into from
Sep 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/components/Launch/LaunchWorkflowForm/DatetimeInput.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { default as momentDateUtils } from '@date-io/moment'; // choose your lib
import { makeStyles, Theme } from '@material-ui/core/styles';
import {
KeyboardDateTimePicker,
MuiPickersUtilsProvider
Expand Down
43 changes: 30 additions & 13 deletions src/components/Launch/LaunchWorkflowForm/LaunchWorkflowForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ import { APIContextValue, useAPIContext } from 'components/data/apiContext';
import { smallFontSize } from 'components/Theme';
import { FilterOperationName, WorkflowId } from 'models';
import * as React from 'react';
import { SearchableSelector } from './SearchableSelector';
import { SimpleInput } from './SimpleInput';
import { InputProps, InputType, LaunchWorkflowFormProps } from './types';
import { UnsupportedInput } from './UnsupportedInput';
import { useLaunchWorkflowFormState } from './useLaunchWorkflowFormState';
import { workflowsToWorkflowSelectorOptions } from './utils';
import { WorkflowSelector } from './WorkflowSelector';
import { workflowsToSearchableSelectorOptions } from './utils';

const useStyles = makeStyles((theme: Theme) => ({
footer: {
Expand Down Expand Up @@ -72,7 +72,7 @@ function generateFetchSearchResults(
}
]
});
const options = workflowsToWorkflowSelectorOptions(workflows);
const options = workflowsToSearchableSelectorOptions(workflows);
if (options.length > 0) {
options[0].description = 'latest';
}
Expand All @@ -84,6 +84,7 @@ function generateFetchSearchResults(
export const LaunchWorkflowForm: React.FC<LaunchWorkflowFormProps> = props => {
const state = useLaunchWorkflowFormState(props);
const { submissionState } = state;
const launchPlanSelected = !!state.selectedLaunchPlan;
const styles = useStyles();
const fetchSearchResults = generateFetchSearchResults(
useAPIContext(),
Expand All @@ -107,26 +108,42 @@ export const LaunchWorkflowForm: React.FC<LaunchWorkflowFormProps> = props => {
{...state.workflowOptionsLoadingState}
>
<div className={styles.formControl}>
<WorkflowSelector
<SearchableSelector
label="Workflow Version"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A const somewhere? ¯\_(ツ)_/¯

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's only used in this file, in this single place. So I'm inclined not to make it a constant. If/when we get around to i18n support, my position on that will definitely change :-)

onSelectionChanged={state.onSelectWorkflow}
options={state.workflowSelectorOptions}
fetchSearchResults={fetchSearchResults}
selectedItem={state.selectedWorkflow}
/>
</div>
<WaitForData
{...state.launchPlanOptionsLoadingState}
spinnerVariant="medium"
{...state.inputLoadingState}
>
{state.inputs.map(input => (
<div
key={input.label}
className={styles.formControl}
>
{getComponentForInput(input)}
</div>
))}
<div className={styles.formControl}>
<SearchableSelector
label="Launch Plan"
onSelectionChanged={state.onSelectLaunchPlan}
options={state.launchPlanSelectorOptions}
selectedItem={state.selectedLaunchPlan}
/>
</div>
</WaitForData>
{launchPlanSelected ? (
<WaitForData
spinnerVariant="medium"
{...state.inputLoadingState}
>
{state.inputs.map(input => (
<div
key={input.label}
className={styles.formControl}
>
{getComponentForInput(input)}
</div>
))}
</WaitForData>
) : null}
</WaitForData>
</section>
<div className={styles.footer}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ import ExpandLess from '@material-ui/icons/ExpandLess';
import ExpandMore from '@material-ui/icons/ExpandMore';
import { escapeKeyListener } from 'components/common/keyboardEvents';
import { useCommonStyles } from 'components/common/styles';
import { FetchFn, useFetchableData } from 'components/hooks';
import { FetchableData, FetchFn, useFetchableData } from 'components/hooks';
import { useDebouncedValue } from 'components/hooks/useDebouncedValue';
import { NamedEntityIdentifier, WorkflowId } from 'models';
import * as React from 'react';
import reactLoadingSkeleton from 'react-loading-skeleton';

Expand Down Expand Up @@ -48,26 +47,49 @@ const useStyles = makeStyles((theme: Theme) => ({
}
}));

export interface WorkflowSelectorOption {
export interface SearchableSelectorOption<DataType> {
id: string;
data: WorkflowId;
data: DataType;
name: string;
description?: string;
}

export interface WorkflowSelectorProps {
options: WorkflowSelectorOption[];
selectedItem?: WorkflowSelectorOption;
fetchSearchResults: FetchFn<WorkflowSelectorOption[], string>;
onSelectionChanged(newSelection: WorkflowSelectorOption): void;
export interface SearchableSelectorProps<DataType> {
label: string;
options: SearchableSelectorOption<DataType>[];
selectedItem?: SearchableSelectorOption<DataType>;
fetchSearchResults?: FetchFn<SearchableSelectorOption<DataType>[], string>;
onSelectionChanged(newSelection: SearchableSelectorOption<DataType>): void;
}

function useWorkflowSelectorState({
interface SearchableSelectorState<DataType> {
isExpanded: boolean;
items: SearchableSelectorOption<DataType>[];
searchResults: FetchableData<SearchableSelectorOption<DataType>[]>;
selectedItem?: SearchableSelectorOption<DataType>;
showList: boolean;
inputValue: string;
onBlur(): void;
onChange(event: React.ChangeEvent<HTMLInputElement>): void;
onFocus(): void;
selectItem(item: SearchableSelectorOption<DataType>): void;
setIsExpanded(expanded: boolean): void;
}

function generateDefaultFetch<DataType>(
options: SearchableSelectorOption<DataType>[]
): FetchFn<SearchableSelectorOption<DataType>[], string> {
return (query: string) =>
Promise.resolve(options.filter(option => option.name.includes(query)));
}

function useSearchableSelectorState<DataType>({
fetchSearchResults,
options,
selectedItem,
onSelectionChanged
}: WorkflowSelectorProps) {
}: SearchableSelectorProps<DataType>): SearchableSelectorState<DataType> {
const fetchResults = fetchSearchResults || generateDefaultFetch(options);
const [rawSearchValue, setSearchValue] = React.useState('');
const debouncedSearchValue = useDebouncedValue(
rawSearchValue,
Expand All @@ -78,12 +100,15 @@ function useWorkflowSelectorState({
const [focused, setFocused] = React.useState(false);
const minimumQueryMet = debouncedSearchValue.length > minimumQuerySize;

const searchResults = useFetchableData<WorkflowSelectorOption[], string>(
const searchResults = useFetchableData<
SearchableSelectorOption<DataType>[],
string
>(
{
defaultValue: [],
autoFetch: minimumQueryMet,
debugName: 'WorkflowSelector Search',
doFetch: fetchSearchResults
debugName: 'SearchableSelector Search',
doFetch: fetchResults
},
debouncedSearchValue
);
Expand All @@ -110,7 +135,7 @@ function useWorkflowSelectorState({
setSearchValue(value);
};

const selectItem = (item: WorkflowSelectorOption) => {
const selectItem = (item: SearchableSelectorOption<DataType>) => {
console.log(item.id);
onSelectionChanged(item);
setSearchValue('');
Expand Down Expand Up @@ -157,12 +182,12 @@ const LoadingContent: React.FC = () => (
</MenuItem>
);

const SelectorItems: React.FC<ReturnType<typeof useWorkflowSelectorState>> = ({
const SearchableSelectorItems = <DataType extends {}>({
items,
selectItem,
selectedItem,
searchResults
}) => {
}: SearchableSelectorState<DataType>) => {
const styles = useStyles();
const commonStyles = useCommonStyles();
if (searchResults.loading) {
Expand Down Expand Up @@ -204,9 +229,11 @@ const SelectorItems: React.FC<ReturnType<typeof useWorkflowSelectorState>> = ({
/** Combines a dropdown selector of default options with a searchable text input
* that will fetch results using a provided function.
*/
export const WorkflowSelector: React.FC<WorkflowSelectorProps> = props => {
export const SearchableSelector = <DataType extends {}>(
props: SearchableSelectorProps<DataType>
) => {
const styles = useStyles();
const state = useWorkflowSelectorState(props);
const state = useSearchableSelectorState(props);
const {
inputValue,
isExpanded,
Expand All @@ -224,7 +251,7 @@ export const WorkflowSelector: React.FC<WorkflowSelectorProps> = props => {
}
};

const selectItem = (item: WorkflowSelectorOption) => {
const selectItem = (item: SearchableSelectorOption<DataType>) => {
state.selectItem(item);
blurInput();
};
Expand Down Expand Up @@ -256,14 +283,17 @@ export const WorkflowSelector: React.FC<WorkflowSelectorProps> = props => {
</InputAdornment>
)
}}
label="Workflow Version"
label={props.label}
onChange={onChange}
value={inputValue}
variant="outlined"
/>
{showList ? (
<Paper className={styles.paper} elevation={1}>
<SelectorItems {...state} selectItem={selectItem} />
<SearchableSelectorItems
{...state}
selectItem={selectItem}
/>
</Paper>
) : null}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,21 @@ import {
createMockWorkflow,
createMockWorkflowVersions
} from 'models/__mocks__/workflowData';
import { WorkflowId } from 'models/Workflow/types';
import * as moment from 'moment';
import * as React from 'react';
import { WorkflowSelector, WorkflowSelectorOption } from '../WorkflowSelector';
import {
SearchableSelector,
SearchableSelectorOption
} from '../SearchableSelector';

const mockWorkflow = createMockWorkflow('MyWorkflow');
const mockWorkflowVersions = createMockWorkflowVersions(
mockWorkflow.id.name,
10
);

const options = mockWorkflowVersions.map<WorkflowSelectorOption>(
const options = mockWorkflowVersions.map<SearchableSelectorOption<WorkflowId>>(
(wf, index) => ({
data: wf.id,
id: wf.id.version,
Expand All @@ -40,7 +44,8 @@ stories.add('Basic', () => {
resolveAfter(500, options.filter(({ name }) => name.includes(query)));

return (
<WorkflowSelector
<SearchableSelector
label="Workflow Version"
fetchSearchResults={fetch}
onSelectionChanged={setSelectedItem}
selectedItem={selectedItem}
Expand Down
4 changes: 2 additions & 2 deletions src/components/Launch/LaunchWorkflowForm/inputConverters.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { dateToTimestamp, millisecondsToDuration } from 'common/utils';
import { Core } from 'flyteidl';
import * as Long from 'long';
import * as moment from 'moment';
import { utc as moment } from 'moment';
import { InputType } from './types';

function booleanToLiteral(value: string): Core.ILiteral {
Expand All @@ -25,7 +25,7 @@ function floatToLiteral(value: string): Core.ILiteral {
}

function dateToLiteral(value: string): Core.ILiteral {
const datetime = dateToTimestamp(moment.utc(value).toDate());
const datetime = dateToTimestamp(moment(value).toDate());
return {
scalar: { primitive: { datetime } }
};
Expand Down
16 changes: 8 additions & 8 deletions src/components/Launch/LaunchWorkflowForm/types.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
import { FetchableData, MultiFetchableState } from 'components/hooks';
import { LaunchPlan, WorkflowExecutionIdentifier, WorkflowId } from 'models';
import { WorkflowSelectorOption } from './WorkflowSelector';
import { SearchableSelectorOption } from './SearchableSelector';

export interface LaunchWorkflowFormProps {
workflowId: WorkflowId;
}

export interface LaunchWorkflowFormState {
defaultLaunchPlan: FetchableData<LaunchPlan | undefined>;
inputLoadingState: MultiFetchableState;
inputs: InputProps[];
launchPlans: FetchableData<LaunchPlan[]>;
selectedLaunchPlan?: LaunchPlan;
launchPlanOptionsLoadingState: MultiFetchableState;
launchPlanSelectorOptions: SearchableSelectorOption<LaunchPlan>[];
selectedLaunchPlan?: SearchableSelectorOption<LaunchPlan>;
submissionState: FetchableData<WorkflowExecutionIdentifier>;
selectedWorkflow?: WorkflowSelectorOption;
selectedWorkflow?: SearchableSelectorOption<WorkflowId>;
workflowName: string;
workflowOptionsLoadingState: MultiFetchableState;
workflowSelectorOptions: WorkflowSelectorOption[];
workflowSelectorOptions: SearchableSelectorOption<WorkflowId>[];
onCancel(): void;
onSelectWorkflow(selected: WorkflowSelectorOption): void;
onSelectWorkflow(selected: SearchableSelectorOption<WorkflowId>): void;
onSubmit(): void;
setLaunchPlan(launchPlan: LaunchPlan): void;
onSelectLaunchPlan(selected: SearchableSelectorOption<LaunchPlan>): void;
}

export enum InputType {
Expand Down
Loading