Skip to content

Commit

Permalink
🔄 synced local 'skyvern-frontend/src/' with remote 'skyvern-frontend/…
Browse files Browse the repository at this point in the history
…src/'

<!-- ELLIPSIS_HIDDEN -->

> [!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.
>
> <sup>This description was created by </sup>[<img alt="Ellipsis" src="https://img.shields.io/badge/Ellipsis-blue?color=175173">](https://www.ellipsis.dev?ref=Skyvern-AI%2Fskyvern-cloud&utm_source=github&utm_medium=referral)<sup> for 9276028e5855356f178604ec1ee1dde8ab59a1fd. It will automatically update as commits are pushed.</sup>

<!-- ELLIPSIS_HIDDEN -->
  • Loading branch information
wintonzheng committed Feb 24, 2025
1 parent 39ab9c0 commit 1e9ae54
Show file tree
Hide file tree
Showing 13 changed files with 337 additions and 27 deletions.
16 changes: 16 additions & 0 deletions skyvern-frontend/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
50 changes: 50 additions & 0 deletions skyvern-frontend/src/components/DropdownWithOptions.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Select
value={value}
onValueChange={(value) => {
onChange(value);
}}
>
<SelectTrigger className={className}>
<SelectValue placeholder={placeholder} />
</SelectTrigger>
<SelectContent className="max-h-48">
{options.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
);
}

export { DropdownWithOptions };
26 changes: 26 additions & 0 deletions skyvern-frontend/src/components/icons/KeyIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
type Props = {
className?: string;
};

function KeyIcon({ className }: Props) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
className={className}
>
<path
d="M17 8.99994C17 8.48812 16.8047 7.9763 16.4142 7.58579C16.0237 7.19526 15.5118 7 15 7M15 15C18.3137 15 21 12.3137 21 9C21 5.68629 18.3137 3 15 3C11.6863 3 9 5.68629 9 9C9 9.27368 9.01832 9.54308 9.05381 9.80704C9.11218 10.2412 9.14136 10.4583 9.12172 10.5956C9.10125 10.7387 9.0752 10.8157 9.00469 10.9419C8.937 11.063 8.81771 11.1823 8.57913 11.4209L3.46863 16.5314C3.29568 16.7043 3.2092 16.7908 3.14736 16.8917C3.09253 16.9812 3.05213 17.0787 3.02763 17.1808C3 17.2959 3 17.4182 3 17.6627V19.4C3 19.9601 3 20.2401 3.10899 20.454C3.20487 20.6422 3.35785 20.7951 3.54601 20.891C3.75992 21 4.03995 21 4.6 21H7V19H9V17H11L12.5791 15.4209C12.8177 15.1823 12.937 15.063 13.0581 14.9953C13.1843 14.9248 13.2613 14.8987 13.4044 14.8783C13.5417 14.8586 13.7588 14.8878 14.193 14.9462C14.4569 14.9817 14.7263 15 15 15Z"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

export { KeyIcon };
Original file line number Diff line number Diff line change
@@ -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<CredentialApiResponse>
>({
queryKey: ["credentials"],
queryFn: async () => {
const client = await getClient(credentialGetter);
return await client.get("/credentials").then((res) => res.data);
},
});

if (isFetching) {
return <Skeleton className="h-10 w-full" />;
}

if (!credentials) {
return null;
}

return (
<Select value={value} onValueChange={onChange}>
<SelectTrigger>
<SelectValue placeholder="Select a credential" />
</SelectTrigger>
<SelectContent>
{credentials.map((credential) => (
<SelectItem
key={credential.credential_id}
value={credential.credential_id}
>
<div className="space-y-2">
<p className="text-sm font-medium">{credential.name}</p>
<p className="text-xs text-slate-400">
{credential.credential_type === "password"
? "Password"
: "Credit Card"}
</p>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
);
}

export { CredentialSelector };
19 changes: 18 additions & 1 deletion skyvern-frontend/src/routes/workflows/editor/FlowRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
BitwardenSensitiveInformationParameterYAML,
BlockYAML,
ContextParameterYAML,
CredentialParameterYAML,
ParameterYAML,
WorkflowCreateYAMLRequest,
WorkflowParameterYAML,
Expand Down Expand Up @@ -91,6 +92,7 @@ function convertToParametersYAML(
| ContextParameterYAML
| BitwardenSensitiveInformationParameterYAML
| BitwardenCreditCardDataParameterYAML
| CredentialParameterYAML
> {
return parameters.map((parameter) => {
if (parameter.parameterType === WorkflowEditorParameterTypes.Workflow) {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -170,7 +181,7 @@ export type ParametersState = Array<
}
| {
key: string;
parameterType: "credential";
parameterType: "bitwardenLoginCredential";
collectionId: string;
urlParameterKey: string;
description?: string | null;
Expand All @@ -196,6 +207,12 @@ export type ParametersState = Array<
collectionId: string;
description?: string | null;
}
| {
key: string;
parameterType: "credential";
credentialId: string;
description?: string | null;
}
>;

type Props = {
Expand Down
29 changes: 22 additions & 7 deletions skyvern-frontend/src/routes/workflows/editor/WorkflowEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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;
Expand All @@ -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";
}
Expand All @@ -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("");
Expand All @@ -76,6 +82,8 @@ function WorkflowParameterAddPanel({ type, onClose, onSave }: Props) {
const [identityFields, setIdentityFields] = useState("");
const [itemId, setItemId] = useState("");

const [credentialId, setCredentialId] = useState("");

return (
<ScrollArea>
<ScrollAreaViewport className="max-h-[500px]">
Expand Down Expand Up @@ -181,7 +189,7 @@ function WorkflowParameterAddPanel({ type, onClose, onSave }: Props) {
</div>
</>
)}
{type === "credential" && (
{type === "bitwardenLoginCredential" && (
<>
<div className="space-y-1">
<Label className="text-xs text-slate-300">
Expand Down Expand Up @@ -255,6 +263,18 @@ function WorkflowParameterAddPanel({ type, onClose, onSave }: Props) {
</div>
</>
)}
{
// temporarily cloud only
type === "credential" && isCloud && (
<div className="space-y-1">
<Label className="text-xs text-slate-300">Credential</Label>
<CredentialSelector
value={credentialId}
onChange={(value) => setCredentialId(value)}
/>
</div>
)
}
<div className="flex justify-end">
<Button
onClick={() => {
Expand Down Expand Up @@ -290,7 +310,7 @@ function WorkflowParameterAddPanel({ type, onClose, onSave }: Props) {
});
}
if (
type === "credential" ||
type === "bitwardenLoginCredential" ||
type === "secret" ||
type === "creditCardData"
) {
Expand All @@ -303,10 +323,10 @@ function WorkflowParameterAddPanel({ type, onClose, onSave }: Props) {
return;
}
}
if (type === "credential") {
if (type === "bitwardenLoginCredential") {
onSave({
key,
parameterType: "credential",
parameterType: "bitwardenLoginCredential",
collectionId,
urlParameterKey,
description,
Expand Down Expand Up @@ -346,7 +366,23 @@ function WorkflowParameterAddPanel({ type, onClose, onSave }: Props) {
onSave({
key,
parameterType: "context",
sourceParameterKey: sourceParameterKey,
sourceParameterKey,
description,
});
}
if (type === "credential") {
if (!credentialId) {
toast({
variant: "destructive",
title: "Failed to add parameter",
description: "Credential is required",
});
return;
}
onSave({
key,
parameterType: "credential",
credentialId,
description,
});
}
Expand Down
Loading

0 comments on commit 1e9ae54

Please # to comment.