Repository: great-expectations/great_expectations

Issue:
Missing markown_text jinja template
**Describe the bug**
I am trying to render our JSON expectations to markdown. I followed a tutorial that showed how to do that for validation results but I think a jinja template is missing.

Here is the traceback:
```
~/Library/Caches/pypoetry/virtualenvs/ge-MUDrC1sS-py3.7/lib/python3.7/site-packages/great_expectations/render/view/view.py in _get_template(self, template)
    106         env.filters["add_data_context_id_to_url"] = self.add_data_context_id_to_url
    107 
--> 108         template = env.get_template(template)
    109         template.globals["now"] = lambda: datetime.datetime.now(datetime.timezone.utc)
    110 

TemplateNotFound: markdown_text.j2
```

I just hacked a template just containing:
```
{{ text | render_content_block }}
```
and it fixed the error, however the comments added to the expectations are missing!

**To Reproduce**
```
import great_expectations as ge
from great_expectations.render.renderer import ValidationResultsPageRenderer, ExpectationSuitePageRenderer
from great_expectations.render.view import DefaultMarkdownPageView
from great_expectations.validation_operators.types.validation_operator_result import (
    ValidationOperatorResult,
)

context = ge.DataContext()

def render_to_markdown(run_info_at_end, expectations):
    expectation_suite_page_renderer = ExpectationSuitePageRenderer()
    rendered_document_content_list = expectation_suite_page_renderer.render(
        expectations
    )
    return DefaultMarkdownPageView().render(rendered_document_content_list)

expectations = context.get_expectation_suite("suite-name")

render_to_markdown(True, expectations)
```

**Expected behavior**
I would expect the expectations to be rendered as markdown

**Environment (please complete the following information):**
 - OS: macOS
 - GE Version: 0.12.10




Discussion:
Thank you for reporting this, @lccambiaghi ! We are going to triage this issue within next couple of days.
@lccambiaghi Could you please post the link to the tutorial that you followed? Thank you!

This does look like a bug. Adding to the core team's queue.
I adapted the code from this util function: https://github.com/great-expectations/great_expectations/blob/develop/great_expectations/render/page_renderer_util.py#L37

I hope the example I provided is clear enough. We have worked around it for the moment by adding the custom jinja template to a custom_views_directory (nice feature!) 

```python
custom_views_directory = "assets/jinja_templates"

def render_expectations_to_markdown(expectations):
    rendered_dcument_contents = {
        dataset_name: ExpectationSuitePageRenderer().render(expectation)
        for dataset_name, expectation in expectations.items()
    }

    markdown_rendered_documents = {
        dataset_name: DefaultMarkdownPageView(custom_views_directory=custom_views_directory).render(
            document=rendered_document_content
        )
        for dataset_name, rendered_document_content in rendered_dcument_contents.items()
    }

    return markdown_rendered_documents
```

The output of this however is badly formatted. I use the python library `mdformat` to format the markdown plus some custom `replace()` calls. Of course, I would love this functionality to be implemented upstream, maybe streamlined with the other rendering capabilities you have.

One issue with our current approach is that comments to JSON expectations are not rendered to markdown. Here is a screenshot of the result:

![Screenshot 2020-12-10 at 11 13 10](https://user-images.githubusercontent.com/15185927/101758594-cd800c00-3ad8-11eb-9d2a-5a42179c71d1.png)

I would expect the comments to be inserted within the dotted lines.

Thank you so much for looking at this!
Hi @eugmandel I was wondering if there had been any progress on this issue? I don't mean to rush you or anything, I am sure you have many exciting features coming that are more worthy of your time! 
@Aylr @eugmandel what is the status of that issue?
Hi @gardnerdev and @lccambiaghi thank you for submitting the issue, and sorry about the delay. 

I just got assigned this issue through the Triage process. Should have something for you soon :) 

