fix(platform): show "Connection URL" instead of "API Key" for database credentials

The SQL query block's credential dialog was misleadingly labeled since a
database connection URL is not an API key. This updates both backend and
frontend:

- Shorten the DatabaseCredentialsField description so it no longer
  truncates in the UI
- Make credential labels provider-aware so the database provider shows
  "Connection URL" instead of "API Key" in tab labels, input fields,
  placeholders, and action buttons
This commit is contained in:
Zamil Majdy
2026-03-26 13:40:55 +07:00
parent dd3349e6bc
commit 292be77b86
7 changed files with 67 additions and 22 deletions

View File

@@ -49,13 +49,7 @@ DatabaseCredentialsInput = CredentialsMetaInput[
def DatabaseCredentialsField() -> DatabaseCredentialsInput:
return CredentialsField(
description=(
"SQLAlchemy connection URL for your database. Examples: "
"postgresql://user:pass@host:5432/db, "
"mysql://user:pass@host:3306/db, "
"sqlite:///path/to/db, "
"mssql+pyodbc://user:pass@host/db?driver=..."
),
description="Database connection URL (e.g., postgresql://user:pass@host:5432/db)",
)

View File

@@ -32,6 +32,7 @@ export function APIKeyCredentialsModal({
isLoading,
isSubmitting,
supportsApiKey,
provider,
providerName,
schemaDescription,
onSubmit,
@@ -41,9 +42,23 @@ export function APIKeyCredentialsModal({
return null;
}
// Use provider-specific labels for non-API-key secrets (e.g. database URLs)
const isDatabase = provider === "database";
const dialogTitle = isDatabase
? `Add new credential for ${providerName ?? ""}`
: `Add new API key for ${providerName ?? ""}`;
const secretLabel = isDatabase ? "Connection URL" : "API Key";
const namePlaceholder = isDatabase
? "Enter a name for this credential..."
: "Enter a name for this API Key...";
const secretPlaceholder = isDatabase
? "Enter connection URL..."
: "Enter API Key...";
const submitLabel = isDatabase ? "Add Credential" : "Add API Key";
return (
<Dialog
title={`Add new API key for ${providerName ?? ""}`}
title={dialogTitle}
controlled={{
isOpen: open,
set: (isOpen) => {
@@ -73,7 +88,7 @@ export function APIKeyCredentialsModal({
id="title"
label="Name"
type="text"
placeholder="Enter a name for this API Key..."
placeholder={namePlaceholder}
{...field}
/>
)}
@@ -85,9 +100,9 @@ export function APIKeyCredentialsModal({
<>
<Input
id="apiKey"
label="API Key"
label={secretLabel}
type="password"
placeholder="Enter API Key..."
placeholder={secretPlaceholder}
hint={
schema.credentials_scopes ? (
<FormDescription>
@@ -145,7 +160,7 @@ export function APIKeyCredentialsModal({
loading={isSubmitting}
disabled={isSubmitting}
>
Add API Key
{submitLabel}
</Button>
</form>
</Form>

View File

@@ -123,7 +123,7 @@ export function CredentialRow({
</Text>
{isRealCredentialType && (
<span className="shrink-0 rounded bg-zinc-100 px-1.5 py-0.5 text-[0.625rem] font-medium leading-tight text-zinc-500">
{getCredentialTypeLabel(credType)}
{getCredentialTypeLabel(credType, provider)}
</span>
)}
</div>

View File

@@ -76,7 +76,7 @@ export function CredentialTypeSelector({
className="inline-flex items-center gap-1.5"
>
<Icon size={16} />
{getCredentialTypeLabel(type)}
{getCredentialTypeLabel(type, provider)}
</TabsLineTrigger>
);
})}
@@ -98,6 +98,7 @@ export function CredentialTypeSelector({
<TabsLineContent value="api_key">
<APIKeyTabContent
schema={schema}
provider={provider}
siblingInputs={siblingInputs}
onCredentialsCreate={(creds) => {
onCredentialsCreate(creds);
@@ -163,12 +164,14 @@ function OAuthTabContent({ providerName, onOAuthLogin }: OAuthTabContentProps) {
type APIKeyTabContentProps = {
schema: BlockIOCredentialsSubSchema;
provider: string;
siblingInputs?: Record<string, unknown>;
onCredentialsCreate: (creds: CredentialsMetaInput) => void;
};
function APIKeyTabContent({
schema,
provider,
siblingInputs,
onCredentialsCreate,
}: APIKeyTabContentProps) {
@@ -181,6 +184,17 @@ function APIKeyTabContent({
onSubmit,
} = useAPIKeyCredentialsModal({ schema, siblingInputs, onCredentialsCreate });
// Use provider-specific labels for non-API-key secrets (e.g. database URLs)
const isDatabase = provider === "database";
const secretLabel = isDatabase ? "Connection URL" : "API Key";
const namePlaceholder = isDatabase
? "Enter a name for this credential..."
: "Enter a name for this API Key...";
const secretPlaceholder = isDatabase
? "Enter connection URL..."
: "Enter API Key...";
const submitLabel = isDatabase ? "Add Credential" : "Add API Key";
if (!supportsApiKey && !isLoading) {
return null;
}
@@ -215,7 +229,7 @@ function APIKeyTabContent({
id="title"
label="Name"
type="text"
placeholder="Enter a name for this API Key..."
placeholder={namePlaceholder}
{...field}
/>
)}
@@ -226,9 +240,9 @@ function APIKeyTabContent({
render={({ field }) => (
<Input
id="apiKey"
label="API Key"
label={secretLabel}
type="password"
placeholder="Enter API Key..."
placeholder={secretPlaceholder}
hint={
schema.credentials_scopes ? (
<FormDescription>
@@ -268,7 +282,7 @@ function APIKeyTabContent({
loading={isSubmitting}
disabled={isSubmitting}
>
Add API Key
{submitLabel}
</Button>
</form>
</Form>

View File

@@ -91,7 +91,11 @@ export function CredentialsSelect({
{credentials.map((credential) => (
<option key={credential.id} value={credential.id}>
{getCredentialDisplayName(credential, displayName)} (
{getCredentialTypeLabel(credential.type as CredentialsType)})
{getCredentialTypeLabel(
credential.type as CredentialsType,
provider,
)}
)
</option>
))}
</select>

View File

@@ -104,7 +104,20 @@ const CREDENTIAL_TYPE_LABELS: Record<CredentialsType, string> = {
host_scoped: "Headers",
};
export function getCredentialTypeLabel(type: CredentialsType): string {
// Providers where api_key credentials represent a connection URL, not an API key
const CONNECTION_URL_PROVIDERS = new Set(["database"]);
export function getCredentialTypeLabel(
type: CredentialsType,
provider?: string,
): string {
if (
type === "api_key" &&
provider &&
CONNECTION_URL_PROVIDERS.has(provider)
) {
return "Connection URL";
}
return CREDENTIAL_TYPE_LABELS[type] ?? type;
}
@@ -131,7 +144,9 @@ export function getActionButtonText(
supportsUserPassword: boolean,
supportsHostScoped: boolean,
hasExistingCredentials: boolean,
provider?: string,
): string {
const isConnectionUrl = provider && CONNECTION_URL_PROVIDERS.has(provider);
const multipleTypes =
countSupportedTypes(
supportsOAuth2,
@@ -146,13 +161,15 @@ export function getActionButtonText(
if (hasExistingCredentials) {
if (supportsOAuth2) return "Connect another account";
if (supportsApiKey) return "Use a new API key";
if (supportsApiKey)
return isConnectionUrl ? "Use a new connection URL" : "Use a new API key";
if (supportsUserPassword) return "Add a new username and password";
if (supportsHostScoped) return "Add new headers";
return "Add new credentials";
} else {
if (supportsOAuth2) return "Add account";
if (supportsApiKey) return "Add API key";
if (supportsApiKey)
return isConnectionUrl ? "Add connection URL" : "Add API key";
if (supportsUserPassword) return "Add username and password";
if (supportsHostScoped) return "Add headers";
return "Add credentials";

View File

@@ -357,6 +357,7 @@ export function useCredentialsInput({
supportsUserPassword,
supportsHostScoped,
userCredentials.length > 0,
provider,
),
setAPICredentialsModalOpen,
setUserPasswordCredentialsModalOpen,