Skip to content

Commit

Permalink
Merge pull request #260 from Tauffer-Consulting/fix/validation-for-an…
Browse files Browse the repository at this point in the history
…y-of

fix: missing validation for anyof inputs
  • Loading branch information
vinicvaz authored Mar 27, 2024
2 parents 7d149cc + d63b350 commit 99fe800
Show file tree
Hide file tree
Showing 10 changed files with 93 additions and 79 deletions.
3 changes: 2 additions & 1 deletion frontend/src/@types/piece/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface Schema {
type: "object";

properties: Properties;
required?: string[];
$defs: Definitions;
}

Expand Down Expand Up @@ -139,7 +140,7 @@ export interface ObjectDefinition {
description: string;
type: "object";
properties: Record<string, EnumDefinition | SimpleProperty | AnyOfProperty>;
required: string[];
required?: string[];
}

export {};
38 changes: 13 additions & 25 deletions frontend/src/components/SelectInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,22 @@ import {
} from "react-hook-form";
import { fetchFromObject } from "utils";

type Props<T> =
| (SelectProps & {
name: Path<T>;
label: string;
options: string[] | Array<{ label: string; value: string }>;
type Props<T> = SelectProps & {
name: Path<T>;
label: string;
options: string[] | Array<{ label: string; value: string }>;

emptyValue: true;
defaultValue?: string;
registerOptions?:
| RegisterOptions<FieldValues, (string | undefined) & Path<T>>
| undefined;
})
| (SelectProps & {
name: Path<T>;
label: string;
options: string[] | Array<{ label: string; value: string }>;

emptyValue?: boolean;
registerOptions?: RegisterOptions<FieldValues>;
});
defaultValue?: string;
registerOptions?: RegisterOptions<
FieldValues,
(string | undefined) & Path<T>
>;
};

