10 Proven Patterns to Transform Dashboards into Intelligent Action
Published by MBIC · 2025
Prefer the PDF? Download the original →
Most organizations invest heavily in Power BI for reporting and analytics, yet 73% of business intelligence implementations fail to drive automated action. Reports sit idle, requiring manual interpretation and intervention. This playbook bridges that gap.
Key Findings:
This playbook provides 10 copy-paste automation patterns with real code, architecture guidance, and benchmark-based ROI metrics drawn from common production patterns.
Who Should Read This:
Figures and ROI metrics in this guide are benchmark-based directional estimates, not guarantees for any specific environment. Implementation examples are realistic reference implementations drawn from common patterns MBIC sees in the field — they are not descriptions of specific client engagements. Validate assumptions against your own data before building a business case.
Power BI excels at answering "what happened?" and "what's happening now?" But the real business value emerges when insights automatically trigger action.
The Traditional BI Workflow:
The Automated BI Workflow:
This 96% reduction in response time is the difference between preventing a problem and managing a crisis.
Most organizations use Power BI for:
Yet only 12% have implemented automated actions triggered by BI insights.
Analyst Time:
Delayed Response:
Opportunity Cost:
Investment Required:
Returns:
Payback Period: 3–6 months for most implementations.
Power BI Service:
Power Automate:
Azure Functions (Optional):
Power BI automation requires proper authentication to maintain security while enabling automated access.
Service Principal Setup:
# Create Azure AD App Registration
# Create service principal
# Grant Power BI Service Admin permissions
# (Done through Power BI Admin Portal)
# Store credentials securely
# Use Azure Key Vault for production environments
Security Considerations:
Use Case: Automatically notify stakeholders and create tickets when KPIs breach thresholds.
Business Value:
Step 1: Create Power BI Alert
In Power BI Service:
Configuration example:
Step 2: Build Power Automate Flow
# Trigger
When a data driven alert is triggered
- Alert ID: [Select your alert]
# Condition: Check Severity
IF Alert Value < 85%
THEN
- Priority: High
- Notify: Executives + Operations
ELSE IF Alert Value < 90%
- Priority: Medium
- Notify: Operations only
# Action 1: Send Adaptive Card to Teams
Post adaptive card in channel
Channel: Operations
Card Content:
Title: "⚠ Revenue Alert Triggered"
Subtitle: "Current: @{alertValue}% | Target: 100%"
Facts:
- Department: @{department}
- Threshold Breached: @{timestamp}
Actions:
- View Dashboard [link]
- Acknowledge Alert [button]
# Action 2: Create Service Ticket
Create item (ServiceNow/Jira)
Summary: "Revenue below target - @{department}"
Priority: @{priority}
Assigned To: @{departmentLead}
Description: Auto-generated from Power BI alert
# Action 3: Log Event
Add row to audit log (SharePoint/SQL)
Alert Type: Revenue Threshold
Value: @{alertValue}
Action Taken: Ticket Created
Timestamp: @{utcnow()}
Expected Results:
Reference implementation (benchmark-based): a manufacturing production-line monitoring scenario built on this pattern:
Use Case: Only send reports when they contain actionable information, reducing email noise.
Business Value:
# Trigger: Recurrence
Run every Monday at 8 AM
# Action 1: Get Dataset Rows
Get rows (Power BI)
Dataset: Weekly Sales Summary
Table: SalesByRegion
Filter: Week = Current Week
# Parse Response
Parse JSON
Content: @{body('Get_rows')}
Schema: [Define based on your dataset]
# Condition: Check if Report Warrants Sending
IF any region < target OR total_sales < forecast
THEN Send Report
ELSE Skip (no email sent)
# Build Dynamic Email Content
Create HTML table from query results
- Color code: Red if below target, Green if above
- Include variance percentages
- Add sparkline images (from Power BI export)
# Send Email with Conditional Subject
Send email (Outlook)
To: @{regionalManagers}
Subject:
IF variance > -10%: "Weekly Sales: Attention Required"
ELSE: "Weekly Sales: Review Recommended"
Body:
- Executive Summary (auto-generated)
- Performance Table
- Embedded Power BI Report Link
- Suggested Actions Based on Patterns
Advanced Variation: Personalized Reports
# Get list of managers and their regions
# Loop through each manager
# Filter data for their region
# Only send if their region needs attention
ROI example (benchmark-based):
Use Case: Automatically detect and recover from data refresh failures without manual intervention.
Business Value:
Common Refresh Failure Scenarios:
# Trigger
When a Power BI refresh fails
Dataset: [Select dataset]
# Action 1: Log Failure
Add row to tracking table
Dataset: @{datasetName}
Error: @{errorMessage}
Timestamp: @{utcnow()}
Attempt: 1
# Action 2: Wait and Retry
Delay
Duration: 5 minutes
Refresh dataset (Power BI)
Dataset: @{datasetName}
# Action 3: Check Result
Get refresh history
Dataset: @{datasetName}
Top: 1
# Condition: Did Retry Succeed?
IF status = "Completed"
THEN
# Success path
Update tracking table: Resolved
Send Teams notification: "✓ Auto-recovered"
ELSE IF attempt < 3
# Retry again
Increment attempt counter
Loop back to delay and retry
ELSE
# Escalate after 3 failures
Send priority email to data team
Subject: "URGENT: Dataset refresh failed 3x"
Include: Error logs, last success timestamp
Create P1 incident ticket
Send stakeholder notification:
"Dashboard may show stale data. Team notified."
Advanced Pattern: Intelligent Retry Logic
# Analyze error message to determine retry strategy
Switch (errorType)
Case "timeout":
# Reduce query complexity temporarily
Update dataset parameter: RowLimit = 100000
Retry refresh
If successful:
Gradually increase RowLimit back to normal
Case "authentication":
# Refresh service principal token
Call Azure Function to renew token
Wait 2 minutes
Retry refresh
Case "rate_limit":
# Wait longer before retry
Delay: 30 minutes
Retry refresh
Case "source_unavailable":
# Check source system health
HTTP request to health endpoint
If healthy:
Retry refresh
Else:
Skip this cycle, notify stakeholders
Monitoring Dashboard: Create a Power BI report tracking automation health:
Reference implementation (benchmark-based): an environment with 45 production datasets:
Use Case: Trigger data refreshes based on business events rather than fixed schedules, reducing unnecessary refreshes and ensuring data freshness when it matters.
Business Value:
Business Scenarios:
# Trigger: When source system completes a process
# Option A: Webhook from source system
When HTTP request received
URL: [Your webhook endpoint]
Method: POST
Expected payload: {
"system": "ERP",
"process": "nightly_batch",
"status": "completed",
"timestamp": "2025-01-15T06:30:00Z"
}
# Option B: Monitor for file drop
When a file is created (SharePoint/OneDrive)
Folder: /DataLanding/
File pattern: "Sales_Export_*.csv"
# Option C: Poll for completion flag
Recurrence: Every 15 minutes (during business hours only)
Get item (SQL/SharePoint)
Query: "SELECT BatchStatus WHERE ProcessName = 'Sales ETL'"
Condition: IF BatchStatus = 'Completed' AND LastProcessed < Current Time
# Action 1: Validate Readiness
# Check if refresh is actually needed
Get last refresh time
Dataset: Sales Dashboard
Calculate time since last refresh
IF timeSince < 30 minutes
THEN terminate (too recent)
ELSE proceed
# Action 2: Trigger Refresh
Refresh dataset (Power BI)
Dataset: Sales Dashboard
Notify: OFF # Handle notifications separately
# Action 3: Monitor Completion
Do until refresh completes (max 30 minutes)
Delay: 1 minute
Get refresh history: top 1
IF status = "Completed"
THEN proceed
ELSE IF status = "Failed"
THEN handle error (Pattern 3)
ELSE IF duration > 30 minutes
THEN timeout error
# Action 4: Notify Stakeholders
Send Teams message
Channel: Data Operations
Message: "✓ Sales Dashboard refreshed following ERP batch completion"
Include:
- Rows processed: @{rowCount}
- Refresh duration: @{duration}
- Next scheduled check: @{nextCheck}
Smart Scheduling Logic:
# Only refresh during business hours when users need data
# Reduce refresh frequency outside business hours
# Get current time and day
# Business rules
# Only refresh if critical threshold met
Power BI Premium charges per refresh. Dynamic refresh reduces costs:
Before:
After (Event-Driven):
Savings: 50% reduction in refresh capacity costs.
Use Case: Use statistical methods to detect unusual patterns and automatically alert relevant teams before small issues become big problems.
Business Value:
Anomaly Detection Methods:
# Trigger: Scheduled
Recurrence: Every hour during business hours
# Action 1: Get Historical Data
Get rows (Power BI)
Dataset: Sales Metrics
Table: DailySales
Filter: Date >= @{addDays(utcnow(), -30)} # Last 30 days
# Action 2: Calculate Statistics
# (Done in Azure Function for complex math)
HTTP: Call Azure Function
URL: https://yourfunction.azurewebsites.net/api/detectAnomalies
Method: POST
Body: {
"data": @{body('Get_rows')},
"sensitivity": 2.5, # Standard deviations
"metric": "daily_revenue"
}
Azure Function logic (Python):
import numpy as np
from scipy import stats
def detect_anomalies(data, sensitivity=2.5):
values = [float(d['daily_revenue']) for d in data]
mean = np.mean(values)
std = np.std(values)
latest_value = values[-1]
z_score = (latest_value - mean) / std
is_anomaly = abs(z_score) > sensitivity
# Determine direction
if is_anomaly:
direction = "spike" if z_score > 0 else "drop"
severity = "high" if abs(z_score) > 3 else "medium"
else:
direction = "normal"
severity = "low"
return {
"is_anomaly": is_anomaly,
"direction": direction,
"severity": severity,
"z_score": z_score,
"latest_value": latest_value,
"expected_range": {
"min": mean - (sensitivity * std),
"max": mean + (sensitivity * std)
},
"historical_mean": mean
}
# Action 3: Parse Results
Parse JSON
Content: @{body('HTTP')}
# Action 4: Conditional Action Based on Anomaly
Condition: Is Anomaly Detected?
IF is_anomaly = true
THEN
# Determine notification recipients based on severity
Switch (severity)
Case "high":
Recipients: Executives + Department Heads + Data Team
Priority: P1
Case "medium":
Recipients: Department Heads + Data Team
Priority: P2
Case "low":
Recipients: Data Team only
Priority: P3
# Create rich notification
Send adaptive card (Teams)
Title: "⚠ Anomaly Detected: @{metricName}"
Subtitle: "@{direction} detected - @{severity} severity"
Body:
- Current Value: $@{formatNumber(latestValue, 2)}
- Expected Range: $@{expectedMin} - $@{expectedMax}
- Historical Average: $@{historicalMean}
- Standard Deviations: @{zScore}
- Time Detected: @{utcnow()}
Actions:
- View Dashboard [button - Power BI link]
- Acknowledge [button]
- False Positive [button - update model]
Chart:
[Embed sparkline showing last 30 days with current value highlighted]
# Create incident ticket
Create work item (Azure DevOps/Jira)
Type: Incident
Title: "Anomaly: @{metricName} - @{direction}"
Priority: @{priority}
Description: Auto-generated anomaly alert
Assigned To: @{dataTeamLead}
# Log for machine learning feedback
Add row to tracking table
Metric: @{metricName}
Anomaly Type: @{direction}
Severity: @{severity}
Value: @{latestValue}
ZScore: @{zScore}
Action Taken: Notification Sent
Feedback: [To be updated by user]
Advanced: Multiple Metrics Monitoring
# Monitor multiple related metrics simultaneously
# Detect compound anomalies (multiple metrics anomalous together)
Initialize array: metricsToCheck
- Revenue
- Order Count
- Average Order Value
- Conversion Rate
- Website Traffic
Apply to each metric:
Get data for metric
Call anomaly detection
Add results to array
# Analyze patterns across metrics
# If multiple metrics anomalous, higher severity
Count anomalies: @{length(filter(array, 'is_anomaly = true'))}
IF anomalyCount >= 3
THEN
Severity: Critical
Message: "Multiple metrics showing anomalies - potential systemic issue"
ELSE IF anomalyCount = 2
THEN
Severity: High
Message: "Related metrics showing anomalies"
ELSE
Severity: Medium
Message: "Single metric anomaly detected"
Learning from Feedback:
# When user clicks "False Positive" button
# Update sensitivity threshold for that metric
# Get current sensitivity setting
# Increase sensitivity (require larger deviation)
# Update configuration
# Close ticket
Reference scenario (benchmark-based): e-commerce daily revenue monitoring:
Use Case: Execute complex business logic or third-party API integrations when Power BI data meets specific conditions.
When to Use Azure Functions:
Business Value:
Example Scenario: Dynamic Pricing Adjustments. When competitor prices change (detected in Power BI), automatically adjust your pricing via API call.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
public static class PricingAdjustment
{
[FunctionName("AdjustPricing")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req,
ILogger log)
{
log.LogInformation("Pricing adjustment triggered");
// Parse request
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
string productId = data?.product_id;
decimal competitorPrice = data?.competitor_price;
decimal currentPrice = data?.current_price;
// Business logic: Calculate new price
decimal priceGap = currentPrice - competitorPrice;
decimal gapPercentage = (priceGap / competitorPrice) * 100;
PricingDecision decision = new PricingDecision();
if (gapPercentage > 15)
{
// We're too expensive - reduce price
decimal newPrice = competitorPrice * 1.05m; // 5% above competitor
decision.Action = "reduce";
decision.NewPrice = Math.Round(newPrice, 2);
decision.Reason = $"Current price {gapPercentage:F1}% above competitor";
}
else if (gapPercentage < -10)
{
// We're underpricing - potential to increase
decimal newPrice = competitorPrice * 0.95m; // 5% below competitor
decision.Action = "increase";
decision.NewPrice = Math.Round(newPrice, 2);
decision.Reason = $"Opportunity to increase price while staying competitive";
}
else
{
// Price is competitive - no change
decision.Action = "maintain";
decision.NewPrice = currentPrice;
decision.Reason = "Current pricing is competitive";
}
// Call pricing API if adjustment needed
if (decision.Action != "maintain")
{
bool success = await UpdatePricingSystem(
productId,
decision.NewPrice,
log
);
decision.Applied = success;
}
// Return decision for Power Automate to process
return new OkObjectResult(decision);
}
private static async Task<bool> UpdatePricingSystem(
string productId,
decimal newPrice,
ILogger log)
{
try
{
using (var client = new HttpClient())
{
// Get API key from environment variables (Azure Key Vault)
string apiKey = Environment.GetEnvironmentVariable("PRICING_API_KEY");
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
var payload = new
{
product_id = productId,
new_price = newPrice,
effective_date = DateTime.UtcNow,
reason = "Automated competitive adjustment",
updated_by = "PowerBI_Automation"
};
var content = new StringContent(
JsonConvert.SerializeObject(payload),
System.Text.Encoding.UTF8,
"application/json"
);
var response = await client.PostAsync(
"https://api.yourpricingsystem.com/v1/prices/update",
content
);
if (response.IsSuccessStatusCode)
{
log.LogInformation($"Price updated successfully for {productId}");
return true;
}
else
{
log.LogError($"Failed to update price: {response.StatusCode}");
return false;
}
}
}
catch (Exception ex)
{
log.LogError($"Error updating pricing system: {ex.Message}");
return false;
}
}
}
public class PricingDecision
{
public string Action { get; set; }
public decimal NewPrice { get; set; }
public string Reason { get; set; }
public bool Applied { get; set; }
}
# Trigger
When Power BI alert fires
Alert: Competitor Price Change Detected
# Action 1: Call Azure Function
HTTP
Method: POST
URI: https://yourfunction.azurewebsites.net/api/AdjustPricing
Headers:
Content-Type: application/json
x-functions-key: @{functionKey}
Body:
{
"product_id": "@{productId}",
"competitor_price": @{competitorPrice},
"current_price": @{currentPrice},
"product_name": "@{productName}"
}
# Action 2: Parse Function Response
Parse JSON
Content: @{body('HTTP')}
# Action 3: Take Action Based on Result
Condition: Was Price Adjusted?
IF action != "maintain" AND applied = true
THEN
# Successful price change
Send approval notification (Teams)
To: Pricing Team
Message:
"✓ Automated price adjustment applied
Product: @{productName}
Old Price: $@{currentPrice}
New Price: $@{newPrice}
Reason: @{reason}
Competitor Price: $@{competitorPrice}"
Actions:
- Approve [button]
- Revert [button]
- View Dashboard [link]
# Log change
Add row to price change audit log
Product: @{productId}
Old Price: @{currentPrice}
New Price: @{newPrice}
Reason: @{reason}
Timestamp: @{utcnow()}
Applied By: Automation
ELSE IF action != "maintain" AND applied = false
# Attempted change failed
Send alert (Email)
To: IT Team + Pricing Team
Subject: "ALERT: Automated pricing update failed"
Priority: High
ELSE
# No change needed
Log event: No action required
Alternative Pattern: Inventory Reordering. When inventory levels fall below threshold, automatically create purchase orders:
# Trigger: Scheduled hourly check
When Power BI dataset refreshes
Dataset: Inventory Levels
# Get low stock items
Get rows (Power BI)
Filter: stock_level < reorder_point AND on_order = 0
# For each low stock item
Apply to each:
# Calculate order quantity (Azure Function)
HTTP: Call function
Endpoint: /api/CalculateOrderQuantity
Body: {
"product_id": "@{productId}",
"current_stock": @{stockLevel},
"average_daily_sales": @{avgSales},
"lead_time_days": @{leadTime},
"safety_stock_days": 7
}
# Create purchase order (ERP API call via Azure Function)
HTTP: Call function
Endpoint: /api/CreatePurchaseOrder
Body: {
"vendor_id": "@{preferredVendor}",
"product_id": "@{productId}",
"quantity": @{calculatedQuantity},
"requested_delivery": "@{addDays(utcnow(), leadTime)}",
"priority": @{priority}
}
# Notify purchasing team
Send email
To: @{purchasingAgent}
Subject: "Auto-generated PO: @{productName}"
Body: Purchase order created automatically based on inventory levels
Azure Functions pricing:
For this pattern:
Reference scenario (benchmark-based): a retailer with 500 SKUs:
Use Case: Enable users to take actions directly from Power BI reports — approve requests, update records, trigger processes.
Business Value:
Step 1: Create Action Button in Power BI
In Power BI Desktop:
// DAX: Create measure for dynamic action URL
ApproveURL =
VAR RequestID = SELECTEDVALUE('Requests'[ID])
VAR FlowURL = "https://prod-12.eastus.logic.azure.com:443/workflows/abc123..."
RETURN
FlowURL & "?requestid=" & RequestID & "&action=approve"
Step 2: Build Power Automate Flow
# Trigger
When HTTP request received
Method: GET
URL: [Auto-generated unique URL]
Parameters:
- requestid (string)
- action (string)
- userid (string, optional)
# Validate request
Condition: Check if requestid exists
# Action 1: Get Request Details
Get row by ID (SharePoint/SQL)
List/Table: PurchaseRequests
ID: @{triggerOutputs()['queries']['requestid']}
# Action 2: Update Status
Switch (action)
Case "approve":
Update item
Status: Approved
Approved By: @{userid}
Approved Date: @{utcnow()}
# Trigger downstream process
Create purchase order (ERP API)
Case "reject":
Update item
Status: Rejected
Rejected By: @{userid}
Rejected Date: @{utcnow()}
# Notify requester
Send email to requester
Case "escalate":
Update item
Status: Escalated
Escalated To: @{managerEmail}
# Send to next level
Post adaptive card to manager's Teams
# Action 3: Refresh Power BI Dataset
# So user sees updated status immediately
Refresh dataset (Power BI)
Dataset: Purchase Requests Dashboard
# Action 4: Return Response
Response
Status: 200
Body: {
"success": true,
"message": "Request @{action}d successfully",
"requestid": "@{requestid}"
}
Step 3: Create Feedback Loop. Show confirmation to user:
# Add Response action
Response (Power Automate)
Status Code: 200
Headers:
Content-Type: text/html
Body:
<html>
<head>
<style>
body { font-family: Arial; padding: 40px; text-align: center; }
.success { color: green; font-size: 24px; }
.details { color: #666; margin-top: 20px; }
</style>
</head>
<body>
<div class="success">✓ Action Completed Successfully</div>
<div class="details">
Request @{requestid} has been @{action}d
<br><br>
<a href="powerbi://...">Return to Dashboard</a>
</div>
</body>
</html>
Advanced Pattern: Bulk Actions
# Create button that processes multiple rows
# User selects multiple items, clicks "Approve Selected"
# In Power BI, pass multiple IDs
BulkApproveURL =
VAR SelectedIDs =
CONCATENATEX(
ALLSELECTED('Requests'),
'Requests'[ID],
","
)
VAR FlowURL = "https://prod-12.eastus.logic.azure.com:443/..."
RETURN
FlowURL & "?requestids=" & SelectedIDs & "&action=approve"
# In Power Automate
When HTTP request received
Parameters:
- requestids (string, comma-separated)
# Split into array
Set variable: requestArray
Value: @{split(triggerOutputs()['queries']['requestids'], ',')}
# Process each
Apply to each: @{variables('requestArray')}
Get item by ID
Update status
Trigger downstream action
# Return summary
Response:
"Processed @{length(variables('requestArray'))} requests"
Security Considerations:
# Add authentication to prevent unauthorized actions
# Option 1: Require User ID
# Check permissions
# Option 2: Time-Limited Token
# Generate token in Power BI with expiration
# Validate token in Power Automate before processing
Reference use case (benchmark-based): invoice approval. A finance team approves invoices directly from a Power BI dashboard:
Use Case: Automatically generate and distribute formatted reports (PDF, Excel, PowerPoint) with current data on schedule or trigger.
Business Value:
# Trigger: Scheduled
Recurrence
Time: Every Monday at 7 AM
Timezone: Eastern Time
# Action 1: Export Report from Power BI
# Using Power BI REST API
HTTP
Method: POST
URI: https://api.powerbi.com/v1.0/myorg/groups/@{workspaceId}/reports/@{reportId}/ExportTo
Headers:
Authorization: Bearer @{pbiToken}
Content-Type: application/json
Body:
{
"format": "PDF",
"paginatedReportConfiguration": {
"formatSettings": {
"PageHeight": "11in",
"PageWidth": "8.5in"
}
},
"defaultBookmark": {
"name": "Executive Summary"
},
"powerBIReportConfiguration": {
"reportLevelFilters": [
{
"filter": "Date ge @{startOfWeek()} and Date le @{endOfWeek()}"
}
]
}
}
# Action 2: Poll for Export Completion
Do until export complete (max 5 minutes)
Delay: 10 seconds
HTTP: Check export status
Method: GET
URI: https://api.powerbi.com/v1.0/myorg/groups/@{workspaceId}/reports/@{reportId}/exports/@{exportId}
Parse JSON: @{body('HTTP')}
Condition: Status = "Succeeded"
# Action 3: Download File
HTTP
Method: GET
URI: https://api.powerbi.com/v1.0/myorg/groups/@{workspaceId}/reports/@{reportId}/exports/@{exportId}/file
Headers:
Authorization: Bearer @{pbiToken}
# Action 4: Store File
Create file (SharePoint/OneDrive)
Folder: /Reports/Weekly
File name: Executive_Summary_@{formatDateTime(utcnow(), 'yyyy-MM-dd')}.pdf
File content: @{body('HTTP_Download')}
# Action 5: Distribute Report
# Option A: Email
Send email (Outlook)
To: executives@company.com
Subject: Weekly Executive Summary - @{formatDateTime(utcnow(), 'MMMM dd, yyyy')}
Body:
"Attached is this week's executive summary report.
Data current as of @{formatDateTime(utcnow(), 'MM/dd/yyyy hh:mm tt')}
Key highlights:
- @{highlight1}
- @{highlight2}
- @{highlight3}
View live dashboard: [Power BI link]"
Attachments: @{body('Create_file')}
# Option B: Post to Teams
Post message (Teams)
Channel: Executive Team
Message: "Weekly Executive Summary available"
Attachment: @{body('Create_file')}
# Option C: Update SharePoint Document Library
# (Already done in Step 4, just notify)
Send Teams notification
Multi-Format Export:
# Export same report in multiple formats
# PDF for executives, Excel for analysts
# Export as PDF
# Export as Excel
HTTP
Body:
"format": "XLSX"
# Create separate distribution lists
# PDF to executives
# Excel to analysts
Paginated Reports with Parameters. For more control over formatting, use Power BI Paginated Reports:
# Trigger paginated report render
HTTP
Method: POST
URI: https://api.powerbi.com/v1.0/myorg/groups/@{workspaceId}/reports/@{paginatedReportId}/ExportTo
Body:
{
"format": "PDF",
"paginatedReportConfiguration": {
"parameterValues": [
{
"name": "StartDate",
"value": "@{startOfMonth()}"
},
{
"name": "EndDate",
"value": "@{endOfMonth()}"
},
{
"name": "Department",
"value": "Sales"
}
]
}
}
Dynamic Distribution Lists:
# Get recipients based on data
# E.g., send regional reports to regional managers
Get items (SharePoint)
List: Regional Managers
Filter: IsActive eq true
Apply to each manager:
# Export report filtered for their region
HTTP: Export Power BI report
Body:
{
"format": "PDF",
"powerBIReportConfiguration": {
"reportLevelFilters": [
{
"filter": "Region eq '@{currentManager.Region}'"
}
]
}
}
# Wait for completion and download
[Standard export flow]
# Send personalized report
Send email
To: @{currentManager.Email}
Subject: "@{currentManager.Region} Region - Weekly Report"
Attachments: [Report file]
Archive & Compliance: maintain a report archive for compliance.
Reference scenario (benchmark-based): a healthcare organization with 12 executive reports:
Use Case: Connect Power BI insights to create workflows spanning multiple business systems — CRM, ERP, ITSM, etc.
Business Value:
Example: Lead to Quote to Order Process. When Power BI identifies a high-value sales opportunity, automatically:
# Trigger
When Power BI alert fires
Alert: High-Value Lead Score Threshold Met
# Parse Alert Data
# Alert contains: lead_id, lead_score, company_name, estimated_value, contact_email
# Step 1: Enrich Lead Data from Marketing System
HTTP: Get lead details
Method: GET
URI: https://api.marketingcloud.com/leads/@{lead_id}
Headers:
Authorization: Bearer @{marketingToken}
Parse JSON: Lead Details
# Step 2: Create Opportunity in CRM (Salesforce)
HTTP: Create Salesforce Opportunity
Method: POST
URI: https://yourinstance.salesforce.com/services/data/v54.0/sobjects/Opportunity
Headers:
Authorization: Bearer @{salesforceToken}
Content-Type: application/json
Body:
{
"Name": "@{companyName} - @{productInterest}",
"StageName": "Prospecting",
"CloseDate": "@{addDays(utcnow(), 60)}",
"Amount": @{estimatedValue},
"LeadSource": "Power BI Automation",
"Description": "Auto-created from lead score @{leadScore}",
"AccountId": "@{accountId}",
"ContactId": "@{contactId}"
}
# Capture Salesforce Opportunity ID
Set variable: opportunityId
Value: @{body('HTTP').id}
# Step 3: Check Inventory Availability (ERP)
HTTP: Check inventory
Method: POST
URI: https://api.erpsystem.com/v1/inventory/check
Body:
{
"products": @{requestedProducts},
"quantity": @{requestedQuantity},
"required_date": "@{addDays(utcnow(), 30)}"
}
Parse JSON: Inventory Response
# Step 4: Generate Quote (CPQ System)
Condition: Is inventory available?
IF available = true
THEN
HTTP: Create quote
Method: POST
URI: https://api.cpqsystem.com/quotes
Body:
{
"opportunity_id": "@{opportunityId}",
"customer": "@{companyName}",
"line_items": @{lineItems},
"discount_tier": "@{calculateDiscountTier(estimatedValue)}",
"valid_until": "@{addDays(utcnow(), 30)}",
"delivery_date": "@{addDays(utcnow(), 45)}"
}
# Generate PDF quote
HTTP: Render quote PDF
URI: https://api.cpqsystem.com/quotes/@{quoteId}/pdf
Set variable: quoteReady = true
ELSE
# Inventory not available
Set variable: quoteReady = false
Set variable: blockerReason = "Insufficient inventory"
# Step 5: Create Project in Project Management Tool
HTTP: Create project (Asana/Monday/Jira)
Method: POST
URI: https://api.asana.com/api/1.0/projects
Headers:
Authorization: Bearer @{asanaToken}
Body:
{
"workspace": "@{workspaceId}",
"name": "@{companyName} - @{opportunityId}",
"team": "@{salesTeamId}",
"notes": "Auto-created from Power BI high-value lead alert",
"custom_fields": {
"estimated_value": @{estimatedValue},
"lead_score": @{leadScore},
"salesforce_id": "@{opportunityId}"
}
}
# Step 6: Create Tasks in Project
HTTP: Create task - Initial Contact
Body:
{
"project": "@{projectId}",
"name": "Initial contact with @{contactName}",
"assignee": "@{salesRep}",
"due_date": "@{addDays(utcnow(), 1)}"
}
HTTP: Create task - Send Quote
Body:
{
"project": "@{projectId}",
"name": "Send quote to @{contactEmail}",
"assignee": "@{salesRep}",
"due_date": "@{addDays(utcnow(), 2)}"
}
HTTP: Create task - Follow Up
Body:
{
"project": "@{projectId}",
"name": "Follow up on quote",
"assignee": "@{salesRep}",
"due_date": "@{addDays(utcnow(), 7)}"
}
# Step 7: Notify Sales Team
Post adaptive card (Teams)
Channel: Sales Team
Card:
Title: "New High-Value Opportunity"
Subtitle: "@{companyName} - $@{formatNumber(estimatedValue, 0)}"
Facts:
- Lead Score: @{leadScore}
- Product Interest: @{productInterest}
- Decision Maker: @{contactName}
- Email: @{contactEmail}
- Inventory: @{IF(quoteReady, 'Available', 'Issue')}
Actions:
- View in Salesforce [link to opportunity]
- View Quote [link to quote PDF]
- View Project [link to Asana]
- Contact Lead [mailto link]
# Send personalized email to assigned sales rep
Send email (Outlook)
To: @{salesRepEmail}
Subject: "New High-Value Lead Assigned: @{companyName}"
Body: [HTML formatted email with details]
Attachments:
- Quote PDF (if ready)
- Lead intelligence report
# Step 8: Update Power BI Dataset
# Add row to tracking table so this appears in dashboards
Add row (SQL)
Table: AutomatedOpportunities
Columns:
OpportunityId: @{opportunityId}
LeadId: @{lead_id}
CompanyName: @{companyName}
EstimatedValue: @{estimatedValue}
LeadScore: @{leadScore}
CreatedDate: @{utcnow()}
ProjectId: @{projectId}
QuoteId: @{quoteId}
AssignedTo: @{salesRep}
Status: Created
# Refresh dataset so new opportunity appears immediately
Refresh dataset (Power BI)
Dataset: Sales Pipeline Dashboard
Error Handling for Multi-System Workflows:
# Wrap each system call in try-catch
# Track which steps succeeded/failed
# For each step:
# Continue or halt based on criticality
# Halt and notify
# Log and continue
# At end, log full execution
This pattern gets complex quickly. Here's how to manage it:
Reference scenario (benchmark-based): a B2B SaaS company with 200 high-value leads per month:
Use Case: Automatically log all automated actions, generate compliance reports, and alert on policy violations.
Business Value:
Why This Matters: when you automate BI workflows, you're often:
All of this needs to be auditable.
# This pattern wraps OTHER patterns
# Every automation flow should include logging
# Standard Logging Function (Azure Function)
# Call this from every Power Automate flow
HTTP: Log Automation Event
Method: POST
URI: https://yourfunction.azurewebsites.net/api/LogEvent
Body:
{
"workflow_name": "@{workflow().name}",
"workflow_id": "@{workflow().run.name}",
"trigger_type": "@{trigger_type}",
"trigger_data": @{triggerOutputs()},
"user_id": "@{user_id}",
"timestamp": "@{utcnow()}",
"action_type": "@{action_type}",
"resource_accessed": "@{resource_id}",
"sensitivity_level": "@{data_classification}",
"result": "@{execution_result}",
"ip_address": "@{client_ip}",
"duration_ms": @{execution_duration}
}
Azure Function: Audit Log Processor
[FunctionName("LogEvent")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req,
[CosmosDB(
databaseName: "ComplianceDB",
collectionName: "AuditLogs",
ConnectionStringSetting = "CosmosDBConnection")] IAsyncCollector<AuditLog> auditLogs,
ILogger log)
{
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
// Create audit log entry
var auditLog = new AuditLog
{
Id = Guid.NewGuid().ToString(),
WorkflowName = data?.workflow_name,
WorkflowId = data?.workflow_id,
TriggerType = data?.trigger_type,
UserId = data?.user_id,
Timestamp = DateTime.UtcNow,
ActionType = data?.action_type,
ResourceAccessed = data?.resource_accessed,
SensitivityLevel = data?.sensitivity_level,
Result = data?.result,
IpAddress = data?.ip_address,
DurationMs = data?.duration_ms
};
// Check for compliance violations
var violations = CheckCompliance(auditLog);
if (violations.Any())
{
auditLog.ComplianceViolations = violations;
await AlertSecurityTeam(auditLog, violations);
}
// Store audit log
await auditLogs.AddAsync(auditLog);
// Check for anomalies
await CheckForAnomalies(auditLog);
return new OkObjectResult(new {
success = true,
auditId = auditLog.Id
});
}
private static List<string> CheckCompliance(AuditLog log)
{
var violations = new List<string>();
// Check for after-hours access to sensitive data
if (log.SensitivityLevel == "High" && !IsDuringBusinessHours(log.Timestamp))
{
violations.Add("After-hours access to sensitive data");
}
// Check for unusual data access patterns
if (log.ActionType == "DataExport" && log.ResourceAccessed.Contains("CustomerPII"))
{
violations.Add("PII data export - requires additional review");
}
// Check for excessive automation actions
// (potential automation gone wrong or malicious activity)
var recentLogs = GetRecentLogs(log.WorkflowName, minutes: 60);
if (recentLogs.Count > 100)
{
violations.Add("Excessive automation activity detected");
}
return violations;
}
Compliance Dashboard in Power BI:
// Measures for compliance monitoring
Automation Events Today =
CALCULATE(
COUNT(AuditLogs[Id]),
AuditLogs[Date] = TODAY()
)
Compliance Violations =
CALCULATE(
COUNT(AuditLogs[Id]),
AuditLogs[HasViolation] = TRUE
)
Violation Rate =
DIVIDE(
[Compliance Violations],
[Automation Events Today],
0
)
// Alert if violation rate exceeds threshold
Violation Alert =
IF(
[Violation Rate] > 0.02, // 2% threshold
"ALERT",
"OK"
)
// Track sensitive data access
PII Access Events =
CALCULATE(
COUNT(AuditLogs[Id]),
AuditLogs[SensitivityLevel] = "High"
)
// After-hours activity
After Hours Access =
CALCULATE(
COUNT(AuditLogs[Id]),
AuditLogs[TimeOfDay] < TIME(8,0,0) ||
AuditLogs[TimeOfDay] > TIME(18,0,0)
)
// Failed automation attempts
Failed Automations =
CALCULATE(
COUNT(AuditLogs[Id]),
AuditLogs[Result] = "Failed"
)
Failure Rate =
DIVIDE(
[Failed Automations],
[Automation Events Today],
0
)
Automated Compliance Reporting:
# Generate monthly compliance report
# Runs first day of each month
Trigger: Recurrence
Frequency: Monthly
Day: 1
Time: 06:00
# Query audit logs for previous month
HTTP: Get Cosmos DB data
Query:
SELECT * FROM AuditLogs
WHERE timestamp >= '@{startOfLastMonth()}'
AND timestamp < '@{startOfThisMonth()}'
# Analyze data
Parse JSON: Audit log data
# Calculate metrics
Compose: Compliance metrics
Total Events: @{length(body('Parse_JSON'))}
Violations: @{length(filter(body('Parse_JSON'), 'HasViolation = true'))}
Unique Users: @{length(union(map(body('Parse_JSON'), 'UserId')))}
Most Active Workflows: [Top 10]
Sensitivity Breakdown: [Count by level]
After Hours Events: [Count and details]
Failed Automations: [Count and details]
# Generate report
HTTP: Create compliance report
Method: POST
URI: https://reportgenerator.com/api/generate
Body:
{
"template": "SOC2_Automation_Report",
"data": @{outputs('Compose')},
"period": "@{formatDateTime(startOfLastMonth(), 'MMMM yyyy')}"
}
# Store report
Create file (SharePoint)
Library: Compliance Reports
Folder: /@{year}/
File: Automation_Compliance_@{formatDateTime(utcnow(), 'yyyy-MM')}.pdf
# Notify compliance team
Send email
To: compliance@company.com
Subject: "Monthly Automation Compliance Report - @{lastMonth}"
Body:
"Attached is the automated compliance report for @{lastMonth}.
Summary:
- Total automation events: @{totalEvents}
- Compliance violations: @{violations}
- Violation rate: @{violationRate}%
- Action required: @{IF(violations > threshold, 'Yes - review attached', 'No')}"
Attachments: [Report PDF]
Real-Time Violation Alerts:
# Separate flow for immediate violations
Trigger: When audit log entry created (Cosmos DB trigger)
Collection: AuditLogs
Condition: Has violations?
IF ComplianceViolations is not empty
THEN
# Determine severity
Switch (ViolationType)
Case "After-hours PII access":
Severity: High
Recipients: Security Team + Compliance Officer
Case "Excessive automation":
Severity: Medium
Recipients: IT Team
Case "Failed authentication":
Severity: High
Recipients: Security Team
# Send immediate alert
Post message (Teams)
Channel: Security Alerts
Message:
"Compliance Violation Detected
Type: @{violationType}
Severity: @{severity}
Workflow: @{workflowName}
User: @{userId}
Time: @{timestamp}
Resource: @{resourceAccessed}
Details: @{violationDetails}"
# Create incident ticket
HTTP: Create ServiceNow incident
Priority: @{severity}
Short Description: "Automation compliance violation"
Assignment Group: Security Operations
# If high severity, page on-call
IF severity = "High"
HTTP: PagerDuty alert
Incident key: @{auditId}
Description: @{violationType}
Data Retention Policy:
# Monthly cleanup of old audit logs per retention policy
Trigger: Recurrence
Frequency: Monthly
Day: 15
Time: 02:00
# Query old logs
# Retention: 7 years for SOC 2 compliance
HTTP: Get Cosmos DB documents
Query:
SELECT * FROM AuditLogs
WHERE timestamp < '@{addYears(utcnow(), -7)}'
AND archived = false
# Archive to cold storage before deletion
Apply to each old log:
# Copy to Azure Blob (cold tier)
Create blob
Container: audit-archive
Blob name: @{year}/@{month}/@{logId}.json
Content: @{json(currentLog)}
Access tier: Archive
# Mark as archived in Cosmos
Update document
Id: @{logId}
Archived: true
ArchiveDate: @{utcnow()}
# Wait 30 days then permanently delete
# (separate scheduled flow)
# Notify compliance team of archival
Send email: Monthly archival report
Audit Log Query API. For compliance audits, provide an API to search logs:
[FunctionName("QueryAuditLogs")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req,
[CosmosDB(ConnectionStringSetting = "CosmosDBConnection")] DocumentClient client)
{
// Parse query parameters
string userId = req.Query["userId"];
string workflowName = req.Query["workflowName"];
DateTime? startDate = ParseDate(req.Query["startDate"]);
DateTime? endDate = ParseDate(req.Query["endDate"]);
// Build Cosmos DB query
var query = client.CreateDocumentQuery<AuditLog>(
UriFactory.CreateDocumentCollectionUri("ComplianceDB", "AuditLogs"),
new FeedOptions { EnableCrossPartitionQuery = true }
);
if (!string.IsNullOrEmpty(userId))
query = query.Where(l => l.UserId == userId);
if (!string.IsNullOrEmpty(workflowName))
query = query.Where(l => l.WorkflowName == workflowName);
if (startDate.HasValue)
query = query.Where(l => l.Timestamp >= startDate.Value);
if (endDate.HasValue)
query = query.Where(l => l.Timestamp <= endDate.Value);
var results = query.ToList();
return new OkObjectResult(results);
}
Reference scenario (benchmark-based): a financial services organization implementing SOC 2 compliance:
Authentication:
Authorization:
Data Protection:
Flow Security:
Monitoring:
Time Savings example:
Business Impact:
Implementation Cost:
For a full calculation methodology, see the companion BI Automation ROI Calculator guide.
Week 1: Assessment
Week 2: Quick Win
Week 3: Foundation
Week 4: Scale
Contact MBIC at hello@mbic.us to:
Complete companion templates for this playbook are available from MBIC on request (hello@mbic.us), including:
MBIC transforms static dashboards into intelligent automation systems. We specialize in Power BI automation, AI agent deployment, and enterprise workflow integration.
Services:
Contact: hello@mbic.us · mbic.us
Get a free AI & Automation Opportunity Audit → mbic.us/ai-audit.html — or book 15 minutes → mbic.us/contact.html
Prefer the PDF? Download the original →