From 88363b543f6f963247c332e9d7830bc782ed6e2d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torbj=C3=B8rn=20Vatn?=
Date: Tue, 21 Jun 2022 11:24:13 +0200
Subject: [PATCH] Switch Markdown engine to markdown-it-py (#19702)
---
airflow/www/static/css/main.css | 4 ++
airflow/www/utils.py | 5 +-
setup.cfg | 3 +
tests/www/test_utils.py | 112 ++++++++++++++++++++++++++------
4 files changed, 103 insertions(+), 21 deletions(-)
diff --git a/airflow/www/static/css/main.css b/airflow/www/static/css/main.css
index 05eda9d42aa04..e735b7b9bb46b 100644
--- a/airflow/www/static/css/main.css
+++ b/airflow/www/static/css/main.css
@@ -465,6 +465,10 @@ label[for="timezone-other"],
z-index: 1070;
}
+details summary {
+ display: list-item;
+}
+
.menu-scroll {
max-height: 300px;
overflow-y: auto;
diff --git a/airflow/www/utils.py b/airflow/www/utils.py
index 2516e9108a344..c8b97ec901538 100644
--- a/airflow/www/utils.py
+++ b/airflow/www/utils.py
@@ -21,7 +21,6 @@
from typing import Any, Dict, List, Optional, Union
from urllib.parse import urlencode
-import markdown
import sqlalchemy as sqla
from flask import Response, request, url_for
from flask.helpers import flash
@@ -31,6 +30,7 @@
from flask_appbuilder.models.sqla.filters import get_field_setup_query, set_value_to_type
from flask_appbuilder.models.sqla.interface import SQLAInterface
from flask_babel import lazy_gettext
+from markdown_it import MarkdownIt
from markupsafe import Markup
from pendulum.datetime import DateTime
from pygments import highlight, lexers
@@ -476,10 +476,11 @@ def json_render(obj, lexer):
def wrapped_markdown(s, css_class='rich_doc'):
"""Convert a Markdown string to HTML."""
+ md = MarkdownIt("gfm-like")
if s is None:
return None
s = textwrap.dedent(s)
- return Markup(f'' + markdown.markdown(s, extensions=['tables']) + "
")
+ return Markup(f'{md.render(s)}
')
def get_attr_renderer():
diff --git a/setup.cfg b/setup.cfg
index e0976f9ba3b9b..1512a6201c46e 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -135,6 +135,7 @@ install_requires =
# we pin to the same upper-bound as connexion.
jsonschema>=3.2.0, <5.0
lazy-object-proxy
+ linkify-it-py>=2.0.0
lockfile>=0.12.2
markdown>=3.0
# Markupsafe 2.1.0 breaks with error: import name 'soft_unicode' from 'markupsafe'.
@@ -142,8 +143,10 @@ install_requires =
# https://github.com/pallets/markupsafe/issues/284
# or when we will be able to upgrade JINJA to newer version (currently limited due to Flask and
# Flask Application Builder)
+ markdown-it-py>=2.1.0
markupsafe>=1.1.1,<2.1.0
marshmallow-oneofschema>=2.0.1
+ mdit-py-plugins>=0.3.0
packaging>=14.0
pathspec~=0.9.0
pendulum>=2.0
diff --git a/tests/www/test_utils.py b/tests/www/test_utils.py
index 01c49e1fdcafd..8ac06f75de010 100644
--- a/tests/www/test_utils.py
+++ b/tests/www/test_utils.py
@@ -184,29 +184,54 @@ def test_markdown_none(self):
class TestWrappedMarkdown(unittest.TestCase):
def test_wrapped_markdown_with_docstring_curly_braces(self):
rendered = wrapped_markdown("{braces}", css_class="a_class")
- assert '' == rendered
+ assert (
+ ''''''
+ == rendered
+ )
def test_wrapped_markdown_with_some_markdown(self):
- rendered = wrapped_markdown("*italic*\n**bold**\n", css_class="a_class")
+ rendered = wrapped_markdown(
+ """*italic*
+ **bold**
+ """,
+ css_class="a_class",
+ )
+
assert (
''''''
+bold
+'''
== rendered
)
def test_wrapped_markdown_with_table(self):
rendered = wrapped_markdown(
- """| Job | Duration |
- | ----------- | ----------- |
- | ETL | 14m |"""
+ """
+| Job | Duration |
+| ----------- | ----------- |
+| ETL | 14m |
+"""
)
assert (
- '\n\n\nJob | \n'
- 'Duration | \n
\n\n\n\nETL'
- ' | \n14m | \n
\n\n'
- '
'
- ) == rendered
+ '''
+
+
+Job |
+Duration |
+
+
+
+
+ETL |
+14m |
+
+
+
+
'''
+ == rendered
+ )
def test_wrapped_markdown_with_indented_lines(self):
rendered = wrapped_markdown(
@@ -217,7 +242,11 @@ def test_wrapped_markdown_with_indented_lines(self):
"""
)
- assert 'header
\n
1st line\n2nd line
' == rendered
+ assert (
+ '''header
\n
1st line\n2nd line
+
'''
+ == rendered
+ )
def test_wrapped_markdown_with_raw_code_block(self):
rendered = wrapped_markdown(
@@ -235,10 +264,12 @@ def test_wrapped_markdown_with_raw_code_block(self):
)
assert (
- 'Markdown code block
\n'
- '
Inline code
works well.
\n'
- '
Code block\ndoes not\nrespect\nnewlines\n
'
- ) == rendered
+ '''Markdown code block
+
Inline code
works well.
+
Code block\ndoes not\nrespect\nnewlines\n
+
'''
+ == rendered
+ )
def test_wrapped_markdown_with_nested_list(self):
rendered = wrapped_markdown(
@@ -251,6 +282,49 @@ def test_wrapped_markdown_with_nested_list(self):
)
assert (
- 'Docstring with a code block
\n'
- '
'
- ) == rendered
+ '''Docstring with a code block
+
+
'''
+ == rendered
+ )
+
+ def test_wrapped_markdown_with_collapsible_section(self):
+ rendered = wrapped_markdown(
+ """
+# A collapsible section with markdown
+
+ Click to expand!
+
+ ## Heading
+ 1. A numbered
+ 2. list
+ * With some
+ * Sub bullets
+
+ """
+ )
+
+ assert (
+ '''A collapsible section with markdown
+
+ Click to expand!
+Heading
+
+- A numbered
+- list
+
+- With some
+- Sub bullets
+
+
+
+
+
'''
+ == rendered
+ )