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

[FEATURE] Add GX Cloud hyperlink to slack notification #6740

Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
38ddd6e
Add base_url param and compose cloud_url for render method
Jan 6, 2023
bc505bb
Add markup logic for rendering GX Cloud link for a validation result
Jan 6, 2023
df0c2a9
Refine conditional logic
Jan 7, 2023
8797eb5
Add tests with refactor using fixtures
Jan 7, 2023
aeed394
Adjust comment
Jan 7, 2023
4f1e1ec
Refine condition logic
Jan 9, 2023
c25d8dd
Add test for SlackNotificationAction with base_url
Jan 9, 2023
d921afa
lint
Jan 9, 2023
56977eb
Remove `base_url` param
Jan 9, 2023
c7916a5
Remove test
Jan 9, 2023
7031fe2
Lint: Remove whitespace
Jan 9, 2023
33aa903
Merge branch 'develop' of github.com:great-expectations/great_expecta…
Jan 9, 2023
79694c3
Merge branch 'develop' into f/CLOUD-1833-add-programmatic-links-to-os…
josectobar Jan 9, 2023
b01a47a
removed base_url param
Jan 10, 2023
bf549f8
Merge branch 'f/CLOUD-1833-add-programmatic-links-to-oss-slack-notifi…
Jan 10, 2023
2be2b60
Adjust naming and references
Jan 11, 2023
dbeb9f5
Update test
Jan 11, 2023
237484f
remove unused variable
Jan 11, 2023
6696a70
Add OSS support
NathanFarmer Jan 11, 2023
7ee708f
Merge branch 'f/CLOUD-1833-add-programmatic-links-to-oss-slack-notifi…
NathanFarmer Jan 11, 2023
0e0d0a7
Merge branch 'develop' into f/CLOUD-1833-add-programmatic-links-to-os…
NathanFarmer Jan 11, 2023
9ace1f6
Not and f string anymore
NathanFarmer Jan 11, 2023
85ff5f2
Merge branch 'f/CLOUD-1833-add-programmatic-links-to-oss-slack-notifi…
NathanFarmer Jan 11, 2023
dfdeb85
Renderer conditional for more than one url
NathanFarmer Jan 11, 2023
1dcb07a
Fix broken link and get base_url from DataContext
NathanFarmer Jan 11, 2023
9e74ee8
Merge branch 'develop' into f/CLOUD-1833-add-programmatic-links-to-os…
NathanFarmer Jan 11, 2023
c10d88d
remove /n from summary_text
josectobar Jan 11, 2023
112dcb7
Add CLOUD_APP_DEFAULT_BASE_URL constant
NathanFarmer Jan 11, 2023
20aba0f
Merge branch 'f/CLOUD-1833-add-programmatic-links-to-oss-slack-notifi…
NathanFarmer Jan 11, 2023
ee12ecb
Default to empty list over None
NathanFarmer Jan 11, 2023
d0697aa
Merge branch 'develop' into f/CLOUD-1833-add-programmatic-links-to-os…
NathanFarmer Jan 11, 2023
127a262
Merge develop into f/CLOUD-1833-add-programmatic-links-to-oss-slack-n…
github-actions[bot] Jan 11, 2023
2a8bb2a
Merge develop into f/CLOUD-1833-add-programmatic-links-to-oss-slack-n…
github-actions[bot] Jan 12, 2023
0b1459f
Merge develop into f/CLOUD-1833-add-programmatic-links-to-oss-slack-n…
github-actions[bot] Jan 12, 2023
465b23f
Merge develop into f/CLOUD-1833-add-programmatic-links-to-oss-slack-n…
github-actions[bot] Jan 12, 2023
738b719
Merge develop into f/CLOUD-1833-add-programmatic-links-to-oss-slack-n…
github-actions[bot] Jan 12, 2023
7947a17
Merge develop into f/CLOUD-1833-add-programmatic-links-to-oss-slack-n…
github-actions[bot] Jan 12, 2023
4a9453b
Merge develop into f/CLOUD-1833-add-programmatic-links-to-oss-slack-n…
github-actions[bot] Jan 12, 2023
2444ce4
Broken import
NathanFarmer Jan 12, 2023
4f9cf24
Not an f-string
NathanFarmer Jan 12, 2023
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: 23 additions & 3 deletions great_expectations/checkpoint/actions.py
Expand Up @@ -7,10 +7,11 @@

