Skip to content

Commit

Permalink
Merge pull request #31 from mareklibra/FLPATH-53.validation
Browse files Browse the repository at this point in the history
FLPATH-53: Add validation of workflow params
  • Loading branch information
anludke authored Mar 1, 2023
2 parents 4c13bc1 + ff00bad commit d75ad33
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 26 deletions.
18 changes: 10 additions & 8 deletions plugins/orion/src/components/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,20 @@ export type ProjectType = {
status?: ProjectStatusType;
};

export type WorkflowParameterComponentType =
| 'PASSWORD'
| 'TEXT'
| 'EMAIL'
| 'DATE'
| 'NUMBER'
| 'URL'
| 'MOCK-SELECT' /* TODO: swagger is missing this type */;

export type WorkFlowTaskParameterType = {
key: string;
description: string;
optional: boolean;
type:
| 'PASSWORD'
| 'TEXT'
| 'EMAIL'
| 'DATE'
| 'NUMBER'
| 'URL'
| 'MOCK-SELECT' /* TODO: swagger is missing this type */;
type: WorkflowParameterComponentType;
options?: {
// for MOCK-SELECT
// TODO: swagger is missing this type
Expand Down
12 changes: 9 additions & 3 deletions plugins/orion/src/components/workflow/Onboarding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ export const OnboardingImpl: React.FC<OnboardingProps> = ({ isNew }) => {
const { workflowId, projectId } = useParams();
const backendUrl = useBackendUrl();
const navigate = useNavigate();
const { getParamValue } = React.useContext(WorkflowParametersContext);
const { getParamValue, getParamValidation } = React.useContext(
WorkflowParametersContext,
);
const [error, setError] = React.useState<string>();
const [workflow, setWorkflow] = React.useState<WorkflowDefinitionType>();
const [workflowParameters, setWorkflowParameters] = React.useState<
Expand Down Expand Up @@ -181,8 +183,12 @@ export const OnboardingImpl: React.FC<OnboardingProps> = ({ isNew }) => {
};

const isStartDisabled = !workflowParameters.every(param => {
// Simple check for Required fields
return param.optional || !!getParamValue(param.key);
// Make sure all required fields are entered
if (param.optional && !getParamValue(param.key)) {
return false;
}

return !getParamValidation(param.key);
});

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,33 @@
import React from 'react';
import { TextField } from '@material-ui/core';
import { makeStyles, TextField } from '@material-ui/core';
import { DatePicker } from '@patternfly/react-core';
import { WorkFlowTaskParameterType } from '../types';
import { Select, SelectedItems, SelectItem } from '@backstage/core-components';
import { WorkflowParametersContext } from '../../context/WorkflowParametersContext';
import { getParamValidator, ValidationType } from './validators';

const useStyles = makeStyles({
validationError: {
color: 'red',
},
});

const ValidationError: React.FC<{ validation: ValidationType }> = ({
validation,
}) => {
const styles = useStyles();
return <div className={styles.validationError}>{validation}</div>;
};

export const WorkflowParameterComponent: React.FC<{
param: WorkFlowTaskParameterType;
}> = ({ param }) => {
const { getParamValue, setParamValue } = React.useContext(
const { getParamValue, setParamValue, getParamValidation } = React.useContext(
WorkflowParametersContext,
);

const validation = getParamValidation(param.key);

if (['TEXT', 'PASSWORD', 'EMAIL', 'URL', 'NUMBER'].includes(param.type)) {
let type: string;
switch (param.type) {
Expand All @@ -34,14 +50,23 @@ export const WorkflowParameterComponent: React.FC<{
return (
<TextField
id={param.key}
helperText={param.description}
helperText={
validation ? (
<ValidationError validation={validation} />
) : (
param.description
)
}
label={param.key}
required={!param.optional}
type={type}
value={getParamValue(param.key)}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
// TODO: add validation
setParamValue(param.key, event.target.value);
setParamValue(
param.key,
event.target.value,
getParamValidator(param),
);
}}
variant="outlined"
/>
Expand All @@ -53,11 +78,16 @@ export const WorkflowParameterComponent: React.FC<{
<DatePicker
label={param.key}
required={!param.optional}
helperText={param.description}
helperText={
validation ? (
<ValidationError validation={validation} />
) : (
param.description
)
}
value={getParamValue(param.key)}
onChange={(_, value: string) => {
// TODO: add validation
setParamValue(param.key, value);
setParamValue(param.key, value, getParamValidator(param));
}}
/>
);
Expand All @@ -73,11 +103,12 @@ export const WorkflowParameterComponent: React.FC<{
<Select
onChange={(selected: SelectedItems) => {
const value = Array.isArray(selected) ? selected[0] : selected;
setParamValue(param.key, value as string);
setParamValue(param.key, value as string, getParamValidator(param));
}}
label={param.key}
// required={!param.optional}
// helperText={param.description}
// TODO: required={!param.optional}
// TODO: helperText={param.description}
// TODO: show validation
items={items}
selected={getParamValue(param.key)}
/>
Expand Down
80 changes: 80 additions & 0 deletions plugins/orion/src/components/workflow/validators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { WorkFlowTaskParameterType } from '../types';

export type ValidationType = string;
// returns error text if validation fails or empty string if validation succeeds
export type ValidatorType = (value: string) => ValidationType;

export const VALIDATION_SUCCESS: ValidationType = '';

export const getParamValidator = (
param: WorkFlowTaskParameterType,
): ValidatorType => {
switch (param.type) {
case 'EMAIL':
return (value: string): ValidationType => {
if (!value && !param.optional) {
return 'This field is required';
}

if (!value?.includes('@')) {
return 'This field must be a valid email';
}

return VALIDATION_SUCCESS;
};
case 'DATE':
return (value: string): ValidationType => {
if (!value && !param.optional) {
return 'This field is required';
}

if (isNaN(Date.parse(value))) {
// TODO: improve
return 'This field must be a valid date';
}

return VALIDATION_SUCCESS;
};
case 'NUMBER':
return (value: string): ValidationType => {
if (!value && !param.optional) {
return 'This field is required';
}

if (!['string', 'number'].includes(typeof value)) {
return 'Unknown type of value';
}

if (isNaN(value as unknown as number) || isNaN(parseFloat(value))) {
return 'This field must be a number';
}

return VALIDATION_SUCCESS;
};
case 'URL':
return (value: string): ValidationType => {
if (!value && !param.optional) {
return 'This field is required';
}
// TODO: improve
if (!value.startsWith('http://') && !value.startsWith('https://')) {
return 'This field must be a valid URL';
}
return VALIDATION_SUCCESS;
};
case 'MOCK-SELECT':
// fallthrough
case 'PASSWORD':
// fallthrough
case 'TEXT':
// fallthrough
default:
return (value: string): ValidationType => {
if (!value && !param.optional) {
return 'This field is required';
}

return VALIDATION_SUCCESS;
};
}
};
27 changes: 23 additions & 4 deletions plugins/orion/src/context/WorkflowParametersContext.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import React from 'react';
import { ValidatorType } from '../components/workflow/validators';

type ParamValidationType = string;

type WorkflowParametersContextType = {
getParamValue: (key: string) => string | undefined;
setParamValue: (key: string, value: string) => void;
setParamValue: (key: string, value: string, validator: ValidatorType) => void;
getParamValidation: (key: string) => ParamValidationType | undefined;
};

const noop = () => {
Expand All @@ -13,29 +17,44 @@ export const WorkflowParametersContext =
React.createContext<WorkflowParametersContextType>({
getParamValue: noop,
setParamValue: noop,
getParamValidation: noop,
});

export const WorkflowParametersContextProvider: React.FC = ({ children }) => {
const [workflowParameters, setWorkflowParameters] = React.useState<
Record<string /* param key */, string /* param value */>
Record<
string /* param key */,
{
value: string;
validation?: ParamValidationType;
}
>
>({});

const getParamValue: WorkflowParametersContextType['getParamValue'] = (
key: string,
) => workflowParameters[key];
) => workflowParameters[key]?.value;

const getParamValidation: WorkflowParametersContextType['getParamValidation'] =
(key: string) => workflowParameters[key]?.validation;

const setParamValue: WorkflowParametersContextType['setParamValue'] = (
key: string,
value: string,
validator: ValidatorType,
) => {
setWorkflowParameters({ ...workflowParameters, [key]: value });
setWorkflowParameters({
...workflowParameters,
[key]: { value, validation: validator(value) },
});
};

return (
<WorkflowParametersContext.Provider
value={{
getParamValue,
setParamValue,
getParamValidation,
}}
>
{children}
Expand Down

0 comments on commit d75ad33

Please sign in to comment.