Issue with Microsoft Teams Webhook: Images Not Displaying

I’ve been using the MicrosoftTeamsNotificationAction to notify my team about critical issues, and almost everything works perfectly. However, the images included in the webhook messages are not showing up in Teams.

I checked the webhook logs and noticed the following image URL was being sent:

                        "type": "ColumnSet",
                        "columns": [
                            {
                                "type": "Column",
                                "items": [
                                    {
                                        "type": "Image",
                                        "url": "https://www.greatexpectations.io/image/gx-logo-mark-400",
                                        "altText": "my_checkpoint",
                                        "size": "small"
                                    }
                                ],
                                "width": "auto"
                            },

https://greatexpectations.io/image/gx-logo-mark-400

When trying to access the image directly, I encountered an error:

In the git repo you will find this setting in great_expectations/great_expectations/render/renderer/microsoft_teams_renderer.py at develop · great-expectations/great_expectations · GitHub

To troubleshoot this, I ran the following notebook code in Databricks.

%pip install great-expectations

# Import great_expectations and request a Data Context.
import great_expectations as gx

context = gx.get_context(mode="file")

# Optional. Request a File Data Context from a specific folder.
context = gx.get_context(mode="file", project_root_dir="./new_context_folder")

# Optional. Review the configuration of the returned File Data Context.
print(context)

context = gx.get_context(mode="file", project_root_dir="./new_context_folder")

# Define the Data Source name
data_source_name = "my_data_source"

# Add the Data Source to the Data Context
data_source = context.data_sources.add_pandas(name=data_source_name)

# Retrieve the Data Source
data_source_name = "my_data_source"
data_source = context.data_sources.get(data_source_name)

# Define the Data Asset name
data_asset_name = "my_dataframe_data_asset"

# Add a Data Asset to the Data Source
data_asset = data_source.add_dataframe_asset(name=data_asset_name)

# Retrieve the Data Asset
data_source_name = "my_data_source"
data_asset_name = "my_dataframe_data_asset"
data_asset = context.data_sources.get(data_source_name).get_asset(data_asset_name)

# Define the Batch Definition name
batch_definition_name = "my_batch_definition"

# Add a Batch Definition to the Data Asset
batch_definition = data_asset.add_batch_definition_whole_dataframe(
    batch_definition_name
)

# Create an Expectation Suite
suite_name = "my_expectation_suite"
suite = gx.ExpectationSuite(name=suite_name)

# Add the Expectation Suite to the Data Context
suite = context.suites.add(suite)

# Create an Expectation to put into an Expectation Suite
expectation = gx.expectations.ExpectColumnValuesToNotBeNull(column="passenger_count")

# Add the previously created Expectation to the Expectation Suite
suite.add_expectation(expectation)

# Add another Expectation to the Expectation Suite.
suite.add_expectation(
    gx.expectations.ExpectColumnValuesToNotBeNull(column="pickup_datetime")
)

# Update the configuration of an Expectation, then push the changes to the Expectation Suite
expectation.column = "pickup_location_id"
expectation.save()

# Retrieve an Expectation Suite from the Data Context
existing_suite_name = (
    "my_expectation_suite"  # replace this with the name of your Expectation Suite
)
suite = context.suites.get(name=existing_suite_name)

context = gx.get_context(mode="file", project_root_dir="./new_context_folder")

# Retrieve an Expectation Suite
expectation_suite_name = "my_expectation_suite"
expectation_suite = context.suites.get(name=expectation_suite_name)

# Retrieve a Batch Definition
data_source_name = "my_data_source"
data_asset_name = "my_dataframe_data_asset"
batch_definition_name = "my_batch_definition"
batch_definition = (
    context.data_sources.get(data_source_name)
    .get_asset(data_asset_name)
    .get_batch_definition(batch_definition_name)
)

# Create a Validation Definition
definition_name = "my_validation_definition"
validation_definition = gx.ValidationDefinition(
    data=batch_definition, suite=expectation_suite, name=definition_name
)

# Add the Validation Definition to the Data Context
validation_definition = context.validation_definitions.add(validation_definition)


import pandas as pd

# Retrieve the Validation Definition
validation_definition_name = "my_validation_definition"
validation_definition = context.validation_definitions.get(validation_definition_name)

# Define Batch parameters
df = pd.read_csv(
    "https://raw.githubusercontent.com/great-expectations/gx_tutorials/main/data/yellow_tripdata_sample_2019-01.csv"
)

# Accepted keys are determined by the BatchDefinition used to instantiate this ValidationDefinition.
batch_parameters_dataframe = {"dataframe": df}
#batch_parameters_daily = {"year": "2020", "month": "1", "day": "17"}
#batch_parameters_yearly = {"year": "2019"}

# Run the Validation Definition
validation_results = validation_definition.run(batch_parameters=batch_parameters_dataframe)


# Review the Validation Results
print(validation_results)


import great_expectations as gx
from great_expectations.checkpoint import (
    MicrosoftTeamsNotificationAction,
)


# Create a list of one or more Validation Definitions for the Checkpoint to run
validation_definitions = [
    context.validation_definitions.get("my_validation_definition")
]

# Create a list of Actions for the Checkpoint to perform
action_list = [
    # This Action sends a Slack Notification if an Expectation fails.
    MicrosoftTeamsNotificationAction(
        name="send_teams_notification_on_all_expectations",
        teams_webhook="${webhook_url}",
        notify_on="all",
#        show_failed_expectations=True,
    ),
    # This Action updates the Data Docs static website with the Validation
    #   Results after the Checkpoint is run.
    # UpdateDataDocsAction(
    #     name="update_all_data_docs",
    # ),
]

# Create the Checkpoint
checkpoint_name = "my_checkpoint"
checkpoint = gx.Checkpoint(
    name=checkpoint_name,
    validation_definitions=validation_definitions,
    actions=action_list,
    result_format={"result_format": "COMPLETE"},
)

# Save the Checkpoint to the Data Context
context.checkpoints.add(checkpoint)

# Retrieve the Checkpoint later
checkpoint_name = "my_checkpoint"
checkpoint = context.checkpoints.get(checkpoint_name)


import os

os.environ["webhook_url"] = "https://your-webhook-url"


checkpoint = context.checkpoints.get("my_checkpoint")

batch_parameters_dataframe = {"dataframe": df}

expectation_parameters = {
    "expect_fare_max_to_be_above": 5.00,
    "expect_fare_max_to_be_below": 1000.00,
}

validation_results = checkpoint.run(
    batch_parameters=batch_parameters_dataframe, expectation_parameters=expectation_parameters
)