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)
