Unify parameter creation dialog with credential type option (SKY-7768) (#4621)

This commit is contained in:
Celal Zamanoglu
2026-02-03 23:45:52 +03:00
committed by GitHub
parent 10fa135aa1
commit 7e35514924
2 changed files with 154 additions and 122 deletions

View File

@@ -42,6 +42,7 @@ type Props = {
const workflowParameterTypeOptions = [ const workflowParameterTypeOptions = [
{ label: "string", value: WorkflowParameterValueType.String }, { label: "string", value: WorkflowParameterValueType.String },
{ label: "credential", value: "credential" },
{ label: "float", value: WorkflowParameterValueType.Float }, { label: "float", value: WorkflowParameterValueType.Float },
{ label: "integer", value: WorkflowParameterValueType.Integer }, { label: "integer", value: WorkflowParameterValueType.Integer },
{ label: "boolean", value: WorkflowParameterValueType.Boolean }, { label: "boolean", value: WorkflowParameterValueType.Boolean },
@@ -52,6 +53,11 @@ const workflowParameterTypeOptions = [
type CredentialDataType = "password" | "secret" | "creditCard"; type CredentialDataType = "password" | "secret" | "creditCard";
type CredentialSource = "bitwarden" | "skyvern" | "onepassword" | "azurevault"; 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 // Determine available sources based on credential data type
function getAvailableSourcesForDataType( function getAvailableSourcesForDataType(
dataType: CredentialDataType, 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"; const prefix = isEdit ? "Edit" : "Add";
if (type === "workflow" && !isEdit) {
// Unified add mode
return `${prefix} Parameter`;
}
if (type === "workflow") { if (type === "workflow") {
return `${prefix} Input Parameter`; return `${prefix} Input Parameter`;
} }
if (type === "credential") { if (type === "credential" || (!isEdit && isCredentialSelected)) {
return `${prefix} Credential Parameter`; return `${prefix} Credential Parameter`;
} }
return `${prefix} Context Parameter`; return `${prefix} Context Parameter`;
@@ -103,8 +117,9 @@ function detectInitialCredentialDataType(
// Helper to detect initial credential source from existing parameter // Helper to detect initial credential source from existing parameter
function detectInitialCredentialSource( function detectInitialCredentialSource(
initialValues: ParametersState[number] | undefined, initialValues: ParametersState[number] | undefined,
isCloud: boolean,
): CredentialSource { ): CredentialSource {
if (!initialValues) return "bitwarden"; if (!initialValues) return isCloud ? "skyvern" : "bitwarden";
if (initialValues.parameterType === "secret") return "bitwarden"; if (initialValues.parameterType === "secret") return "bitwarden";
if (initialValues.parameterType === "creditCardData") return "bitwarden"; if (initialValues.parameterType === "creditCardData") return "bitwarden";
@@ -116,7 +131,7 @@ function detectInitialCredentialSource(
if (parameterIsAzureVaultCredential(initialValues)) return "azurevault"; if (parameterIsAzureVaultCredential(initialValues)) return "azurevault";
} }
return "bitwarden"; return isCloud ? "skyvern" : "bitwarden";
} }
function WorkflowParameterEditPanel({ function WorkflowParameterEditPanel({
@@ -156,7 +171,7 @@ function WorkflowParameterEditPanel({
detectInitialCredentialDataType(initialValues), detectInitialCredentialDataType(initialValues),
); );
const [credentialSource, setCredentialSource] = useState<CredentialSource>( const [credentialSource, setCredentialSource] = useState<CredentialSource>(
detectInitialCredentialSource(initialValues), detectInitialCredentialSource(initialValues, isCloud),
); );
const [urlParameterKey, setUrlParameterKey] = useState( const [urlParameterKey, setUrlParameterKey] = useState(
@@ -172,12 +187,13 @@ function WorkflowParameterEditPanel({
? initialValues?.collectionId ?? "" ? initialValues?.collectionId ?? ""
: "", : "",
); );
const [parameterType, setParameterType] = const [parameterType, setParameterType] = useState<ParameterTypeSelection>(
useState<WorkflowParameterValueType>( type === "credential"
initialValues?.parameterType === "workflow" ? "credential"
: initialValues?.parameterType === "workflow"
? initialValues.dataType ? initialValues.dataType
: "string", : "string",
); );
const [defaultValueState, setDefaultValueState] = useState<{ const [defaultValueState, setDefaultValueState] = useState<{
hasDefaultValue: boolean; hasDefaultValue: boolean;
@@ -261,32 +277,38 @@ function WorkflowParameterEditPanel({
isCloud, 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 // Determine what fields to show based on credential data type and source
const showBitwardenPasswordFields = const showBitwardenPasswordFields =
type === "credential" && showCredentialFields &&
credentialDataType === "password" && credentialDataType === "password" &&
credentialSource === "bitwarden"; credentialSource === "bitwarden";
const showBitwardenSecretFields = const showBitwardenSecretFields =
type === "credential" && showCredentialFields &&
credentialDataType === "secret" && credentialDataType === "secret" &&
credentialSource === "bitwarden"; credentialSource === "bitwarden";
const showBitwardenCreditCardFields = const showBitwardenCreditCardFields =
type === "credential" && showCredentialFields &&
credentialDataType === "creditCard" && credentialDataType === "creditCard" &&
credentialSource === "bitwarden"; credentialSource === "bitwarden";
const showOnePasswordFields = const showOnePasswordFields =
type === "credential" && credentialSource === "onepassword"; showCredentialFields && credentialSource === "onepassword";
const showAzureVaultFields = const showAzureVaultFields =
type === "credential" && credentialSource === "azurevault"; showCredentialFields && credentialSource === "azurevault";
const showSkyvernCredentialSelector = const showSkyvernCredentialSelector =
type === "credential" && credentialSource === "skyvern" && isCloud; showCredentialFields && credentialSource === "skyvern" && isCloud;
return ( return (
<ScrollArea> <ScrollArea>
<ScrollAreaViewport className="max-h-[500px]"> <ScrollAreaViewport className="max-h-[500px]">
<div className="space-y-4 p-1 px-4"> <div className="space-y-4 p-1 px-4">
<header className="flex items-center justify-between"> <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} /> <Cross2Icon className="h-6 w-6 cursor-pointer" onClick={onClose} />
</header> </header>
<div className="space-y-1"> <div className="space-y-1">
@@ -312,15 +334,42 @@ function WorkflowParameterEditPanel({
<Select <Select
value={parameterType} value={parameterType}
onValueChange={(value) => { onValueChange={(value) => {
setParameterType(value as WorkflowParameterValueType); const newValue = value as ParameterTypeSelection;
setDefaultValueState((state) => { const wasCredential = parameterType === "credential";
return { const isNowCredential = newValue === "credential";
...state,
defaultValue: getDefaultValueForParameterType( setParameterType(newValue);
value as WorkflowParameterValueType,
), // 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"> <SelectTrigger className="w-full">
@@ -328,72 +377,84 @@ function WorkflowParameterEditPanel({
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
{workflowParameterTypeOptions.map((option) => ( {workflowParameterTypeOptions
<SelectItem key={option.value} value={option.value}> .filter((option) => {
{option.label} // In edit mode, don't show credential option
</SelectItem> if (isEditMode && option.value === "credential") {
))} return false;
}
return true;
})
.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectGroup> </SelectGroup>
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
<div className="space-y-4"> {/* Default value section - only for non-credential types */}
<div className="flex items-center gap-2"> {!isCredentialSelected && (
<Checkbox <div className="space-y-4">
checked={defaultValueState.hasDefaultValue} <div className="flex items-center gap-2">
onCheckedChange={(checked) => { <Checkbox
if (!checked) { checked={defaultValueState.hasDefaultValue}
onCheckedChange={(checked) => {
if (!checked) {
setDefaultValueState({
hasDefaultValue: false,
defaultValue: null,
});
return;
}
setDefaultValueState({ setDefaultValueState({
hasDefaultValue: false, hasDefaultValue: true,
defaultValue: null, defaultValue: getDefaultValueForParameterType(
parameterType as WorkflowParameterValueType,
),
}); });
return; }}
} />
setDefaultValueState({ <Label className="text-xs text-slate-300">
hasDefaultValue: true, Use Default Value
defaultValue: </Label>
getDefaultValueForParameterType(parameterType), </div>
}); {defaultValueState.hasDefaultValue && (
}} <WorkflowParameterInput
/> onChange={(value) => {
<Label className="text-xs text-slate-300"> if (
Use Default Value parameterType === "file_url" &&
</Label> typeof value === "object" &&
</div> value &&
{defaultValueState.hasDefaultValue && ( "s3uri" in value
<WorkflowParameterInput ) {
onChange={(value) => { setDefaultValueState((state) => {
if ( return {
parameterType === "file_url" && ...state,
typeof value === "object" && defaultValue: value.s3uri,
value && };
"s3uri" in value });
) { return;
}
setDefaultValueState((state) => { setDefaultValueState((state) => {
return { return {
...state, ...state,
defaultValue: value.s3uri, defaultValue: value,
}; };
}); });
return; }}
} type={parameterType as WorkflowParameterValueType}
setDefaultValueState((state) => { value={defaultValueState.defaultValue}
return { />
...state, )}
defaultValue: value, </div>
}; )}
});
}}
type={parameterType}
value={defaultValueState.defaultValue}
/>
)}
</div>
</> </>
)} )}
{/* Credential Parameter - Unified Flow */} {/* Credential Parameter - Unified Flow */}
{type === "credential" && ( {showCredentialFields && (
<> <>
{/* Step 1: Credential Type */} {/* Step 1: Credential Type */}
<div className="space-y-1"> <div className="space-y-1">
@@ -717,8 +778,8 @@ function WorkflowParameterEditPanel({
return; return;
} }
// Handle workflow parameters // Handle workflow parameters (non-credential)
if (type === "workflow") { if (type === "workflow" && !isCredentialSelected) {
if ( if (
parameterType === "json" && parameterType === "json" &&
typeof defaultValueState.defaultValue === "string" typeof defaultValueState.defaultValue === "string"
@@ -763,7 +824,7 @@ function WorkflowParameterEditPanel({
onSave({ onSave({
key, key,
parameterType: "workflow", parameterType: "workflow",
dataType: parameterType, dataType: parameterType as WorkflowParameterValueType,
description, description,
defaultValue: defaultValueState.hasDefaultValue defaultValue: defaultValueState.hasDefaultValue
? defaultValue ? defaultValue
@@ -792,7 +853,7 @@ function WorkflowParameterEditPanel({
} }
// Handle credential parameters based on type + source combination // Handle credential parameters based on type + source combination
if (type === "credential") { if (type === "credential" || isCredentialSelected) {
// Skyvern managed credentials // Skyvern managed credentials
if (credentialSource === "skyvern") { if (credentialSource === "skyvern") {
if (!credentialId) { if (!credentialId) {

View File

@@ -4,14 +4,6 @@ import { WorkflowParameterEditPanel } from "./WorkflowParameterEditPanel";
import { MixerVerticalIcon, PlusIcon } from "@radix-ui/react-icons"; import { MixerVerticalIcon, PlusIcon } from "@radix-ui/react-icons";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { GarbageIcon } from "@/components/icons/GarbageIcon"; 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 { useNodes, useReactFlow } from "@xyflow/react";
import { useWorkflowHasChangesStore } from "@/store/WorkflowHasChangesStore"; import { useWorkflowHasChangesStore } from "@/store/WorkflowHasChangesStore";
import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore"; import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore";
@@ -106,40 +98,19 @@ function WorkflowParametersPanel({ onMouseDownCapture }: Props) {
prompted to fill them in before running your workflow. prompted to fill them in before running your workflow.
</span> </span>
</header> </header>
<DropdownMenu> <Button
<DropdownMenuTrigger asChild> className="w-full"
<Button className="w-full"> onClick={() => {
<PlusIcon className="mr-2 h-6 w-6" /> setOperationPanelState({
Add Parameter active: true,
</Button> operation: "add",
</DropdownMenuTrigger> type: WorkflowEditorParameterTypes.Workflow,
<DropdownMenuContent className="w-60"> });
<DropdownMenuLabel>Add Parameter</DropdownMenuLabel> }}
<DropdownMenuSeparator /> >
<DropdownMenuItem <PlusIcon className="mr-2 h-6 w-6" />
onClick={() => { Add Parameter
setOperationPanelState({ </Button>
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>
<ScrollArea> <ScrollArea>
<ScrollAreaViewport className="max-h-96"> <ScrollAreaViewport className="max-h-96">