天天看點

利用Flow每天擷取Flow的狀态及運作記錄

我是微軟Dynamics 365 & Power Platform方面的工程師/顧問羅勇,也是2015年7月到2018年6月連續三年Dynamics CRM/Business Solutions方面的微軟最有價值專家(Microsoft MVP),歡迎關注我的微信公衆号 MSFTDynamics365erLuoYong ,回複464或者20220307可友善擷取本文,同時可以在第一間得到我釋出的最新博文資訊,follow me!

前面的文章 ​​通過Custom Connector擷取flow的運作記錄​​​ 講述了通過flow擷取flow的運作記錄,這篇文章擴充下,我們做個配置表出來,配置監控哪些flow,以及實際将flow的運作記錄主要資訊放到Microsoft Dataverse中。

首先我履歷一個配置Flow監控的表,狀态為可用的就是要監控的,停用的記錄就不再監控,這樣利用标準的啟用/停用功能來配置是否監控。還做了個Flow運作記錄表,用來存儲flow運作記錄的主要資訊。

然後建立一個環境變量 (了解環境變量可以參考我的博文 ​​在Power Apps中使用環境變量​​ )來存儲一些配置性的東西,如下:

利用Flow每天擷取Flow的狀态及運作記錄

儲存後打開這個環境變量,設定它的預設值(Default Value)如下:

{
    "EnvironmentId":"6cd1dc1a-56f7-4090-ba7c-e06c4055c81a",
    "DefaultSolutionId":"fd140aaf-4df4-11dd-bd17-0019b9312238",
    "AlertEmails":"[email protected]",
    "ViewFlowLink":"https://asia.flow.microsoft.com/manage/environments/{0}/solutions/{1}/flows/{2}/",
    "ViewFlowRunHistoryLink":"https://asia.flow.microsoft.com/manage/environments/{0}/solutions/{1}/flows/{2}/runs/{3}"
}      

然後我建立一個Custom API來批量建立和設定Flow設定記錄:

利用Flow每天擷取Flow的狀态及運作記錄

這個Custom API我使用的代碼如下:

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Json;
using System.Text;