Resolving patch:
diff --git a/great_expectations/render/view/view.py b/great_expectations/render/view/view.py
--- a/great_expectations/render/view/view.py
+++ b/great_expectations/render/view/view.py
@@ -176,6 +176,7 @@ def render_content_block(
         content_block_type = content_block.get("content_block_type")
         if content_block_type is None:
             return content_block
+
         if render_to_markdown:
             template_filename = f"markdown_{content_block_type}.j2"
         else:
@@ -504,6 +505,11 @@ def render_string_template(self, template: pTemplate) -> pTemplate:
         if not isinstance(template, (dict, OrderedDict)):
             return template
 
+        # replace and render any horizontal lines using ***
+        tag = template.get("tag", None)
+        if tag and tag == "hr":
+            template["template"] = "***"
+
         # if there are any groupings of two or more $, we need to double the groupings to account
         # for template string substitution escaping
         template["template"] = re.sub(
@@ -524,6 +530,11 @@ def render_string_template(self, template: pTemplate) -> pTemplate:
             if parameter == "html_success_icon":
                 template["params"][parameter] = ""
                 continue
+            # to escape any values that are '*' which, when combined with bold ('**') in markdown,
+            # does not give the output we want.
+            elif template["params"][parameter] == "*":
+                template["params"][parameter] = "\\*"
+                continue
 
             template["params"][parameter] = pTemplate(
                 base_param_template_string


Test patch:
diff --git a/tests/render/test_default_markdown_view.py b/tests/render/test_default_markdown_view.py
--- a/tests/render/test_default_markdown_view.py
+++ b/tests/render/test_default_markdown_view.py
@@ -6,10 +6,14 @@
 
 import great_expectations as ge
 import great_expectations.render as render
+from great_expectations.core import ExpectationConfiguration, ExpectationSuite
 from great_expectations.core.expectation_validation_result import (
     ExpectationSuiteValidationResultSchema,
 )
-from great_expectations.render.renderer import ValidationResultsPageRenderer
+from great_expectations.render.renderer import (
+    ExpectationSuitePageRenderer,
+    ValidationResultsPageRenderer,
+)
 from great_expectations.render.types import (
     RenderedDocumentContent,
     RenderedHeaderContent,
@@ -40,6 +44,44 @@ def validation_operator_result():
         return validation_operator_result
 
 
+@pytest.fixture()
+def expectation_suite_to_render_with_notes():
+    expectation_suite = ExpectationSuite(
+        expectation_suite_name="default",
+        meta={"great_expectations_version": "0.13.0-test"},
+        expectations=[
+            ExpectationConfiguration(
+                expectation_type="expect_column_to_exist",
+                kwargs={"column": "infinities"},
+            ),
+            ExpectationConfiguration(
+                expectation_type="expect_column_to_exist", kwargs={"column": "nulls"}
+            ),
+            ExpectationConfiguration(
+                expectation_type="expect_column_to_exist", kwargs={"column": "naturals"}
+            ),
+            ExpectationConfiguration(
+                expectation_type="expect_column_distinct_values_to_be_in_set",
+                kwargs={"column": "irrationals", "value_set": ["*", "1st", "2nd"]},
+            ),
+            ExpectationConfiguration(
+                expectation_type="expect_column_values_to_be_unique",
+                kwargs={"column": "testings"},
+                meta={
+                    "notes": {
+                        "content": [
+                            "Example notes about this expectation. **Markdown** `Supported`.",
+                            "Second example note **with** *Markdown*",
+                        ],
+                        "format": "markdown",
+                    }
+                },
+            ),
+        ],
+    )
+    return expectation_suite
+
+
 def test_render_section_page():
     section = RenderedSectionContent(
         **{
@@ -124,65 +166,24 @@ def test_snapshot_render_section_page_with_fixture_data(validation_operator_resu
         md_str
         == """
 # Validation Results
-
-
-
-
 ## Overview
 ### **Expectation Suite:** **basic.warning**
 **Data asset:** **None**
 **Status:**  **Failed**
-
-
-
-
-
 ### Statistics
-
-
-
-
-
-
  |  |  |
  | ------------  | ------------ |
 Evaluated Expectations  | 11
 Successful Expectations  | 9
 Unsuccessful Expectations  | 2
 Success Percent  | ≈81.82%
-
-
-
-
-
 ## Table-Level Expectations
-
-
-
-
-
-
-
-
  | Status | Expectation | Observed Value |
  | ------------  | ------------  | ------------ |
 ❌  | Must have greater than or equal to **27000** and less than or equal to **33000** rows.  | 30
 ✅  | Must have exactly **3** columns.  | 3
 ✅  | Must have these columns in this order: **Team**, ** "Payroll (millions)"**, ** "Wins"**  | ['Team', ' "Payroll (millions)"', ' "Wins"']
-
-
-
-
-
 ##  "Payroll (millions)"
-
-
-
-
-
-
-
-
  | Status | Expectation | Observed Value |
  | ------------  | ------------  | ------------ |
 ✅  | values must never be null.  | 100% not null
@@ -191,10 +192,6 @@ def test_snapshot_render_section_page_with_fixture_data(validation_operator_resu
 ✅  | mean must be greater than or equal to **97.01899999999998** and less than or equal to **99.01899999999998**.  | ≈98.019
 ❌  | median must be greater than or equal to **84000.75** and less than or equal to **86000.75**.  | 85.75
 ✅  | quantiles must be within the following value ranges.
-
-
-
-
  | Quantile | Min Value | Max Value |
  | ------------  | ------------  | ------------ |
 0.05  | 54.37  | 56.37
@@ -203,10 +200,6 @@ def test_snapshot_render_section_page_with_fixture_data(validation_operator_resu
 Q3  | 116.62  | 118.62
 0.95  | 173.54  | 175.54
   |
-
-
-
-
  | Quantile | Value |
  | ------------  | ------------ |
 0.05  | 55.37
@@ -214,143 +207,49 @@ def test_snapshot_render_section_page_with_fixture_data(validation_operator_resu
 Median  | 83.31
 Q3  | 117.62
 0.95  | 174.54
-
-
-
-
-
-
 ## Team
-
-
-
-
-
-
-
-
  | Status | Expectation | Observed Value |
  | ------------  | ------------  | ------------ |
 ✅  | values must never be null.  | 100% not null
 ✅  | values must always be greater than or equal to **1** characters long.  | 0% unexpected
-
-
-
-
-
-
-
 ### Info
-
-
-
-
-
-
  |  |  |
  | ------------  | ------------ |
 Great Expectations Version  | 0.11.8+4.g4ab34df3.dirty
 Run Name  | getest run
 Run Time  | 2020-07-27T17:19:32.959193+00:00
-
-
-
-
-
 ### Batch Markers
-
-
-
-
-
-
  |  |  |
  | ------------  | ------------ |
 **ge_load_time**  | **20200727T171932.954810Z**
 **pandas_data_fingerprint**  | **8c46fdaf0bd356fd58b7bcd9b2e6012d**
-
-
-
-
-
 ### Batch Kwargs
-
-
-
-
-
-
  |  |  |
  | ------------  | ------------ |
 **PandasInMemoryDF**  | **True**
 **datasource**  | **getest**
 **ge_batch_id**  | **56615f40-d02d-11ea-b6ea-acde48001122**
-
-
-
-
 -----------------------------------------------------------
 Powered by [Great Expectations](https://greatexpectations.io/)
 # Validation Results
-
-
-
-
 ## Overview
 ### **Expectation Suite:** **basic.warning**
 **Data asset:** **None**
 **Status:**  **Failed**
-
-
-
-
-
 ### Statistics
-
-
-
-
-
-
  |  |  |
  | ------------  | ------------ |
 Evaluated Expectations  | 11
 Successful Expectations  | 9
 Unsuccessful Expectations  | 2
 Success Percent  | ≈81.82%
-
-
-
-
-
 ## Table-Level Expectations
-
-
-
-
-
-
-
-
  | Status | Expectation | Observed Value |
  | ------------  | ------------  | ------------ |
 ❌  | Must have greater than or equal to **27000** and less than or equal to **33000** rows.  | 30
 ✅  | Must have exactly **3** columns.  | 3
 ✅  | Must have these columns in this order: **Team**, ** "Payroll (millions)"**, ** "Wins"**  | ['Team', ' "Payroll (millions)"', ' "Wins"']
-
-
-
-
-
 ##  "Payroll (millions)"
-
-
-
-
-
-
-
-
  | Status | Expectation | Observed Value |
  | ------------  | ------------  | ------------ |
 ✅  | values must never be null.  | 100% not null
@@ -359,10 +258,6 @@ def test_snapshot_render_section_page_with_fixture_data(validation_operator_resu
 ✅  | mean must be greater than or equal to **97.01899999999998** and less than or equal to **99.01899999999998**.  | ≈98.019
 ❌  | median must be greater than or equal to **84000.75** and less than or equal to **86000.75**.  | 85.75
 ✅  | quantiles must be within the following value ranges.
-
-
-
-
  | Quantile | Min Value | Max Value |
  | ------------  | ------------  | ------------ |
 0.05  | 54.37  | 56.37
@@ -371,10 +266,6 @@ def test_snapshot_render_section_page_with_fixture_data(validation_operator_resu
 Q3  | 116.62  | 118.62
 0.95  | 173.54  | 175.54
   |
-
-
-
-
  | Quantile | Value |
  | ------------  | ------------ |
 0.05  | 55.37
@@ -382,81 +273,28 @@ def test_snapshot_render_section_page_with_fixture_data(validation_operator_resu
 Median  | 83.31
 Q3  | 117.62
 0.95  | 174.54
-
-
-
-
-
-
 ## Team
-
-
-
-
-
-
-
-
  | Status | Expectation | Observed Value |
  | ------------  | ------------  | ------------ |
 ✅  | values must never be null.  | 100% not null
 ✅  | values must always be greater than or equal to **1** characters long.  | 0% unexpected
-
-
-
-
-
-
-
 ### Info
-
-
-
-
-
-
  |  |  |
  | ------------  | ------------ |
 Great Expectations Version  | 0.11.8+4.g4ab34df3.dirty
 Run Name  | getest run
 Run Time  | 2020-07-27T17:19:32.959193+00:00
-
-
-
-
-
 ### Batch Markers
-
-
-
-
-
-
  |  |  |
  | ------------  | ------------ |
 **ge_load_time**  | **20200727T171932.954810Z**
 **pandas_data_fingerprint**  | **8c46fdaf0bd356fd58b7bcd9b2e6012d**
-
-
-
-
-
 ### Batch Kwargs
-
-
-
-
-
-
  |  |  |
  | ------------  | ------------ |
 **PandasInMemoryDF**  | **True**
 **datasource**  | **getest**
 **ge_batch_id**  | **56615f40-d02d-11ea-b6ea-acde48001122**
-
-
-
-
 -----------------------------------------------------------
 Powered by [Great Expectations](https://greatexpectations.io/)
 """.replace(
@@ -494,71 +332,30 @@ def test_render_section_page_with_fixture_data_multiple_validations(
 
     md_str = md_str.replace(" ", "").replace("\t", "").replace("\n", "")
 
-    print(md_str)
+    # print(md_str)
 
     assert (
         md_str
         == """
 # Validation Results
-
-
-
-
 ## Overview
 ### **Expectation Suite:** **basic.warning**
 **Dataasset:** **None**
 **Status:**  **Failed**
-
-
-
-
-
 ### Statistics
-
-
-
-
-
-
  |  |  |
  | ------------  | ------------ |
 Evaluated Expectations  | 11
 Successful Expectations  | 9
 Unsuccessful Expectations  | 2
 Success Percent  | ≈81.82%
-
-
-
-
-
 ## Table-Level Expectations
-
-
-
-
-
-
-
-
  | Status | Expectation | Observed Value |
  | ------------  | ------------  | ------------ |
 ❌  | Must have greater than or equal to **27000** and less than or equal to **33000** rows.  | 30
 ✅  | Must have exactly **3** columns.  | 3
 ✅  | Must have these columns in this order: **Team**, ** "Payroll (millions)"**, ** "Wins"**  | ['Team', ' "Payroll (millions)"', ' "Wins"']
-
-
-
-
-
 ##  "Payroll (millions)"
-
-
-
-
-
-
-
-
  | Status | Expectation | Observed Value |
  | ------------  | ------------  | ------------ |
 ✅  | values must never be null.  | 100% not null
@@ -567,10 +364,6 @@ def test_render_section_page_with_fixture_data_multiple_validations(
 ✅  | mean must be greater than or equal to **97.01899999999998** and less than or equal to **99.01899999999998**.  | ≈98.019
 ❌  | median must be greater than or equal to **84000.75** and less than or equal to **86000.75**.  | 85.75
 ✅  | quantiles must be within the following value ranges.
-
-
-
-
  | Quantile | Min Value | Max Value |
  | ------------  | ------------  | ------------ |
 0.05  | 54.37  | 56.37
@@ -579,10 +372,6 @@ def test_render_section_page_with_fixture_data_multiple_validations(
 Q3  | 116.62  | 118.62
 0.95  | 173.54  | 175.54
   |
-
-
-
-
  | Quantile | Value |
  | ------------  | ------------ |
 0.05  | 55.37
@@ -590,143 +379,49 @@ def test_render_section_page_with_fixture_data_multiple_validations(
 Median  | 83.31
 Q3  | 117.62
 0.95  | 174.54
-
-
-
-
-
-
 ## Team
-
-
-
-
-
-
-
-
  | Status | Expectation | Observed Value |
  | ------------  | ------------  | ------------ |
 ✅  | values must never be null.  | 100% not null
 ✅  | values must always be greater than or equal to **1** characters long.  | 0% unexpected
-
-
-
-
-
-
-
 ### Info
-
-
-
-
-
-
  |  |  |
  | ------------  | ------------ |
 Great Expectations Version  | 0.11.8+4.g4ab34df3.dirty
 Run Name  | getest run
 Run Time  | 2020-07-27T17:19:32.959193+00:00
-
-
-
-
-
 ### Batch Markers
-
-
-
-
-
-
  |  |  |
  | ------------  | ------------ |
 **ge_load_time**  | **20200727T171932.954810Z**
 **pandas_data_fingerprint**  | **8c46fdaf0bd356fd58b7bcd9b2e6012d**
-
-
-
-
-
 ### Batch Kwargs
-
-
-
-
-
-
  |  |  |
  | ------------  | ------------ |
 **PandasInMemoryDF**  | **True**
 **datasource**  | **getest**
 **ge_batch_id**  | **56615f40-d02d-11ea-b6ea-acde48001122**
-
-
-
-
 -----------------------------------------------------------
 Powered by [Great Expectations](https://greatexpectations.io/)
 # Validation Results
-
-
-
-
 ## Overview
 ### **Expectation Suite:** **basic.warning**
 **Dataasset:** **None**
 **Status:**  **Failed**
-
-
-
-
-
 ### Statistics
-
-
-
-
-
-
  |  |  |
  | ------------  | ------------ |
 Evaluated Expectations  | 11
 Successful Expectations  | 9
 Unsuccessful Expectations  | 2
 Success Percent  | ≈81.82%
-
-
-
-
-
 ## Table-Level Expectations
-
-
-
-
-
-
-
-
  | Status | Expectation | Observed Value |
  | ------------  | ------------  | ------------ |
 ❌  | Must have greater than or equal to **27000** and less than or equal to **33000** rows.  | 30
 ✅  | Must have exactly **3** columns.  | 3
 ✅  | Must have these columns in this order: **Team**, ** "Payroll (millions)"**, ** "Wins"**  | ['Team', ' "Payroll (millions)"', ' "Wins"']
-
-
-
-
-
 ##  "Payroll (millions)"
-
-
-
-
-
-
-
-
  | Status | Expectation | Observed Value |
  | ------------  | ------------  | ------------ |
 ✅  | values must never be null.  | 100% not null
@@ -735,10 +430,6 @@ def test_render_section_page_with_fixture_data_multiple_validations(
 ✅  | mean must be greater than or equal to **97.01899999999998** and less than or equal to **99.01899999999998**.  | ≈98.019
 ❌  | median must be greater than or equal to **84000.75** and less than or equal to **86000.75**.  | 85.75
 ✅  | quantiles must be within the following value ranges.
-
-
-
-
  | Quantile | Min Value | Max Value |
  | ------------  | ------------  | ------------ |
 0.05  | 54.37  | 56.37
@@ -747,10 +438,6 @@ def test_render_section_page_with_fixture_data_multiple_validations(
 Q3  | 116.62  | 118.62
 0.95  | 173.54  | 175.54
   |
-
-
-
-
  | Quantile | Value |
  | ------------  | ------------ |
 0.05  | 55.37
@@ -758,84 +445,80 @@ def test_render_section_page_with_fixture_data_multiple_validations(
 Median  | 83.31
 Q3  | 117.62
 0.95  | 174.54
-
-
-
-
-
-
 ## Team
-
-
-
-
-
-
-
-
  | Status | Expectation | Observed Value |
  | ------------  | ------------  | ------------ |
 ✅  | values must never be null.  | 100% not null
 ✅  | values must always be greater than or equal to **1** characters long.  | 0% unexpected
-
-
-
-
-
-
-
 ### Info
-
-
-
-
-
-
  |  |  |
  | ------------  | ------------ |
 Great Expectations Version  | 0.11.8+4.g4ab34df3.dirty
 Run Name  | getest run
 Run Time  | 2020-07-27T17:19:32.959193+00:00
-
-
-
-
-
 ### Batch Markers
-
-
-
-
-
-
  |  |  |
  | ------------  | ------------ |
 **ge_load_time**  | **20200727T171932.954810Z**
 **pandas_data_fingerprint**  | **8c46fdaf0bd356fd58b7bcd9b2e6012d**
-
-
-
-
-
 ### Batch Kwargs
-
-
-
-
-
-
  |  |  |
  | ------------  | ------------ |
 **PandasInMemoryDF**  | **True**
 **datasource**  | **getest**
 **ge_batch_id**  | **56615f40-d02d-11ea-b6ea-acde48001122**
+-----------------------------------------------------------
+Powered by [Great Expectations](https://greatexpectations.io/)
+""".replace(
+            " ", ""
+        )
+        .replace("\t", "")
+        .replace("\n", "")
+    )
 
 
-
-
+def test_render_expectation_suite_for_Markdown(expectation_suite_to_render_with_notes):
+    expectation_suite_page_renderer = ExpectationSuitePageRenderer()
+    rendered_document_content_list = expectation_suite_page_renderer.render(
+        expectation_suite_to_render_with_notes
+    )
+    md_str = DefaultMarkdownPageView().render(rendered_document_content_list)
+    md_str = " ".join(md_str)
+    md_str = md_str.replace(" ", "").replace("\t", "").replace("\n", "")
+    assert (
+        md_str
+        == """
+   # Validation Results
+## Overview
+### Info
+ |  |  |
+ | ------------  | ------------ |
+Expectation Suite Name  | default
+Great Expectations Version  | 0.13.0-test
+### Notes
+    This Expectation suite currently contains 5 total Expectations across 5 columns.
+## infinities
+  * is a required field.
+  * ***
+## irrationals
+  * distinct values must belong to this set: \* **1st** **2nd**.
+  * ***
+## naturals
+  * is a required field.
+  * ***
+## nulls
+  * is a required field.
+  * ***
+## testings
+  * values must be unique.
+    #### Notes:
+      Example notes about this expectation. **Markdown** `Supported`.
+
+      Second example note **with** *Markdown*
+  * ***
 -----------------------------------------------------------
 Powered by [Great Expectations](https://greatexpectations.io/)
-""".replace(
+    """.replace(
             " ", ""
         )
         .replace("\t", "")