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"""