From 117b2469e4e4d98e5eac508f3d2df1fa805c9268 Mon Sep 17 00:00:00 2001 From: Stanislav Novosad Date: Thu, 23 Oct 2025 11:02:41 -0600 Subject: [PATCH] Refactoring: merge WorkflowParameterEditPanel and WorkflowParameterAddPanel (#3750) --- .../panels/WorkflowParameterAddPanel.tsx | 615 ------------------ .../panels/WorkflowParameterEditPanel.tsx | 110 ++-- .../editor/panels/WorkflowParametersPanel.tsx | 3 +- skyvern-frontend/vitest.config.ts | 10 +- 4 files changed, 69 insertions(+), 669 deletions(-) delete mode 100644 skyvern-frontend/src/routes/workflows/editor/panels/WorkflowParameterAddPanel.tsx diff --git a/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowParameterAddPanel.tsx b/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowParameterAddPanel.tsx deleted file mode 100644 index 0055dda1..00000000 --- a/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowParameterAddPanel.tsx +++ /dev/null @@ -1,615 +0,0 @@ -import { Button } from "@/components/ui/button"; -import { Checkbox } from "@/components/ui/checkbox"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { ScrollArea, ScrollAreaViewport } from "@/components/ui/scroll-area"; -import { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { toast } from "@/components/ui/use-toast"; -import CloudContext from "@/store/CloudContext"; -import { Cross2Icon } from "@radix-ui/react-icons"; -import { useContext, useState } from "react"; -import { CredentialParameterSourceSelector } from "../../components/CredentialParameterSourceSelector"; -import { SourceParameterKeySelector } from "../../components/SourceParameterKeySelector"; -import { - WorkflowEditorParameterType, - WorkflowParameterValueType, -} from "../../types/workflowTypes"; -import { WorkflowParameterInput } from "../../WorkflowParameterInput"; -import { ParametersState } from "../types"; -import { getDefaultValueForParameterType } from "../workflowEditorUtils"; -import { validateBitwardenLoginCredential } from "./util"; -import { HelpTooltip } from "@/components/HelpTooltip"; - -type Props = { - type: WorkflowEditorParameterType; - onClose: () => void; - onSave: (value: ParametersState[number]) => void; -}; - -const workflowParameterTypeOptions = [ - { label: "string", value: WorkflowParameterValueType.String }, - { label: "float", value: WorkflowParameterValueType.Float }, - { label: "integer", value: WorkflowParameterValueType.Integer }, - { label: "boolean", value: WorkflowParameterValueType.Boolean }, - { label: "file", value: WorkflowParameterValueType.FileURL }, - { label: "JSON", value: WorkflowParameterValueType.JSON }, -]; - -function header(type: WorkflowEditorParameterType) { - if (type === "workflow") { - return "Add Input Parameter"; - } - if (type === "credential") { - return "Add Credential Parameter"; - } - if (type === "secret") { - return "Add Secret Parameter"; - } - if (type === "creditCardData") { - return "Add Credit Card Parameter"; - } - return "Add Context Parameter"; -} - -function WorkflowParameterAddPanel({ type, onClose, onSave }: Props) { - const reservedKeys = ["current_item", "current_value", "current_index"]; - const isCloud = useContext(CloudContext); - const [key, setKey] = useState(""); - const hasWhitespace = /\s/.test(key); - const [urlParameterKey, setUrlParameterKey] = useState(""); - const [description, setDescription] = useState(""); - const [bitwardenCollectionId, setBitwardenCollectionId] = useState(""); - const [bitwardenLoginCredentialItemId, setBitwardenLoginCredentialItemId] = - useState(""); - const [parameterType, setParameterType] = - useState("string"); - const [defaultValueState, setDefaultValueState] = useState<{ - hasDefaultValue: boolean; - defaultValue: unknown; - }>({ - hasDefaultValue: false, - defaultValue: null, - }); - const [sourceParameterKey, setSourceParameterKey] = useState< - string | undefined - >(undefined); - - const [credentialType, setCredentialType] = useState< - "bitwarden" | "skyvern" | "onepassword" | "azurevault" - >("skyvern"); - const [vaultId, setVaultId] = useState(""); - const [itemId, setItemId] = useState(""); - - const [identityKey, setIdentityKey] = useState(""); - const [identityFields, setIdentityFields] = useState(""); - const [sensitiveInformationItemId, setSensitiveInformationItemId] = - useState(""); - - const [credentialId, setCredentialId] = useState(""); - - const [azureVaultName, setAzureVaultName] = useState(""); - const [azureUsernameKey, setAzureUsernameKey] = useState(""); - const [azurePasswordKey, setAzurePasswordKey] = useState(""); - const [azureTotpSecretKey, setAzureTotpKey] = useState(""); - - return ( - - -
-
- {header(type)} - -
-
- - setKey(e.target.value)} /> - {hasWhitespace && ( -

- Spaces are not allowed, consider using _ -

- )} -
-
- - setDescription(e.target.value)} - /> -
- {type === "workflow" && ( - <> -
- - -
-
-
- { - if (!checked) { - setDefaultValueState({ - hasDefaultValue: false, - defaultValue: null, - }); - return; - } - setDefaultValueState({ - hasDefaultValue: true, - defaultValue: - getDefaultValueForParameterType(parameterType), - }); - }} - /> - -
- {defaultValueState.hasDefaultValue && ( - { - 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} - /> - )} -
- - )} - {type === "credential" && ( - <> -
- - -
- - )} - {type === "credential" && credentialType === "bitwarden" && ( - <> -
- - setUrlParameterKey(e.target.value)} - /> -
-
- - setBitwardenCollectionId(e.target.value)} - /> -
-
- - - setBitwardenLoginCredentialItemId(e.target.value) - } - /> -
- - )} - {type === "credential" && credentialType === "onepassword" && ( - <> -
-
- - -
- setVaultId(e.target.value)} - /> -
-
-
- - -
- setItemId(e.target.value)} - /> -
-
-
- * Credit Cards: Due to a 1Password limitation, add the - expiration date as a separate text field named "Expire Date" - in the format MM/YYYY (e.g. 09/2027). -
-
- - )} - {type === "credential" && credentialType === "azurevault" && ( - <> -
- - setAzureVaultName(e.target.value)} - /> -
-
- - setAzureUsernameKey(e.target.value)} - /> -
-
- - setAzurePasswordKey(e.target.value)} - /> -
-
- - setAzureTotpKey(e.target.value)} - /> -
- - )} - {type === "context" && ( -
- - -
- )} - {type === "secret" && ( - <> -
- - setIdentityKey(e.target.value)} - /> -
-
- - setIdentityFields(e.target.value)} - /> -
-
- - setBitwardenCollectionId(e.target.value)} - /> -
- - )} - {type === "creditCardData" && ( - <> -
- - setBitwardenCollectionId(e.target.value)} - /> -
-
- - - setSensitiveInformationItemId(e.target.value) - } - /> -
- - )} - { - // temporarily cloud only - type === "credential" && - credentialType === "skyvern" && - isCloud && ( -
- - setCredentialId(value)} - /> -
- ) - } -
- -
-
-
-
- ); -} - -export { WorkflowParameterAddPanel }; diff --git a/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowParameterEditPanel.tsx b/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowParameterEditPanel.tsx index d520858e..909eb7df 100644 --- a/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowParameterEditPanel.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowParameterEditPanel.tsx @@ -37,7 +37,7 @@ type Props = { type: WorkflowEditorParameterType; onClose: () => void; onSave: (value: ParametersState[number]) => void; - initialValues: ParametersState[number]; + initialValues?: ParametersState[number]; }; const workflowParameterTypeOptions = [ @@ -50,20 +50,21 @@ const workflowParameterTypeOptions = [ { label: "JSON", value: WorkflowParameterValueType.JSON }, ]; -function header(type: WorkflowEditorParameterType) { +function header(type: WorkflowEditorParameterType, isEdit: boolean) { + const prefix = isEdit ? "Edit" : "Add"; if (type === "workflow") { - return "Edit Input Parameter"; + return `${prefix} Input Parameter`; } if (type === "credential") { - return "Edit Credential Parameter"; + return `${prefix} Credential Parameter`; } if (type === "secret") { - return "Edit Secret Parameter"; + return `${prefix} Secret Parameter`; } if (type === "creditCardData") { - return "Edit Credit Card Parameter"; + return `${prefix} Credit Card Parameter`; } - return "Edit Context Parameter"; + return `${prefix} Context Parameter`; } function WorkflowParameterEditPanel({ @@ -72,20 +73,22 @@ function WorkflowParameterEditPanel({ onSave, initialValues, }: Props) { + const reservedKeys = ["current_item", "current_value", "current_index"]; const isCloud = useContext(CloudContext); - const [key, setKey] = useState(initialValues.key); + const isEditMode = !!initialValues; + const [key, setKey] = useState(initialValues?.key ?? ""); const hasWhitespace = /\s/.test(key); const isBitwardenCredential = - initialValues.parameterType === "credential" && + initialValues?.parameterType === "credential" && parameterIsBitwardenCredential(initialValues); const isSkyvernCredential = - initialValues.parameterType === "credential" && + initialValues?.parameterType === "credential" && parameterIsSkyvernCredential(initialValues); const isOnePasswordCredential = - initialValues.parameterType === "onepassword" && + initialValues?.parameterType === "onepassword" && parameterIsOnePasswordCredential(initialValues); const isAzureVaultCredential = - initialValues.parameterType === "credential" && + initialValues?.parameterType === "credential" && parameterIsAzureVaultCredential(initialValues); const [credentialType, setCredentialType] = useState< "bitwarden" | "skyvern" | "onepassword" | "azurevault" @@ -99,21 +102,21 @@ function WorkflowParameterEditPanel({ : "skyvern", ); const [urlParameterKey, setUrlParameterKey] = useState( - isBitwardenCredential ? initialValues.urlParameterKey ?? "" : "", + isBitwardenCredential ? initialValues?.urlParameterKey ?? "" : "", ); const [description, setDescription] = useState( - initialValues.description ?? "", + initialValues?.description ?? "", ); - const [collectionId, setCollectionId] = useState( + const [bitwardenCollectionId, setBitwardenCollectionId] = useState( isBitwardenCredential || - initialValues.parameterType === "secret" || - initialValues.parameterType === "creditCardData" - ? initialValues.collectionId ?? "" + initialValues?.parameterType === "secret" || + initialValues?.parameterType === "creditCardData" + ? initialValues?.collectionId ?? "" : "", ); const [parameterType, setParameterType] = useState( - initialValues.parameterType === "workflow" + initialValues?.parameterType === "workflow" ? initialValues.dataType : "string", ); @@ -122,7 +125,7 @@ function WorkflowParameterEditPanel({ hasDefaultValue: boolean; defaultValue: unknown; }>( - initialValues.parameterType === "workflow" + initialValues?.parameterType === "workflow" ? { hasDefaultValue: initialValues.defaultValue !== null, defaultValue: initialValues.defaultValue ?? null, @@ -136,23 +139,23 @@ function WorkflowParameterEditPanel({ const [sourceParameterKey, setSourceParameterKey] = useState< string | undefined >( - initialValues.parameterType === "context" + initialValues?.parameterType === "context" ? initialValues.sourceParameterKey : undefined, ); const [identityKey, setIdentityKey] = useState( - initialValues.parameterType === "secret" ? initialValues.identityKey : "", + initialValues?.parameterType === "secret" ? initialValues.identityKey : "", ); const [identityFields, setIdentityFields] = useState( - initialValues.parameterType === "secret" + initialValues?.parameterType === "secret" ? initialValues.identityFields.join(", ") : "", ); - const [itemId, setItemId] = useState( - initialValues.parameterType === "creditCardData" + const [sensitiveInformationItemId, setSensitiveInformationItemId] = useState( + initialValues?.parameterType === "creditCardData" ? initialValues.itemId : "", ); @@ -160,7 +163,7 @@ function WorkflowParameterEditPanel({ const [credentialId, setCredentialId] = useState( isSkyvernCredential ? initialValues.credentialId : "", ); - const [vaultId, setVaultId] = useState( + const [opVaultId, setOpVaultId] = useState( isOnePasswordCredential ? initialValues.vaultId : "", ); const [opItemId, setOpItemId] = useState( @@ -168,7 +171,7 @@ function WorkflowParameterEditPanel({ ); const [bitwardenLoginCredentialItemId, setBitwardenLoginCredentialItemId] = - useState(isBitwardenCredential ? initialValues.itemId ?? "" : ""); + useState(isBitwardenCredential ? initialValues?.itemId ?? "" : ""); const [azureVaultName, setAzureVaultName] = useState( isAzureVaultCredential ? initialValues.vaultName : "", @@ -188,7 +191,7 @@ function WorkflowParameterEditPanel({
- {header(type)} + {header(type, isEditMode)}
@@ -274,7 +277,7 @@ function WorkflowParameterEditPanel({ setDefaultValueState((state) => { return { ...state, - defaultValue: value, + defaultValue: value.s3uri, }; }); return; @@ -340,8 +343,8 @@ function WorkflowParameterEditPanel({
setCollectionId(e.target.value)} + value={bitwardenCollectionId} + onChange={(e) => setBitwardenCollectionId(e.target.value)} />
@@ -363,8 +366,8 @@ function WorkflowParameterEditPanel({
setVaultId(e.target.value)} + value={opVaultId} + onChange={(e) => setOpVaultId(e.target.value)} />
@@ -451,8 +454,8 @@ function WorkflowParameterEditPanel({
setCollectionId(e.target.value)} + value={bitwardenCollectionId} + onChange={(e) => setBitwardenCollectionId(e.target.value)} />
@@ -462,15 +465,17 @@ function WorkflowParameterEditPanel({
setCollectionId(e.target.value)} + value={bitwardenCollectionId} + onChange={(e) => setBitwardenCollectionId(e.target.value)} />
setItemId(e.target.value)} + value={sensitiveInformationItemId} + onChange={(e) => + setSensitiveInformationItemId(e.target.value) + } />
@@ -509,6 +514,14 @@ function WorkflowParameterEditPanel({ }); return; } + if (!isEditMode && reservedKeys.includes(key)) { + toast({ + variant: "destructive", + title: "Failed to add parameter", + description: `${key} is reserved, please use another key`, + }); + return; + } if (type === "workflow") { if ( parameterType === "json" && @@ -542,7 +555,7 @@ function WorkflowParameterEditPanel({ } if (type === "credential" && credentialType === "bitwarden") { const errorMessage = validateBitwardenLoginCredential( - collectionId, + bitwardenCollectionId, bitwardenLoginCredentialItemId, urlParameterKey, ); @@ -563,12 +576,15 @@ function WorkflowParameterEditPanel({ : bitwardenLoginCredentialItemId, urlParameterKey: urlParameterKey === "" ? null : urlParameterKey, - collectionId: collectionId === "" ? null : collectionId, + collectionId: + bitwardenCollectionId === "" + ? null + : bitwardenCollectionId, description, }); } if (type === "credential" && credentialType === "onepassword") { - if (vaultId.trim() === "" || opItemId.trim() === "") { + if (opVaultId.trim() === "" || opItemId.trim() === "") { toast({ variant: "destructive", title: "Failed to save parameter", @@ -579,7 +595,7 @@ function WorkflowParameterEditPanel({ onSave({ key, parameterType: "onepassword", - vaultId, + vaultId: opVaultId, itemId: opItemId, description, }); @@ -610,7 +626,7 @@ function WorkflowParameterEditPanel({ }); } if (type === "secret" || type === "creditCardData") { - if (!collectionId) { + if (!bitwardenCollectionId) { toast({ variant: "destructive", title: "Failed to save parameter", @@ -623,7 +639,7 @@ function WorkflowParameterEditPanel({ onSave({ key, parameterType: "secret", - collectionId, + collectionId: bitwardenCollectionId, identityFields: identityFields .split(",") .filter((s) => s.length > 0) @@ -636,8 +652,8 @@ function WorkflowParameterEditPanel({ onSave({ key, parameterType: "creditCardData", - collectionId, - itemId, + collectionId: bitwardenCollectionId, + itemId: sensitiveInformationItemId, description, }); } diff --git a/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowParametersPanel.tsx b/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowParametersPanel.tsx index 2453ea9e..8ed57202 100644 --- a/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowParametersPanel.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowParametersPanel.tsx @@ -1,5 +1,4 @@ import { useState } from "react"; -import { WorkflowParameterAddPanel } from "./WorkflowParameterAddPanel"; import { ParametersState } from "../types"; import { WorkflowParameterEditPanel } from "./WorkflowParameterEditPanel"; import { MixerVerticalIcon, PlusIcon } from "@radix-ui/react-icons"; @@ -242,7 +241,7 @@ function WorkflowParametersPanel({ onMouseDownCapture }: Props) { > {operationPanelState.operation === "add" && (
- { setWorkflowParameters([...workflowParameters, parameter]); diff --git a/skyvern-frontend/vitest.config.ts b/skyvern-frontend/vitest.config.ts index 274f0b86..000083fa 100644 --- a/skyvern-frontend/vitest.config.ts +++ b/skyvern-frontend/vitest.config.ts @@ -1,6 +1,6 @@ -import { defineConfig } from 'vitest/config' -import react from '@vitejs/plugin-react-swc' -import path from 'path' +import { defineConfig } from "vitest/config"; +import react from "@vitejs/plugin-react-swc"; +import path from "path"; export default defineConfig({ plugins: [react()], @@ -9,7 +9,7 @@ export default defineConfig({ }, resolve: { alias: { - '@': path.resolve(__dirname, './src'), + "@": path.resolve(__dirname, "./src"), }, }, -}) +});