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" accept=".csv"
className="hidden" className="hidden"
/> />
<div className="flex max-w-full gap-2 truncate"> <div className="flex max-w-full gap-2 px-2">
{uploadFileMutation.isPending && ( {uploadFileMutation.isPending && (
<ReloadIcon className="mr-2 h-4 w-4 animate-spin" /> <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 { Checkbox } from "@/components/ui/checkbox";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { CodeEditor } from "./components/CodeEditor"; import { CodeEditor } from "./components/CodeEditor";
import { AutoResizingTextarea } from "@/components/AutoResizingTextarea/AutoResizingTextarea";
import { Label } from "@/components/ui/label";
type Props = { type Props = {
type: WorkflowParameterValueType; type: WorkflowParameterValueType;
@@ -26,7 +28,7 @@ function WorkflowParameterInput({ type, value, onChange }: Props) {
if (type === "string") { if (type === "string") {
return ( return (
<Input <AutoResizingTextarea
value={value as string} value={value as string}
onChange={(e) => onChange(e.target.value)} onChange={(e) => onChange(e.target.value)}
/> />
@@ -55,12 +57,16 @@ function WorkflowParameterInput({ type, value, onChange }: Props) {
} }
if (type === "boolean") { if (type === "boolean") {
const checked = typeof value === "boolean" ? value : Boolean(value);
return ( return (
<Checkbox <div className="flex items-center gap-2">
checked={value as boolean} <Checkbox
onCheckedChange={(checked) => onChange(checked)} checked={checked}
className="block" onCheckedChange={(checked) => onChange(checked)}
/> className="block"
/>
<Label>{value ? "True" : "False"}</Label>
</div>
); );
} }

View File

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

View File

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

View File

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

View File

@@ -43,7 +43,7 @@ function WorkflowHeader({
inputClassName="text-3xl" inputClassName="text-3xl"
/> />
</div> </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> <TooltipProvider>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>

View File

@@ -13,6 +13,10 @@ import {
} from "@/components/ui/select"; } from "@/components/ui/select";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { ParametersState } from "../FlowRenderer"; 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 Props = {
type: "workflow" | "credential"; type: "workflow" | "credential";
@@ -35,6 +39,13 @@ function WorkflowParameterAddPanel({ type, onClose, onSave }: Props) {
const [collectionId, setCollectionId] = useState(""); const [collectionId, setCollectionId] = useState("");
const [parameterType, setParameterType] = const [parameterType, setParameterType] =
useState<WorkflowParameterValueType>("string"); useState<WorkflowParameterValueType>("string");
const [defaultValueState, setDefaultValueState] = useState<{
hasDefaultValue: boolean;
defaultValue: unknown;
}>({
hasDefaultValue: false,
defaultValue: null,
});
return ( return (
<div className="space-y-4"> <div className="space-y-4">
@@ -56,28 +67,90 @@ function WorkflowParameterAddPanel({ type, onClose, onSave }: Props) {
/> />
</div> </div>
{type === "workflow" && ( {type === "workflow" && (
<div className="space-y-1"> <>
<Label className="text-xs">Value Type</Label> <div className="space-y-1">
<Select <Label className="text-xs">Value Type</Label>
value={parameterType} <Select
onValueChange={(value) => value={parameterType}
setParameterType(value as WorkflowParameterValueType) onValueChange={(value) => {
} setParameterType(value as WorkflowParameterValueType);
> setDefaultValueState((state) => {
<SelectTrigger className="w-full"> return {
<SelectValue placeholder="Select a type" /> ...state,
</SelectTrigger> defaultValue: getDefaultValueForParameterType(
<SelectContent> value as WorkflowParameterValueType,
<SelectGroup> ),
{workflowParameterTypeOptions.map((option) => ( };
<SelectItem key={option.value} value={option.value}> });
{option.label} }}
</SelectItem> >
))} <SelectTrigger className="w-full">
</SelectGroup> <SelectValue placeholder="Select a type" />
</SelectContent> </SelectTrigger>
</Select> <SelectContent>
</div> <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" && ( {type === "credential" && (
<> <>
@@ -101,11 +174,32 @@ function WorkflowParameterAddPanel({ type, onClose, onSave }: Props) {
<Button <Button
onClick={() => { onClick={() => {
if (type === "workflow") { 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({ onSave({
key, key,
parameterType: "workflow", parameterType: "workflow",
dataType: parameterType, dataType: parameterType,
description, description,
defaultValue: defaultValue,
}); });
} }
if (type === "credential") { if (type === "credential") {

View File

@@ -13,6 +13,10 @@ import {
} from "@/components/ui/select"; } from "@/components/ui/select";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { ParametersState } from "../FlowRenderer"; 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 Props = {
type: "workflow" | "credential"; type: "workflow" | "credential";
@@ -56,6 +60,21 @@ function WorkflowParameterEditPanel({
: "string", : "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 ( return (
<div className="space-y-4"> <div className="space-y-4">
<header className="flex items-center justify-between"> <header className="flex items-center justify-between">
@@ -76,28 +95,90 @@ function WorkflowParameterEditPanel({
/> />
</div> </div>
{type === "workflow" && ( {type === "workflow" && (
<div className="space-y-1"> <>
<Label className="text-xs">Value Type</Label> <div className="space-y-1">
<Select <Label className="text-xs">Value Type</Label>
value={parameterType} <Select
onValueChange={(value) => value={parameterType}
setParameterType(value as WorkflowParameterValueType) onValueChange={(value) => {
} setParameterType(value as WorkflowParameterValueType);
> setDefaultValueState((state) => {
<SelectTrigger className="w-full"> return {
<SelectValue placeholder="Select a type" /> ...state,
</SelectTrigger> defaultValue: getDefaultValueForParameterType(
<SelectContent> value as WorkflowParameterValueType,
<SelectGroup> ),
{workflowParameterTypeOptions.map((option) => ( };
<SelectItem key={option.value} value={option.value}> });
{option.label} }}
</SelectItem> >
))} <SelectTrigger className="w-full">
</SelectGroup> <SelectValue placeholder="Select a type" />
</SelectContent> </SelectTrigger>
</Select> <SelectContent>
</div> <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" && ( {type === "credential" && (
<> <>
@@ -121,11 +202,32 @@ function WorkflowParameterEditPanel({
<Button <Button
onClick={() => { onClick={() => {
if (type === "workflow") { 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({ onSave({
key, key,
parameterType: "workflow", parameterType: "workflow",
dataType: parameterType, dataType: parameterType,
description, description,
defaultValue: defaultValue,
}); });
} }
if (type === "credential") { if (type === "credential") {

View File

@@ -1,7 +1,10 @@
import Dagre from "@dagrejs/dagre"; import Dagre from "@dagrejs/dagre";
import { Edge } from "@xyflow/react"; import { Edge } from "@xyflow/react";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import type { WorkflowBlock } from "../types/workflowTypes"; import type {
WorkflowBlock,
WorkflowParameterValueType,
} from "../types/workflowTypes";
import { BlockYAML, ParameterYAML } from "../types/workflowYamlTypes"; import { BlockYAML, ParameterYAML } from "../types/workflowYamlTypes";
import { import {
EMAIL_BLOCK_SENDER, EMAIL_BLOCK_SENDER,
@@ -764,6 +767,29 @@ function getLabelForExistingNode(label: string, existingLabels: Array<string>) {
return label; 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 { export {
createNode, createNode,
generateNodeData, generateNodeData,
@@ -778,4 +804,5 @@ export {
getLabelForExistingNode, getLabelForExistingNode,
isOutputParameterKey, isOutputParameterKey,
getBlockNameOfOutputParameterKey, getBlockNameOfOutputParameterKey,
getDefaultValueForParameterType,
}; };

View File

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