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

kaxilnaik pushed a commit to branch fix-dagrun-edit
in repository https://gitbox.apache.org/repos/asf/airflow.git

commit 1892420b2a363912edcdad32907025e68bf886bc
Author: Kaxil Naik <[email protected]>
AuthorDate: Thu Dec 3 02:21:49 2020 +0000

    BugFix: Editing a DAG run or Task Instance on UI causes an Error
    
    closes https://github.com/apache/airflow/issues/12489
---
 airflow/www/forms.py   | 62 ++++++++++++++++++++++++++++++++++++++++++++++++--
 airflow/www/views.py   | 18 ++++++++++++++-
 airflow/www/widgets.py | 26 ++++++++++++++++++++-
 3 files changed, 102 insertions(+), 4 deletions(-)

diff --git a/airflow/www/forms.py b/airflow/www/forms.py
index 2485010..4b0f3ed 100644
--- a/airflow/www/forms.py
+++ b/airflow/www/forms.py
@@ -46,7 +46,12 @@ from airflow.configuration import conf
 from airflow.utils import timezone
 from airflow.utils.types import DagRunType
 from airflow.www.validators import ValidJson
-from airflow.www.widgets import AirflowDateTimePickerWidget
+from airflow.www.widgets import (
+    AirflowDateTimePickerROWidget,
+    AirflowDateTimePickerWidget,
+    BS3TextAreaROWidget,
+    BS3TextFieldROWidget,
+)
 
 
 class DateTimeWithTimezoneField(Field):
@@ -127,7 +132,7 @@ class 
DateTimeWithNumRunsWithDagRunsForm(DateTimeWithNumRunsForm):
 
 
 class DagRunForm(DynamicForm):
-    """Form for editing and adding DAG Run"""
+    """Form for adding DAG Run"""
 
     dag_id = StringField(lazy_gettext('Dag Id'), validators=[DataRequired()], 
widget=BS3TextFieldWidget())
     start_date = DateTimeWithTimezoneField(lazy_gettext('Start Date'), 
widget=AirflowDateTimePickerWidget())
@@ -158,6 +163,59 @@ class DagRunForm(DynamicForm):
             item.conf = json.loads(item.conf)
 
 
