Add secret parameter to workflow (#1150)

This commit is contained in:
Shuchang Zheng
2024-11-06 12:29:38 -08:00
committed by GitHub
parent b62c2caae0
commit b73602dad4
6 changed files with 170 additions and 36 deletions

View File

@@ -16,12 +16,12 @@ import { nanoid } from "nanoid";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { import {
AWSSecretParameter, AWSSecretParameter,
BitwardenSensitiveInformationParameter,
WorkflowApiResponse, WorkflowApiResponse,
WorkflowParameterValueType, WorkflowParameterValueType,
} from "../types/workflowTypes"; } from "../types/workflowTypes";
import { import {
BitwardenLoginCredentialParameterYAML, BitwardenLoginCredentialParameterYAML,
BitwardenSensitiveInformationParameterYAML,
BlockYAML, BlockYAML,
ContextParameterYAML, ContextParameterYAML,
ParameterYAML, ParameterYAML,
@@ -74,6 +74,7 @@ function convertToParametersYAML(
| WorkflowParameterYAML | WorkflowParameterYAML
| BitwardenLoginCredentialParameterYAML | BitwardenLoginCredentialParameterYAML
| ContextParameterYAML | ContextParameterYAML
| BitwardenSensitiveInformationParameterYAML
> { > {
return parameters.map((parameter) => { return parameters.map((parameter) => {
if (parameter.parameterType === "workflow") { if (parameter.parameterType === "workflow") {
@@ -93,6 +94,20 @@ function convertToParametersYAML(
description: parameter.description || null, description: parameter.description || null,
source_parameter_key: parameter.sourceParameterKey, source_parameter_key: parameter.sourceParameterKey,
}; };
} else if (parameter.parameterType === "secret") {
return {
parameter_type: "bitwarden_sensitive_information",
key: parameter.key,
bitwarden_identity_key: parameter.identityKey,
bitwarden_identity_fields: parameter.identityFields,
description: parameter.description || null,
bitwarden_collection_id: parameter.collectionId,
bitwarden_client_id_aws_secret_key: "SKYVERN_BITWARDEN_CLIENT_ID",
bitwarden_client_secret_aws_secret_key:
"SKYVERN_BITWARDEN_CLIENT_SECRET",
bitwarden_master_password_aws_secret_key:
"SKYVERN_BITWARDEN_MASTER_PASSWORD",
};
} else { } else {
return { return {
parameter_type: "bitwarden_login_credential", parameter_type: "bitwarden_login_credential",
@@ -131,6 +146,14 @@ export type ParametersState = Array<
sourceParameterKey: string; sourceParameterKey: string;
description?: string | null; description?: string | null;
} }
| {
key: string;
parameterType: "secret";
identityKey: string;
identityFields: Array<string>;
collectionId: string;
description?: string | null;
}
>; >;
type Props = { type Props = {
@@ -246,12 +269,9 @@ function FlowRenderer({
const parametersInYAMLConvertibleJSON = convertToParametersYAML(parameters); const parametersInYAMLConvertibleJSON = convertToParametersYAML(parameters);
const filteredParameters = workflow.workflow_definition.parameters.filter( const filteredParameters = workflow.workflow_definition.parameters.filter(
(parameter) => { (parameter) => {
return ( return parameter.parameter_type === "aws_secret";
parameter.parameter_type === "aws_secret" ||
parameter.parameter_type === "bitwarden_sensitive_information"
);
}, },
) as Array<AWSSecretParameter | BitwardenSensitiveInformationParameter>; ) as Array<AWSSecretParameter>;
const echoParameters = convertEchoParameters(filteredParameters); const echoParameters = convertEchoParameters(filteredParameters);

View File

@@ -53,7 +53,8 @@ function WorkflowEditor() {
(parameter) => (parameter) =>
parameter.parameter_type === "workflow" || parameter.parameter_type === "workflow" ||
parameter.parameter_type === "bitwarden_login_credential" || parameter.parameter_type === "bitwarden_login_credential" ||
parameter.parameter_type === "context", parameter.parameter_type === "context" ||
parameter.parameter_type === "bitwarden_sensitive_information",
) )
.map((parameter) => { .map((parameter) => {
if (parameter.parameter_type === "workflow") { if (parameter.parameter_type === "workflow") {
@@ -71,6 +72,17 @@ function WorkflowEditor() {
sourceParameterKey: parameter.source.key, sourceParameterKey: parameter.source.key,
description: parameter.description, description: parameter.description,
}; };
} else if (
parameter.parameter_type === "bitwarden_sensitive_information"
) {
return {
key: parameter.key,
parameterType: "secret",
collectionId: parameter.bitwarden_collection_id,
identityKey: parameter.bitwarden_identity_key,
identityFields: parameter.bitwarden_identity_fields,
description: parameter.description,
};
} else { } else {
return { return {
key: parameter.key, key: parameter.key,

View File

@@ -20,7 +20,7 @@ import { toast } from "@/components/ui/use-toast";
import { SourceParameterKeySelector } from "../../components/SourceParameterKeySelector"; import { SourceParameterKeySelector } from "../../components/SourceParameterKeySelector";
type Props = { type Props = {
type: "workflow" | "credential" | "context"; type: "workflow" | "credential" | "context" | "secret";
onClose: () => void; onClose: () => void;
onSave: (value: ParametersState[number]) => void; onSave: (value: ParametersState[number]) => void;
}; };
@@ -34,13 +34,16 @@ const workflowParameterTypeOptions = [
{ label: "JSON", value: WorkflowParameterValueType.JSON }, { label: "JSON", value: WorkflowParameterValueType.JSON },
]; ];
function header(type: "workflow" | "credential" | "context") { function header(type: "workflow" | "credential" | "context" | "secret") {
if (type === "workflow") { if (type === "workflow") {
return "Add Input Parameter"; return "Add Input Parameter";
} }
if (type === "credential") { if (type === "credential") {
return "Add Credential Parameter"; return "Add Credential Parameter";
} }
if (type === "secret") {
return "Add Secret Parameter";
}
return "Add Context Parameter"; return "Add Context Parameter";
} }
@@ -62,6 +65,9 @@ function WorkflowParameterAddPanel({ type, onClose, onSave }: Props) {
string | undefined string | undefined
>(undefined); >(undefined);
const [identityKey, setIdentityKey] = useState("");
const [identityFields, setIdentityFields] = useState("");
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<header className="flex items-center justify-between"> <header className="flex items-center justify-between">
@@ -192,6 +198,31 @@ function WorkflowParameterAddPanel({ type, onClose, onSave }: Props) {
/> />
</div> </div>
)} )}
{type === "secret" && (
<>
<div className="space-y-1">
<Label className="text-xs text-slate-300">Identity Key</Label>
<Input
value={identityKey}
onChange={(e) => setIdentityKey(e.target.value)}
/>
</div>
<div className="space-y-1">
<Label className="text-xs text-slate-300">Identity Fields</Label>
<Input
value={identityFields}
onChange={(e) => setIdentityFields(e.target.value)}
/>
</div>
<div className="space-y-1">
<Label className="text-xs text-slate-300">Collection ID</Label>
<Input
value={collectionId}
onChange={(e) => setCollectionId(e.target.value)}
/>
</div>
</>
)}
<div className="flex justify-end"> <div className="flex justify-end">
<Button <Button
onClick={() => { onClick={() => {
@@ -243,6 +274,27 @@ function WorkflowParameterAddPanel({ type, onClose, onSave }: Props) {
description, description,
}); });
} }
if (type === "secret") {
if (!collectionId) {
toast({
variant: "destructive",
title: "Failed to add parameter",
description: "Collection ID is required",
});
return;
}
onSave({
key,
parameterType: "secret",
collectionId,
identityFields: identityFields
.split(",")
.filter((s) => s.length > 0)
.map((field) => field.trim()),
identityKey,
description,
});
}
if (type === "context") { if (type === "context") {
if (!sourceParameterKey) { if (!sourceParameterKey) {
toast({ toast({

View File

@@ -20,7 +20,7 @@ import { toast } from "@/components/ui/use-toast";
import { SourceParameterKeySelector } from "../../components/SourceParameterKeySelector"; import { SourceParameterKeySelector } from "../../components/SourceParameterKeySelector";
type Props = { type Props = {
type: "workflow" | "credential" | "context"; type: "workflow" | "credential" | "context" | "secret";
onClose: () => void; onClose: () => void;
onSave: (value: ParametersState[number]) => void; onSave: (value: ParametersState[number]) => void;
initialValues: ParametersState[number]; initialValues: ParametersState[number];
@@ -35,13 +35,16 @@ const workflowParameterTypeOptions = [
{ label: "JSON", value: WorkflowParameterValueType.JSON }, { label: "JSON", value: WorkflowParameterValueType.JSON },
]; ];
function header(type: "workflow" | "credential" | "context") { function header(type: "workflow" | "credential" | "context" | "secret") {
if (type === "workflow") { if (type === "workflow") {
return "Edit Input Parameter"; return "Edit Input Parameter";
} }
if (type === "credential") { if (type === "credential") {
return "Edit Credential Parameter"; return "Edit Credential Parameter";
} }
if (type === "secret") {
return "Edit Secret Parameter";
}
return "Edit Context Parameter"; return "Edit Context Parameter";
} }
@@ -61,7 +64,8 @@ function WorkflowParameterEditPanel({
initialValues.description ?? "", initialValues.description ?? "",
); );
const [collectionId, setCollectionId] = useState( const [collectionId, setCollectionId] = useState(
initialValues.parameterType === "credential" initialValues.parameterType === "credential" ||
initialValues.parameterType === "secret"
? initialValues.collectionId ? initialValues.collectionId
: "", : "",
); );
@@ -95,6 +99,16 @@ function WorkflowParameterEditPanel({
: undefined, : undefined,
); );
const [identityKey, setIdentityKey] = useState(
initialValues.parameterType === "secret" ? initialValues.identityKey : "",
);
const [identityFields, setIdentityFields] = useState(
initialValues.parameterType === "secret"
? initialValues.identityFields.join(", ")
: "",
);
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<header className="flex items-center justify-between"> <header className="flex items-center justify-between">
@@ -225,6 +239,31 @@ function WorkflowParameterEditPanel({
/> />
</div> </div>
)} )}
{type === "secret" && (
<>
<div className="space-y-1">
<Label className="text-xs text-slate-300">Identity Key</Label>
<Input
value={identityKey}
onChange={(e) => setIdentityKey(e.target.value)}
/>
</div>
<div className="space-y-1">
<Label className="text-xs text-slate-300">Identity Fields</Label>
<Input
value={identityFields}
onChange={(e) => setIdentityFields(e.target.value)}
/>
</div>
<div className="space-y-1">
<Label className="text-xs text-slate-300">Collection ID</Label>
<Input
value={collectionId}
onChange={(e) => setCollectionId(e.target.value)}
/>
</div>
</>
)}
<div className="flex justify-end"> <div className="flex justify-end">
<Button <Button
onClick={() => { onClick={() => {
@@ -276,6 +315,27 @@ function WorkflowParameterEditPanel({
description, description,
}); });
} }
if (type === "secret") {
if (!collectionId) {
toast({
variant: "destructive",
title: "Failed to add parameter",
description: "Collection ID is required",
});
return;
}
onSave({
key,
parameterType: "secret",
collectionId,
identityFields: identityFields
.split(",")
.filter((s) => s.length > 0)
.map((field) => field.trim()),
identityKey,
description,
});
}
if (type === "context") { if (type === "context") {
if (!sourceParameterKey) { if (!sourceParameterKey) {
toast({ toast({

View File

@@ -41,7 +41,7 @@ function WorkflowParametersPanel() {
active: boolean; active: boolean;
operation: "add" | "edit"; operation: "add" | "edit";
parameter?: ParametersState[number] | null; parameter?: ParametersState[number] | null;
type: "workflow" | "credential" | "context"; type: "workflow" | "credential" | "context" | "secret";
}>({ }>({
active: false, active: false,
operation: "add", operation: "add",
@@ -103,6 +103,17 @@ function WorkflowParametersPanel() {
> >
Context Parameter Context Parameter
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
setOperationPanelState({
active: true,
operation: "add",
type: "secret",
});
}}
>
Secret Parameter
</DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>

View File

@@ -4,8 +4,6 @@ import { Edge } from "@xyflow/react";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import type { import type {
AWSSecretParameter, AWSSecretParameter,
BitwardenSensitiveInformationParameter,
ContextParameter,
OutputParameter, OutputParameter,
Parameter, Parameter,
WorkflowApiResponse, WorkflowApiResponse,
@@ -45,11 +43,11 @@ import { fileParserNodeDefaultData } from "./nodes/FileParserNode/types";
import { LoopNode, loopNodeDefaultData } from "./nodes/LoopNode/types"; import { LoopNode, loopNodeDefaultData } from "./nodes/LoopNode/types";
import { NodeAdderNode } from "./nodes/NodeAdderNode/types"; import { NodeAdderNode } from "./nodes/NodeAdderNode/types";
import { sendEmailNodeDefaultData } from "./nodes/SendEmailNode/types"; import { sendEmailNodeDefaultData } from "./nodes/SendEmailNode/types";
import { StartNode } from "./nodes/StartNode/types";
import { taskNodeDefaultData } from "./nodes/TaskNode/types"; import { taskNodeDefaultData } from "./nodes/TaskNode/types";
import { textPromptNodeDefaultData } from "./nodes/TextPromptNode/types"; import { textPromptNodeDefaultData } from "./nodes/TextPromptNode/types";
import { NodeBaseData } from "./nodes/types"; import { NodeBaseData } from "./nodes/types";
import { uploadNodeDefaultData } from "./nodes/UploadNode/types"; import { uploadNodeDefaultData } from "./nodes/UploadNode/types";
import { StartNode } from "./nodes/StartNode/types";
export const NEW_NODE_LABEL_PREFIX = "block_"; export const NEW_NODE_LABEL_PREFIX = "block_";
@@ -685,11 +683,7 @@ function generateNodeLabel(existingLabels: Array<string>) {
* If a parameter is not displayed in the editor, we should echo its value back when saved. * If a parameter is not displayed in the editor, we should echo its value back when saved.
*/ */
function convertEchoParameters( function convertEchoParameters(
parameters: Array< parameters: Array<AWSSecretParameter>,
| ContextParameter
| BitwardenSensitiveInformationParameter
| AWSSecretParameter
>,
): Array<ParameterYAML> { ): Array<ParameterYAML> {
return parameters.map((parameter) => { return parameters.map((parameter) => {
if (parameter.parameter_type === "aws_secret") { if (parameter.parameter_type === "aws_secret") {
@@ -699,21 +693,6 @@ function convertEchoParameters(
aws_key: parameter.aws_key, aws_key: parameter.aws_key,
}; };
} }
if (parameter.parameter_type === "bitwarden_sensitive_information") {
return {
key: parameter.key,
parameter_type: "bitwarden_sensitive_information",
bitwarden_collection_id: parameter.bitwarden_collection_id,
bitwarden_identity_key: parameter.bitwarden_identity_key,
bitwarden_identity_fields: parameter.bitwarden_identity_fields,
bitwarden_client_id_aws_secret_key:
parameter.bitwarden_client_id_aws_secret_key,
bitwarden_client_secret_aws_secret_key:
parameter.bitwarden_client_secret_aws_secret_key,
bitwarden_master_password_aws_secret_key:
parameter.bitwarden_master_password_aws_secret_key,
};
}
throw new Error("Unknown parameter type"); throw new Error("Unknown parameter type");
}); });
} }