Showing posts with label Json. Show all posts
Showing posts with label Json. Show all posts

Friday, April 24, 2026

Parsing Copilot Studio Conversation Transcripts in Power Apps

Parsing Copilot Studio Conversation Transcripts in Power Apps

Ever stared at a Copilot Studio transcript JSON and wondered how to turn that nested mess into a clean, readable table? 

The raw transcript comes as a single JSON string with dozens of activities: traces, events, debug plans, tool calls, and somewhere buried in there, the actual user prompts and bot responses. Most of it is plumbing. What you really care about are the messages where role = 1 (user) and role = 0 (bot), paired together by the replyToId field.

The trick in Power Fx is ParseJSON combined with a Clear + Collect pattern. ForAll iterates over each transcript, filters activities down to type = "message", then pairs each user prompt with its matching bot response using the replyToId. Timestamps come through as Unix epoch seconds, so a quick DateAdd against DateTime(1970,1,1,0,0,0) with TimeUnit.Seconds converts them to real datetimes.

Here's the full formula I dropped into App.OnStart:

Clear(colChatTurns);
ForAll(
    ConversationTranscripts As Transcript,
    With(
        { parsed: ParseJSON(Transcript.Content) },
        With(
            {
                allMessages: Filter(
                    Table(parsed.activities),
                    Text(ThisRecord.Value.type) = "message"
                )
            },
            ForAll(
                Filter(allMessages, Text(ThisRecord.Value.from.role) = "1") As UserMsg,
                Collect(
                    colChatTurns,
                    {
                        ReplyToId: Text(UserMsg.Value.id),
                        UserPrompt: Text(UserMsg.Value.text),
                        PromptTimestamp: DateAdd(
                            DateTime(1970,1,1,0,0,0),
                            Value(UserMsg.Value.timestamp),
                            TimeUnit.Seconds
                        ),
                        BotResponse: Text(
                            First(
                                Filter(
                                    allMessages,
                                    Text(ThisRecord.Value.from.role) = "0" 
                                    And Text(ThisRecord.Value.replyToId) = Text(UserMsg.Value.id)
                                )
                            ).Value.text
                        ),
                        ResponseTimestamp: DateAdd(
                            DateTime(1970,1,1,0,0,0),
                            Value(
                                First(
                                    Filter(
                                        allMessages,
                                        Text(ThisRecord.Value.from.role) = "0" 
                                        And Text(ThisRecord.Value.replyToId) = Text(UserMsg.Value.id)
                                    )
                                ).Value.timestamp
                            ),
                            TimeUnit.Seconds
                        )
                    }
                )
            )
        )
    )
)

A few gotchas worth knowing. ParseJSON returns untyped objects, so every field needs explicit coercion with Text() or Value(). The role field can behave as either a number or a string depending on the source, so comparing with "1" and "0" as strings is safer than numeric comparisons. Ungroup sounds like the right tool for flattening nested results, but it chokes on string identifiers — Clear + Collect inside a ForAll loop is cleaner and easier to debug. And always check View → Collections to see if your data is actually landing where you think it is.

Once the collection is populated, binding it to a Data Table gives you an instant conversation viewer. Load it on App.OnStart for speed, or on Screen.OnVisible if you need fresh data every visit. Skip the intermediate gallery and point straight at your data source to keep things lean.

From messy telemetry JSON to a working chat review dashboard.

Tags: PowerApps, PowerFx, CopilotStudio, LowCode, MicrosoftPowerPlatform, JSON, ParseJSON, Dataverse, SharePoint, ConversationAnalytics, ChatbotAnalytics, AIAgents, PowerAppsTutorial, EnterpriseIT, SolutionArchitecture

Monday, March 10, 2025

Actionable Email Developer Dashboard

 Actionable Email Developer Dashboard:

Source:
Get started with actionable messages in Office 365: https://learn.microsoft.com/en-us/outlook/actionable-messages/get-started

Register your service with the actionable email developer dashboard: https://learn.microsoft.com/en-us/outlook/actionable-messages/email-dev-dashboard

1. Create Provider in ''Actionable Email Developer Dashboard - https://outlook.office.com/connectors/oam/publish "

2. Save "Provider Id (originator)" in notepad.

3. Approve submitted provider here: https://outlook.office.com/connectors/oam/admin





4. Prepare Json as below: 

<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <script type="application/adaptivecard+json">{
    "type": "AdaptiveCard",
    "version": "1.0",
    "hideOriginalBody": true,
    "originator": "Provider Id (originator) : GUID from Previous step",
    "body": [
      {
        "type": "TextBlock",
        "text": "Visit the Outlook Dev Portal",
        "size": "large"
      },
      {
        "type": "TextBlock",
        "text": "Click **Learn More** to learn more about Actionable Messages!"
      },
      {
        "type": "Input.Text",
        "id": "feedbackText",
        "placeholder": "Let us know what you think about Actionable Messages"
      }
    ],
    "actions": [
      {
        "type": "Action.Http",
        "title": "Send Feedback",
        "method": "POST",
        "url": "https://...",
        "body": "{{feedbackText.value}}"
      },
      {
        "type": "Action.OpenUrl",
        "title": "Learn More",
        "url": "https://learn.microsoft.com/outlook/actionable-messages"
      }
    ]
  }
  </script>