+class DagRunEditForm(DynamicForm):
+    """Form for editing DAG Run"""
+
+    dag_id = StringField(lazy_gettext('Dag Id'), validators=[DataRequired()], 
widget=BS3TextFieldROWidget())
+    start_date = DateTimeWithTimezoneField(lazy_gettext('Start Date'), 
widget=AirflowDateTimePickerROWidget())
+    end_date = DateTimeWithTimezoneField(lazy_gettext('End Date'), 
widget=AirflowDateTimePickerROWidget())
+    run_id = StringField(lazy_gettext('Run Id'), validators=[DataRequired()], 
widget=BS3TextFieldROWidget())
+    state = SelectField(
+        lazy_gettext('State'),
+        choices=(
+            ('success', 'success'),
+            ('running', 'running'),
+            ('failed', 'failed'),
+        ),
+        widget=Select2Widget(),
+    )
+    execution_date = DateTimeWithTimezoneField(
+        lazy_gettext('Execution Date'), widget=AirflowDateTimePickerROWidget()
+    )
+    conf = TextAreaField(
+        lazy_gettext('Conf'), validators=[ValidJson(), Optional()], 
widget=BS3TextAreaROWidget()
+    )
+
+    def populate_obj(self, item):
+        """Populates the attributes of the passed obj with data from the 
form’s fields."""
+        super().populate_obj(item)  # pylint: disable=no-member
+        item.run_type = DagRunType.from_run_id(item.run_id)
+        if item.conf:
+            item.conf = json.loads(item.conf)
+
+
+class TaskInstanceEditForm(DynamicForm):
+    """Form for editing TaskInstance"""
+
+    dag_id = StringField(lazy_gettext('Dag Id'), validators=[DataRequired()], 
widget=BS3TextFieldROWidget())
+    task_id = StringField(lazy_gettext('Task Id'), 
validators=[DataRequired()], widget=BS3TextFieldROWidget())
+    start_date = DateTimeWithTimezoneField(lazy_gettext('Start Date'), 
widget=AirflowDateTimePickerROWidget())
+    end_date = DateTimeWithTimezoneField(lazy_gettext('End Date'), 
widget=AirflowDateTimePickerROWidget())
+    state = SelectField(
+        lazy_gettext('State'),
+        choices=(
+            ('success', 'success'),
+            ('running', 'running'),
+            ('failed', 'failed'),
+            ('up_for_retry', 'up_for_retry'),
+        ),
+        widget=Select2Widget(),
+    )
+    execution_date = DateTimeWithTimezoneField(
+        lazy_gettext('Execution Date'), widget=AirflowDateTimePickerROWidget()
+    )
+
+
 _connection_types = [
     ('docker', 'Docker Registry'),
     ('elasticsearch', 'Elasticsearch'),
diff --git a/airflow/www/views.py b/airflow/www/views.py
index 1d386a4..70840dd 100644
--- a/airflow/www/views.py
+++ b/airflow/www/views.py
@@ -91,10 +91,12 @@ from airflow.www import auth, utils as wwwutils
 from airflow.www.decorators import action_logging, gzipped
 from airflow.www.forms import (
     ConnectionForm,
+    DagRunEditForm,
     DagRunForm,
     DateTimeForm,
     DateTimeWithNumRunsForm,
     DateTimeWithNumRunsWithDagRunsForm,
+    TaskInstanceEditForm,
 )
 from airflow.www.widgets import AirflowModelListWidget
 
@@ -3229,12 +3231,14 @@ class DagRunModelView(AirflowModelView):
     add_columns = ['state', 'dag_id', 'execution_date', 'run_id', 
'external_trigger', 'conf']
     list_columns = ['state', 'dag_id', 'execution_date', 'run_id', 'run_type', 
'external_trigger', 'conf']
     search_columns = ['state', 'dag_id', 'execution_date', 'run_id', 
'run_type', 'external_trigger', 'conf']
+    edit_columns = ['state', 'dag_id', 'execution_date', 'run_id', 'conf']
 
     base_order = ('execution_date', 'desc')
 
     base_filters = [['dag_id', DagFilter, lambda: []]]
 
-    add_form = edit_form = DagRunForm
+    add_form = DagRunForm
+    edit_form = DagRunEditForm
 
     formatters_columns = {
         'execution_date': wwwutils.datetime_f('execution_date'),
@@ -3391,6 +3395,7 @@ class TaskRescheduleModelView(AirflowModelView):
     route_base = '/taskreschedule'
 
     datamodel = AirflowModelView.CustomSQLAInterface(models.TaskReschedule)  # 
noqa # type: ignore
+    related_views = [DagRunModelView]
 
     class_permission_name = permissions.RESOURCE_TASK_RESCHEDULE
     method_permission_name = {
@@ -3500,6 +3505,17 @@ class TaskInstanceModelView(AirflowModelView):
         'end_date',
     ]
 
+    edit_columns = [
+        'state',
+        'dag_id',
+        'task_id',
+        'execution_date',
+        'start_date',
+        'end_date',
+    ]
+
+    edit_form = TaskInstanceEditForm
+
     base_order = ('job_id', 'asc')
 
     base_filters = [['dag_id', DagFilter, lambda: []]]
diff --git a/airflow/www/widgets.py b/airflow/www/widgets.py
index 172380c..ab2e4cf 100644
--- a/airflow/www/widgets.py
+++ b/airflow/www/widgets.py
@@ -15,7 +15,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-
+from flask_appbuilder.fieldwidgets import BS3TextAreaFieldWidget, 
BS3TextFieldWidget
 from flask_appbuilder.widgets import RenderTemplateWidget
 from markupsafe import Markup
 from wtforms.widgets import html_params
@@ -46,3 +46,27 @@ class AirflowDateTimePickerWidget:
         template = self.data_template
 
         return Markup(template % {"text": html_params(type="text", 
value=field.data, **kwargs)})
+
+
+class AirflowDateTimePickerROWidget(AirflowDateTimePickerWidget):
+    """Airflow Read-only date time picker widget"""
+
+    def __call__(self, field, **kwargs):
+        kwargs['readonly'] = 'true'
+        return super().__call__(field, **kwargs)
+
+
+class BS3TextFieldROWidget(BS3TextFieldWidget):
+    """Read-only single-line text input Widget (BS3TextFieldWidget)"""
+
+    def __call__(self, field, **kwargs):
+        kwargs['readonly'] = 'true'
+        return super().__call__(field, **kwargs)
+
+
+class BS3TextAreaROWidget(BS3TextAreaFieldWidget):
+    """Read-only multi-line text area Widget (BS3TextAreaROWidget)"""
+
+    def __call__(self, field, **kwargs):
+        kwargs['readonly'] = 'true'
+        return super().__call__(field, **kwargs)

Reply via email to