namespace D365.Plugins
{
    public class CustomAPIUpsertFlowMonitorSettings : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService adminOrgSvc = serviceFactory.CreateOrganizationService(null);
            var flowSetting = GetEnvironmentVariableValue("ly_FlowMonitorSetting", adminOrgSvc, tracingService);
            if (string.IsNullOrEmpty(flowSetting))
            {
                throw new InvalidPluginExecutionException("名稱為ly_FlowMonitorSetting的環境變量沒有設定,請聯系系統管理者!");
            }
            var flowSettingDic = DeserializeDictionary(flowSetting);
            if (!flowSettingDic.ContainsKey("EnvironmentId") || !flowSettingDic.ContainsKey("DefaultSolutionId") || !flowSettingDic.ContainsKey("AlertEmails") || !flowSettingDic.ContainsKey("ViewFlowLink") || !flowSettingDic.ContainsKey("ViewFlowRunHistoryLink"))
            {
                throw new InvalidPluginExecutionException("名稱為ly_FlowMonitorSetting的環境變量設定有問題,部分元素沒有設定,請聯系系統管理者!");
            }
            var queryWorkflow = new QueryExpression("workflow");
            queryWorkflow.NoLock = true;
            queryWorkflow.ColumnSet = new ColumnSet("name", "workflowidunique", "statecode");
            queryWorkflow.Criteria.AddCondition("category", ConditionOperator.Equal, 5);
            queryWorkflow.Criteria.AddCondition("type", ConditionOperator.Equal, 1);
            var workflowEc = adminOrgSvc.RetrieveMultiple(queryWorkflow);
            QueryExpression qe;
            EntityCollection ec;
            foreach (var workflowEntity in workflowEc.Entities)
            {
                qe = new QueryExpression("ly_flowmonitorsetting");
                qe.TopCount = 1;
                qe.NoLock = true;
                qe.ColumnSet.AddColumn("ly_flowmonitorsettingid");
                qe.Criteria.AddCondition("ly_workflowidunique", ConditionOperator.Equal, workflowEntity.GetAttributeValue<Guid>("workflowidunique").ToString());
                ec = adminOrgSvc.RetrieveMultiple(qe);
                if (ec.Entities.Any())
                {
                    var updateEntity = new Entity("ly_flowmonitorsetting", ec.Entities[0].Id);
                    updateEntity["ly_flowname"] = workflowEntity.GetAttributeValue<string>("name");
                    updateEntity["ly_isturnon"] = workflowEntity.GetAttributeValue<OptionSetValue>("statecode").Value == 0 ? false : true;
                    adminOrgSvc.Update(updateEntity);
                }
                else
                {
                    var createEntity = new Entity("ly_flowmonitorsetting");
                    createEntity["ly_name"] = workflowEntity.GetAttributeValue<string>("name");
                    createEntity["ly_isturnon"] = workflowEntity.GetAttributeValue<OptionSetValue>("statecode").Value == 0 ? false : true;
                    createEntity["ly_workflowidunique"] = workflowEntity.GetAttributeValue<Guid>("workflowidunique").ToString();
                    createEntity["ly_flowname"] = workflowEntity.GetAttributeValue<string>("name");
                    createEntity["ly_viewflowlink"] = string.Format(flowSettingDic["ViewFlowLink"], flowSettingDic["EnvironmentId"], flowSettingDic["DefaultSolutionId"], workflowEntity.GetAttributeValue<Guid>("workflowidunique").ToString());
                    adminOrgSvc.Create(createEntity);
                }
            }
        }

        private string GetEnvironmentVariableValue(string variableSchemaName, IOrganizationService service, ITracingService tracingService)
        {
            if (string.IsNullOrEmpty(variableSchemaName))
            {
                throw new InvalidPluginExecutionException("環境變量名稱為空,中止查詢該變量的值!");
            }
            string returnVal = string.Empty;
            var fetchXml = string.Format(@"<fetch versinotallow='1.0' mapping='logical' top='1' no-lock='true'>
  <entity name='environmentvariabledefinition'>
    <attribute name='defaultvalue'/>
    <filter type='and'>
      <condition attribute='schemaname' operator='eq' value='{0}'/>
      <condition attribute='statecode' operator='eq' value='0'/>
    </filter>
    <link-entity name='environmentvariableval(new FetchExpression(fetchXml));
            if (ec.Entities.Count >= 1)
            {
                if (ec.Entities[0].Contains("varvalue.value"))
                {
                    returnVal = ec.Entities[0].GetAttributeValue<AliasedValue>("varvalue.value").Value.ToString();
                }
                else
                {
                    returnVal = ec.Entities[0].GetAttributeValue<string>("defaultvalue");
                }
            }
            tracingService.Trace($"名稱為{variableSchemaName}環境變量的值是{returnVal}");
            return returnVal;
        }

        private Dictionary<string, string> DeserializeDictionary(string josnStr)
        {
            Dictionary<string, string> returnVal = null;
            if (!string.IsNullOrEmpty(josnStr))
            {
                DataContractJsonSerializerSettings serializerSettings = new DataContractJsonSerializerSettings
                {
                    UseSimpleDictionaryFormat = true
                };

                using (var ms = new MemoryStream(Encoding.Unicode.GetBytes(josnStr)))
                {
                    DataContractJsonSerializer deseralizer = new DataContractJsonSerializer(typeof(Dictionary<string, string>), serializerSettings);
                    returnVal = (Dictionary<string, string>)deseralizer.ReadObject(ms);
                }
            }
            return returnVal;
        }
    }
}      

然後在Flow監控配置的實體的清單界面我做了一個按鈕,使用的調用代碼如下:

"use strict";
var LY = window.LY || {};
LY.flowmonitorsettingRibbon = LY.flowmonitorsettingRibbon || {};
(function () {
    this.bulkUpsertRecords = function (selectedControl) {
        Xrm.Utility.showProgressIndicator("進行中,請稍候...");
        var clientUrl = Xrm.Utility.getGlobalContext().getClientUrl();
        var req = new XMLHttpRequest();
        req.open("POST", `${clientUrl}/api/data/v9.2/ly_UpsertFlowMonitorSettings`, true);
        req.setRequestHeader("Accept", "application/json");
        req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
        req.setRequestHeader("OData-MaxVersion", "4.0");
        req.setRequestHeader("OData-Version", "4.0");
        req.onreadystatechange = function () {
            if (this.readyState == 4) {
                req.onreadystatechange = null;
                if (this.status == 204) {
                    Xrm.Utility.closeProgressIndicator();
                    selectedControl.refresh();
                }
                else {
                    Xrm.Utility.closeProgressIndicator();
                    selectedControl.refresh();
                    var error = JSON.parse(this.response).error;
                    Xrm.Navigation.openErrorDialog({ message: error.message });
                }
            }
        }
        req.send();
    }
}).call(LY.flowmonitorsettingRibbon);
//# flowmonitorsetting_ribbon.js      

這個按鈕涉及到的RibbonDiff内容如下:

<RibbonDiffXml>
        <CustomActions>
          <CustomAction Id="ly.ly_flowmonitorsetting.bulkUpsertRecords.Button.CustomAction" Location="Mscrm.HomepageGrid.ly_flowmonitorsetting.MainTab.Management.Controls._children" Sequence="55">
            <CommandUIDefinition>
              <Button Command="ly.ly_flowmonitorsetting.bulkUpsertRecords.Command" Id="ly.ly_flowmonitorsetting.bulkUpsertRecords.Button" LabelText="$LocLabels:ly.ly_flowmonitorsetting.bulkUpsertRecords.Button.LabelText" Sequence="55" TemplateAlias="o2" ToolTipDescription="$LocLabels:ly.ly_flowmonitorsetting.bulkUpsertRecords.Button.ToolTipDescription" ModernImage="New" />
            </CommandUIDefinition>
          </CustomAction>
        </CustomActions>
        <Templates>
          <RibbonTemplates Id="Mscrm.Templates"></RibbonTemplates>
        </Templates>
        <CommandDefinitions>
          <CommandDefinition Id="ly.ly_flowmonitorsetting.bulkUpsertRecords.Command">
            <EnableRules>
              <EnableRule Id="ly.ly_flowmonitorsetting.bulkUpsertRecords.EnableRule" />
            </EnableRules>
            <DisplayRules />
            <Actions>
              <JavaScriptFunction FunctionName="LY.flowmonitorsettingRibbon.bulkUpsertRecords" Library="$webresource:ly_/script/flowmonitorsetting/flowmonitorsetting_ribbon.js">
                <CrmParameter Value="SelectedControl" />
              </JavaScriptFunction>
            </Actions>
          </CommandDefinition>
        </CommandDefinitions>
        <RuleDefinitions>
          <TabDisplayRules />
          <DisplayRules />
          <EnableRules>
            <EnableRule Id="ly.ly_flowmonitorsetting.bulkUpsertRecords.EnableRule">
              <SelectionCountRule AppliesTo="PrimaryEntity" Minimum="0" Maximum="250" Default="false" InvertResult="false" />
            </EnableRule>
          </EnableRules>
        </RuleDefinitions>
        <LocLabels>
          <LocLabel Id="ly.ly_flowmonitorsetting.bulkUpsertRecords.Button.LabelText">
            <Titles>
              <Title description="建立/更新配置" languagecode="1033" />
            </Titles>
          </LocLabel>
          <LocLabel Id="ly.ly_flowmonitorsetting.bulkUpsertRecords.Button.ToolTipDescription">
            <Titles>
              <Title description="自動擷取Flow資訊,建立/更新Flow監控配置資訊" languagecode="1033" />
            </Titles>
          </LocLabel>
        </LocLabels>
      </RibbonDiffXml>      

然後我點選按鈕就将現有的Cloud Flow的資訊插入/更新到Flow監控配置實體中了。然後我将其他flow監控記錄停用,隻保留我自己的這個flow用于示範。

利用Flow每天擷取Flow的狀态及運作記錄

我再做個定期運作的flow用來重新整理flow的資訊,擷取flow的運作記錄。

