Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MAINTENANCE] Refactor EmailAction for V1 #9725

Merged
merged 6 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
26 changes: 26 additions & 0 deletions great_expectations/checkpoint/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,32 @@

return values

@override
def v1_run(self, checkpoint_result: CheckpointResult) -> str | dict:
success = checkpoint_result.success or False
if not self._is_enabled(success=success):
return {"email_result": ""}

Check warning on line 794 in great_expectations/checkpoint/actions.py

View check run for this annotation

Codecov / codecov/patch

great_expectations/checkpoint/actions.py#L794

Added line #L794 was not covered by tests

title, html = self.renderer.v1_render(checkpoint_result=checkpoint_result)
receiver_emails_list = list(map(lambda x: x.strip(), self.receiver_emails.split(",")))

# this will actually send the email
email_result = send_email(
title=title,
html=html,
smtp_address=self.smtp_address,
smtp_port=self.smtp_port,
sender_login=self.sender_login,
sender_password=self.sender_password,
sender_alias=self.sender_alias,
receiver_emails_list=receiver_emails_list,
use_tls=self.use_tls,
use_ssl=self.use_ssl,
)

# sending payload back as dictionary
return {"email_result": email_result}

@override
def _run( # type: ignore[override] # signature does not match parent # noqa: PLR0913
self,
Expand Down
46 changes: 46 additions & 0 deletions great_expectations/render/renderer/email_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,60 @@

import logging
import textwrap
from typing import TYPE_CHECKING

logger = logging.getLogger(__name__)

from great_expectations.core.id_dict import BatchKwargs
from great_expectations.render.renderer.renderer import Renderer

if TYPE_CHECKING:
from great_expectations.checkpoint.v1_checkpoint import CheckpointResult
from great_expectations.core.expectation_validation_result import (

Check warning on line 14 in great_expectations/render/renderer/email_renderer.py

View check run for this annotation

Codecov / codecov/patch

great_expectations/render/renderer/email_renderer.py#L13-L14

Added lines #L13 - L14 were not covered by tests
ExpectationSuiteValidationResult,
)


class EmailRenderer(Renderer):
def v1_render(self, checkpoint_result: CheckpointResult) -> tuple[str, str]:
checkpoint_name = checkpoint_result.checkpoint_config.name
status = checkpoint_result.success
title = f"{checkpoint_name}: {status}"

text_blocks: list[str] = []
for result in checkpoint_result.run_results.values():
html = self._render_validation_result(result=result)
text_blocks.append(html)

return title, self._concatenate_blocks(text_blocks=text_blocks)

def _render_validation_result(self, result: ExpectationSuiteValidationResult) -> str:
suite_name = result.suite_name
asset_name = result.asset_name or "__no_asset_name__"
n_checks_succeeded = result.statistics["successful_expectations"]
n_checks = result.statistics["evaluated_expectations"]
run_id = result.meta.get("run_id", "__no_run_id__")
batch_id = result.batch_id
check_details_text = f"<strong>{n_checks_succeeded}</strong> of <strong>{n_checks}</strong> expectations were met" # noqa: E501
status = "Success 🎉" if result.success else "Failed ❌"

title = f"<h3><u>{suite_name}</u></h3>"
html = textwrap.dedent(
f"""\
<p><strong>{title}</strong></p>
<p><strong>Batch Validation Status</strong>: {status}</p>
<p><strong>Expectation Suite Name</strong>: {suite_name}</p>
<p><strong>Data Asset Name</strong>: {asset_name}</p>
<p><strong>Run ID</strong>: {run_id}</p>
<p><strong>Batch ID</strong>: {batch_id}</p>
<p><strong>Summary</strong>: {check_details_text}</p>"""
)

return html

def _concatenate_blocks(self, text_blocks: list[str]) -> str:
return "<br>".join(text_blocks)

def render( # noqa: C901, PLR0912
self, validation_result=None, data_docs_pages=None, notify_with=None
):
Expand Down
25 changes: 20 additions & 5 deletions tests/actions/test_core_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -868,14 +868,29 @@ def test_APINotificationAction_run(self, checkpoint_result: CheckpointResult):
)

