From 1e9ae54a67965a487014830deb6ac4f3251bfb97 Mon Sep 17 00:00:00 2001 From: Shuchang Zheng Date: Mon, 24 Feb 2025 19:51:32 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=84=20synced=20local=20'skyvern-fronte?= =?UTF-8?q?nd/src/'=20with=20remote=20'skyvern-frontend/src/'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit > [!IMPORTANT] > Add a new Credentials UI feature with components, routes, and workflow integration for managing passwords and credit cards. > > - **UI Components**: > - Add `CredentialsPage`, `CredentialsList`, `CredentialsModal`, `CredentialItem`, `DeleteCredentialButton`, `PasswordCredentialContent`, `CreditCardCredentialContent` for managing credentials. > - Add `DropdownWithOptions` and `KeyIcon` components. > - **Routing**: > - Add new route `/credentials` with `CredentialsPageLayout` and `CredentialsPage` in `router.tsx`. > - Update `SideNav.tsx` to include a link to the Credentials page. > - **Context and State**: > - Introduce `CloudContext` in `CloudContext.ts` to manage cloud-specific features. > - Implement `useCredentialModalState` for managing credential modal state. > - **API and Types**: > - Define types for credentials in `types.ts` and `api/types.ts`. > - Add functions `isPasswordCredential` and `isCreditCardCredential` in `api/types.ts`. > - **Workflow Integration**: > - Update `FlowRenderer.tsx`, `WorkflowEditor.tsx`, and related panels to support credential parameters. > - Add `CredentialSelector` component for selecting credentials in workflows. > > This description was created by [Ellipsis](https://www.ellipsis.dev?ref=Skyvern-AI%2Fskyvern-cloud&utm_source=github&utm_medium=referral) for 9276028e5855356f178604ec1ee1dde8ab59a1fd. It will automatically update as commits are pushed. --- skyvern-frontend/src/api/types.ts | 16 +++++ .../src/components/DropdownWithOptions.tsx | 50 ++++++++++++++ .../src/components/icons/KeyIcon.tsx | 26 ++++++++ .../components/CredentialSelector.tsx | 66 +++++++++++++++++++ .../routes/workflows/editor/FlowRenderer.tsx | 19 +++++- .../workflows/editor/WorkflowEditor.tsx | 29 ++++++-- .../panels/WorkflowParameterAddPanel.tsx | 48 ++++++++++++-- .../panels/WorkflowParameterEditPanel.tsx | 54 +++++++++++++-- .../editor/panels/WorkflowParametersPanel.tsx | 17 ++++- .../workflows/editor/workflowEditorUtils.ts | 7 ++ .../routes/workflows/types/workflowTypes.ts | 21 +++++- .../workflows/types/workflowYamlTypes.ts | 6 ++ skyvern-frontend/src/store/CloudContext.ts | 5 ++ 13 files changed, 337 insertions(+), 27 deletions(-) create mode 100644 skyvern-frontend/src/components/DropdownWithOptions.tsx create mode 100644 skyvern-frontend/src/components/icons/KeyIcon.tsx create mode 100644 skyvern-frontend/src/routes/workflows/components/CredentialSelector.tsx create mode 100644 skyvern-frontend/src/store/CloudContext.ts 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)} + /> +
+ ) + }