天天看点

利用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的状态及运行记录

继续阅读