@pytest.mark.unit
@pytest.mark.xfail(
reason="Not yet implemented for this class", strict=True, raises=NotImplementedError
)
def test_EmailAction_run(self, checkpoint_result: CheckpointResult):
action = EmailAction(
smtp_address="test", smtp_port=587, receiver_emails="test1@gmail.com, test2@hotmail.com"
smtp_address="test",
smtp_port="587",
receiver_emails="test1@gmail.com, test2@hotmail.com",
cdkini marked this conversation as resolved.
Show resolved Hide resolved
)
action.v1_run(checkpoint_result=checkpoint_result)

with mock.patch("great_expectations.checkpoint.actions.send_email") as mock_send_email:
out = action.v1_run(checkpoint_result=checkpoint_result)

mock_send_email.assert_called_once_with(
title=mock.ANY,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think ANY makes sense here, but I might add another assertion below to verify that the argument passed kind of looks like the right string. (e.g. suite_name in call_args.kwargs["title"] (or whatever the syntax is for that))

html=mock.ANY,
receiver_emails_list=["test1@gmail.com", "test2@hotmail.com"],
sender_alias=None,
sender_login=None,
sender_password=None,
smtp_address="test",
smtp_port="587",
use_ssl=None,
use_tls=None,
)
assert out == {"email_result": mock_send_email()}

@pytest.mark.unit
@pytest.mark.xfail(
Expand Down
29 changes: 26 additions & 3 deletions tests/render/test_EmailRenderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@
)
from great_expectations.render.renderer import EmailRenderer

# module level markers
pytestmark = pytest.mark.big


@pytest.mark.big
def test_EmailRenderer_validation_results_with_datadocs():
validation_result_suite = ExpectationSuiteValidationResult(
results=[],
Expand Down Expand Up @@ -64,6 +62,7 @@ def test_EmailRenderer_validation_results_with_datadocs():
assert rendered_output == expected_output


@pytest.mark.big
def test_EmailRenderer_checkpoint_validation_results_with_datadocs():
batch_definition = LegacyBatchDefinition(
datasource_name="test_datasource",
Expand Down Expand Up @@ -120,6 +119,7 @@ def test_EmailRenderer_checkpoint_validation_results_with_datadocs():
assert rendered_output == expected_output


@pytest.mark.big
def test_EmailRenderer_get_report_element():
email_renderer = EmailRenderer()

Expand All @@ -130,3 +130,26 @@ def test_EmailRenderer_get_report_element():

# this should work
assert email_renderer._get_report_element(docs_link="i_should_work") is not None


@pytest.mark.unit
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2024-04-09 at 8 06 49 AM

def test_EmailRenderer_v1_render(v1_checkpoint_result):
email_renderer = EmailRenderer()
_, raw_html = email_renderer.v1_render(checkpoint_result=v1_checkpoint_result)
html_blocks = raw_html.split("\n")

assert html_blocks == [
"<p><strong><h3><u>my_bad_suite</u></h3></strong></p>",
"<p><strong>Batch Validation Status</strong>: Failed ❌</p>",
"<p><strong>Expectation Suite Name</strong>: my_bad_suite</p>",
"<p><strong>Data Asset Name</strong>: my_first_asset</p>",
"<p><strong>Run ID</strong>: __no_run_id__</p>",
"<p><strong>Batch ID</strong>: my_batch</p>",
"<p><strong>Summary</strong>: <strong>3</strong> of <strong>5</strong> expectations were met</p><br><p><strong><h3><u>my_good_suite</u></h3></strong></p>", # noqa: E501
"<p><strong>Batch Validation Status</strong>: Success 🎉</p>",
"<p><strong>Expectation Suite Name</strong>: my_good_suite</p>",
"<p><strong>Data Asset Name</strong>: __no_asset_name__</p>",
"<p><strong>Run ID</strong>: my_run_id</p>",
"<p><strong>Batch ID</strong>: my_other_batch</p>",
"<p><strong>Summary</strong>: <strong>1</strong> of <strong>1</strong> expectations were met</p>", # noqa: E501
]