From b1e0a070bf913739e795a12c9b98b34a40539f7e Mon Sep 17 00:00:00 2001 From: Ali Adamu Date: Mon, 18 Aug 2025 17:56:47 +0200 Subject: [PATCH 01/18] Moved to .Net 8.0 Changed the propety of appsettings.json to copy when debugging Added a few comments for consumers --- .../ODataCoreConsoleApp.csproj | 26 +++++++++------ Integration/ODataCoreConsoleApp/Program.cs | 32 ++++++++++++++++--- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/Integration/ODataCoreConsoleApp/ODataCoreConsoleApp.csproj b/Integration/ODataCoreConsoleApp/ODataCoreConsoleApp.csproj index 88fbe28e..465eeddb 100644 --- a/Integration/ODataCoreConsoleApp/ODataCoreConsoleApp.csproj +++ b/Integration/ODataCoreConsoleApp/ODataCoreConsoleApp.csproj @@ -2,23 +2,29 @@ Exe - netcoreapp3.1 + net8.0 - - - - - - - - - + + + + + + + + + + + + PreserveNewest + + + diff --git a/Integration/ODataCoreConsoleApp/Program.cs b/Integration/ODataCoreConsoleApp/Program.cs index a82bf2f6..44a81536 100644 --- a/Integration/ODataCoreConsoleApp/Program.cs +++ b/Integration/ODataCoreConsoleApp/Program.cs @@ -1,14 +1,33 @@ // This example solution requires OData Connected Service (https://github.com/odata/ODataConnectedService) // and NuGet packages System.Text.Json, Microsoft.Identity.Client, Microsoft.Extensions.Configuration, // Microsoft.Extensions.Configuration.Binder, Microsoft.Extensions.Configuration.Json + +/* + * - Update the needed dependecies using Nuget packages + - If needed - Add the Odata Conencted service extension (https://learn.microsoft.com/en-us/odata/connectedservice/getting-started) + - Generate the Odata proxy using the Connected service: + ○ Right click on the project + ○ Select Add --> Connected service + ○ Select Odata + Use the metadata URI to generate the proxy (/data/$metadata) + ○ ○ (refet to https://github.com/OData/ODataConnectedService/issues/331 if generated class does not compile correctly); + ○ TIP: igf gettig the erro at compile time Error CS8103 Combined length of user strings used by the program exceeds allowed limit. Try to decrease use of string literals. You can add an experimenta feture to the project + Opened my ProjectName.csproj. + Insert this line of code in the : + $(Features);experimental-data-section-string-literals + ○ Update the appsettings.json file with the correct values for your environment + * + */ using System; using System.Linq; using System.Text.Json; using System.Web; using System.Threading.Tasks; using Microsoft.Identity.Client; -using Microsoft.Dynamics.DataEntities; +using Microsoft.Extensions.Configuration; using Microsoft.OData.Client; +using Microsoft.OData; +using Microsoft.Dynamics.DataEntities; //This is available when generated by OData Connected Service namespace ODataCoreConsoleApp { @@ -48,12 +67,13 @@ private static void ReportODataError(DataServiceQueryException ex) } private static async Task MainAsync() - { - Console.WriteLine("Authenticating with AAD..."); + { + + Console.WriteLine("Authenticating with EntraId..."); AuthenticationConfig config = AuthenticationConfig.ReadFromJsonFile("appsettings.json"); string bearerToken = await GetAuthenticationHeader(config); - var context = new Resources(new Uri($"{config.BaseUrl}/data/")); + var context = new Microsoft.Dynamics.DataEntities.Resources(new Uri($"{config.BaseUrl}/data/")); //Make all the OData requests cross-company, otherwise you will only reference records in the default company context.BuildingRequest += (sender, eventArgs) => @@ -78,7 +98,8 @@ private static async Task MainAsync() //Query some customer groups Console.WriteLine("Listing top 10 customer groups with Id '20' in all companies..."); - var custGroupsQuery = context.CustomerGroups.AddQueryOption("$filter","CustomerGroupId eq '20'").AddQueryOption("$top", "10").IncludeCount(); + var custGroupsQuery = context.CustomerGroups.AddQueryOption("$filter","CustomerGroupId eq '20'").AddQueryOption("$top", "10").IncludeCount(); + Console.WriteLine(custGroupsQuery.ToString()); try { var custGroupsResponse = custGroupsQuery.Execute() as QueryOperationResponse; //Use this query form if you need response metadata @@ -218,6 +239,7 @@ private static async Task MainAsync() { ReportODataError(ex); } + Console.WriteLine("All done!"); } From a8818a457c28d3c72b86154daceb9f749542b9a9 Mon Sep 17 00:00:00 2001 From: Ali Adamu Date: Mon, 18 Aug 2025 18:05:28 +0200 Subject: [PATCH 02/18] Formatting comments --- Integration/ODataCoreConsoleApp/Program.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Integration/ODataCoreConsoleApp/Program.cs b/Integration/ODataCoreConsoleApp/Program.cs index 44a81536..fa077a8b 100644 --- a/Integration/ODataCoreConsoleApp/Program.cs +++ b/Integration/ODataCoreConsoleApp/Program.cs @@ -9,13 +9,13 @@ ○ Right click on the project ○ Select Add --> Connected service ○ Select Odata - Use the metadata URI to generate the proxy (/data/$metadata) - ○ ○ (refet to https://github.com/OData/ODataConnectedService/issues/331 if generated class does not compile correctly); - ○ TIP: igf gettig the erro at compile time Error CS8103 Combined length of user strings used by the program exceeds allowed limit. Try to decrease use of string literals. You can add an experimenta feture to the project + ○ Use the metadata URI to generate the proxy (/data/$metadata) + ○ (refet to https://github.com/OData/ODataConnectedService/issues/331 if generated class does not compile correctly); + - TIP: igf gettig the erro at compile time Error CS8103 Combined length of user strings used by the program exceeds allowed limit. Try to decrease use of string literals. You can add an experimenta feture to the project Opened my ProjectName.csproj. Insert this line of code in the : $(Features);experimental-data-section-string-literals - ○ Update the appsettings.json file with the correct values for your environment + - Update the appsettings.json file with the correct values for your environment * */ using System; From 46b9f2aa25d9e240c041d6d4ae06f46511bf92b6 Mon Sep 17 00:00:00 2001 From: Ali Adamu Date: Wed, 20 Aug 2025 17:40:32 +0200 Subject: [PATCH 03/18] MessageProcessorConsoleApp --- Integration/MessageProcessorConsoleApp.sln | 25 ++++ .../AuthenticationConfig.cs | 87 +++++++++++ .../MessageProcessorConsoleApp.csproj | 26 ++++ .../MessageProcessorConsoleApp/Program.cs | 141 ++++++++++++++++++ .../appsettings.json | 7 + 5 files changed, 286 insertions(+) create mode 100644 Integration/MessageProcessorConsoleApp.sln create mode 100644 Integration/MessageProcessorConsoleApp/AuthenticationConfig.cs create mode 100644 Integration/MessageProcessorConsoleApp/MessageProcessorConsoleApp.csproj create mode 100644 Integration/MessageProcessorConsoleApp/Program.cs create mode 100644 Integration/MessageProcessorConsoleApp/appsettings.json diff --git a/Integration/MessageProcessorConsoleApp.sln b/Integration/MessageProcessorConsoleApp.sln new file mode 100644 index 00000000..11281f9d --- /dev/null +++ b/Integration/MessageProcessorConsoleApp.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36408.4 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessageProcessorConsoleApp", "MessageProcessorConsoleApp\MessageProcessorConsoleApp.csproj", "{04476EAC-E2DE-4D93-B5EE-995EF477D854}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {04476EAC-E2DE-4D93-B5EE-995EF477D854}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {04476EAC-E2DE-4D93-B5EE-995EF477D854}.Debug|Any CPU.Build.0 = Debug|Any CPU + {04476EAC-E2DE-4D93-B5EE-995EF477D854}.Release|Any CPU.ActiveCfg = Release|Any CPU + {04476EAC-E2DE-4D93-B5EE-995EF477D854}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {66531EC9-4EA5-4839-84BC-74553326B5BA} + EndGlobalSection +EndGlobal diff --git a/Integration/MessageProcessorConsoleApp/AuthenticationConfig.cs b/Integration/MessageProcessorConsoleApp/AuthenticationConfig.cs new file mode 100644 index 00000000..c66818a3 --- /dev/null +++ b/Integration/MessageProcessorConsoleApp/AuthenticationConfig.cs @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Extensions.Configuration; +using System; +using System.Globalization; +using System.IO; + +namespace ODataCoreConsoleApp +{ + /// + /// Description of the configuration of an AzureAD public client application (desktop/mobile application). This should + /// match the application registration done in the Azure portal + /// + public class AuthenticationConfig + { + /// + /// instance of Azure AD, for example public Azure or a Sovereign cloud (Azure China, Germany, US government, etc ...) + /// + public string Instance { get; set; } = "https://login.microsoftonline.com/{0}"; + + /// + /// API endpoint (e.g. Graph API), could be public Azure (default) or a Sovereign cloud (US government, etc ...) + /// + public string BaseUrl { get; set; } = "https://graph.microsoft.com"; + + /// + /// The Tenant is: + /// - either the tenant ID of the Azure AD tenant in which this application is registered (a guid) + /// or a domain name associated with the tenant + /// - or 'organizations' (for a multi-tenant application) + /// + public string Tenant { get; set; } + + /// + /// Guid used by the application to uniquely identify itself to Azure AD + /// + public string ClientId { get; set; } + + /// + /// URL of the authority + /// + public string Authority + { + get + { + return String.Format(CultureInfo.InvariantCulture, Instance, Tenant); + } + } + + /// + /// Client secret (application password) + /// + /// Daemon applications can authenticate with AAD through two mechanisms: ClientSecret + /// (which is a kind of application password: this property) + /// or a certificate previously shared with AzureAD during the application registration + /// (and identified by the CertificateName property belows) + /// + public string ClientSecret { get; set; } + + /// + /// Name of a certificate in the user certificate store + /// + /// Daemon applications can authenticate with AAD through two mechanisms: ClientSecret + /// (which is a kind of application password: the property above) + /// or a certificate previously shared with AzureAD during the application registration + /// (and identified by this CertificateName property) + /// + public string CertificateName { get; set; } + + /// + /// Reads the configuration from a json file + /// + /// Path to the configuration json file + /// AuthenticationConfig read from the json file + public static AuthenticationConfig ReadFromJsonFile(string path) + { + IConfigurationRoot Configuration; + + var builder = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile(path); + + Configuration = builder.Build(); + return Configuration.Get(); + } + } + +} diff --git a/Integration/MessageProcessorConsoleApp/MessageProcessorConsoleApp.csproj b/Integration/MessageProcessorConsoleApp/MessageProcessorConsoleApp.csproj new file mode 100644 index 00000000..633fdf04 --- /dev/null +++ b/Integration/MessageProcessorConsoleApp/MessageProcessorConsoleApp.csproj @@ -0,0 +1,26 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/Integration/MessageProcessorConsoleApp/Program.cs b/Integration/MessageProcessorConsoleApp/Program.cs new file mode 100644 index 00000000..c8abd046 --- /dev/null +++ b/Integration/MessageProcessorConsoleApp/Program.cs @@ -0,0 +1,141 @@ +// This example solution requires NuGet packages System.Text.Json, Microsoft.Identity.Client, +// Microsoft.Extensions.Configuration, Microsoft.Extensions.Configuration.Binder, Microsoft.Extensions.Configuration.Json + +using Microsoft.Extensions.Configuration; +using Microsoft.Identity.Client; +using System; +using System.Diagnostics.Metrics; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using System.Web; + + + + +namespace ODataCoreConsoleApp +{ + class Program + { + private const string Endpoint = "/api/services/SysMessageServices/SysMessageService/SendMessage"; + private static async Task GetAuthenticationHeader(AuthenticationConfig config) + { + IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create(config.ClientId) + .WithClientSecret(config.ClientSecret) + .WithAuthority(new Uri(config.Authority)) + .Build(); + string[] scopes = new string[] { $"{config.BaseUrl}/.default" }; + AuthenticationResult result = await app.AcquireTokenForClient(scopes) + .ExecuteAsync(); + return result.CreateAuthorizationHeader(); + } + + private static void ReportError(Exception ex) + { + while (ex != null) + { + Console.WriteLine(ex.Message); + ex = ex.InnerException; + } + + } + + + private static async Task MainAsync() + { + string? orderPrefix = "IMP"; + int initialOrderNumber = 1; + int numberOfOrders = 2; + + //Get user input for order details + Console.WriteLine("Please provide the number of orders to generate (default=2, limit 2000)..."); + string? input = Console.ReadLine(); + if (!string.IsNullOrEmpty(input)) + { + numberOfOrders = int.Parse(input); + } + numberOfOrders = numberOfOrders > 0 ? numberOfOrders : 2; + numberOfOrders = numberOfOrders > 2000 ? 2000 : numberOfOrders; + + Console.WriteLine("Please provide the OrederPrefix (default IMP)..."); + orderPrefix = Console.ReadLine(); + orderPrefix = string.IsNullOrEmpty(orderPrefix) ? "IMP" : orderPrefix; + + Console.WriteLine("Please provide the initial order number (default=1)..."); + input = Console.ReadLine(); + if (!string.IsNullOrEmpty(input)) + { + initialOrderNumber = int.Parse(input); + } + initialOrderNumber = initialOrderNumber > 0 ? initialOrderNumber : 1; + try + { + //Authenticate with Entra ID + Console.WriteLine("Authenticating with EntraId..."); + AuthenticationConfig config = AuthenticationConfig.ReadFromJsonFile("appsettings.json"); + string bearerToken = await GetAuthenticationHeader(config); + bearerToken = bearerToken.Split(' ')[1]; + + + string fullUrl = $"{config.BaseUrl.TrimEnd('/')}{Endpoint}"; + Console.WriteLine(fullUrl); + + //Prepare oreders and send them to the Message Procesor endpoint + for (int i = 0; i < numberOfOrders; i++) + { + string nextOrderNumber = orderPrefix + (initialOrderNumber + i).ToString("D4"); + Console.Write($"Sending order {nextOrderNumber}..."); + + var jsonBody = @" + { + ""_companyId"": ""USMF"", + ""_messageQueue"": ""SalesOrderQuickQueue"", + ""_messageType"": ""SalesOrderQuickMessage"", + ""_messageContent"": ""{\""CustomerAccount\"": \""US-001\"", \""SalesOrderNumber\"": \""" + nextOrderNumber + @"\"", \""SalesOrderLines\"": [{\""ItemNumber\"": \""D0001\"", \""Qty\"": 23},{\""ItemNumber\"": \""D0003\"", \""Qty\"": 17}]}"" + }"; + + + using var httpClient = new HttpClient(); + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken); + + var content = new StringContent(jsonBody, Encoding.UTF8, "application/json"); + + var response = await httpClient.PostAsync(fullUrl, content); + string responseContent = await response.Content.ReadAsStringAsync(); + + Console.WriteLine($"Status Code: {response.StatusCode}"); + if (!String.IsNullOrEmpty(responseContent)) + { + Console.WriteLine("Response:"); + Console.WriteLine(responseContent); + } + } + } + catch (Exception ex) + { + ReportError(ex); + } + + Console.WriteLine("All done!"); + + } + + public static int Main(string[] args) + { + try + { + MainAsync().Wait(); + return 0; + } + catch (Exception ex) + { + Console.Error.WriteLine(ex); + return -1; + } + } + } + +} diff --git a/Integration/MessageProcessorConsoleApp/appsettings.json b/Integration/MessageProcessorConsoleApp/appsettings.json new file mode 100644 index 00000000..398018d3 --- /dev/null +++ b/Integration/MessageProcessorConsoleApp/appsettings.json @@ -0,0 +1,7 @@ +{ + "Instance": "https://login.microsoftonline.com/{0}", + "BaseUrl": "[Enter here the base URL for your D365FO instance, no terminating slash]", + "Tenant": "[Enter here the tenantID or domain name for your Azure AD tenant]", + "ClientId": "[Enter here the ClientId for your application]", + "ClientSecret": "[Enter here a client secret for your application]" +} From 2eba4cb5463e86ee262643b936c8b2b404f38ce1 Mon Sep 17 00:00:00 2001 From: Ali Adamu Date: Wed, 20 Aug 2025 17:52:32 +0200 Subject: [PATCH 04/18] X++ project --- .../QuickOrderProcessor.axpp | Bin 0 -> 7758 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Integration/MessageProcessorConsoleApp/QuickOrderProcessor.axpp diff --git a/Integration/MessageProcessorConsoleApp/QuickOrderProcessor.axpp b/Integration/MessageProcessorConsoleApp/QuickOrderProcessor.axpp new file mode 100644 index 0000000000000000000000000000000000000000..3b54b89cbcededc83d8080700beb29693fe1b816 GIT binary patch literal 7758 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 literal 0 HcmV?d00001 From 9d0e7704ed6ebd0f5a0a1625c246a4da3e26599a Mon Sep 17 00:00:00 2001 From: Ali Adamu Date: Wed, 20 Aug 2025 18:02:04 +0200 Subject: [PATCH 05/18] Readme.md added --- .../MessageProcessorConsoleApp/README.md | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 Integration/MessageProcessorConsoleApp/README.md diff --git a/Integration/MessageProcessorConsoleApp/README.md b/Integration/MessageProcessorConsoleApp/README.md new file mode 100644 index 00000000..696a6a61 --- /dev/null +++ b/Integration/MessageProcessorConsoleApp/README.md @@ -0,0 +1,39 @@ + +# Dynamics 365 for Finance and Operations sample Message Processor console application + +This asset contains a sample console application that demonstrates how to use the the Message Processor to import simple sales orders in Dynamics 365 for Finance and Operations (F&O). +The sample consists of + +## How to install + 1. Download the sample code from the [GitHub repository](https://github.com/microsoft/Dynamics-365-FastTrack-Implementation-Assets). + 2. Import and compile the Dynamics 365 for Finance and Operations project into your F&O environment. + 3. Configure the message processr in your environemnt by following the instructions in the [Message Processor documentation](https://docs.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/data-entities/message-processor). + 4. Open the solution in Visual Studio 2022. + 5. Edit the `appsettings.json` file to include the correct settings for your F&O environment. + 6. Build the solution. + 7. Run the console application. + +# Contents +| File/folder | Description | +|-------------|-------------| +| `README.md` | This README file. | +| `MessageProcessorConsoleApp.csproj` | Visual Studio 2022 project definition. | +| `AuthenticationConfig.cs` | Utility class for EntraId authentication using the web application (EntraId client ID + secret) flow. | +| `Program.cs` | Main console application performing the import of sales orders via the Message Processor. | +| `appsettings.json` | Settings template file to be edited for target F&O environment. Must be present in the same folder as the executable console application. | +| `QuickOrderProcessor.axpp` | F&O project containing the Message Processor configuration and the data entity used for the import. | + + + + From 3dc5802c3634ccd38eb0cac5a2f6ccbbf3177bff Mon Sep 17 00:00:00 2001 From: Ali Adamu Date: Wed, 20 Aug 2025 18:23:48 +0200 Subject: [PATCH 06/18] README.md update --- .../MessageProcessorConsoleApp/README.md | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/Integration/MessageProcessorConsoleApp/README.md b/Integration/MessageProcessorConsoleApp/README.md index 696a6a61..20915099 100644 --- a/Integration/MessageProcessorConsoleApp/README.md +++ b/Integration/MessageProcessorConsoleApp/README.md @@ -15,14 +15,20 @@ urlFragment: "d365-fo-message-processor-console" This asset contains a sample console application that demonstrates how to use the the Message Processor to import simple sales orders in Dynamics 365 for Finance and Operations (F&O). The sample consists of -## How to install - 1. Download the sample code from the [GitHub repository](https://github.com/microsoft/Dynamics-365-FastTrack-Implementation-Assets). - 2. Import and compile the Dynamics 365 for Finance and Operations project into your F&O environment. - 3. Configure the message processr in your environemnt by following the instructions in the [Message Processor documentation](https://docs.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/data-entities/message-processor). - 4. Open the solution in Visual Studio 2022. - 5. Edit the `appsettings.json` file to include the correct settings for your F&O environment. - 6. Build the solution. - 7. Run the console application. +## How to instal + +This sample requires Dynamics 365 for Finance and Operations version 10.0.31 or later, Visual Studio 2022, and the .NET 8 SDK. +Instructions to set up the sample are provided below. +1. Download the sample code from the [GitHub repository](https://github.com/microsoft/Dynamics-365-FastTrack-Implementation-Assets). +2. Import and compile the Dynamics 365 for Finance and Operations project into your F&O environment. The project has been following the [official documentation page](https://learn.microsoft.com/en-us/dynamics365/supply-chain/message-processor/developer/message-processor-develop). +3. Configure the message processor in your environment by following the instructions in the [Message Processor documentation](https://learn.microsoft.com/en-us/dynamics365/supply-chain/message-processor/message-processor). + * Navigate to _System administration > Message processor > Message queue_ setup and create a new message processor queue selecting the new type "Quick sales orders". Choose teh number of processors (e.g. 4) + * Navigate to _System administration > Message processor > Message processor_ to set upo the processing batch job. In the batch setupo page select e new queue type "Quick sales orders". + * From now you can navigate to _System administration > Message processor > Message precessor messages_ to check the incoming messages. +4. Open the solution in Visual Studio 2022. +5. Edit the `appsettings.json` file to include the correct settings for your F&O environment. +6. Build the solution. +7. Run the console application. # Contents | File/folder | Description | From 116f7d3e07553c65eb07f718bded00b1e9e656b9 Mon Sep 17 00:00:00 2001 From: Ali Date: Wed, 1 Oct 2025 12:26:12 +0200 Subject: [PATCH 07/18] Modify appsettings.json for Azure configurations Updated appsettings.json to include Azure Key Vault and Azure AD configurations. --- .../MessageProcessorConsoleApp/appsettings.json | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Integration/MessageProcessorConsoleApp/appsettings.json b/Integration/MessageProcessorConsoleApp/appsettings.json index 398018d3..c6cc0ade 100644 --- a/Integration/MessageProcessorConsoleApp/appsettings.json +++ b/Integration/MessageProcessorConsoleApp/appsettings.json @@ -1,7 +1,14 @@ -{ +{ + "BaseUri": "", "Instance": "https://login.microsoftonline.com/{0}", - "BaseUrl": "[Enter here the base URL for your D365FO instance, no terminating slash]", - "Tenant": "[Enter here the tenantID or domain name for your Azure AD tenant]", - "ClientId": "[Enter here the ClientId for your application]", - "ClientSecret": "[Enter here a client secret for your application]" + "AzureKeyVault": { + "VaultUri": "", + "AppIdentifier": "", + "KeyVaultTenantId": "" + }, + "AzureAd": { + "ClientId": "", + "TenantId": "", + "Audience": "" + } } From f3afa1ad7af123b4b5bbae70206a955e6925216a Mon Sep 17 00:00:00 2001 From: Ali Adamu Date: Wed, 1 Oct 2025 13:11:00 +0200 Subject: [PATCH 08/18] New configurations for keyvault --- Integration/MessageProcessorConsoleApp/appsettings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Integration/MessageProcessorConsoleApp/appsettings.json b/Integration/MessageProcessorConsoleApp/appsettings.json index c6cc0ade..3b05ba9f 100644 --- a/Integration/MessageProcessorConsoleApp/appsettings.json +++ b/Integration/MessageProcessorConsoleApp/appsettings.json @@ -1,4 +1,4 @@ -{ +{ "BaseUri": "", "Instance": "https://login.microsoftonline.com/{0}", "AzureKeyVault": { From e0e3d16a709712d12b98512ec463175e3194f24d Mon Sep 17 00:00:00 2001 From: Ali Adamu Date: Wed, 1 Oct 2025 13:11:39 +0200 Subject: [PATCH 09/18] New configurations for keyvault --- .../AuthenticationConfig.cs | 87 ------------------- .../AuthenticationConfiguration.cs | 77 ++++++++++++++++ .../AuthenticationHelper.cs | 39 +++++++++ .../MessageProcessorConsoleApp/Program.cs | 41 +++++++-- 4 files changed, 149 insertions(+), 95 deletions(-) delete mode 100644 Integration/MessageProcessorConsoleApp/AuthenticationConfig.cs create mode 100644 Integration/MessageProcessorConsoleApp/AuthenticationConfiguration.cs create mode 100644 Integration/MessageProcessorConsoleApp/AuthenticationHelper.cs diff --git a/Integration/MessageProcessorConsoleApp/AuthenticationConfig.cs b/Integration/MessageProcessorConsoleApp/AuthenticationConfig.cs deleted file mode 100644 index c66818a3..00000000 --- a/Integration/MessageProcessorConsoleApp/AuthenticationConfig.cs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.Extensions.Configuration; -using System; -using System.Globalization; -using System.IO; - -namespace ODataCoreConsoleApp -{ - /// - /// Description of the configuration of an AzureAD public client application (desktop/mobile application). This should - /// match the application registration done in the Azure portal - /// - public class AuthenticationConfig - { - /// - /// instance of Azure AD, for example public Azure or a Sovereign cloud (Azure China, Germany, US government, etc ...) - /// - public string Instance { get; set; } = "https://login.microsoftonline.com/{0}"; - - /// - /// API endpoint (e.g. Graph API), could be public Azure (default) or a Sovereign cloud (US government, etc ...) - /// - public string BaseUrl { get; set; } = "https://graph.microsoft.com"; - - /// - /// The Tenant is: - /// - either the tenant ID of the Azure AD tenant in which this application is registered (a guid) - /// or a domain name associated with the tenant - /// - or 'organizations' (for a multi-tenant application) - /// - public string Tenant { get; set; } - - /// - /// Guid used by the application to uniquely identify itself to Azure AD - /// - public string ClientId { get; set; } - - /// - /// URL of the authority - /// - public string Authority - { - get - { - return String.Format(CultureInfo.InvariantCulture, Instance, Tenant); - } - } - - /// - /// Client secret (application password) - /// - /// Daemon applications can authenticate with AAD through two mechanisms: ClientSecret - /// (which is a kind of application password: this property) - /// or a certificate previously shared with AzureAD during the application registration - /// (and identified by the CertificateName property belows) - /// - public string ClientSecret { get; set; } - - /// - /// Name of a certificate in the user certificate store - /// - /// Daemon applications can authenticate with AAD through two mechanisms: ClientSecret - /// (which is a kind of application password: the property above) - /// or a certificate previously shared with AzureAD during the application registration - /// (and identified by this CertificateName property) - /// - public string CertificateName { get; set; } - - /// - /// Reads the configuration from a json file - /// - /// Path to the configuration json file - /// AuthenticationConfig read from the json file - public static AuthenticationConfig ReadFromJsonFile(string path) - { - IConfigurationRoot Configuration; - - var builder = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile(path); - - Configuration = builder.Build(); - return Configuration.Get(); - } - } - -} diff --git a/Integration/MessageProcessorConsoleApp/AuthenticationConfiguration.cs b/Integration/MessageProcessorConsoleApp/AuthenticationConfiguration.cs new file mode 100644 index 00000000..0cd263fc --- /dev/null +++ b/Integration/MessageProcessorConsoleApp/AuthenticationConfiguration.cs @@ -0,0 +1,77 @@ +/** + * SAMPLE CODE NOTICE + * + * THIS SAMPLE CODE IS MADE AVAILABLE AS IS. MICROSOFT MAKES NO WARRANTIES, WHETHER EXPRESS OR IMPLIED, + * OF FITNESS FOR A PARTICULAR PURPOSE, OF ACCURACY OR COMPLETENESS OF RESPONSES, OF RESULTS, OR CONDITIONS OF MERCHANTABILITY. + * THE ENTIRE RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS SAMPLE CODE REMAINS WITH THE USER. + * NO TECHNICAL SUPPORT IS PROVIDED. YOU MAY NOT DISTRIBUTE THIS CODE UNLESS YOU HAVE A LICENSE AGREEMENT WITH MICROSOFT THAT ALLOWS YOU TO DO SO. + */ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; + +namespace MessageProcessorConsoleApp +{ + /* + "BaseUri": "https://aladamuvirtualentity765eccb33923d24ddevaos.axcloud.dynamics.com", + "AzureKeyVault": { + "VaultUri": "https://aladamuvirtualentitykv.vault.azure.net/", + "AppIdentifier": "aladamuVirtualEntity", + "KeyVaultTenantId": "979fd422-22c4-4a36-bea6-1cf87b6502dd" + }, + "AzureAd": { + "ClientId": "2fd29989-4f6b-4fbf-8e09-c86c703255bd", + "Authority": "https://login.microsoftonline.com/{0}", + "TenantId": "979fd422-22c4-4a36-bea6-1cf87b6502dd", + "Audience": "https://aladamuvirtualentity765eccb33923d24ddevaos.axcloud.dynamics.com" + } + */ + + public class AuthenticationConfiguration + { + public string BaseUri { get; set; } + public AzureKeyVaultConfig AzureKeyVault { get; set; } + public AzureAdConfig AzureAd { get; set; } + public string Instance { get; set; } = "https://login.microsoftonline.com/{0}"; + public string Authority + { + get + { + return String.Format(CultureInfo.InvariantCulture, Instance, AzureAd.TenantId); + } + } + + public static AuthenticationConfiguration ReadFromJsonFile(string path) + { + // build a config object, using the appsettings.json file as the default source + var config = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile(path, optional: false, reloadOnChange: true) + .Build(); + // bind the config object to the AppSettings class + var result = new AuthenticationConfiguration(); + config.Bind(result); + // return the populated AppSettings object + return result; + } + + } + + public class AzureKeyVaultConfig + { + public string VaultUri { get; set; } + public string AppIdentifier { get; set; } + public string KeyVaultTenantId { get; set; } + } + + public class AzureAdConfig + { + public string ClientId { get; set; } + public string TenantId { get; set; } + public string Audience { get; set; } + } +} diff --git a/Integration/MessageProcessorConsoleApp/AuthenticationHelper.cs b/Integration/MessageProcessorConsoleApp/AuthenticationHelper.cs new file mode 100644 index 00000000..ef27d893 --- /dev/null +++ b/Integration/MessageProcessorConsoleApp/AuthenticationHelper.cs @@ -0,0 +1,39 @@ +/** + * SAMPLE CODE NOTICE + * + * THIS SAMPLE CODE IS MADE AVAILABLE AS IS. MICROSOFT MAKES NO WARRANTIES, WHETHER EXPRESS OR IMPLIED, + * OF FITNESS FOR A PARTICULAR PURPOSE, OF ACCURACY OR COMPLETENESS OF RESPONSES, OF RESULTS, OR CONDITIONS OF MERCHANTABILITY. + * THE ENTIRE RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS SAMPLE CODE REMAINS WITH THE USER. + * NO TECHNICAL SUPPORT IS PROVIDED. YOU MAY NOT DISTRIBUTE THIS CODE UNLESS YOU HAVE A LICENSE AGREEMENT WITH MICROSOFT THAT ALLOWS YOU TO DO SO. + */ +using Microsoft.Identity.Client; + +namespace MessageProcessorConsoleApp +{ + public class AuthenticationHelper + { + /// + /// Get the access token to call CSU API + /// + /// + /// + /// + /// + /// + /// + public static async Task GetAuthenticationResult(string clientId, string authority, string clientSecret, string tenantId, string audience) + { + var confidentialClientApplication = ConfidentialClientApplicationBuilder. + Create(clientId) + .WithAuthority(authority + tenantId) + .WithClientSecret(clientSecret); + string[] scopes = [$"{audience}/.default"]; + AuthenticationResult authResult = await confidentialClientApplication + .Build() + .AcquireTokenForClient(scopes) + .ExecuteAsync() + .ConfigureAwait(false); + return authResult.AccessToken; + } + } +} diff --git a/Integration/MessageProcessorConsoleApp/Program.cs b/Integration/MessageProcessorConsoleApp/Program.cs index c8abd046..13c7facd 100644 --- a/Integration/MessageProcessorConsoleApp/Program.cs +++ b/Integration/MessageProcessorConsoleApp/Program.cs @@ -1,6 +1,17 @@ -// This example solution requires NuGet packages System.Text.Json, Microsoft.Identity.Client, +/** + * SAMPLE CODE NOTICE + * + * THIS SAMPLE CODE IS MADE AVAILABLE AS IS. MICROSOFT MAKES NO WARRANTIES, WHETHER EXPRESS OR IMPLIED, + * OF FITNESS FOR A PARTICULAR PURPOSE, OF ACCURACY OR COMPLETENESS OF RESPONSES, OF RESULTS, OR CONDITIONS OF MERCHANTABILITY. + * THE ENTIRE RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS SAMPLE CODE REMAINS WITH THE USER. + * NO TECHNICAL SUPPORT IS PROVIDED. YOU MAY NOT DISTRIBUTE THIS CODE UNLESS YOU HAVE A LICENSE AGREEMENT WITH MICROSOFT THAT ALLOWS YOU TO DO SO. + */ +// This example solution requires NuGet packages System.Text.Json, Microsoft.Identity.Client, // Microsoft.Extensions.Configuration, Microsoft.Extensions.Configuration.Binder, Microsoft.Extensions.Configuration.Json +using Azure.Security.KeyVault.Secrets; +using Azure.Identity; +using MessageProcessorConsoleApp; using Microsoft.Extensions.Configuration; using Microsoft.Identity.Client; using System; @@ -21,13 +32,28 @@ namespace ODataCoreConsoleApp class Program { private const string Endpoint = "/api/services/SysMessageServices/SysMessageService/SendMessage"; - private static async Task GetAuthenticationHeader(AuthenticationConfig config) + private static async Task GetAuthenticationHeader(AuthenticationConfiguration config) { - IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create(config.ClientId) - .WithClientSecret(config.ClientSecret) + + var interactiveCredential = new InteractiveBrowserCredential( + new InteractiveBrowserCredentialOptions + { + TenantId = config.AzureKeyVault.KeyVaultTenantId, + RedirectUri = new Uri("http://localhost") + }); + + var client = new SecretClient(new Uri(config.AzureKeyVault.VaultUri), interactiveCredential); + KeyVaultSecret secret = await client.GetSecretAsync(config.AzureKeyVault.AppIdentifier); + string clientSecret = secret.Value; + + + IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create(config.AzureAd.ClientId) + .WithClientSecret(clientSecret) .WithAuthority(new Uri(config.Authority)) .Build(); - string[] scopes = new string[] { $"{config.BaseUrl}/.default" }; + string[] scopes = new string[] { $"{config.BaseUri}/.default" }; + + AuthenticationResult result = await app.AcquireTokenForClient(scopes) .ExecuteAsync(); return result.CreateAuthorizationHeader(); @@ -43,7 +69,6 @@ private static void ReportError(Exception ex) } - private static async Task MainAsync() { string? orderPrefix = "IMP"; @@ -75,12 +100,12 @@ private static async Task MainAsync() { //Authenticate with Entra ID Console.WriteLine("Authenticating with EntraId..."); - AuthenticationConfig config = AuthenticationConfig.ReadFromJsonFile("appsettings.json"); + AuthenticationConfiguration config = AuthenticationConfiguration.ReadFromJsonFile("appsettings.json"); string bearerToken = await GetAuthenticationHeader(config); bearerToken = bearerToken.Split(' ')[1]; - string fullUrl = $"{config.BaseUrl.TrimEnd('/')}{Endpoint}"; + string fullUrl = $"{config.BaseUri.TrimEnd('/')}{Endpoint}"; Console.WriteLine(fullUrl); //Prepare oreders and send them to the Message Procesor endpoint From abde575b347495eb628475ebe995fd3818794646 Mon Sep 17 00:00:00 2001 From: Ali Adamu Date: Wed, 1 Oct 2025 13:12:45 +0200 Subject: [PATCH 10/18] New configurations for keyvault --- .../MessageProcessorConsoleApp.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Integration/MessageProcessorConsoleApp/MessageProcessorConsoleApp.csproj b/Integration/MessageProcessorConsoleApp/MessageProcessorConsoleApp.csproj index 633fdf04..5ffdccf5 100644 --- a/Integration/MessageProcessorConsoleApp/MessageProcessorConsoleApp.csproj +++ b/Integration/MessageProcessorConsoleApp/MessageProcessorConsoleApp.csproj @@ -8,6 +8,9 @@ + + + From ab0f1efd5e0618c5a7965233e4b11665acae3a6f Mon Sep 17 00:00:00 2001 From: Ali Adamu Date: Wed, 1 Oct 2025 13:13:50 +0200 Subject: [PATCH 11/18] Correction in the list --- Integration/ODataCoreConsoleApp/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Integration/ODataCoreConsoleApp/README.md b/Integration/ODataCoreConsoleApp/README.md index bb3b00e5..410f061b 100644 --- a/Integration/ODataCoreConsoleApp/README.md +++ b/Integration/ODataCoreConsoleApp/README.md @@ -17,14 +17,14 @@ Conceptually, this asset is similar to the OData console application sample prov Instead of the older client libraries used in the previous sample, it has been written to take advantage of the modern [OData Connected Service](https://github.com/odata/ODataConnectedService), .Net Core and supporting NuGet packages. -You will need to add the Connected service as described at https://docs.microsoft.com/en-us/odata/client/getting-started#using-the-odata-connected-service. +You will need to add the Connected service as described at https://learn.microsoft.com/en-us/odata/client/getting-started#using-the-odata-connected-service. # Contents | File/folder | Description | |-------------|-------------| | `README.md` | This README file. | -| `ODataCoreConsoleApp.csproj` | Visual Studio 2019 project definition. | -| `AuthenticationConfig.cs` | Utility class for AAD authentication using the web application (AAD client ID + secret) flow. | +| `ODataCoreConsoleApp.csproj` | Visual Studio 2022 project definition. | +| `AuthenticationConfig.cs` | Utility class for EntraId authentication using the web application (EntraId client ID + secret) flow. | | `Program.cs` | Main console application performing some CRUD operations via OData. | | `appsettings.json` | Settings template file to be edited for target F&O environment. Must be present in the same folder as the executable console application. | From 6071aa757e44e247f6ce93083590336c56d3bd2b Mon Sep 17 00:00:00 2001 From: Ali Adamu Date: Thu, 9 Oct 2025 16:22:48 +0200 Subject: [PATCH 12/18] Removing a secret in a comment --- .../AuthenticationConfiguration.cs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/Integration/MessageProcessorConsoleApp/AuthenticationConfiguration.cs b/Integration/MessageProcessorConsoleApp/AuthenticationConfiguration.cs index 0cd263fc..814d945c 100644 --- a/Integration/MessageProcessorConsoleApp/AuthenticationConfiguration.cs +++ b/Integration/MessageProcessorConsoleApp/AuthenticationConfiguration.cs @@ -16,21 +16,6 @@ namespace MessageProcessorConsoleApp { - /* - "BaseUri": "https://aladamuvirtualentity765eccb33923d24ddevaos.axcloud.dynamics.com", - "AzureKeyVault": { - "VaultUri": "https://aladamuvirtualentitykv.vault.azure.net/", - "AppIdentifier": "aladamuVirtualEntity", - "KeyVaultTenantId": "979fd422-22c4-4a36-bea6-1cf87b6502dd" - }, - "AzureAd": { - "ClientId": "2fd29989-4f6b-4fbf-8e09-c86c703255bd", - "Authority": "https://login.microsoftonline.com/{0}", - "TenantId": "979fd422-22c4-4a36-bea6-1cf87b6502dd", - "Audience": "https://aladamuvirtualentity765eccb33923d24ddevaos.axcloud.dynamics.com" - } - */ - public class AuthenticationConfiguration { public string BaseUri { get; set; } From ba03d8684ce559f5a38ba7ff1515f0120d73b486 Mon Sep 17 00:00:00 2001 From: Ali Adamu Date: Thu, 9 Oct 2025 16:41:34 +0200 Subject: [PATCH 13/18] Revert "Removing a secret in a comment" This reverts commit 6071aa757e44e247f6ce93083590336c56d3bd2b. --- .../AuthenticationConfiguration.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Integration/MessageProcessorConsoleApp/AuthenticationConfiguration.cs b/Integration/MessageProcessorConsoleApp/AuthenticationConfiguration.cs index 814d945c..0cd263fc 100644 --- a/Integration/MessageProcessorConsoleApp/AuthenticationConfiguration.cs +++ b/Integration/MessageProcessorConsoleApp/AuthenticationConfiguration.cs @@ -16,6 +16,21 @@ namespace MessageProcessorConsoleApp { + /* + "BaseUri": "https://aladamuvirtualentity765eccb33923d24ddevaos.axcloud.dynamics.com", + "AzureKeyVault": { + "VaultUri": "https://aladamuvirtualentitykv.vault.azure.net/", + "AppIdentifier": "aladamuVirtualEntity", + "KeyVaultTenantId": "979fd422-22c4-4a36-bea6-1cf87b6502dd" + }, + "AzureAd": { + "ClientId": "2fd29989-4f6b-4fbf-8e09-c86c703255bd", + "Authority": "https://login.microsoftonline.com/{0}", + "TenantId": "979fd422-22c4-4a36-bea6-1cf87b6502dd", + "Audience": "https://aladamuvirtualentity765eccb33923d24ddevaos.axcloud.dynamics.com" + } + */ + public class AuthenticationConfiguration { public string BaseUri { get; set; } From e8441994839e0ed96faf0f0a78546a38f1d14ca7 Mon Sep 17 00:00:00 2001 From: Ali Adamu Date: Thu, 9 Oct 2025 17:26:36 +0200 Subject: [PATCH 14/18] Clean up authenticatin configuration --- .../AuthenticationConfiguration.cs | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/Integration/MessageProcessorConsoleApp/AuthenticationConfiguration.cs b/Integration/MessageProcessorConsoleApp/AuthenticationConfiguration.cs index 0cd263fc..f96bac70 100644 --- a/Integration/MessageProcessorConsoleApp/AuthenticationConfiguration.cs +++ b/Integration/MessageProcessorConsoleApp/AuthenticationConfiguration.cs @@ -16,21 +16,7 @@ namespace MessageProcessorConsoleApp { - /* - "BaseUri": "https://aladamuvirtualentity765eccb33923d24ddevaos.axcloud.dynamics.com", - "AzureKeyVault": { - "VaultUri": "https://aladamuvirtualentitykv.vault.azure.net/", - "AppIdentifier": "aladamuVirtualEntity", - "KeyVaultTenantId": "979fd422-22c4-4a36-bea6-1cf87b6502dd" - }, - "AzureAd": { - "ClientId": "2fd29989-4f6b-4fbf-8e09-c86c703255bd", - "Authority": "https://login.microsoftonline.com/{0}", - "TenantId": "979fd422-22c4-4a36-bea6-1cf87b6502dd", - "Audience": "https://aladamuvirtualentity765eccb33923d24ddevaos.axcloud.dynamics.com" - } - */ - + public class AuthenticationConfiguration { public string BaseUri { get; set; } From aaf916ac4da1c3fc1cad31b5be7a3a2e63ba87f2 Mon Sep 17 00:00:00 2001 From: Ali Adamu Date: Tue, 14 Oct 2025 11:28:42 +0200 Subject: [PATCH 15/18] Main README updated --- Integration/README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Integration/README.md b/Integration/README.md index f7f68901..f3703e37 100644 --- a/Integration/README.md +++ b/Integration/README.md @@ -19,9 +19,8 @@ This implemetation asset provides sample integration code for Dynamics 365. | File/folder | Description | |-------------|-------------| | `README.md` | This README file. | -| `ODataCoreConsoleApp.sln` | Visual Studio 2019 solution file for the sample OData console application. | +| `ODataCoreConsoleApp.sln` | Visual Studio 2022 solution file for the sample OData console application. | | `ODataCoreConsoleApp` | Sample F&O OData client console application written to illustrate the use of .Net Core and the OData Connected Service in a client application. | +| `MessageProcessorConsoleApp.sln` | Visual Studio 2022 solution file for the sample Message Processor console application. [ODataCoreConsoleApp](https://github.com/microsoft/Dynamics-365-FastTrack-Implementation-Assets/tree/master/Integration/ODataCoreConsoleApp) | +| `MessageProcessorConsoleApp` | Message Processor console application written to illustrate the use of .Net to interact with Dynamics 365 F&O Message processor framework. [MessageProcessorConsoleApp](https://github.com/microsoft/Dynamics-365-FastTrack-Implementation-Assets/tree/master/Integration/MessageProcessorConsoleApp) | | `Field Service integration with F&O` | [Field Service integration with F&O extensibility examples](https://github.com/microsoft/Dynamics-365-FastTrack-Implementation-Assets/blob/master/Integration/Field%20service%20integration%20with%20F%26O/readme.md) | - -### Sample OData Console Application -- [OData Console sample](https://github.com/microsoft/Dynamics-365-FastTrack-Implementation-Assets/tree/master/Integration/ODataCoreConsoleApp). From 20a341fd93cbbcbe1d329cc7026745fe182bfbc2 Mon Sep 17 00:00:00 2001 From: Ali Adamu Date: Tue, 14 Oct 2025 11:56:47 +0200 Subject: [PATCH 16/18] general README update --- Integration/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Integration/README.md b/Integration/README.md index f3703e37..7a53d3e8 100644 --- a/Integration/README.md +++ b/Integration/README.md @@ -20,7 +20,7 @@ This implemetation asset provides sample integration code for Dynamics 365. |-------------|-------------| | `README.md` | This README file. | | `ODataCoreConsoleApp.sln` | Visual Studio 2022 solution file for the sample OData console application. | -| `ODataCoreConsoleApp` | Sample F&O OData client console application written to illustrate the use of .Net Core and the OData Connected Service in a client application. | -| `MessageProcessorConsoleApp.sln` | Visual Studio 2022 solution file for the sample Message Processor console application. [ODataCoreConsoleApp](https://github.com/microsoft/Dynamics-365-FastTrack-Implementation-Assets/tree/master/Integration/ODataCoreConsoleApp) | -| `MessageProcessorConsoleApp` | Message Processor console application written to illustrate the use of .Net to interact with Dynamics 365 F&O Message processor framework. [MessageProcessorConsoleApp](https://github.com/microsoft/Dynamics-365-FastTrack-Implementation-Assets/tree/master/Integration/MessageProcessorConsoleApp) | +| `ODataCoreConsoleApp` | [The OData Core Console App](https://github.com/microsoft/Dynamics-365-FastTrack-Implementation-Assets/tree/master/Integration/ODataCoreConsoleApp) is a sample F&O OData client console application written to illustrate the use of .Net Core and the OData Connected Service in a client application.| +| `MessageProcessorConsoleApp.sln` | Visual Studio 2022 solution file for the sample Message Processor console application. | +| `MessageProcessorConsoleApp` | [The Message Processor Console App](https://github.com/microsoft/Dynamics-365-FastTrack-Implementation-Assets/tree/master/Integration/MessageProcessorConsoleApp) is a message Processor console application written to illustrate the use of .Net to interact with Dynamics 365 F&O Message processor framework.| | `Field Service integration with F&O` | [Field Service integration with F&O extensibility examples](https://github.com/microsoft/Dynamics-365-FastTrack-Implementation-Assets/blob/master/Integration/Field%20service%20integration%20with%20F%26O/readme.md) | From 5d98f6b093850e56e190091264a0ec798c4285aa Mon Sep 17 00:00:00 2001 From: Ali Adamu Date: Thu, 20 Nov 2025 12:34:24 +0100 Subject: [PATCH 17/18] Initial commit for Latency Monitoring tools for Fabric Link --- .../FabricLinkDataStaleness.ipynb | 1 + ...abricLinkDataStaleness_Summarization.ipynb | 1 + .../Staleness analysis pipeline.zip | Bin 0 -> 41333 bytes .../Staleness report Fabric Link.pbix | Bin 0 -> 132197 bytes .../DataverseLink/LatencyMonitoring/readme.md | 58 ++++++++++++++++++ 5 files changed, 60 insertions(+) create mode 100644 Analytics/DataverseLink/LatencyMonitoring/FabricLinkDataStaleness.ipynb create mode 100644 Analytics/DataverseLink/LatencyMonitoring/FabricLinkDataStaleness_Summarization.ipynb create mode 100644 Analytics/DataverseLink/LatencyMonitoring/Staleness analysis pipeline.zip create mode 100644 Analytics/DataverseLink/LatencyMonitoring/Staleness report Fabric Link.pbix create mode 100644 Analytics/DataverseLink/LatencyMonitoring/readme.md diff --git a/Analytics/DataverseLink/LatencyMonitoring/FabricLinkDataStaleness.ipynb b/Analytics/DataverseLink/LatencyMonitoring/FabricLinkDataStaleness.ipynb new file mode 100644 index 00000000..4fc91d9c --- /dev/null +++ b/Analytics/DataverseLink/LatencyMonitoring/FabricLinkDataStaleness.ipynb @@ -0,0 +1 @@ +{"cells":[{"cell_type":"code","source":["# Parameters\n","tables_list = \"salestable,salesline\"\n","source_workspace_id = \"6b887591-99a7-4544-a8c4-80d4a784d7b1\"\n","source_lakehouse_name = 'Contoso_D365_LH'\n","hours_back = 2\n","target_table_name = \"cdf_staleness_table\"\n","versions_bucket_size = 5\n","display_sample = False\n","# If target_table_base_path is empty, the standard tables' path will be used\n","target_table_base_path = \"\""],"outputs":[{"output_type":"display_data","data":{"application/vnd.jupyter.statement-meta+json":{"session_id":"78bf0436-5490-4025-a3fc-ef9b17d864e5","normalized_state":"finished","queued_time":"2025-10-22T16:11:04.4044278Z","session_start_time":"2025-10-22T16:11:04.4053081Z","execution_start_time":"2025-10-22T16:11:08.7330132Z","execution_finish_time":"2025-10-22T16:11:10.9632492Z","parent_msg_id":"46f67a4a-9f01-42ed-9744-0dc8fcfc1098"}},"metadata":{}}],"execution_count":1,"metadata":{"microsoft":{"language":"python","language_group":"jupyter_python"},"tags":["parameters"]},"id":"369f3876-6c8d-4345-8eff-a771e99d6bf4"},{"cell_type":"code","source":["from deltalake import DeltaTable\n","from pyspark.sql.functions import col\n","from datetime import datetime, timedelta\n","import json\n","import pyarrow as pa\n","import pandas as pd\n","import numpy as np\n","\n","import re\n","\n","\n","\n","# ---------------- Helpers ----------------\n","\n","def deduplicate_lakehouse_table_python(\n"," input_path: str,\n"," \n","):\n"," from deltalake import write_deltalake\n"," \n"," order_cols=(\"first_SinkCreatedOn\", \"commit_version\")\n"," key_cols=(\"recid\", \"versionnumber\")\n"," \n"," # Read all Parquet files into a single DataFrame\n"," print(\"Reading DeltaTable files...\")\n"," df = DeltaTable(input_path).to_pandas()\n"," \n"," print(f\"Loaded {len(df):,} rows.\")\n"," \n"," # Deduplicate\n"," print(\"Deduplicating...\")\n"," df = df.sort_values(list(order_cols), ascending=[True]*len(order_cols))\n"," dedup_df = df.drop_duplicates(subset=list(key_cols), keep=\"first\")\n"," \n"," print(f\"Deduplicated: {len(dedup_df):,} rows remain (removed {len(df)-len(dedup_df):,}).\")\n"," \n"," # Overwrite original folder with deduplicated data\n"," print(\"Writing back...\")\n"," dedup_df = dedup_df.reset_index(drop=True)\n"," write_deltalake(\n"," target_path,\n"," dedup_df, \n"," mode=\"overwrite\",\n"," schema_mode = \"overwrite\",\n"," partition_by=[\"Table_Name\"], \n"," )\n","\n"," \n"," print(f\"Deduplication complete. Table updated at: {input_path}\")\n"," return len(dedup_df)\n","\n","def getTargetPath(target_table, target_table_base_path = \"\"):\n"," \n"," if target_table_base_path == \"\":\n"," tables_now = notebookutils.lakehouse.listTables(source_lakehouse_name, source_workspace_id)\n"," exists = any(t[\"name\"] == target_table_name for t in tables_now)\n"," # Derive the Lakehouse \"Tables\" base path from any existing table location\n"," # Example existing location:\n"," # abfss://@onelake.dfs.fabric.microsoft.com//Tables/\n"," if tables_now:\n"," any_loc = tables_now[0][\"location\"]\n"," elif filtered_tables:\n"," any_loc = filtered_tables[0][\"location\"]\n"," else:\n"," raise RuntimeError(\"Cannot infer Lakehouse Tables path (no tables found).\")\n"," \n"," tables_base = any_loc.rsplit(\"/\", 1)[0] # strip the trailing table name\n"," target_path = f\"{tables_base}/{target_table_name}\" # .../Tables/cdf_staleness_table\n"," else:\n"," target_path = f\"{target_table_base_path}/{target_table_name}\"\n"," return target_path\n","\n","def writeinthelake(partial_result, target_table, target_path, mode=\"overwrite\",):\n"," from deltalake import write_deltalake\n","\n"," target_table = DeltaTable(target_path).to_pandas()\n"," \n"," write_deltalake(\n"," target_path,\n"," partial_result, \n"," mode=mode,\n"," schema_mode = \"overwrite\",\n"," partition_by=[\"Table_Name\"], \n"," )\n"," return len(partial_result)\n"," \n","\n","\n","def to_datetime_ms_or_parse(series):\n"," \"\"\"\n"," Convert epoch-ms ints/strings or ISO strings to UTC datetimes. Treat 0 as missing.\n"," Works with Arrow-backed dtypes too.\n"," \"\"\"\n"," s = series\n"," if pd.api.types.is_datetime64_any_dtype(s):\n"," return pd.to_datetime(s, errors=\"coerce\", utc=True)\n","\n"," pa_dtype = getattr(s.dtype, \"pyarrow_dtype\", None)\n"," is_arrow_int = pa_dtype is not None and pa.types.is_integer(pa_dtype)\n"," is_arrow_ts = pa_dtype is not None and pa.types.is_timestamp(pa_dtype)\n","\n"," if is_arrow_ts:\n"," return pd.to_datetime(s, errors=\"coerce\", utc=True)\n","\n"," if is_arrow_int or pd.api.types.is_integer_dtype(s) or pd.api.types.is_float_dtype(s) or s.dtype == object:\n"," sn = pd.to_numeric(s, errors=\"coerce\")\n"," sn = sn.where(sn != 0, pd.NA) # treat 0 as missing\n"," return pd.to_datetime(sn, unit=\"ms\", errors=\"coerce\", utc=True)\n","\n"," return pd.to_datetime(s, errors=\"coerce\", utc=True)\n","\n","\n","# ---------------- Main function ----------------\n","\n","def compute_cdf_staleness(table_uri, starting_version=0, ending_version = 0, storage_options=None):\n"," \n"," dt = DeltaTable(table_uri, storage_options=storage_options or {})\n"," tables = []\n"," \n"," cdf_iter = dt.load_cdf(starting_version=starting_version, ending_version = ending_version)\n"," \n"," # No CDF iterator: return empty schema\n"," if cdf_iter is None:\n"," print(\"CDF is null\")\n"," return pd.DataFrame(columns=[\n"," \"Table_Name\", \"Id\", \"recid\", \"versionnumber\", \"Version_Staleness\",\n"," \"commit_version\", \n"," \"modifiedon\", \"createdon\",\n"," \"first_SinkCreatedOn\", \"last_SinkCreatedOn\",\n"," ])\n"," \n"," # Collect Arrow chunks\n"," for chunk in cdf_iter:\n"," if isinstance(chunk, pa.RecordBatch):\n"," tables.append(pa.Table.from_batches([chunk]))\n"," elif isinstance(chunk, pa.Table):\n"," tables.append(chunk)\n"," else:\n"," raise TypeError(f\"Unexpected CDF chunk type: {type(chunk)}\")\n"," \n"," #For debugging\n"," #print(f\"tables len: {len(tables)}\")\n","\n"," if not tables:\n"," print(\"No tables\")\n"," return pd.DataFrame(columns=[\n"," \"Table_Name\", \"Id\", \"recid\", \"versionnumber\", \"Version_Staleness\",\n"," \"commit_version\", \n"," \"modifiedon\", \"createdon\",\n"," \"first_SinkCreatedOn\", \"last_SinkCreatedOn\",\n"," ])\n","\n"," # Concat tables (new API first, fallback to old)\n"," try:\n"," cdf_tbl = pa.concat_tables(tables, promote_options=\"default\")\n"," except TypeError:\n"," cdf_tbl = pa.concat_tables(tables, promote=True)\n","\n"," df = cdf_tbl.to_pandas(types_mapper=pd.ArrowDtype)\n","\n"," #For debugging\n"," #print(f\"df len: {len(df)}\")\n","\n"," # Pick/normalize core CDF metadata cols\n"," def pick_col(opts, default=None):\n"," for c in opts:\n"," if c in df.columns:\n"," return c\n"," return default\n","\n"," change_col = pick_col([\"_change_type\", \"change_type\"])\n"," cver_col = pick_col([\"_commit_version\", \"commit_version\"])\n"," cts_col = pick_col([\"_commit_timestamp\", \"commit_timestamp\"])\n"," if not change_col or not cver_col or not cts_col:\n"," missing = [x for x, v in {\n"," \"change_type/_change_type\": change_col,\n"," \"commit_version/_commit_version\": cver_col,\n"," \"commit_timestamp/_commit_timestamp\": cts_col,\n"," }.items() if not v]\n"," raise ValueError(f\"CDF columns not found: {missing}. Ensure CDF is enabled/retained.\")\n","\n"," df = df.rename(columns={\n"," change_col: \"change_type\",\n"," cver_col: \"commit_version\",\n"," cts_col: \"commit_timestamp\",\n"," })\n","\n"," # Domain columns\n"," if \"Id\" not in df.columns and \"id\" in df.columns:\n"," df = df.rename(columns={\"id\": \"Id\"})\n"," if \"versionnumber\" not in df.columns and \"VersionNumber\" in df.columns:\n"," df = df.rename(columns={\"VersionNumber\": \"versionnumber\"})\n","\n"," if \"recid\" not in df.columns:\n"," for alt in [\"recId\", \"RecId\", \"RECID\"]:\n"," if alt in df.columns:\n"," df = df.rename(columns={alt: \"recid\"})\n"," break\n"," if \"recid\" not in df.columns:\n"," df[\"recid\"] = 0\n","\n"," sink_col = pick_col([\"SinkCreatedOn\", \"SyncCreatedOn\", \"synccreatedon\", \"sinkcreatedon\"])\n"," if sink_col and sink_col != \"SinkCreatedOn\":\n"," df = df.rename(columns={sink_col: \"SinkCreatedOn\"})\n"," if \"SinkCreatedOn\" not in df.columns:\n"," df[\"SinkCreatedOn\"] = pd.NaT\n","\n"," if \"modifiedon\" not in df.columns:\n"," df[\"modifiedon\"] = pd.NaT\n","\n"," if \"createdon\" not in df.columns:\n"," df[\"createdon\"] = pd.NaT\n","\n"," # Required\n"," required = [\"Id\", \"versionnumber\", \"commit_version\", \"recid\"]\n"," miss = [c for c in required if c not in df.columns]\n"," if miss:\n"," raise ValueError(f\"Missing required columns: {miss}\")\n"," \n","\n"," # Dtypes & timestamps\n"," df[\"commit_version\"] = pd.to_numeric(df[\"commit_version\"], errors=\"coerce\")\n"," \n","\n"," # Stable order\n"," df = df.sort_values([\"Id\", \"versionnumber\", \"commit_version\", \"commit_timestamp\"])\n","\n"," # Version birth per (Id, versionnumber)\n"," v_birth = (\n"," df[df[\"change_type\"].isin([\"insert\", \"update_postimage\"])]\n"," .sort_values([\"commit_version\", \"commit_timestamp\"])\n"," .groupby([\"Id\", \"versionnumber\"], as_index=False)\n"," .head(1)\n"," .loc[:, [\"Id\", \"versionnumber\", \"recid\", \"commit_version\", \"SinkCreatedOn\", \"modifieddatetime\", \"createddatetime\"]]\n"," .rename(columns={\n"," \"SinkCreatedOn\": \"first_SinkCreatedOn\",\n"," \"modifieddatetime\": \"modifiedon\",\n"," \"createddatetime\": \"createdon\",\n"," })\n"," )\n","\n"," #For debugging\n"," # print(f\"v_birth len: {len(v_birth)}\")\n","\n"," v_last = (\n"," df[df[\"change_type\"].isin([\"insert\", \"update_postimage\"])]\n"," .sort_values([\"commit_version\", \"commit_timestamp\"], ascending=False)\n"," .groupby([\"Id\", \"versionnumber\"], as_index=False)\n"," .head(1)\n"," .loc[:, [\"Id\", \"versionnumber\", \"recid\", \"SinkCreatedOn\"]]\n"," .rename(columns={\n"," \"SinkCreatedOn\": \"last_SinkCreatedOn\",\n"," })\n"," )\n","\n"," out = (\n"," v_birth\n"," .merge(v_last, on=[\"Id\", \"versionnumber\", \"recid\"], how=\"left\")\n"," .loc[:, [\n"," \"Id\", \"versionnumber\", \"recid\",\n"," \"commit_version\", \n"," \"modifiedon\", \"createdon\",\n"," \"first_SinkCreatedOn\", \"last_SinkCreatedOn\",\n"," ]]\n"," .sort_values([\"Id\", \"versionnumber\"])\n"," .reset_index(drop=True)\n"," )\n"," \n","\n"," \n"," # Enforce datetimes; treat epoch 0 as missing\n"," date_cols = [\n"," \"modifiedon\",\n"," \"createdon\",\n"," \"first_SinkCreatedOn\",\n"," \"last_SinkCreatedOn\",\n"," ]\n"," for c in date_cols:\n"," out[c] = to_datetime_ms_or_parse(out[c])\n"," out[c] = out[c].where(out[c] != pd.Timestamp(0, tz=\"UTC\"), pd.NaT)\n","\n"," \n","\n"," out = out.assign(Version_Staleness = out[\"first_SinkCreatedOn\"] - out[\"modifiedon\"].combine(out[\"createdon\"], func=max) )\n"," out = out.assign(Table_Name = table_uri.rsplit(\"/\", 1)[-1] )\n","\n"," \n"," # Compute timedelta\n"," \n"," fs_col = \"first_SinkCreatedOn\"\n"," lm_col = \"modifiedon\"\n"," \n","\n"," # Treat epoch-zero (1970-01-01) as missing; build a mask for missing/zero last_modified\n"," \n","\n"," zero_dt = pd.Timestamp(0, tz=\"UTC\")\n"," sent_1900 = pd.Timestamp(\"1900-01-01T00:00:00Z\")\n","\n"," mask_missing_last = out[lm_col].isna() | out[lm_col].eq(zero_dt) | out[lm_col].eq(sent_1900)\n","\n"," # 3) Compute timedelta; force staleness to 0 where last_modified is \"null date\"\n"," delta = out[fs_col] - out[lm_col]\n"," delta = delta.where(~mask_missing_last, pd.Timedelta(0))\n","\n","\n","\n"," # Build sign and absolute components\n"," sign = np.where(delta.notna() & (delta < pd.Timedelta(0)), \"-\", \"\")\n"," abs_delta = delta.abs()\n"," \n","\n"," # Days, hours, minutes, seconds (hours/minutes/seconds are within the day)\n"," days = abs_delta.dt.days\n"," hours = (abs_delta.dt.seconds // 3600)\n"," minutes = (abs_delta.dt.seconds % 3600) // 60\n"," seconds = abs_delta.dt.seconds % 60\n"," \n","\n","\n"," # Format dd.HH:mm:ss (zero-padded), keep NaN/NaT as \n"," out[\"Version_Staleness\"] = np.where(\n"," delta.notna(),\n"," sign\n"," + days.astype(\"Int64\").astype(str).str.zfill(2) + \".\"\n"," + hours.astype(\"Int64\").astype(str).str.zfill(2) + \":\"\n"," + minutes.astype(\"Int64\").astype(str).str.zfill(2) + \":\"\n"," + seconds.astype(\"Int64\").astype(str).str.zfill(2),\n"," pd.NA\n"," )\n"," \n"," #reorder cols\n"," cols = [\n"," \"Table_Name\", \"Id\", \"recid\", \"versionnumber\", \"Version_Staleness\",\n"," \"commit_version\", \n"," \"modifiedon\", \"createdon\",\n"," \"first_SinkCreatedOn\", \"last_SinkCreatedOn\",\n"," ]\n"," out = out[cols]\n"," out.sort_values([\"Table_Name\",\"Id\", \"versionnumber\"]).reset_index(drop=True)\n"," return out\n","\n","\n","# ---------------- Driver ----------------\n","\n","allowed_tables = tables_list.split(\",\")\n","\n","artifacts_tables_list = notebookutils.lakehouse.listTables(source_lakehouse_name, source_workspace_id)\n","filtered_tables = [t for t in artifacts_tables_list if t['name'] in allowed_tables]\n","\n","#filtered_tables = artifacts_tables_list.copy() #all tables\n","first = True\n","NumOfLines = 0\n","\n","target_path = getTargetPath(target_table_name, target_table_base_path )\n","\n","# Calculate timestamp\n","target_time = datetime.now() - timedelta(hours=hours_back)\n","print(f\"Process start - target changes after {target_time}\")\n","for table in filtered_tables:\n"," tablename = table['name']\n"," location = table['location']\n"," print(\"Running \" , tablename)\n"," dt = DeltaTable(location)\n"," history = dt.history()\n"," \n"," #filter History\n"," #history =[row for row in history if row.get(\"operation\") in {\"VacuumEnd\", \"VacuumStart\"}]\n"," #show history\n"," #display(history)\n"," last_version = history[0][\"version\"]\n"," print(f\"Last version: {last_version}\")\n"," \n"," matching_versions = []\n"," #There is no timestamp so we must use the LastVacuumTimestamp\n"," for entry in history:\n"," commit_info = entry.get(\"additionalCommitInfo\")\n"," if not commit_info:\n"," continue\n","\n"," # Parse JSON string to dict\n"," try:\n"," info_dict = json.loads(commit_info)\n"," vacuum_ts_str = info_dict.get(\"LastVacuumTimestamp\")\n"," if vacuum_ts_str:\n"," # Convert string to datetime\n"," vacuum_ts = datetime.strptime(vacuum_ts_str, \"%m/%d/%Y %I:%M:%S %p\")\n"," if vacuum_ts >= target_time:\n"," matching_versions.append((entry[\"version\"], vacuum_ts))\n"," except Exception as e:\n"," print(f\"Error parsing entry: {e}\")\n"," \n"," # Sort and get the latest version before the target time\n"," if matching_versions:\n"," matching_versions.sort(key=lambda x: x[0], reverse=False)\n"," start_version = matching_versions[0][0]\n"," print(f\"First version from target_time based on LastVacuumTimestamp: {start_version} {matching_versions[0][1]}\")\n"," else:\n"," print(f\"No matching version found after the target time {target_time}.\")\n"," print(\"Table not processed: \", tablename)\n"," continue\n"," \n","\n"," print(f\"Start version {start_version} - Last Version {last_version}\")\n"," ending_version = last_version\n"," if ending_version > (start_version + versions_bucket_size):\n"," ending_version = start_version + versions_bucket_size\n"," table_completed = False\n"," while table_completed == False:\n"," if ending_version >= last_version:\n"," ending_version = last_version\n"," table_completed = True\n"," result = compute_cdf_staleness(table_uri=location, starting_version=start_version, ending_version = ending_version)\n"," start_version = ending_version + 1\n"," ending_version = ending_version + versions_bucket_size\n"," \n"," if(len(result) == 0): continue\n","\n"," # Columns you want to show as human-readable strings\n"," date_cols = [\n"," \"modifiedon\", \"createdon\",\n"," \"first_SinkCreatedOn\", \"last_SinkCreatedOn\",\n"," ]\n","\n"," pretty = result.copy()\n"," \n"," # Show in UTC (ISO-ish)\n"," for c in date_cols:\n"," if c in pretty.columns and pd.api.types.is_datetime64_any_dtype(pretty[c]):\n"," pretty[c] = pretty[c].dt.strftime(\"%Y-%m-%d %H:%M:%S%z\")\n"," \n"," #Write in the table cdf_staleness_table\n"," NumOfLines += writeinthelake(pretty.copy(), target_table_name, target_path, \"append\") \n"," if display_sample:\n"," if(first): \n"," totalResult = pretty.head(1)\n"," else: \n"," totalResult = pd.concat([totalResult, pretty.head(1)]).reset_index(drop=True)\n"," first = False\n"," print(f\"Table {tablename} processed up to version {start_version - 1} (completed={table_completed})\")\n","\n","if(first == False):\n"," dedup_len = deduplicate_lakehouse_table_python(input_path=target_path)\n"," print(f\"{NumOfLines} rows created in cdf_staleness_table. Current tables records: {dedup_len}\")\n","else : print (\"No rows creted\")\n","\n","if display_sample and NumOfLines > 0 :\n"," try:\n"," display(totalResult)\n"," except NameError:\n"," print(\"totalResult is empty, no rows creted\")\n","\n","\n","\n","\n"],"outputs":[{"output_type":"display_data","data":{"application/vnd.jupyter.statement-meta+json":{"session_id":"78bf0436-5490-4025-a3fc-ef9b17d864e5","normalized_state":"finished","queued_time":"2025-10-22T16:11:04.4097607Z","session_start_time":null,"execution_start_time":"2025-10-22T16:11:10.9646568Z","execution_finish_time":"2025-10-22T16:11:44.997708Z","parent_msg_id":"acbbb162-c882-448a-8c88-f865f93ed7a4"}},"metadata":{}},{"output_type":"stream","name":"stdout","text":["Process start - target changes after 2025-10-18 12:11:30.051644\nRunning salestable\nLast version: 312\nFirst version from target_time based on LastVacuumTimestamp: 312 2025-10-21 09:21:35\nStart version 312 - Last Version 312\nnot tables\nRunning salesline\nLast version: 371\nFirst version from target_time based on LastVacuumTimestamp: 370 2025-10-21 09:21:33\nStart version 370 - Last Version 371\nTable salesline processed up to version 371 (completed=True)\nReading DeltaTable files...\nLoaded 10 rows.\nDeduplicating...\nDeduplicated: 5 rows remain (removed 5).\nWriting back...\nDeduplication complete. Table updated at: abfss://6b887591-99a7-4544-a8c4-80d4a784d7b1@onelake.dfs.fabric.microsoft.com/bca1ccdf-fa53-48c9-ac82-31536fe18832/Tables/cdf_staleness_table\n5 rows created in cdf_staleness_table. Current tables records: 5\n"]}],"execution_count":2,"metadata":{"jupyter":{"source_hidden":false,"outputs_hidden":false},"nteract":{"transient":{"deleting":false}},"microsoft":{"language":"python","language_group":"jupyter_python"},"tags":[]},"id":"e08a59b5-deba-469f-b80a-add19a54b8f6"}],"metadata":{"kernel_info":{"name":"jupyter","jupyter_kernel_name":"python3.11"},"kernelspec":{"name":"jupyter","language":"Jupyter","display_name":"Jupyter"},"language_info":{"name":"python"},"microsoft":{"language":"python","language_group":"jupyter_python","ms_spell_check":{"ms_spell_check_language":"en"}},"nteract":{"version":"nteract-front-end@1.0.0"},"spark_compute":{"compute_id":"/trident/default","session_options":{"conf":{"spark.synapse.nbs.session.timeout":"1200000"}}},"dependencies":{"lakehouse":{"known_lakehouses":[{"id":"bca1ccdf-fa53-48c9-ac82-31536fe18832"}],"default_lakehouse":"bca1ccdf-fa53-48c9-ac82-31536fe18832","default_lakehouse_name":"Contoso_D365_LH","default_lakehouse_workspace_id":"6b887591-99a7-4544-a8c4-80d4a784d7b1"}}},"nbformat":4,"nbformat_minor":5} \ No newline at end of file diff --git a/Analytics/DataverseLink/LatencyMonitoring/FabricLinkDataStaleness_Summarization.ipynb b/Analytics/DataverseLink/LatencyMonitoring/FabricLinkDataStaleness_Summarization.ipynb new file mode 100644 index 00000000..62166dc7 --- /dev/null +++ b/Analytics/DataverseLink/LatencyMonitoring/FabricLinkDataStaleness_Summarization.ipynb @@ -0,0 +1 @@ +{"cells":[{"cell_type":"code","source":["# ========= Parameters =========\n","lookback_hours = 3 # process rows with first_SinkCreatedOn < now - lookback_hours\n","grouping_time_zone = \"UTC\" # keep UTC or change\n","source_table = \"cdf_staleness_table\"\n","target_table = \"cdf_staleness_table_summarized\"\n","# =============================="],"outputs":[{"output_type":"display_data","data":{"application/vnd.livy.statement-meta+json":{"spark_pool":null,"statement_id":5,"statement_ids":[5],"state":"finished","livy_statement_state":"available","session_id":"5303a7df-58c4-47b4-878a-34385b0f8404","normalized_state":"finished","queued_time":"2025-10-22T16:22:47.4374596Z","session_start_time":null,"execution_start_time":"2025-10-22T16:22:47.4386429Z","execution_finish_time":"2025-10-22T16:22:47.8247882Z","parent_msg_id":"21be79dd-c90c-43c9-9114-7514a67284f9"},"text/plain":"StatementMeta(, 5303a7df-58c4-47b4-878a-34385b0f8404, 5, Finished, Available, Finished)"},"metadata":{}}],"execution_count":null,"metadata":{"jupyter":{"source_hidden":false,"outputs_hidden":false},"nteract":{"transient":{"deleting":false}},"microsoft":{"language":"python","language_group":"synapse_pyspark"},"tags":["parameters"]},"id":"c1bf6326-9e2a-4a38-ac96-6dc1c25005cf"},{"cell_type":"code","source":["from pyspark.sql import functions as F\n","from pyspark.sql import types as T\n","\n","\n","# ------------ Safety checks ------------\n","if not spark.catalog.tableExists(source_table):\n"," raise RuntimeError(f\"Source table '{source_table}' does not exist in the current Lakehouse.\")\n","\n","if lookback_hours < 1:\n"," raise RuntimeError(f\"Lookback hours paramenter must be greather than 1. Current value: {lookback_hours}\")\n","\n","# Ensure target exists with desired schema (keys + measures). If not, create empty.\n","spark.sql(f\"\"\"\n"," CREATE TABLE IF NOT EXISTS {target_table} (\n"," TableName STRING,\n"," HourOfDay INT,\n"," Date STRING,\n"," StalenessAvg DOUBLE,\n"," StalenessMin DOUBLE,\n"," StalenessMax DOUBLE,\n"," Staleness05_00to05min BIGINT,\n"," Staleness10_05to10min BIGINT,\n"," Staleness15_10to15min BIGINT,\n"," Staleness20_15to20min BIGINT,\n"," Staleness25_20to25min BIGINT,\n"," Staleness30_25to30min BIGINT,\n"," Staleness60_30to60min BIGINT,\n"," StanelessO6_Over60 BIGINT\n"," ) USING delta\n","\"\"\")\n","\n","# ------------ Filter source by lookback ------------\n","lookback_cutoff_expr = F.expr(f\"current_timestamp() - INTERVAL {lookback_hours} HOURS\")\n","src = (\n"," spark.table(source_table)\n"," .filter(F.col(\"first_SinkCreatedOn\") < lookback_cutoff_expr)\n"," .withColumn(\"first_SinkCreatedOn\", F.col(\"first_SinkCreatedOn\").cast(\"timestamp\"))\n",")\n","\n","# If nothing to process, exit early\n","if src.rdd.isEmpty():\n"," print(f\"Nothing to process: no rows with first_SinkCreatedOn older than {lookback_hours} hour(s).\")\n","else:\n"," # ------------ Parse Version_Staleness: 'd.hh:mm:ss' -> total minutes (double) ------------\n"," # Robust to either 'd.hh:mm:ss' or 'hh:mm:ss' (treat missing days as 0)\n"," vs_str = F.col(\"Version_Staleness\").cast(\"string\")\n"," dot_split = F.split(vs_str, \"\\\\.\") # [days, hh:mm:ss] OR [hh:mm:ss]\n"," has_day = (F.size(dot_split) == F.lit(2))\n"," days = F.when(vs_str.isNull(), F.lit(None).cast(\"int\")) \\\n"," .otherwise(F.when(has_day, dot_split.getItem(0).cast(\"int\")).otherwise(F.lit(0)))\n","\n"," time_part = F.when(has_day, dot_split.getItem(1)).otherwise(dot_split.getItem(0))\n"," t_split = F.split(time_part, \":\") # [hh, mm, ss]\n","\n"," hours = F.when(vs_str.isNull(), F.lit(None).cast(\"int\")).otherwise(t_split.getItem(0).cast(\"int\"))\n"," minutes = F.when(vs_str.isNull(), F.lit(None).cast(\"int\")).otherwise(t_split.getItem(1).cast(\"int\"))\n"," seconds = F.when(vs_str.isNull(), F.lit(None).cast(\"int\")).otherwise(t_split.getItem(2).cast(\"int\"))\n","\n"," total_minutes = (\n"," days.cast(\"double\") * 1440.0 + # days -> minutes\n"," hours.cast(\"double\") * 60.0 +\n"," minutes.cast(\"double\") +\n"," seconds.cast(\"double\") / 60.0\n"," )\n","\n"," src = src.withColumn(\"Version_Staleness_Minutes\", total_minutes)\n","\n"," # ------------ Derive grouping keys (UTC) ------------\n"," # Convert from UTC-to-UTC (no-op) but consistent; use this column for Date/HourOfDay\n"," ts_group = F.from_utc_timestamp(F.col(\"first_SinkCreatedOn\"), grouping_time_zone)\n","\n"," df = (\n"," src\n"," .withColumn(\"TableName\", F.col(\"Table_Name\"))\n"," .withColumn(\"ts_group\", ts_group)\n"," .withColumn(\"HourOfDay\", F.hour(\"ts_group\"))\n"," .withColumn(\"Date\", F.date_format(F.to_date(\"ts_group\"), \"yyyy-MM-dd\"))\n"," )\n","\n"," st_mins = F.col(\"Version_Staleness_Minutes\").cast(\"double\")\n","\n"," # ------------ Bucket counts ------------\n"," c_00_05 = F.sum(F.when(st_mins <= 5, 1).otherwise(0)).cast(\"bigint\")\n"," c_05_10 = F.sum(F.when((st_mins > 5) & (st_mins <= 10), 1).otherwise(0)).cast(\"bigint\")\n"," c_10_15 = F.sum(F.when((st_mins > 10) & (st_mins <= 15), 1).otherwise(0)).cast(\"bigint\")\n"," c_15_20 = F.sum(F.when((st_mins > 15) & (st_mins <= 20), 1).otherwise(0)).cast(\"bigint\")\n"," c_20_25 = F.sum(F.when((st_mins > 20) & (st_mins <= 25), 1).otherwise(0)).cast(\"bigint\")\n"," c_25_30 = F.sum(F.when((st_mins > 25) & (st_mins <= 30), 1).otherwise(0)).cast(\"bigint\")\n"," c_30_60 = F.sum(F.when((st_mins > 30) & (st_mins <= 60), 1).otherwise(0)).cast(\"bigint\")\n"," c_o60 = F.sum(F.when((st_mins > 60), 1).otherwise(0)).cast(\"bigint\")\n","\n"," agg = (\n"," df.groupBy(\"TableName\", \"HourOfDay\", \"Date\")\n"," .agg(\n"," F.avg(st_mins).alias(\"StalenessAvg\"),\n"," F.min(st_mins).alias(\"StalenessMin\"),\n"," F.max(st_mins).alias(\"StalenessMax\"),\n"," c_00_05.alias(\"Staleness05_00to05min\"),\n"," c_05_10.alias(\"Staleness10_05to10min\"),\n"," c_10_15.alias(\"Staleness15_10to15min\"),\n"," c_15_20.alias(\"Staleness20_15to20min\"),\n"," c_20_25.alias(\"Staleness25_20to25min\"),\n"," c_25_30.alias(\"Staleness30_25to30min\"),\n"," c_30_60.alias(\"Staleness60_30to60min\"),\n"," c_o60.alias(\"StanelessO6_Over60\"),\n"," )\n"," )\n","\n"," # ------------ Upsert into target (override by composite key) ------------\n"," agg.createOrReplaceTempView(\"agg_staleness_summary_tmp\")\n","\n"," spark.sql(f\"\"\"\n"," MERGE INTO {target_table} AS tgt\n"," USING agg_staleness_summary_tmp AS src\n"," ON tgt.TableName = src.TableName\n"," AND tgt.HourOfDay = src.HourOfDay\n"," AND tgt.Date = src.Date\n"," WHEN MATCHED THEN UPDATE SET\n"," tgt.StalenessAvg = src.StalenessAvg,\n"," tgt.StalenessMin = src.StalenessMin,\n"," tgt.StalenessMax = src.StalenessMax,\n"," tgt.Staleness05_00to05min = src.Staleness05_00to05min,\n"," tgt.Staleness10_05to10min = src.Staleness10_05to10min,\n"," tgt.Staleness15_10to15min = src.Staleness15_10to15min,\n"," tgt.Staleness20_15to20min = src.Staleness20_15to20min,\n"," tgt.Staleness25_20to25min = src.Staleness25_20to25min,\n"," tgt.Staleness30_25to30min = src.Staleness30_25to30min,\n"," tgt.Staleness60_30to60min = src.Staleness60_30to60min,\n"," tgt.StanelessO6_Over60 = src.StanelessO6_Over60\n"," WHEN NOT MATCHED THEN INSERT (\n"," TableName, HourOfDay, Date,\n"," StalenessAvg, StalenessMin, StalenessMax,\n"," Staleness05_00to05min, Staleness10_05to10min, Staleness15_10to15min,\n"," Staleness20_15to20min, Staleness25_20to25min, Staleness30_25to30min, \n"," Staleness60_30to60min, StanelessO6_Over60\n"," ) VALUES (\n"," src.TableName, src.HourOfDay, src.Date,\n"," src.StalenessAvg, src.StalenessMin, src.StalenessMax,\n"," src.Staleness05_00to05min, src.Staleness10_05to10min, src.Staleness15_10to15min,\n"," src.Staleness20_15to20min, src.Staleness25_20to25min, src.Staleness30_25to30min, \n"," src.Staleness60_30to60min, src.StanelessO6_Over60\n"," )\n"," \"\"\")\n","\n"," # ------------ Remove processed source rows ------------\n"," spark.sql(f\"\"\"\n"," DELETE FROM {source_table}\n"," WHERE first_SinkCreatedOn < (current_timestamp() - INTERVAL {lookback_hours} HOURS)\n"," \"\"\")\n","\n"," print(f\"Summarization complete (UTC). Processed & removed rows older than {lookback_hours} hour(s).\")\n","\n"],"outputs":[{"output_type":"display_data","data":{"application/vnd.livy.statement-meta+json":{"spark_pool":null,"statement_id":6,"statement_ids":[6],"state":"finished","livy_statement_state":"available","session_id":"5303a7df-58c4-47b4-878a-34385b0f8404","normalized_state":"finished","queued_time":"2025-10-22T16:22:47.5483141Z","session_start_time":null,"execution_start_time":"2025-10-22T16:22:47.827003Z","execution_finish_time":"2025-10-22T16:23:08.3397606Z","parent_msg_id":"f7a83a80-57c7-43c0-be6d-f7de9ccd0ac8"},"text/plain":"StatementMeta(, 5303a7df-58c4-47b4-878a-34385b0f8404, 6, Finished, Available, Finished)"},"metadata":{}},{"output_type":"stream","name":"stdout","text":["Summarization complete (UTC). Processed & removed rows older than 3 hour(s).\n"]}],"execution_count":null,"metadata":{"jupyter":{"source_hidden":false,"outputs_hidden":false},"nteract":{"transient":{"deleting":false}},"microsoft":{"language":"python","language_group":"synapse_pyspark"}},"id":"0c8a47d4-e58a-4b46-beff-bf10acda639a"}],"metadata":{"kernel_info":{"name":"synapse_pyspark"},"kernelspec":{"name":"synapse_pyspark","display_name":"synapse_pyspark"},"language_info":{"name":"python"},"microsoft":{"language":"python","language_group":"synapse_pyspark","ms_spell_check":{"ms_spell_check_language":"en"}},"nteract":{"version":"nteract-front-end@1.0.0"},"spark_compute":{"compute_id":"/trident/default","session_options":{"conf":{"spark.synapse.nbs.session.timeout":"1200000"}}},"dependencies":{"lakehouse":{"known_lakehouses":[{"id":"bca1ccdf-fa53-48c9-ac82-31536fe18832"}],"default_lakehouse":"bca1ccdf-fa53-48c9-ac82-31536fe18832","default_lakehouse_name":"Contoso_D365_LH","default_lakehouse_workspace_id":"6b887591-99a7-4544-a8c4-80d4a784d7b1"}}},"nbformat":4,"nbformat_minor":5} \ No newline at end of file diff --git a/Analytics/DataverseLink/LatencyMonitoring/Staleness analysis pipeline.zip b/Analytics/DataverseLink/LatencyMonitoring/Staleness analysis pipeline.zip new file mode 100644 index 0000000000000000000000000000000000000000..86b0b7a2d177c7d81e07372215c835502c9cb829 GIT binary patch literal 41333 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

)9I)W;tbM&RWUW;&FYdJ2 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 literal 0 HcmV?d00001 diff --git a/Analytics/DataverseLink/LatencyMonitoring/readme.md b/Analytics/DataverseLink/LatencyMonitoring/readme.md new file mode 100644 index 00000000..15254bf1 --- /dev/null +++ b/Analytics/DataverseLink/LatencyMonitoring/readme.md @@ -0,0 +1,58 @@ +# Fabric Link Staleness Report +This repository contaions the components to crete a stalenessreport base in the data extracted using Fabric Link. + +## Challenge +Many Dynamics 365 Fiance and SCM customers measure latency by simply comparing the value of **SinkModifiedOn** with **ModifiedDateTime** (or CretedDateTime). ​Problem: Fabric Link can sink multiple times the same RecId/RecVersion leaving only the last SinkModifiedOn. This leads to perceived latency that can be way higher than the real latency.​ +This solution allow to crete a report on top of Fabric, that provides accurate latency.​ + +## Solution Components +1. Table to store the actual staleness for RecId/RecVersion pairs (granular).​ +2. Table to store aggregated information (summarized).​ +3. Two scripts (Fabric Notebooks) to maintain the tables.​ +4. (opt) Pipeline to run the notebooks with a schedule.​ +5. (opt) Power BI dashboard to visualize the status.​ + +## Content +- **FabricLinkDataStaleness.ipynb**: the main notebook that generates the granular staleness data +- **FabricLinkDataStaleness_Summarization.ipynb**: the aggregation script that generates the summarized data and cleans up the ranualr table +- **Staleness analysis pipeline.zip**: contining the pileline to run the scripts in sequence +- **Staleness report Fabric Link.pbix**: simple example of Power BI report leveraging the generated data + +## Preprequisites +The script works only with tables that have modifieddatatime or creteddatetime activated. + +## Intructions +Download the contet and set up the two notebboks in fabric. +Import the two notebooks in Fabric and set up the needed paramentes. + +#### FabricLinkDataStaleness paramenters: +- **tables_list**: the comma-separated list of tables to analyze (*mandatory*) +- **source_workspace_id**: the id of the workspace that contains the tables (*mandatory*) +- **source_lakehouse_name**: the name of he lakehouse that conatins teh tables (*mandatory*) +- **hours_back**: how many hours back the script has to consider. This paraenter must be an integer greaterthan zero (*mandatory*) +- **target_table_name**: name of the table that will contain the granular staleness data (*mandatory*) +- **versions_bucket_size**: the script works in chunks, this paramenter decides how many versions must be considered for each chunk. It hepls redeuce the strain on the processig resources. It must be an integer greater than 2. (*mandatory*) +- **display_sample**: this paramenter is used for debugging purpuses to display samples of records in the console durin the runs +- **target_table_base_path**: use this paramenter to have the target table sitting in a different lake. If target_table_base_path is empty, the standard tables' path will be used (*optional*) + +#### FabricLinkDataStaleness_Summarization paramenters: +- **lookback_hours**: The granular rows are processed starting form *first_SinkCreatedOn < now - lookback_hours*. This paramenter must be an integer greather than 1 +- **grouping_time_zone**: Default timezone UTC +- **source_table**: Must be the same as *target_table_name* paramenter in the FabricLinkDataStaleness script +- **target_table**: The target summarization table *Staleness analysis pipeline.zip* + + +*(opt.)* Crete the pipeline using **Staleness analysis pipeline.zip**. In your workspace crete a new pipeline then import the zip file from the edit page. +Note that all the above paramenters can be changed in the pipeline in order to adapt to the specific needs. Make sure the pipeline's recurrence matcher the scripts paramentes *hours_back* and *lookback_hours* to avoid leaving ou soe changes. + +Use the Power BI report example or build your own to easily analyze the data. + +## Suggestions +1. Run the recurring pipeline​ to have a stable and autoamted flow of information +2. Limit the analysis to critical tables​. The latency analysis can be quite heavy depending on the change rate of tables +3. Make the summarization chase the granular analysis. For example: Granular analysis can process the last 3 hours while the summarization compresses everything before that. +4. If the update flow is massive, keep the granular level window small. There is hardly any value in having details for billions of records. +5. In general, adapt the parameters to your scenario.​ +6. Test with few tables and then increase as needed.​ +7. Use *versions_bucket_size* paramenter to reduce the CPU strain. + From a90b542e55ee5bb020736ddd612aa50acaf1cf22 Mon Sep 17 00:00:00 2001 From: Ali Adamu Date: Thu, 20 Nov 2025 12:53:46 +0100 Subject: [PATCH 18/18] readme.md corrections --- .../DataverseLink/LatencyMonitoring/readme.md | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/Analytics/DataverseLink/LatencyMonitoring/readme.md b/Analytics/DataverseLink/LatencyMonitoring/readme.md index 15254bf1..60714fcf 100644 --- a/Analytics/DataverseLink/LatencyMonitoring/readme.md +++ b/Analytics/DataverseLink/LatencyMonitoring/readme.md @@ -1,58 +1,58 @@ # Fabric Link Staleness Report -This repository contaions the components to crete a stalenessreport base in the data extracted using Fabric Link. +This repository contains the components to crete a staleness report based on the data extracted using Fabric Link. ## Challenge -Many Dynamics 365 Fiance and SCM customers measure latency by simply comparing the value of **SinkModifiedOn** with **ModifiedDateTime** (or CretedDateTime). ​Problem: Fabric Link can sink multiple times the same RecId/RecVersion leaving only the last SinkModifiedOn. This leads to perceived latency that can be way higher than the real latency.​ -This solution allow to crete a report on top of Fabric, that provides accurate latency.​ +Many Dynamics 365 Finance and SCM customers measure latency by simply comparing the value of **SinkModifiedOn** with **ModifiedDateTime** (or CretedDateTime). Problem: Fabric Link can sink multiple times the same RecId/RecVersion leaving only the last SinkModifiedOn. This leads to perceived latency that can be way higher than the real latency. +This solution allow to crete a report on top of Fabric, that provides accurate latency. ## Solution Components -1. Table to store the actual staleness for RecId/RecVersion pairs (granular).​ -2. Table to store aggregated information (summarized).​ -3. Two scripts (Fabric Notebooks) to maintain the tables.​ -4. (opt) Pipeline to run the notebooks with a schedule.​ -5. (opt) Power BI dashboard to visualize the status.​ +1. Table to store the actual staleness for RecId/RecVersion pairs (granular). +2. Table to store aggregated information (summarized). +3. Two scripts (Fabric Notebooks) to maintain the tables. +4. (opt) Pipeline to run the notebooks with a schedule. +5. (opt) Power BI dashboard to visualize the status. ## Content - **FabricLinkDataStaleness.ipynb**: the main notebook that generates the granular staleness data - **FabricLinkDataStaleness_Summarization.ipynb**: the aggregation script that generates the summarized data and cleans up the ranualr table -- **Staleness analysis pipeline.zip**: contining the pileline to run the scripts in sequence +- **Staleness analysis pipeline.zip**: containing the pipeline to run the scripts in sequence - **Staleness report Fabric Link.pbix**: simple example of Power BI report leveraging the generated data ## Preprequisites The script works only with tables that have modifieddatatime or creteddatetime activated. ## Intructions -Download the contet and set up the two notebboks in fabric. -Import the two notebooks in Fabric and set up the needed paramentes. +Download the content and set up the two notebooks in fabric. +Import the two notebooks in Fabric and set up the needed parameters. #### FabricLinkDataStaleness paramenters: - **tables_list**: the comma-separated list of tables to analyze (*mandatory*) - **source_workspace_id**: the id of the workspace that contains the tables (*mandatory*) -- **source_lakehouse_name**: the name of he lakehouse that conatins teh tables (*mandatory*) -- **hours_back**: how many hours back the script has to consider. This paraenter must be an integer greaterthan zero (*mandatory*) +- **source_lakehouse_name**: the name of the Lakehouse that contains the tables (*mandatory*) +- **hours_back**: how many hours back the script has to consider. This parameter must be an integer greaterthan zero (*mandatory*) - **target_table_name**: name of the table that will contain the granular staleness data (*mandatory*) -- **versions_bucket_size**: the script works in chunks, this paramenter decides how many versions must be considered for each chunk. It hepls redeuce the strain on the processig resources. It must be an integer greater than 2. (*mandatory*) -- **display_sample**: this paramenter is used for debugging purpuses to display samples of records in the console durin the runs -- **target_table_base_path**: use this paramenter to have the target table sitting in a different lake. If target_table_base_path is empty, the standard tables' path will be used (*optional*) +- **versions_bucket_size**: the script works in chunks, this parameter decides how many versions must be considered for each chunk. It helps reducing the strain on the processing resources. It must be an integer greater than 2. (*mandatory*) +- **display_sample**: this parameter is used for debugging purposes to display samples of records in the console durin the runs +- **target_table_base_path**: use this parameter to have the target table sitting in a different lake. If *target_table_base_path* is empty, the standard tables' path will be used (*optional*) #### FabricLinkDataStaleness_Summarization paramenters: -- **lookback_hours**: The granular rows are processed starting form *first_SinkCreatedOn < now - lookback_hours*. This paramenter must be an integer greather than 1 -- **grouping_time_zone**: Default timezone UTC -- **source_table**: Must be the same as *target_table_name* paramenter in the FabricLinkDataStaleness script +- **lookback_hours**: The granular rows are processed starting from *first_SinkCreatedOn < now - lookback_hours*. This paramenter must be an integer greater than 1 +- **grouping_time_zone**: Default time zone UTC +- **source_table**: Must be the same as *target_table_name* parameter in the FabricLinkDataStaleness script - **target_table**: The target summarization table *Staleness analysis pipeline.zip* - *(opt.)* Crete the pipeline using **Staleness analysis pipeline.zip**. In your workspace crete a new pipeline then import the zip file from the edit page. -Note that all the above paramenters can be changed in the pipeline in order to adapt to the specific needs. Make sure the pipeline's recurrence matcher the scripts paramentes *hours_back* and *lookback_hours* to avoid leaving ou soe changes. +Note that all the above parameters can be changed in the pipeline in order to adapt to the specific needs. Make sure the pipeline's recurrence matcher the scripts parameters *hours_back* and *lookback_hours* to avoid leaving out some changes. Use the Power BI report example or build your own to easily analyze the data. ## Suggestions -1. Run the recurring pipeline​ to have a stable and autoamted flow of information -2. Limit the analysis to critical tables​. The latency analysis can be quite heavy depending on the change rate of tables -3. Make the summarization chase the granular analysis. For example: Granular analysis can process the last 3 hours while the summarization compresses everything before that. +1. Run the recurring pipeline to have a stable and automated flow of information +2. Limit the analysis to critical tables. The latency analysis can be quite heavy depending on the change rate of tables +3. Make the summarization chase the granular analysis. For example: Granular analysis can process the last 3 hours while summarization compresses everything before that. 4. If the update flow is massive, keep the granular level window small. There is hardly any value in having details for billions of records. -5. In general, adapt the parameters to your scenario.​ -6. Test with few tables and then increase as needed.​ -7. Use *versions_bucket_size* paramenter to reduce the CPU strain. +5. In general, adapt the parameters to your scenario. +6. Test with few tables and then increase as needed. +7. Use *versions_bucket_size* parameter to reduce the CPU strain. +