</head>
<body>
Visit the <a href="https://learn.microsoft.com/outlook/actionable-messages">Outlook Dev Portal</a> to learn more about Actionable Messages.
</body>
</html>

C# Code:
using Azure.Identity;
using Microsoft.Graph;
using Microsoft.Graph.Models;
using Microsoft.Graph.Users.Item.SendMail;

public class Program
{
    public static async Task Main(string[] args)
    {
        var clientId = "6e4110e7-e5b0d411db60";
        var tenantId = "bb55f134-82bf54373c6d";
        var clientSecret = "imx8Q~Q~AHcGN";
        var userFromEmail = "user1@test.onmicrosoft.com";
        var userToEmails = "user2@test.onmicrosoft.com,user3@test.onmicrosoft.com";

        var credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
        var graphClient = new GraphServiceClient(credential);

        string adaptiveCardJson = @"<html>
        <head>
          <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
          <script type='application/adaptivecard+json'>{
            'type': 'AdaptiveCard',
            'version': '1.0',
            'hideOriginalBody': true,
            'originator': 'a115aabe-03994fbaf1d',
            'body': [
              {
                'type': 'TextBlock',
                'text': 'Visit the Outlook Dev Portal',
                'size': 'large'
              },
              {
                'type': 'TextBlock',
                'text': 'Click **Learn More** to learn more about Actionable Messages!'
              },
              {
                'type': 'Input.Text',
                'id': 'feedbackText',
                'placeholder': 'Let us know what you think about Actionable Messages'
              }
            ],
            'actions': [
              {
                'type': 'Action.Http',
                'title': 'Send Feedback',
                'method': 'POST',
                'url': 'https://...',
                'body': '{{feedbackText.value}}'
              },
              {
                'type': 'Action.OpenUrl',
                'title': 'Learn More',
                'url': 'https://learn.microsoft.com/outlook/actionable-messages'
              }
            ]
          }
          </script>
        </head>
        <body>
        Visit the <a href='https://learn.microsoft.com/outlook/actionable-messages'>Outlook Dev
        Portal</a> to learn more about Actionable Messages.
        </body>
        </html>";

        List<string> userToEmailList = new List<string>(userToEmails.Split(','));
        var message = new Message
        {
            Subject = "Test Subject",
            Body = new ItemBody
            {
                ContentType = BodyType.Html,
                Content = $"{adaptiveCardJson}"
            },
            ToRecipients = userToEmailList.Select(email => new Recipient { EmailAddress =
            new EmailAddress { Address = email } }).ToList(),
        };
        var sendMailRequest = new SendMailPostRequestBody { Message = message };
        await graphClient.Users[userFromEmail].SendMail.PostAsync(sendMailRequest);

        Console.ReadKey();
    }
}



OutPut:






Thursday, February 27, 2020

Get started with Microsoft Graph

Get started with Microsoft Graph
https://developer.microsoft.com/en-us/graph/get-started

Step 1: App registrations in Azure Active Directory
Strep 2: C# code to read User Events

Step 1:
go to http://portal.azure.com/ click on 'Azure Active Directory'

click on 'App registration' -> 'New registration' to register new app.

provide required details in app registration page.

from below screen copy client id, tenant id to use in code.

go to 'app permission' -> 'add a permissions' -> 'Microsoft Graph' -> 'Application Permission' -> select below permissions:

Calendars.Read
Calendars.ReadWrite
User.Read
User.Read.All




click on 'Grant admin consent for ' -> click on OK 



Click on 'Certification & Secrets' -> 'New client secret' -> provide required details -> click on 'Add' button. 

make sure copy below secret now itself to use in code, later it is not visible. 

Step 2: Code

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;

namespace graph
{
    public partial class WebForm1 : System.Web.UI.Page
    {
        private const string TenantId = "19d5137a-bf36-xxx";
        private string graphAccessUrl = "https://login.microsoftonline.com/" + TenantId + "/oauth2/v2.0/token";
        private string clientId = "f548e759-fe41-xxx";
        private string clientSecret = "e.e:7E=[QioQfFhTxxx";
        private string grant_type = "client_credentials";
        private string scope = "https://graph.microsoft.com/.default";
        private string graphUrl_event = "https://graph.microsoft.com/v1.0/users/sreekanth@sreekanth07.onmicrosoft.com/events?startdatetime=2020-02-28T10:47:27.2680000&enddatetime=2020-02-29T10:47:27.2680000&$select=subject,body,bodyPreview,organizer,attendees,start,end,location,showAs";
        private string graphUrl_createEvent = "https://graph.microsoft.com/v1.0/users/sreekanth@sreekanth07.onmicrosoft.com/events";
        private string accessToken = string.Empty;
        private JObject events = null;

