Add comprehensive agentic coding documentation
- Add 6 MCP server documentation pages (bc-code-intelligence, al-dependency, azure-devops, serena, al-objid, clockify) - Add AI Coding Agents documentation (GitHub Copilot, Claude, Cursor) - Add Getting Started section (what is agentic coding, setup, prompting, best practices, limitations, glossary) - Add Getting More section (code review, documentation, telemetry examples) - Add Community Resources placeholders (articles, videos, tools) - Update Tools section to focus on MCP servers instead of VSCode extensions
This commit is contained in:
parent
3254d9b7e8
commit
4db38ba4d3
28 changed files with 8152 additions and 0 deletions
47
content/docs/agentic-coding/GettingMore/_index.md
Normal file
47
content/docs/agentic-coding/GettingMore/_index.md
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
title: "Getting More"
|
||||
linkTitle: "Getting More"
|
||||
weight: 20
|
||||
description: >
|
||||
Practical examples and advanced techniques for AI-assisted AL development
|
||||
---
|
||||
|
||||
This section provides hands-on examples of how to use AI assistants for common AL development tasks. Each guide walks through a realistic scenario with step-by-step instructions and prompts.
|
||||
|
||||
## In This Section
|
||||
|
||||
- **[AI-Assisted Code Review](code-review)** - Use AI to review code for quality, performance, and best practices
|
||||
- **[Generating Documentation](documentation)** - Automatically create and maintain documentation for your AL code
|
||||
- **[Adding Telemetry](telemetry)** - Instrument your extensions with Application Insights telemetry
|
||||
- **[Refactoring Legacy Code](refactoring)** - Modernize and improve existing AL code
|
||||
- **[Writing Tests](testing)** - Create comprehensive test coverage with AI assistance
|
||||
|
||||
## How to Use These Guides
|
||||
|
||||
Each guide follows a practical, scenario-based approach:
|
||||
|
||||
1. **Scenario**: A realistic development task
|
||||
2. **Goal**: What you're trying to achieve
|
||||
3. **Step-by-Step**: Detailed instructions with actual prompts
|
||||
4. **Review Points**: What to check in the AI-generated code
|
||||
5. **Tips**: Additional insights and variations
|
||||
|
||||
## Before You Start
|
||||
|
||||
Make sure you've completed the [Getting Started](../getting-started) section to:
|
||||
- Understand agentic coding concepts
|
||||
- Set up your environment
|
||||
- Learn effective prompting
|
||||
- Know the best practices and limitations
|
||||
|
||||
## Learning by Doing
|
||||
|
||||
These examples are designed to be:
|
||||
- **Practical**: Based on real AL development tasks
|
||||
- **Detailed**: Step-by-step instructions you can follow
|
||||
- **Educational**: Explains why, not just what
|
||||
- **Adaptable**: Patterns you can apply to your own work
|
||||
|
||||
## Contributing Examples
|
||||
|
||||
Have a great example of AI-assisted AL development? Consider contributing! See the [Contributing](../../contributing) section for guidelines.
|
||||
368
content/docs/agentic-coding/GettingMore/code-review.md
Normal file
368
content/docs/agentic-coding/GettingMore/code-review.md
Normal file
|
|
@ -0,0 +1,368 @@
|
|||
---
|
||||
title: "AI-Assisted Code Review"
|
||||
linkTitle: "Code Review"
|
||||
weight: 1
|
||||
description: >
|
||||
Learn how to use AI assistants to review AL code for quality, performance, and best practices
|
||||
---
|
||||
|
||||
## Scenario
|
||||
|
||||
You've just finished implementing a new feature: a codeunit that processes sales orders and applies volume-based discounts. Before submitting for peer review, you want to use AI to catch potential issues.
|
||||
|
||||
## Goal
|
||||
|
||||
Use AI to identify:
|
||||
- Potential bugs or logical errors
|
||||
- Performance issues
|
||||
- AL best practice violations
|
||||
- Missing error handling
|
||||
- Code quality improvements
|
||||
|
||||
## The Code to Review
|
||||
|
||||
Here's the codeunit we'll review:
|
||||
|
||||
```al
|
||||
codeunit 50100 "Sales Order Discount Processor"
|
||||
{
|
||||
procedure ApplyVolumeDiscounts(var SalesHeader: Record "Sales Header")
|
||||
var
|
||||
SalesLine: Record "Sales Line";
|
||||
TotalQuantity: Decimal;
|
||||
DiscountPct: Decimal;
|
||||
begin
|
||||
TotalQuantity := 0;
|
||||
SalesLine.SetRange("Document Type", SalesHeader."Document Type");
|
||||
SalesLine.SetRange("Document No.", SalesHeader."No.");
|
||||
if SalesLine.FindSet() then
|
||||
repeat
|
||||
TotalQuantity := TotalQuantity + SalesLine.Quantity;
|
||||
until SalesLine.Next() = 0;
|
||||
|
||||
if TotalQuantity > 100 then
|
||||
DiscountPct := 15
|
||||
else if TotalQuantity > 50 then
|
||||
DiscountPct := 10
|
||||
else if TotalQuantity > 10 then
|
||||
DiscountPct := 5;
|
||||
|
||||
SalesLine.Reset();
|
||||
if SalesLine.FindSet() then
|
||||
repeat
|
||||
SalesLine."Line Discount %" := DiscountPct;
|
||||
SalesLine.Modify();
|
||||
until SalesLine.Next() = 0;
|
||||
end;
|
||||
}
|
||||
```
|
||||
|
||||
## Step-by-Step Review Process
|
||||
|
||||
### Step 1: Initial Quality Review
|
||||
|
||||
**Prompt**:
|
||||
```
|
||||
Review this AL codeunit for potential bugs, code quality issues, and best practice violations.
|
||||
|
||||
[Paste the code above]
|
||||
|
||||
Specifically check for:
|
||||
- Logical errors
|
||||
- Missing error handling
|
||||
- Variable declarations
|
||||
- Commit/transaction issues
|
||||
- Performance concerns
|
||||
```
|
||||
|
||||
**Expected AI Findings**:
|
||||
The AI should identify issues like:
|
||||
- Missing `FindSet(true, false)` parameters for modification
|
||||
- No error handling
|
||||
- Multiple FindSet operations (inefficient)
|
||||
- Hard-coded discount percentages
|
||||
- No validation of input parameters
|
||||
- Missing ModifyAll opportunity
|
||||
- No telemetry or logging
|
||||
|
||||
### Step 2: Performance Analysis
|
||||
|
||||
**Prompt**:
|
||||
```
|
||||
Analyze the performance of this code, especially database operations.
|
||||
What are potential performance issues? How can it be optimized?
|
||||
|
||||
[Include the code or reference it if in context]
|
||||
```
|
||||
|
||||
**Expected AI Suggestions**:
|
||||
- Combine the two FindSet loops into one
|
||||
- Use ModifyAll instead of looping for discount application
|
||||
- Consider using SetLoadFields for better performance
|
||||
- Cache discount percentages instead of hard-coding
|
||||
|
||||
### Step 3: Best Practices Check
|
||||
|
||||
**Prompt**:
|
||||
```
|
||||
Check this code against AL coding best practices and Business Central development standards.
|
||||
What improvements would you recommend?
|
||||
```
|
||||
|
||||
**Expected AI Recommendations**:
|
||||
- Add XML documentation
|
||||
- Extract discount calculation to separate function
|
||||
- Use constants for discount tiers
|
||||
- Add error handling
|
||||
- Implement proper transaction handling
|
||||
- Add telemetry for monitoring
|
||||
|
||||
### Step 4: Request Improved Version
|
||||
|
||||
**Prompt**:
|
||||
```
|
||||
Rewrite this codeunit implementing all the improvements you suggested.
|
||||
Include:
|
||||
- XML documentation
|
||||
- Error handling
|
||||
- Performance optimizations
|
||||
- Best practice compliance
|
||||
- Telemetry/logging
|
||||
- Discount tiers from setup table instead of hard-coded values
|
||||
```
|
||||
|
||||
**Review the Generated Code**:
|
||||
The AI should generate something like:
|
||||
|
||||
```al
|
||||
/// <summary>
|
||||
/// Processes sales orders and applies volume-based discounts
|
||||
/// </summary>
|
||||
codeunit 50100 "Sales Order Discount Processor"
|
||||
{
|
||||
/// <summary>
|
||||
/// Applies volume-based discounts to a sales order
|
||||
/// </summary>
|
||||
/// <param name="SalesHeader">The sales header to process</param>
|
||||
procedure ApplyVolumeDiscounts(var SalesHeader: Record "Sales Header")
|
||||
var
|
||||
SalesLine: Record "Sales Line";
|
||||
TotalQuantity: Decimal;
|
||||
DiscountPct: Decimal;
|
||||
begin
|
||||
if SalesHeader."No." = '' then
|
||||
Error('Sales order number cannot be empty.');
|
||||
|
||||
TotalQuantity := CalculateTotalQuantity(SalesHeader);
|
||||
DiscountPct := GetDiscountPercentage(TotalQuantity);
|
||||
|
||||
if DiscountPct > 0 then
|
||||
ApplyDiscountToLines(SalesHeader, DiscountPct);
|
||||
|
||||
LogTelemetry(SalesHeader."No.", TotalQuantity, DiscountPct);
|
||||
end;
|
||||
|
||||
local procedure CalculateTotalQuantity(SalesHeader: Record "Sales Header"): Decimal
|
||||
var
|
||||
SalesLine: Record "Sales Line";
|
||||
TotalQty: Decimal;
|
||||
begin
|
||||
SalesLine.SetRange("Document Type", SalesHeader."Document Type");
|
||||
SalesLine.SetRange("Document No.", SalesHeader."No.");
|
||||
SalesLine.SetLoadFields(Quantity);
|
||||
|
||||
if SalesLine.FindSet() then
|
||||
repeat
|
||||
TotalQty += SalesLine.Quantity;
|
||||
until SalesLine.Next() = 0;
|
||||
|
||||
exit(TotalQty);
|
||||
end;
|
||||
|
||||
local procedure GetDiscountPercentage(TotalQuantity: Decimal): Decimal
|
||||
var
|
||||
VolumeDiscountSetup: Record "Volume Discount Setup"; // Assuming this table exists
|
||||
begin
|
||||
VolumeDiscountSetup.SetFilter("Minimum Quantity", '<=%1', TotalQuantity);
|
||||
VolumeDiscountSetup.SetCurrentKey("Minimum Quantity");
|
||||
VolumeDiscountSetup.Ascending(false);
|
||||
|
||||
if VolumeDiscountSetup.FindFirst() then
|
||||
exit(VolumeDiscountSetup."Discount Percentage");
|
||||
|
||||
exit(0);
|
||||
end;
|
||||
|
||||
local procedure ApplyDiscountToLines(SalesHeader: Record "Sales Header"; DiscountPct: Decimal)
|
||||
var
|
||||
SalesLine: Record "Sales Line";
|
||||
begin
|
||||
SalesLine.SetRange("Document Type", SalesHeader."Document Type");
|
||||
SalesLine.SetRange("Document No.", SalesHeader."No.");
|
||||
SalesLine.ModifyAll("Line Discount %", DiscountPct, true);
|
||||
end;
|
||||
|
||||
local procedure LogTelemetry(DocumentNo: Code[20]; TotalQty: Decimal; DiscountPct: Decimal)
|
||||
var
|
||||
Telemetry: Codeunit Telemetry;
|
||||
CustomDimensions: Dictionary of [Text, Text];
|
||||
begin
|
||||
CustomDimensions.Add('DocumentNo', DocumentNo);
|
||||
CustomDimensions.Add('TotalQuantity', Format(TotalQty));
|
||||
CustomDimensions.Add('DiscountPercentage', Format(DiscountPct));
|
||||
|
||||
Telemetry.LogMessage('0001', 'Volume discount applied', Verbosity::Normal,
|
||||
DataClassification::SystemMetadata, TelemetryScope::All, CustomDimensions);
|
||||
end;
|
||||
}
|
||||
```
|
||||
|
||||
## Review Points: What to Check
|
||||
|
||||
After AI generates the improved code, verify:
|
||||
|
||||
### ✅ Correctness
|
||||
- [ ] Logic matches business requirements
|
||||
- [ ] All edge cases handled
|
||||
- [ ] No regression in functionality
|
||||
- [ ] Error messages are clear
|
||||
|
||||
### ✅ Performance
|
||||
- [ ] Efficient database queries
|
||||
- [ ] Proper use of SetLoadFields
|
||||
- [ ] ModifyAll used where appropriate
|
||||
- [ ] No unnecessary loops
|
||||
|
||||
### ✅ Best Practices
|
||||
- [ ] XML documentation complete
|
||||
- [ ] Proper error handling
|
||||
- [ ] Appropriate transaction handling
|
||||
- [ ] Good function decomposition
|
||||
|
||||
### ✅ AL Specifics
|
||||
- [ ] Correct AL syntax
|
||||
- [ ] Proper use of BC APIs
|
||||
- [ ] No deprecated features
|
||||
- [ ] Appropriate data types
|
||||
|
||||
### ✅ Maintainability
|
||||
- [ ] Clear, descriptive names
|
||||
- [ ] Logical organization
|
||||
- [ ] Easy to test
|
||||
- [ ] Well documented
|
||||
|
||||
## Additional Review Prompts
|
||||
|
||||
### Security Review
|
||||
```
|
||||
Review this code for potential security issues:
|
||||
- Input validation
|
||||
- Authorization checks
|
||||
- Data access controls
|
||||
- Potential injection vulnerabilities
|
||||
```
|
||||
|
||||
### Testability Review
|
||||
```
|
||||
Analyze this code for testability.
|
||||
What makes it easy or hard to test?
|
||||
How can it be restructured to be more testable?
|
||||
```
|
||||
|
||||
### Documentation Review
|
||||
```
|
||||
Review the XML documentation for this code.
|
||||
Is it complete? Clear? Helpful?
|
||||
What additional documentation would be valuable?
|
||||
```
|
||||
|
||||
## Common Issues AI Might Miss
|
||||
|
||||
Be aware AI might not catch:
|
||||
|
||||
1. **Business Logic Errors**
|
||||
- AI doesn't know your specific discount rules
|
||||
- Verify the logic matches actual requirements
|
||||
|
||||
2. **Integration Issues**
|
||||
- AI doesn't know about other extensions
|
||||
- Check for conflicts with existing code
|
||||
|
||||
3. **BC Version Compatibility**
|
||||
- AI might use features not in your BC version
|
||||
- Verify all APIs are available
|
||||
|
||||
4. **Organization Standards**
|
||||
- AI doesn't know your specific standards
|
||||
- Adapt to match your conventions
|
||||
|
||||
## Best Practices for AI Code Review
|
||||
|
||||
### 1. Use Specific Review Criteria
|
||||
Instead of "review this code", specify what to look for:
|
||||
```
|
||||
Review for: performance, error handling, AL best practices, testability
|
||||
```
|
||||
|
||||
### 2. Review in Layers
|
||||
Don't try to review everything at once:
|
||||
- First: Correctness and logic
|
||||
- Second: Performance
|
||||
- Third: Best practices
|
||||
- Fourth: Documentation
|
||||
|
||||
### 3. Combine with Tools
|
||||
Use AI review alongside:
|
||||
- AL code analyzers
|
||||
- Static analysis tools
|
||||
- Peer review
|
||||
- Testing
|
||||
|
||||
### 4. Iterate
|
||||
Review, improve, review again:
|
||||
```
|
||||
Review the updated code. Are there any remaining issues?
|
||||
```
|
||||
|
||||
### 5. Document Findings
|
||||
Keep track of:
|
||||
- Common issues AI finds
|
||||
- Issues AI misses
|
||||
- Effective review prompts
|
||||
|
||||
## Practice Exercise
|
||||
|
||||
Try reviewing this code with AI:
|
||||
|
||||
```al
|
||||
procedure CalculateShippingCost(SalesHeader: Record "Sales Header"): Decimal
|
||||
var
|
||||
SalesLine: Record "Sales Line";
|
||||
Weight: Decimal;
|
||||
begin
|
||||
Weight := 0;
|
||||
SalesLine.SetRange("Document No.", SalesHeader."No.");
|
||||
if SalesLine.Find('-') then
|
||||
repeat
|
||||
Weight := Weight + SalesLine.Quantity;
|
||||
until SalesLine.Next() = 0;
|
||||
|
||||
if Weight < 10 then
|
||||
exit(5.00)
|
||||
else
|
||||
exit(10.00);
|
||||
end;
|
||||
```
|
||||
|
||||
**Your Tasks**:
|
||||
1. Ask AI to review for issues
|
||||
2. Request performance improvements
|
||||
3. Ask for best practice compliance
|
||||
4. Get an improved version
|
||||
5. Review the improved version yourself
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Learn how to use AI for [generating documentation](../documentation)
|
||||
- See how AI can help with [adding telemetry](../telemetry)
|
||||
- Explore [refactoring legacy code](../refactoring) with AI assistance
|
||||
661
content/docs/agentic-coding/GettingMore/documentation.md
Normal file
661
content/docs/agentic-coding/GettingMore/documentation.md
Normal file
|
|
@ -0,0 +1,661 @@
|
|||
---
|
||||
title: "Generating Documentation"
|
||||
linkTitle: "Documentation"
|
||||
weight: 2
|
||||
description: >
|
||||
Use AI to create and maintain comprehensive documentation for your AL code
|
||||
---
|
||||
|
||||
## Scenario
|
||||
|
||||
You have a well-functioning AL extension but it lacks documentation. You need to create:
|
||||
- XML documentation comments for all procedures
|
||||
- A comprehensive README file
|
||||
- API documentation for public interfaces
|
||||
- User-facing feature documentation
|
||||
|
||||
## Goal
|
||||
|
||||
Use AI to quickly generate high-quality documentation that:
|
||||
- Explains what the code does
|
||||
- Documents parameters and return values
|
||||
- Provides usage examples
|
||||
- Helps developers and users understand the extension
|
||||
|
||||
## Types of Documentation
|
||||
|
||||
### 1. XML Documentation Comments
|
||||
In-code documentation for procedures, triggers, and events.
|
||||
|
||||
### 2. README Files
|
||||
Project overview, setup instructions, and getting started guides.
|
||||
|
||||
### 3. API Documentation
|
||||
Documentation for public APIs and integration points.
|
||||
|
||||
### 4. User Documentation
|
||||
End-user guides and feature explanations.
|
||||
|
||||
## Step-by-Step Guide
|
||||
|
||||
### Step 1: XML Documentation for Procedures
|
||||
|
||||
**Scenario**: You have a codeunit without documentation.
|
||||
|
||||
**Starting Code**:
|
||||
```al
|
||||
codeunit 50100 "Customer Email Manager"
|
||||
{
|
||||
procedure SendWelcomeEmail(CustomerNo: Code[20]): Boolean
|
||||
var
|
||||
Customer: Record Customer;
|
||||
EmailMessage: Codeunit "Email Message";
|
||||
begin
|
||||
if not Customer.Get(CustomerNo) then
|
||||
exit(false);
|
||||
|
||||
if Customer."E-Mail" = '' then
|
||||
exit(false);
|
||||
|
||||
EmailMessage.Create(Customer."E-Mail", 'Welcome!', GetWelcomeEmailBody(Customer));
|
||||
exit(EmailMessage.Send());
|
||||
end;
|
||||
|
||||
local procedure GetWelcomeEmailBody(Customer: Record Customer): Text
|
||||
begin
|
||||
exit(StrSubstNo('Dear %1, welcome to our service!', Customer.Name));
|
||||
end;
|
||||
}
|
||||
```
|
||||
|
||||
**Prompt**:
|
||||
```
|
||||
Add XML documentation comments to all procedures in this codeunit.
|
||||
Include:
|
||||
- Summary for each procedure
|
||||
- Parameter descriptions
|
||||
- Return value descriptions
|
||||
- Example usage where helpful
|
||||
- Any important notes or warnings
|
||||
|
||||
[Paste the code]
|
||||
```
|
||||
|
||||
**Expected Result**:
|
||||
```al
|
||||
/// <summary>
|
||||
/// Manages customer email communications
|
||||
/// </summary>
|
||||
codeunit 50100 "Customer Email Manager"
|
||||
{
|
||||
/// <summary>
|
||||
/// Sends a welcome email to a customer
|
||||
/// </summary>
|
||||
/// <param name="CustomerNo">The customer number to send the email to</param>
|
||||
/// <returns>True if the email was sent successfully, false otherwise</returns>
|
||||
/// <remarks>
|
||||
/// The procedure will return false if:
|
||||
/// - The customer does not exist
|
||||
/// - The customer has no email address
|
||||
/// - The email fails to send
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// if CustomerEmailManager.SendWelcomeEmail('C001') then
|
||||
/// Message('Welcome email sent successfully');
|
||||
/// </example>
|
||||
procedure SendWelcomeEmail(CustomerNo: Code[20]): Boolean
|
||||
var
|
||||
Customer: Record Customer;
|
||||
EmailMessage: Codeunit "Email Message";
|
||||
begin
|
||||
if not Customer.Get(CustomerNo) then
|
||||
exit(false);
|
||||
|
||||
if Customer."E-Mail" = '' then
|
||||
exit(false);
|
||||
|
||||
EmailMessage.Create(Customer."E-Mail", 'Welcome!', GetWelcomeEmailBody(Customer));
|
||||
exit(EmailMessage.Send());
|
||||
end;
|
||||
|
||||
/// <summary>
|
||||
/// Generates the body text for the welcome email
|
||||
/// </summary>
|
||||
/// <param name="Customer">The customer record to generate the email for</param>
|
||||
/// <returns>The formatted email body text</returns>
|
||||
local procedure GetWelcomeEmailBody(Customer: Record Customer): Text
|
||||
begin
|
||||
exit(StrSubstNo('Dear %1, welcome to our service!', Customer.Name));
|
||||
end;
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Batch Documentation for Multiple Files
|
||||
|
||||
**Prompt**:
|
||||
```
|
||||
I have multiple AL files that need XML documentation.
|
||||
For each public procedure, add:
|
||||
- Summary
|
||||
- Parameter descriptions
|
||||
- Return value (if applicable)
|
||||
- Example usage
|
||||
|
||||
Process these files:
|
||||
[List files or paste multiple code blocks]
|
||||
```
|
||||
|
||||
**Tip**: Do this in batches of 3-5 files to maintain context and quality.
|
||||
|
||||
### Step 3: Creating a README File
|
||||
|
||||
**Prompt**:
|
||||
```
|
||||
Create a comprehensive README.md for my AL extension with the following information:
|
||||
|
||||
Extension Name: Customer Engagement Suite
|
||||
Purpose: Enhance customer communication and engagement features
|
||||
Key Features:
|
||||
- Automated welcome emails
|
||||
- Customer portal access
|
||||
- Newsletter subscription management
|
||||
- Customer satisfaction surveys
|
||||
|
||||
Include sections for:
|
||||
- Overview and purpose
|
||||
- Features list
|
||||
- Installation instructions
|
||||
- Configuration steps
|
||||
- Usage examples
|
||||
- Dependencies
|
||||
- Compatibility (BC version 21+)
|
||||
- Contributing guidelines
|
||||
- License (MIT)
|
||||
```
|
||||
|
||||
**Expected Structure**:
|
||||
```markdown
|
||||
# Customer Engagement Suite
|
||||
|
||||
> Enhance customer communication and engagement in Microsoft Dynamics 365 Business Central
|
||||
|
||||
## Overview
|
||||
|
||||
The Customer Engagement Suite is a Business Central extension that provides comprehensive
|
||||
tools for managing customer communications and engagement activities.
|
||||
|
||||
## Features
|
||||
|
||||
### Automated Welcome Emails
|
||||
Automatically send personalized welcome emails to new customers upon registration.
|
||||
|
||||
### Customer Portal Access
|
||||
Enable customers to access a self-service portal for order tracking and account management.
|
||||
|
||||
### Newsletter Subscription Management
|
||||
Allow customers to manage their newsletter preferences and subscriptions.
|
||||
|
||||
### Customer Satisfaction Surveys
|
||||
Collect and analyze customer feedback through integrated surveys.
|
||||
|
||||
## Installation
|
||||
|
||||
### Prerequisites
|
||||
- Microsoft Dynamics 365 Business Central version 21.0 or higher
|
||||
- Email account configured in Business Central
|
||||
- System Application extension installed
|
||||
|
||||
### Steps
|
||||
|
||||
1. Download the extension file (.app)
|
||||
2. Open Business Central Administration Shell
|
||||
3. Run the installation command:
|
||||
```powershell
|
||||
Publish-NAVApp -ServerInstance BC210 -Path "CustomerEngagementSuite.app"
|
||||
Install-NAVApp -ServerInstance BC210 -Name "Customer Engagement Suite"
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Email Setup
|
||||
1. Navigate to **Email Accounts** page
|
||||
2. Configure your SMTP settings
|
||||
3. Test the email connection
|
||||
|
||||
### Welcome Email Template
|
||||
1. Go to **Customer Engagement Setup**
|
||||
2. Configure the welcome email template
|
||||
3. Enable automatic sending
|
||||
|
||||
## Usage
|
||||
|
||||
### Sending Welcome Emails
|
||||
```al
|
||||
codeunit 50100 "Customer Email Manager"
|
||||
{
|
||||
procedure SendWelcomeEmail(CustomerNo: Code[20]): Boolean
|
||||
}
|
||||
```
|
||||
|
||||
**Example:**
|
||||
```al
|
||||
CustomerEmailManager: Codeunit "Customer Email Manager";
|
||||
begin
|
||||
if CustomerEmailManager.SendWelcomeEmail('C001') then
|
||||
Message('Welcome email sent successfully');
|
||||
end;
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **System Application**: Core BC functionality
|
||||
- **Base Application**: Standard BC tables and pages
|
||||
|
||||
## Compatibility
|
||||
|
||||
- **Business Central**: Version 21.0 and higher
|
||||
- **Platform**: Compatible with both SaaS and On-Premises
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see [LICENSE](LICENSE) file for details.
|
||||
|
||||
## Support
|
||||
|
||||
For issues and questions:
|
||||
- Create an issue in our [GitHub repository](https://github.com/yourorg/customer-engagement)
|
||||
- Email: support@yourcompany.com
|
||||
|
||||
## Changelog
|
||||
|
||||
See [CHANGELOG.md](CHANGELOG.md) for version history.
|
||||
```
|
||||
|
||||
### Step 4: API Documentation
|
||||
|
||||
**Prompt**:
|
||||
```
|
||||
Create API documentation for the public procedures in my Customer Email Manager codeunit.
|
||||
Format it as markdown suitable for a developer portal.
|
||||
|
||||
Include:
|
||||
- Procedure signature
|
||||
- Description
|
||||
- Parameters with types and descriptions
|
||||
- Return values
|
||||
- Code examples
|
||||
- Error conditions
|
||||
- Notes and best practices
|
||||
|
||||
[Include the codeunit code]
|
||||
```
|
||||
|
||||
**Expected Output**:
|
||||
```markdown
|
||||
# Customer Email Manager API
|
||||
|
||||
## Overview
|
||||
The Customer Email Manager provides functionality for sending customer-related emails.
|
||||
|
||||
## Procedures
|
||||
|
||||
### SendWelcomeEmail
|
||||
|
||||
Sends a welcome email to a specified customer.
|
||||
|
||||
**Signature:**
|
||||
```al
|
||||
procedure SendWelcomeEmail(CustomerNo: Code[20]): Boolean
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Name | Type | Description |
|
||||
|------|------|-------------|
|
||||
| CustomerNo | Code[20] | The unique identifier of the customer |
|
||||
|
||||
**Returns:**
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| Boolean | `true` if email sent successfully, `false` otherwise |
|
||||
|
||||
**Example:**
|
||||
```al
|
||||
var
|
||||
CustomerEmailManager: Codeunit "Customer Email Manager";
|
||||
Success: Boolean;
|
||||
begin
|
||||
Success := CustomerEmailManager.SendWelcomeEmail('C00001');
|
||||
if Success then
|
||||
Message('Email sent successfully')
|
||||
else
|
||||
Error('Failed to send email');
|
||||
end;
|
||||
```
|
||||
|
||||
**Error Conditions:**
|
||||
|
||||
The procedure returns `false` in the following cases:
|
||||
- Customer does not exist
|
||||
- Customer has no email address configured
|
||||
- Email service is unavailable
|
||||
|
||||
**Best Practices:**
|
||||
- Always check the return value
|
||||
- Ensure email account is configured before calling
|
||||
- Consider calling asynchronously for better performance
|
||||
|
||||
**See Also:**
|
||||
- Email Account Setup
|
||||
- Customer Configuration
|
||||
```
|
||||
|
||||
### Step 5: User Documentation
|
||||
|
||||
**Prompt**:
|
||||
```
|
||||
Create user-facing documentation for the Welcome Email feature.
|
||||
Target audience: Business users who are not developers.
|
||||
|
||||
Explain:
|
||||
- What the feature does
|
||||
- How to enable/configure it
|
||||
- How to use it
|
||||
- Troubleshooting common issues
|
||||
|
||||
Use simple language and include screenshots placeholders where helpful.
|
||||
```
|
||||
|
||||
**Expected Output**:
|
||||
```markdown
|
||||
# Welcome Email Feature
|
||||
|
||||
## What is the Welcome Email Feature?
|
||||
|
||||
The Welcome Email feature automatically sends a personalized email to new customers
|
||||
when they are added to your system. This helps create a positive first impression
|
||||
and provides important information to your customers right away.
|
||||
|
||||
## Enabling Welcome Emails
|
||||
|
||||
1. Open Business Central
|
||||
2. Search for "Customer Engagement Setup"
|
||||
3. Check the box next to "Send Welcome Emails"
|
||||
4. Click "OK" to save
|
||||
|
||||
![Setup Screenshot]
|
||||
|
||||
## Configuring the Email Template
|
||||
|
||||
### Step 1: Access Email Templates
|
||||
1. Search for "Email Templates"
|
||||
2. Find "Customer Welcome Email"
|
||||
3. Click "Edit"
|
||||
|
||||
### Step 2: Customize the Message
|
||||
1. Update the email subject line
|
||||
2. Modify the email body
|
||||
3. Add your company logo
|
||||
4. Click "OK" to save
|
||||
|
||||
### Step 3: Test the Email
|
||||
1. Open a test customer
|
||||
2. Click "Actions" > "Send Welcome Email"
|
||||
3. Check that you receive the email
|
||||
|
||||
## Using Welcome Emails
|
||||
|
||||
### Automatic Sending
|
||||
When enabled, welcome emails are sent automatically when:
|
||||
- A new customer is created
|
||||
- An existing customer is marked as active for the first time
|
||||
|
||||
### Manual Sending
|
||||
You can also send welcome emails manually:
|
||||
|
||||
1. Open the customer card
|
||||
2. Click "Actions" > "Email" > "Send Welcome Email"
|
||||
3. Confirm the action
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Email Not Sending
|
||||
|
||||
**Problem:** Welcome emails are not being sent
|
||||
|
||||
**Solutions:**
|
||||
- Check that the feature is enabled in setup
|
||||
- Verify the customer has an email address
|
||||
- Confirm your email account is configured correctly
|
||||
- Check that email service is running
|
||||
|
||||
### Wrong Email Template
|
||||
|
||||
**Problem:** Customers receive the wrong email content
|
||||
|
||||
**Solutions:**
|
||||
- Review the email template configuration
|
||||
- Ensure you're editing the correct template
|
||||
- Clear any cached templates
|
||||
|
||||
### Customer Not Receiving Email
|
||||
|
||||
**Problem:** Email shows as sent but customer didn't receive it
|
||||
|
||||
**Solutions:**
|
||||
- Ask customer to check spam folder
|
||||
- Verify the email address is correct
|
||||
- Check email delivery logs
|
||||
- Test with a different email address
|
||||
|
||||
## Tips and Best Practices
|
||||
|
||||
- **Personalization**: Use customer name and company name in the email
|
||||
- **Timing**: Send welcome emails within 24 hours of customer creation
|
||||
- **Follow-up**: Consider a follow-up email series
|
||||
- **Feedback**: Ask customers if they received and found the email helpful
|
||||
|
||||
## Need Help?
|
||||
|
||||
Contact your Business Central administrator or support team for assistance.
|
||||
```
|
||||
|
||||
## Review Points
|
||||
|
||||
After generating documentation, verify:
|
||||
|
||||
### ✅ Accuracy
|
||||
- [ ] Documentation matches actual code behavior
|
||||
- [ ] Parameter descriptions are correct
|
||||
- [ ] Return values accurately described
|
||||
- [ ] Examples work as shown
|
||||
|
||||
### ✅ Completeness
|
||||
- [ ] All public procedures documented
|
||||
- [ ] All parameters explained
|
||||
- [ ] Edge cases mentioned
|
||||
- [ ] Error conditions documented
|
||||
|
||||
### ✅ Clarity
|
||||
- [ ] Easy to understand
|
||||
- [ ] Appropriate level of detail
|
||||
- [ ] Good examples provided
|
||||
- [ ] Proper formatting
|
||||
|
||||
### ✅ Maintenance
|
||||
- [ ] Easy to update
|
||||
- [ ] Versioned appropriately
|
||||
- [ ] Consistent style
|
||||
- [ ] Well organized
|
||||
|
||||
## Advanced Documentation Prompts
|
||||
|
||||
### Generate CHANGELOG
|
||||
```
|
||||
Create a CHANGELOG.md file documenting these changes from version 1.0 to 2.0:
|
||||
- Added: Customer portal access
|
||||
- Added: Newsletter preferences
|
||||
- Changed: Welcome email now includes portal link
|
||||
- Fixed: Email encoding issues with special characters
|
||||
- Deprecated: Old email API (will be removed in 3.0)
|
||||
|
||||
Follow Keep a Changelog format.
|
||||
```
|
||||
|
||||
### Create Migration Guide
|
||||
```
|
||||
Create a migration guide for users upgrading from version 1.x to 2.0.
|
||||
Include:
|
||||
- Breaking changes
|
||||
- New features
|
||||
- Configuration changes needed
|
||||
- Data migration steps
|
||||
- Deprecation warnings
|
||||
```
|
||||
|
||||
### Generate Inline Code Comments
|
||||
```
|
||||
Add helpful inline comments to this complex procedure explaining the logic flow.
|
||||
Don't over-comment obvious code, but do explain:
|
||||
- Complex algorithms
|
||||
- Business rule implementations
|
||||
- Non-obvious optimizations
|
||||
- Workarounds
|
||||
|
||||
[Paste code]
|
||||
```
|
||||
|
||||
## Keeping Documentation Updated
|
||||
|
||||
### When Code Changes
|
||||
**Prompt**:
|
||||
```
|
||||
I've updated this procedure to add a new parameter.
|
||||
Update the XML documentation to reflect the change:
|
||||
|
||||
Old procedure:
|
||||
[paste old code]
|
||||
|
||||
New procedure:
|
||||
[paste new code]
|
||||
```
|
||||
|
||||
### Regular Documentation Reviews
|
||||
**Prompt**:
|
||||
```
|
||||
Review the documentation for this codeunit.
|
||||
Check for:
|
||||
- Outdated information
|
||||
- Missing documentation
|
||||
- Incorrect examples
|
||||
- Deprecated features
|
||||
|
||||
[Paste codeunit]
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Document As You Code
|
||||
```
|
||||
I'm about to write a procedure to validate customer credit limits.
|
||||
Create the XML documentation comment first, then we'll implement the procedure.
|
||||
```
|
||||
|
||||
### 2. Use Consistent Style
|
||||
Create a documentation template:
|
||||
```
|
||||
Create an XML documentation template I can use for all my procedures.
|
||||
Include sections for: summary, parameters, returns, exceptions, examples, and remarks.
|
||||
```
|
||||
|
||||
### 3. Generate Documentation in Batches
|
||||
Document related code together for consistency:
|
||||
```
|
||||
Document all procedures in this codeunit that relate to email sending.
|
||||
Use consistent terminology and structure.
|
||||
```
|
||||
|
||||
### 4. Include Real Examples
|
||||
```
|
||||
Add a realistic code example to this procedure's documentation showing:
|
||||
- Typical usage
|
||||
- Error handling
|
||||
- Integration with other features
|
||||
```
|
||||
|
||||
## Common Documentation Patterns
|
||||
|
||||
### For Validation Procedures
|
||||
```
|
||||
Document this validation procedure. Include:
|
||||
- What is being validated
|
||||
- Valid conditions
|
||||
- Error messages that can be raised
|
||||
- Example of valid and invalid inputs
|
||||
```
|
||||
|
||||
### For Event Subscribers
|
||||
```
|
||||
Document this event subscriber. Include:
|
||||
- What event it subscribes to
|
||||
- When it triggers
|
||||
- What it does
|
||||
- Side effects or implications
|
||||
- Integration points
|
||||
```
|
||||
|
||||
### For APIs
|
||||
```
|
||||
Create REST API documentation for this AL API page.
|
||||
Include:
|
||||
- Endpoint URL
|
||||
- HTTP methods supported
|
||||
- Request/response examples
|
||||
- Authentication requirements
|
||||
- Error codes
|
||||
```
|
||||
|
||||
## Practice Exercise
|
||||
|
||||
Generate documentation for this code:
|
||||
|
||||
```al
|
||||
codeunit 50110 "Order Status Manager"
|
||||
{
|
||||
procedure UpdateOrderStatus(OrderNo: Code[20]; NewStatus: Enum "Order Status"): Boolean
|
||||
var
|
||||
SalesHeader: Record "Sales Header";
|
||||
begin
|
||||
if not SalesHeader.Get(SalesHeader."Document Type"::Order, OrderNo) then
|
||||
exit(false);
|
||||
|
||||
SalesHeader.Status := NewStatus;
|
||||
SalesHeader.Modify(true);
|
||||
SendStatusNotification(OrderNo, NewStatus);
|
||||
exit(true);
|
||||
end;
|
||||
|
||||
local procedure SendStatusNotification(OrderNo: Code[20]; Status: Enum "Order Status")
|
||||
begin
|
||||
// Implementation
|
||||
end;
|
||||
}
|
||||
```
|
||||
|
||||
**Your Tasks**:
|
||||
1. Generate XML documentation
|
||||
2. Create a README section explaining this feature
|
||||
3. Write API documentation
|
||||
4. Create user documentation
|
||||
5. Review and improve the generated docs
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Learn how to use AI for [adding telemetry](../telemetry)
|
||||
- Explore [refactoring legacy code](../refactoring) while maintaining documentation
|
||||
- See how to conduct [AI-assisted code reviews](../code-review)
|
||||
599
content/docs/agentic-coding/GettingMore/telemetry.md
Normal file
599
content/docs/agentic-coding/GettingMore/telemetry.md
Normal file
|
|
@ -0,0 +1,599 @@
|
|||
---
|
||||
title: "Adding Telemetry"
|
||||
linkTitle: "Telemetry"
|
||||
weight: 3
|
||||
description: >
|
||||
Use AI to instrument your AL extensions with Application Insights telemetry
|
||||
---
|
||||
|
||||
## Scenario
|
||||
|
||||
You have a Business Central extension that's running in production, but you have limited visibility into:
|
||||
- How often features are used
|
||||
- Where errors occur
|
||||
- Performance bottlenecks
|
||||
- User behavior patterns
|
||||
|
||||
You want to add comprehensive telemetry using Application Insights to monitor your extension in production.
|
||||
|
||||
## Goal
|
||||
|
||||
Use AI to help you:
|
||||
- Add telemetry calls throughout your code
|
||||
- Log important events and errors
|
||||
- Track feature usage
|
||||
- Monitor performance
|
||||
- Create custom dimensions for better analysis
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Understanding of:
|
||||
- Business Central telemetry concepts
|
||||
- Application Insights basics
|
||||
- When to log (without over-logging)
|
||||
- Data classification requirements
|
||||
|
||||
## Step-by-Step Guide
|
||||
|
||||
### Step 1: Understanding Your Code
|
||||
|
||||
**Starting Code** (without telemetry):
|
||||
```al
|
||||
codeunit 50100 "Sales Order Processor"
|
||||
{
|
||||
procedure ProcessOrder(var SalesHeader: Record "Sales Header"): Boolean
|
||||
var
|
||||
SalesLine: Record "Sales Line";
|
||||
begin
|
||||
if not ValidateOrder(SalesHeader) then
|
||||
exit(false);
|
||||
|
||||
CalculateTotals(SalesHeader);
|
||||
ApplyDiscounts(SalesHeader);
|
||||
|
||||
if not PostOrder(SalesHeader) then
|
||||
exit(false);
|
||||
|
||||
exit(true);
|
||||
end;
|
||||
|
||||
local procedure ValidateOrder(var SalesHeader: Record "Sales Header"): Boolean
|
||||
var
|
||||
Customer: Record Customer;
|
||||
begin
|
||||
if not Customer.Get(SalesHeader."Sell-to Customer No.") then begin
|
||||
Error('Customer %1 does not exist.', SalesHeader."Sell-to Customer No.");
|
||||
end;
|
||||
|
||||
if Customer.Blocked <> Customer.Blocked::" " then begin
|
||||
Error('Customer %1 is blocked.', Customer."No.");
|
||||
end;
|
||||
|
||||
exit(true);
|
||||
end;
|
||||
|
||||
local procedure PostOrder(var SalesHeader: Record "Sales Header"): Boolean
|
||||
var
|
||||
SalesPost: Codeunit "Sales-Post";
|
||||
begin
|
||||
exit(SalesPost.Run(SalesHeader));
|
||||
end;
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Request Telemetry Strategy
|
||||
|
||||
**Prompt**:
|
||||
```
|
||||
I want to add Application Insights telemetry to this Business Central codeunit.
|
||||
|
||||
Help me plan what to log:
|
||||
- What events should be logged?
|
||||
- What custom dimensions should be included?
|
||||
- What data classification is appropriate?
|
||||
- Where should we log errors vs. information?
|
||||
|
||||
[Paste the code above]
|
||||
```
|
||||
|
||||
**Expected AI Response**:
|
||||
The AI should suggest logging:
|
||||
|
||||
**Events to Log:**
|
||||
1. Order processing started (Informational)
|
||||
2. Order validation completed (Informational)
|
||||
3. Order posted successfully (Informational)
|
||||
4. Validation failures (Warning)
|
||||
5. Posting failures (Error)
|
||||
6. Performance metrics (Informational)
|
||||
|
||||
**Custom Dimensions:**
|
||||
- Order Number
|
||||
- Customer Number
|
||||
- Order Amount
|
||||
- Number of Lines
|
||||
- Processing Duration
|
||||
- Error Details (when applicable)
|
||||
|
||||
**Data Classification:**
|
||||
- Order Number: CustomerContent
|
||||
- Customer Number: CustomerContent
|
||||
- Amounts: CustomerContent
|
||||
- Counts: SystemMetadata
|
||||
- Duration: SystemMetadata
|
||||
|
||||
### Step 3: Add Telemetry to Main Procedure
|
||||
|
||||
**Prompt**:
|
||||
```
|
||||
Add Application Insights telemetry to the ProcessOrder procedure.
|
||||
|
||||
Include:
|
||||
- Start event when processing begins
|
||||
- Success event when complete
|
||||
- Error event on failure
|
||||
- Custom dimensions: OrderNo, CustomerNo, TotalAmount, LineCount, Duration
|
||||
- Use appropriate data classification
|
||||
- Use the Telemetry codeunit from System Application
|
||||
|
||||
Update this code:
|
||||
[Paste ProcessOrder procedure]
|
||||
```
|
||||
|
||||
**Expected Result**:
|
||||
```al
|
||||
procedure ProcessOrder(var SalesHeader: Record "Sales Header"): Boolean
|
||||
var
|
||||
Telemetry: Codeunit Telemetry;
|
||||
CustomDimensions: Dictionary of [Text, Text];
|
||||
StartTime: DateTime;
|
||||
Duration: Duration;
|
||||
LineCount: Integer;
|
||||
begin
|
||||
StartTime := CurrentDateTime;
|
||||
|
||||
// Log start of processing
|
||||
AddOrderDimensions(CustomDimensions, SalesHeader);
|
||||
Telemetry.LogMessage('SALESORD-001', 'Order processing started',
|
||||
Verbosity::Normal, DataClassification::SystemMetadata,
|
||||
TelemetryScope::ExtensionPublisher, CustomDimensions);
|
||||
|
||||
if not ValidateOrder(SalesHeader) then begin
|
||||
LogProcessingError(SalesHeader, 'Validation failed');
|
||||
exit(false);
|
||||
end;
|
||||
|
||||
CalculateTotals(SalesHeader);
|
||||
ApplyDiscounts(SalesHeader);
|
||||
|
||||
if not PostOrder(SalesHeader) then begin
|
||||
LogProcessingError(SalesHeader, 'Posting failed');
|
||||
exit(false);
|
||||
end;
|
||||
|
||||
// Log successful completion
|
||||
Duration := CurrentDateTime - StartTime;
|
||||
Clear(CustomDimensions);
|
||||
AddOrderDimensions(CustomDimensions, SalesHeader);
|
||||
CustomDimensions.Add('Duration', Format(Duration));
|
||||
CustomDimensions.Add('LineCount', Format(CountLines(SalesHeader)));
|
||||
|
||||
Telemetry.LogMessage('SALESORD-002', 'Order processing completed successfully',
|
||||
Verbosity::Normal, DataClassification::SystemMetadata,
|
||||
TelemetryScope::ExtensionPublisher, CustomDimensions);
|
||||
|
||||
exit(true);
|
||||
end;
|
||||
|
||||
local procedure AddOrderDimensions(var CustomDimensions: Dictionary of [Text, Text]; SalesHeader: Record "Sales Header")
|
||||
begin
|
||||
CustomDimensions.Add('OrderNo', SalesHeader."No.");
|
||||
CustomDimensions.Add('CustomerNo', SalesHeader."Sell-to Customer No.");
|
||||
CustomDimensions.Add('OrderType', Format(SalesHeader."Document Type"));
|
||||
end;
|
||||
|
||||
local procedure LogProcessingError(SalesHeader: Record "Sales Header"; ErrorMessage: Text)
|
||||
var
|
||||
Telemetry: Codeunit Telemetry;
|
||||
CustomDimensions: Dictionary of [Text, Text];
|
||||
begin
|
||||
AddOrderDimensions(CustomDimensions, SalesHeader);
|
||||
CustomDimensions.Add('ErrorMessage', ErrorMessage);
|
||||
CustomDimensions.Add('ErrorCallStack', GetLastErrorCallStack());
|
||||
|
||||
Telemetry.LogMessage('SALESORD-E001', StrSubstNo('Order processing failed: %1', ErrorMessage),
|
||||
Verbosity::Error, DataClassification::SystemMetadata,
|
||||
TelemetryScope::ExtensionPublisher, CustomDimensions);
|
||||
end;
|
||||
|
||||
local procedure CountLines(SalesHeader: Record "Sales Header"): Integer
|
||||
var
|
||||
SalesLine: Record "Sales Line";
|
||||
begin
|
||||
SalesLine.SetRange("Document Type", SalesHeader."Document Type");
|
||||
SalesLine.SetRange("Document No.", SalesHeader."No.");
|
||||
exit(SalesLine.Count);
|
||||
end;
|
||||
```
|
||||
|
||||
### Step 4: Add Error Tracking
|
||||
|
||||
**Prompt**:
|
||||
```
|
||||
Update the ValidateOrder procedure to log validation failures with telemetry.
|
||||
|
||||
For each validation check that fails:
|
||||
- Log a warning message
|
||||
- Include relevant details (customer number, block reason, etc.)
|
||||
- Use appropriate custom dimensions
|
||||
- Use proper data classification
|
||||
|
||||
[Paste ValidateOrder procedure]
|
||||
```
|
||||
|
||||
**Expected Result**:
|
||||
```al
|
||||
local procedure ValidateOrder(var SalesHeader: Record "Sales Header"): Boolean
|
||||
var
|
||||
Customer: Record Customer;
|
||||
Telemetry: Codeunit Telemetry;
|
||||
CustomDimensions: Dictionary of [Text, Text];
|
||||
begin
|
||||
if not Customer.Get(SalesHeader."Sell-to Customer No.") then begin
|
||||
AddValidationDimensions(CustomDimensions, SalesHeader, 'Customer not found');
|
||||
Telemetry.LogMessage('SALESORD-W001', 'Order validation failed: Customer not found',
|
||||
Verbosity::Warning, DataClassification::SystemMetadata,
|
||||
TelemetryScope::ExtensionPublisher, CustomDimensions);
|
||||
|
||||
Error('Customer %1 does not exist.', SalesHeader."Sell-to Customer No.");
|
||||
end;
|
||||
|
||||
if Customer.Blocked <> Customer.Blocked::" " then begin
|
||||
Clear(CustomDimensions);
|
||||
AddValidationDimensions(CustomDimensions, SalesHeader, 'Customer blocked');
|
||||
CustomDimensions.Add('BlockedReason', Format(Customer.Blocked));
|
||||
|
||||
Telemetry.LogMessage('SALESORD-W002', 'Order validation failed: Customer blocked',
|
||||
Verbosity::Warning, DataClassification::SystemMetadata,
|
||||
TelemetryScope::ExtensionPublisher, CustomDimensions);
|
||||
|
||||
Error('Customer %1 is blocked.', Customer."No.");
|
||||
end;
|
||||
|
||||
exit(true);
|
||||
end;
|
||||
|
||||
local procedure AddValidationDimensions(var CustomDimensions: Dictionary of [Text, Text]; SalesHeader: Record "Sales Header"; Reason: Text)
|
||||
begin
|
||||
CustomDimensions.Add('OrderNo', SalesHeader."No.");
|
||||
CustomDimensions.Add('CustomerNo', SalesHeader."Sell-to Customer No.");
|
||||
CustomDimensions.Add('ValidationFailure', Reason);
|
||||
end;
|
||||
```
|
||||
|
||||
### Step 5: Add Performance Tracking
|
||||
|
||||
**Prompt**:
|
||||
```
|
||||
Add performance telemetry to track how long different operations take.
|
||||
|
||||
Track duration for:
|
||||
- Overall processing
|
||||
- Validation
|
||||
- Calculation
|
||||
- Posting
|
||||
|
||||
Log if any operation takes longer than expected thresholds:
|
||||
- Validation > 1 second
|
||||
- Calculation > 2 seconds
|
||||
- Posting > 5 seconds
|
||||
|
||||
[Include the codeunit]
|
||||
```
|
||||
|
||||
**Expected Result**:
|
||||
```al
|
||||
local procedure ValidateOrder(var SalesHeader: Record "Sales Header"): Boolean
|
||||
var
|
||||
Customer: Record Customer;
|
||||
Telemetry: Codeunit Telemetry;
|
||||
CustomDimensions: Dictionary of [Text, Text];
|
||||
StartTime: DateTime;
|
||||
Duration: Duration;
|
||||
begin
|
||||
StartTime := CurrentDateTime;
|
||||
|
||||
// Validation logic here...
|
||||
|
||||
Duration := CurrentDateTime - StartTime;
|
||||
|
||||
// Log if validation took too long
|
||||
if Duration > 1000 then begin
|
||||
AddOrderDimensions(CustomDimensions, SalesHeader);
|
||||
CustomDimensions.Add('Duration', Format(Duration));
|
||||
CustomDimensions.Add('Operation', 'Validation');
|
||||
|
||||
Telemetry.LogMessage('SALESORD-P001', 'Slow validation detected',
|
||||
Verbosity::Warning, DataClassification::SystemMetadata,
|
||||
TelemetryScope::ExtensionPublisher, CustomDimensions);
|
||||
end;
|
||||
|
||||
exit(true);
|
||||
end;
|
||||
```
|
||||
|
||||
### Step 6: Add Feature Usage Tracking
|
||||
|
||||
**Prompt**:
|
||||
```
|
||||
Add telemetry to track feature usage for business analytics.
|
||||
|
||||
Track:
|
||||
- How often each discount type is applied
|
||||
- Which payment methods are used
|
||||
- Popular shipping methods
|
||||
- Order size distribution
|
||||
|
||||
Create a FeatureUsageTelemetry codeunit for this purpose.
|
||||
```
|
||||
|
||||
**Expected Result**:
|
||||
```al
|
||||
codeunit 50101 "Feature Usage Telemetry"
|
||||
{
|
||||
var
|
||||
Telemetry: Codeunit Telemetry;
|
||||
|
||||
procedure LogDiscountApplied(DiscountType: Text; DiscountPct: Decimal; OrderNo: Code[20])
|
||||
var
|
||||
CustomDimensions: Dictionary of [Text, Text];
|
||||
begin
|
||||
CustomDimensions.Add('FeatureArea', 'Discounts');
|
||||
CustomDimensions.Add('DiscountType', DiscountType);
|
||||
CustomDimensions.Add('DiscountPercentage', Format(DiscountPct));
|
||||
CustomDimensions.Add('OrderNo', OrderNo);
|
||||
|
||||
Telemetry.LogMessage('FEATURE-DISCOUNT', 'Discount applied',
|
||||
Verbosity::Normal, DataClassification::SystemMetadata,
|
||||
TelemetryScope::ExtensionPublisher, CustomDimensions);
|
||||
end;
|
||||
|
||||
procedure LogPaymentMethod(PaymentMethodCode: Code[10]; Amount: Decimal)
|
||||
var
|
||||
CustomDimensions: Dictionary of [Text, Text];
|
||||
begin
|
||||
CustomDimensions.Add('FeatureArea', 'Payment');
|
||||
CustomDimensions.Add('PaymentMethod', PaymentMethodCode);
|
||||
CustomDimensions.Add('AmountRange', GetAmountRange(Amount));
|
||||
|
||||
Telemetry.LogMessage('FEATURE-PAYMENT', 'Payment method used',
|
||||
Verbosity::Normal, DataClassification::SystemMetadata,
|
||||
TelemetryScope::ExtensionPublisher, CustomDimensions);
|
||||
end;
|
||||
|
||||
local procedure GetAmountRange(Amount: Decimal): Text
|
||||
begin
|
||||
case true of
|
||||
Amount < 100:
|
||||
exit('0-100');
|
||||
Amount < 1000:
|
||||
exit('100-1000');
|
||||
Amount < 10000:
|
||||
exit('1000-10000');
|
||||
else
|
||||
exit('10000+');
|
||||
end;
|
||||
end;
|
||||
}
|
||||
```
|
||||
|
||||
## Telemetry Best Practices
|
||||
|
||||
### ✅ Do's
|
||||
|
||||
**Use Consistent Event IDs**
|
||||
```
|
||||
SALESORD-001: Processing started
|
||||
SALESORD-002: Processing completed
|
||||
SALESORD-E001: Processing error
|
||||
SALESORD-W001: Validation warning
|
||||
SALESORD-P001: Performance warning
|
||||
```
|
||||
|
||||
**Use Meaningful Messages**
|
||||
```al
|
||||
// Good
|
||||
Telemetry.LogMessage('SALESORD-001', 'Sales order processing started for large order', ...);
|
||||
|
||||
// Bad
|
||||
Telemetry.LogMessage('001', 'Started', ...);
|
||||
```
|
||||
|
||||
**Include Helpful Custom Dimensions**
|
||||
```al
|
||||
CustomDimensions.Add('OrderNo', OrderNo);
|
||||
CustomDimensions.Add('CustomerNo', CustomerNo);
|
||||
CustomDimensions.Add('LineCount', Format(LineCount));
|
||||
CustomDimensions.Add('TotalAmount', Format(TotalAmount));
|
||||
CustomDimensions.Add('ProcessingDuration', Format(Duration));
|
||||
```
|
||||
|
||||
**Use Appropriate Data Classification**
|
||||
```al
|
||||
// Customer data
|
||||
DataClassification::CustomerContent
|
||||
|
||||
// System metrics
|
||||
DataClassification::SystemMetadata
|
||||
|
||||
// Organizational data
|
||||
DataClassification::OrganizationIdentifiableInformation
|
||||
```
|
||||
|
||||
### ❌ Don'ts
|
||||
|
||||
**Don't Over-Log**
|
||||
```al
|
||||
// Too much logging
|
||||
Telemetry.LogMessage('001', 'Line 1', ...);
|
||||
Telemetry.LogMessage('002', 'Line 2', ...);
|
||||
// Don't log every single step
|
||||
```
|
||||
|
||||
**Don't Log Sensitive Data**
|
||||
```al
|
||||
// Bad - logging password
|
||||
CustomDimensions.Add('Password', Password);
|
||||
|
||||
// Bad - logging full credit card
|
||||
CustomDimensions.Add('CreditCard', CreditCardNo);
|
||||
|
||||
// Bad - logging personal data unnecessarily
|
||||
CustomDimensions.Add('SSN', SSN);
|
||||
```
|
||||
|
||||
**Don't Use Generic Messages**
|
||||
```al
|
||||
// Bad
|
||||
Telemetry.LogMessage('001', 'Error', ...);
|
||||
|
||||
// Good
|
||||
Telemetry.LogMessage('SALESORD-E001', 'Failed to post sales order due to credit limit exceeded', ...);
|
||||
```
|
||||
|
||||
## Advanced Telemetry Patterns
|
||||
|
||||
### Pattern 1: Telemetry Wrapper
|
||||
**Prompt**:
|
||||
```
|
||||
Create a telemetry wrapper codeunit that simplifies logging for my extension.
|
||||
Include helper methods for:
|
||||
- LogStart/LogEnd (with automatic duration calculation)
|
||||
- LogError (with automatic call stack)
|
||||
- LogFeatureUsage
|
||||
- LogPerformanceWarning
|
||||
|
||||
Prefix all event IDs with my extension prefix "SALESEXT-"
|
||||
```
|
||||
|
||||
### Pattern 2: Telemetry Constants
|
||||
**Prompt**:
|
||||
```
|
||||
Create a codeunit with constants for all my telemetry event IDs and messages.
|
||||
This will ensure consistency and make it easier to update messages.
|
||||
```
|
||||
|
||||
### Pattern 3: Contextual Telemetry
|
||||
**Prompt**:
|
||||
```
|
||||
Create a system that automatically adds common context to all telemetry:
|
||||
- User ID (anonymized)
|
||||
- Company name (anonymized)
|
||||
- Extension version
|
||||
- BC version
|
||||
- Environment type (Production/Sandbox)
|
||||
|
||||
Make this context added automatically to all telemetry calls.
|
||||
```
|
||||
|
||||
## Monitoring and Analysis
|
||||
|
||||
### Creating Useful Queries
|
||||
|
||||
**Prompt**:
|
||||
```
|
||||
Create KQL (Kusto Query Language) queries for Application Insights to analyze:
|
||||
|
||||
1. Most common errors in the last 24 hours
|
||||
2. Average processing duration by order size
|
||||
3. Feature usage statistics
|
||||
4. Performance trends over time
|
||||
5. Error rate percentage
|
||||
|
||||
Base the queries on the telemetry we added to the Sales Order Processor codeunit.
|
||||
```
|
||||
|
||||
**Expected Result**:
|
||||
```kql
|
||||
// Most common errors in last 24 hours
|
||||
traces
|
||||
| where timestamp > ago(24h)
|
||||
| where message startswith "SALESORD-E"
|
||||
| summarize ErrorCount = count() by message, customDimensions.ErrorMessage
|
||||
| order by ErrorCount desc
|
||||
| take 10
|
||||
|
||||
// Average processing duration by line count
|
||||
traces
|
||||
| where message == "SALESORD-002"
|
||||
| extend LineCount = toint(customDimensions.LineCount)
|
||||
| extend Duration = toint(customDimensions.Duration)
|
||||
| summarize AvgDuration = avg(Duration) by LineCountBucket = bin(LineCount, 5)
|
||||
| order by LineCountBucket asc
|
||||
|
||||
// Feature usage - discount types
|
||||
traces
|
||||
| where message == "FEATURE-DISCOUNT"
|
||||
| summarize UsageCount = count() by DiscountType = tostring(customDimensions.DiscountType)
|
||||
| order by UsageCount desc
|
||||
|
||||
// Performance warnings
|
||||
traces
|
||||
| where message startswith "SALESORD-P"
|
||||
| summarize WarningCount = count() by Operation = tostring(customDimensions.Operation), bin(timestamp, 1h)
|
||||
| render timechart
|
||||
|
||||
// Error rate percentage
|
||||
let totalOrders = toscalar(
|
||||
traces
|
||||
| where message == "SALESORD-001"
|
||||
| where timestamp > ago(24h)
|
||||
| count
|
||||
);
|
||||
let failedOrders = toscalar(
|
||||
traces
|
||||
| where message startswith "SALESORD-E"
|
||||
| where timestamp > ago(24h)
|
||||
| count
|
||||
);
|
||||
print ErrorRate = (todouble(failedOrders) / todouble(totalOrders)) * 100
|
||||
```
|
||||
|
||||
## Practice Exercise
|
||||
|
||||
Add comprehensive telemetry to this code:
|
||||
|
||||
```al
|
||||
codeunit 50200 "Inventory Adjustment Handler"
|
||||
{
|
||||
procedure AdjustInventory(ItemNo: Code[20]; Quantity: Decimal; ReasonCode: Code[10])
|
||||
var
|
||||
Item: Record Item;
|
||||
ItemJnlLine: Record "Item Journal Line";
|
||||
begin
|
||||
Item.Get(ItemNo);
|
||||
|
||||
ItemJnlLine.Init();
|
||||
ItemJnlLine."Item No." := ItemNo;
|
||||
ItemJnlLine.Quantity := Quantity;
|
||||
ItemJnlLine."Reason Code" := ReasonCode;
|
||||
ItemJnlLine.Insert(true);
|
||||
|
||||
CODEUNIT.Run(CODEUNIT::"Item Jnl.-Post Line", ItemJnlLine);
|
||||
end;
|
||||
}
|
||||
```
|
||||
|
||||
**Your Tasks**:
|
||||
1. Add start/end telemetry
|
||||
2. Add error handling and logging
|
||||
3. Track performance
|
||||
4. Log feature usage
|
||||
5. Add appropriate custom dimensions
|
||||
6. Create KQL queries for analysis
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Learn about [refactoring legacy code](../refactoring) while adding telemetry
|
||||
- See how [code review](../code-review) can catch telemetry issues
|
||||
- Explore [testing strategies](../testing) for telemetry code
|
||||
Loading…
Add table
Add a link
Reference in a new issue