利用Flow每天擷取Flow的狀态及運作記錄

為Flow起個名字,點選Create按鈕

利用Flow每天擷取Flow的狀态及運作記錄

根據需要設定運作頻率:

利用Flow每天擷取Flow的狀态及運作記錄

為了避免表達式太長,我聲明三個變量來存儲環境變量中的值:

使用的表達式類似 parameters('FlowMonitorSetting (ly_FlowMonitorSetting)')['DefaultSolutionId']

利用Flow每天擷取Flow的狀态及運作記錄

然後擷取需要監控flow的基本資訊,步驟如下:

利用Flow每天擷取Flow的狀态及運作記錄

使用的FetchXml是

<fetch mapping="logical" distinct="false" no-lock="true">
  <entity name="workflow">
   <attribute name="workflowid" />
    <attribute name="statecode" />
    <attribute name="workflowidunique" />
    <filter type="and">
      <condition attribute="category" operator="eq" value="5" />
      <condition attribute="type" operator="eq" value="1" />
    </filter>
    <link-entity name="ly_flowmonitorsetting" from="ly_workflowidunique" to="workflowidunique" link-type="inner" alias="setting">
      <filter type="and">
        <condition attribute="statecode" operator="eq" value="0" />
      </filter>
    </link-entity>
  </entity>
</fetch>      

 對于每個監控的flow,我先根據Workflow的唯一ID去找到Flow監控配置記錄,然後更新其開關狀态字段,用到的主要表達式是

ly_workflowidunique eq '@{items('Apply_to_each')?['workflowidunique']}' and statecode eq 0

if(equals(items('Apply_to_each')?['statecode'],0),false,true)

利用Flow每天擷取Flow的狀态及運作記錄

然後擷取監控的每個flow的運作記錄,并循環每個運作記錄,看是否已經存在運作記錄唯一ID相同的記錄,用到的主要表達式如下:

@{parameters('FlowMonitorSetting (ly_FlowMonitorSetting)')['EnvironmentId']}

@{items('Apply_to_each')?['workflowidunique']}

body('Get_Flow_Run_History')['value']

利用Flow每天擷取Flow的狀态及運作記錄

如果沒有就要新增flow運作記錄:

用到的主要表達式如下,有寫地方用了if,因為如果flow運作成功的話,有些元素是沒有的,如果不管有沒有都去讀取的話,flow運作會報錯。

length(outputs('查找相同運作id的flow運作記錄')?['body/value'])

/ly_flowmonitorsettings(@{items('Apply_to_each_2')?['ly_flowmonitorsettingid']})

@{replace(replace(replace(replace(variables('ViewFlowRunHistoryLink'),'{0}',variables('EnvironmentId')),'{1}',variables('DefaultSolutionId')),'{2}',items('Apply_to_each')?['workflowidunique']),'{3}',items('Apply_to_each_Flow_Run_History')['name'])}

@{items('Apply_to_each_Flow_Run_History')['properties/startTime']}

@{items('Apply_to_each_Flow_Run_History')['properties/status']}

@{items('Apply_to_each_Flow_Run_History')['properties/endTime']}

@{if(equals(items('Apply_to_each_Flow_Run_History')['properties/status'],'Succeeded'),'',items('Apply_to_each_Flow_Run_History')['properties/code'])}

@{items('Apply_to_each_Flow_Run_History')['name']}

@{if(equals(items('Apply_to_each_Flow_Run_History')['properties/status'],'Succeeded'),'',items('Apply_to_each_Flow_Run_History')['properties/error/code'])}

@{if(equals(items('Apply_to_each_Flow_Run_History')['properties/status'],'Succeeded'),'',items('Apply_to_each_Flow_Run_History')['properties/error/message'])}

利用Flow每天擷取Flow的狀态及運作記錄

效果如下:

擷取到的錯誤代碼、錯誤消息不一定是顯示詳細錯誤資訊,此處有待改進。

擷取的其他資訊我得測試結果表明是準确的。

利用Flow每天擷取Flow的狀态及運作記錄

繼續閱讀