diff --git a/skyvern-frontend/src/api/types.ts b/skyvern-frontend/src/api/types.ts index 1f77542444..38ede48b4a 100644 --- a/skyvern-frontend/src/api/types.ts +++ b/skyvern-frontend/src/api/types.ts @@ -296,3 +296,19 @@ export type Createv2TaskRequest = { webhook_callback_url?: string | null; proxy_location?: ProxyLocation | null; }; + +export type PasswordCredentialApiResponse = { + username: string; +}; + +export type CreditCardCredentialApiResponse = { + last_four: string; + brand: string; +}; + +export type CredentialApiResponse = { + credential_id: string; + credential: PasswordCredentialApiResponse | CreditCardCredentialApiResponse; + credential_type: "password" | "credit_card"; + name: string; +}; diff --git a/skyvern-frontend/src/components/DropdownWithOptions.tsx b/skyvern-frontend/src/components/DropdownWithOptions.tsx new file mode 100644 index 0000000000..fead5c14bc --- /dev/null +++ b/skyvern-frontend/src/components/DropdownWithOptions.tsx @@ -0,0 +1,50 @@ +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "./ui/select"; + +type Item = { + label: string; + value: string; +}; + +type Props = { + options: Item[]; + value: string; + onChange: (selected: string) => void; + placeholder?: string; + className?: string; +}; + +function DropdownWithOptions({ + options, + value, + onChange, + placeholder, + className, +}: Props) { + return ( + + ); +} + +export { DropdownWithOptions }; diff --git a/skyvern-frontend/src/components/icons/KeyIcon.tsx b/skyvern-frontend/src/components/icons/KeyIcon.tsx new file mode 100644 index 0000000000..aba90c00a9 --- /dev/null +++ b/skyvern-frontend/src/components/icons/KeyIcon.tsx @@ -0,0 +1,26 @@ +type Props = { + className?: string; +}; + +function KeyIcon({ className }: Props) { + return ( + + + + ); +} + +export { KeyIcon }; diff --git a/skyvern-frontend/src/routes/workflows/components/CredentialSelector.tsx b/skyvern-frontend/src/routes/workflows/components/CredentialSelector.tsx new file mode 100644 index 0000000000..be72fe6b54 --- /dev/null +++ b/skyvern-frontend/src/routes/workflows/components/CredentialSelector.tsx @@ -0,0 +1,66 @@ +import { getClient } from "@/api/AxiosClient"; +import { CredentialApiResponse } from "@/api/types"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Skeleton } from "@/components/ui/skeleton"; +import { useCredentialGetter } from "@/hooks/useCredentialGetter"; +import { useQuery } from "@tanstack/react-query"; + +type Props = { + value: string; + onChange: (value: string) => void; +}; + +function CredentialSelector({ value, onChange }: Props) { + const credentialGetter = useCredentialGetter(); + + const { data: credentials, isFetching } = useQuery< + Array + >({ + queryKey: ["credentials"], + queryFn: async () => { + const client = await getClient(credentialGetter); + return await client.get("/credentials").then((res) => res.data); + }, + }); + + if (isFetching) { + return ; + } + + if (!credentials) { + return null; + } + + return ( + + ); +} + +export { CredentialSelector }; diff --git a/skyvern-frontend/src/routes/workflows/editor/FlowRenderer.tsx b/skyvern-frontend/src/routes/workflows/editor/FlowRenderer.tsx index b3fde4127e..f9aa274f8e 100644 --- a/skyvern-frontend/src/routes/workflows/editor/FlowRenderer.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/FlowRenderer.tsx @@ -47,6 +47,7 @@ import { BitwardenSensitiveInformationParameterYAML, BlockYAML, ContextParameterYAML, + CredentialParameterYAML, ParameterYAML, WorkflowCreateYAMLRequest, WorkflowParameterYAML, @@ -91,6 +92,7 @@ function convertToParametersYAML( | ContextParameterYAML | BitwardenSensitiveInformationParameterYAML | BitwardenCreditCardDataParameterYAML + | CredentialParameterYAML > { return parameters.map((parameter) => { if (parameter.parameterType === WorkflowEditorParameterTypes.Workflow) { @@ -143,6 +145,15 @@ function convertToParametersYAML( bitwarden_master_password_aws_secret_key: BITWARDEN_MASTER_PASSWORD_AWS_SECRET_KEY, }; + } else if ( + parameter.parameterType === WorkflowEditorParameterTypes.Credential + ) { + return { + parameter_type: WorkflowParameterTypes.Credential, + key: parameter.key, + description: parameter.description || null, + credential_id: parameter.credentialId, + }; } else { return { parameter_type: WorkflowParameterTypes.Bitwarden_Login_Credential, @@ -170,7 +181,7 @@ export type ParametersState = Array< } | { key: string; - parameterType: "credential"; + parameterType: "bitwardenLoginCredential"; collectionId: string; urlParameterKey: string; description?: string | null; @@ -196,6 +207,12 @@ export type ParametersState = Array< collectionId: string; description?: string | null; } + | { + key: string; + parameterType: "credential"; + credentialId: string; + description?: string | null; + } >; type Props = { diff --git a/skyvern-frontend/src/routes/workflows/editor/WorkflowEditor.tsx b/skyvern-frontend/src/routes/workflows/editor/WorkflowEditor.tsx index df4f55c9df..cafc110ccb 100644 --- a/skyvern-frontend/src/routes/workflows/editor/WorkflowEditor.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/WorkflowEditor.tsx @@ -75,27 +75,32 @@ function WorkflowEditor() { initialParameters={workflow.workflow_definition.parameters .filter((parameter) => isDisplayedInWorkflowEditor(parameter)) .map((parameter) => { - if (parameter.parameter_type === "workflow") { + if ( + parameter.parameter_type === WorkflowParameterTypes.Workflow + ) { return { key: parameter.key, - parameterType: "workflow", + parameterType: WorkflowEditorParameterTypes.Workflow, dataType: parameter.workflow_parameter_type, defaultValue: parameter.default_value, description: parameter.description, }; - } else if (parameter.parameter_type === "context") { + } else if ( + parameter.parameter_type === WorkflowParameterTypes.Context + ) { return { key: parameter.key, - parameterType: "context", + parameterType: WorkflowEditorParameterTypes.Context, sourceParameterKey: parameter.source.key, description: parameter.description, }; } else if ( - parameter.parameter_type === "bitwarden_sensitive_information" + parameter.parameter_type === + WorkflowParameterTypes.Bitwarden_Sensitive_Information ) { return { key: parameter.key, - parameterType: "secret", + parameterType: WorkflowEditorParameterTypes.Secret, collectionId: parameter.bitwarden_collection_id, identityKey: parameter.bitwarden_identity_key, identityFields: parameter.bitwarden_identity_fields, @@ -112,10 +117,20 @@ function WorkflowEditor() { itemId: parameter.bitwarden_item_id, description: parameter.description, }; + } else if ( + parameter.parameter_type === WorkflowParameterTypes.Credential + ) { + return { + key: parameter.key, + parameterType: WorkflowEditorParameterTypes.Credential, + credentialId: parameter.credential_id, + description: parameter.description, + }; } else { return { key: parameter.key, - parameterType: "credential", + parameterType: + WorkflowEditorParameterTypes.BitwardenLoginCredential, collectionId: parameter.bitwarden_collection_id, urlParameterKey: parameter.url_parameter_key, description: parameter.description, diff --git a/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowParameterAddPanel.tsx b/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowParameterAddPanel.tsx index 0fa52bb196..f10924e34c 100644 --- a/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowParameterAddPanel.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowParameterAddPanel.tsx @@ -1,7 +1,7 @@ import { Cross2Icon } from "@radix-ui/react-icons"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; -import { useState } from "react"; +import { useContext, useState } from "react"; import { WorkflowEditorParameterType, WorkflowParameterValueType, @@ -22,6 +22,8 @@ import { getDefaultValueForParameterType } from "../workflowEditorUtils"; import { toast } from "@/components/ui/use-toast"; import { SourceParameterKeySelector } from "../../components/SourceParameterKeySelector"; import { ScrollArea, ScrollAreaViewport } from "@/components/ui/scroll-area"; +import CloudContext from "@/store/CloudContext"; +import { CredentialSelector } from "../../components/CredentialSelector"; type Props = { type: WorkflowEditorParameterType; @@ -45,6 +47,9 @@ function header(type: WorkflowEditorParameterType) { if (type === "credential") { return "Add Credential Parameter"; } + if (type === "bitwardenLoginCredential") { + return "Add Bitwarden Login Credential Parameter"; + } if (type === "secret") { return "Add Secret Parameter"; } @@ -55,6 +60,7 @@ function header(type: WorkflowEditorParameterType) { } function WorkflowParameterAddPanel({ type, onClose, onSave }: Props) { + const isCloud = useContext(CloudContext); const [key, setKey] = useState(""); const [urlParameterKey, setUrlParameterKey] = useState(""); const [description, setDescription] = useState(""); @@ -76,6 +82,8 @@ function WorkflowParameterAddPanel({ type, onClose, onSave }: Props) { const [identityFields, setIdentityFields] = useState(""); const [itemId, setItemId] = useState(""); + const [credentialId, setCredentialId] = useState(""); + return ( @@ -181,7 +189,7 @@ function WorkflowParameterAddPanel({ type, onClose, onSave }: Props) { )} - {type === "credential" && ( + {type === "bitwardenLoginCredential" && ( <>
)} + { + // temporarily cloud only + type === "credential" && isCloud && ( +
+ + setCredentialId(value)} + /> +
+ ) + }
)} - {type === "credential" && ( + {type === "bitwardenLoginCredential" && ( <>
)} + { + // temporarily cloud only + type === "credential" && isCloud && ( +
+ + setCredentialId(value)} + /> +
+ ) + }