You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by ur...@apache.org on 2021/11/30 04:15:54 UTC

[airflow] branch main updated: Dynamically enable "Test Connection" button by connection type (#19792)

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

uranusjr 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 121e1f1  Dynamically enable "Test Connection" button by connection type (#19792)
121e1f1 is described below

commit 121e1f197ac580ea4712b7a0e72b02cf7ed9b27a
Author: Josh Fell <48...@users.noreply.github.com>
AuthorDate: Mon Nov 29 23:15:03 2021 -0500

    Dynamically enable "Test Connection" button by connection type (#19792)
    
    Co-authored-by: Tzu-ping Chung <tp...@astronomer.io>
---
 airflow/models/connection.py                   | 21 +++++++---------
 airflow/providers_manager.py                   |  2 ++
 airflow/www/static/js/connection_form.js       | 33 +++++++++++++++++++++++++-
 airflow/www/templates/airflow/conn_create.html |  1 +
 airflow/www/templates/airflow/conn_edit.html   |  1 +
 airflow/www/views.py                           | 15 +++++++++++-
 6 files changed, 59 insertions(+), 14 deletions(-)

diff --git a/airflow/models/connection.py b/airflow/models/connection.py
index b3261a4..83213b0 100644
--- a/airflow/models/connection.py
+++ b/airflow/models/connection.py
@@ -287,26 +287,23 @@ class Connection(Base, LoggingMixin):
 
     def get_hook(self, *, hook_params=None):
         """Return hook based on conn_type"""
-        (
-            hook_class_name,
-            conn_id_param,
-            package_name,
-            hook_name,
-            connection_type,
-        ) = ProvidersManager().hooks.get(self.conn_type, (None, None, None, None, None))
-
-        if not hook_class_name:
+        hook = ProvidersManager().hooks.get(self.conn_type, None)
+
+        if hook is None:
             raise AirflowException(f'Unknown hook type "{self.conn_type}"')
         try:
-            hook_class = import_string(hook_class_name)
+            hook_class = import_string(hook.hook_class_name)
         except ImportError:
             warnings.warn(
-                "Could not import %s when discovering %s %s", hook_class_name, hook_name, package_name
+                "Could not import %s when discovering %s %s",
+                hook.hook_class_name,
+                hook.hook_name,
+                hook.package_name,
             )
             raise
         if hook_params is None:
             hook_params = {}
-        return hook_class(**{conn_id_param: self.conn_id}, **hook_params)
+        return hook_class(**{hook.connection_id_attribute_name: self.conn_id}, **hook_params)
 
     def __repr__(self):
         return self.conn_id
diff --git a/airflow/providers_manager.py b/airflow/providers_manager.py
index f0a8912..7b47a9a 100644
--- a/airflow/providers_manager.py
+++ b/airflow/providers_manager.py
@@ -173,6 +173,7 @@ class HookInfo(NamedTuple):
     package_name: str
     hook_name: str
     connection_type: str
+    connection_testable: bool
 
 
 class ConnectionFormWidgetInfo(NamedTuple):
@@ -691,6 +692,7 @@ class ProvidersManager(LoggingMixin):
             package_name=package_name,
             hook_name=hook_name,
             connection_type=connection_type,
+            connection_testable=hasattr(hook_class, 'test_connection'),
         )
 
     def _add_widgets(self, package_name: str, hook_class: type, widgets: Dict[str, Any]):
diff --git a/airflow/www/static/js/connection_form.js b/airflow/www/static/js/connection_form.js
index d2859f8..1e9afc4 100644
--- a/airflow/www/static/js/connection_form.js
+++ b/airflow/www/static/js/connection_form.js
@@ -113,9 +113,34 @@ function applyFieldBehaviours(connection) {
   }
 }
 
+/**
+ * Dynamically enable/disable the Test Connection button as determined by the selected
+ * connection type.
+ @param {string} connectionType The connection type to change to.
+ @param {Array} testableConnections Connection types that currently support testing via
+  Airflow REST API.
+ */
+function handleTestConnection(connectionType, testableConnections) {
+  const testButton = document.getElementById('test-connection');
+  const testConnEnabled = testableConnections.includes(connectionType);
+
+  if (testConnEnabled) {
+  // If connection type can be tested in via REST API, enable button and clear toolip.
+    $(testButton).prop('disabled', false).removeAttr('title');
+  } else {
+  // If connection type can NOT be tested via REST API, disable button and display toolip
+  // alerting the user.
+    $(testButton).prop('disabled', true)
+      .attr('title', 'This connection type does not currently support testing via '
+        + 'Airflow REST API.');
+  }
+}
+
 $(document).ready(() => {
   const fieldBehavioursElem = document.getElementById('field_behaviours');
   const config = JSON.parse(decode(fieldBehavioursElem.textContent));
+  const testableConnsElem = document.getElementById('testable_connection_types');
+  const testableConns = decode(testableConnsElem.textContent);
 
   // Prevent login/password fields from triggering browser auth extensions
   const form = document.getElementById('model_form');
@@ -129,8 +154,9 @@ $(document).ready(() => {
   const testConnBtn = $('<button id="test-connection" type="button" class="btn btn-sm btn-primary" '
     + 'style="margin-left: 3px; pointer-events: all">Test\n <i class="fa fa-rocket"></i></button>');
 
+  // Disable the Test Connection button if Airflow REST APIs are not enabled.
   if (!restApiEnabled) {
-    $(testConnBtn).addClass('disabled')
+    $(testConnBtn).prop('disabled', true)
       .attr('title', 'Airflow REST APIs have been disabled. '
         + 'See api->auth_backend section of the Airflow configuration.');
   }
@@ -156,6 +182,11 @@ $(document).ready(() => {
 
     // Apply behaviours to fields.
     applyFieldBehaviours(config[connType]);
+
+    // Enable/Disable the Test Connection button. Only applicable if Airflow REST APIs are enabled.
+    if (restApiEnabled) {
+      handleTestConnection(connType, testableConns);
+    }
   }
 
   /**
diff --git a/airflow/www/templates/airflow/conn_create.html b/airflow/www/templates/airflow/conn_create.html
index af2a7aa..af89c16 100644
--- a/airflow/www/templates/airflow/conn_create.html
+++ b/airflow/www/templates/airflow/conn_create.html
@@ -29,4 +29,5 @@
   {{ super() }}
   <script src="{{ url_for_asset('connectionForm.js') }}"></script>
   <script id="field_behaviours" type="text/json">{{ widgets["add"].field_behaviours }}</script>
+  <script id="testable_connection_types" type="text/json">{{ widgets["add"].testable_connection_types }}</script>
 {% endblock %}
diff --git a/airflow/www/templates/airflow/conn_edit.html b/airflow/www/templates/airflow/conn_edit.html
index f424d71..a9a4164 100644
--- a/airflow/www/templates/airflow/conn_edit.html
+++ b/airflow/www/templates/airflow/conn_edit.html
@@ -29,4 +29,5 @@
   {{ super() }}
   <script src="{{ url_for_asset(filename='connectionForm.js') }}"></script>
   <script id="field_behaviours" type="text/json">{{ widgets["edit"].field_behaviours }}</script>
+  <script id="testable_connection_types" type="text/json">{{ widgets["edit"].testable_connection_types }}</script>
 {% endblock %}
diff --git a/airflow/www/views.py b/airflow/www/views.py
index 372590e..e7f037d 100644
--- a/airflow/www/views.py
+++ b/airflow/www/views.py
@@ -3377,7 +3377,12 @@ def lazy_add_provider_discovered_options_to_connection_form():
 # Used to store a dictionary of field behaviours used to dynamically change available
 # fields in ConnectionForm based on type of connection chosen
 # See airflow.hooks.base_hook.DiscoverableHook for details on how to customize your Hooks.
-# those field behaviours are rendered as scripts in the conn_create.html and conn_edit.html templates
+#
+# Additionally, a list of connection types that support testing via Airflow REST API is stored to dynamically
+# enable/disable the Test Connection button.
+#
+# These field behaviours and testable connection types are rendered as scripts in the conn_create.html and
+# conn_edit.html templates.
 class ConnectionFormWidget(FormWidget):
     """Form widget used to display connection"""
 
@@ -3385,6 +3390,14 @@ class ConnectionFormWidget(FormWidget):
     def field_behaviours(self):
         return json.dumps(ProvidersManager().field_behaviours)
 
+    @cached_property
+    def testable_connection_types(self):
+        return [
+            connection_type
+            for connection_type, provider_info in ProvidersManager().hooks.items()
+            if provider_info.connection_testable
+        ]
+
 
 class ConnectionModelView(AirflowModelView):
     """View to show records from Connections table"""