In ADOC, navigate to the left main navigation menu, click Control Center -> Settings.
From the Settings page, select Notification Templates under Notifications.
You’ll see a table of existing templates and template groups (if configured).
On the Notification page, click Create Notification Template.
Enter:
Name: Use something descriptive (e.g., Prod – DQ – Slack & PagerDuty)
Description (Optional): Include purpose and audience, e.g., “On-call alerts for prod DQ policies”
Click Save.
You’ll be redirected to the Edit Notification Template page for this new group.
Templates in a group are defined per source type and per channel.
In the Edit Notification Template page:
From the left panel, choose a Source Type (for example, Data Quality, Pipeline Monitoring, Profile).
Select a Channel:
Email
Webhook
Slack
Google Chat
Microsoft Teams
ServiceNow
Jira
ADOC loads the system template for that combination as a starting point.
For each channel:
Channel | What You Edit |
|---|
Email | Subject and Body (HTML or plain text) |
Webhook | JSON payload content |
Slack | Block Kit JSON body |
Google Chat | Card JSON body |
Microsoft Teams | MessageCard / Adaptive Card JSON |
ServiceNow | Plain text body or JSON fields |
Jira | JSON payload (issue fields / ADF document) |
You write the content in FreeMarker Template Language (FTL) using variables such as: ${policy.name}, ${execution.resultStatus}, ${result.qualityScore}, etc.
For Jira and ServiceNow, the first test creates a record; subsequent tests update the same record.
Once your Template Group is ready:
Navigate to Settings -> Notifications -> Notification Groups.
Open or create a Notification Group.
In the group configuration, select your Notification Template Group.
Click Save.
From now on, alerts that use that Notification Group will:
Concept | Relationship |
|---|
Notification Group | Who receives the alert and via which channels |
Template Group | How each alert looks for each channel and source type |
Variables are exposed to templates based on policy type and execution context. You can explore them from the Variables tab (right panel) in the editor.
Below is a consolidated reference of the key categories. Exact availability may vary by source type.
Category | What It Describes | Typical Prefix |
|---|
Execution Context | Result, severity, timestamps, messages | execution
|
Policy Information | Policy id, name, type, description, tags | policy
|
Data Quality Results | Scores, row counts, rule outcomes | result, rule
|
Reconciliation Results | Cross-system record comparison | result, reconciliation
|
Schema Drift | Structural changes to assets | drift
|
Data Drift | Distribution changes | drift
|
Freshness | Expected vs. actual arrival times | freshness
|
Pipeline Monitoring | Pipeline execution metrics and errors | pipeline
|
Environmental Context | Tenant, domains, assets, integrations | tenant, asset, integration
|
Incident Context | Incident entity for failures/warnings only | incident, severity, status, etc.
|
Note This table focuses on the most heavily used fields. Your Variables panel in ADOC is the source of truth for exact names and availability.
Variable | Type | Description | Example Usage |
|---|
execution.resultStatus
| String | Execution result: SUCCESS / WARNING / FAILED / ERRORED | ${execution.resultStatus}
|
execution.severity
| String | Alert severity: Critical / High / Medium / Low | ${execution.severity}
|
execution.startTime
| Timestamp | Policy execution start time | ${execution.startTime?string["yyyy-MM-dd HH:mm:ss"]}
|
execution.endTime
| Timestamp | Policy execution end time | ${execution.endTime?string["yyyy-MM-dd HH:mm:ss"]}
|
execution.id
| Number | Unique execution identifier | ${execution.id}
|
execution.message
| String | Execution details / error message | ${execution.message}
|
Variable | Type | Description | Example Usage |
|---|
policy.name
| String | Human-readable policy name | ${policy.name}
|
policy.id
| Number | Unique policy identifier | ${policy.id}
|
policy.type
| String | Policy type (DATA_QUALITY, RECONCILIATION, etc.) | ${policy.type}
|
policy.description
| String | Policy description | ${policy.description}
|
policy.scheduled
| Boolean | Whether the policy runs on schedule | ${policy.scheduled?string('Yes','No')}
|
policy.tags
| Array | Tags assigned to policy | ${policy.tags?join(", ")}
|
Applies to Data Quality policies.
Variable | Type | Description | Example Usage |
|---|
result.qualityScore
| Number | Overall quality score (%) | ${result.qualityScore}%
|
result.totalRowsScanned
| Number | Total rows evaluated | ${result.totalRowsScanned?string[",###"]}
|
result.rowsPassed
| Number | Rows that passed all rules | ${result.rowsPassed}
|
result.rowsFailed
| Number | Rows that failed one or more rules | ${result.rowsFailed}
|
result.warningRows
| Number | Rows in warning threshold | ${result.warningRows!0}
|
result.totalRules
| Number | Number of rules evaluated | ${result.totalRules}
|
result.rulesPassed
| Number | Rules that passed | ${result.rulesPassed}
|
result.rulesFailed
| Number | Rules that failed | ${result.rulesFailed}
|
Rule-level details are typically exposed via a rules collection:
Field Name | Description |
|---|
rule.name
| Rule name |
rule.description
| Rule description |
rule.status
| FAILED / PASSED |
rule.failedRows
| (If available) Number of bad rows |
Applies to reconciliation policies.
Variable | Type | Description | Example Usage |
|---|
result.totalRecords
| Number | Total records compared | ${result.totalRecords}
|
result.matchedRecords
| Number | Records that match | ${result.matchedRecords}
|
result.unmatchedRecords
| Number | Records that do not match | ${result.unmatchedRecords}
|
result.equalityPercentage
| Number | Percentage of equality | ${result.equalityPercentage}%
|
result.leftRowCount
| Number | Row count in left/source system | ${result.leftRowCount}
|
result.rightRowCount
| Number | Row count in right/target system | ${result.rightRowCount}
|
reconciliation.leftDataSource
| String | Left/source system identifier | ${reconciliation.leftDataSource}
|
reconciliation.rightDataSource
| String | Right/target system identifier | ${reconciliation.rightDataSource}
|
reconciliation.joinKeys
| Array | Join keys used for matching records | ${reconciliation.joinKeys?join(", ")}
|
Applies to schema drift / structure monitoring policies.
Variable | Type | Description | Example Usage |
|---|
drift.addedColumns
| Array | Newly added columns | ${drift.addedColumns?join(", ")}
|
drift.removedColumns
| Array | Removed columns | ${drift.removedColumns?join(", ")}
|
drift.modifiedColumns
| Array | Columns with type or constraint changes | ${drift.modifiedColumns?join(", ")}
|
drift.lastAlteredTime
| Timestamp | Most recent schema modification | ${drift.lastAlteredTime?string["yyyy-MM-dd HH:mm:ss"]}
|
drift.changeCount
| Number | Total changes detected | ${drift.changeCount}
|
Example snippet:
<#if drift.addedColumns?has_content>
New columns: ${drift.addedColumns?join(", ")}
</#if>
Variable | Type | Description |
|---|
drift.driftScore
| Number | Overall drift score (change magnitude) |
drift.driftedColumns
| Array | Columns with significant distribution changes |
The following variables are available for use in Notification Templates for Data Cadence (Freshness) policies.
Variable | Type | Description |
|---|
freshness.expectedTime
| Timestamp | When data was expected |
freshness.actualTime
| Timestamp | When data actually arrived |
freshness.delayMinutes
| Number | Delay in minutes |
freshness.expectedBatchCount
| Number | Expected batch count (if batch-based) |
freshness.actualBatchCount
| Number | Actual batch count |
freshness.warningThreshold
| Number | Warning threshold in minutes |
freshness.failureThreshold
| Number | Failure threshold in minutes |
result.alertingRules[0].name
| String | Name of the alerting rule |
result.alertingRules[0].status
| String | Status of the freshness check (e.g., WARNING, CRITICAL, OK) |
result.alertingRules[0].expected
| String | The expected freshness threshold defined in the rule |
result.alertingRules[0].actual
| String | The actual last-updated timestamp of the dataset |
result.alertingRules[0].isBreached
| Boolean | Whether the freshness threshold was breached (true or false) |
result.alertingRulesCount
| Number | Total number of alerting rules evaluated for this policy |
Note The index [0] refers to the first alerting rule. If multiple alerting rules are configured, use [1], [2], etc., up to result.alertingRulesCount - 1.
Applies to pipeline monitoring policies.
Variable | Type | Description |
|---|
pipeline.id
| Number | Pipeline identifier |
pipeline.name
| String | Pipeline name |
pipeline.status
| String | SUCCESS, FAILED, etc.
|
pipeline.startTime
| Timestamp | Start time |
pipeline.endTime
| Timestamp | End time |
pipeline.duration
| Number | Duration (seconds) |
pipeline.recordsProcessed
| Number | Records processed |
pipeline.bytesProcessed
| Number | Bytes processed |
pipeline.errorMessage
| String | Error message for failed executions |
pipeline.errorType
| String | Error category |
pipeline.dependencies
| Array | Upstream / downstream dependencies |
Variable | Type | Description |
|---|
tenant.name
| String | Tenant name |
tenant.id
| String | Unique tenant identifier |
domains
| Array | Logical domains associated with the asset |
asset.name
| String | Data asset (table / file / view) name |
asset.id
| String | Unique asset identifier |
asset.type
| String | Asset type category |
integration.name
| String | Related integration name |
integration.id
| String | Related integration id |
Available only when an incident is created (FAILED / WARNING executions).
Variable | Type | Description |
|---|
incidentId
| String | Unique incident identifier |
createdAt
| Timestamp | Incident creation time |
updatedAt
| Timestamp | Last updated time |
severity
| String | Incident severity |
status
| String | Incident status |
totalOccurrences
| Number | Number of times this incident occurred |
openInAccelDataUrl
| String | Deep link to incident / execution in ADOC |
These are not available for SUCCESS runs. Design templates either to:
Use conditionals to check availability, or
Have separate templates for success vs. failure notifications.
FreeMarker is strict about booleans. Do not use ${policy.scheduled} directly. Instead:
Scheduled: ${policy.scheduled?string('Yes','No')}
or
Scheduled (raw): ${policy.scheduled?c}
Use ?join for simple lists:
Tags: ${policy.tags?join(", ")}
Or iterate
<#if policy.tags?has_content>
<#list policy.tags as tag>
- ${tag}
</#list>
<#else>
No tags configured.
</#if>
Use ! to provide default values:
Warning rows: ${result.warningRows!0}
Link: ${openInAccelDataUrl!"Not available"}
Use ?? to test if a variable exists:
<#if result.qualityScore??>
Score: ${result.qualityScore}%
<#else>
Score not available.
</#if>
Total Rows: ${result.totalRowsScanned?string[",###"]}
Execution Time: ${execution.startTime?string["yyyy-MM-dd HH:mm:ss"]}
Policy ${policy.name} finished with status ${execution.resultStatus}.
<#if execution.resultStatus == "SUCCESS">
✅ Check passed for ${policy.name}
<#elseif execution.resultStatus == "WARNING">
⚠ Check completed with warnings
<#else>
🚨 Check failed
</#if>
<#list rules as rule>
- ${rule.name}: ${rule.status}
</#list>
This section provides complete, production-ready templates for common notification scenarios. These templates demonstrate best practices and can be used as starting points for your own customizations.
This template provides detailed data quality information in a well-structured HTML email format.
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
.header { background-color: <#if execution.resultStatus == "SUCCESS">#28a745<#elseif execution.resultStatus == "WARNING">#ffc107<#else>#dc3545</#if>; color: white; padding: 20px; text-align: center; }
.content { padding: 20px; }
.metric-box { display: inline-block; background: #f8f9fa; padding: 15px; margin: 10px; border-left: 4px solid #007bff; }
.metric-label { font-size: 12px; color: #666; text-transform: uppercase; }
.metric-value { font-size: 24px; font-weight: bold; color: #333; }
.rule-table { width: 100%; border-collapse: collapse; margin-top: 20px; }
.rule-table th { background: #007bff; color: white; padding: 10px; text-align: left; }
.rule-table td { padding: 10px; border-bottom: 1px solid #ddd; }
.status-pass { color: #28a745; font-weight: bold; }
.status-fail { color: #dc3545; font-weight: bold; }
.footer { background: #f8f9fa; padding: 15px; margin-top: 20px; text-align: center; font-size: 12px; }
</style>
</head>
<body>
<div class="header">
<h1><#if execution.resultStatus == "SUCCESS">✓<#elseif execution.resultStatus == "WARNING">⚠<#else>✗</#if> Data Quality Alert</h1>
<h2>${policy.name}</h2>
</div>
<div class="content">
<h3>Execution Summary</h3>
<p><strong>Status:</strong> ${execution.resultStatus}</p>
<p><strong>Severity:</strong> ${execution.severity}</p>
<p><strong>Asset:</strong> ${asset.name}</p>
<p><strong>Execution Time:</strong> ${execution.startTime?string["yyyy-MM-dd HH:mm:ss"]} to ${execution.endTime?string["yyyy-MM-dd HH:mm:ss"]}</p>
<h3>Key Metrics</h3>
<div>
<div class="metric-box">
<div class="metric-label">Quality Score</div>
<div class="metric-value">${result.qualityScore}%</div>
</div>
<div class="metric-box">
<div class="metric-label">Rows Scanned</div>
<div class="metric-value">${result.totalRowsScanned?string[",###"]}</div>
</div>
<div class="metric-box">
<div class="metric-label">Rows Failed</div>
<div class="metric-value">${result.rowsFailed?string[",###"]}</div>
</div>
<div class="metric-box">
<div class="metric-label">Rules Failed</div>
<div class="metric-value">${result.rulesFailed} / ${result.totalRules}</div>
</div>
</div>
<#if result.rulesFailed gt 0>
<h3>Failed Rules</h3>
<table class="rule-table">
<thead>
<tr>
<th>Rule Name</th>
<th>Status</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<#list rules as rule>
<#if rule.status == "FAILED">
<tr>
<td>${rule.name}</td>
<td class="status-fail">${rule.status}</td>
<td>${rule.description!""}</td>
</tr>
</#if>
</#list>
</tbody>
</table>
</#if>
<#if execution.message?has_content>
<h3>Additional Details</h3>
<p>${execution.message}</p>
</#if>
</div>
<div class="footer">
<p>Policy: ${policy.name} (ID: ${policy.id}) | Tenant: ${tenant.name}</p>
<p><a href="${openInAccelDataUrl!}" style="color: #007bff;">View in ADOC</a></p>
</div>
</body>
</html>
This template creates a rich, formatted Slack message using Block Kit for data quality alerts.
{
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "<#if execution.resultStatus == 'SUCCESS'>✅<#elseif execution.resultStatus == 'WARNING'>⚠️<#else>🚨</#if> Data Quality: ${policy.name}"
}
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": "*Status:*\n${execution.resultStatus}"
},
{
"type": "mrkdwn",
"text": "*Severity:*\n${execution.severity}"
},
{
"type": "mrkdwn",
"text": "*Quality Score:*\n${result.qualityScore}%"
},
{
"type": "mrkdwn",
"text": "*Asset:*\n${asset.name}"
}
]
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Execution Details:*\n• Rows Scanned: ${result.totalRowsScanned?string[",###"]}\n• Rows Failed: ${result.rowsFailed?string[",###"]}\n• Rules Failed: ${result.rulesFailed} of ${result.totalRules}\n• Time: ${execution.startTime?string["HH:mm:ss"]}"
}
},
<#if result.rulesFailed gt 0>
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Failed Rules:*\n<#list rules as rule><#if rule.status == 'FAILED'>• ${rule.name}\n</#if></#list>"
}
},
</#if>
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "View in ADOC"
},
"url": "${openInAccelDataUrl!}",
"style": "primary"
}
]
},
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": "Policy ID: ${policy.id} | Execution ID: ${execution.id} | ${execution.endTime?string["yyyy-MM-dd HH:mm:ss"]}"
}
]
}
]
}
This template creates a PagerDuty-compatible webhook payload for critical data quality failures.
{
"routing_key": "YOUR_INTEGRATION_KEY",
"event_action": "trigger",
"payload": {
"summary": "Data Quality Failure: ${policy.name} - ${asset.name}",
"severity": "<#if execution.severity == 'Critical'>critical<#elseif execution.severity == 'High'>error<#elseif execution.severity == 'Medium'>warning<#else>info</#if>",
"source": "${tenant.name}",
"component": "${asset.name}",
"group": "data-quality",
"class": "${policy.type}",
"custom_details": {
"policy_name": "${policy.name}",
"policy_id": "${policy.id}",
"execution_id": "${execution.id}",
"asset": "${asset.name}",
"quality_score": "${result.qualityScore}%",
"rows_scanned": ${result.totalRowsScanned},
"rows_failed": ${result.rowsFailed},
"rules_failed": ${result.rulesFailed},
"total_rules": ${result.totalRules},
"execution_time": "${execution.startTime?string["yyyy-MM-dd HH:mm:ss"]}",
"status": "${execution.resultStatus}",
"severity": "${execution.severity}",
<#if execution.message?has_content>"message": "${execution.message}",</#if>
"adoc_link": "${openInAccelDataUrl!}"
}
},
"dedup_key": "adoc-dq-${policy.id}-${execution.id}",
"client": "Acceldata ADOC",
"client_url": "${openInAccelDataUrl!}"
}
This template creates a ServiceNow incident with appropriate field mappings.
{
"short_description": "Data Quality ${execution.resultStatus}: ${policy.name}",
"description": "Data Quality Alert Details:\n\nPolicy: ${policy.name}\nAsset: ${asset.name}\nStatus: ${execution.resultStatus}\nSeverity: ${execution.severity}\n\nMetrics:\n- Quality Score: ${result.qualityScore}%\n- Rows Scanned: ${result.totalRowsScanned?string[",###"]}\n- Rows Failed: ${result.rowsFailed?string[",###"]}\n- Rules Failed: ${result.rulesFailed} of ${result.totalRules}\n\nExecution Time: ${execution.startTime?string["yyyy-MM-dd HH:mm:ss"]} to ${execution.endTime?string["yyyy-MM-dd HH:mm:ss"]}\n\n<#if result.rulesFailed gt 0>Failed Rules:\n<#list rules as rule><#if rule.status == 'FAILED'>- ${rule.name}: ${rule.description!''}\n</#if></#list></#if>\n<#if execution.message?has_content>Additional Details:\n${execution.message}\n</#if>\nView in ADOC: ${openInAccelDataUrl!}",
"impact": "<#if execution.severity == 'Critical'>1<#elseif execution.severity == 'High'>2<#else>3</#if>",
"urgency": "<#if execution.resultStatus == 'FAILED'>1<#elseif execution.resultStatus == 'WARNING'>2<#else>3</#if>",
"category": "Data Quality",
"subcategory": "${policy.type}",
"caller_id": "${tenant.name}",
"u_source_system": "Acceldata ADOC",
"u_policy_name": "${policy.name}",
"u_policy_id": "${policy.id}",
"u_execution_id": "${execution.id}",
"u_asset": "${asset.name}",
"u_quality_score": "${result.qualityScore}",
"work_notes": "Execution ID: ${execution.id}\nPolicy ID: ${policy.id}\nTenant: ${tenant.name}\nDomains: ${domains?join(', ')!''}"
}
This template provides comprehensive pipeline failure information.
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
.alert-banner { background-color: #dc3545; color: white; padding: 20px; text-align: center; font-size: 24px; font-weight: bold; }
.content { padding: 20px; }
.info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin: 20px 0; }
.info-item { background: #f8f9fa; padding: 15px; border-left: 4px solid #dc3545; }
.label { font-weight: bold; color: #666; font-size: 12px; text-transform: uppercase; }
.value { font-size: 18px; color: #333; margin-top: 5px; }
.error-box { background: #fff3cd; border: 1px solid #ffc107; padding: 15px; margin: 20px 0; border-radius: 5px; }
</style>
</head>
<body>
<div class="alert-banner">
🚨 Pipeline Failure Alert
</div>
<div class="content">
<h2>${pipeline.name}</h2>
<div class="info-grid">
<div class="info-item">
<div class="label">Status</div>
<div class="value">${pipeline.status}</div>
</div>
<div class="info-item">
<div class="label">Severity</div>
<div class="value">${execution.severity}</div>
</div>
<div class="info-item">
<div class="label">Start Time</div>
<div class="value">${pipeline.startTime?string["yyyy-MM-dd HH:mm:ss"]}</div>
</div>
<div class="info-item">
<div class="label">Duration</div>
<div class="value">${pipeline.duration} seconds</div>
</div>
<div class="info-item">
<div class="label">Records Processed</div>
<div class="value">${pipeline.recordsProcessed?string[",###"]}</div>
</div>
<div class="info-item">
<div class="label">Bytes Processed</div>
<div class="value">${pipeline.bytesProcessed?string[",###"]}</div>
</div>
</div>
<#if pipeline.errorMessage?has_content>
<div class="error-box">
<h3>Error Details</h3>
<p><strong>Error Type:</strong> ${pipeline.errorType!""}</p>
<p><strong>Message:</strong> ${pipeline.errorMessage}</p>
</div>
</#if>
<#if pipeline.dependencies?has_content>
<h3>Affected Dependencies</h3>
<ul>
<#list pipeline.dependencies as dep>
<li>${dep}</li>
</#list>
</ul>
</#if>
<p style="margin-top: 30px;">
<a href="${openInAccelDataUrl!}" style="background: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; display: inline-block;">View Pipeline Details</a>
</p>
<p style="margin-top: 20px; color: #666; font-size: 12px;">
Pipeline ID: ${pipeline.id} | Execution ID: ${execution.id} | Tenant: ${tenant.name}
</p>
</div>
</body>
</html>
This template alerts on reconciliation mismatches between source and target systems.
{
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "⚠️ Reconciliation Mismatch Detected"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Policy:* ${policy.name}\n*Status:* ${execution.resultStatus}"
}
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": "*Source System:*\n${reconciliation.leftDataSource}\n${result.leftRowCount?string[",###"]} records"
},
{
"type": "mrkdwn",
"text": "*Target System:*\n${reconciliation.rightDataSource}\n${result.rightRowCount?string[",###"]} records"
}
]
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": "*Matched Records:*\n${result.matchedRecords?string[",###"]}"
},
{
"type": "mrkdwn",
"text": "*Unmatched Records:*\n${result.unmatchedRecords?string[",###"]}"
},
{
"type": "mrkdwn",
"text": "*Equality:*\n${result.equalityPercentage}%"
},
{
"type": "mrkdwn",
"text": "*Severity:*\n${execution.severity}"
}
]
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Join Keys:* ${reconciliation.joinKeys?join(', ')}"
}
},
{
"type": "divider"
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Investigate in ADOC"
},
"url": "${openInAccelDataUrl!}",
"style": "danger"
}
]
}
]
}
This template provides a clear summary of schema changes detected.
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
.header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 25px; text-align: center; }
.content { padding: 20px; max-width: 800px; margin: 0 auto; }
.change-section { margin: 20px 0; padding: 15px; border-radius: 5px; }
.added { background: #d4edda; border-left: 5px solid #28a745; }
.removed { background: #f8d7da; border-left: 5px solid #dc3545; }
.modified { background: #fff3cd; border-left: 5px solid #ffc107; }
.change-title { font-weight: bold; font-size: 16px; margin-bottom: 10px; }
.column-list { list-style: none; padding-left: 20px; }
.column-list li { padding: 5px 0; }
.metadata { background: #f8f9fa; padding: 15px; margin-top: 20px; border-radius: 5px; }
</style>
</head>
<body>
<div class="header">
<h1>📊 Schema Drift Detected</h1>
<h2>${policy.name}</h2>
</div>
<div class="content">
<p><strong>Asset:</strong> ${asset.name}</p>
<p><strong>Detection Time:</strong> ${execution.endTime?string["yyyy-MM-dd HH:mm:ss"]}</p>
<p><strong>Total Changes:</strong> ${drift.changeCount}</p>
<p><strong>Last Schema Alteration:</strong> ${drift.lastAlteredTime?string["yyyy-MM-dd HH:mm:ss"]}</p>
<#if drift.addedColumns?has_content>
<div class="change-section added">
<div class="change-title">✅ Added Columns (${drift.addedColumns?size})</div>
<ul class="column-list">
<#list drift.addedColumns as column>
<li>${column}</li>
</#list>
</ul>
</div>
</#if>
<#if drift.removedColumns?has_content>
<div class="change-section removed">
<div class="change-title">❌ Removed Columns (${drift.removedColumns?size})</div>
<ul class="column-list">
<#list drift.removedColumns as column>
<li>${column}</li>
</#list>
</ul>
</div>
</#if>
<#if drift.modifiedColumns?has_content>
<div class="change-section modified">
<div class="change-title">⚠️ Modified Columns (${drift.modifiedColumns?size})</div>
<ul class="column-list">
<#list drift.modifiedColumns as column>
<li>${column}</li>
</#list>
</ul>
</div>
</#if>
<div class="metadata">
<p><strong>Policy Description:</strong> ${policy.description!""}</p>
<p><strong>Domains:</strong> ${domains?join(', ')!""}</p>
<p><a href="${openInAccelDataUrl!}" style="color: #667eea; font-weight: bold;">View Full Details in ADOC →</a></p>
</div>
</div>
</body>
</html>
This template creates an Adaptive Card for Microsoft Teams to alert on data freshness issues.
{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.4",
"body": [
{
"type": "Container",
"style": "<#if execution.resultStatus == 'FAILED'>attention<#elseif execution.resultStatus == 'WARNING'>warning<#else>good</#if>",
"items": [
{
"type": "TextBlock",
"text": "<#if execution.resultStatus == 'FAILED'>⏰<#elseif execution.resultStatus == 'WARNING'>⚠️<#else>✅</#if> Data Freshness Alert",
"weight": "bolder",
"size": "large",
"wrap": true
},
{
"type": "TextBlock",
"text": "${policy.name}",
"size": "medium",
"wrap": true
}
]
},
{
"type": "FactSet",
"facts": [
{
"title": "Status",
"value": "${execution.resultStatus}"
},
{
"title": "Severity",
"value": "${execution.severity}"
},
{
"title": "Asset",
"value": "${asset.name}"
},
{
"title": "Expected Time",
"value": "${freshness.expectedTime?string['HH:mm:ss']}"
},
{
"title": "Actual Time",
"value": "${freshness.actualTime?string['HH:mm:ss']}"
},
{
"title": "Delay",
"value": "${freshness.delayMinutes} minutes"
}
]
},
<#if freshness.expectedBatchCount??>
{
"type": "FactSet",
"facts": [
{
"title": "Expected Batches",
"value": "${freshness.expectedBatchCount}"
},
{
"title": "Actual Batches",
"value": "${freshness.actualBatchCount}"
}
]
},
</#if>
{
"type": "TextBlock",
"text": "Thresholds: Warning at ${freshness.warningThreshold}min, Failure at ${freshness.failureThreshold}min",
"size": "small",
"wrap": true,
"spacing": "medium"
}
],
"actions": [
{
"type": "Action.OpenUrl",
"title": "View in ADOC",
"url": "${openInAccelDataUrl!}"
}
]
}
This template provides a flexible JSON structure suitable for most webhook integrations.
{
"notification_type": "adoc_alert",
"timestamp": "${execution.endTime?string['yyyy-MM-dd\'T\'HH:mm:ss\'Z\'']}",
"alert": {
"title": "${policy.type}: ${policy.name}",
"status": "${execution.resultStatus}",
"severity": "${execution.severity}",
"message": "${execution.message!''}"
},
"policy": {
"id": ${policy.id},
"name": "${policy.name}",
"type": "${policy.type}",
"description": "${policy.description!''}",
"scheduled": ${policy.scheduled?c},
"tags": [<#list policy.tags as tag>"${tag}"<#sep>, </#list>]
},
"execution": {
"id": ${execution.id},
"start_time": "${execution.startTime?string['yyyy-MM-dd\'T\'HH:mm:ss\'Z\'']}",
"end_time": "${execution.endTime?string['yyyy-MM-dd\'T\'HH:mm:ss\'Z\'']}",
"duration_seconds": ${(execution.endTime?long - execution.startTime?long) / 1000}
},
"asset": {
"name": "${asset.name}",
"id": "${asset.id}",
"type": "${asset.type}"
},
"tenant": {
"id": "${tenant.id}",
"name": "${tenant.name}"
},
"context": {
"domains": [<#list domains as domain>"${domain}"<#sep>, </#list>],
"integration": {
"name": "${integration.name!''}",
"id": "${integration.id!''}"
}
},
<#if policy.type == "DATA_QUALITY">
"results": {
"quality_score": ${result.qualityScore},
"total_rows_scanned": ${result.totalRowsScanned},
"rows_passed": ${result.rowsPassed},
"rows_failed": ${result.rowsFailed},
"warning_rows": ${result.warningRows!0},
"total_rules": ${result.totalRules},
"rules_passed": ${result.rulesPassed},
"rules_failed": ${result.rulesFailed}
},
</#if>
"links": {
"adoc_url": "${openInAccelDataUrl!''}"
}
}