This is an automated email from the ASF dual-hosted git repository.

bbovenzi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 23a9a99e93b feat: add test connection button to ui (#51055)
23a9a99e93b is described below

commit 23a9a99e93b40e6a20bab3ad8b1d393f6550b9b3
Author: Zhen-Lun (Kevin) Hong <[email protected]>
AuthorDate: Fri May 30 22:46:54 2025 +0800

    feat: add test connection button to ui (#51055)
    
    * feat: add test connection button to ui
    
    * fix: determine test connection access based on configuration settings
    
    * fix: simplify button design
    
    * refactor: small changes on test connection button
    
    * fix: remove toasts
    
    * fix: add loading prop to the button
    
    * feat: support i18n in test connection button
---
 airflow-core/src/airflow/ui/src/i18n/config.ts     |  4 +-
 .../ui/src/i18n/locales/en/connections.json        |  4 +
 .../ui/src/pages/Connections/Connections.tsx       |  2 +
 .../src/pages/Connections/TestConnectionButton.tsx | 89 ++++++++++++++++++++++
 .../airflow/ui/src/queries/useTestConnection.ts    | 43 +++++++++++
 5 files changed, 141 insertions(+), 1 deletion(-)

diff --git a/airflow-core/src/airflow/ui/src/i18n/config.ts 
b/airflow-core/src/airflow/ui/src/i18n/config.ts
index 2af9d4c7acc..4161febba45 100644
--- a/airflow-core/src/airflow/ui/src/i18n/config.ts
+++ b/airflow-core/src/airflow/ui/src/i18n/config.ts
@@ -23,6 +23,7 @@ import { initReactI18next } from "react-i18next";
 import deCommon from "./locales/de/common.json";
 import deDashboard from "./locales/de/dashboard.json";
 import enCommon from "./locales/en/common.json";
+import enConnections from "./locales/en/connections.json";
 import enDags from "./locales/en/dags.json";
 import enDashboard from "./locales/en/dashboard.json";
 import koCommon from "./locales/ko/common.json";
@@ -48,7 +49,7 @@ export const supportedLanguages = [
 ] as const;
 
 export const defaultLanguage = "en";
-export const namespaces = ["common", "dashboard", "dags"] as const;
+export const namespaces = ["common", "dashboard", "dags", "connections"] as 
const;
 
 const resources = {
   de: {
@@ -57,6 +58,7 @@ const resources = {
   },
   en: {
     common: enCommon,
+    connections: enConnections,
     dags: enDags,
     dashboard: enDashboard,
   },
diff --git a/airflow-core/src/airflow/ui/src/i18n/locales/en/connections.json 
b/airflow-core/src/airflow/ui/src/i18n/locales/en/connections.json
new file mode 100644
index 00000000000..c50cd75a2c7
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/i18n/locales/en/connections.json
@@ -0,0 +1,4 @@
+{
+    "test": "Test Connection",
+    "testDisabled": "Testing connections disabled. Contact your admin to 
enable it."
+}
diff --git a/airflow-core/src/airflow/ui/src/pages/Connections/Connections.tsx 
b/airflow-core/src/airflow/ui/src/pages/Connections/Connections.tsx
index 68d96d82097..0f4ff93f391 100644
--- a/airflow-core/src/airflow/ui/src/pages/Connections/Connections.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Connections/Connections.tsx
@@ -38,6 +38,7 @@ import AddConnectionButton from "./AddConnectionButton";
 import DeleteConnectionButton from "./DeleteConnectionButton";
 import DeleteConnectionsButton from "./DeleteConnectionsButton";
 import EditConnectionButton from "./EditConnectionButton";
+import TestConnectionButton from "./TestConnectionButton";
 
 export type ConnectionBody = {
   conn_type: string;
@@ -104,6 +105,7 @@ const getColumns = ({
     accessorKey: "actions",
     cell: ({ row: { original } }) => (
       <Flex justifyContent="end">
+        <TestConnectionButton connection={original} />
         <EditConnectionButton connection={original} 
disabled={selectedRows.size > 0} />
         <DeleteConnectionButton connectionId={original.connection_id} 
disabled={selectedRows.size > 0} />
       </Flex>
diff --git 
a/airflow-core/src/airflow/ui/src/pages/Connections/TestConnectionButton.tsx 
b/airflow-core/src/airflow/ui/src/pages/Connections/TestConnectionButton.tsx
new file mode 100644
index 00000000000..3fddfb62e8b
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/pages/Connections/TestConnectionButton.tsx
@@ -0,0 +1,89 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { useState } from "react";
+import { useTranslation } from "react-i18next";
+import { FiActivity, FiWifi, FiWifiOff } from "react-icons/fi";
+
+import type { ConnectionResponse, ConnectionBody } from 
"openapi/requests/types.gen";
+import ActionButton from "src/components/ui/ActionButton";
+import { useConfig } from "src/queries/useConfig";
+import { useTestConnection } from "src/queries/useTestConnection";
+
+type TestConnectionOption = "Disabled" | "Enabled" | "Hidden";
+type Props = {
+  readonly connection: ConnectionResponse;
+};
+
+const defaultIcon = <FiActivity />;
+const connectedIcon = <FiWifi color="green" />;
+const disconnectedIcon = <FiWifiOff color="red" />;
+
+const TestConnectionButton = ({ connection }: Props) => {
+  const { t: translate } = useTranslation("connections");
+  const [icon, setIcon] = useState(defaultIcon);
+  const testConnection = useConfig("test_connection");
+  let option: TestConnectionOption;
+
+  if (testConnection === "Enabled") {
+    option = "Enabled";
+  } else if (testConnection === "Hidden") {
+    option = "Hidden";
+  } else {
+    option = "Disabled";
+  }
+
+  const connectionBody: ConnectionBody = {
+    conn_type: connection.conn_type,
+    connection_id: connection.connection_id,
+    description: connection.description ?? "",
+    extra: connection.extra === "" || connection.extra === null ? "{}" : 
connection.extra,
+    host: connection.host ?? "",
+    login: connection.login ?? "",
+    password: connection.password ?? "",
+    port: Number(connection.port),
+    schema: connection.schema ?? "",
+  };
+
+  const { isPending, mutate } = useTestConnection((result) => {
+    if (result === undefined) {
+      setIcon(defaultIcon);
+    } else if (result === true) {
+      setIcon(connectedIcon);
+    } else {
+      setIcon(disconnectedIcon);
+    }
+  });
+
+  return (
+    <ActionButton
+      actionName={option === "Enabled" ? translate("test") : 
translate("testDisabled")}
+      disabled={option === "Disabled"}
+      display={option === "Hidden" ? "none" : "flex"}
+      icon={icon}
+      loading={isPending}
+      onClick={() => {
+        mutate({ requestBody: connectionBody });
+      }}
+      text={translate("test")}
+      withText={false}
+    />
+  );
+};
+
+export default TestConnectionButton;
diff --git a/airflow-core/src/airflow/ui/src/queries/useTestConnection.ts 
b/airflow-core/src/airflow/ui/src/queries/useTestConnection.ts
new file mode 100644
index 00000000000..f68b3c5d562
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/queries/useTestConnection.ts
@@ -0,0 +1,43 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { useQueryClient } from "@tanstack/react-query";
+import type { Dispatch, SetStateAction } from "react";
+
+import { useConnectionServiceTestConnection, 
useConnectionServiceGetConnectionsKey } from "openapi/queries";
+import type { ConnectionTestResponse } from "openapi/requests/types.gen";
+
+export const useTestConnection = (setConnected: 
Dispatch<SetStateAction<boolean | undefined>>) => {
+  const queryClient = useQueryClient();
+
+  const onSuccess = async (res: ConnectionTestResponse) => {
+    await queryClient.invalidateQueries({
+      queryKey: [useConnectionServiceGetConnectionsKey],
+    });
+    setConnected(res.status);
+  };
+
+  const onError = () => {
+    setConnected(false);
+  };
+
+  return useConnectionServiceTestConnection({
+    onError,
+    onSuccess,
+  });
+};

Reply via email to