        protected async void Page_Load(object sender, EventArgs e)
        {
            accessToken = await GetAppOnlyAccessTokekn();
            events = await GetEvents(accessToken);
            string strEvent = await CreateEvent(accessToken);
        }
        private async Task<string> GetAppOnlyAccessTokekn()
        {
            try
            {
                using (HttpClient client = new HttpClient())
                {
                    client.BaseAddress = new Uri(graphAccessUrl);
                    client.DefaultRequestHeaders.Accept.Clear();
                    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));

                    List<KeyValuePair<string, string>> values = new List<KeyValuePair<string, string>>
                    {
                        new KeyValuePair<string, string>("grant_type", grant_type),
                        new KeyValuePair<string, string>("client_id", clientId),
                        new KeyValuePair<string, string>("client_secret", clientSecret),
                        new KeyValuePair<string, string>("scope", scope)
                    };

                    try
                    {
                        HttpContent httpContent = new FormUrlEncodedContent(values);
                        HttpResponseMessage response = await client.PostAsync(new Uri(graphAccessUrl), httpContent);
                        if (response.IsSuccessStatusCode)
                        {
                            string responseString = await response.Content.ReadAsStringAsync();
                            Data reponseObj = JsonConvert.DeserializeObject<Data>(responseString);
                            return reponseObj.access_token;
                        }
                        else
                        {
                            return null;
                        }
                    }
                    catch (Exception)
                    {
                        return null;
                    }
                }
            }
            catch (Exception)
            {
                return null;
            }
        }
        public async Task<JObject> GetEvents(string accessToken)
        {
            try
            {
                using (HttpClient client = new HttpClient())
                {
                    client.BaseAddress = new Uri(graphUrl_event);
                    client.DefaultRequestHeaders.Accept.Clear();
                    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

                    try
                    {
                        HttpResponseMessage response = await client.GetAsync(new Uri(graphUrl_event));
                        if (response.IsSuccessStatusCode)
                        {
                            string responseJSON = await response.Content.ReadAsStringAsync();
                            JObject jObj = JObject.Parse(responseJSON);

                            return jObj;
                        }
                        else
                        {
                            return null;
                        }
                    }
                    catch (Exception)
                    {
                        return null;
                    }
                }
            }
            catch (Exception)
            {
                return null;
            }
        }
        public async Task<string> CreateEvent(string accessToken)
        {
            try
            {
                using (HttpClient client = new HttpClient())
                {
                    client.BaseAddress = new Uri(graphUrl_createEvent);
                    client.DefaultRequestHeaders.Accept.Clear();
                    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

                    try
                    {
                        string body = "{\"subject\":\"My event 123\",\"start\":{\"dateTime\":\"2020-02-28T10:47:27.268Z\",\"timeZone\":\"UTC\"},\"end\":{\"dateTime\":\"2020-02-29T10:47:27.268Z\",\"timeZone\":\"UTC\"},\"location\":{\"displayName\":\"Harry's Bar\"},\"attendees\":[{\"emailAddress\":{\"address\":\"user1@sreekanth07.onmicrosoft.com\",\"name\":\"user 1\"},\"type\":\"required\"},{\"emailAddress\":{\"address\":\"user2@sreekanth07.onmicrosoft.com\",\"name\":\"user 2\"},\"type\":\"required\"},{\"emailAddress\":{\"address\":\"user3@sreekanth07.onmicrosoft.com\",\"name\":\"user 3\"},\"type\":\"required\"}]}";

                        List<KeyValuePair<string, string>> values = new List<KeyValuePair<string, string>>
                        {
                            new KeyValuePair<string, string>("application/json", body)
                        };

                        HttpContent httpContent = new StringContent(body, Encoding.UTF32, "application/json");
                        HttpResponseMessage response = await client.PostAsync(new Uri(graphUrl_createEvent), httpContent);

                        if (response.IsSuccessStatusCode)
                        {
                            string responseJSON = await response.Content.ReadAsStringAsync();
                            return responseJSON;
                        }
                        else
                        {
                            return null;
                        }
                    }
                    catch (Exception)
                    {
                        return null;
                    }
                }
            }
            catch (Exception)
            {
                return null;
            }
        }
    }
    public class Data
    {
        public string token_type { get; set; }
        public string expires_in { get; set; }
        public string ext_expires_in { get; set; }
        public string access_token { get; set; }

    }
}

Featured Post

Microsoft Copilot Studio: The Complete Beginner to Advanced Guide

Microsoft Copilot Studio: The Complete Beginner to Advanced Guide Version: July 2026 | Audience: Beginners, Citizen Developers, Busine...

Popular posts