import logging
import warnings
from typing import TYPE_CHECKING, Dict, Optional, Union
from typing import TYPE_CHECKING, Dict, Final, Optional, Union
from urllib.parse import urljoin

from great_expectations.core import ExpectationSuiteValidationResult
from great_expectations.data_context.cloud_constants import CLOUD_APP_DEFAULT_BASE_URL
from great_expectations.data_context.types.refs import GXCloudResourceRef

try:
Expand Down Expand Up @@ -152,7 +153,6 @@ class SlackNotificationAction(ValidationAction):
module_name: great_expectations.render.renderer.slack_renderer
class_name: SlackRenderer
show_failed_expectations: *Optional* (boolean) shows a list of failed expectation types. default is false.

"""

def __init__(
Expand Down Expand Up @@ -236,13 +236,32 @@ def _run(

validation_success = validation_result_suite.success
data_docs_pages = None

if payload:
# process the payload
for action_names in payload.keys():
if payload[action_names]["class"] == "UpdateDataDocsAction":
data_docs_pages = payload[action_names]

# Assemble complete GX Cloud URL for a specific validation result
data_docs_urls: list[
dict[str, str | None]
] = self.data_context.get_docs_sites_urls(
resource_identifier=validation_result_suite_identifier
)

validation_result_urls: list[str] = [
data_docs_url["site_url"]
for data_docs_url in data_docs_urls
if data_docs_url["site_url"]
]
if (
isinstance(validation_result_suite_identifier, GXCloudIdentifier)
and validation_result_suite_identifier.cloud_id
):
cloud_base_url: Final[str] = CLOUD_APP_DEFAULT_BASE_URL
validation_result_url = f"{cloud_base_url}?validationResultId={validation_result_suite_identifier.cloud_id}"
validation_result_urls.append(validation_result_url)

if (
self.notify_on == "all"
or self.notify_on == "success"
Expand All @@ -255,6 +274,7 @@ def _run(
data_docs_pages,
self.notify_with,
self.show_failed_expectations,
validation_result_urls,
)

# this will actually send the POST request to the Slack webapp server
Expand Down
1 change: 1 addition & 0 deletions great_expectations/data_context/cloud_constants.py
Expand Up @@ -4,6 +4,7 @@

SUPPORT_EMAIL = "support@greatexpectations.io"
CLOUD_DEFAULT_BASE_URL: Final[str] = "https://api.greatexpectations.io/"
CLOUD_APP_DEFAULT_BASE_URL: Final[str] = "https://app.greatexpectations.io/"


class GXCloudEnvironmentVariable(str, Enum):
Expand Down
19 changes: 18 additions & 1 deletion great_expectations/render/renderer/slack_renderer.py
Expand Up @@ -17,6 +17,7 @@ def render( # noqa: C901 - 17
data_docs_pages=None,
notify_with=None,
show_failed_expectations: bool = False,
validation_result_urls: List[str] = [],
):
default_text = (
"No validation occurred. Please ensure you passed a validation_result."
Expand Down Expand Up @@ -75,8 +76,24 @@ def render( # noqa: C901 - 17
failed_expectations_text = self.create_failed_expectations_text(
validation_result["results"]
)
summary_text = ""
if validation_result_urls:
# This adds hyperlinks for defined URL
if len(validation_result_urls) == 1:
title_hlink = f"*<{validation_result_urls[0]} | Validation Result>*"
else:
title_hlink = f"*Validation Result*"
batch_validation_status_hlinks = "".join(
f"*Batch Validation Status*: *<{validation_result_url} | {status}>*"
for validation_result_url in validation_result_urls
)
summary_text += f"""{title_hlink}
{batch_validation_status_hlinks}
"""
else:
summary_text += f"*Batch Validation Status*: {status}"

summary_text = f"""*Batch Validation Status*: {status}
summary_text += f"""
*Expectation suite name*: `{expectation_suite_name}`
*Data asset name*: `{data_asset_name}`
*Run ID*: `{run_id}`
Expand Down
124 changes: 78 additions & 46 deletions tests/render/test_SlackRenderer.py
@@ -1,12 +1,61 @@
import pytest

from great_expectations.core.batch import BatchDefinition, IDDict
from great_expectations.core.expectation_validation_result import (
ExpectationSuiteValidationResult,
)
from great_expectations.render.renderer import SlackRenderer


def test_SlackRenderer_validation_results_with_datadocs():
validation_result_suite = ExpectationSuiteValidationResult(
@pytest.fixture
def failed_expectation_suite_validation_result():
return ExpectationSuiteValidationResult(
results=[
{
"exception_info": {
"raised_exception": False,
"exception_traceback": None,
"exception_message": None,
},
"success": False,
"meta": {},
"result": {"observed_value": 8565},
"expectation_config": {
"meta": {},
"kwargs": {
"column": "my_column",
"max_value": 10000,
"min_value": 10000,
"batch_id": "b9e06d3884bbfb6e3352ced3836c3bc8",
},
"expectation_type": "expect_column_values_to_be_between",
},
}
],
success=False,
statistics={
"evaluated_expectations": 0,
"successful_expectations": 0,
"unsuccessful_expectations": 0,
"success_percent": None,
},
meta={
"great_expectations_version": "v0.8.0__develop",
"batch_kwargs": {"data_asset_name": "x/y/z"},
"data_asset_name": {
"datasource": "x",
"generator": "y",
"generator_asset": "z",
},
"expectation_suite_name": "default",
"run_id": "2019-09-25T060538.829112Z",
},
)


@pytest.fixture
def success_expectation_suite_validation_result():
return ExpectationSuiteValidationResult(
results=[],
success=True,
statistics={
Expand All @@ -28,6 +77,12 @@ def test_SlackRenderer_validation_results_with_datadocs():
},
)


def test_SlackRenderer_validation_results_with_datadocs(
success_expectation_suite_validation_result,
):
validation_result_suite = success_expectation_suite_validation_result

rendered_output = SlackRenderer().render(validation_result_suite)

expected_output = {
Expand Down Expand Up @@ -367,56 +422,33 @@ def test_create_failed_expectations_text():
)


def test_SlackRenderer_show_failed_expectations():
validation_result = ExpectationSuiteValidationResult(
results=[
{
"exception_info": {
"raised_exception": False,
"exception_traceback": None,
"exception_message": None,
},
"success": False,
"meta": {},
"result": {"observed_value": 8565},
"expectation_config": {
"meta": {},
"kwargs": {
"column": "my_column",
"max_value": 10000,
"min_value": 10000,
"batch_id": "b9e06d3884bbfb6e3352ced3836c3bc8",
},
"expectation_type": "expect_column_values_to_be_between",
},
}
],
success=False,
statistics={
"evaluated_expectations": 0,
"successful_expectations": 0,
"unsuccessful_expectations": 0,
"success_percent": None,
},
meta={
"great_expectations_version": "v0.8.0__develop",
"batch_kwargs": {"data_asset_name": "x/y/z"},
"data_asset_name": {
"datasource": "x",
"generator": "y",
"generator_asset": "z",
},
"expectation_suite_name": "default",
"run_id": "2019-09-25T060538.829112Z",
},
)
def test_SlackRenderer_show_failed_expectations(
failed_expectation_suite_validation_result,
):
slack_renderer = SlackRenderer()
rendered_msg = slack_renderer.render(
validation_result, show_failed_expectations=True
validation_result=failed_expectation_suite_validation_result,
show_failed_expectations=True,
)

assert (
"""*Failed Expectations*:
:x:expect_column_values_to_be_between (my_column)"""
in rendered_msg["blocks"][0]["text"]["text"]
)


def test_slack_renderer_shows_gx_cloud_url(failed_expectation_suite_validation_result):
slack_renderer = SlackRenderer()
cloud_url = "app.greatexpectations.io/?validationResultId=123-456-789"
rendered_msg = slack_renderer.render(
validation_result=failed_expectation_suite_validation_result,
show_failed_expectations=True,
validation_result_urls=[cloud_url],
)

assert (
""
f"*<{cloud_url} | Failed :x:>*"
"" in rendered_msg["blocks"][0]["text"]["text"]
)