Add ability to add default values to workflow parameters (#865)

This commit is contained in:
Kerem Yilmaz
2024-09-20 04:53:51 -07:00
committed by GitHub
parent df463e042b
commit e9184bc399
10 changed files with 295 additions and 59 deletions

View File

@@ -150,7 +150,7 @@ function FileUpload({ value, onChange }: Props) {
accept=".csv"
className="hidden"
/>
<div className="flex max-w-full gap-2 truncate">
<div className="flex max-w-full gap-2 px-2">
{uploadFileMutation.isPending && (
<ReloadIcon className="mr-2 h-4 w-4 animate-spin" />
)}

View File

@@ -3,6 +3,8 @@ import { FileInputValue, FileUpload } from "@/components/FileUpload";
import { Checkbox } from "@/components/ui/checkbox";
import { Input } from "@/components/ui/input";
import { CodeEditor } from "./components/CodeEditor";
import { AutoResizingTextarea } from "@/components/AutoResizingTextarea/AutoResizingTextarea";
import { Label } from "@/components/ui/label";
type Props = {
type: WorkflowParameterValueType;
@@ -26,7 +28,7 @@ function WorkflowParameterInput({ type, value, onChange }: Props) {
if (type === "string") {
return (
<Input
<AutoResizingTextarea
value={value as string}
onChange={(e) => onChange(e.target.value)}
/>
@@ -55,12 +57,16 @@ function WorkflowParameterInput({ type, value, onChange }: Props) {
}
if (type === "boolean") {
const checked = typeof value === "boolean" ? value : Boolean(value);
return (
<Checkbox
checked={value as boolean}
onCheckedChange={(checked) => onChange(checked)}
className="block"
/>
<div className="flex items-center gap-2">
<Checkbox
checked={checked}
onCheckedChange={(checked) => onChange(checked)}
className="block"
/>
<Label>{value ? "True" : "False"}</Label>
</div>
);
}

View File

@@ -45,10 +45,6 @@ function WorkflowRunParameters() {
? location.state.data
: workflowParameters?.reduce(
(acc, curr) => {
if (curr.workflow_parameter_type === "file_url") {
acc[curr.key] = null;
return acc;
}
if (curr.workflow_parameter_type === "json") {
if (typeof curr.default_value === "string") {
acc[curr.key] = curr.default_value;
@@ -59,6 +55,13 @@ function WorkflowRunParameters() {
return acc;
}
}
if (
curr.default_value &&
curr.workflow_parameter_type === "boolean"
) {
acc[curr.key] = Boolean(curr.default_value);
return acc;
}
if (curr.default_value) {
acc[curr.key] = curr.default_value;
return acc;

View File

@@ -73,7 +73,9 @@ function convertToParametersYAML(
key: parameter.key,
description: parameter.description || null,
workflow_parameter_type: parameter.dataType,
default_value: null,
...(parameter.defaultValue === null
? {}
: { default_value: parameter.defaultValue }),
};
} else {
return {
@@ -98,6 +100,7 @@ export type ParametersState = Array<
parameterType: "workflow";
dataType: WorkflowParameterValueType;
description?: string;
defaultValue: unknown;
}
| {
key: string;

View File

@@ -59,6 +59,7 @@ function WorkflowEditor() {
key: parameter.key,
parameterType: "workflow",
dataType: parameter.workflow_parameter_type,
defaultValue: parameter.default_value,
};
} else {
return {

View File

@@ -43,7 +43,7 @@ function WorkflowHeader({
inputClassName="text-3xl"
/>
</div>
<div className="flex h-full w-1/3 items-center justify-end gap-4">
<div className="flex h-full items-center justify-end gap-4">
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>

View File

@@ -13,6 +13,10 @@ import {
} from "@/components/ui/select";
import { Button } from "@/components/ui/button";
import { ParametersState } from "../FlowRenderer";
import { WorkflowParameterInput } from "../../WorkflowParameterInput";
import { Checkbox } from "@/components/ui/checkbox";
import { getDefaultValueForParameterType } from "../workflowEditorUtils";
import { toast } from "@/components/ui/use-toast";
type Props = {
type: "workflow" | "credential";
@@ -35,6 +39,13 @@ function WorkflowParameterAddPanel({ type, onClose, onSave }: Props) {
const [collectionId, setCollectionId] = useState("");
const [parameterType, setParameterType] =
useState<WorkflowParameterValueType>("string");
const [defaultValueState, setDefaultValueState] = useState<{
hasDefaultValue: boolean;
defaultValue: unknown;
}>({
hasDefaultValue: false,
defaultValue: null,
});
return (
<div className="space-y-4">
@@ -56,28 +67,90 @@ function WorkflowParameterAddPanel({ type, onClose, onSave }: Props) {
/>
</div>
{type === "workflow" && (
<div className="space-y-1">
<Label className="text-xs">Value Type</Label>
<Select
value={parameterType}
onValueChange={(value) =>
setParameterType(value as WorkflowParameterValueType)
}
>
<SelectTrigger className="w-full">
<SelectValue placeholder="Select a type" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{workflowParameterTypeOptions.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
<>
<div className="space-y-1">
<Label className="text-xs">Value Type</Label>
<Select
value={parameterType}
onValueChange={(value) => {
setParameterType(value as WorkflowParameterValueType);
setDefaultValueState((state) => {
return {
...state,
defaultValue: getDefaultValueForParameterType(
value as WorkflowParameterValueType,
),
};
});
}}
>
<SelectTrigger className="w-full">
<SelectValue placeholder="Select a type" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{workflowParameterTypeOptions.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
<div className="space-y-4">
<div className="flex items-center gap-2">
<Checkbox
checked={defaultValueState.hasDefaultValue}
onCheckedChange={(checked) => {
if (!checked) {
setDefaultValueState({
hasDefaultValue: false,
defaultValue: null,
});
return;
}
setDefaultValueState({
hasDefaultValue: true,
defaultValue:
getDefaultValueForParameterType(parameterType),
});
}}
/>
<Label className="text-xs text-slate-300">
Use Default Value
</Label>
</div>
{defaultValueState.hasDefaultValue && (
<WorkflowParameterInput
onChange={(value) => {
if (
parameterType === "file_url" &&
typeof value === "object" &&
value &&
"s3uri" in value
) {
setDefaultValueState((state) => {
return {
...state,
defaultValue: value.s3uri,
};
});
return;
}
setDefaultValueState((state) => {
return {
...state,
defaultValue: value,
};
});
}}
type={parameterType}
value={defaultValueState.defaultValue}
/>
)}
</div>
</>
)}
{type === "credential" && (
<>
@@ -101,11 +174,32 @@ function WorkflowParameterAddPanel({ type, onClose, onSave }: Props) {
<Button
onClick={() => {
if (type === "workflow") {
if (
parameterType === "json" &&
typeof defaultValueState.defaultValue === "string"
) {
try {
JSON.parse(defaultValueState.defaultValue);
} catch (e) {
toast({
variant: "destructive",
title: "Failed to save parameters",
description: "Invalid JSON for default value",
});
return;
}
}
const defaultValue =
parameterType === "json" &&
typeof defaultValueState.defaultValue === "string"
? JSON.parse(defaultValueState.defaultValue)
: defaultValueState.defaultValue;
onSave({
key,
parameterType: "workflow",
dataType: parameterType,
description,
defaultValue: defaultValue,
});
}
if (type === "credential") {

View File

@@ -13,6 +13,10 @@ import {
} from "@/components/ui/select";
import { Button } from "@/components/ui/button";
import { ParametersState } from "../FlowRenderer";
import { Checkbox } from "@/components/ui/checkbox";
import { getDefaultValueForParameterType } from "../workflowEditorUtils";
import { WorkflowParameterInput } from "../../WorkflowParameterInput";
import { toast } from "@/components/ui/use-toast";
type Props = {
type: "workflow" | "credential";
@@ -56,6 +60,21 @@ function WorkflowParameterEditPanel({
: "string",
);
const [defaultValueState, setDefaultValueState] = useState<{
hasDefaultValue: boolean;
defaultValue: unknown;
}>(
initialValues.parameterType === "workflow"
? {
hasDefaultValue: initialValues.defaultValue !== null,
defaultValue: initialValues.defaultValue ?? null,
}
: {
hasDefaultValue: false,
defaultValue: null,
},
);
return (
<div className="space-y-4">
<header className="flex items-center justify-between">
@@ -76,28 +95,90 @@ function WorkflowParameterEditPanel({
/>
</div>
{type === "workflow" && (
<div className="space-y-1">
<Label className="text-xs">Value Type</Label>
<Select
value={parameterType}
onValueChange={(value) =>
setParameterType(value as WorkflowParameterValueType)
}
>
<SelectTrigger className="w-full">
<SelectValue placeholder="Select a type" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{workflowParameterTypeOptions.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
<>
<div className="space-y-1">
<Label className="text-xs">Value Type</Label>
<Select
value={parameterType}
onValueChange={(value) => {
setParameterType(value as WorkflowParameterValueType);
setDefaultValueState((state) => {
return {
...state,
defaultValue: getDefaultValueForParameterType(
value as WorkflowParameterValueType,
),
};
});
}}
>
<SelectTrigger className="w-full">
<SelectValue placeholder="Select a type" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{workflowParameterTypeOptions.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
<div className="space-y-4">
<div className="flex items-center gap-2">
<Checkbox
checked={defaultValueState.hasDefaultValue}
onCheckedChange={(checked) => {
if (!checked) {
setDefaultValueState({
hasDefaultValue: false,
defaultValue: null,
});
return;
}
setDefaultValueState({
hasDefaultValue: true,
defaultValue:
getDefaultValueForParameterType(parameterType),
});
}}
/>
<Label className="text-xs text-slate-300">
Use Default Value
</Label>
</div>
{defaultValueState.hasDefaultValue && (
<WorkflowParameterInput
onChange={(value) => {
if (
parameterType === "file_url" &&
typeof value === "object" &&
value &&
"s3uri" in value
) {
setDefaultValueState((state) => {
return {
...state,
defaultValue: value,
};
});
return;
}
setDefaultValueState((state) => {
return {
...state,
defaultValue: value,
};
});
}}
type={parameterType}
value={defaultValueState.defaultValue}
/>
)}
</div>
</>
)}
{type === "credential" && (
<>
@@ -121,11 +202,32 @@ function WorkflowParameterEditPanel({
<Button
onClick={() => {
if (type === "workflow") {
if (
parameterType === "json" &&
typeof defaultValueState.defaultValue === "string"
) {
try {
JSON.parse(defaultValueState.defaultValue);
} catch (e) {
toast({
variant: "destructive",
title: "Failed to save parameters",
description: "Invalid JSON for default value",
});
return;
}
}
const defaultValue =
parameterType === "json" &&
typeof defaultValueState.defaultValue === "string"
? JSON.parse(defaultValueState.defaultValue)
: defaultValueState.defaultValue;
onSave({
key,
parameterType: "workflow",
dataType: parameterType,
description,
defaultValue: defaultValue,
});
}
if (type === "credential") {

View File

@@ -1,7 +1,10 @@
import Dagre from "@dagrejs/dagre";
import { Edge } from "@xyflow/react";
import { nanoid } from "nanoid";
import type { WorkflowBlock } from "../types/workflowTypes";
import type {
WorkflowBlock,
WorkflowParameterValueType,
} from "../types/workflowTypes";
import { BlockYAML, ParameterYAML } from "../types/workflowYamlTypes";
import {
EMAIL_BLOCK_SENDER,
@@ -764,6 +767,29 @@ function getLabelForExistingNode(label: string, existingLabels: Array<string>) {
return label;
}
function getDefaultValueForParameterType(
parameterType: WorkflowParameterValueType,
): unknown {
switch (parameterType) {
case "json": {
return "{}";
}
case "string": {
return "";
}
case "boolean": {
return false;
}
case "float":
case "integer": {
return 0;
}
case "file_url": {
return null;
}
}
}
export {
createNode,
generateNodeData,
@@ -778,4 +804,5 @@ export {
getLabelForExistingNode,
isOutputParameterKey,
getBlockNameOfOutputParameterKey,
getDefaultValueForParameterType,
};

View File

@@ -30,7 +30,7 @@ export type ParameterYAMLBase = {
export type WorkflowParameterYAML = ParameterYAMLBase & {
parameter_type: "workflow";
workflow_parameter_type: string;
default_value: string | null;
default_value?: unknown;
};
export type BitwardenLoginCredentialParameterYAML = ParameterYAMLBase & {