From 300ed8ab602630b13e0da751f68bc1199d2818b0 Mon Sep 17 00:00:00 2001 From: Maina Wycliffe Date: Tue, 24 Oct 2023 10:02:34 +0300 Subject: [PATCH 1/9] feat: add new git connection type for connections --- src/components/Connections/ConnectionForm.tsx | 1 + .../Connections/connectionTypes.tsx | 63 +++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/src/components/Connections/ConnectionForm.tsx b/src/components/Connections/ConnectionForm.tsx index b5e5db95a..3efb6bc2d 100644 --- a/src/components/Connections/ConnectionForm.tsx +++ b/src/components/Connections/ConnectionForm.tsx @@ -58,6 +58,7 @@ export type Connection = { webhook?: string; workstation?: string; properties?: Record; + ref?: string; }; type ConnectionFormProps = React.HTMLProps & { diff --git a/src/components/Connections/connectionTypes.tsx b/src/components/Connections/connectionTypes.tsx index b7d6ec58d..ac6c3e8d7 100644 --- a/src/components/Connections/connectionTypes.tsx +++ b/src/components/Connections/connectionTypes.tsx @@ -1807,6 +1807,69 @@ export const connectionTypes: ConnectionType[] = [ } }; } + }, + { + title: "Git", + icon: "git", + value: ConnectionValueType.Git, + fields: [ + { + label: "Name", + key: "name", + type: fieldTypes.input, + required: true + }, + { + label: "URL", + key: "url", + type: fieldTypes.input, + required: true + }, + { + label: "Username", + key: "username", + type: fieldTypes.EnvVarSource, + required: true + }, + { + label: "Password", + key: "password", + type: fieldTypes.EnvVarSource, + required: true + }, + { + label: "SSH Key", + key: "certificate", + type: fieldTypes.EnvVarSource, + variant: variants.large, + required: true + }, + { + label: "Ref", + default: "main", + key: "ref", + type: fieldTypes.input, + required: true + } + ], + convertToFormSpecificValue: (data: Record) => { + return { + ...data, + ref: data.properties?.ref + } as Connection; + }, + preSubmitConverter: (data: Record) => { + return { + name: data.name, + url: data.url, + password: data.password, + username: data.username, + certificate: data.certificate, + properties: { + ref: data.ref + } + }; + } } ] .sort((v1, v2) => { From 3473a049c01db79644bc238da3e2cf569f7a8223 Mon Sep 17 00:00:00 2001 From: Maina Wycliffe Date: Fri, 27 Oct 2023 08:47:14 +0300 Subject: [PATCH 2/9] refactor: use enum instead of object --- .../Connections/connectionTypes.tsx | 369 +++++++++--------- 1 file changed, 184 insertions(+), 185 deletions(-) diff --git a/src/components/Connections/connectionTypes.tsx b/src/components/Connections/connectionTypes.tsx index ac6c3e8d7..03ca2d6e0 100644 --- a/src/components/Connections/connectionTypes.tsx +++ b/src/components/Connections/connectionTypes.tsx @@ -3,12 +3,12 @@ import { DiGoogleCloudPlatform } from "react-icons/di"; import { stringSortHelper } from "../../utils/common"; import { Connection } from "./ConnectionForm"; -const fieldTypes = { - checkbox: "checkbox", - input: "input", - numberInput: "numberInput", - EnvVarSource: "EnvVarSource" -}; +const enum ConnectionsFieldTypes { + checkbox = "checkbox", + input = "input", + numberInput = "numberInput", + EnvVarSource = "EnvVarSource" +} type Variant = "small" | "large"; @@ -20,7 +20,7 @@ const variants: { [key: string]: Variant } = { export type Field = { label: string; key: string; - type: string; + type: ConnectionsFieldTypes; variant?: Variant; required?: boolean; hint?: string; @@ -92,25 +92,25 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "URL", key: "url", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Username", key: "username", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true }, { label: "Password", key: "password", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true } ] @@ -123,25 +123,25 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "URL", key: "url", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Username", key: "username", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true }, { label: "Password", key: "password", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true } ] @@ -154,25 +154,25 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "URL", key: "url", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Username", key: "username", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true }, { label: "Password", key: "password", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true } ] @@ -185,31 +185,31 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "URL", key: "url", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Username", key: "username", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true }, { label: "Password", key: "password", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true }, { label: "Insecure TLS", key: "insecure_tls", - type: fieldTypes.checkbox + type: ConnectionsFieldTypes.checkbox } ] }, @@ -221,31 +221,31 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "URL", key: "url", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Username", key: "username", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: false }, { label: "Password", key: "password", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: false }, { label: "Insecure TLS", key: "insecure_tls", - type: fieldTypes.checkbox + type: ConnectionsFieldTypes.checkbox } ] }, @@ -257,31 +257,31 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "URL", key: "url", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Username", key: "username", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true }, { label: "Password", key: "password", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true }, { label: "Insecure TLS", key: "insecure_tls", - type: fieldTypes.checkbox + type: ConnectionsFieldTypes.checkbox } ] }, @@ -293,31 +293,31 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "URL", key: "url", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Username", key: "username", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true }, { label: "Password", key: "password", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true }, { label: "Insecure TLS", key: "insecure_tls", - type: fieldTypes.checkbox + type: ConnectionsFieldTypes.checkbox } ] }, @@ -329,31 +329,31 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "URL", key: "url", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Username", key: "username", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true }, { label: "Password", key: "password", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true }, { label: "Insecure TLS", key: "insecure_tls", - type: fieldTypes.checkbox + type: ConnectionsFieldTypes.checkbox } ] }, @@ -365,37 +365,37 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Database", key: "db", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "URL", key: "url", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Username", key: "username", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: false }, { label: "Password", key: "password", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: false }, { label: "Insecure TLS", key: "insecure_tls", - type: fieldTypes.checkbox + type: ConnectionsFieldTypes.checkbox } ], convertToFormSpecificValue: (data: Record) => { @@ -425,25 +425,25 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Username", key: "username", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true }, { label: "Password", key: "password", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true }, { label: "Domain", key: "domain", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: false } ], @@ -473,19 +473,19 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Endpoint", key: "url", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Certificate", key: "certificate", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, variant: variants.large, required: true } @@ -499,31 +499,31 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Host", key: "host", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Username", key: "username", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true }, { label: "Password", key: "password", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true }, { label: "Port", key: "port", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: false } ], @@ -553,37 +553,37 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Region", key: "region", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: false }, { label: "Profile", key: "profile", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: false }, { label: "Access Key", key: "username", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: false }, { label: "Secret Key", key: "password", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: false }, { label: "Insecure TLS", key: "insecure_tls", - type: fieldTypes.checkbox + type: ConnectionsFieldTypes.checkbox } ], convertToFormSpecificValue: (data: Record) => { @@ -615,13 +615,13 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Certificate", key: "certificate", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, variant: variants.large, required: false } @@ -635,19 +635,19 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Organization", key: "username", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Personal Access Token", key: "password", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true } ] @@ -660,25 +660,25 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Client ID", key: "username", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Client Secret", key: "password", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true }, { label: "Tenant ID", key: "tenant", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true } ], @@ -707,13 +707,13 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Personal Access Token", key: "password", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true } ] @@ -726,37 +726,37 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Password", key: "password", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true }, { label: "Repository URL", key: "url", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "AWS Connection Name", key: "awsConnectionName", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Access Key", key: "accessKey", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true }, { label: "Secret Key", key: "secretKey", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true } ], @@ -785,40 +785,40 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Username", key: "username", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Password", key: "password", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Workstation", key: "workstation", - type: fieldTypes.input + type: ConnectionsFieldTypes.input }, { label: "Share name", key: "sharename", - type: fieldTypes.input + type: ConnectionsFieldTypes.input }, { label: "Search path", key: "searchPath", - type: fieldTypes.input + type: ConnectionsFieldTypes.input }, { label: "Port", key: "port", - type: fieldTypes.numberInput, + type: ConnectionsFieldTypes.numberInput, default: 445 } ], @@ -854,19 +854,19 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Host", key: "url", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Port", key: "port", - type: fieldTypes.numberInput + type: ConnectionsFieldTypes.numberInput } ], convertToFormSpecificValue: (data: Record) => { @@ -893,25 +893,25 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Host", key: "url", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "API Key", key: "password", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true }, { label: "Scheme", key: "scheme", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true } ], @@ -941,19 +941,19 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Webhook ID", key: "username", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Token", key: "password", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true } ], @@ -975,51 +975,51 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Username", key: "username", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Password", key: "password", hint: "SMTP server password or hash (for OAuth2)", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true }, { label: "From Address", key: "from", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "From Name", key: "fromName", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Host", key: "host", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Port", key: "port", - type: fieldTypes.numberInput, + type: ConnectionsFieldTypes.numberInput, default: 25, required: true }, { label: "Encryption method", key: "encryptionMethod", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, hint: "None, ExplicitTLS, ImplicitTLS, Auto (default)", default: "Auto", required: true @@ -1027,7 +1027,7 @@ export const connectionTypes: ConnectionType[] = [ { label: "SMTP authentication method", key: "authMethod", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, hint: "None, Plain, CRAMMD5, Unknown, OAuth2", default: "Unknown", required: true @@ -1035,7 +1035,7 @@ export const connectionTypes: ConnectionType[] = [ { label: "Insecure TLS", key: "insecure_tls", - type: fieldTypes.checkbox + type: ConnectionsFieldTypes.checkbox } ], convertToFormSpecificValue: (data: Record) => { @@ -1076,25 +1076,25 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Key", key: "username", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true }, { label: "Token", key: "password", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true }, { label: "Webhook Name", key: "webhook", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true } ], @@ -1125,13 +1125,13 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Webhook ID", key: "username", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true } ], @@ -1152,35 +1152,35 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Username", key: "username", hint: "Override webhook user", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: false }, { label: "Host", key: "host", hint: "Mattermost server host (host:port)", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Token", key: "password", hint: "Webhook token", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true }, { label: "Channel", key: "channel", hint: "Override webhook channel", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: false } ], @@ -1213,33 +1213,33 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "User", key: "username", hint: "Username or empty when using access token", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: false }, { label: "Password", key: "password", hint: "Password or access token", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true }, { label: "Host", key: "host", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Insecure TLS", key: "insecure_tls", - type: fieldTypes.checkbox + type: ConnectionsFieldTypes.checkbox } ], convertToFormSpecificValue: (data: Record) => { @@ -1271,19 +1271,19 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Username", key: "username", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: false }, { label: "Password", key: "password", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: false }, { @@ -1291,20 +1291,20 @@ export const connectionTypes: ConnectionType[] = [ key: "host", hint: "Server hostname and port", default: "ntfy.sh", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Topic", key: "topic", hint: "Target topic name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Insecure TLS", key: "insecure_tls", - type: fieldTypes.checkbox + type: ConnectionsFieldTypes.checkbox } ], convertToFormSpecificValue: (data: Record) => { @@ -1338,27 +1338,27 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Host", key: "host", default: "api.opsgenie.com", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Port", key: "port", default: 443, - type: fieldTypes.numberInput, + type: ConnectionsFieldTypes.numberInput, required: true }, { label: "API Key", key: "password", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true } ], @@ -1390,19 +1390,19 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Token", key: "password", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true }, { label: "Targets", key: "targets", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true } ], @@ -1432,19 +1432,19 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "User", key: "username", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Token", key: "password", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true } ], @@ -1466,43 +1466,43 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Username", key: "user", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: false }, { label: "Host", key: "host", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Port", key: "port", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Token A", key: "username", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true }, { label: "Token B", key: "password", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true }, { label: "Channel", key: "channel", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true } ], @@ -1539,26 +1539,26 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Channel", key: "username", hint: "Channel to send messages to in Cxxxxxxxxxx format", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Bot Token", key: "password", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true }, { label: "Bot Name", key: "fromName", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: false } ], @@ -1589,37 +1589,37 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Group", key: "group", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: false }, { label: "Tenant", key: "tenant", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "AltID", key: "altID", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "GroupOwner", key: "groupOwner", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Host", key: "host", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, default: "outlook.office.com", required: true } @@ -1657,20 +1657,20 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Token", key: "password", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true }, { label: "Chats", hitns: "Chat IDs or Channel names (using @channel-name)", key: "username", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true } ], @@ -1692,26 +1692,26 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "BotMail", key: "username", hint: "Bot e-mail address", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: false }, { label: "BotKey", key: "password", - type: fieldTypes.EnvVarSource, + type: ConnectionsFieldTypes.EnvVarSource, required: true }, { label: "Host", key: "host", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true } ], @@ -1742,25 +1742,25 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "URL", key: "username", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "ContentType", default: "application/json", key: "contentType", - type: fieldTypes.input + type: ConnectionsFieldTypes.input }, { label: "Request Method", key: "requestMethod", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, default: "POST", required: true }, @@ -1768,20 +1768,20 @@ export const connectionTypes: ConnectionType[] = [ label: "Message Key", default: "message", key: "key", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Title Key", default: "title", key: "titleKey", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Insecure TLS", key: "insecure_tls", - type: fieldTypes.checkbox + type: ConnectionsFieldTypes.checkbox } ], convertToFormSpecificValue: (data: Record) => { @@ -1816,39 +1816,38 @@ export const connectionTypes: ConnectionType[] = [ { label: "Name", key: "name", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "URL", key: "url", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true }, { label: "Username", key: "username", - type: fieldTypes.EnvVarSource, - required: true + type: ConnectionsFieldTypes.EnvVarSource, + variant: variants.large }, { label: "Password", key: "password", - type: fieldTypes.EnvVarSource, - required: true + type: ConnectionsFieldTypes.EnvVarSource, + variant: variants.large }, { label: "SSH Key", key: "certificate", - type: fieldTypes.EnvVarSource, - variant: variants.large, - required: true + type: ConnectionsFieldTypes.EnvVarSource, + variant: variants.large }, { label: "Ref", default: "main", key: "ref", - type: fieldTypes.input, + type: ConnectionsFieldTypes.input, required: true } ], From 0bebfdbd20a2418c888e1dbb760cff299dd9cc0b Mon Sep 17 00:00:00 2001 From: Maina Wycliffe Date: Fri, 27 Oct 2023 09:29:37 +0300 Subject: [PATCH 3/9] feat: add option to have switch field for connection form fix: add option to have switch field for connection form fix: fix edit to show correct option group --- src/components/Connections/ConnectionForm.tsx | 69 ++----------------- .../FormikConnectionOptionsSwitchField.tsx | 62 +++++++++++++++++ .../RenderConnectionFormFields.tsx | 60 ++++++++++++++++ .../Connections/connectionTypes.tsx | 62 ++++++++++++----- 4 files changed, 172 insertions(+), 81 deletions(-) create mode 100644 src/components/Connections/FormikConnectionOptionsSwitchField.tsx create mode 100644 src/components/Connections/RenderConnectionFormFields.tsx diff --git a/src/components/Connections/ConnectionForm.tsx b/src/components/Connections/ConnectionForm.tsx index 3efb6bc2d..a4767f640 100644 --- a/src/components/Connections/ConnectionForm.tsx +++ b/src/components/Connections/ConnectionForm.tsx @@ -1,21 +1,17 @@ import clsx from "clsx"; import { Form, Formik } from "formik"; -import FormikTextInput from "../Forms/Formik/FormikTextInput"; -import FormikCheckbox from "../Forms/Formik/FormikCheckbox"; +import { mapValues, method } from "lodash"; +import React, { useEffect, useState } from "react"; +import { FaTrash } from "react-icons/fa"; +import { Button } from "../Button"; +import { Icon } from "../Icon"; import { Modal } from "../Modal"; -import { useEffect, useState } from "react"; +import RenderConnectionFormFields from "./RenderConnectionFormFields"; import { ConnectionType, ConnectionValueType, - Field, connectionTypes } from "./connectionTypes"; -import { FormikEnvVarSource } from "../Forms/Formik/FormikEnvVarSource"; -import { Icon } from "../Icon"; -import React from "react"; -import { FaTrash } from "react-icons/fa"; -import { Button } from "../Button"; -import { mapValues, method } from "lodash"; export type Connection = { altID?: string; @@ -119,55 +115,6 @@ export default function ConnectionForm({ } as Connection; }; - const getFieldView = (field: Field) => { - const type = field.type ?? "input"; - switch (type) { - case "input": - return ( - - ); - case "numberInput": - return ( - - ); - case "checkbox": - return ( - - ); - case "EnvVarSource": - return ( - - ); - default: - return null; - } - }; - const getFormView = (connectionType: ConnectionType) => { return ( {connectionType.fields.map((field, index) => { return ( - - {getFieldView(field)} - + ); })} diff --git a/src/components/Connections/FormikConnectionOptionsSwitchField.tsx b/src/components/Connections/FormikConnectionOptionsSwitchField.tsx new file mode 100644 index 000000000..c58cfe882 --- /dev/null +++ b/src/components/Connections/FormikConnectionOptionsSwitchField.tsx @@ -0,0 +1,62 @@ +import { useFormikContext } from "formik"; +import { get } from "lodash"; +import { useState } from "react"; +import { Switch } from "../Switch"; +import RenderConnectionFormFields from "./RenderConnectionFormFields"; +import { ConnectionFormFields } from "./connectionTypes"; + +type Props = { + field: ConnectionFormFields; +}; + +export default function FormikConnectionOptionsSwitchField({ field }: Props) { + const { setFieldValue, values } = useFormikContext>(); + + const [selectedGroup, setSelectedGroup] = useState(() => { + // find the first field that has a value + const firstField = field.options?.find((option) => { + return option.fields?.find((field) => get(values, field.key)); + }); + return firstField?.key ?? field.default ?? field.options?.[0]?.key; + }); + + if (!field.options) { + return null; + } + + const selectedField = field.options.find( + (option) => option.key === selectedGroup + ); + + return ( +
+ +
+ option.label)]} + defaultValue="None" + value={ + field.options?.find((option) => option.key === selectedGroup)?.label + } + onChange={(v) => { + // reset all other fields that are not selected + field.options?.forEach((option) => { + if (option.key === v) { + return; + } + setFieldValue(option.key, undefined); + }); + setSelectedGroup( + field.options?.find((option) => option.label === v)?.key + ); + }} + /> +
+
+ {selectedField?.fields?.map((field) => ( + + ))} +
+
+ ); +} diff --git a/src/components/Connections/RenderConnectionFormFields.tsx b/src/components/Connections/RenderConnectionFormFields.tsx new file mode 100644 index 000000000..f3d9b3d62 --- /dev/null +++ b/src/components/Connections/RenderConnectionFormFields.tsx @@ -0,0 +1,60 @@ +import FormikConnectionOptionsSwitchField from "./FormikConnectionOptionsSwitchField"; +import FormikCheckbox from "../Forms/Formik/FormikCheckbox"; +import { FormikEnvVarSource } from "../Forms/Formik/FormikEnvVarSource"; +import FormikTextInput from "../Forms/Formik/FormikTextInput"; +import { ConnectionFormFields } from "./connectionTypes"; + +interface FieldViewProps { + field: ConnectionFormFields; +} + +export default function RenderConnectionFormFields({ field }: FieldViewProps) { + const type = field.type ?? "input"; + switch (type) { + case "input": + return ( + + ); + case "numberInput": + return ( + + ); + case "checkbox": + return ( + + ); + case "EnvVarSource": + return ( + + ); + case "switch": + return ; + default: + return null; + } +} diff --git a/src/components/Connections/connectionTypes.tsx b/src/components/Connections/connectionTypes.tsx index 03ca2d6e0..af76b5d53 100644 --- a/src/components/Connections/connectionTypes.tsx +++ b/src/components/Connections/connectionTypes.tsx @@ -7,7 +7,8 @@ const enum ConnectionsFieldTypes { checkbox = "checkbox", input = "input", numberInput = "numberInput", - EnvVarSource = "EnvVarSource" + EnvVarSource = "EnvVarSource", + switch = "switch" } type Variant = "small" | "large"; @@ -17,7 +18,7 @@ const variants: { [key: string]: Variant } = { large: "large" }; -export type Field = { +export type ConnectionFormFields = { label: string; key: string; type: ConnectionsFieldTypes; @@ -25,6 +26,11 @@ export type Field = { required?: boolean; hint?: string; default?: boolean | number | string; + options?: { + label: string; + key: string; + fields: Omit[]; + }[]; }; export const enum ConnectionValueType { @@ -74,7 +80,7 @@ export type ConnectionType = { title: string; value: ConnectionValueType; icon?: React.ReactNode | string | null; - fields: Field[]; + fields: ConnectionFormFields[]; convertToFormSpecificValue?: (data: Record) => Connection; preSubmitConverter?: (data: Record) => object; hide?: boolean; @@ -1826,22 +1832,40 @@ export const connectionTypes: ConnectionType[] = [ required: true }, { - label: "Username", - key: "username", - type: ConnectionsFieldTypes.EnvVarSource, - variant: variants.large - }, - { - label: "Password", - key: "password", - type: ConnectionsFieldTypes.EnvVarSource, - variant: variants.large - }, - { - label: "SSH Key", - key: "certificate", - type: ConnectionsFieldTypes.EnvVarSource, - variant: variants.large + label: "Authentication", + key: "authentication", + type: ConnectionsFieldTypes.switch, + default: "password", + options: [ + { + label: "Password", + key: "password", + fields: [ + { + label: "Username", + key: "username", + type: ConnectionsFieldTypes.EnvVarSource + }, + { + label: "Password", + key: "password", + type: ConnectionsFieldTypes.EnvVarSource + } + ] + }, + { + label: "SSH Key", + key: "certificate", + fields: [ + { + label: "SSH Key", + key: "certificate", + type: ConnectionsFieldTypes.EnvVarSource, + variant: variants.large + } + ] + } + ] }, { label: "Ref", From 256e1e3a9153f7a67dae620b7afda35cf706553a Mon Sep 17 00:00:00 2001 From: Maina Wycliffe Date: Wed, 1 Nov 2023 23:42:54 +0300 Subject: [PATCH 4/9] refactor: fix missing key for title --- src/pages/Settings/ConnectionsPage.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pages/Settings/ConnectionsPage.tsx b/src/pages/Settings/ConnectionsPage.tsx index 1e725f485..acab9f7a0 100644 --- a/src/pages/Settings/ConnectionsPage.tsx +++ b/src/pages/Settings/ConnectionsPage.tsx @@ -7,9 +7,9 @@ import { updateResource } from "../../api/schemaResources"; import { BreadcrumbNav, BreadcrumbRoot } from "../../components/BreadcrumbNav"; -import ConnectionForm, { +import ConnectionFormModal, { Connection -} from "../../components/Connections/ConnectionForm"; +} from "../../components/Connections/ConnectionFormModal"; import { ConnectionList } from "../../components/Connections/ConnectionsList"; import { Head } from "../../components/Head/Head"; import { SearchLayout } from "../../components/Layout"; @@ -137,10 +137,11 @@ export function ConnectionsPage() { title={ + Connections , - )} -
-
- - -
- ); - }; - - const getConnectionListingView = () => { - return ( -
- {connectionTypes.map((item) => { - return ( -
-
{ - setConnectionType(item); - }} - > - {typeof item.icon === "string" ? ( - - ) : ( - item.icon - )} -
{item.title}
-
-
- ); - })} -
- ); + const handleDelete = () => { + onConnectionDelete?.(formValue!); }; return ( -
- - {typeof connectionType?.icon === "string" ? ( - - ) : ( - connectionType.icon - )} -
- {connectionType.title} Connection Details -
-
- ) : ( -
Select Connection Type
- ) + { - setIsOpen(false); - }} - open={isOpen} - bodyClass="flex flex-col w-full flex-1 h-full overflow-y-auto" - > - {connectionType && getFormView(connectionType)} - {!connectionType && getConnectionListingView()} - - + } + onSubmit={handleSubmit} + > +
+
+
+
+ {connectionType.fields.map((field, index) => { + return ( + + ); + })} +
+
+
+
+ {Boolean(formValue?.id) && ( + + )} +
+
+
+
+
); } + +export default ConnectionForm; diff --git a/src/components/Connections/ConnectionFormModal.tsx b/src/components/Connections/ConnectionFormModal.tsx new file mode 100644 index 000000000..d1fab599f --- /dev/null +++ b/src/components/Connections/ConnectionFormModal.tsx @@ -0,0 +1,130 @@ +import React, { useEffect, useState } from "react"; +import { Icon } from "../Icon"; +import { Modal } from "../Modal"; +import ConnectionForm from "./ConnectionForm"; +import { + ConnectionType, + ConnectionValueType, + connectionTypes +} from "./connectionTypes"; +import ConnectionListView from "./ConnectionListView"; + +export type Connection = { + altID?: string; + authMethod?: string; + certificate?: string; + channel?: string; + checkIntegrity?: boolean; + contentType?: string; + db?: string; + domain?: string; + email?: string; + encryptionMethod?: string; + from?: string; + fromName?: string; + group?: string; + groupOwner?: string; + host?: string; + id?: string; + insecure_tls?: boolean; + key?: string; + maxAge?: number; + name: string; + password?: string; + path?: string; + port?: string | number; + profile?: string; + region?: string; + requestMethod?: string; + scheme?: string; + searchPath?: string; + sharename?: string; + targets?: string; + tenant?: string; + titleKey?: string; + topic?: string; + type?: ConnectionValueType; + url?: string; + user?: string; + username?: string; + webhook?: string; + workstation?: string; + properties?: Record; + ref?: string; +}; + +type ConnectionFormProps = React.HTMLProps & { + isOpen: boolean; + setIsOpen: (val: boolean) => void; + onConnectionSubmit: (data: Connection) => Promise; + onConnectionDelete: (data: Connection) => Promise; + formValue?: Connection; +}; + +export default function ConnectionFormModal({ + className, + isOpen, + setIsOpen, + onConnectionSubmit, + onConnectionDelete, + formValue +}: ConnectionFormProps) { + const [connectionType, setConnectionType] = useState< + ConnectionType | undefined + >(() => connectionTypes.find((item) => item.title === formValue?.type)); + + useEffect(() => { + let connection = connectionTypes.find( + (item) => item.value === formValue?.type + ); + setConnectionType(connection); + }, [isOpen, formValue]); + + const type = + connectionTypes.find((item) => item.value === formValue?.type) ?? + connectionType; + + return ( +
+ + {typeof connectionType?.icon === "string" ? ( + + ) : ( + connectionType.icon + )} +
+ {connectionType.title} Connection Details +
+
+ ) : ( +
Select Connection Type
+ ) + } + onClose={() => { + setIsOpen(false); + }} + open={isOpen} + bodyClass="flex flex-col w-full flex-1 h-full overflow-y-auto" + > + {type ? ( + setConnectionType(undefined)} + connectionType={type} + onConnectionSubmit={onConnectionSubmit} + onConnectionDelete={onConnectionDelete} + formValue={formValue} + className={className} + /> + ) : ( + + )} + + + ); +} diff --git a/src/components/Connections/ConnectionListView.tsx b/src/components/Connections/ConnectionListView.tsx new file mode 100644 index 000000000..bd4f43427 --- /dev/null +++ b/src/components/Connections/ConnectionListView.tsx @@ -0,0 +1,33 @@ +import { Icon } from "../Icon"; +import { ConnectionType, connectionTypes } from "./connectionTypes"; + +type Props = { + setConnectionType: (connectionType: ConnectionType) => void; +}; + +export default function ConnectionListView({ setConnectionType }: Props) { + return ( +
+ {connectionTypes.map((item) => { + return ( +
+
{ + setConnectionType(item); + }} + > + {typeof item.icon === "string" ? ( + + ) : ( + item.icon + )} +
{item.title}
+
+
+ ); + })} +
+ ); +} diff --git a/src/components/Connections/ConnectionsList.tsx b/src/components/Connections/ConnectionsList.tsx index 92d2fd5a6..80bfc2621 100644 --- a/src/components/Connections/ConnectionsList.tsx +++ b/src/components/Connections/ConnectionsList.tsx @@ -2,7 +2,7 @@ import { CellContext, ColumnDef } from "@tanstack/table-core"; import clsx from "clsx"; import { DataTable } from "../DataTable"; import { Avatar } from "../Avatar"; -import { Connection } from "./ConnectionForm"; +import { Connection } from "./ConnectionFormModal"; import { Icon } from "../Icon"; import { DateCell } from "../../ui/table"; diff --git a/src/components/Connections/__tests__/ConnectionForm.unit.test.tsx b/src/components/Connections/__tests__/ConnectionForm.unit.test.tsx new file mode 100644 index 000000000..2a983c815 --- /dev/null +++ b/src/components/Connections/__tests__/ConnectionForm.unit.test.tsx @@ -0,0 +1,98 @@ +import React from "react"; +import { render, screen, fireEvent, waitFor } from "@testing-library/react"; +import ConnectionForm from "./../ConnectionForm"; +import { + ConnectionType, + ConnectionValueType, + connectionTypes +} from "../connectionTypes"; + +describe("ConnectionForm", () => { + const connectionType: ConnectionType = connectionTypes.find( + (type) => type.value === ConnectionValueType.Git + )!; + + const formInitialValue = { + name: "Test Connection", + url: "https://test.com", + certificate: "test", + ref: "main" + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("renders the form fields", () => { + render( + + ); + expect(screen.getByLabelText("Name")).toHaveValue("Test Connection"); + expect(screen.getByLabelText("URL")).toHaveValue("https://test.com"); + }); + + it("calls onConnectionSubmit when the form is submitted", async () => { + const onConnectionSubmit = jest.fn(); + render( + + ); + fireEvent.click( + screen.getByRole("button", { + name: /Save/i + }) + ); + + await waitFor(() => { + expect(onConnectionSubmit).toHaveBeenCalledWith({ + certificate: "test", + name: "Test Connection", + password: undefined, + properties: { + ref: "ref" + }, + type: "git", + url: "https://test.com", + username: undefined + }); + }); + }); + + it("calls onConnectionDelete when the delete button is clicked", () => { + const onConnectionDelete = jest.fn(); + render( + + ); + fireEvent.click(screen.getByText("Delete")); + expect(onConnectionDelete).toHaveBeenCalledWith({ + name: "Test Connection", + url: "https://test.com", + ref: "main", + certificate: "test", + id: "123" + }); + }); + + it("calls handleBack when the back button is clicked", () => { + const handleBack = jest.fn(); + render( + + ); + fireEvent.click(screen.getByText("Back")); + expect(handleBack).toHaveBeenCalled(); + }); +}); diff --git a/src/components/Connections/__tests__/ConnectionFormModal.unit.test.tsx b/src/components/Connections/__tests__/ConnectionFormModal.unit.test.tsx new file mode 100644 index 000000000..0fd4a2f67 --- /dev/null +++ b/src/components/Connections/__tests__/ConnectionFormModal.unit.test.tsx @@ -0,0 +1,93 @@ +import { fireEvent, render, screen, waitFor } from "@testing-library/react"; +import ConnectionFormModal from "../ConnectionFormModal"; +import { ConnectionValueType } from "../connectionTypes"; + +global.ResizeObserver = jest.fn().mockImplementation(() => ({ + observe: jest.fn(), + unobserve: jest.fn(), + disconnect: jest.fn() +})); + +describe("ConnectionForm", () => { + const formInitialValue = { + type: ConnectionValueType.Git, + name: "Test Connection", + url: "https://test.com", + certificate: "test", + ref: "main" + }; + + const onConnectionSubmit = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("renders form when provided with initial value", async () => { + render( + {}} + onConnectionDelete={async (data) => {}} + onConnectionSubmit={onConnectionSubmit} + /> + ); + + await waitFor(() => screen.findByRole("button", { name: /Save/i })); + + expect(screen.getByLabelText("Name")).toHaveValue("Test Connection"); + expect(screen.getByLabelText("URL")).toHaveValue("https://test.com"); + + fireEvent.click( + screen.getByRole("button", { + name: /Save/i + }) + ); + + await waitFor(() => { + expect(onConnectionSubmit).toHaveBeenCalledWith({ + certificate: "test", + name: "Test Connection", + password: undefined, + properties: { + ref: "ref" + }, + type: "git", + url: "https://test.com", + username: undefined + }); + }); + }); + + it("renders list of connection types", async () => { + render( + {}} + onConnectionSubmit={async (data) => {}} + onConnectionDelete={async (data) => {}} + /> + ); + + expect(await screen.findByText("Git")).toBeInTheDocument(); + expect(screen.getByText("GitHub")).toBeInTheDocument(); + }); + + it("renders form when connection type is selected", async () => { + render( + {}} + onConnectionSubmit={async (data) => {}} + onConnectionDelete={async (data) => {}} + /> + ); + + fireEvent.click(await screen.findByText("Git")); + + expect(await screen.findByLabelText("URL")).toBeInTheDocument(); + }); +}); diff --git a/src/components/Connections/connectionTypes.tsx b/src/components/Connections/connectionTypes.tsx index af76b5d53..69f43b7ec 100644 --- a/src/components/Connections/connectionTypes.tsx +++ b/src/components/Connections/connectionTypes.tsx @@ -1,7 +1,7 @@ import { FaWindows } from "react-icons/fa"; import { DiGoogleCloudPlatform } from "react-icons/di"; import { stringSortHelper } from "../../utils/common"; -import { Connection } from "./ConnectionForm"; +import { Connection } from "./ConnectionFormModal"; const enum ConnectionsFieldTypes { checkbox = "checkbox", @@ -407,7 +407,7 @@ export const connectionTypes: ConnectionType[] = [ convertToFormSpecificValue: (data: Record) => { return { ...data, - db: data.properties?.db + db: data?.properties?.db } as Connection; }, preSubmitConverter: (data: Record) => { @@ -456,7 +456,7 @@ export const connectionTypes: ConnectionType[] = [ convertToFormSpecificValue: (data: Record) => { return { ...data, - domain: data.properties?.domain + domain: data?.properties?.domain } as Connection; }, preSubmitConverter: (data: Record) => { @@ -595,9 +595,9 @@ export const connectionTypes: ConnectionType[] = [ convertToFormSpecificValue: (data: Record) => { return { ...data, - region: data.properties?.region, - profile: data.properties?.profile, - insecure_tls: data.properties?.insecureTLS === "true" + region: data?.properties?.region, + profile: data?.properties?.profile, + insecure_tls: data?.properties?.insecureTLS === "true" } as Connection; }, preSubmitConverter: (data: Record) => { @@ -691,7 +691,7 @@ export const connectionTypes: ConnectionType[] = [ convertToFormSpecificValue: (data: Record) => { return { ...data, - tenant: data.properties?.tenant + tenant: data?.properties?.tenant } as Connection; }, preSubmitConverter: (data: Record) => { @@ -831,10 +831,10 @@ export const connectionTypes: ConnectionType[] = [ convertToFormSpecificValue: (data: Record) => { return { ...data, - workstation: data.properties?.workstation, - sharename: data.properties?.sharename, - searchPath: data.properties?.searchPath, - port: data.properties?.port + workstation: data?.properties?.workstation, + sharename: data?.properties?.sharename, + searchPath: data?.properties?.searchPath, + port: data?.properties?.port } as Connection; }, preSubmitConverter: (data: Record) => { @@ -878,7 +878,7 @@ export const connectionTypes: ConnectionType[] = [ convertToFormSpecificValue: (data: Record) => { return { ...data, - port: data.properties?.port + port: data?.properties?.port } as Connection; }, preSubmitConverter: (data: Record) => { @@ -924,7 +924,7 @@ export const connectionTypes: ConnectionType[] = [ convertToFormSpecificValue: (data: Record) => { return { ...data, - scheme: data.properties?.scheme + scheme: data?.properties?.scheme } as Connection; }, preSubmitConverter: (data: Record) => { @@ -1047,12 +1047,12 @@ export const connectionTypes: ConnectionType[] = [ convertToFormSpecificValue: (data: Record) => { return { ...data, - authMethod: data.properties?.authMethod, - encryptionMethod: data.properties?.encryptionMethod, - from: data.properties?.from, - fromName: data.properties?.fromName, - host: data.properties?.host, - port: data.properties?.port + authMethod: data?.properties?.authMethod, + encryptionMethod: data?.properties?.encryptionMethod, + from: data?.properties?.from, + fromName: data?.properties?.fromName, + host: data?.properties?.host, + port: data?.properties?.port } as Connection; }, preSubmitConverter: (data: Record) => { @@ -1107,7 +1107,7 @@ export const connectionTypes: ConnectionType[] = [ convertToFormSpecificValue: (data: Record) => { return { ...data, - webhook: data.properties?.webhook + webhook: data?.properties?.webhook } as Connection; }, preSubmitConverter: (data: Record) => { @@ -1193,8 +1193,8 @@ export const connectionTypes: ConnectionType[] = [ convertToFormSpecificValue: (data: Record) => { return { ...data, - host: data.properties?.host, - channel: data.properties?.channel + host: data?.properties?.host, + channel: data?.properties?.channel } as Connection; }, preSubmitConverter: (data: Record) => { @@ -1251,8 +1251,8 @@ export const connectionTypes: ConnectionType[] = [ convertToFormSpecificValue: (data: Record) => { return { ...data, - host: data.properties?.host, - channel: data.properties?.channel + host: data?.properties?.host, + channel: data?.properties?.channel } as Connection; }, preSubmitConverter: (data: Record) => { @@ -1316,8 +1316,8 @@ export const connectionTypes: ConnectionType[] = [ convertToFormSpecificValue: (data: Record) => { return { ...data, - host: data.properties?.host, - topic: data.properties?.topic + host: data?.properties?.host, + topic: data?.properties?.topic } as Connection; }, preSubmitConverter: (data: Record) => { @@ -1371,8 +1371,8 @@ export const connectionTypes: ConnectionType[] = [ convertToFormSpecificValue: (data: Record) => { return { ...data, - host: data.properties?.host, - port: data.properties?.port + host: data?.properties?.host, + port: data?.properties?.port } as Connection; }, preSubmitConverter: (data: Record) => { @@ -1415,7 +1415,7 @@ export const connectionTypes: ConnectionType[] = [ convertToFormSpecificValue: (data: Record) => { return { ...data, - targets: data.properties?.targets + targets: data?.properties?.targets } as Connection; }, preSubmitConverter: (data: Record) => { @@ -1515,10 +1515,10 @@ export const connectionTypes: ConnectionType[] = [ convertToFormSpecificValue: (data: Record) => { return { ...data, - host: data.properties?.host, - port: data.properties?.port, - user: data.properties?.user, - channel: data.properties?.channel + host: data?.properties?.host, + port: data?.properties?.port, + user: data?.properties?.user, + channel: data?.properties?.channel } as Connection; }, preSubmitConverter: (data: Record) => { @@ -1571,7 +1571,7 @@ export const connectionTypes: ConnectionType[] = [ convertToFormSpecificValue: (data: Record) => { return { ...data, - fromName: data.properties?.BotName + fromName: data?.properties?.BotName } as Connection; }, preSubmitConverter: (data: Record) => { @@ -1633,11 +1633,11 @@ export const connectionTypes: ConnectionType[] = [ convertToFormSpecificValue: (data: Record) => { return { ...data, - group: data.properties?.group, - tenant: data.properties?.tenant, - altID: data.properties?.altID, - host: data.properties?.host, - groupOwner: data.properties?.groupOwner + group: data?.properties?.group, + tenant: data?.properties?.tenant, + altID: data?.properties?.altID, + host: data?.properties?.host, + groupOwner: data?.properties?.groupOwner } as Connection; }, preSubmitConverter: (data: Record) => { @@ -1724,7 +1724,7 @@ export const connectionTypes: ConnectionType[] = [ convertToFormSpecificValue: (data: Record) => { return { ...data, - host: data.properties?.host + host: data?.properties?.host } as Connection; }, preSubmitConverter: (data: Record) => { @@ -1793,10 +1793,10 @@ export const connectionTypes: ConnectionType[] = [ convertToFormSpecificValue: (data: Record) => { return { ...data, - contentType: data.properties?.contentType, - requestMethod: data.properties?.requestMethod, - key: data.properties?.key, - titleKey: data.properties?.titleKey + contentType: data?.properties?.contentType, + requestMethod: data?.properties?.requestMethod, + key: data?.properties?.key, + titleKey: data?.properties?.titleKey } as Connection; }, preSubmitConverter: (data: Record) => { @@ -1878,7 +1878,7 @@ export const connectionTypes: ConnectionType[] = [ convertToFormSpecificValue: (data: Record) => { return { ...data, - ref: data.properties?.ref + ref: data?.properties?.ref ?? "ref" } as Connection; }, preSubmitConverter: (data: Record) => { From 43cd592f616b82079f84724557e7e5392c952f68 Mon Sep 17 00:00:00 2001 From: Maina Wycliffe Date: Wed, 1 Nov 2023 23:50:06 +0300 Subject: [PATCH 6/9] refactor: use react query for fetching connections --- src/pages/Settings/ConnectionsPage.tsx | 51 +++++++++----------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/pages/Settings/ConnectionsPage.tsx b/src/pages/Settings/ConnectionsPage.tsx index acab9f7a0..096a084c1 100644 --- a/src/pages/Settings/ConnectionsPage.tsx +++ b/src/pages/Settings/ConnectionsPage.tsx @@ -1,3 +1,4 @@ +import { useQuery } from "@tanstack/react-query"; import { useEffect, useState } from "react"; import { AiFillPlusCircle } from "react-icons/ai"; import { @@ -16,7 +17,6 @@ import { SearchLayout } from "../../components/Layout"; import { SchemaApi } from "../../components/SchemaResourcePage/resourceTypes"; import { toastError, toastSuccess } from "../../components/Toast/toast"; import { useUser } from "../../context"; -import { useLoader } from "../../hooks"; const connectionsSchemaConnection: SchemaApi = { table: "connections", @@ -25,27 +25,21 @@ const connectionsSchemaConnection: SchemaApi = { }; export function ConnectionsPage() { - const [connections, setConnections] = useState([]); const user = useUser(); const [isOpen, setIsOpen] = useState(false); - const { loading, setLoading } = useLoader(); const [editedRow, setEditedRow] = useState(); - async function fetchConnections() { - setLoading(true); - try { + const { + isLoading: loading, + data: connections, + refetch + } = useQuery({ + queryKey: ["connections", "all"], + queryFn: async () => { const response = await getAll(connectionsSchemaConnection); - if (response.data) { - setConnections(response.data as unknown as Connection[]); - setLoading(false); - return; - } - toastError(response.statusText); - } catch (ex) { - toastError((ex as Error).message); + return (response.data ?? []) as unknown as Connection[]; } - setLoading(false); - } + }); async function onSubmit(data: Connection) { if (!editedRow?.id) { @@ -56,16 +50,15 @@ export function ConnectionsPage() { } async function createConnection(data: Connection) { - setLoading(true); try { const response = await createResource(connectionsSchemaConnection, { ...data, created_by: user.user?.id }); if (response?.data) { - fetchConnections(); + refetch(); setIsOpen(false); - setLoading(false); + toastSuccess("Connection added successfully"); return; } @@ -73,20 +66,17 @@ export function ConnectionsPage() { } catch (ex) { toastError((ex as Error).message); } - setLoading(false); } async function updateConnection(data: Connection) { - setLoading(true); try { const response = await updateResource(connectionsSchemaConnection, { ...data, created_by: user.user?.id }); if (response?.data) { - fetchConnections(); + refetch(); setIsOpen(false); - setLoading(false); toastSuccess("Connection updated successfully"); return; } @@ -94,11 +84,9 @@ export function ConnectionsPage() { } catch (ex) { toastError((ex as Error).message); } - setLoading(false); } async function onDelete(data: Connection) { - setLoading(true); try { const response = await deleteResource( connectionsSchemaConnection, @@ -106,9 +94,9 @@ export function ConnectionsPage() { ); setEditedRow(undefined); if (response?.data) { - fetchConnections(); + refetch(); setIsOpen(false); - setLoading(false); + toastSuccess("Connection removed successfully"); return; } @@ -116,13 +104,8 @@ export function ConnectionsPage() { } catch (ex) { toastError((ex as Error).message); } - setLoading(false); } - useEffect(() => { - fetchConnections(); - }, []); - useEffect(() => { if (isOpen) { return; @@ -152,7 +135,7 @@ export function ConnectionsPage() { /> } onRefresh={() => { - fetchConnections(); + refetch(); }} contentClass="p-0 h-full" loading={loading} @@ -160,7 +143,7 @@ export function ConnectionsPage() {
{ setIsOpen(true); From 966f3730bf96b31adba978a4d5259b0236e93bd2 Mon Sep 17 00:00:00 2001 From: Maina Wycliffe Date: Thu, 16 Nov 2023 09:47:53 +0300 Subject: [PATCH 7/9] fix: fix failing build --- src/components/Forms/Formik/FormikConnectionField.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Forms/Formik/FormikConnectionField.tsx b/src/components/Forms/Formik/FormikConnectionField.tsx index 3f2390926..9116922c0 100644 --- a/src/components/Forms/Formik/FormikConnectionField.tsx +++ b/src/components/Forms/Formik/FormikConnectionField.tsx @@ -1,9 +1,9 @@ import { useQuery } from "@tanstack/react-query"; import { getAll } from "../../../api/schemaResources"; +import { Connection } from "../../Connections/ConnectionFormModal"; +import { Icon } from "../../Icon"; import { toastError } from "../../Toast/toast"; import FormikSelectDropdown from "./FormikSelectDropdown"; -import { Icon } from "../../Icon"; -import { Connection } from "../../Connections/ConnectionForm"; type Props = { name: string; From 0d1a7795223b57d6f8311b10d30e6ef74d8e1cc1 Mon Sep 17 00:00:00 2001 From: Maina Wycliffe Date: Mon, 6 Nov 2023 10:01:14 +0300 Subject: [PATCH 8/9] feat: use AuthFields to for git password and username fix: fix issue withEnvVarSource not working --- .../FormikConnectionOptionsSwitchField.tsx | 8 +- .../RenderConnectionFormFields.tsx | 16 +- .../Connections/connectionTypes.tsx | 13 +- .../Formik/FormikCompactEnvVarSource.tsx | 148 ++++++++++++++++++ .../Forms/Formik/FormikEnvVarSource.tsx | 134 ++++------------ .../Formik/utils/FormikEnvVarK8SView.tsx | 65 ++++++++ .../Formik/utils/FormikEnvVarStaticView.tsx | 71 +++++++++ 7 files changed, 339 insertions(+), 116 deletions(-) create mode 100644 src/components/Forms/Formik/FormikCompactEnvVarSource.tsx create mode 100644 src/components/Forms/Formik/utils/FormikEnvVarK8SView.tsx create mode 100644 src/components/Forms/Formik/utils/FormikEnvVarStaticView.tsx diff --git a/src/components/Connections/FormikConnectionOptionsSwitchField.tsx b/src/components/Connections/FormikConnectionOptionsSwitchField.tsx index c58cfe882..9260ba702 100644 --- a/src/components/Connections/FormikConnectionOptionsSwitchField.tsx +++ b/src/components/Connections/FormikConnectionOptionsSwitchField.tsx @@ -17,7 +17,7 @@ export default function FormikConnectionOptionsSwitchField({ field }: Props) { const firstField = field.options?.find((option) => { return option.fields?.find((field) => get(values, field.key)); }); - return firstField?.key ?? field.default ?? field.options?.[0]?.key; + return firstField?.key ?? "None"; }); if (!field.options) { @@ -33,7 +33,7 @@ export default function FormikConnectionOptionsSwitchField({ field }: Props) {
option.label)]} + options={["None", ...field.options?.map((option) => option.label)]} defaultValue="None" value={ field.options?.find((option) => option.key === selectedGroup)?.label @@ -47,12 +47,12 @@ export default function FormikConnectionOptionsSwitchField({ field }: Props) { setFieldValue(option.key, undefined); }); setSelectedGroup( - field.options?.find((option) => option.label === v)?.key + field.options?.find((option) => option.label === v)?.key ?? "None" ); }} />
-
+
{selectedField?.fields?.map((field) => ( ))} diff --git a/src/components/Connections/RenderConnectionFormFields.tsx b/src/components/Connections/RenderConnectionFormFields.tsx index f3d9b3d62..a3f4cac24 100644 --- a/src/components/Connections/RenderConnectionFormFields.tsx +++ b/src/components/Connections/RenderConnectionFormFields.tsx @@ -1,7 +1,8 @@ -import FormikConnectionOptionsSwitchField from "./FormikConnectionOptionsSwitchField"; import FormikCheckbox from "../Forms/Formik/FormikCheckbox"; +import { FormikCompactEnvVarSource } from "../Forms/Formik/FormikCompactEnvVarSource"; import { FormikEnvVarSource } from "../Forms/Formik/FormikEnvVarSource"; import FormikTextInput from "../Forms/Formik/FormikTextInput"; +import FormikConnectionOptionsSwitchField from "./FormikConnectionOptionsSwitchField"; import { ConnectionFormFields } from "./connectionTypes"; interface FieldViewProps { @@ -46,7 +47,7 @@ export default function RenderConnectionFormFields({ field }: FieldViewProps) { return ( ; + + case "authentication": + return ( + + ); default: return null; } diff --git a/src/components/Connections/connectionTypes.tsx b/src/components/Connections/connectionTypes.tsx index 69f43b7ec..d627c47dd 100644 --- a/src/components/Connections/connectionTypes.tsx +++ b/src/components/Connections/connectionTypes.tsx @@ -8,7 +8,8 @@ const enum ConnectionsFieldTypes { input = "input", numberInput = "numberInput", EnvVarSource = "EnvVarSource", - switch = "switch" + switch = "switch", + Authentication = "authentication" } type Variant = "small" | "large"; @@ -26,6 +27,7 @@ export type ConnectionFormFields = { required?: boolean; hint?: string; default?: boolean | number | string; + hideLabel?: boolean; options?: { label: string; key: string; @@ -1838,27 +1840,28 @@ export const connectionTypes: ConnectionType[] = [ default: "password", options: [ { - label: "Password", + label: "Basic", key: "password", fields: [ { label: "Username", key: "username", - type: ConnectionsFieldTypes.EnvVarSource + type: ConnectionsFieldTypes.Authentication }, { label: "Password", key: "password", - type: ConnectionsFieldTypes.EnvVarSource + type: ConnectionsFieldTypes.Authentication } ] }, { - label: "SSH Key", + label: "SSH", key: "certificate", fields: [ { label: "SSH Key", + hideLabel: true, key: "certificate", type: ConnectionsFieldTypes.EnvVarSource, variant: variants.large diff --git a/src/components/Forms/Formik/FormikCompactEnvVarSource.tsx b/src/components/Forms/Formik/FormikCompactEnvVarSource.tsx new file mode 100644 index 000000000..37ed3179e --- /dev/null +++ b/src/components/Forms/Formik/FormikCompactEnvVarSource.tsx @@ -0,0 +1,148 @@ +import { useField } from "formik"; +import { useEffect, useState } from "react"; +import { Switch } from "../../Switch"; +import { + EnvVarSourceType, + FormikEnvVarSourceProps, + configmapValueRegex, + secretValueRegex +} from "./FormikEnvVarSource"; +import FormikEnvVarK8SView from "./utils/FormikEnvVarK8SView"; +import FormikEnvVarStaticView from "./utils/FormikEnvVarStaticView"; + +export function FormikCompactEnvVarSource({ + className, + variant = "small", + name, + label, + hint, + disabled, + readOnly, + required, + ...props +}: FormikEnvVarSourceProps) { + const [type, setType] = useState("Static"); + const prefix = `${name}.${type === "ConfigMap" ? "configmap" : "secret"}`; + const [data, setData] = useState({ + static: "", + name: "", + key: "" + }); + const [field, meta] = useField({ + name: name!, + type: "text", + required, + validate: (value) => { + if (required && !value) { + return "This field is required"; + } + } + }); + + useEffect(() => { + const value = getValue(); + if (field.value !== value) { + field.onChange({ + target: { + name, + value + } + }); + } + }, [data]); + + useEffect(() => { + if (field.value === getValue()) { + return; + } + let value = + field.value?.match(configmapValueRegex) || + field.value?.match(secretValueRegex); + if (value?.length === 3 && field.value?.includes("configmap")) { + setData({ + static: "", + key: value[2], + name: value[1] + }); + setType("ConfigMap"); + return; + } + if (value?.length === 3 && field.value?.includes("secret")) { + setData({ + static: "", + key: value[2], + name: value[1] + }); + setType("Secret"); + return; + } + setData({ + static: field.value, + key: "", + name: "" + }); + setType("Static"); + }, []); + + const getValue = () => { + let value = ""; + if (type === "Static") { + value = data.static; + } + if (type === "Secret" && data.name && data.key) { + value = `secret://${data.name}/${data.key}`; + } + if (type === "ConfigMap" && data.name && data.key) { + value = `configmap://${data.name}/${data.key}`; + } + return value; + }; + + return ( +
+ +
+
+ {type === "Static" ? ( + + ) : ( + + )} + {meta.touched && meta.error ? ( +

{meta.error}

+ ) : null} +
+ { + if (readOnly || disabled) { + return; + } + setData({ + static: "", + key: "", + name: "" + }); + setType(v as EnvVarSourceType); + }} + className="w-[24rem]" + /> +
+ {hint &&

{hint}

} +
+ ); +} diff --git a/src/components/Forms/Formik/FormikEnvVarSource.tsx b/src/components/Forms/Formik/FormikEnvVarSource.tsx index ad742e119..5cfed41e8 100644 --- a/src/components/Forms/Formik/FormikEnvVarSource.tsx +++ b/src/components/Forms/Formik/FormikEnvVarSource.tsx @@ -1,20 +1,20 @@ +import { useField } from "formik"; import { useEffect, useState } from "react"; import { Switch } from "../../Switch"; -import { TextInput } from "../../TextInput"; -import { TextArea } from "../../TextArea/TextArea"; -import { useField } from "formik"; +import FormikEnvVarK8SView from "./utils/FormikEnvVarK8SView"; +import FormikEnvVarStaticView from "./utils/FormikEnvVarStaticView"; -type FormikEnvVarSourceProps = React.HTMLProps< +export type FormikEnvVarSourceProps = React.HTMLProps< HTMLInputElement | HTMLTextAreaElement > & { variant?: "small" | "large"; hint?: string; }; -type EnvVarSourceType = "Static" | "K8S Secret" | "K8S Configmap"; +export type EnvVarSourceType = "Static" | "Secret" | "ConfigMap"; -const configmapValueRegex = /^configmap:\/\/(.+)?\/(.+)?/; -const secretValueRegex = /^secret:\/\/(.+)?\/(.+)?/; +export const configmapValueRegex = /^configmap:\/\/(.+)?\/(.+)?/; +export const secretValueRegex = /^secret:\/\/(.+)?\/(.+)?/; export function FormikEnvVarSource({ className, @@ -28,7 +28,7 @@ export function FormikEnvVarSource({ ...props }: FormikEnvVarSourceProps) { const [type, setType] = useState("Static"); - const prefix = `${name}.${type === "K8S Configmap" ? "configmap" : "secret"}`; + const prefix = `${name}.${type === "ConfigMap" ? "configmap" : "secret"}`; const [data, setData] = useState({ static: "", name: "", @@ -69,7 +69,7 @@ export function FormikEnvVarSource({ key: value[2], name: value[1] }); - setType("K8S Configmap"); + setType("ConfigMap"); return; } if (value?.length === 3 && field.value?.includes("secret")) { @@ -78,7 +78,7 @@ export function FormikEnvVarSource({ key: value[2], name: value[1] }); - setType("K8S Secret"); + setType("Secret"); return; } setData({ @@ -94,110 +94,21 @@ export function FormikEnvVarSource({ if (type === "Static") { value = data.static; } - if (type === "K8S Secret" && data.name && data.key) { + if (type === "Secret" && data.name && data.key) { value = `secret://${data.name}/${data.key}`; } - if (type === "K8S Configmap" && data.name && data.key) { + if (type === "ConfigMap" && data.name && data.key) { value = `configmap://${data.name}/${data.key}`; } return value; }; - const getStaticView = () => { - if (variant === "large") { - return ( -