Unify parameter creation dialog with credential type option (SKY-7768) (#4621)
This commit is contained in:
@@ -42,6 +42,7 @@ type Props = {
|
||||
|
||||
const workflowParameterTypeOptions = [
|
||||
{ label: "string", value: WorkflowParameterValueType.String },
|
||||
{ label: "credential", value: "credential" },
|
||||
{ label: "float", value: WorkflowParameterValueType.Float },
|
||||
{ label: "integer", value: WorkflowParameterValueType.Integer },
|
||||
{ label: "boolean", value: WorkflowParameterValueType.Boolean },
|
||||
@@ -52,6 +53,11 @@ const workflowParameterTypeOptions = [
|
||||
type CredentialDataType = "password" | "secret" | "creditCard";
|
||||
type CredentialSource = "bitwarden" | "skyvern" | "onepassword" | "azurevault";
|
||||
|
||||
// When selecting from the Value Type dropdown, "credential" is a special value that triggers
|
||||
// credential-specific UI. This is separate from WorkflowParameterValueType which only includes
|
||||
// data types like string, integer, etc.
|
||||
type ParameterTypeSelection = WorkflowParameterValueType | "credential";
|
||||
|
||||
// Determine available sources based on credential data type
|
||||
function getAvailableSourcesForDataType(
|
||||
dataType: CredentialDataType,
|
||||
@@ -79,12 +85,20 @@ function getAvailableSourcesForDataType(
|
||||
}
|
||||
}
|
||||
|
||||
function header(type: WorkflowEditorParameterType, isEdit: boolean) {
|
||||
function header(
|
||||
type: WorkflowEditorParameterType,
|
||||
isEdit: boolean,
|
||||
isCredentialSelected: boolean,
|
||||
) {
|
||||
const prefix = isEdit ? "Edit" : "Add";
|
||||
if (type === "workflow" && !isEdit) {
|
||||
// Unified add mode
|
||||
return `${prefix} Parameter`;
|
||||
}
|
||||
if (type === "workflow") {
|
||||
return `${prefix} Input Parameter`;
|
||||
}
|
||||
if (type === "credential") {
|
||||
if (type === "credential" || (!isEdit && isCredentialSelected)) {
|
||||
return `${prefix} Credential Parameter`;
|
||||
}
|
||||
return `${prefix} Context Parameter`;
|
||||
@@ -103,8 +117,9 @@ function detectInitialCredentialDataType(
|
||||
// Helper to detect initial credential source from existing parameter
|
||||
function detectInitialCredentialSource(
|
||||
initialValues: ParametersState[number] | undefined,
|
||||
isCloud: boolean,
|
||||
): CredentialSource {
|
||||
if (!initialValues) return "bitwarden";
|
||||
if (!initialValues) return isCloud ? "skyvern" : "bitwarden";
|
||||
|
||||
if (initialValues.parameterType === "secret") return "bitwarden";
|
||||
if (initialValues.parameterType === "creditCardData") return "bitwarden";
|
||||
@@ -116,7 +131,7 @@ function detectInitialCredentialSource(
|
||||
if (parameterIsAzureVaultCredential(initialValues)) return "azurevault";
|
||||
}
|
||||
|
||||
return "bitwarden";
|
||||
return isCloud ? "skyvern" : "bitwarden";
|
||||
}
|
||||
|
||||
function WorkflowParameterEditPanel({
|
||||
@@ -156,7 +171,7 @@ function WorkflowParameterEditPanel({
|
||||
detectInitialCredentialDataType(initialValues),
|
||||
);
|
||||
const [credentialSource, setCredentialSource] = useState<CredentialSource>(
|
||||
detectInitialCredentialSource(initialValues),
|
||||
detectInitialCredentialSource(initialValues, isCloud),
|
||||
);
|
||||
|
||||
const [urlParameterKey, setUrlParameterKey] = useState(
|
||||
@@ -172,12 +187,13 @@ function WorkflowParameterEditPanel({
|
||||
? initialValues?.collectionId ?? ""
|
||||
: "",
|
||||
);
|
||||
const [parameterType, setParameterType] =
|
||||
useState<WorkflowParameterValueType>(
|
||||
initialValues?.parameterType === "workflow"
|
||||
const [parameterType, setParameterType] = useState<ParameterTypeSelection>(
|
||||
type === "credential"
|
||||
? "credential"
|
||||
: initialValues?.parameterType === "workflow"
|
||||
? initialValues.dataType
|
||||
: "string",
|
||||
);
|
||||
);
|
||||
|
||||
const [defaultValueState, setDefaultValueState] = useState<{
|
||||
hasDefaultValue: boolean;
|
||||
@@ -261,32 +277,38 @@ function WorkflowParameterEditPanel({
|
||||
isCloud,
|
||||
);
|
||||
|
||||
// Check if we're in unified add mode and credential is selected
|
||||
const isCredentialSelected = parameterType === "credential";
|
||||
const showCredentialFields =
|
||||
type === "credential" ||
|
||||
(type === "workflow" && !isEditMode && isCredentialSelected);
|
||||
|
||||
// Determine what fields to show based on credential data type and source
|
||||
const showBitwardenPasswordFields =
|
||||
type === "credential" &&
|
||||
showCredentialFields &&
|
||||
credentialDataType === "password" &&
|
||||
credentialSource === "bitwarden";
|
||||
const showBitwardenSecretFields =
|
||||
type === "credential" &&
|
||||
showCredentialFields &&
|
||||
credentialDataType === "secret" &&
|
||||
credentialSource === "bitwarden";
|
||||
const showBitwardenCreditCardFields =
|
||||
type === "credential" &&
|
||||
showCredentialFields &&
|
||||
credentialDataType === "creditCard" &&
|
||||
credentialSource === "bitwarden";
|
||||
const showOnePasswordFields =
|
||||
type === "credential" && credentialSource === "onepassword";
|
||||
showCredentialFields && credentialSource === "onepassword";
|
||||
const showAzureVaultFields =
|
||||
type === "credential" && credentialSource === "azurevault";
|
||||
showCredentialFields && credentialSource === "azurevault";
|
||||
const showSkyvernCredentialSelector =
|
||||
type === "credential" && credentialSource === "skyvern" && isCloud;
|
||||
showCredentialFields && credentialSource === "skyvern" && isCloud;
|
||||
|
||||
return (
|
||||
<ScrollArea>
|
||||
<ScrollAreaViewport className="max-h-[500px]">
|
||||
<div className="space-y-4 p-1 px-4">
|
||||
<header className="flex items-center justify-between">
|
||||
<span>{header(type, isEditMode)}</span>
|
||||
<span>{header(type, isEditMode, isCredentialSelected)}</span>
|
||||
<Cross2Icon className="h-6 w-6 cursor-pointer" onClick={onClose} />
|
||||
</header>
|
||||
<div className="space-y-1">
|
||||
@@ -312,15 +334,42 @@ function WorkflowParameterEditPanel({
|
||||
<Select
|
||||
value={parameterType}
|
||||
onValueChange={(value) => {
|
||||
setParameterType(value as WorkflowParameterValueType);
|
||||
setDefaultValueState((state) => {
|
||||
return {
|
||||
...state,
|
||||
defaultValue: getDefaultValueForParameterType(
|
||||
value as WorkflowParameterValueType,
|
||||
),
|
||||
};
|
||||
});
|
||||
const newValue = value as ParameterTypeSelection;
|
||||
const wasCredential = parameterType === "credential";
|
||||
const isNowCredential = newValue === "credential";
|
||||
|
||||
setParameterType(newValue);
|
||||
|
||||
// Clear credential-specific state when switching away from credential
|
||||
// to prevent stale data if user switches back
|
||||
if (wasCredential && !isNowCredential) {
|
||||
setCredentialId("");
|
||||
setCredentialDataType("password");
|
||||
setCredentialSource(isCloud ? "skyvern" : "bitwarden");
|
||||
setBitwardenLoginCredentialItemId("");
|
||||
setBitwardenCollectionId("");
|
||||
setUrlParameterKey("");
|
||||
setIdentityKey("");
|
||||
setIdentityFields("");
|
||||
setSensitiveInformationItemId("");
|
||||
setOpVaultId("");
|
||||
setOpItemId("");
|
||||
setAzureVaultName("");
|
||||
setAzureUsernameKey("");
|
||||
setAzurePasswordKey("");
|
||||
setAzureTotpKey("");
|
||||
}
|
||||
|
||||
if (!isNowCredential) {
|
||||
setDefaultValueState((state) => {
|
||||
return {
|
||||
...state,
|
||||
defaultValue: getDefaultValueForParameterType(
|
||||
newValue as WorkflowParameterValueType,
|
||||
),
|
||||
};
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
@@ -328,72 +377,84 @@ function WorkflowParameterEditPanel({
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
{workflowParameterTypeOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
{workflowParameterTypeOptions
|
||||
.filter((option) => {
|
||||
// In edit mode, don't show credential option
|
||||
if (isEditMode && option.value === "credential") {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.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) {
|
||||
{/* Default value section - only for non-credential types */}
|
||||
{!isCredentialSelected && (
|
||||
<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: false,
|
||||
defaultValue: null,
|
||||
hasDefaultValue: true,
|
||||
defaultValue: getDefaultValueForParameterType(
|
||||
parameterType as WorkflowParameterValueType,
|
||||
),
|
||||
});
|
||||
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
|
||||
) {
|
||||
}}
|
||||
/>
|
||||
<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.s3uri,
|
||||
defaultValue: value,
|
||||
};
|
||||
});
|
||||
return;
|
||||
}
|
||||
setDefaultValueState((state) => {
|
||||
return {
|
||||
...state,
|
||||
defaultValue: value,
|
||||
};
|
||||
});
|
||||
}}
|
||||
type={parameterType}
|
||||
value={defaultValueState.defaultValue}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
}}
|
||||
type={parameterType as WorkflowParameterValueType}
|
||||
value={defaultValueState.defaultValue}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Credential Parameter - Unified Flow */}
|
||||
{type === "credential" && (
|
||||
{showCredentialFields && (
|
||||
<>
|
||||
{/* Step 1: Credential Type */}
|
||||
<div className="space-y-1">
|
||||
@@ -717,8 +778,8 @@ function WorkflowParameterEditPanel({
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle workflow parameters
|
||||
if (type === "workflow") {
|
||||
// Handle workflow parameters (non-credential)
|
||||
if (type === "workflow" && !isCredentialSelected) {
|
||||
if (
|
||||
parameterType === "json" &&
|
||||
typeof defaultValueState.defaultValue === "string"
|
||||
@@ -763,7 +824,7 @@ function WorkflowParameterEditPanel({
|
||||
onSave({
|
||||
key,
|
||||
parameterType: "workflow",
|
||||
dataType: parameterType,
|
||||
dataType: parameterType as WorkflowParameterValueType,
|
||||
description,
|
||||
defaultValue: defaultValueState.hasDefaultValue
|
||||
? defaultValue
|
||||
@@ -792,7 +853,7 @@ function WorkflowParameterEditPanel({
|
||||
}
|
||||
|
||||
// Handle credential parameters based on type + source combination
|
||||
if (type === "credential") {
|
||||
if (type === "credential" || isCredentialSelected) {
|
||||
// Skyvern managed credentials
|
||||
if (credentialSource === "skyvern") {
|
||||
if (!credentialId) {
|
||||
|
||||
@@ -4,14 +4,6 @@ import { WorkflowParameterEditPanel } from "./WorkflowParameterEditPanel";
|
||||
import { MixerVerticalIcon, PlusIcon } from "@radix-ui/react-icons";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { GarbageIcon } from "@/components/icons/GarbageIcon";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { useNodes, useReactFlow } from "@xyflow/react";
|
||||
import { useWorkflowHasChangesStore } from "@/store/WorkflowHasChangesStore";
|
||||
import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore";
|
||||
@@ -106,40 +98,19 @@ function WorkflowParametersPanel({ onMouseDownCapture }: Props) {
|
||||
prompted to fill them in before running your workflow.
|
||||
</span>
|
||||
</header>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button className="w-full">
|
||||
<PlusIcon className="mr-2 h-6 w-6" />
|
||||
Add Parameter
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-60">
|
||||
<DropdownMenuLabel>Add Parameter</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setOperationPanelState({
|
||||
active: true,
|
||||
operation: "add",
|
||||
type: WorkflowEditorParameterTypes.Workflow,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Input Parameter
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setOperationPanelState({
|
||||
active: true,
|
||||
operation: "add",
|
||||
type: WorkflowEditorParameterTypes.Credential,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Credential Parameter
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Button
|
||||
className="w-full"
|
||||
onClick={() => {
|
||||
setOperationPanelState({
|
||||
active: true,
|
||||
operation: "add",
|
||||
type: WorkflowEditorParameterTypes.Workflow,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<PlusIcon className="mr-2 h-6 w-6" />
|
||||
Add Parameter
|
||||
</Button>
|
||||
|
||||
<ScrollArea>
|
||||
<ScrollAreaViewport className="max-h-96">
|
||||
|
||||
Reference in New Issue
Block a user