function SelectInput<T extends FieldValues>({
options,
label,
name,
emptyValue,
...rest
}: Props<T>) {
const {
Expand Down Expand Up @@ -70,11 +60,9 @@ function SelectInput<T extends FieldValues>({
field.onChange(e.target.value as any);
}}
>
{emptyValue && (
<MenuItem value="" disabled>
<em>None</em>
</MenuItem>
)}
<MenuItem value="" disabled>
<em>None</em>
</MenuItem>
{options.map((option) => {
if (typeof option === "object") {
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export const EnumInput: React.FC<EnumInputProps> = ({
return (
<SelectInput<WorkflowPieceData>
label={definition?.title ?? itemKey.split(".").pop() ?? ""}
emptyValue
name={itemKey}
options={enumOptions}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const InputElement: React.FC<Props> = React.memo(
checkedFromUpstream,
definitions,
isItemArray,
isItemObject,
isItemObject = false,
}) => {
const optionalType = getOptionalType(schema);

Expand All @@ -64,14 +64,15 @@ const InputElement: React.FC<Props> = React.memo(
if (checkedFromUpstream === true) {
const options = upstreamOptions[upstreamKey];
const checkboxKey = isItemObject
? itemKey.replace(/(\.value)(?!.*\.value)/, "fromUpstream")
? itemKey.replace(/(\.value)(?!.*\.value)/, ".upstreamValue")
: itemKey.replace(/\.value$/, "");

return (
<SelectUpstreamInput
name={checkboxKey as any}
label={schema?.title}
options={options ?? []}
object={isItemObject}
/>
);
} else if (isEnumType(schema, definitions)) {
Expand Down Expand Up @@ -158,8 +159,6 @@ const InputElement: React.FC<Props> = React.memo(
/>
);
} else {
console.log("optionalType", optionalType);

return (
<div style={{ color: "red", fontWeight: "bold" }}>
Unknown widget type for {schema.title}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,33 @@ import {
import { type WorkflowPieceData } from "features/workflowEditor/context/types";
import { type Option } from "features/workflowEditor/utils";
import React, { useCallback } from "react";
import { Controller, useFormContext } from "react-hook-form";
import { Controller, type FieldValues, useFormContext } from "react-hook-form";
import { fetchFromObject } from "utils";

type ObjectName = `inputs.${string}.value.${number}.upstreamValue.${string}`;
type Name = `inputs.${string}`;
type Props =
| {
label: string;
name: Name;
options: Option[];
object?: false;
}
| {
label: string;
name: ObjectName;
options: Option[];
object: true;
};
type Props = FieldValues &
(
| {
label: string;
name: Name;
options: Option[];
object?: false;
}
| {
label: string;
name: ObjectName;
options: Option[];
object: true;
}
);

const SelectUpstreamInput: React.FC<Props> = ({
options,
label,
name,
object,
...rest
}) => {
const {
setValue,
Expand All @@ -48,8 +51,8 @@ const SelectUpstreamInput: React.FC<Props> = ({
let nameId = "";

if (object) {
nameArgument = name.replace(`.upstreamValue.`, ".upstreamArgument.");
nameId = name.replace(`.upstreamValue.`, ".upstreamId.");
nameArgument = name.replace(`upstreamValue`, ".upstreamArgument");
nameId = name.replace(`upstreamValue`, ".upstreamId");
} else {
nameArgument = `${name}.upstreamArgument`;
nameId = `${name}.upstreamId`;
Expand All @@ -72,19 +75,18 @@ const SelectUpstreamInput: React.FC<Props> = ({

return (
<FormControl fullWidth>
<InputLabel error={!!error} id={name}>
<InputLabel id={name} error={!!error}>
{label}
</InputLabel>
<Controller
control={control}
name={object ? name : `${name}.upstreamValue`}
control={control}
render={({ field }) => (
<Select
fullWidth
defaultValue={""}
labelId={name}
label={label}
error={!!error}
{...rest}
{...field}
onChange={(event) => {
handleSelectChange(event, field.onChange);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Drawer, Grid, Typography, Tooltip } from "@mui/material";
import { Drawer, Grid, Typography } from "@mui/material";
import DatetimeInput from "components/DatetimeInput";
import SelectInput from "components/SelectInput";
import TextInput from "components/TextInput";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ export const WorkflowsEditorComponent: React.FC = () => {
}, {}),
) as any;

console.log("validationSchema", validationSchema);

const resolver = yupResolver(validationSchema);

const validatedData = await resolver(payload.workflowPiecesData);
Expand Down
2 changes: 0 additions & 2 deletions frontend/src/features/workflowEditor/utils/upstreamOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ export const getUpstreamOptions = (
}
}

console.log("upstreamPieces", upstreamPieces);

if (!schema.properties || isEmpty(schema.properties)) {
return {};
}
Expand Down
68 changes: 44 additions & 24 deletions frontend/src/features/workflowEditor/utils/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,13 @@ const validationObject = () => {
});
};

function getValidationValueBySchemaType(schema: any, required: boolean) {
function getValidationValueBySchemaType(
schema: Property | ObjectDefinition,
required: boolean,
) {
let inputSchema;

if (schema.type === "number" && !schema.format) {
if ("type" in schema && schema.type === "number") {
inputSchema = yup.object({
...defaultValidation,
value: yup.number().when("fromUpstream", ([fromUpstream]) => {
Expand All @@ -80,7 +83,7 @@ function getValidationValueBySchemaType(schema: any, required: boolean) {
.required(); // number is always required
}),
});
} else if (schema.type === "integer" && !schema.format) {
} else if ("type" in schema && schema.type === "integer") {
inputSchema = yup.object({
...defaultValidation,
value: yup.number().when("fromUpstream", ([fromUpstream]) => {
Expand All @@ -94,7 +97,7 @@ function getValidationValueBySchemaType(schema: any, required: boolean) {
.required(); // number is always required
}),
});
} else if (schema.type === "boolean" && !schema.format) {
} else if ("type" in schema && schema.type === "boolean") {
inputSchema = yup.object({
...defaultValidation,
value: yup.boolean().when("fromUpstream", ([fromUpstream]) => {
Expand All @@ -104,7 +107,11 @@ function getValidationValueBySchemaType(schema: any, required: boolean) {
return yup.boolean().required(); // boolean is always required
}),
});
} else if (schema.type === "string" && schema.format === "date") {
} else if (
"type" in schema &&
schema.type === "string" &&
schema?.format === "date"
) {
inputSchema = yup.object({
...defaultValidation,
value: yup.string().when("fromUpstream", ([fromUpstream]) => {
Expand All @@ -117,7 +124,11 @@ function getValidationValueBySchemaType(schema: any, required: boolean) {
.required(); // date is always required
}),
});
} else if (schema.type === "string" && schema?.format === "time") {
} else if (
"type" in schema &&
schema.type === "string" &&
schema?.format === "time"
) {
inputSchema = yup.object({
...defaultValidation,
value: yup.string().when("fromUpstream", ([fromUpstream]) => {
Expand All @@ -133,7 +144,11 @@ function getValidationValueBySchemaType(schema: any, required: boolean) {
}); // Time is always required
}),
});
} else if (schema.type === "string" && schema?.format === "date-time") {
} else if (
"type" in schema &&
schema.type === "string" &&
schema?.format === "date-time"
) {
inputSchema = yup.object({
...defaultValidation,
value: yup.string().when("fromUpstream", ([fromUpstream]) => {
Expand All @@ -150,7 +165,11 @@ function getValidationValueBySchemaType(schema: any, required: boolean) {
}); // Datetime is always required
}),
});
} else if (schema.type === "string" && schema?.widget === "codeeditor") {
} else if (
"type" in schema &&
schema.type === "string" &&
schema?.widget === "codeeditor"
) {
inputSchema = yup.object({
...defaultValidation,
value: yup.string().when("fromUpstream", ([fromUpstream]) => {
Expand All @@ -160,7 +179,7 @@ function getValidationValueBySchemaType(schema: any, required: boolean) {
return required ? yup.string().required() : yup.string();
}),
});
} else if (schema.type === "string" && !schema.format) {
} else if ("type" in schema && schema.type === "string" && !schema.format) {
inputSchema = yup.object({
...defaultValidation,
value: yup.string().when("fromUpstream", ([fromUpstream]) => {
Expand All @@ -170,21 +189,29 @@ function getValidationValueBySchemaType(schema: any, required: boolean) {
return required ? yup.string().required() : yup.string().nullable();
}),
});
} else if (schema.type === "object") {
} else if ("type" in schema && schema.type === "object") {
inputSchema = validationObject();
} else {
inputSchema = yup.mixed().notRequired();
inputSchema = yup.object({
...defaultValidation,
value: yup.string().when("fromUpstream", ([fromUpstream]) => {
if (fromUpstream) {
return yup.mixed().notRequired();
}
return required ? yup.string().required() : yup.string().nullable();
}),
});
}

return inputSchema;
}

export function createInputsSchemaValidation(schema: any) {
export function createInputsSchemaValidation(schema: Schema) {
if (!schema?.properties) {
return yup.mixed().notRequired();
}

const requiredFields = schema?.required || [];
const requiredFields = schema?.required ?? [];

const validationSchema = Object.entries(schema.properties).reduce(
(acc, cur: [string, any]) => {
Expand All @@ -197,20 +224,13 @@ export function createInputsSchemaValidation(schema: any) {
const subItemSchemaName = subSchema.items.$ref.split("/").pop();
subItemSchema = schema.$defs?.[subItemSchemaName];
}

const required = true; // for arrays, we always require the value
inputSchema = yup.object({
...defaultValidation,
value: yup.array().when("fromUpstream", (fromUpstream) => {
if (fromUpstream) {
return yup.mixed().notRequired();
}

return yup
.array()
.of(
getValidationValueBySchemaType(subItemSchema, required) as any,
);
}),
value: yup
.array()
.of(getValidationValueBySchemaType(subItemSchema, required) as any),
});
} else {
const required = requiredFields.includes(key);
Expand Down
7 changes: 6 additions & 1 deletion rest/services/workflow_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -647,7 +647,12 @@ def list_workflow_runs(self, workflow_id: int, page: int, page_size: int):

data = []
for run in dag_runs:
#duration = run.get('end_date') - run.get('start_date')
if run.get('end_date') is None or run.get('start_date') is None:
run['duration_in_seconds'] = None
data.append(
GetWorkflowRunsResponseData(**run)
)
continue
end_date_dt = datetime.fromisoformat(run.get('end_date'))
start_date_dt = datetime.fromisoformat(run.get('start_date'))
duration = end_date_dt - start_date_dt
Expand Down

0 comments on commit 99fe800

Please sign in to comment.