<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Ham Sandwich]]></title><description><![CDATA[Articles and step-by-step guides for Microsoft's Power Platform, SharePoint Online and M365]]></description><link>https://hamsandwich.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1730367317580/1ff6e547-41d2-4aef-b56f-e2b7ea776822.png</url><title>Ham Sandwich</title><link>https://hamsandwich.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Wed, 08 Apr 2026 12:56:18 GMT</lastBuildDate><atom:link href="https://hamsandwich.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Custom Table of Contents for SharePoint Page Libraries -  JSON View Formatting]]></title><description><![CDATA[Background
JSON view customizations in SharePoint Online are usually created for local use within a list or library. The view described in this article, however, is intended for web parts - altering how Site Page contents are displayed across pages a...]]></description><link>https://hamsandwich.dev/custom-table-of-contents-for-sharepoint-page-libraries-json-view-formatting</link><guid isPermaLink="true">https://hamsandwich.dev/custom-table-of-contents-for-sharepoint-page-libraries-json-view-formatting</guid><category><![CDATA[json]]></category><category><![CDATA[SharePoint]]></category><category><![CDATA[SharePoint Online]]></category><category><![CDATA[site]]></category><category><![CDATA[library]]></category><category><![CDATA[formatting]]></category><dc:creator><![CDATA[Jake Mannion]]></dc:creator><pubDate>Mon, 19 Jan 2026 03:06:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1768791362115/979b6db7-bf74-4ac4-b5f4-f82201192790.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-background">Background</h1>
<p>JSON <a target="_blank" href="https://learn.microsoft.com/en-us/sharepoint/dev/declarative-customization/formatting-syntax-reference">view customizations</a> in SharePoint Online are usually created for local use within a list or library. The view described in this article, however, is intended for <strong>web parts</strong> - altering how Site Page contents are displayed across pages and templates.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768787433439/85acf32a-15d0-41f4-825e-9c25bf0bc305.png" alt="Site Page library custom Table of Content view within web part" class="image--center mx-auto" /></p>
<center><b>Site Page Table of Contents - Example</b></center>

<h3 id="heading-automatic-updates">Automatic Updates</h3>
<p>Changes and additions to the Site Page library are automatically reflected <em>everywhere</em> the TOC view is currently implemented via web part.</p>
<p>Even when installed across hundreds of pages, updates require just a single action at the source library. This can be quite useful for wiki-style and informational sites… especially ones that will evolve and grow over time.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">The TOC view is to meant to <em>augment</em> (but not replace) site and hub navigation</div>
</div>

<h1 id="heading-implementing">Implementing</h1>
<blockquote>
<p>Note: Don’t let the length of this section fool you. These steps are not particularly complicated or time-consuming. I always try to include extra detail, as a fair amount of visitors stumble across this blog. 👍</p>
</blockquote>
<h2 id="heading-site-page-library-columns">Site Page Library - <em>Columns</em></h2>
<p>Several custom columns are needed to define the TOC structure. These columns can be created directly within the <em>Site Pages</em> library (easiest) or within the corresponding <em>content type</em> (more flexible).</p>
<h3 id="heading-sort-order">🔷 Sort Order</h3>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Technically, this step is optional. You can rely on alpha sort (or another system column) for your TOC view. However, a custom sort method (1) <strong>enables indentation to work predictably</strong>. It also (2) <strong>prevents ambiguity</strong>, like the page titled <em>Home</em> showing up in the middle<em> </em>(after items A-G), for example.</div>
</div>

<p>Create a <em>number</em> column, which will be used to define the TOC page sequence. This field is not referenced by the formatting JSON, so name it as you wish:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768761593758/6be2de78-5693-4a02-8d8b-2829ba4440a4.png" alt class="image--center mx-auto" /></p>
<center><b>Column setup for <i>Order</i> field</b></center>

<p>The above settings will work fine, but you don’t have to <em>strictly</em> adhere to them. You might want to disable <strong>enforce unique values</strong>, for example, as it can be a minor pain later on, without much benefit.</p>
<h3 id="heading-indentation">🔷 Indentation</h3>
<p>Create another <em>number</em> column, which will define the <strong>hierarchy</strong> within the TOC. Name it <em>Indent</em>, to <strong>align with field references</strong> in the formatting JSON:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768762321765/740b8d08-8ebf-4f24-8087-a7c5aee7a1d6.png" alt class="image--center mx-auto" /></p>
<center><b>Column setup for <i>Indent</i> field</b></center>

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">Setting a default value is recommended. The JSON formatting expects indentation values of <strong>0, 2, 4 or 6</strong></div>
</div>

<h2 id="heading-site-page-library-custom-views">Site Page Library - <em>Custom View(s)</em></h2>
<h3 id="heading-table-of-contents-display-view">🔷 Table of Contents (display view)</h3>
<p>This view will (later) receive the custom JSON formatting.</p>
<ol>
<li><p>Create a new public view (call it <em>TOC</em> or something similar)</p>
</li>
<li><p>Include these columns:</p>
<ol>
<li><p><em>Indent</em></p>
</li>
<li><p><em>Name (for use in forms)</em></p>
</li>
<li><p><em>Title</em></p>
</li>
</ol>
</li>
<li><p>Set the Sort parameter to use the custom <em>Order</em> column (assuming you created it):</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768764440171/903aa2c9-ac9c-44a5-9931-dde4444396cb.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Set the Filter criteria to only include items with an <em>Indent</em> value:</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768764522931/d6e0e28d-15d5-4c6e-8fda-4897d6e19138.png" alt class="image--center mx-auto" /></p>
 <div data-node-type="callout">
 <div data-node-type="callout-emoji">💡</div>
 <div data-node-type="callout-text">Ignore the lowercase “i” in the screenshot above - field name is <em>Indent </em>(woops)</div>
 </div>

 <div data-node-type="callout">
 <div data-node-type="callout-emoji">ℹ</div>
 <div data-node-type="callout-text">You can add a boolean column (or similar) if you’d like more control over item visibility in the TOC. <em>Indent </em>works fine, but if that column has a default value set, new pages will show up instantly.</div>
 </div>


</li>
</ol>
<h3 id="heading-table-of-contents-management-view">🔷 Table of Contents (management view)</h3>
<p>Optional step, but you may want to do <strong>one</strong> of these:</p>
<ol>
<li><p><strong>Add the new library columns</strong> to the <em>All Pages</em> system view (and make it the library default, instead of the clumsy, grouped <em>By Author</em> view).</p>
</li>
<li><p><strong>Create a new view</strong> for working with TOC site page metadata (that <em>won’t</em> receive the JSON formatting)</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768790186167/a356502f-b978-480c-ae4c-e8fcb5ee3691.png" alt="Modified All Pages system view" class="image--center mx-auto" /></p>
<center><b>Example modified <i>All Pages</i> view</b></center>

<h2 id="heading-site-page-library-bulk-edit-existing-pages">Site Page Library - <em>Bulk Edit Existing Pages</em></h2>
<p>If there are existing pages in the library, <strong>use quick edit to assign column values</strong> (<em>Order</em>, <em>Indent</em>) to the pages you want included in the TOC.</p>
<p>Also, make sure each page has a <em>Title</em> value.</p>
<h3 id="heading-column-usage-tips-indent">Column Usage Tips: <em>Indent</em></h3>
<p>The indent value affects the padding of TOC items and for the more indented ones, font size.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768762968075/e99b1db3-0c64-43af-b442-46bdfdcfb3fa.png" alt class="image--center mx-auto" /></p>
<center><b><i>Indent</i> value conditional formatting</b></center>

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Note: the TOC items will not display the <em>Indent </em>value, as seen above. That was added temporarily for illustration purposes.</div>
</div>

<h3 id="heading-column-usage-tips-order">Column Usage Tips: <em>Order</em></h3>
<p>Give yourself plenty of “space” when assigning <em>Order</em> values. Space them apart by counts of 10-100, to provide flexibility for reordering or adding later.</p>
<p>If you number each page sequentially (1,2,3…) you might need to re-number a <strong>bunch</strong> of Order values to insert new pages where you want them, later.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768771564891/1b8c9246-e3c3-4209-89c8-f7e3e74ab3a7.png" alt class="image--center mx-auto" /></p>
<center><b><i>All Pages</i> library view, showing <i>Order</i> values</b></center>

<h1 id="heading-view-formatting-json">View Formatting JSON</h1>
<h2 id="heading-details-notes">Details / Notes</h2>
<p>Overall, it’s a <em>decent</em> custom view - not a perfect one.</p>
<ul>
<li><p>Much of the styling in this JSON is done using <a target="_blank" href="https://zerg00s.github.io/sp-modern-classes/">classes</a>, but there are some fixed values in the mix (margins, e.g.)</p>
</li>
<li><p>There are probably some minor opportunities to improve HTML / flexbox stuff, too</p>
</li>
<li><p>The key lines for conditional styling are <strong><mark>20</mark></strong><mark>, </mark> <strong><mark>39</mark></strong> <mark>and </mark> <strong><mark>45</mark></strong></p>
<ul>
<li>Some of those conditions resolve identically. Those are legacies from development, left in to allow for future customization.</li>
</ul>
</li>
</ul>
<pre><code class="lang-json">{
  <span class="hljs-attr">"$schema"</span>: <span class="hljs-string">"https://developer.microsoft.com/json-schemas/sp/v2/row-formatting.schema.json"</span>,
  <span class="hljs-attr">"schema"</span>: <span class="hljs-string">"https://developer.microsoft.com/json-schemas/sp/view-formatting.schema.json"</span>,
  <span class="hljs-attr">"target"</span>: <span class="hljs-string">"_self"</span>,
  <span class="hljs-attr">"debugMode"</span>: <span class="hljs-literal">false</span>,
  <span class="hljs-attr">"hideColumnHeader"</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">"hideSelection"</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">"rowFormatter"</span>: {
    <span class="hljs-attr">"elmType"</span>: <span class="hljs-string">"a"</span>,
    <span class="hljs-attr">"attributes"</span>: {
      <span class="hljs-attr">"class"</span>: <span class="hljs-string">"ms-fontColor-themeDarker ms-fontColor-themeDarker--hover"</span>,
      <span class="hljs-attr">"href"</span>: <span class="hljs-string">"=@currentWeb + '/SitePages/' + [$FileLeafRef]"</span>,
      <span class="hljs-attr">"target"</span>: <span class="hljs-string">"_self"</span>
    },
    <span class="hljs-attr">"style"</span>: {
      <span class="hljs-attr">"text-decoration"</span>: <span class="hljs-string">"none"</span>,
      <span class="hljs-attr">"border"</span>: <span class="hljs-string">"none"</span>,
      <span class="hljs-attr">"cursor"</span>: <span class="hljs-string">"pointer"</span>,
      <span class="hljs-attr">"margin-bottom"</span>: <span class="hljs-string">"2px"</span>,
      <span class="hljs-attr">"margin-left"</span>: <span class="hljs-string">"=if([$indent] == 2,'12px', if([$indent] == 4, '24px', if([$indent] == 6,'36px', '0px')))"</span>,
      <span class="hljs-attr">"padding"</span>: <span class="hljs-string">"2px"</span>,
      <span class="hljs-attr">"overflow"</span>: <span class="hljs-string">"hidden"</span>,
              <span class="hljs-attr">"width"</span>: <span class="hljs-string">"100%"</span>,
              <span class="hljs-attr">"max-width"</span>: <span class="hljs-string">"280px"</span>
    },
    <span class="hljs-attr">"customRowAction"</span>: {
      <span class="hljs-attr">"action"</span>: <span class="hljs-string">"defaultClick"</span>
    },
    <span class="hljs-attr">"children"</span>: [
      {
        <span class="hljs-attr">"elmType"</span>: <span class="hljs-string">"div"</span>,
        <span class="hljs-attr">"style"</span>: {
          <span class="hljs-attr">"display"</span>: <span class="hljs-string">"flex"</span>,
          <span class="hljs-attr">"flex-direction"</span>: <span class="hljs-string">"column"</span>,
          <span class="hljs-attr">"align-items"</span>: <span class="hljs-string">"baseline"</span>,
          <span class="hljs-attr">"flex-wrap"</span>: <span class="hljs-string">"wrap"</span>,
          <span class="hljs-attr">"box-sizing"</span>: <span class="hljs-string">"border-box"</span>,
          <span class="hljs-attr">"border-width"</span>: <span class="hljs-string">"0px"</span>,
          <span class="hljs-attr">"border-left-width"</span>: <span class="hljs-string">"=if([$indent] == 0,'3px', if([$indent] == 2, '3px', '3px'))"</span>,
          <span class="hljs-attr">"border-style"</span>: <span class="hljs-string">"solid"</span>,
          <span class="hljs-attr">"padding-left"</span>: <span class="hljs-string">"4px"</span>,
              <span class="hljs-attr">"width"</span>: <span class="hljs-string">"100%"</span>
        },
        <span class="hljs-attr">"attributes"</span>: {
          <span class="hljs-attr">"class"</span>: <span class="hljs-string">"=if([$indent] == 0,'ms-fontSize-mPlus ms-borderColor-themeDark', if([$indent] == 2, 'ms-fontSize-mPlus ms-borderColor-themeSecondary', if([$indent] == 4,'ms-fontSize-m ms-borderColor-themeLight', 'ms-fontSize-m ms-borderColor-themeLighter'))) + ' ms-bgColor-themeLighter--hover'"</span>
        },
        <span class="hljs-attr">"children"</span>: [
          {
            <span class="hljs-attr">"elmType"</span>: <span class="hljs-string">"div"</span>,
            <span class="hljs-attr">"style"</span>: {
              <span class="hljs-attr">"line-height"</span>: <span class="hljs-string">"1.0em"</span>,
              <span class="hljs-attr">"margin"</span>: <span class="hljs-string">"2px 0"</span>,
              <span class="hljs-attr">"text-align"</span>: <span class="hljs-string">"left"</span>
            },
            <span class="hljs-attr">"txtContent"</span>: <span class="hljs-string">"[$Title]"</span>
          }
        ]
      }
    ]
  }
}
</code></pre>
<h1 id="heading-document-library-web-part">Document Library Web Part</h1>
<p>Add a <strong>Document Library web part</strong> onto a site page and configure it like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768773772106/886e7040-1f6d-4aeb-9262-4926cd8d2464.png" alt class="image--center mx-auto" /></p>
<center><b><i>Document Library</i> web part setup and config</b></center>

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">Keep in mind, you can also add the web part to <em>page templates. </em>This is a great way to rapidly propagate the web part and view across a site in a consistent manner.</div>
</div>

<p>By this point, you should be in business.</p>
<p>If this worked out for you, or if you have any difficulties, please <strong>drop me a note</strong> in the comments.</p>
]]></content:encoded></item><item><title><![CDATA[Custom Logging in Power Automate]]></title><description><![CDATA[Handling errors gracefully within your flow logic is important. Fortunately, there is plenty of guidance to be found on the subject:

Error Handling - THE FLYING POLYMATH

Try Catch - D365 Demystified

Error Handling Standards - Matthew Devaney


How...]]></description><link>https://hamsandwich.dev/custom-logging-in-power-automate</link><guid isPermaLink="true">https://hamsandwich.dev/custom-logging-in-power-automate</guid><category><![CDATA[PowerPlatform]]></category><category><![CDATA[automation]]></category><category><![CDATA[SharePoint]]></category><category><![CDATA[SharePoint Online]]></category><category><![CDATA[logging]]></category><category><![CDATA[power-automate]]></category><dc:creator><![CDATA[Jake Mannion]]></dc:creator><pubDate>Wed, 01 Jan 2025 22:23:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1735770144193/5cf0d622-7025-44dd-98db-19e93e4c9606.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Handling errors gracefully within your flow logic is important. Fortunately, there is plenty of guidance to be found on the subject:</p>
<ul>
<li><p><a target="_blank" href="https://www.flyingpolymath.com/post/error-handling-in-power-automate-for-the-business-user">Error Handling - THE FLYING POLYMATH</a></p>
</li>
<li><p><a target="_blank" href="https://d365demystified.com/2022/01/20/try-catch-error-handling-using-scope-in-power-automate/">Try Catch - D365 Demystified</a></p>
</li>
<li><p><a target="_blank" href="https://www.matthewdevaney.com/power-automate-coding-standards-for-cloud-flows/power-automate-standards-error-handling/">Error Handling Standards - Matthew Devaney</a></p>
</li>
</ul>
<p>How can we investigate exceptions without wading through countless pages of flow run history?</p>
<p>If integrating with <a target="_blank" href="https://learn.microsoft.com/en-us/power-platform/admin/app-insights-cloud-flow">Azure Application Insights</a> is not in scope and the information available within the <a target="_blank" href="https://learn.microsoft.com/en-us/power-platform/release-plan/2024wave1/power-automate/automation-center-monitor-manage-all-automations-related-assets">Automation Center</a> is not granular or specific enough, consider custom event logging.</p>
<h2 id="heading-scenarios-use-cases">Scenarios / Use Cases</h2>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🏭</div>
<div data-node-type="callout-text">Performing bulk content operations and a certain percentage of failures is expected</div>
</div>

<div data-node-type="callout">
<div data-node-type="callout-emoji">📆</div>
<div data-node-type="callout-text">Building a solution that involves chronological activities and events</div>
</div>

<div data-node-type="callout">
<div data-node-type="callout-emoji">🧺</div>
<div data-node-type="callout-text">Collecting flow errors in a centralized location, instead of relying on “push” notifications</div>
</div>

<div data-node-type="callout">
<div data-node-type="callout-emoji">🤖</div>
<div data-node-type="callout-text">Providing visibility to runs from higher environments or from a service account context</div>
</div>

<div data-node-type="callout">
<div data-node-type="callout-emoji">🖥</div>
<div data-node-type="callout-text">Displaying flow outcome data adjacent to the automation context (for SPO scenarios)</div>
</div>

<h2 id="heading-log-list-examples">Log List Examples</h2>
<h3 id="heading-approval-event-history">Approval Event History</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1735761262537/441478ff-d5ef-47a0-bd2a-ffcb692af42b.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-scheduled-automation">Scheduled Automation</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1735762931024/12a90626-2740-405c-98eb-e84deca7f186.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-bulk-content-sync">Bulk Content Sync</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1735762844036/51ac9590-b307-4d76-bc86-b21f24bef99a.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-creating-a-log-or-event-list">Creating a Log or Event List</h2>
<h3 id="heading-columns">Columns</h3>
<p>There’s no perfect recipe, but I usually include a column for each of these values:</p>
<ul>
<li><p>Run outcome (success, error, warning, etc.)</p>
</li>
<li><p>Outcome details</p>
</li>
<li><p>Associated item title</p>
</li>
<li><p>Associated item ID</p>
</li>
<li><p>Flow run ID</p>
</li>
</ul>
<p>Then, add additional columns <strong>for your specific logging scenario</strong>, as needed.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Keep in mind, simplicity is the goal here. Use single-line text columns whenever possible - avoid choice fields or complex data types (lookup columns, etc.)</div>
</div>

<p>For ALM scenarios, you may find adding these helpful:</p>
<ul>
<li><p>Flow name</p>
</li>
<li><p>Solution name</p>
</li>
<li><p>Environment</p>
</li>
</ul>
<h3 id="heading-configuration-tips">Configuration Tips</h3>
<ol>
<li><p>Disable search on the list (advanced settings) or restrict list permissions to only the relevant individuals/audiences</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1735765616171/b2fd104d-1aa6-40b4-a98f-ee6b0cb47074.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Index any columns that might be needed for sorting/filtering</p>
</li>
<li><p>Include a multi-line plain text column, if you’d like to store JSON output</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1735764688679/9a988e4b-d5a4-4617-ac1f-72ff0c5312d5.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Create view(s) and add <a target="_blank" href="https://learn.microsoft.com/en-us/sharepoint/dev/declarative-customization/column-formatting">custom column formatting</a>, if that helps readability</p>
</li>
<li><p>Examine the list after log entries start to populate - you may think of another field or two worth capturing</p>
</li>
</ol>
<h3 id="heading-cloning-an-existing-list">Cloning an Existing List</h3>
<p>Assuming custom logging proves useful, you may want to create more lists in the future. You can use an existing list as a template, to speed creation. Some options:</p>
<ul>
<li><p><a target="_blank" href="https://sharegate.com/microsoft-migration">ShareGate</a> migration tool</p>
</li>
<li><p><em>Use an existing list</em> option in the SharePoint UI (under site contents)</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1735765649874/050dbbc0-0fde-4d87-8d3a-699304bebdc3.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Microsoft Lists</p>
</li>
</ul>
<h2 id="heading-adding-logging-actions">Adding Logging Actions</h2>
<p>Once your list is ready, it’s time to add some flow logic. New log entries will be added using a <a target="_blank" href="https://learn.microsoft.com/en-us/connectors/sharepointonline/#create-item"><em>Create Item</em></a> action.</p>
<p>There are two general approaches for adding logging steps - both achieve the same result, but have different considerations.</p>
<h3 id="heading-add-to-existing-flows">Add to Existing Flow(s)</h3>
<p>You can sprinkle logging actions within your <strong>existing</strong> flow logic, utilizing copy/paste.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1735765917259/21830ba7-69e8-482e-b112-8cd074611504.png" alt class="image--center mx-auto" /></p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Pros</strong></td><td><strong>Cons</strong></td></tr>
</thead>
<tbody>
<tr>
<td>Simple, fast approach</td><td>If the list schema changes, you may need to update (and find) all previously-added actions</td></tr>
<tr>
<td>No additional licensing cost</td><td>Cannot add additional actions later (like sending notifications) unless done manually</td></tr>
<tr>
<td>Can store complex data types, JSON</td><td></td></tr>
<tr>
<td>Uses existing SharePoint Online connection / reference (if present)</td></tr>
</tbody>
</table>
</div><h3 id="heading-create-standalone-logging-flow"><strong>Create Standalone Logging Flow</strong></h3>
<p>Alternatively, you can create a separate flow (HTTP trigger or <a target="_blank" href="https://learn.microsoft.com/en-us/power-automate/create-child-flows">child solution flow</a>) to store your logging steps:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1735765881733/9a7898f1-6c86-4ded-942f-6848c0e68564.png" alt class="image--center mx-auto" /></p>
<p>Once created, invoke it from existing flow(s) as needed:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1735767554600/e8b1cbff-3b89-4ce4-a175-5cb53f893307.png" alt class="image--center mx-auto" /></p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Pros</strong></td><td><strong>Cons</strong></td></tr>
</thead>
<tbody>
<tr>
<td>Much easier to extend later, if needed</td><td>Additional setup time and complexity</td></tr>
<tr>
<td>Can handle complex data types (HTTP trigger only)</td><td>Requires premium licensing (HTTP trigger only)</td></tr>
<tr>
<td>One logging flow can be shared by multiple solutions / flows</td><td>Need to collect and send parent workflow context</td></tr>
<tr>
<td>Option to run asynchronously - won’t hold up parent flow</td></tr>
</tbody>
</table>
</div><div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Don’t forget a <strong>Response</strong> action (for HTTP trigger flows) or <strong>Respond to Power Apps or Flow</strong> action (for child solution flows)</div>
</div>

<h2 id="heading-other-notes">Other Notes</h2>
<ul>
<li><p>Consider disabling retry policies whenever a standalone logging flow gets invoked or a Create Item log action is called. You don’t want to hold up a parent flow run in the unlikely event that logging hits a snag.</p>
</li>
<li><p>For logging hierarchical flow outcomes, consider creating an entry for when the parent begins, ends, and every child iteration in between. Store a shared identifier (like parent flow run ID) to help group entries later.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1735768845180/6457ef32-43ab-4bbf-a215-f09932ffd049.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Calculating overall flow run duration, error and success totals, etc. can add additional value to the last log entry for a given flow run. Don’t spend the time adding that logic unless the extra insight is worth it, however.</p>
</li>
<li><p>You can use the logging technique described on this page to store errors / exceptions only, or you can include successful outcomes and other event types, depending on the situation.</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Create Planner Tasks with Microsoft Form Attachments]]></title><description><![CDATA[Form Options
There are several options available when file uploads are enabled for Microsoft Forms:

Can be optional or required

Can be limited from 1-10 attachments

Can restrict to certain file types



You can construct workflow logic around thes...]]></description><link>https://hamsandwich.dev/create-planner-tasks-with-microsoft-form-attachments</link><guid isPermaLink="true">https://hamsandwich.dev/create-planner-tasks-with-microsoft-form-attachments</guid><category><![CDATA[PowerPlatform]]></category><category><![CDATA[Logic Apps]]></category><category><![CDATA[Microsoft365]]></category><category><![CDATA[Planner]]></category><category><![CDATA[forms]]></category><dc:creator><![CDATA[Jake Mannion]]></dc:creator><pubDate>Fri, 02 Jun 2023 04:30:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1685680099701/48c96273-fbca-47fc-8eda-705cbd1f1f32.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-form-options">Form Options</h2>
<p>There are several options available when file uploads are enabled for Microsoft Forms:</p>
<ul>
<li><p>Can be optional or required</p>
</li>
<li><p>Can be limited from 1-10 attachments</p>
</li>
<li><p>Can restrict to certain file types</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1685676368941/8bf3f9a4-9fe8-4710-b7c4-5c3b668c812a.png" alt class="image--center mx-auto" /></p>
<p>You can construct workflow logic around these options, but it's usually best to handle a multitude of scenarios, in case the form or connector changes later.</p>
<h2 id="heading-storage-locations">Storage Locations</h2>
<p>Attachments for <a target="_blank" href="https://support.microsoft.com/en-au/office/create-a-group-form-or-quiz-7228eebb-a6ab-45ec-8123-52026a2f52ff">both types of Forms</a> are stored in different locations:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Form Ownership</td><td>Location</td></tr>
</thead>
<tbody>
<tr>
<td>Personal</td><td>Stored within the form creator's OneDrive, under ...<strong>\Apps\Microsoft Forms\</strong></td></tr>
<tr>
<td>Group</td><td>Stored within the group's file storage, within the General folder for the associated Team</td></tr>
</tbody>
</table>
</div><p><strong>Tip</strong>: Using the same group for both the Form (source) and Planner board (destination) simplifies permissions and avoids the need to move attachment content.</p>
<h2 id="heading-workflow-logic">Workflow Logic</h2>
<p>The automation is triggered by new form submissions. With each run, the details for each submission are collected and a corresponding Planner task is created.</p>
<p>Optionally, add a condition to determine if attachment(s) were included in the form response, so the subsequent logic can account for both scenarios:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1685676993872/21a09390-3954-4b34-bc01-4c0c7c9cbb2d.png" alt class="image--center mx-auto" /></p>
<p>You will need an expression to determine if the file upload "question" within the form is empty or not:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1685677484948/48167799-e461-4adf-aa20-3968940e06f3.png" alt class="image--center mx-auto" /></p>
<p>The expression uses a <em>length</em> function on the question within the form that collects attachments. If the outcome is greater than 0, the workflow will "know" attachments were included and can respond accordingly:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1685677937439/fd4cb300-9742-4dc9-9d52-5555348f88c2.png" alt class="image--center mx-auto" /></p>
<p>Within the <strong>Yes</strong> branch of the condition, use a <a target="_blank" href="https://learn.microsoft.com/en-us/power-automate/data-operations#use-the-select-action">Select action</a> to convert the array of form attachments into a format compatible with Planner. Once complete, it will look like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1685678044992/cb80ae76-b540-4ece-989b-89df1795f49d.png" alt class="image--center mx-auto" /></p>
<p>For the <strong>From</strong> field, wrap the dynamic value used above (your attachment "question" within the form) inside a JSON function:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1685678128221/40c8ef74-5703-4463-bcc9-e43343f8dc6f.png" alt class="image--center mx-auto" /></p>
<p>Next, map two properties - <strong>resourceLink</strong> and <strong>alias</strong>. Toggle to advanced/text mode...</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1685678330489/6d88ba04-f652-438f-bef7-99a57feb5c9b.png" alt class="image--center mx-auto" /></p>
<p>...and paste the following snippet:</p>
<pre><code class="lang-plaintext">{
    "resourceLink": @{item()?['link']},
    "alias": @{item()?['name']}
}
</code></pre>
<p>Once that's complete, add an <strong>Update Task Details</strong> action, using the ID value from the preceding <strong>Create Task</strong> action:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1685678804191/63a607f6-34cc-4a45-b767-500c38fc9d77.png" alt class="image--center mx-auto" /></p>
<p>Switch to advanced mode within the References section of the Select action and add the Output from the previous Select statement:</p>
<p><strong>Note</strong>: attachments in Planner are known internally as <em>references</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1685679369582/8ac268e6-be67-4382-a357-209da34c973a.png" alt class="image--center mx-auto" /></p>
<p>At this point, your attachment handling logic should be complete.</p>
<p>Create a test form submission, and you should see a task card with the correct attachments included:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1685679754162/cc83ed04-36da-44f8-bd23-a64d8c838dff.png" alt class="image--center mx-auto" /></p>
<p>Don't forget to go back and complete the <strong>No</strong> path for the <em>"does the response contain attachments"</em> condition, and incorporate any additional logic you might need in the workflow.</p>
]]></content:encoded></item><item><title><![CDATA[Handling Paginated Results from Microsoft Graph with Azure Data Factory]]></title><description><![CDATA[This topic is covered in a handful of blog posts, forum topics, etc. but none of the information I found recently got me 100% of the way there. That can happen with methods involving two evolving services - Azure Data Factory and Microsoft Graph - in...]]></description><link>https://hamsandwich.dev/handling-paginated-results-from-microsoft-graph-with-azure-data-factory</link><guid isPermaLink="true">https://hamsandwich.dev/handling-paginated-results-from-microsoft-graph-with-azure-data-factory</guid><category><![CDATA[Azure]]></category><category><![CDATA[Azure Data Factory]]></category><category><![CDATA[Graph]]></category><category><![CDATA[Microsoft]]></category><category><![CDATA[APIs]]></category><dc:creator><![CDATA[Jake Mannion]]></dc:creator><pubDate>Mon, 16 Jan 2023 20:09:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1673899265363/230c5f79-1b50-4cc4-ae77-1c1581554227.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This topic is covered in a handful of blog posts, forum topics, etc. but none of the information I found recently got me 100% of the way there. That can happen with methods involving two evolving services - <a target="_blank" href="https://learn.microsoft.com/en-us/azure/data-factory/">Azure Data Factory</a> and <a target="_blank" href="https://learn.microsoft.com/en-us/graph/use-the-api">Microsoft Graph</a> - information becomes out of date.</p>
<p>Here's what worked for me in early 2023!</p>
<h1 id="heading-prerequisites">Prerequisites</h1>
<p>Even though some of these are detailed topics, I am going to gloss over them since they are covered in-depth elsewhere on the web:</p>
<ol>
<li><p><a target="_blank" href="https://learn.microsoft.com/en-us/graph/auth-register-app-v2">Register an Azure App</a> for use as a service principal (later) with your data factory</p>
</li>
<li><p>Grant the application <a target="_blank" href="https://learn.microsoft.com/en-us/graph/permissions-reference">appropriate roles</a> for your intended use of Graph</p>
</li>
<li><p>Store the <em>client secret</em> value for the app in <a target="_blank" href="https://azure.microsoft.com/en-us/products/key-vault/">Azure Key Vaul</a>t</p>
</li>
<li><p>Enable managed identities for your data factory</p>
</li>
<li><p>Give the managed identity for your data factory the appropriate role on the Key Vault (to retrieve stored <em>client secret</em>)</p>
</li>
<li><p>Configure a data sink to store the Graph results, and ensure the data factory has permission to write there</p>
</li>
<li><p>Create a new <a target="_blank" href="https://learn.microsoft.com/en-us/azure/data-factory/concepts-linked-services?tabs=data-factory">Linked Service</a> within ADF (type: REST)</p>
<ol>
<li><p>Set the Authentication type as <strong>Service Principal</strong></p>
</li>
<li><p>Use the Azure Key Vault method to retrieve the <em>client secret</em> from AKV</p>
</li>
<li><p>AAD Resource: <a target="_blank" href="https://graph.microsoft.com/"><strong>https://graph.microsoft.com/</strong></a></p>
</li>
<li><p>Parameterize the Base URL value</p>
</li>
</ol>
</li>
</ol>
<p>This will get you something like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673897531734/8b9d0cb5-8105-4a1f-aa7c-3e792f084013.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-dataset-pipeline-and-activity-setup">Dataset, Pipeline and Activity Setup</h1>
<ol>
<li><p>Create a new Dataset using the new Linked Service you created (above steps)</p>
<ol>
<li><p>You can parameterize the <strong>baseURL</strong> value if you'd like to use <em>one</em> Dataset for <em>multiple</em> Graph queries.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673898081449/92b92657-c219-47ba-9588-31baa4dd2049.png" alt class="image--center mx-auto" /></p>
</li>
</ol>
</li>
<li><p>Otherwise, provide a fixed Graph API endpoint value within the Dataset, like:</p>
<p> https://graph.microsoft.com/v1.0/users?$select=id,accountEnabled,createdDateTime,department,displayName,employeeId,employeeType,jobTitle,mail,userPrincipalName,userType,manager,employeeHireDate&amp;$expand=manager($select=id)</p>
</li>
<li><p>Create a new Pipeline and add a Copy Data activity</p>
</li>
<li><p>Configure the Copy Data activity to use the Dataset you created</p>
</li>
<li><p>Delete the automatically-supplied Pagination criteria (RFC5988)</p>
</li>
<li><p>Add a new Pagination Rule, like this:</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673898523166/87e4ede4-a47c-4881-8f0e-6bb024ce8796.png" alt class="image--center mx-auto" /></p>
</li>
</ol>
<p>Your source definition within the Copy activity should look something like this:</p>
<pre><code class="lang-json"><span class="hljs-string">"source"</span>: {
    <span class="hljs-attr">"type"</span>: <span class="hljs-string">"RestSource"</span>,
    <span class="hljs-attr">"httpRequestTimeout"</span>: <span class="hljs-string">"00:01:40"</span>,
    <span class="hljs-attr">"requestInterval"</span>: <span class="hljs-string">"00.00:00:00.010"</span>,
    <span class="hljs-attr">"requestMethod"</span>: <span class="hljs-string">"GET"</span>,
    <span class="hljs-attr">"paginationRules"</span>: {
        <span class="hljs-attr">"AbsoluteUrl"</span>: <span class="hljs-string">"$['@odata.nextLink']"</span>
    }
}
</code></pre>
<p>Now, when your Copy Data activity executes, it will keep fetching results from Graph until the <strong>@odata.nextlink</strong> is no longer present in the result body, at which point it will start the sink activity.</p>
<blockquote>
<p>Note: this is my first time using the new Neptune editor on Hashnode ... forgive any oddities with the numbered list formatting... I am in a bit of a hurry and will try to fix the markdown later</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Extract Excel Worksheet Data Without Tables - Power Automate]]></title><description><![CDATA[Overview
One of the more common uses for Power Automate is working with **Excel files and data**. Overall compatibility is pretty good, with one significant exception.





Data LocationPower Automate Compatibility



Excel tableGood functionality, w...]]></description><link>https://hamsandwich.dev/extract-excel-worksheet-data-without-tables-power-automate</link><guid isPermaLink="true">https://hamsandwich.dev/extract-excel-worksheet-data-without-tables-power-automate</guid><category><![CDATA[Automate]]></category><category><![CDATA[PowerPlatform]]></category><category><![CDATA[excel]]></category><category><![CDATA[data]]></category><category><![CDATA[table]]></category><dc:creator><![CDATA[Jake Mannion]]></dc:creator><pubDate>Thu, 22 Sep 2022 17:37:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1663722716067/sC8JvaU5a.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-overview">Overview</h1>
<p>One of the more common uses for <a target="_blank" href="https://powerautomate.microsoft.com/en-us/">Power Automate</a> is working with **Excel files and data**. Overall compatibility is pretty good, with one significant exception.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663786634965/jSAKMcyb4.png" alt /></p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Data Location</td><td>Power Automate Compatibility</td></tr>
</thead>
<tbody>
<tr>
<td>Excel table</td><td>Good functionality, with some <a target="_blank" href="https://learn.microsoft.com/en-us/connectors/excelonlinebusiness/#known-issues-and-limitations-with-actions">thresholds and limitations</a></td></tr>
<tr>
<td>Excel table (with key column)</td><td>Same as above, plus additional OData capabilities</td></tr>
<tr>
<td>Excel CSV</td><td>Supported, but not through the <a target="_blank" href="https://learn.microsoft.com/en-us/connectors/excelonlinebusiness/">Excel Online connector</a></td></tr>
<tr>
<td>Excel worksheet</td><td>Data operations not supported</td></tr>
</tbody>
</table>
</div><p>Historically, that last item creates a lot of frustration. 😐 Excel is used extensively in the business world and this limitation puts its <em>most commonly-used format</em> outside the reach of Microsoft's own automation platform.</p>
<h3 id="heading-unstructured-data-example">Unstructured Data - Example</h3>
<p>The data in this worksheet <em>looks</em> like a table to you and me, but without a formal definition, Automate can't work with the information here.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663715614372/VtT7UFgQM.png" alt="image.png" class="image--center mx-auto" /></p>
<p>It needs <em>explicit</em> structure before column relationships and the corresponding data inside the Excel document can be utilized.</p>
<h1 id="heading-the-scenario">The Scenario</h1>
<p>You might not use Automate to work with Excel very often<a class="post-section-overview" href="#heading-thoughts-on-excel-as-a-data-source"><sup>1</sup></a>, but like most juggernauts, it's sometime unavoidable. Here is a situation that presented itself recently:</p>
<ol>
<li><p>A partner organization emails a collection of Excel worksheets every morning</p>
</li>
<li><p>A flow stores them in a SharePoint Online document library</p>
</li>
<li><p>The worksheets have consistent structures but the data is not contained within tables</p>
</li>
<li><p>The data needs to reach Azure SQL each day without manual intervention</p>
</li>
</ol>
<h1 id="heading-the-solution">The Solution</h1>
<h2 id="heading-preparing-for-multiple-filesformats">Preparing For Multiple Files/Formats</h2>
<p>Our scenario involves multiple worksheets, each with a unique data structure. I used an array variable to store configuration details at the beginning of the flow:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663813947635/gO2Ypc4F0.png" alt="image.png" /></p>
<blockquote>
<p>Note: If you're working with <em>one</em> Excel file, feel free to skip to the next section</p>
</blockquote>
<p>Each object contains information about a worksheet:</p>
<p>Name | Example | Usage --- | --- filename | incomingWorksheet2.xlsx | Name of the Excel worksheet colWidth | BA | Identifier for the last expected column rowLength | 2500 | Number of table rows to create (see notes on "padding" in next section) colHeadings | CustomerName;Item;Price | <em>OPTIONAL</em> - column heading overrides tableName | Sales | SQL table where data will be stored. Also used to name each Excel table created. keyColumn | saleID | Column used to filter results - use one that has a value in every row</p>
<p>Next, we will loop over the array, and the real party can begin:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663717233734/MNBWFKP8J.png" alt="image.png" /></p>
<blockquote>
<p>Note: Pre-baked arrays are okay when quickly proving out a new method. For an actual solution, environment variables are a better choice.</p>
</blockquote>
<h2 id="heading-adding-tables-into-excel-worksheets-with-existing-data">Adding Tables into Excel Worksheets with Existing Data</h2>
<p>The Excel connector within Power Automate has a <a target="_blank" href="https://learn.microsoft.com/en-us/connectors/excelonlinebusiness/#create-table">Create Table</a> action. You can use this action to insert a table into an Excel worksheet that already contains data. I was very surprised to see this work!</p>
<p>We're not out of the woods yet, though. The amount of worksheet data fluctuates each day in our scenario, which is a problem because Automate can't "see" how much data exists at each runtime.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663742393608/bLg-9ZFKr.png" alt="image.png" class="image--center mx-auto" /></p>
<p>We can mitigate some of the uncertainty by "padding" the <a target="_blank" href="https://learn.microsoft.com/en-us/connectors/excelonlinebusiness/#create-table">Create Table</a> action. For example, if you expect ~2,000 rows of data in a worksheet, have the flow create 2,500-3,000 rows when inserting the table.</p>
<p>Without surplus rows, you risk isolating data. Automate can't create tables twice in the same worksheet, so options for self-correction are slim if you undershoot the mark. Aim high, but don't go <em>too</em> far overboard!</p>
<blockquote>
<p>Note: This "padding" technique only works if you can make solid predictions about the expected data</p>
</blockquote>
<p>Here is the <strong>Create Table</strong> action, using dynamic values from the configuration array:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663718102271/TCdrcr03n.png" alt="image.png" /></p>
<blockquote>
<p>Note: Use fixed values for the parameters if you're operating on a single worksheet</p>
</blockquote>
<p>If your setup is correct, the existing Excel data will remain in place while the new table is inserted. The action will continue until it hits the row limit, adding blank rows to the worksheet once it runs past the data.</p>
<h2 id="heading-getting-data-back-from-excel">Getting Data Back from Excel</h2>
<p>Use <strong>List Table Rows</strong> to pull the data into Automate. Provide an OData filter query (under advanced options) to retrieve non-blank rows only:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663814263174/v4t4gZcmT.png" alt="image.png" /></p>
<pre><code class="lang-plaintext">items('for_each_Excel_document')?['keyColumn']
</code></pre>
<p>Unless you are dealing with very small data sets, don't forget to enable pagination for the <strong>List Table Rows</strong> action. This will allow the flow to pull <em>all</em> the data back from Excel in successive chunks, then proceed once complete.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663835875635/GKrVcwSWk.png" alt="image.png" /></p>
<p>Set the threshold equal to the highest row count you'll be using in the <strong>Create Table</strong> action(s).</p>
<h2 id="heading-do-something-with-the-results">Do Something With the Results</h2>
<p>If you've made it this far, the possibilities are wide open! Extracting unstructured data from Excel worksheets using Power Automate is no small feat.</p>
<h3 id="heading-possibilities-from-here">Possibilities From Here</h3>
<ul>
<li><p>Loop over each row in the result set and store the data somewhere (SQL, SharePoint, Dataverse, etc.) line-by-line</p>
</li>
<li><p>Use a switch statement to handle the results from unique worksheets in different ways</p>
</li>
<li><p>Pass the result set into a SQL table (1,000 rows at a time) using the <strong>Execute a SQL query</strong> action</p>
</li>
</ul>
<h1 id="heading-thoughts-on-excel-as-a-data-source">Thoughts on Excel as a Data Source</h1>
<p>Excel is often a popular data source for users getting started with the Power Platform. It's a familiar, flexible and portable format.</p>
<p>I don't typically recommend it - there are more performant and less volatile options available. Sometimes you don't have a choice, so it's good to have a few tricks up your sleeve.</p>
<h1 id="heading-disclosure-and-final-thought">Disclosure and Final Thought</h1>
<p>There are <a target="_blank" href="https://sharepains.com/2018/10/17/microsoft-flow-read-large-excel-files-within-seconds-without-creating-tables-using-microsoft-graph/">better, far more elegant ways to solve this problem</a> than the approach described on this page. What's outlined above isn't a foolproof method. It's not designed for heavy usage across a broad spectrum of enterprise-grade scenarios.</p>
<p>But in a pinch, and for the right use cases, this method should get the job done. I hope this comes in handy for individuals who don't necessarily have the means, expertise or time to handle app registration, HTTP calls, Graph, and so forth. As you continue your journey along the Power Platform progression arc, I hope you learn those methods, too.</p>
]]></content:encoded></item><item><title><![CDATA[Advanced SharePoint Permission Actions with Power Automate]]></title><description><![CDATA[Background
SharePoint is known for its highly customizable permissions. That flexibility can have its perils, of course, but with the right approach (and for the right scenarios) it can be very effective.
Power Automate provides a few basic connector...]]></description><link>https://hamsandwich.dev/managing-permissions-in-sharepoint-online-with-power-automate</link><guid isPermaLink="true">https://hamsandwich.dev/managing-permissions-in-sharepoint-online-with-power-automate</guid><category><![CDATA[REST API]]></category><category><![CDATA[Security]]></category><category><![CDATA[PowerPlatform]]></category><category><![CDATA[SharePoint]]></category><category><![CDATA[PowerAutomate]]></category><dc:creator><![CDATA[Jake Mannion]]></dc:creator><pubDate>Fri, 27 May 2022 00:06:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1653594273735/dRTmXz1y1.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-background">Background</h1>
<p>SharePoint is known for its highly customizable permissions. That flexibility can have its perils, of course, but with the right approach (and for the right scenarios) it can be very effective.</p>
<p>Power Automate provides a few basic connector actions for working with SharePoint permissions:</p>
<ol>
<li><p><em>Revoke access</em> to an item</p>
</li>
<li><p>Create access <em>links</em> for an item</p>
</li>
</ol>
<p>Not much for now, but fortunately the <strong>HTTP to SharePoint</strong> action opens up numerous other possibilities.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653594177575/zjwp9i7LH.png" alt="http to sharepoint action" /></p>
<h1 id="heading-familiar-methods-new-approach">Familiar Methods, New Approach</h1>
<p>SharePoint's REST API has a number of endpoints for managing permissions. Most methods can be re-used across different entities like sites, lists, items, etc. If you've done on-premises SharePoint development, you may already be familiar <a target="_blank" href="https://www.c-sharpcorner.com/UploadFile/fc34aa/break-inheritance-and-add-role-permissions-using-rest-api-in/">with some of them</a>.</p>
<p>The calls are simple - no payloads, headers or returns to process for most scenarios. The only tricky part can be finding the exact syntax, and there may be some data peculiarities to manage, depending on your scenario.</p>
<h1 id="heading-power-automate-http-examples-sharepoint-permissions">Power Automate HTTP Examples - SharePoint Permissions</h1>
<h2 id="heading-getting-the-principal-id-of-a-group">Getting the Principal ID of a Group</h2>
<p>Type: GET</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653609712448/lqdSAD-mh.png" alt="flow http action" /></p>
<p>Note: Replace <em>GROUP NAME</em> with your target group name</p>
<pre><code class="lang-plaintext">_api/web/sitegroups/getbyname('GROUP NAME')/Id
</code></pre>
<h2 id="heading-getting-principal-ids-for-all-groups">Getting Principal IDs for <em>All</em> Groups</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653597117939/q0RBXQ6eF.png" alt="flow http action" /></p>
<pre><code class="lang-plaintext">_api/web/sitegroups?$select=Title,ID
</code></pre>
<p>Results will be an array of all groups available on the site.</p>
<p>Note: You can find group IDs manually by clicking individual groups within Site Settings -&gt; People and Groups and checking the address bar.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653609560344/wkyKIyUwv.png" alt="People and Groups" /></p>
<p>Cutting this corner won't save you much time, however. For most scenarios, it's better to fetch the value(s) at runtime.</p>
<h2 id="heading-getting-principal-id-for-specific-user">Getting Principal ID for Specific User</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653602710522/XFQm8joSt.png" alt="exploring data structure with SP Insider" /></p>
<p>There are a couple ways to do this. First, you might want to use a tool like <a target="_blank" href="https://chrome.google.com/webstore/detail/sp-insider/gjckpigahcbffmeofjfedlffddhfidhj?hl=en">SP Insider</a> (pictured above) to get familiar with the SiteUsers data structure within your site.</p>
<p>Or, with Power Automate…</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653602837728/Ru6xDxgR9.png" alt="flow action http" /></p>
<p>Here are two sample queries - one for email, one for user name:</p>
<pre><code class="lang-plaintext">_api/Web/SiteUsers?$filter=Email eq 'Sample.Person@samplesite.com'
</code></pre>
<p>Note: The search terms are case sensitive!</p>
<pre><code class="lang-plaintext">_api/Web/SiteUsers?$filter=Title eq 'Sample Person'
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653602203605/rxIHGnor3.png" alt="http result array" /></p>
<p>You'll get a single-node array in the return, containing the user's ID value for that site.</p>
<h2 id="heading-getting-principal-ids-for-all-users">Getting Principal IDs for All Users</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653609661323/fCd7Rb-8e.png" alt="flow http action" /></p>
<p>You'll be getting a lot of data back with this one, potentially, so consider using <em>select</em> to narrow the return down to just the fields you need.</p>
<pre><code class="lang-plaintext">_api/Web/SiteUsers?$select=ID,Title,Email
</code></pre>
<p>There are some important limitations to consider here:</p>
<ol>
<li><p>SiteUsers data is <em>site-specific</em></p>
</li>
<li><p>A user's Principal ID on <em>one</em> site will be different from their Principal ID on <em>another</em> site</p>
</li>
<li><p>The SiteUsers list will only contain records for <em>users who have visited the site</em> (or have been added via a few other methods).</p>
</li>
</ol>
<p>There is an <a target="_blank" href="https://docs.microsoft.com/en-us/previous-versions/office/sharepoint-visio/jj245233(v=office.15)">ensureUser method</a> to explore, if you want to pursue this approach further.</p>
<h2 id="heading-getting-all-listlibrary-guids">Getting All List/Library GUIDs</h2>
<p>I tend to work with GUIDs more than GetByTitle endpoints, so the examples on this page are tailored to that approach. Here's a quick way to get all list GUIDs on a site, in case you need it:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653608951166/CM_u0Odja.png" alt="flow action GUIDs" /></p>
<p>Type: GET</p>
<pre><code class="lang-plaintext">/_api/Web/Lists?&amp;$select=Title, ID
</code></pre>
<h2 id="heading-getting-all-role-definitions">Getting All Role Definitions</h2>
<p>Each permission role in SharePoint (Full Control, Edit, etc.) has a specific ID associated with it. You'll need this value for some of the example calls below.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653608272920/z7wfD5kHh.png" alt="image.png" /></p>
<p>Type: GET</p>
<pre><code class="lang-plaintext">_api/web/roledefinitions
</code></pre>
<p>Results will be an array containing objects for each role definition.</p>
<h2 id="heading-get-current-permissions-on-itemdocument">Get Current Permissions on Item/Document</h2>
<p>Type: GET</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681151777936/86c44ab9-e082-4506-858a-6fe7b10e3f2c.png" alt class="image--center mx-auto" /></p>
<pre><code class="lang-plaintext">_api/web/lists/getbytitle('LIST NAME')/items(ITEM ID)/roleassignments
</code></pre>
<p>Will return an array of objects. Each <strong>PrincipalId</strong> will tell you <em>who</em> has permission to the item, but not the permission type.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681153060752/575ed915-4703-4d4c-8d29-f29d2962c35f.png" alt class="image--center mx-auto" /></p>
<p>Use an <strong>Apply to Each</strong> step to loop through the results and perform further actions.</p>
<h2 id="heading-break-permission-inheritance-on-an-item-or-document">Break Permission Inheritance on an Item or Document</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653608716377/aijsSa0n9.png" alt="flow action" /></p>
<p>Type: POST</p>
<pre><code class="lang-plaintext">_api/Web/Lists(guid'LIST GUID')/Items(12)/breakroleinheritance(copyRoleAssignments=true, clearSubscopes=true)
</code></pre>
<h2 id="heading-restore-permission-inheritance-on-itemdocument">Restore Permission Inheritance on Item/Document</h2>
<p>*Added August, 2024</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Tip of the cap to <a target="_blank" href="https://tomriha.com/how-to-restore-sharepoint-item-file-permissions-with-power-automate/">Tom Riha</a> on this one, which I neglected to include when I first slapped this page together. Saved me a few minutes of head scratching tonight.</div>
</div>

<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724045162715/7435a2a0-a1bb-47b4-8e09-ca3b00363ea4.png" alt class="image--center mx-auto" /></p>
<pre><code class="lang-plaintext">_api/Web/lists/getByTitle('YOUR-LIST-OR-LIBRARY-HERE')/items(ITEM-ID-HERE)/ResetRoleInheritance()
</code></pre>
<p>Type: POST</p>
<h2 id="heading-remove-permissions-from-a-listlibrary">Remove Permissions from a List/Library</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653594693429/JL9p6OsuQ.png" alt="flow http action" /></p>
<p>Type: DELETE</p>
<pre><code class="lang-plaintext">_api/Web/Lists(guid'LIST GUID')/RoleAssignments/GetByPrincipalId(5)
</code></pre>
<p>Note: Replace the principal ID value for the user/group you are targeting, and the GUID for your list or library.</p>
<h2 id="heading-remove-permissions-from-an-itemdocument">Remove Permissions from an Item/Document</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653609149352/7CJqZEyzM.png" alt="image.png" /></p>
<p>Type: DELETE</p>
<pre><code class="lang-plaintext">_api/Web/Lists(guid'LIST GUID')/Items(12)/RoleAssignments/GetByPrincipalId(5)
</code></pre>
<p>Note: Replace the the principal ID and list GUID values to suit your specific scenario</p>
<p>You don't need a specific role definition for this one - it will simply remove the target entity's permissions (whatever those may be) for the item.</p>
<h2 id="heading-add-permissions-to-a-listlibrary">Add Permissions to a List/Library</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653594982798/oCusLlUbk.png" alt="flow http action" /></p>
<p>Type: POST</p>
<pre><code class="lang-plaintext">_api/Web/Lists(guid'LIST GUID')/RoleAssignments/addroleassignment(principalid=5,roledefid=1073741827)
</code></pre>
<p>Note: Replace the the principal ID, role definition and list GUID values to suit your specific scenario</p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>That covers many of the more common permission management activities in SharePoint. You can extend this approach to cover lots of other scenarios, as well. 👍</p>
]]></content:encoded></item><item><title><![CDATA[Parsing Custom Planner Labels with Power Automate]]></title><description><![CDATA[Context
I'm a fan of Microsoft Planner. It's easy to use, flexible, and the UI/UX is rock solid. The tie-in with O365 Groups and Teams is a strong selling point, as well.
Now, Planner isn't the perfect solution for every scenario (you probably wouldn...]]></description><link>https://hamsandwich.dev/parsing-custom-planner-labels-with-power-automate</link><guid isPermaLink="true">https://hamsandwich.dev/parsing-custom-planner-labels-with-power-automate</guid><category><![CDATA[PowerPlatform]]></category><category><![CDATA[Microsoft]]></category><category><![CDATA[Planner]]></category><category><![CDATA[PowerAutomate]]></category><category><![CDATA[SQL]]></category><category><![CDATA[power-automate]]></category><dc:creator><![CDATA[Jake Mannion]]></dc:creator><pubDate>Wed, 18 May 2022 22:48:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1652912933691/hiN8Q0Hyj.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-context">Context</h1>
<p>I'm a fan of <a target="_blank" href="https://tasks.office.com/">Microsoft Planner</a>. It's easy to use, flexible, and the UI/UX is rock solid. The tie-in with O365 Groups and Teams is a strong selling point, as well.</p>
<p>Now, Planner isn't the perfect solution for <em>every</em> scenario (you probably wouldn't want to manage sprints with it) but it is very popular within many companies, large and small.</p>
<h2 id="heading-definitions">Definitions</h2>
<p>Microsoft naming conventions don't always make things easy, so:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Term</td><td>Meaning</td></tr>
</thead>
<tbody>
<tr>
<td>Planner</td><td>the name for the overall application/service</td></tr>
<tr>
<td>Plan</td><td>an individual task board for a team or person</td></tr>
<tr>
<td>Task</td><td>the items within a Plan</td></tr>
<tr>
<td>Bucket</td><td>used to group collections of similar tasks</td></tr>
<tr>
<td>Label</td><td>a way to tag/classify individual tasks</td></tr>
</tbody>
</table>
</div><h2 id="heading-integration-opportunities-abound">Integration Opportunities Abound</h2>
<p>Planner can be used in many different ways, but at its core it's all about task management, which makes it a juicy target for automation. The <a target="_blank" href="https://docs.microsoft.com/en-us/connectors/planner/">Power Platform connector</a> is pretty robust, and you can extend that even further by hitting the Planner API through <a target="_blank" href="https://docs.microsoft.com/en-us/graph/overview">Microsoft Graph</a>.</p>
<p>However, any integration efforts quickly expose Planner's underlying data structure, which has some quirks.</p>
<h1 id="heading-task-labels">Task Labels</h1>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652907372232/jIfiT92CE.png" alt="planner label default name" /></p>
<p>Planner boards come with 25 labels to help you and your team classify the individual tasks on the board. Each label has a <em>default</em> name - its associated color - and that name can be customized to provide more meaning and context for the plan users. Cool feature!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652907131573/hL91NaTY5.png" alt="Planner task label customization" /></p>
<p>Customizations, like label names, are contained at the individual <em>plan</em> level. For example, one team might use <em>red</em> for marking "Urgent" work and a different team may use it to classify something as "Top Secret."</p>
<h2 id="heading-the-challenge">The Challenge</h2>
<p>When retrieving Planner <em>task</em> data, any label information comes back as an <strong>appliedCategories</strong> object:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652908590244/IIymDzzzZ.png" alt="label data from task" /></p>
<p>It's not an array.</p>
<p>There are no name/value pairs.</p>
<p>It's just... <em>booleans</em> with no tie-back to the label names. What does <strong>category18</strong> mean? Has it been customized? How can I tell?</p>
<p>Fortunately, you can get a bit of a "glossary" if you retrieve the overall <em>plan</em> data:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652909292622/tOctbXrNT.png" alt="plan label customization data" /></p>
<p>That's great, but how do I map those values? How do I bounce a name/value object against an object of booleans?</p>
<h2 id="heading-the-power-automate-way">The Power Automate Way</h2>
<p>Different platforms/languages will have their own approach to the challenges above. For <a target="_blank" href="https://powerautomate.microsoft.com/">Automate</a>, it required some considerable thinking!</p>
<p>At the time this came up, I was working on a solution that iterated over <em>many</em> plans, so I needed a method that scaled. If you are working with data from just an individual plan, there are definitely some corners you could cut.</p>
<h3 id="heading-building-a-glossary-array">Building a Glossary Array</h3>
<p>1.) When I got to the point in my automation where I was working with individual plans, I used the <strong>Get plan details</strong> action.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652910714701/fZDddaP94.png" alt="plan details and array" /></p>
<p>2.) Next, I used a <strong>Compose</strong> statement to craft my array. It took a lot of typing, but copy/paste made it less of an ordeal.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652910916961/nTTsqO4RQ.png" alt="coalesce expression within array" /></p>
<p>3.) Most teams that use labels within Planner also customize the label names - but it's not an absolute certainty. The <strong>Get plan details</strong> action only returns customized values. I wanted to be certain I covered both scenarios, so I used a coalesce expression within each node of the array.</p>
<h3 id="heading-creating-a-task-label-array">Creating a Task Label Array</h3>
<p>The steps above get us halfway there, but we still need to handle the other part of the mapping - the data from each individual task.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652911439704/FzSoxExpW.png" alt="parse JSON for task labels" /></p>
<p>4.) Inside of a <strong>For each task</strong> loop, I started with a <strong>Parse JSON</strong> step, using the <strong>appliedCategories</strong> value as the content source.</p>
<p>5.) The schema needs to be created manually (unless you magically have a task with all 25 labels applied, to feed into the generator):</p>
<p>{ "type": "object", "properties": { "category1": { "type": "boolean" }, "category2": { "type": "boolean" }, "category3": { "type": "boolean" }, "category4": { "type": "boolean" }, "category5": { "type": "boolean" }, "category6": { "type": "boolean" }, "category7": { "type": "boolean" }, "category8": { "type": "boolean" }, "category9": { "type": "boolean" }, "category10": { "type": "boolean" }, "category11": { "type": "boolean" }, "category12": { "type": "boolean" }, "category13": { "type": "boolean" }, "category14": { "type": "boolean" }, "category15": { "type": "boolean" }, "category16": { "type": "boolean" }, "category17": { "type": "boolean" }, "category18": { "type": "boolean" }, "category19": { "type": "boolean" }, "category20": { "type": "boolean" }, "category21": { "type": "boolean" }, "category22": { "type": "boolean" }, "category23": { "type": "boolean" }, "category24": { "type": "boolean" }, "category25": { "type": "boolean" } } }</p>
<p>The <strong>Parse JSON</strong> step is important because it saves you having to write 25 expressions with null-handling logic in the next step.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652911792904/VfYKm_0uW.png" alt="task array creation" /></p>
<p>6.) Next, I used a <strong>Compose</strong> statement (and a lot of copy/pasting) to create the initial array for the task labels. Note the key/value format matches the "glossary" we created previously.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652912429052/rRKoSZl0P.png" alt="filtering nulls out of array" /></p>
<p>7.) Then we <strong>filter</strong> the task label array to remove any nodes containing nulls. Booleans are tricky when filtering arrays in Automate, so switch to advanced mode and use an expression:</p>
<p><code>@equals(item()?['value'], true)</code></p>
<p>This yields an array like this:</p>
<p>[ { "key": "category14", "value": true }, { "key": "category18", "value": true }, { "key": "category19", "value": true }, { "key": "category23", "value": true } ]</p>
<h3 id="heading-putting-it-all-together">Putting it All Together</h3>
<p>8.) Now, you've got matching arrays - both sides of the equation! Where you take it from here may vary depending on your solution needs.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652912740639/3c0hwcamA.png" alt="final iteration and array filtering" /></p>
<p>I needed each applied label as an individual record, so I <strong>looped</strong> through the newly-created task label array, <strong>filtering</strong> against the <em>glossary</em> array we created earlier with each iteration.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><strong>Start of Additional Information</strong> - October, 2023</div>
</div>

<p>A couple people reached out to ask for more details on step #8 recently.</p>
<p>Here are the details on the <em>For Each</em> action and the <em>Filter</em> step just inside. You may need to zoom in or click the picture to see smaller details:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697259257592/84962063-c316-4983-93a2-51369c552ed7.png" alt class="image--center mx-auto" /></p>
<p>This last part may vary, depending on how you want the label data structured in your flow. I packed each value into an object and rolled those into an array variable using an <em>Append</em> action:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697259569761/69284625-ffe9-4db6-a09b-70c2003222d9.png" alt class="image--center mx-auto" /></p>
<p>Here is the object, with each expression expanded. I include taskID since it's an effective way to relate child entities in Planner (labels, assignments, etc.) back to the parent:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"labelTitle"</span>: first(body('filter_plan_labels_with_task_labels'))?['value'],
  <span class="hljs-attr">"taskID"</span>: items('for_each_task')?['ID'],
  <span class="hljs-attr">"labelID"</span>: int(
                substring(
                    first(body('filter_plan_labels_with_task_labels'))?['key'],
                    8
                )
             )
}
</code></pre>
<p>The final stage for this workflow gathers up the data collected for the various Planner entities and ships it off to a separate flow for processing and storage.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697260427550/8bb0e481-4171-400c-8ba7-f20d42135be9.png" alt class="image--center mx-auto" /></p>
<p>I hope that helps!</p>
<p>There is a pretty fun story to be told about this particular solution, but I will save that for another article - don't want to get off track here tonight.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><strong>End of Additional Information</strong> - October. 2023</div>
</div>

<h2 id="heading-conclusion">Conclusion</h2>
<p>This was an interesting puzzle to solve, and the only real drawback was that it involved a fair amount of repetition. But now that it's done, I've got a robust solution that can be scaled considerably.</p>
<p>Some of the upper-echelon Automate junkies might be able to squeeze a <em>little</em> more speed out of this method, but overall it is quite performant. Using <strong>Compose</strong> actions instead of variables any time you can get away with it definitely helps. Automate handles arrays and array filtering very quickly, too.</p>
<h2 id="heading-flow-source">Flow Source</h2>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><strong>Additional Information Added</strong> - June, 2024</div>
</div>

<p>If you want to see a mermaid visual of the logic flow, please visit <a target="_blank" href="https://gist.github.com/jakemannion/a96d4575c70f520a54ab7689f69654cc">this GitHub Gist</a>.</p>
<p>And here is the (sanitized) source JSON for the flow:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"$schema"</span>: <span class="hljs-string">"https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#"</span>,
  <span class="hljs-attr">"contentVersion"</span>: <span class="hljs-string">"1.0.0.0"</span>,
  <span class="hljs-attr">"parameters"</span>: {
    <span class="hljs-attr">"$connections"</span>: {
      <span class="hljs-attr">"defaultValue"</span>: {},
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"Object"</span>
    },
    <span class="hljs-attr">"$authentication"</span>: {
      <span class="hljs-attr">"defaultValue"</span>: {},
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"SecureObject"</span>
    },
    <span class="hljs-attr">"bool_emailNotifications (jake_emailNotifications)"</span>: {
      <span class="hljs-attr">"defaultValue"</span>: <span class="hljs-literal">true</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"Bool"</span>,
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"schemaName"</span>: <span class="hljs-string">"jake_emailNotifications"</span>,
        <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Set to No if you want to turn off 'process complete' emails"</span>
      }
    },
    <span class="hljs-attr">"text_sqlServer (jake_sqlServer)"</span>: {
      <span class="hljs-attr">"defaultValue"</span>: <span class="hljs-string">"***SCRUBBED***"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"String"</span>,
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"schemaName"</span>: <span class="hljs-string">"jake_sqlServer"</span>
      }
    },
    <span class="hljs-attr">"text_sqlDatabase (jake_sqlTable)"</span>: {
      <span class="hljs-attr">"defaultValue"</span>: <span class="hljs-string">"***SCRUBBED***"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"String"</span>,
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"schemaName"</span>: <span class="hljs-string">"jake_sqlTable"</span>
      }
    },
    <span class="hljs-attr">"text_emailRecipients (jake_emailRecipients)"</span>: {
      <span class="hljs-attr">"defaultValue"</span>: <span class="hljs-string">"***SCRUBBED***"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"String"</span>,
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"schemaName"</span>: <span class="hljs-string">"jake_emailRecipients"</span>
      }
    },
    <span class="hljs-attr">"text_processFlowEndpoint (jake_test_postURL)"</span>: {
      <span class="hljs-attr">"defaultValue"</span>: <span class="hljs-string">"***SCRUBBED***"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"String"</span>,
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"schemaName"</span>: <span class="hljs-string">"jake_test_postURL"</span>,
        <span class="hljs-attr">"description"</span>: <span class="hljs-string">"endpoint for triggering processing sub flow "</span>
      }
    }
  },
  <span class="hljs-attr">"triggers"</span>: {
    <span class="hljs-attr">"Recurrence"</span>: {
      <span class="hljs-attr">"recurrence"</span>: {
        <span class="hljs-attr">"frequency"</span>: <span class="hljs-string">"Day"</span>,
        <span class="hljs-attr">"interval"</span>: <span class="hljs-number">1</span>,
        <span class="hljs-attr">"timeZone"</span>: <span class="hljs-string">"Pacific Standard Time"</span>,
        <span class="hljs-attr">"startTime"</span>: <span class="hljs-string">"2022-05-01T00:00:00Z"</span>,
        <span class="hljs-attr">"schedule"</span>: {
          <span class="hljs-attr">"hours"</span>: [
            <span class="hljs-string">"1"</span>
          ]
        }
      },
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"48f13d96-ccee-42b8-9de4-d9e92c683b79"</span>
      },
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"Recurrence"</span>
    }
  },
  <span class="hljs-attr">"actions"</span>: {
    <span class="hljs-attr">"init_assignmentArray"</span>: {
      <span class="hljs-attr">"runAfter"</span>: {
        <span class="hljs-attr">"init_taskArray"</span>: [
          <span class="hljs-string">"Succeeded"</span>
        ]
      },
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"ee36ea65-589b-4cbb-88f4-4d427253f226"</span>
      },
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"InitializeVariable"</span>,
      <span class="hljs-attr">"inputs"</span>: {
        <span class="hljs-attr">"variables"</span>: [
          {
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"assignmentArray"</span>,
            <span class="hljs-attr">"type"</span>: <span class="hljs-string">"array"</span>
          }
        ]
      }
    },
    <span class="hljs-attr">"init_taskObject"</span>: {
      <span class="hljs-attr">"runAfter"</span>: {
        <span class="hljs-attr">"init_errorCount"</span>: [
          <span class="hljs-string">"Succeeded"</span>
        ]
      },
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"df6b1fbf-f47b-42a4-ac2e-f6af361e2887"</span>
      },
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"InitializeVariable"</span>,
      <span class="hljs-attr">"inputs"</span>: {
        <span class="hljs-attr">"variables"</span>: [
          {
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"taskObject"</span>,
            <span class="hljs-attr">"type"</span>: <span class="hljs-string">"object"</span>,
            <span class="hljs-attr">"value"</span>: {
              <span class="hljs-attr">"taskID"</span>: <span class="hljs-string">""</span>,
              <span class="hljs-attr">"planID"</span>: <span class="hljs-string">""</span>,
              <span class="hljs-attr">"bucketID"</span>: <span class="hljs-string">""</span>,
              <span class="hljs-attr">"taskTitle"</span>: <span class="hljs-string">""</span>,
              <span class="hljs-attr">"planTitle"</span>: <span class="hljs-string">""</span>,
              <span class="hljs-attr">"bucketTitle"</span>: <span class="hljs-string">""</span>,
              <span class="hljs-attr">"taskDescription"</span>: <span class="hljs-string">""</span>,
              <span class="hljs-attr">"startDate"</span>: <span class="hljs-string">""</span>,
              <span class="hljs-attr">"createdDate"</span>: <span class="hljs-string">""</span>,
              <span class="hljs-attr">"dueDate"</span>: <span class="hljs-string">""</span>,
              <span class="hljs-attr">"completeDate"</span>: <span class="hljs-string">""</span>,
              <span class="hljs-attr">"createdByID"</span>: <span class="hljs-string">""</span>,
              <span class="hljs-attr">"orderHint"</span>: <span class="hljs-string">""</span>,
              <span class="hljs-attr">"pctComplete"</span>: <span class="hljs-string">""</span>
            }
          }
        ]
      }
    },
    <span class="hljs-attr">"init_errorString"</span>: {
      <span class="hljs-attr">"runAfter"</span>: {
        <span class="hljs-attr">"init_outcomeArray"</span>: [
          <span class="hljs-string">"Succeeded"</span>
        ]
      },
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"8b4796a3-4846-4fef-9c94-11860d9767c9"</span>
      },
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"InitializeVariable"</span>,
      <span class="hljs-attr">"inputs"</span>: {
        <span class="hljs-attr">"variables"</span>: [
          {
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"errorString"</span>,
            <span class="hljs-attr">"type"</span>: <span class="hljs-string">"string"</span>
          }
        ]
      }
    },
    <span class="hljs-attr">"init_taskArray"</span>: {
      <span class="hljs-attr">"runAfter"</span>: {
        <span class="hljs-attr">"init_taskObject"</span>: [
          <span class="hljs-string">"Succeeded"</span>
        ]
      },
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"242f3f77-c144-4d0e-a9f4-5dd030a23526"</span>
      },
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"InitializeVariable"</span>,
      <span class="hljs-attr">"inputs"</span>: {
        <span class="hljs-attr">"variables"</span>: [
          {
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"taskArray"</span>,
            <span class="hljs-attr">"type"</span>: <span class="hljs-string">"array"</span>
          }
        ]
      }
    },
    <span class="hljs-attr">"init_checklistArray"</span>: {
      <span class="hljs-attr">"runAfter"</span>: {
        <span class="hljs-attr">"init_labelArray"</span>: [
          <span class="hljs-string">"Succeeded"</span>
        ]
      },
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"a8e30162-dc67-40a2-aa2c-5c3d9234b896"</span>
      },
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"InitializeVariable"</span>,
      <span class="hljs-attr">"inputs"</span>: {
        <span class="hljs-attr">"variables"</span>: [
          {
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"checklistArray"</span>,
            <span class="hljs-attr">"type"</span>: <span class="hljs-string">"array"</span>
          }
        ]
      }
    },
    <span class="hljs-attr">"init_labelArray"</span>: {
      <span class="hljs-attr">"runAfter"</span>: {
        <span class="hljs-attr">"init_labelValuesArray"</span>: [
          <span class="hljs-string">"Succeeded"</span>
        ]
      },
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"fb3b0418-82c1-4001-8135-04c7bcde2a7b"</span>
      },
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"InitializeVariable"</span>,
      <span class="hljs-attr">"inputs"</span>: {
        <span class="hljs-attr">"variables"</span>: [
          {
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"labelArray"</span>,
            <span class="hljs-attr">"type"</span>: <span class="hljs-string">"array"</span>
          }
        ]
      }
    },
    <span class="hljs-attr">"init_attachmentArray"</span>: {
      <span class="hljs-attr">"runAfter"</span>: {
        <span class="hljs-attr">"init_assignmentArray"</span>: [
          <span class="hljs-string">"Succeeded"</span>
        ]
      },
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"40513abf-01c1-4699-8681-263ee9e882a4"</span>
      },
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"InitializeVariable"</span>,
      <span class="hljs-attr">"inputs"</span>: {
        <span class="hljs-attr">"variables"</span>: [
          {
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"attachmentArray"</span>,
            <span class="hljs-attr">"type"</span>: <span class="hljs-string">"array"</span>
          }
        ]
      }
    },
    <span class="hljs-attr">"init_labelValuesArray"</span>: {
      <span class="hljs-attr">"runAfter"</span>: {
        <span class="hljs-attr">"init_attachmentArray"</span>: [
          <span class="hljs-string">"Succeeded"</span>
        ]
      },
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"9d17111f-b964-422c-b416-9a3ee0b59349"</span>
      },
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"InitializeVariable"</span>,
      <span class="hljs-attr">"inputs"</span>: {
        <span class="hljs-attr">"variables"</span>: [
          {
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"labelValuesArray"</span>,
            <span class="hljs-attr">"type"</span>: <span class="hljs-string">"array"</span>
          }
        ]
      },
      <span class="hljs-attr">"description"</span>: <span class="hljs-string">"used for storing the label info for a given plan - dont think it's used"</span>
    },
    <span class="hljs-attr">"Plan_and_task_iteration"</span>: {
      <span class="hljs-attr">"actions"</span>: {
        <span class="hljs-attr">"for_each_plan"</span>: {
          <span class="hljs-attr">"foreach"</span>: <span class="hljs-string">"@outputs('get_active_plans')?['body/value']"</span>,
          <span class="hljs-attr">"actions"</span>: {
            <span class="hljs-attr">"List_buckets"</span>: {
              <span class="hljs-attr">"runAfter"</span>: {},
              <span class="hljs-attr">"metadata"</span>: {
                <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"0e8aa5dd-aac0-4784-b418-693f426629a2"</span>
              },
              <span class="hljs-attr">"type"</span>: <span class="hljs-string">"OpenApiConnection"</span>,
              <span class="hljs-attr">"inputs"</span>: {
                <span class="hljs-attr">"host"</span>: {
                  <span class="hljs-attr">"connectionName"</span>: <span class="hljs-string">"shared_planner"</span>,
                  <span class="hljs-attr">"operationId"</span>: <span class="hljs-string">"ListBuckets_V3"</span>,
                  <span class="hljs-attr">"apiId"</span>: <span class="hljs-string">"/providers/Microsoft.PowerApps/apis/shared_planner"</span>
                },
                <span class="hljs-attr">"parameters"</span>: {
                  <span class="hljs-attr">"groupId"</span>: <span class="hljs-string">"@items('for_each_plan')?['GroupID']"</span>,
                  <span class="hljs-attr">"id"</span>: <span class="hljs-string">"@items('for_each_plan')?['PlanID']"</span>
                },
                <span class="hljs-attr">"authentication"</span>: <span class="hljs-string">"@parameters('$authentication')"</span>
              }
            },
            <span class="hljs-attr">"List_tasks"</span>: {
              <span class="hljs-attr">"runAfter"</span>: {
                <span class="hljs-attr">"plan_label_values_array"</span>: [
                  <span class="hljs-string">"Succeeded"</span>
                ]
              },
              <span class="hljs-attr">"metadata"</span>: {
                <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"d7847aa4-8952-41c1-8f4e-533c880b946b"</span>
              },
              <span class="hljs-attr">"type"</span>: <span class="hljs-string">"OpenApiConnection"</span>,
              <span class="hljs-attr">"inputs"</span>: {
                <span class="hljs-attr">"host"</span>: {
                  <span class="hljs-attr">"connectionName"</span>: <span class="hljs-string">"shared_planner"</span>,
                  <span class="hljs-attr">"operationId"</span>: <span class="hljs-string">"ListTasks_V3"</span>,
                  <span class="hljs-attr">"apiId"</span>: <span class="hljs-string">"/providers/Microsoft.PowerApps/apis/shared_planner"</span>
                },
                <span class="hljs-attr">"parameters"</span>: {
                  <span class="hljs-attr">"groupId"</span>: <span class="hljs-string">"@items('for_each_plan')?['GroupID']"</span>,
                  <span class="hljs-attr">"id"</span>: <span class="hljs-string">"@items('for_each_plan')?['PlanID']"</span>
                },
                <span class="hljs-attr">"authentication"</span>: <span class="hljs-string">"@parameters('$authentication')"</span>
              }
            },
            <span class="hljs-attr">"for_each_task"</span>: {
              <span class="hljs-attr">"foreach"</span>: <span class="hljs-string">"@outputs('List_tasks')?['body/value']"</span>,
              <span class="hljs-attr">"actions"</span>: {
                <span class="hljs-attr">"filter_bucket_array"</span>: {
                  <span class="hljs-attr">"runAfter"</span>: {},
                  <span class="hljs-attr">"metadata"</span>: {
                    <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"bab69193-9ca3-4715-8d61-254f7fa86136"</span>
                  },
                  <span class="hljs-attr">"type"</span>: <span class="hljs-string">"Query"</span>,
                  <span class="hljs-attr">"inputs"</span>: {
                    <span class="hljs-attr">"from"</span>: <span class="hljs-string">"@outputs('List_buckets')?['body/value']"</span>,
                    <span class="hljs-attr">"where"</span>: <span class="hljs-string">"@equals(item()?['id'], items('for_each_task')?['bucketId'])"</span>
                  }
                },
                <span class="hljs-attr">"is_more_task_detail_needed"</span>: {
                  <span class="hljs-attr">"actions"</span>: {
                    <span class="hljs-attr">"Get_task_details"</span>: {
                      <span class="hljs-attr">"runAfter"</span>: {},
                      <span class="hljs-attr">"metadata"</span>: {
                        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"40bd4ba4-1fe8-4566-9c90-783a1a028b88"</span>
                      },
                      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"OpenApiConnection"</span>,
                      <span class="hljs-attr">"inputs"</span>: {
                        <span class="hljs-attr">"host"</span>: {
                          <span class="hljs-attr">"connectionName"</span>: <span class="hljs-string">"shared_planner"</span>,
                          <span class="hljs-attr">"operationId"</span>: <span class="hljs-string">"GetTaskDetails_V2"</span>,
                          <span class="hljs-attr">"apiId"</span>: <span class="hljs-string">"/providers/Microsoft.PowerApps/apis/shared_planner"</span>
                        },
                        <span class="hljs-attr">"parameters"</span>: {
                          <span class="hljs-attr">"id"</span>: <span class="hljs-string">"@items('for_each_task')?['id']"</span>
                        },
                        <span class="hljs-attr">"authentication"</span>: <span class="hljs-string">"@parameters('$authentication')"</span>
                      }
                    },
                    <span class="hljs-attr">"for_each_checklist_item"</span>: {
                      <span class="hljs-attr">"foreach"</span>: <span class="hljs-string">"@outputs('Get_task_details')?['body/checklist']"</span>,
                      <span class="hljs-attr">"actions"</span>: {
                        <span class="hljs-attr">"append_to_checklist_array"</span>: {
                          <span class="hljs-attr">"runAfter"</span>: {},
                          <span class="hljs-attr">"metadata"</span>: {
                            <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"77340521-feb2-4cf3-8a6d-00f31d7277d1"</span>
                          },
                          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"AppendToArrayVariable"</span>,
                          <span class="hljs-attr">"inputs"</span>: {
                            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"checklistArray"</span>,
                            <span class="hljs-attr">"value"</span>: {
                              <span class="hljs-attr">"itemTitle"</span>: <span class="hljs-string">"@items('for_each_checklist_item')?['value/title']"</span>,
                              <span class="hljs-attr">"itemComplete"</span>: <span class="hljs-string">"@items('for_each_checklist_item')?['value/isChecked']"</span>,
                              <span class="hljs-attr">"taskID"</span>: <span class="hljs-string">"@items('for_each_task')?['id']"</span>,
                              <span class="hljs-attr">"orderHint"</span>: <span class="hljs-string">"@items('for_each_checklist_item')?['value/orderHint']"</span>,
                              <span class="hljs-attr">"itemID"</span>: <span class="hljs-string">"@int( items('for_each_checklist_item')?['id'] )"</span>,
                              <span class="hljs-attr">"modifiedByID"</span>: <span class="hljs-string">"@items('for_each_checklist_item')?['value/lastModifiedBy/user/id']"</span>,
                              <span class="hljs-attr">"modifiedDate"</span>: <span class="hljs-string">"@items('for_each_checklist_item')?['value/lastModifiedDateTime']"</span>
                            }
                          }
                        }
                      },
                      <span class="hljs-attr">"runAfter"</span>: {
                        <span class="hljs-attr">"append_taskObject_to_taskArray_detailed"</span>: [
                          <span class="hljs-string">"Succeeded"</span>
                        ]
                      },
                      <span class="hljs-attr">"metadata"</span>: {
                        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"491f333b-8f0c-4b00-90c9-f19c10948a6c"</span>
                      },
                      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"Foreach"</span>,
                      <span class="hljs-attr">"runtimeConfiguration"</span>: {
                        <span class="hljs-attr">"concurrency"</span>: {
                          <span class="hljs-attr">"repetitions"</span>: <span class="hljs-number">50</span>
                        }
                      }
                    },
                    <span class="hljs-attr">"for_each_attachment"</span>: {
                      <span class="hljs-attr">"foreach"</span>: <span class="hljs-string">"@outputs('Get_task_details')?['body/references']"</span>,
                      <span class="hljs-attr">"actions"</span>: {
                        <span class="hljs-attr">"append_to_attachmentArray"</span>: {
                          <span class="hljs-attr">"runAfter"</span>: {},
                          <span class="hljs-attr">"metadata"</span>: {
                            <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"23902441-6a89-4dd2-b1eb-6b2068053cb7"</span>
                          },
                          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"AppendToArrayVariable"</span>,
                          <span class="hljs-attr">"inputs"</span>: {
                            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"attachmentArray"</span>,
                            <span class="hljs-attr">"value"</span>: {
                              <span class="hljs-attr">"createdByID"</span>: <span class="hljs-string">"@items('for_each_attachment')?['value/lastModifiedBy/user/id']"</span>,
                              <span class="hljs-attr">"attachmentTitle"</span>: <span class="hljs-string">"@items('for_each_attachment')?['value/alias']"</span>,
                              <span class="hljs-attr">"attachmentLink"</span>: <span class="hljs-string">"@items('for_each_attachment')?['resourceLink']"</span>,
                              <span class="hljs-attr">"taskID"</span>: <span class="hljs-string">"@items('for_each_task')?['id']"</span>
                            }
                          }
                        }
                      },
                      <span class="hljs-attr">"runAfter"</span>: {
                        <span class="hljs-attr">"for_each_checklist_item"</span>: [
                          <span class="hljs-string">"Succeeded"</span>
                        ]
                      },
                      <span class="hljs-attr">"metadata"</span>: {
                        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"1db02863-5401-4ccb-a543-835d83505745"</span>
                      },
                      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"Foreach"</span>,
                      <span class="hljs-attr">"runtimeConfiguration"</span>: {
                        <span class="hljs-attr">"concurrency"</span>: {
                          <span class="hljs-attr">"repetitions"</span>: <span class="hljs-number">50</span>
                        }
                      }
                    },
                    <span class="hljs-attr">"append_taskObject_to_taskArray_detailed"</span>: {
                      <span class="hljs-attr">"runAfter"</span>: {
                        <span class="hljs-attr">"Get_task_details"</span>: [
                          <span class="hljs-string">"Succeeded"</span>
                        ]
                      },
                      <span class="hljs-attr">"metadata"</span>: {
                        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"8a962b72-e609-496a-b789-05e80f48f3ee"</span>
                      },
                      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"AppendToArrayVariable"</span>,
                      <span class="hljs-attr">"inputs"</span>: {
                        <span class="hljs-attr">"name"</span>: <span class="hljs-string">"taskArray"</span>,
                        <span class="hljs-attr">"value"</span>: {
                          <span class="hljs-attr">"taskID"</span>: <span class="hljs-string">"@items('for_each_task')?['ID']"</span>,
                          <span class="hljs-attr">"planID"</span>: <span class="hljs-string">"@items('for_each_task')?['planId']"</span>,
                          <span class="hljs-attr">"bucketID"</span>: <span class="hljs-string">"@items('for_each_task')?['bucketId']"</span>,
                          <span class="hljs-attr">"taskTitle"</span>: <span class="hljs-string">"@items('for_each_task')?['title']"</span>,
                          <span class="hljs-attr">"planTitle"</span>: <span class="hljs-string">"@items('for_each_plan')?['PlanName']"</span>,
                          <span class="hljs-attr">"bucketTitle"</span>: <span class="hljs-string">"@first(body('filter_bucket_array'))?['name']"</span>,
                          <span class="hljs-attr">"taskDescription"</span>: <span class="hljs-string">"@outputs('Get_task_details')?['body/description']"</span>,
                          <span class="hljs-attr">"startDate"</span>: <span class="hljs-string">"@items('for_each_task')?['startDateTime']"</span>,
                          <span class="hljs-attr">"createdDate"</span>: <span class="hljs-string">"@items('for_each_task')?['createdDateTime']"</span>,
                          <span class="hljs-attr">"dueDate"</span>: <span class="hljs-string">"@items('for_each_task')?['dueDateTime']"</span>,
                          <span class="hljs-attr">"completeDate"</span>: <span class="hljs-string">"@items('for_each_task')?['completedDateTime']"</span>,
                          <span class="hljs-attr">"createdByID"</span>: <span class="hljs-string">"@items('for_each_task')?['createdBy/user/id']"</span>,
                          <span class="hljs-attr">"orderHint"</span>: <span class="hljs-string">"@items('for_each_task')?['orderHint']"</span>,
                          <span class="hljs-attr">"pctComplete"</span>: <span class="hljs-string">"@items('for_each_task')?['percentComplete']"</span>
                        }
                      }
                    }
                  },
                  <span class="hljs-attr">"runAfter"</span>: {
                    <span class="hljs-attr">"filter_bucket_array"</span>: [
                      <span class="hljs-string">"Succeeded"</span>
                    ]
                  },
                  <span class="hljs-attr">"else"</span>: {
                    <span class="hljs-attr">"actions"</span>: {
                      <span class="hljs-attr">"append_taskObject_to_taskArray"</span>: {
                        <span class="hljs-attr">"runAfter"</span>: {},
                        <span class="hljs-attr">"metadata"</span>: {
                          <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"8a962b72-e609-496a-b789-05e80f48f3ee"</span>
                        },
                        <span class="hljs-attr">"type"</span>: <span class="hljs-string">"AppendToArrayVariable"</span>,
                        <span class="hljs-attr">"inputs"</span>: {
                          <span class="hljs-attr">"name"</span>: <span class="hljs-string">"taskArray"</span>,
                          <span class="hljs-attr">"value"</span>: {
                            <span class="hljs-attr">"taskID"</span>: <span class="hljs-string">"@items('for_each_task')?['ID']"</span>,
                            <span class="hljs-attr">"planID"</span>: <span class="hljs-string">"@items('for_each_task')?['planId']"</span>,
                            <span class="hljs-attr">"bucketID"</span>: <span class="hljs-string">"@items('for_each_task')?['bucketId']"</span>,
                            <span class="hljs-attr">"taskTitle"</span>: <span class="hljs-string">"@items('for_each_task')?['title']"</span>,
                            <span class="hljs-attr">"planTitle"</span>: <span class="hljs-string">"@items('for_each_plan')?['PlanName']"</span>,
                            <span class="hljs-attr">"bucketTitle"</span>: <span class="hljs-string">"@first(\n    body('filter_bucket_array')\n    )?['name']"</span>,
                            <span class="hljs-attr">"taskDescription"</span>: <span class="hljs-string">""</span>,
                            <span class="hljs-attr">"startDate"</span>: <span class="hljs-string">"@items('for_each_task')?['startDateTime']"</span>,
                            <span class="hljs-attr">"createdDate"</span>: <span class="hljs-string">"@items('for_each_task')?['createdDateTime']"</span>,
                            <span class="hljs-attr">"dueDate"</span>: <span class="hljs-string">"@items('for_each_task')?['dueDateTime']"</span>,
                            <span class="hljs-attr">"completeDate"</span>: <span class="hljs-string">"@items('for_each_task')?['completedDateTime']"</span>,
                            <span class="hljs-attr">"createdByID"</span>: <span class="hljs-string">"@items('for_each_task')?['createdBy/user/id']"</span>,
                            <span class="hljs-attr">"orderHint"</span>: <span class="hljs-string">"@items('for_each_task')?['orderHint']"</span>,
                            <span class="hljs-attr">"pctComplete"</span>: <span class="hljs-string">"@items('for_each_task')?['percentComplete']"</span>
                          }
                        }
                      }
                    }
                  },
                  <span class="hljs-attr">"expression"</span>: {
                    <span class="hljs-attr">"or"</span>: [
                      {
                        <span class="hljs-attr">"equals"</span>: [
                          <span class="hljs-string">"@items('for_each_task')?['hasDescription']"</span>,
                          <span class="hljs-literal">true</span>
                        ]
                      },
                      {
                        <span class="hljs-attr">"greater"</span>: [
                          <span class="hljs-string">"@items('for_each_task')?['referenceCount']"</span>,
                          <span class="hljs-number">0</span>
                        ]
                      },
                      {
                        <span class="hljs-attr">"greater"</span>: [
                          <span class="hljs-string">"@items('for_each_task')?['checklistItemCount']"</span>,
                          <span class="hljs-number">0</span>
                        ]
                      }
                    ]
                  },
                  <span class="hljs-attr">"metadata"</span>: {
                    <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"b8b0c2c6-d713-480e-9669-b824959b2c54"</span>
                  },
                  <span class="hljs-attr">"type"</span>: <span class="hljs-string">"If"</span>,
                  <span class="hljs-attr">"description"</span>: <span class="hljs-string">"if a task has no description, checklist, or attachments, we don't need to run 'Get task details'"</span>
                },
                <span class="hljs-attr">"for_each_assignment"</span>: {
                  <span class="hljs-attr">"foreach"</span>: <span class="hljs-string">"@items('for_each_task')?['_assignments']"</span>,
                  <span class="hljs-attr">"actions"</span>: {
                    <span class="hljs-attr">"append_to_assignmentArray"</span>: {
                      <span class="hljs-attr">"runAfter"</span>: {},
                      <span class="hljs-attr">"metadata"</span>: {
                        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"1d93b63a-b9e4-4c3b-8667-84e4845b2ed3"</span>
                      },
                      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"AppendToArrayVariable"</span>,
                      <span class="hljs-attr">"inputs"</span>: {
                        <span class="hljs-attr">"name"</span>: <span class="hljs-string">"assignmentArray"</span>,
                        <span class="hljs-attr">"value"</span>: {
                          <span class="hljs-attr">"taskID"</span>: <span class="hljs-string">"@items('for_each_task')?['id']"</span>,
                          <span class="hljs-attr">"assignedToID"</span>: <span class="hljs-string">"@items('for_each_assignment')?['userId']"</span>
                        }
                      }
                    }
                  },
                  <span class="hljs-attr">"runAfter"</span>: {
                    <span class="hljs-attr">"is_more_task_detail_needed"</span>: [
                      <span class="hljs-string">"Succeeded"</span>
                    ]
                  },
                  <span class="hljs-attr">"metadata"</span>: {
                    <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"0aacd0d8-1c14-46c3-9ac5-80431deb8100"</span>
                  },
                  <span class="hljs-attr">"type"</span>: <span class="hljs-string">"Foreach"</span>,
                  <span class="hljs-attr">"runtimeConfiguration"</span>: {
                    <span class="hljs-attr">"concurrency"</span>: {
                      <span class="hljs-attr">"repetitions"</span>: <span class="hljs-number">50</span>
                    }
                  }
                },
                <span class="hljs-attr">"task_labels_object"</span>: {
                  <span class="hljs-attr">"runAfter"</span>: {
                    <span class="hljs-attr">"for_each_assignment"</span>: [
                      <span class="hljs-string">"Succeeded"</span>
                    ]
                  },
                  <span class="hljs-attr">"metadata"</span>: {
                    <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"1bd3d278-4616-405e-a76d-f9f1bb457947"</span>
                  },
                  <span class="hljs-attr">"type"</span>: <span class="hljs-string">"Compose"</span>,
                  <span class="hljs-attr">"inputs"</span>: <span class="hljs-string">"@items('for_each_task')?['appliedCategories']"</span>
                },
                <span class="hljs-attr">"Parse_JSON_for_task_labels"</span>: {
                  <span class="hljs-attr">"runAfter"</span>: {
                    <span class="hljs-attr">"task_labels_object"</span>: [
                      <span class="hljs-string">"Succeeded"</span>
                    ]
                  },
                  <span class="hljs-attr">"metadata"</span>: {
                    <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"c7ce52b2-6115-4ad8-81f1-107cc7efed71"</span>
                  },
                  <span class="hljs-attr">"type"</span>: <span class="hljs-string">"ParseJson"</span>,
                  <span class="hljs-attr">"inputs"</span>: {
                    <span class="hljs-attr">"content"</span>: <span class="hljs-string">"@outputs('task_labels_object')"</span>,
                    <span class="hljs-attr">"schema"</span>: {
                      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"object"</span>,
                      <span class="hljs-attr">"properties"</span>: {
                        <span class="hljs-attr">"category1"</span>: {
                          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"boolean"</span>
                        },
                        <span class="hljs-attr">"category2"</span>: {
                          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"boolean"</span>
                        },
                        <span class="hljs-attr">"category3"</span>: {
                          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"boolean"</span>
                        },
                        <span class="hljs-attr">"category4"</span>: {
                          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"boolean"</span>
                        },
                        <span class="hljs-attr">"category5"</span>: {
                          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"boolean"</span>
                        },
                        <span class="hljs-attr">"category6"</span>: {
                          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"boolean"</span>
                        },
                        <span class="hljs-attr">"category7"</span>: {
                          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"boolean"</span>
                        },
                        <span class="hljs-attr">"category8"</span>: {
                          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"boolean"</span>
                        },
                        <span class="hljs-attr">"category9"</span>: {
                          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"boolean"</span>
                        },
                        <span class="hljs-attr">"category10"</span>: {
                          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"boolean"</span>
                        },
                        <span class="hljs-attr">"category11"</span>: {
                          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"boolean"</span>
                        },
                        <span class="hljs-attr">"category12"</span>: {
                          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"boolean"</span>
                        },
                        <span class="hljs-attr">"category13"</span>: {
                          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"boolean"</span>
                        },
                        <span class="hljs-attr">"category14"</span>: {
                          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"boolean"</span>
                        },
                        <span class="hljs-attr">"category15"</span>: {
                          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"boolean"</span>
                        },
                        <span class="hljs-attr">"category16"</span>: {
                          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"boolean"</span>
                        },
                        <span class="hljs-attr">"category17"</span>: {
                          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"boolean"</span>
                        },
                        <span class="hljs-attr">"category18"</span>: {
                          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"boolean"</span>
                        },
                        <span class="hljs-attr">"category19"</span>: {
                          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"boolean"</span>
                        },
                        <span class="hljs-attr">"category20"</span>: {
                          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"boolean"</span>
                        },
                        <span class="hljs-attr">"category21"</span>: {
                          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"boolean"</span>
                        },
                        <span class="hljs-attr">"category22"</span>: {
                          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"boolean"</span>
                        },
                        <span class="hljs-attr">"category23"</span>: {
                          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"boolean"</span>
                        },
                        <span class="hljs-attr">"category24"</span>: {
                          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"boolean"</span>
                        },
                        <span class="hljs-attr">"category25"</span>: {
                          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"boolean"</span>
                        }
                      }
                    }
                  }
                },
                <span class="hljs-attr">"task_labels_array"</span>: {
                  <span class="hljs-attr">"runAfter"</span>: {
                    <span class="hljs-attr">"Parse_JSON_for_task_labels"</span>: [
                      <span class="hljs-string">"Succeeded"</span>
                    ]
                  },
                  <span class="hljs-attr">"metadata"</span>: {
                    <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"65d680cc-2ba7-4198-a3ef-007d5841dcf0"</span>
                  },
                  <span class="hljs-attr">"type"</span>: <span class="hljs-string">"Compose"</span>,
                  <span class="hljs-attr">"inputs"</span>: [
                    {
                      <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category1"</span>,
                      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@body('Parse_JSON_for_task_labels')?['category1']"</span>
                    },
                    {
                      <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category2"</span>,
                      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@body('Parse_JSON_for_task_labels')?['category2']"</span>
                    },
                    {
                      <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category3"</span>,
                      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@body('Parse_JSON_for_task_labels')?['category3']"</span>
                    },
                    {
                      <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category4"</span>,
                      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@body('Parse_JSON_for_task_labels')?['category4']"</span>
                    },
                    {
                      <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category5"</span>,
                      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@body('Parse_JSON_for_task_labels')?['category5']"</span>
                    },
                    {
                      <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category6"</span>,
                      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@body('Parse_JSON_for_task_labels')?['category6']"</span>
                    },
                    {
                      <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category7"</span>,
                      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@body('Parse_JSON_for_task_labels')?['category7']"</span>
                    },
                    {
                      <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category8"</span>,
                      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@body('Parse_JSON_for_task_labels')?['category8']"</span>
                    },
                    {
                      <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category9"</span>,
                      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@body('Parse_JSON_for_task_labels')?['category9']"</span>
                    },
                    {
                      <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category10"</span>,
                      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@body('Parse_JSON_for_task_labels')?['category10']"</span>
                    },
                    {
                      <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category11"</span>,
                      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@body('Parse_JSON_for_task_labels')?['category11']"</span>
                    },
                    {
                      <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category12"</span>,
                      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@body('Parse_JSON_for_task_labels')?['category12']"</span>
                    },
                    {
                      <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category13"</span>,
                      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@body('Parse_JSON_for_task_labels')?['category13']"</span>
                    },
                    {
                      <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category14"</span>,
                      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@body('Parse_JSON_for_task_labels')?['category14']"</span>
                    },
                    {
                      <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category15"</span>,
                      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@body('Parse_JSON_for_task_labels')?['category15']"</span>
                    },
                    {
                      <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category16"</span>,
                      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@body('Parse_JSON_for_task_labels')?['category16']"</span>
                    },
                    {
                      <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category17"</span>,
                      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@body('Parse_JSON_for_task_labels')?['category17']"</span>
                    },
                    {
                      <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category18"</span>,
                      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@body('Parse_JSON_for_task_labels')?['category18']"</span>
                    },
                    {
                      <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category19"</span>,
                      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@body('Parse_JSON_for_task_labels')?['category19']"</span>
                    },
                    {
                      <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category20"</span>,
                      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@body('Parse_JSON_for_task_labels')?['category20']"</span>
                    },
                    {
                      <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category21"</span>,
                      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@body('Parse_JSON_for_task_labels')?['category21']"</span>
                    },
                    {
                      <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category22"</span>,
                      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@body('Parse_JSON_for_task_labels')?['category22']"</span>
                    },
                    {
                      <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category23"</span>,
                      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@body('Parse_JSON_for_task_labels')?['category23']"</span>
                    },
                    {
                      <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category24"</span>,
                      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@body('Parse_JSON_for_task_labels')?['category24']"</span>
                    },
                    {
                      <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category25"</span>,
                      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@body('Parse_JSON_for_task_labels')?['category25']"</span>
                    }
                  ]
                },
                <span class="hljs-attr">"for_each_task_label"</span>: {
                  <span class="hljs-attr">"foreach"</span>: <span class="hljs-string">"@body('filter_task_labels_array')"</span>,
                  <span class="hljs-attr">"actions"</span>: {
                    <span class="hljs-attr">"filter_plan_labels_with_task_labels"</span>: {
                      <span class="hljs-attr">"runAfter"</span>: {},
                      <span class="hljs-attr">"metadata"</span>: {
                        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"a9bad329-fa19-41ff-8533-527fdba98367"</span>
                      },
                      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"Query"</span>,
                      <span class="hljs-attr">"inputs"</span>: {
                        <span class="hljs-attr">"from"</span>: <span class="hljs-string">"@outputs('plan_label_values_array')"</span>,
                        <span class="hljs-attr">"where"</span>: <span class="hljs-string">"@equals(item()?['key'], items('for_each_task_label')?['key'])"</span>
                      }
                    },
                    <span class="hljs-attr">"append_to_labelArray"</span>: {
                      <span class="hljs-attr">"runAfter"</span>: {
                        <span class="hljs-attr">"filter_plan_labels_with_task_labels"</span>: [
                          <span class="hljs-string">"Succeeded"</span>
                        ]
                      },
                      <span class="hljs-attr">"metadata"</span>: {
                        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"4b7a974e-2343-4abc-b9fe-ca9ce06465fc"</span>
                      },
                      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"AppendToArrayVariable"</span>,
                      <span class="hljs-attr">"inputs"</span>: {
                        <span class="hljs-attr">"name"</span>: <span class="hljs-string">"labelArray"</span>,
                        <span class="hljs-attr">"value"</span>: {
                          <span class="hljs-attr">"labelTitle"</span>: <span class="hljs-string">"@first(body('filter_plan_labels_with_task_labels'))?['value']"</span>,
                          <span class="hljs-attr">"taskID"</span>: <span class="hljs-string">"@items('for_each_task')?['ID']"</span>,
                          <span class="hljs-attr">"labelID"</span>: <span class="hljs-string">"@int(\r\n    substring(\r\n        first(body('filter_plan_labels_with_task_labels'))?['key'],\r\n        8\r\n    )\r\n)"</span>
                        }
                      }
                    }
                  },
                  <span class="hljs-attr">"runAfter"</span>: {
                    <span class="hljs-attr">"filter_task_labels_array"</span>: [
                      <span class="hljs-string">"Succeeded"</span>
                    ]
                  },
                  <span class="hljs-attr">"metadata"</span>: {
                    <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"503bf544-b0e9-433c-82f5-12ffaedbc419"</span>
                  },
                  <span class="hljs-attr">"type"</span>: <span class="hljs-string">"Foreach"</span>,
                  <span class="hljs-attr">"runtimeConfiguration"</span>: {
                    <span class="hljs-attr">"concurrency"</span>: {
                      <span class="hljs-attr">"repetitions"</span>: <span class="hljs-number">50</span>
                    }
                  }
                },
                <span class="hljs-attr">"filter_task_labels_array"</span>: {
                  <span class="hljs-attr">"runAfter"</span>: {
                    <span class="hljs-attr">"task_labels_array"</span>: [
                      <span class="hljs-string">"Succeeded"</span>
                    ]
                  },
                  <span class="hljs-attr">"metadata"</span>: {
                    <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"ab454f83-d0f0-4956-9311-40840ab8ca9e"</span>
                  },
                  <span class="hljs-attr">"type"</span>: <span class="hljs-string">"Query"</span>,
                  <span class="hljs-attr">"inputs"</span>: {
                    <span class="hljs-attr">"from"</span>: <span class="hljs-string">"@outputs('task_labels_array')"</span>,
                    <span class="hljs-attr">"where"</span>: <span class="hljs-string">"@equals(item()?['value'], true)"</span>
                  }
                },
                <span class="hljs-attr">"if_label_25_support_is_true"</span>: {
                  <span class="hljs-attr">"actions"</span>: {
                    <span class="hljs-attr">"if_task_not_complete"</span>: {
                      <span class="hljs-attr">"actions"</span>: {
                        <span class="hljs-attr">"link_to_this_task"</span>: {
                          <span class="hljs-attr">"runAfter"</span>: {},
                          <span class="hljs-attr">"metadata"</span>: {
                            <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"559bc95a-b489-45af-8d41-5b6e96875fa7"</span>
                          },
                          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"Compose"</span>,
                          <span class="hljs-attr">"inputs"</span>: <span class="hljs-string">"&lt;a href=\"https://tasks.office.com/***SCRUBBED***/Home/Task/@{outputs('Get_task_details')?['body/id']}\"&gt;@{items('for_each_task')?['title']}&lt;/a&gt;"</span>
                        },
                        <span class="hljs-attr">"Post_message_in_a_chat_or_channel"</span>: {
                          <span class="hljs-attr">"runAfter"</span>: {
                            <span class="hljs-attr">"link_to_this_task"</span>: [
                              <span class="hljs-string">"Succeeded"</span>
                            ]
                          },
                          <span class="hljs-attr">"metadata"</span>: {
                            <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"e6abb82c-7b65-42e2-99b6-19cc68093c5c"</span>
                          },
                          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"OpenApiConnection"</span>,
                          <span class="hljs-attr">"inputs"</span>: {
                            <span class="hljs-attr">"host"</span>: {
                              <span class="hljs-attr">"connectionName"</span>: <span class="hljs-string">"shared_teams_1"</span>,
                              <span class="hljs-attr">"operationId"</span>: <span class="hljs-string">"PostMessageToConversation"</span>,
                              <span class="hljs-attr">"apiId"</span>: <span class="hljs-string">"/providers/Microsoft.PowerApps/apis/shared_teams"</span>
                            },
                            <span class="hljs-attr">"parameters"</span>: {
                              <span class="hljs-attr">"poster"</span>: <span class="hljs-string">"Flow bot"</span>,
                              <span class="hljs-attr">"location"</span>: <span class="hljs-string">"Channel"</span>,
                              <span class="hljs-attr">"body/recipient/groupId"</span>: <span class="hljs-string">"***SCRUBBED***"</span>,
                              <span class="hljs-attr">"body/recipient/channelId"</span>: <span class="hljs-string">"***SCRUBBED***"</span>,
                              <span class="hljs-attr">"body/messageBody"</span>: <span class="hljs-string">"&lt;p&gt;There has been a request for help from a task on the @{items('for_each_plan')?['PlanName']} (@{items('for_each_plan')?['Team']}) board in Planner.&lt;br&gt;\n&lt;br&gt;\n&lt;span style=\"font-size: 16px\"&gt;&lt;/span&gt;&lt;span style=\"font-size: 16px\"&gt;@{outputs('link_to_this_task')}&lt;/span&gt;&lt;span style=\"font-size: 16px\"&gt;&lt;/span&gt;&lt;/p&gt;"</span>
                            },
                            <span class="hljs-attr">"authentication"</span>: <span class="hljs-string">"@parameters('$authentication')"</span>
                          }
                        },
                        <span class="hljs-attr">"log_help_request"</span>: {
                          <span class="hljs-attr">"runAfter"</span>: {
                            <span class="hljs-attr">"Post_message_in_a_chat_or_channel"</span>: [
                              <span class="hljs-string">"Succeeded"</span>
                            ]
                          },
                          <span class="hljs-attr">"metadata"</span>: {
                            <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"57d990aa-2533-4875-a43a-032328619dca"</span>
                          },
                          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"OpenApiConnection"</span>,
                          <span class="hljs-attr">"inputs"</span>: {
                            <span class="hljs-attr">"host"</span>: {
                              <span class="hljs-attr">"connectionName"</span>: <span class="hljs-string">"shared_sql"</span>,
                              <span class="hljs-attr">"operationId"</span>: <span class="hljs-string">"PostItem_V2"</span>,
                              <span class="hljs-attr">"apiId"</span>: <span class="hljs-string">"/providers/Microsoft.PowerApps/apis/shared_sql"</span>
                            },
                            <span class="hljs-attr">"parameters"</span>: {
                              <span class="hljs-attr">"server"</span>: <span class="hljs-string">"***SCRUBBED***"</span>,
                              <span class="hljs-attr">"database"</span>: <span class="hljs-string">"***SCRUBBED***"</span>,
                              <span class="hljs-attr">"table"</span>: <span class="hljs-string">"***SCRUBBED***"</span>,
                              <span class="hljs-attr">"item/workflowType"</span>: <span class="hljs-string">"taskRetrieve"</span>,
                              <span class="hljs-attr">"item/outcome"</span>: <span class="hljs-string">"Support Request"</span>,
                              <span class="hljs-attr">"item/details"</span>: <span class="hljs-string">"Task: @{items('for_each_task')?['title']}\nPlan: @{items('for_each_plan')?['PlanName']}\nTeam: @{items('for_each_plan')?['Team']}"</span>,
                              <span class="hljs-attr">"item/startTime"</span>: <span class="hljs-string">"@body('Start_time_PST')"</span>
                            },
                            <span class="hljs-attr">"authentication"</span>: <span class="hljs-string">"@parameters('$authentication')"</span>
                          }
                        }
                      },
                      <span class="hljs-attr">"runAfter"</span>: {},
                      <span class="hljs-attr">"expression"</span>: {
                        <span class="hljs-attr">"not"</span>: {
                          <span class="hljs-attr">"equals"</span>: [
                            <span class="hljs-string">"@items('for_each_task')?['percentComplete']"</span>,
                            <span class="hljs-number">100</span>
                          ]
                        }
                      },
                      <span class="hljs-attr">"metadata"</span>: {
                        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"97078d0f-d82e-4382-9501-5f5da3d914d6"</span>
                      },
                      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"If"</span>
                    }
                  },
                  <span class="hljs-attr">"runAfter"</span>: {
                    <span class="hljs-attr">"for_each_task_label"</span>: [
                      <span class="hljs-string">"Succeeded"</span>
                    ]
                  },
                  <span class="hljs-attr">"expression"</span>: {
                    <span class="hljs-attr">"equals"</span>: [
                      <span class="hljs-string">"@body('Parse_JSON_for_task_labels')?['category25']"</span>,
                      <span class="hljs-string">"@true"</span>
                    ]
                  },
                  <span class="hljs-attr">"metadata"</span>: {
                    <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"d9694eb5-44b2-4dfc-87aa-445050326d1c"</span>
                  },
                  <span class="hljs-attr">"type"</span>: <span class="hljs-string">"If"</span>
                }
              },
              <span class="hljs-attr">"runAfter"</span>: {
                <span class="hljs-attr">"List_tasks"</span>: [
                  <span class="hljs-string">"Succeeded"</span>
                ]
              },
              <span class="hljs-attr">"metadata"</span>: {
                <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"c62e1965-b11e-4ba9-bd86-d226d0c6aa8b"</span>
              },
              <span class="hljs-attr">"type"</span>: <span class="hljs-string">"Foreach"</span>,
              <span class="hljs-attr">"runtimeConfiguration"</span>: {
                <span class="hljs-attr">"concurrency"</span>: {
                  <span class="hljs-attr">"repetitions"</span>: <span class="hljs-number">50</span>
                }
              }
            },
            <span class="hljs-attr">"plan_label_values_array"</span>: {
              <span class="hljs-attr">"runAfter"</span>: {
                <span class="hljs-attr">"Get_plan_details"</span>: [
                  <span class="hljs-string">"Succeeded"</span>
                ]
              },
              <span class="hljs-attr">"metadata"</span>: {
                <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"6d9990c3-257a-44a3-b7dc-4a290f5d9bc5"</span>
              },
              <span class="hljs-attr">"type"</span>: <span class="hljs-string">"Compose"</span>,
              <span class="hljs-attr">"inputs"</span>: [
                {
                  <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category1"</span>,
                  <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@coalesce(\r\n    outputs('Get_plan_details')?['body/categoryDescriptions/category1'],\r\n    'Pink'\r\n)"</span>
                },
                {
                  <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category2"</span>,
                  <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@coalesce(\r\n    outputs('Get_plan_details')?['body/categoryDescriptions/category2'],\r\n    'Red'\r\n)"</span>
                },
                {
                  <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category3"</span>,
                  <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@coalesce(\r\n    outputs('Get_plan_details')?['body/categoryDescriptions/category3'],\r\n    'Yellow'\r\n)"</span>
                },
                {
                  <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category4"</span>,
                  <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@coalesce(\r\n    outputs('Get_plan_details')?['body/categoryDescriptions/category4'],\r\n    'Green'\r\n)"</span>
                },
                {
                  <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category5"</span>,
                  <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@coalesce(\r\n    outputs('Get_plan_details')?['body/categoryDescriptions/category5'],\r\n    'Blue'\r\n)"</span>
                },
                {
                  <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category6"</span>,
                  <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@coalesce(\r\n    outputs('Get_plan_details')?['body/categoryDescriptions/category6'],\r\n    'Purple'\r\n)"</span>
                },
                {
                  <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category7"</span>,
                  <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@coalesce(\r\n    outputs('Get_plan_details')?['body/categoryDescriptions/category7'],\r\n    'Bronze'\r\n)"</span>
                },
                {
                  <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category8"</span>,
                  <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@coalesce(\r\n    outputs('Get_plan_details')?['body/categoryDescriptions/category8'],\r\n    'Lime'\r\n)"</span>
                },
                {
                  <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category9"</span>,
                  <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@coalesce(\r\n    outputs('Get_plan_details')?['body/categoryDescriptions/category9'],\r\n    'Aqua'\r\n)"</span>
                },
                {
                  <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category10"</span>,
                  <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@coalesce(\r\n    outputs('Get_plan_details')?['body/categoryDescriptions/category10'],\r\n    'Gray'\r\n)"</span>
                },
                {
                  <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category11"</span>,
                  <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@coalesce(\r\n    outputs('Get_plan_details')?['body/categoryDescriptions/category11'],\r\n    'Silver'\r\n)"</span>
                },
                {
                  <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category12"</span>,
                  <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@coalesce(\r\n    outputs('Get_plan_details')?['body/categoryDescriptions/category12'],\r\n    'Brown'\r\n)"</span>
                },
                {
                  <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category13"</span>,
                  <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@coalesce(\r\n    outputs('Get_plan_details')?['body/categoryDescriptions/category13'],\r\n    'Cranberry'\r\n)"</span>
                },
                {
                  <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category14"</span>,
                  <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@coalesce(\r\n    outputs('Get_plan_details')?['body/categoryDescriptions/category14'],\r\n    'Orange'\r\n)"</span>
                },
                {
                  <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category15"</span>,
                  <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@coalesce(\r\n    outputs('Get_plan_details')?['body/categoryDescriptions/category15'],\r\n    'Peach'\r\n)"</span>
                },
                {
                  <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category16"</span>,
                  <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@coalesce(\r\n    outputs('Get_plan_details')?['body/categoryDescriptions/category16'],\r\n    'Marigold'\r\n)"</span>
                },
                {
                  <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category17"</span>,
                  <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@coalesce(\r\n    outputs('Get_plan_details')?['body/categoryDescriptions/category17'],\r\n    'Light green'\r\n)"</span>
                },
                {
                  <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category18"</span>,
                  <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@coalesce(\r\n    outputs('Get_plan_details')?['body/categoryDescriptions/category18'],\r\n    'Dark green'\r\n)"</span>
                },
                {
                  <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category19"</span>,
                  <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@coalesce(\r\n    outputs('Get_plan_details')?['body/categoryDescriptions/category19'],\r\n    'Teal'\r\n)"</span>
                },
                {
                  <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category20"</span>,
                  <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@coalesce(\r\n    outputs('Get_plan_details')?['body/categoryDescriptions/category20'],\r\n    'Light blue'\r\n)"</span>
                },
                {
                  <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category21"</span>,
                  <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@coalesce(\r\n    outputs('Get_plan_details')?['body/categoryDescriptions/category21'],\r\n    'Dark blue'\r\n)"</span>
                },
                {
                  <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category22"</span>,
                  <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@coalesce(\r\n    outputs('Get_plan_details')?['body/categoryDescriptions/category22'],\r\n    'Lavender'\r\n)"</span>
                },
                {
                  <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category23"</span>,
                  <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@coalesce(\r\n    outputs('Get_plan_details')?['body/categoryDescriptions/category23'],\r\n    'Plum'\r\n)"</span>
                },
                {
                  <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category24"</span>,
                  <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@coalesce(\r\n    outputs('Get_plan_details')?['body/categoryDescriptions/category24'],\r\n    'Light gray'\r\n)"</span>
                },
                {
                  <span class="hljs-attr">"key"</span>: <span class="hljs-string">"category25"</span>,
                  <span class="hljs-attr">"value"</span>: <span class="hljs-string">"@coalesce(\r\n    outputs('Get_plan_details')?['body/categoryDescriptions/category25'],\r\n    'Dark gray'\r\n)"</span>
                }
              ]
            },
            <span class="hljs-attr">"Get_plan_details"</span>: {
              <span class="hljs-attr">"runAfter"</span>: {
                <span class="hljs-attr">"List_buckets"</span>: [
                  <span class="hljs-string">"Succeeded"</span>
                ]
              },
              <span class="hljs-attr">"metadata"</span>: {
                <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"0dc5fdf6-2fa2-48ae-a4e5-020dec34cb88"</span>
              },
              <span class="hljs-attr">"type"</span>: <span class="hljs-string">"OpenApiConnection"</span>,
              <span class="hljs-attr">"inputs"</span>: {
                <span class="hljs-attr">"host"</span>: {
                  <span class="hljs-attr">"connectionName"</span>: <span class="hljs-string">"shared_planner"</span>,
                  <span class="hljs-attr">"operationId"</span>: <span class="hljs-string">"GetPlanDetails"</span>,
                  <span class="hljs-attr">"apiId"</span>: <span class="hljs-string">"/providers/Microsoft.PowerApps/apis/shared_planner"</span>
                },
                <span class="hljs-attr">"parameters"</span>: {
                  <span class="hljs-attr">"id"</span>: <span class="hljs-string">"@items('for_each_plan')?['PlanID']"</span>
                },
                <span class="hljs-attr">"authentication"</span>: <span class="hljs-string">"@parameters('$authentication')"</span>
              }
            },
            <span class="hljs-attr">"Append_to_errorString_2"</span>: {
              <span class="hljs-attr">"runAfter"</span>: {
                <span class="hljs-attr">"List_buckets"</span>: [
                  <span class="hljs-string">"Failed"</span>
                ]
              },
              <span class="hljs-attr">"metadata"</span>: {
                <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"62a6b5ae-3e2e-490c-9f7e-932225197b4e"</span>
              },
              <span class="hljs-attr">"type"</span>: <span class="hljs-string">"AppendToStringVariable"</span>,
              <span class="hljs-attr">"inputs"</span>: {
                <span class="hljs-attr">"name"</span>: <span class="hljs-string">"errorString"</span>,
                <span class="hljs-attr">"value"</span>: <span class="hljs-string">"Failed to get Planner data (buckets)\n@{items('for_each_plan')?['PlanName']} | @{items('for_each_plan')?['PlanID']} | @{items('for_each_plan')?['GroupID']} | @{items('for_each_plan')?['GroupName']} | @{items('for_each_plan')?['InsertedOn']} | @{items('for_each_plan')?['Team']} | @{items('for_each_plan')?['Leader']} | @{items('for_each_plan')?['LastRunDate']}&lt;br&gt;"</span>
              },
              <span class="hljs-attr">"description"</span>: <span class="hljs-string">"if service account does not have plan access, an error will generate"</span>
            },
            <span class="hljs-attr">"increment_errorCount_2"</span>: {
              <span class="hljs-attr">"runAfter"</span>: {
                <span class="hljs-attr">"Append_to_errorString_2"</span>: [
                  <span class="hljs-string">"Succeeded"</span>
                ]
              },
              <span class="hljs-attr">"metadata"</span>: {
                <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"dab9642e-300b-4202-9be7-323e2f874e11"</span>
              },
              <span class="hljs-attr">"type"</span>: <span class="hljs-string">"IncrementVariable"</span>,
              <span class="hljs-attr">"inputs"</span>: {
                <span class="hljs-attr">"name"</span>: <span class="hljs-string">"errorCount"</span>,
                <span class="hljs-attr">"value"</span>: <span class="hljs-number">1</span>
              }
            },
            <span class="hljs-attr">"update_lastRunDate_on_source_row"</span>: {
              <span class="hljs-attr">"runAfter"</span>: {
                <span class="hljs-attr">"for_each_task"</span>: [
                  <span class="hljs-string">"Succeeded"</span>
                ]
              },
              <span class="hljs-attr">"metadata"</span>: {
                <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"9288db35-967c-4ad8-8010-55fe7fef0234"</span>
              },
              <span class="hljs-attr">"type"</span>: <span class="hljs-string">"OpenApiConnection"</span>,
              <span class="hljs-attr">"inputs"</span>: {
                <span class="hljs-attr">"host"</span>: {
                  <span class="hljs-attr">"connectionName"</span>: <span class="hljs-string">"shared_sql"</span>,
                  <span class="hljs-attr">"operationId"</span>: <span class="hljs-string">"PatchItem_V2"</span>,
                  <span class="hljs-attr">"apiId"</span>: <span class="hljs-string">"/providers/Microsoft.PowerApps/apis/shared_sql"</span>
                },
                <span class="hljs-attr">"parameters"</span>: {
                  <span class="hljs-attr">"server"</span>: <span class="hljs-string">"@parameters('text_sqlServer (jake_sqlServer)')"</span>,
                  <span class="hljs-attr">"database"</span>: <span class="hljs-string">"@parameters('text_sqlDatabase (jake_sqlTable)')"</span>,
                  <span class="hljs-attr">"table"</span>: <span class="hljs-string">"***SCRUBBED***"</span>,
                  <span class="hljs-attr">"id"</span>: <span class="hljs-string">"@items('for_each_plan')?['ID']"</span>,
                  <span class="hljs-attr">"item/LastRunDate"</span>: <span class="hljs-string">"@body('Start_time_PST')"</span>
                },
                <span class="hljs-attr">"authentication"</span>: <span class="hljs-string">"@parameters('$authentication')"</span>
              }
            }
          },
          <span class="hljs-attr">"runAfter"</span>: {
            <span class="hljs-attr">"get_active_plans"</span>: [
              <span class="hljs-string">"Succeeded"</span>
            ]
          },
          <span class="hljs-attr">"metadata"</span>: {
            <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"4795519f-7313-40f8-9956-4b55cd3d8164"</span>
          },
          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"Foreach"</span>,
          <span class="hljs-attr">"runtimeConfiguration"</span>: {
            <span class="hljs-attr">"concurrency"</span>: {
              <span class="hljs-attr">"repetitions"</span>: <span class="hljs-number">50</span>
            }
          }
        },
        <span class="hljs-attr">"get_active_plans"</span>: {
          <span class="hljs-attr">"runAfter"</span>: {},
          <span class="hljs-attr">"metadata"</span>: {
            <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"41fba7a5-dec9-4cf3-b9c9-2ab3af9320f1"</span>
          },
          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"OpenApiConnection"</span>,
          <span class="hljs-attr">"inputs"</span>: {
            <span class="hljs-attr">"host"</span>: {
              <span class="hljs-attr">"connectionName"</span>: <span class="hljs-string">"shared_sql"</span>,
              <span class="hljs-attr">"operationId"</span>: <span class="hljs-string">"GetItems_V2"</span>,
              <span class="hljs-attr">"apiId"</span>: <span class="hljs-string">"/providers/Microsoft.PowerApps/apis/shared_sql"</span>
            },
            <span class="hljs-attr">"parameters"</span>: {
              <span class="hljs-attr">"server"</span>: <span class="hljs-string">"@parameters('text_sqlServer (jake_sqlServer)')"</span>,
              <span class="hljs-attr">"database"</span>: <span class="hljs-string">"@parameters('text_sqlDatabase (jake_sqlTable)')"</span>,
              <span class="hljs-attr">"table"</span>: <span class="hljs-string">"***SCRUBBED***"</span>,
              <span class="hljs-attr">"$filter"</span>: <span class="hljs-string">"Active eq 1"</span>
            },
            <span class="hljs-attr">"authentication"</span>: <span class="hljs-string">"@parameters('$authentication')"</span>
          },
          <span class="hljs-attr">"description"</span>: <span class="hljs-string">"get active plans from SQL table (populated and managed by customer via powerApp)"</span>
        },
        <span class="hljs-attr">"increment_errorCount"</span>: {
          <span class="hljs-attr">"runAfter"</span>: {
            <span class="hljs-attr">"get_active_plans"</span>: [
              <span class="hljs-string">"Failed"</span>,
              <span class="hljs-string">"TimedOut"</span>
            ]
          },
          <span class="hljs-attr">"metadata"</span>: {
            <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"5fe7081a-63d9-4a0c-86d9-23e0e70d964a"</span>
          },
          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"IncrementVariable"</span>,
          <span class="hljs-attr">"inputs"</span>: {
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"errorCount"</span>,
            <span class="hljs-attr">"value"</span>: <span class="hljs-number">1</span>
          }
        },
        <span class="hljs-attr">"append_errorString"</span>: {
          <span class="hljs-attr">"runAfter"</span>: {
            <span class="hljs-attr">"increment_errorCount"</span>: [
              <span class="hljs-string">"Succeeded"</span>
            ]
          },
          <span class="hljs-attr">"metadata"</span>: {
            <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"4331ac95-7f3e-447a-8a1f-c5510d0c2ca8"</span>
          },
          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"AppendToStringVariable"</span>,
          <span class="hljs-attr">"inputs"</span>: {
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"errorString"</span>,
            <span class="hljs-attr">"value"</span>: <span class="hljs-string">"Failed to get active plans&lt;br&gt;"</span>
          }
        }
      },
      <span class="hljs-attr">"runAfter"</span>: {
        <span class="hljs-attr">"init_recordCounts"</span>: [
          <span class="hljs-string">"Succeeded"</span>
        ]
      },
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"97ae9def-14eb-4ffe-95f7-2f5e5ec0b360"</span>
      },
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"Scope"</span>
    },
    <span class="hljs-attr">"check_for_email_notification_preference"</span>: {
      <span class="hljs-attr">"actions"</span>: {
        <span class="hljs-attr">"send_completion_email_lite"</span>: {
          <span class="hljs-attr">"runAfter"</span>: {},
          <span class="hljs-attr">"metadata"</span>: {
            <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"b340dff7-7d1b-4081-ab75-dbe9dc7180d1"</span>
          },
          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"OpenApiConnection"</span>,
          <span class="hljs-attr">"inputs"</span>: {
            <span class="hljs-attr">"host"</span>: {
              <span class="hljs-attr">"connectionName"</span>: <span class="hljs-string">"shared_office365_1"</span>,
              <span class="hljs-attr">"operationId"</span>: <span class="hljs-string">"SendEmailV2"</span>,
              <span class="hljs-attr">"apiId"</span>: <span class="hljs-string">"/providers/Microsoft.PowerApps/apis/shared_office365"</span>
            },
            <span class="hljs-attr">"parameters"</span>: {
              <span class="hljs-attr">"emailMessage/To"</span>: <span class="hljs-string">"@parameters('text_emailRecipients (jake_emailRecipients)')"</span>,
              <span class="hljs-attr">"emailMessage/Subject"</span>: <span class="hljs-string">"Task Aggegation Complete - @{div(sub(ticks(body('End_Time_PST')), ticks(body('Start_time_PST'))), 600000000)} minutes - @{variables('errorCount')} errors"</span>,
              <span class="hljs-attr">"emailMessage/Body"</span>: <span class="hljs-string">"&lt;p&gt;@{variables('recordCounts')} &lt;/p&gt;"</span>,
              <span class="hljs-attr">"emailMessage/Importance"</span>: <span class="hljs-string">"Normal"</span>
            },
            <span class="hljs-attr">"authentication"</span>: <span class="hljs-string">"@parameters('$authentication')"</span>
          }
        }
      },
      <span class="hljs-attr">"runAfter"</span>: {
        <span class="hljs-attr">"http_response"</span>: [
          <span class="hljs-string">"TimedOut"</span>
        ]
      },
      <span class="hljs-attr">"expression"</span>: {
        <span class="hljs-attr">"equals"</span>: [
          <span class="hljs-string">"@parameters('bool_emailNotifications (jake_emailNotifications)')"</span>,
          <span class="hljs-literal">true</span>
        ]
      },
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"05ae3102-3d82-424e-ad05-b2dfc05f743f"</span>
      },
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"If"</span>
    },
    <span class="hljs-attr">"init_errorCount"</span>: {
      <span class="hljs-attr">"runAfter"</span>: {
        <span class="hljs-attr">"init_errorString"</span>: [
          <span class="hljs-string">"Succeeded"</span>
        ]
      },
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"31631ada-9b18-4f95-9e4e-660597cb8d1d"</span>
      },
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"InitializeVariable"</span>,
      <span class="hljs-attr">"inputs"</span>: {
        <span class="hljs-attr">"variables"</span>: [
          {
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"errorCount"</span>,
            <span class="hljs-attr">"type"</span>: <span class="hljs-string">"integer"</span>,
            <span class="hljs-attr">"value"</span>: <span class="hljs-number">0</span>
          }
        ]
      }
    },
    <span class="hljs-attr">"Start_time_PST"</span>: {
      <span class="hljs-attr">"runAfter"</span>: {},
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"ccf5cf73-8dfa-4d3b-9b80-df852045c97e"</span>
      },
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"Expression"</span>,
      <span class="hljs-attr">"kind"</span>: <span class="hljs-string">"ConvertTimeZone"</span>,
      <span class="hljs-attr">"inputs"</span>: {
        <span class="hljs-attr">"baseTime"</span>: <span class="hljs-string">"@{utcNow()}"</span>,
        <span class="hljs-attr">"formatString"</span>: <span class="hljs-string">"o"</span>,
        <span class="hljs-attr">"sourceTimeZone"</span>: <span class="hljs-string">"UTC"</span>,
        <span class="hljs-attr">"destinationTimeZone"</span>: <span class="hljs-string">"Pacific Standard Time"</span>
      }
    },
    <span class="hljs-attr">"get_existing_SQL_records"</span>: {
      <span class="hljs-attr">"actions"</span>: {
        <span class="hljs-attr">"get_existing_tasks"</span>: {
          <span class="hljs-attr">"runAfter"</span>: {},
          <span class="hljs-attr">"metadata"</span>: {
            <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"076b756a-9716-4a44-87ec-2582a1f0b7e8"</span>
          },
          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"OpenApiConnection"</span>,
          <span class="hljs-attr">"inputs"</span>: {
            <span class="hljs-attr">"host"</span>: {
              <span class="hljs-attr">"connectionName"</span>: <span class="hljs-string">"shared_sql"</span>,
              <span class="hljs-attr">"operationId"</span>: <span class="hljs-string">"GetItems_V2"</span>,
              <span class="hljs-attr">"apiId"</span>: <span class="hljs-string">"/providers/Microsoft.PowerApps/apis/shared_sql"</span>
            },
            <span class="hljs-attr">"parameters"</span>: {
              <span class="hljs-attr">"server"</span>: <span class="hljs-string">"@parameters('text_sqlServer (jake_sqlServer)')"</span>,
              <span class="hljs-attr">"database"</span>: <span class="hljs-string">"@parameters('text_sqlDatabase (jake_sqlTable)')"</span>,
              <span class="hljs-attr">"table"</span>: <span class="hljs-string">"***SCRUBBED***"</span>,
              <span class="hljs-attr">"$filter"</span>: <span class="hljs-string">"isActive eq 1"</span>
            },
            <span class="hljs-attr">"authentication"</span>: <span class="hljs-string">"@parameters('$authentication')"</span>
          },
          <span class="hljs-attr">"runtimeConfiguration"</span>: {
            <span class="hljs-attr">"paginationPolicy"</span>: {
              <span class="hljs-attr">"minimumItemCount"</span>: <span class="hljs-number">25000</span>
            }
          }
        },
        <span class="hljs-attr">"get_existing_assignments"</span>: {
          <span class="hljs-attr">"runAfter"</span>: {
            <span class="hljs-attr">"get_existing_tasks"</span>: [
              <span class="hljs-string">"Succeeded"</span>
            ]
          },
          <span class="hljs-attr">"metadata"</span>: {
            <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"076b756a-9716-4a44-87ec-2582a1f0b7e8"</span>
          },
          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"OpenApiConnection"</span>,
          <span class="hljs-attr">"inputs"</span>: {
            <span class="hljs-attr">"host"</span>: {
              <span class="hljs-attr">"connectionName"</span>: <span class="hljs-string">"shared_sql"</span>,
              <span class="hljs-attr">"operationId"</span>: <span class="hljs-string">"GetItems_V2"</span>,
              <span class="hljs-attr">"apiId"</span>: <span class="hljs-string">"/providers/Microsoft.PowerApps/apis/shared_sql"</span>
            },
            <span class="hljs-attr">"parameters"</span>: {
              <span class="hljs-attr">"server"</span>: <span class="hljs-string">"@parameters('text_sqlServer (jake_sqlServer)')"</span>,
              <span class="hljs-attr">"database"</span>: <span class="hljs-string">"@parameters('text_sqlDatabase (jake_sqlTable)')"</span>,
              <span class="hljs-attr">"table"</span>: <span class="hljs-string">"***SCRUBBED***"</span>,
              <span class="hljs-attr">"$filter"</span>: <span class="hljs-string">"isActive eq 1"</span>
            },
            <span class="hljs-attr">"authentication"</span>: <span class="hljs-string">"@parameters('$authentication')"</span>
          },
          <span class="hljs-attr">"runtimeConfiguration"</span>: {
            <span class="hljs-attr">"paginationPolicy"</span>: {
              <span class="hljs-attr">"minimumItemCount"</span>: <span class="hljs-number">25000</span>
            }
          }
        },
        <span class="hljs-attr">"get_existing_attachments"</span>: {
          <span class="hljs-attr">"runAfter"</span>: {
            <span class="hljs-attr">"get_existing_assignments"</span>: [
              <span class="hljs-string">"Succeeded"</span>
            ]
          },
          <span class="hljs-attr">"metadata"</span>: {
            <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"076b756a-9716-4a44-87ec-2582a1f0b7e8"</span>
          },
          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"OpenApiConnection"</span>,
          <span class="hljs-attr">"inputs"</span>: {
            <span class="hljs-attr">"host"</span>: {
              <span class="hljs-attr">"connectionName"</span>: <span class="hljs-string">"shared_sql"</span>,
              <span class="hljs-attr">"operationId"</span>: <span class="hljs-string">"GetItems_V2"</span>,
              <span class="hljs-attr">"apiId"</span>: <span class="hljs-string">"/providers/Microsoft.PowerApps/apis/shared_sql"</span>
            },
            <span class="hljs-attr">"parameters"</span>: {
              <span class="hljs-attr">"server"</span>: <span class="hljs-string">"@parameters('text_sqlServer (jake_sqlServer)')"</span>,
              <span class="hljs-attr">"database"</span>: <span class="hljs-string">"@parameters('text_sqlDatabase (jake_sqlTable)')"</span>,
              <span class="hljs-attr">"table"</span>: <span class="hljs-string">"***SCRUBBED***"</span>,
              <span class="hljs-attr">"$filter"</span>: <span class="hljs-string">"isActive eq 1"</span>
            },
            <span class="hljs-attr">"authentication"</span>: <span class="hljs-string">"@parameters('$authentication')"</span>
          },
          <span class="hljs-attr">"runtimeConfiguration"</span>: {
            <span class="hljs-attr">"paginationPolicy"</span>: {
              <span class="hljs-attr">"minimumItemCount"</span>: <span class="hljs-number">25000</span>
            }
          }
        },
        <span class="hljs-attr">"get_existing_checklist_items"</span>: {
          <span class="hljs-attr">"runAfter"</span>: {
            <span class="hljs-attr">"get_existing_attachments"</span>: [
              <span class="hljs-string">"Succeeded"</span>
            ]
          },
          <span class="hljs-attr">"metadata"</span>: {
            <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"076b756a-9716-4a44-87ec-2582a1f0b7e8"</span>
          },
          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"OpenApiConnection"</span>,
          <span class="hljs-attr">"inputs"</span>: {
            <span class="hljs-attr">"host"</span>: {
              <span class="hljs-attr">"connectionName"</span>: <span class="hljs-string">"shared_sql"</span>,
              <span class="hljs-attr">"operationId"</span>: <span class="hljs-string">"GetItems_V2"</span>,
              <span class="hljs-attr">"apiId"</span>: <span class="hljs-string">"/providers/Microsoft.PowerApps/apis/shared_sql"</span>
            },
            <span class="hljs-attr">"parameters"</span>: {
              <span class="hljs-attr">"server"</span>: <span class="hljs-string">"@parameters('text_sqlServer (jake_sqlServer)')"</span>,
              <span class="hljs-attr">"database"</span>: <span class="hljs-string">"@parameters('text_sqlDatabase (jake_sqlTable)')"</span>,
              <span class="hljs-attr">"table"</span>: <span class="hljs-string">"***SCRUBBED***"</span>,
              <span class="hljs-attr">"$filter"</span>: <span class="hljs-string">"isActive eq 1"</span>
            },
            <span class="hljs-attr">"authentication"</span>: <span class="hljs-string">"@parameters('$authentication')"</span>
          },
          <span class="hljs-attr">"runtimeConfiguration"</span>: {
            <span class="hljs-attr">"paginationPolicy"</span>: {
              <span class="hljs-attr">"minimumItemCount"</span>: <span class="hljs-number">25000</span>
            }
          }
        },
        <span class="hljs-attr">"get_existing_labels"</span>: {
          <span class="hljs-attr">"runAfter"</span>: {
            <span class="hljs-attr">"get_existing_checklist_items"</span>: [
              <span class="hljs-string">"Succeeded"</span>
            ]
          },
          <span class="hljs-attr">"metadata"</span>: {
            <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"076b756a-9716-4a44-87ec-2582a1f0b7e8"</span>
          },
          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"OpenApiConnection"</span>,
          <span class="hljs-attr">"inputs"</span>: {
            <span class="hljs-attr">"host"</span>: {
              <span class="hljs-attr">"connectionName"</span>: <span class="hljs-string">"shared_sql"</span>,
              <span class="hljs-attr">"operationId"</span>: <span class="hljs-string">"GetItems_V2"</span>,
              <span class="hljs-attr">"apiId"</span>: <span class="hljs-string">"/providers/Microsoft.PowerApps/apis/shared_sql"</span>
            },
            <span class="hljs-attr">"parameters"</span>: {
              <span class="hljs-attr">"server"</span>: <span class="hljs-string">"@parameters('text_sqlServer (jake_sqlServer)')"</span>,
              <span class="hljs-attr">"database"</span>: <span class="hljs-string">"@parameters('text_sqlDatabase (jake_sqlTable)')"</span>,
              <span class="hljs-attr">"table"</span>: <span class="hljs-string">"***SCRUBBED***"</span>,
              <span class="hljs-attr">"$filter"</span>: <span class="hljs-string">"isActive eq 1"</span>
            },
            <span class="hljs-attr">"authentication"</span>: <span class="hljs-string">"@parameters('$authentication')"</span>
          },
          <span class="hljs-attr">"runtimeConfiguration"</span>: {
            <span class="hljs-attr">"paginationPolicy"</span>: {
              <span class="hljs-attr">"minimumItemCount"</span>: <span class="hljs-number">25000</span>
            }
          }
        }
      },
      <span class="hljs-attr">"runAfter"</span>: {
        <span class="hljs-attr">"init_checklistArray"</span>: [
          <span class="hljs-string">"Succeeded"</span>
        ]
      },
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"0a15e51f-ea62-4662-9ec6-64805e8c3d3e"</span>
      },
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"Scope"</span>
    },
    <span class="hljs-attr">"init_recordCounts"</span>: {
      <span class="hljs-attr">"runAfter"</span>: {
        <span class="hljs-attr">"get_existing_SQL_records"</span>: [
          <span class="hljs-string">"Succeeded"</span>
        ]
      },
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"18ee4e3e-b4c7-4071-97b4-0bed010e698b"</span>
      },
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"InitializeVariable"</span>,
      <span class="hljs-attr">"inputs"</span>: {
        <span class="hljs-attr">"variables"</span>: [
          {
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"recordCounts"</span>,
            <span class="hljs-attr">"type"</span>: <span class="hljs-string">"string"</span>,
            <span class="hljs-attr">"value"</span>: <span class="hljs-string">"&lt;h3&gt;EXISTING SQL RECORDS&lt;/h3&gt;\n&lt;ul&gt;\n&lt;li&gt;Tasks: @{length( outputs('get_existing_tasks')?['body/value'] )}&lt;/li&gt;\n&lt;li&gt;Assignments: @{length( outputs('get_existing_assignments')?['body/value'] )}&lt;/li&gt;\n&lt;li&gt;Attachments: @{length( outputs('get_existing_attachments')?['body/value'] )}&lt;/li&gt;\n&lt;li&gt;Checklist Items: @{length( outputs('get_existing_checklist_items')?['body/value'] )}&lt;/li&gt;\n&lt;li&gt;Labels: @{length( outputs('get_existing_labels')?['body/value'] )}&lt;/li&gt;\n&lt;/ul&gt;"</span>
          }
        ]
      }
    },
    <span class="hljs-attr">"init_outcomeArray"</span>: {
      <span class="hljs-attr">"runAfter"</span>: {
        <span class="hljs-attr">"init_inactiveChecklist_array"</span>: [
          <span class="hljs-string">"Succeeded"</span>
        ]
      },
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"ac0964c2-d3e3-4baa-a5a7-d611751a2567"</span>
      },
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"InitializeVariable"</span>,
      <span class="hljs-attr">"inputs"</span>: {
        <span class="hljs-attr">"variables"</span>: [
          {
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"outcomeArray"</span>,
            <span class="hljs-attr">"type"</span>: <span class="hljs-string">"array"</span>
          }
        ]
      }
    },
    <span class="hljs-attr">"init_inactiveTasks_array"</span>: {
      <span class="hljs-attr">"runAfter"</span>: {
        <span class="hljs-attr">"if_weekday"</span>: [
          <span class="hljs-string">"Succeeded"</span>
        ]
      },
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"81057d90-262d-409e-87fc-5d3ddaf9d56f"</span>
      },
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"InitializeVariable"</span>,
      <span class="hljs-attr">"inputs"</span>: {
        <span class="hljs-attr">"variables"</span>: [
          {
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"inactiveTasks"</span>,
            <span class="hljs-attr">"type"</span>: <span class="hljs-string">"array"</span>
          }
        ]
      }
    },
    <span class="hljs-attr">"init_inactiveLabels_array"</span>: {
      <span class="hljs-attr">"runAfter"</span>: {
        <span class="hljs-attr">"init_inactiveAssignments_array"</span>: [
          <span class="hljs-string">"Succeeded"</span>
        ]
      },
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"2b6ede09-4521-4637-b8cd-559e7d298517"</span>
      },
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"InitializeVariable"</span>,
      <span class="hljs-attr">"inputs"</span>: {
        <span class="hljs-attr">"variables"</span>: [
          {
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"inactiveLabels"</span>,
            <span class="hljs-attr">"type"</span>: <span class="hljs-string">"array"</span>
          }
        ]
      }
    },
    <span class="hljs-attr">"init_inactiveAttachments_array"</span>: {
      <span class="hljs-attr">"runAfter"</span>: {
        <span class="hljs-attr">"init_inactiveTasks_array"</span>: [
          <span class="hljs-string">"Succeeded"</span>
        ]
      },
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"822732d7-f448-4df5-a865-e10ca2898905"</span>
      },
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"InitializeVariable"</span>,
      <span class="hljs-attr">"inputs"</span>: {
        <span class="hljs-attr">"variables"</span>: [
          {
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"inactiveAttachments"</span>,
            <span class="hljs-attr">"type"</span>: <span class="hljs-string">"array"</span>
          }
        ]
      }
    },
    <span class="hljs-attr">"init_inactiveAssignments_array"</span>: {
      <span class="hljs-attr">"runAfter"</span>: {
        <span class="hljs-attr">"init_inactiveAttachments_array"</span>: [
          <span class="hljs-string">"Succeeded"</span>
        ]
      },
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"0e5642fd-169e-4d2f-acc7-36fc12415e49"</span>
      },
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"InitializeVariable"</span>,
      <span class="hljs-attr">"inputs"</span>: {
        <span class="hljs-attr">"variables"</span>: [
          {
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"inactiveAssignments"</span>,
            <span class="hljs-attr">"type"</span>: <span class="hljs-string">"array"</span>
          }
        ]
      }
    },
    <span class="hljs-attr">"init_inactiveChecklist_array"</span>: {
      <span class="hljs-attr">"runAfter"</span>: {
        <span class="hljs-attr">"init_inactiveLabels_array"</span>: [
          <span class="hljs-string">"Succeeded"</span>
        ]
      },
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"5bb90e84-300c-4a53-a9c4-a2440bc7452c"</span>
      },
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"InitializeVariable"</span>,
      <span class="hljs-attr">"inputs"</span>: {
        <span class="hljs-attr">"variables"</span>: [
          {
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"inactiveChecklist"</span>,
            <span class="hljs-attr">"type"</span>: <span class="hljs-string">"array"</span>
          }
        ]
      }
    },
    <span class="hljs-attr">"HTTP"</span>: {
      <span class="hljs-attr">"runAfter"</span>: {
        <span class="hljs-attr">"array_payloads"</span>: [
          <span class="hljs-string">"Succeeded"</span>
        ]
      },
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"062aff9d-f992-4b7d-b23a-3bd6cdfc8ee1"</span>
      },
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"Http"</span>,
      <span class="hljs-attr">"inputs"</span>: {
        <span class="hljs-attr">"method"</span>: <span class="hljs-string">"POST"</span>,
        <span class="hljs-attr">"uri"</span>: <span class="hljs-string">"@parameters('text_processFlowEndpoint (jake_test_postURL)')"</span>,
        <span class="hljs-attr">"headers"</span>: {
          <span class="hljs-attr">"Content-Type"</span>: <span class="hljs-string">"application/json"</span>
        },
        <span class="hljs-attr">"body"</span>: <span class="hljs-string">"@outputs('array_payloads')"</span>
      },
      <span class="hljs-attr">"operationOptions"</span>: <span class="hljs-string">"DisableAsyncPattern"</span>
    },
    <span class="hljs-attr">"http_response"</span>: {
      <span class="hljs-attr">"runAfter"</span>: {
        <span class="hljs-attr">"HTTP"</span>: [
          <span class="hljs-string">"Succeeded"</span>
        ]
      },
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"fbe15233-84cb-4618-91cb-c350236a79ec"</span>
      },
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"Compose"</span>,
      <span class="hljs-attr">"inputs"</span>: <span class="hljs-string">"@{outputs('HTTP')['statusCode']}\n@{body('HTTP')}"</span>
    },
    <span class="hljs-attr">"End_Time_PST"</span>: {
      <span class="hljs-attr">"runAfter"</span>: {
        <span class="hljs-attr">"Plan_and_task_iteration"</span>: [
          <span class="hljs-string">"Succeeded"</span>,
          <span class="hljs-string">"Failed"</span>
        ]
      },
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"844d3531-4728-412e-855f-690ba1bd04f5"</span>
      },
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"Expression"</span>,
      <span class="hljs-attr">"kind"</span>: <span class="hljs-string">"ConvertTimeZone"</span>,
      <span class="hljs-attr">"inputs"</span>: {
        <span class="hljs-attr">"baseTime"</span>: <span class="hljs-string">"@{utcNow()}"</span>,
        <span class="hljs-attr">"formatString"</span>: <span class="hljs-string">"o"</span>,
        <span class="hljs-attr">"sourceTimeZone"</span>: <span class="hljs-string">"UTC"</span>,
        <span class="hljs-attr">"destinationTimeZone"</span>: <span class="hljs-string">"Pacific Standard Time"</span>
      }
    },
    <span class="hljs-attr">"compose_totals"</span>: {
      <span class="hljs-attr">"runAfter"</span>: {
        <span class="hljs-attr">"End_Time_PST"</span>: [
          <span class="hljs-string">"Succeeded"</span>
        ]
      },
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"33b358a6-a5d6-4f25-aef4-4714c2e8e736"</span>
      },
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"Compose"</span>,
      <span class="hljs-attr">"inputs"</span>: <span class="hljs-string">"&lt;h2&gt;Gather Task Data&lt;/h2&gt;\n&lt;h3&gt;@{length(\noutputs('get_active_plans')?['body/value']\n)} Plans&lt;/h3&gt;\n&lt;ul&gt;&lt;li&gt;Tasks: @{length(\n    variables('taskArray')\n)}&lt;/li&gt;&lt;/ul&gt;\n&lt;ul&gt;&lt;li&gt;Assignments: @{length(\n    variables('assignmentArray')\n)}&lt;/li&gt;&lt;/ul&gt;\n&lt;ul&gt;&lt;li&gt;Attachments: @{length(\n    variables('attachmentArray')\n)}&lt;/li&gt;&lt;/ul&gt;\n&lt;ul&gt;&lt;li&gt;Checklist items: @{length(\n    variables('checklistArray')\n)}&lt;/li&gt;&lt;/ul&gt;\n&lt;ul&gt;&lt;li&gt;Labels: @{length(variables('labelArray'))}&lt;/li&gt;&lt;/ul&gt;\n\n&lt;h3&gt;ERROR COUNT&lt;/h3&gt;\n&lt;p&gt;@{variables('errorCount')}&lt;/p&gt;\n\n&lt;h3&gt;DEBUG LOG&lt;/h3&gt;\n&lt;p&gt;@{variables('errorString')}&lt;/p&gt;"</span>
    },
    <span class="hljs-attr">"array_payloads"</span>: {
      <span class="hljs-attr">"runAfter"</span>: {
        <span class="hljs-attr">"compose_totals"</span>: [
          <span class="hljs-string">"Succeeded"</span>
        ]
      },
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"b459749d-99fd-45a0-90b5-91fa046a813a"</span>
      },
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"Compose"</span>,
      <span class="hljs-attr">"inputs"</span>: {
        <span class="hljs-attr">"parentFlowDuration"</span>: <span class="hljs-string">"@div(sub(ticks(body('End_Time_PST')), ticks(body('Start_time_PST'))), 600000000)"</span>,
        <span class="hljs-attr">"parentFlowMessage"</span>: <span class="hljs-string">"@{outputs('compose_totals')}"</span>,
        <span class="hljs-attr">"payloads"</span>: [
          {
            <span class="hljs-attr">"contentType"</span>: <span class="hljs-string">"task"</span>,
            <span class="hljs-attr">"values"</span>: {
              <span class="hljs-attr">"arraySQL"</span>: <span class="hljs-string">"@outputs('get_existing_tasks')?['body/value']"</span>,
              <span class="hljs-attr">"arrayCurrent"</span>: <span class="hljs-string">"@variables('taskArray')"</span>
            }
          },
          {
            <span class="hljs-attr">"contentType"</span>: <span class="hljs-string">"checklist"</span>,
            <span class="hljs-attr">"values"</span>: {
              <span class="hljs-attr">"arraySQL"</span>: <span class="hljs-string">"@outputs('get_existing_checklist_items')?['body/value']"</span>,
              <span class="hljs-attr">"arrayCurrent"</span>: <span class="hljs-string">"@variables('checklistArray')"</span>
            }
          },
          {
            <span class="hljs-attr">"contentType"</span>: <span class="hljs-string">"label"</span>,
            <span class="hljs-attr">"values"</span>: {
              <span class="hljs-attr">"arraySQL"</span>: <span class="hljs-string">"@outputs('get_existing_labels')?['body/value']"</span>,
              <span class="hljs-attr">"arrayCurrent"</span>: <span class="hljs-string">"@variables('labelArray')"</span>
            }
          },
          {
            <span class="hljs-attr">"contentType"</span>: <span class="hljs-string">"attachment"</span>,
            <span class="hljs-attr">"values"</span>: {
              <span class="hljs-attr">"arraySQL"</span>: <span class="hljs-string">"@outputs('get_existing_attachments')?['body/value']"</span>,
              <span class="hljs-attr">"arrayCurrent"</span>: <span class="hljs-string">"@variables('attachmentArray')"</span>
            }
          },
          {
            <span class="hljs-attr">"contentType"</span>: <span class="hljs-string">"assignment"</span>,
            <span class="hljs-attr">"values"</span>: {
              <span class="hljs-attr">"arraySQL"</span>: <span class="hljs-string">"@outputs('get_existing_assignments')?['body/value']"</span>,
              <span class="hljs-attr">"arrayCurrent"</span>: <span class="hljs-string">"@variables('assignmentArray')"</span>
            }
          }
        ]
      }
    },
    <span class="hljs-attr">"log_outcome"</span>: {
      <span class="hljs-attr">"runAfter"</span>: {
        <span class="hljs-attr">"http_response"</span>: [
          <span class="hljs-string">"Succeeded"</span>
        ]
      },
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"57d990aa-2533-4875-a43a-032328619dca"</span>
      },
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"OpenApiConnection"</span>,
      <span class="hljs-attr">"inputs"</span>: {
        <span class="hljs-attr">"host"</span>: {
          <span class="hljs-attr">"connectionName"</span>: <span class="hljs-string">"shared_sql"</span>,
          <span class="hljs-attr">"operationId"</span>: <span class="hljs-string">"PostItem_V2"</span>,
          <span class="hljs-attr">"apiId"</span>: <span class="hljs-string">"/providers/Microsoft.PowerApps/apis/shared_sql"</span>
        },
        <span class="hljs-attr">"parameters"</span>: {
          <span class="hljs-attr">"server"</span>: <span class="hljs-string">"***SCRUBBED***"</span>,
          <span class="hljs-attr">"database"</span>: <span class="hljs-string">"***SCRUBBED***"</span>,
          <span class="hljs-attr">"table"</span>: <span class="hljs-string">"***SCRUBBED***"</span>,
          <span class="hljs-attr">"item/workflowType"</span>: <span class="hljs-string">"taskRetrieve"</span>,
          <span class="hljs-attr">"item/outcome"</span>: <span class="hljs-string">"@if(\r\n    greater(\r\n        variables('errorCount'),\r\n        0\r\n    ),\r\n    'Complete with Error',\r\n    'Success'\r\n)"</span>,
          <span class="hljs-attr">"item/itemCount"</span>: <span class="hljs-string">"@add(\r\n    add(\r\n        length(variables('assignmentArray')),\r\n        length(variables('checklistArray'))\r\n    ),\r\n    add(\r\n        add(\r\n            length(variables('labelArray')),\r\n            length(outputs('get_active_plans')?['body/value'])\r\n        ),\r\n        add(\r\n            length(variables('taskArray')),\r\n            length(variables('attachmentArray'))\r\n        )\r\n    )\r\n)"</span>,
          <span class="hljs-attr">"item/errorCount"</span>: <span class="hljs-string">"@variables('errorCount')"</span>,
          <span class="hljs-attr">"item/details"</span>: <span class="hljs-string">"Start: @{body('Start_time_PST')} | Runtime: @{div(\r\n    sub(\r\n        ticks(body('End_Time_PST')),\r\n        ticks(body('Start_time_PST'))\r\n    ),\r\n    600000000\r\n)\r\n\r\n} minutes\n@{length(outputs('get_active_plans')?['body/value'])} pln | @{length(variables('assignmentArray'))} asn | @{length(variables('attachmentArray'))} atc | @{length(variables('checklistArray'))} chk | @{length(variables('labelArray'))} lbl | @{length(variables('taskArray'))}tsk@{variables('errorString')}"</span>,
          <span class="hljs-attr">"item/startTime"</span>: <span class="hljs-string">"@body('Start_time_PST')"</span>
        },
        <span class="hljs-attr">"authentication"</span>: <span class="hljs-string">"@parameters('$authentication')"</span>
      }
    },
    <span class="hljs-attr">"if_weekday"</span>: {
      <span class="hljs-attr">"actions"</span>: {},
      <span class="hljs-attr">"runAfter"</span>: {
        <span class="hljs-attr">"Start_time_PST"</span>: [
          <span class="hljs-string">"Succeeded"</span>
        ]
      },
      <span class="hljs-attr">"else"</span>: {
        <span class="hljs-attr">"actions"</span>: {
          <span class="hljs-attr">"Terminate"</span>: {
            <span class="hljs-attr">"runAfter"</span>: {},
            <span class="hljs-attr">"metadata"</span>: {
              <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"2309e5bb-9878-4721-8a1b-273d5e86b574"</span>
            },
            <span class="hljs-attr">"type"</span>: <span class="hljs-string">"Terminate"</span>,
            <span class="hljs-attr">"inputs"</span>: {
              <span class="hljs-attr">"runStatus"</span>: <span class="hljs-string">"Cancelled"</span>
            }
          }
        }
      },
      <span class="hljs-attr">"expression"</span>: {
        <span class="hljs-attr">"and"</span>: [
          {
            <span class="hljs-attr">"less"</span>: [
              <span class="hljs-string">"@dayOfWeek(body('Start_time_PST'))"</span>,
              <span class="hljs-number">6</span>
            ]
          },
          {
            <span class="hljs-attr">"greater"</span>: [
              <span class="hljs-string">"@dayOfWeek(body('Start_time_PST'))"</span>,
              <span class="hljs-number">0</span>
            ]
          }
        ]
      },
      <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"operationMetadataId"</span>: <span class="hljs-string">"9fafc0e2-9c45-40f1-b824-ee5cf3561784"</span>
      },
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"If"</span>
    }
  },
  <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Fetches all active plans and enumerates tasks, assignments, checklists, assignees, and labels. Aggregates those data points and gets them into Azure SQL."</span>
}
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Environment Variables in Power Platform Solutions]]></title><description><![CDATA[Overview
The availability of environment variables within Power Platform solutions is an exciting piece of tech. But like most developing features and platforms, there are some rough edges and pitfalls to be aware of.
Our recent push to a more mature...]]></description><link>https://hamsandwich.dev/environment-variables-in-power-platform-solutions</link><guid isPermaLink="true">https://hamsandwich.dev/environment-variables-in-power-platform-solutions</guid><category><![CDATA[variables]]></category><category><![CDATA[automation]]></category><category><![CDATA[Microsoft]]></category><category><![CDATA[development]]></category><dc:creator><![CDATA[Jake Mannion]]></dc:creator><pubDate>Fri, 01 Apr 2022 22:16:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1648850596587/MD0HmtpP3.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-overview">Overview</h2>
<p>The availability of <a target="_blank" href="https://docs.microsoft.com/en-us/powerapps/maker/data-platform/environmentvariables">environment variables</a> within Power Platform solutions is an exciting piece of tech. But like most developing features and platforms, there are some rough edges and pitfalls to be aware of.</p>
<p>Our recent push to a more mature <a target="_blank" href="https://en.wikipedia.org/wiki/Application_lifecycle_management">ALM</a> model for custom solutions exposed some of those rough edges, so I sought to help clarify and document some guidelines and tips - for my own sanity and yours!</p>
<p>It's easy to get started with environment variables. It's enticingly easy to create them within a solution or environment. Your apps and flows create them automatically, too - just like connections. If you're like me, you'll be absolutely SWIMMING in variables before long - many of them duplicative, redundant or unused.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">⚠</div>
<div data-node-type="callout-text">January 2025 note: Some of the information on this page is outdated!</div>
</div>

<h2 id="heading-the-untangling-process">The Untangling Process</h2>
<h3 id="heading-connections">Connections</h3>
<p>I started with the environment connections - sort of the lowest common denominator in solution config. Power Platform loves to create these on the fly, leaving you with tons of duplicates and no viable way to rename or organize them. (Side note: I used to have luck renaming connections by editing the JSON source with <a target="_blank" href="https://flowstudio.app/">Flow Studio</a>, but I think Microsoft has locked that particular backdoor)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648846337280/OtSVwWL7W.png" alt="accessing power platform connections" /></p>
<p>You can quickly see if a connection is being used by accessing the details pane, and checking the app and flow panes. Not being used? Delete it!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648849194441/HZLHW8pCy.png" alt="connection details" /></p>
<p>But what if you have duplicate connections and they're being used? You can either live with the mess, or clean it up. Living with the mess is a viable option - redundant connections are not as detrimental to a deployment as extra environment variables.</p>
<p>Since this was a DEV environment, I made the decision to try and pare everything down to just ONE connection for each connector type. I knew this would cause rework, but I'd be tinkering with the apps and flows later, giving me the chance to wire everything back up again.</p>
<p>Once I was done, I was left with a clean and tidy set of connections. I <em>really</em> should have taken a "before" picture when this list was five times longer (easily) with duplicates galore.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648848820034/zj5jk5wE-.png" alt="cleaned connections" /></p>
<p>Doing this prework will ensure you have <em>one</em> SharePoint connection, <em>one</em> SQL connection, etc. for use within connection references and the environment variables we'll be sorting out next.</p>
<h3 id="heading-environment-variables">Environment Variables</h3>
<p>A fair amount of trial and error led me to the following observations. Microsoft may smooth this experience out in the future, but I believe these findings are accurate at the time of this posting:</p>
<ol>
<li><p>Data source variables created in an <strong>app</strong> can be used elsewhere in a solution (within flows, e.g.)</p>
</li>
<li><p>Data source variables created in a <strong>flow</strong> cannot be used in an app within the same solution</p>
</li>
<li><p>Data source variables created in a <strong>solution</strong> cannot be used in apps, but can be used within flows</p>
</li>
</ol>
<p>The conclusion here is obvious - let the app(s) within your solution create your site and list variables when working with the SharePoint connector. <em>Don't</em> create them within the solution itself or within Automate.</p>
<p>If you've already got a tangled mess of environment variables in your solution, and aren't sure which ones were created from PowerApps, you'll probably want a fresh start. Append something like "DELETEME" to the preexisting variables within your solution.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648849922461/McPXkKiCW.png" alt="flag confusing variables for deletion later" /></p>
<p>Before deleting the old mess, however, we'll want to create clean, permanent variables - our "keepers." To get started, open your app, remove your SharePoint data sources, and take a deep breath as you watch countless error warnings cascade across your screen.</p>
<p>When you re-add your data sources to the app, the Advanced tab will not work. Don't despair. Paste in the site URL and the app will create variables on the next step within the pane - one for the site, and one for each list.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648851095723/PcLC1HldT.png" alt="confusing data source interface powerapps" /></p>
<p>Note: If your app doesn't seem to be creating variables for you automatically, check <strong>Settings -&gt; General -&gt; Automatically create environment variables</strong> when adding data sources</p>
<p>Once those variables are created, back out of the app, find them within your solution, and give them friendly titles. I found it helpful to use a convention like:</p>
<pre><code class="lang-plaintext">type_VarName
</code></pre>
<p>Delete any site/list variables created via other methods (manually, or via Automate) - if you marked them earlier, this step will be easy. Sometimes deleting one will cause several to disappear at once, because you're kneecapping dependencies. Be careful not to delete the variables you just created.</p>
<h3 id="heading-rewiring-flows">Rewiring Flows</h3>
<p>Go back into your flows, and remap any Site and List values. You must X out the current values and work methodically for this to work correctly. A tip from a colleague: let the list names resolve after you add the site variable, to ensure the method is working.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648847972866/sOLlCs63Y.png" alt="rewiring flow config" /></p>
<p>Your flow should save without errors. If you have errors, go back, and make doubly sure you clear the existing site and list values before adding your app-created environment variables.</p>
<h3 id="heading-other-recommendations">Other Recommendations</h3>
<p>If you have environment variables floating around, outside of your custom solution(s), you can find them in the Default Solution in your development environment:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648848128114/XfE5FZ6dk.png" alt="image.png" /></p>
<p>You'll probably want to clean this up too, while you're at it. Be careful not to delete anything you want to keep!</p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>When you're finished, you should have an ultra clean and easy-to-understand, minimalist set of environment variables. This will make solution development, packaging and deployment <em>much</em> easier!</p>
<p>I went from 12 environment variables down to 3, by following the steps and guidelines outlined above.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648848216617/zrhAQ59yM.png" alt="clean and tidy environment variables" /></p>
<p>It's worth mentioning, this is a loose approach. Your situation may vary. And these guidelines really only apply to SharePoint Online environment variables. Once Microsoft extends data source environment variables to other connector types, the experience may get smoothed out and the information in this article will be outdated. Only time will tell. 😀</p>
]]></content:encoded></item><item><title><![CDATA[Parsing HTML Tables from Incoming Email via Power Automate]]></title><description><![CDATA[Initial Disclosure
This is not a perfect pattern! What I outline here is not enterprise-grade by any means. The whole thing would fall on its face, if scaled. 😀
I approached this problem (and corresponding solution) as a low-risk, thought exercise.
...]]></description><link>https://hamsandwich.dev/parsing-html-tables-from-incoming-email-via-power-automate</link><guid isPermaLink="true">https://hamsandwich.dev/parsing-html-tables-from-incoming-email-via-power-automate</guid><category><![CDATA[HTML Emails]]></category><category><![CDATA[HTML]]></category><category><![CDATA[data]]></category><category><![CDATA[automation]]></category><category><![CDATA[Microsoft]]></category><category><![CDATA[power-automate]]></category><dc:creator><![CDATA[Jake Mannion]]></dc:creator><pubDate>Tue, 15 Mar 2022 19:47:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1647373735856/mLCgxOA8N.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-initial-disclosure">Initial Disclosure</h3>
<p>This is not a perfect pattern! What I outline here is not enterprise-grade by any means. The whole thing would fall on its face, if scaled. 😀</p>
<p>I approached this problem (and corresponding solution) as a low-risk, thought exercise.</p>
<h3 id="heading-the-problem">The Problem</h3>
<p>A few weeks ago, one of our data engineers approached me with an issue: a vendor that handles a bit of customer outreach for the company will <strong>only</strong> return results to us <em>in an HTML table</em>, emailed weekly. There's no API to hit, no CSV we can grab. Nothing!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647367731520/r-RxvQzcm.png" alt="HTML table in a message body" /></p>
<p>How do we get that tabular information into a database so we can report on it?</p>
<h3 id="heading-thinking-through-it">Thinking Through It</h3>
<p>Sending HTML tables via email is a decent way to enable folks to quickly <em>view</em> data, but it's a terrible way to <em>share</em> data. There's no direct path to ingest it, and all of the information is wrapped in heavy layers of HTML - both the table structure itself and the surrounding message body.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647369692042/mdD7ibd-K.png" alt="Examining the HTML structure within the email" /></p>
<p>What makes this tricky is that every scenario is unique. Your "how do I get tabular data out of an email body" problem is almost certainly different from this one. Table structures vary, different systems generate message bodies in different ways, some inject a ton of HTML "junk" and some don't.</p>
<p>Our table contained 93 rows of data - not very much. Those 93 rows of data, however, were wrapped inside <strong>47 nested tables comprising 48,000 lines of HTML</strong> ... yikes!</p>
<h3 id="heading-solutioning-with-power-automate">Solutioning with Power Automate</h3>
<p>While there's no one-size-fits-all approach to solving this, there are some proven methods we can employ!</p>
<ol>
<li>I started by bringing the email message body into a htmlConversion action:</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647369515111/2-dZXBxCB.png" alt="Power Automate htmlConversion action" /></p>
<p>☝ Don't be fooled by Automate's award-winning UI... there's a value in there!</p>
<ol start="2">
<li>The original HTML goes in, and something resembling plaintext comes out after the HTML structure and syntax gets stripped out:</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647370173616/N4SInsNv2.png" alt="Converted HTML into plaintext" /></p>
<ol start="3">
<li>I used a "landmark" method next, finding a reliable chunk of text, in a fixed position near the start and end of the table. Using a subString expression with two Compose actions allowed me to isolate the table:</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647370476839/YpK57Ca0V.png" alt="subString trimming within Compose actions" /></p>
<pre><code class="lang-plaintext">substring(
    body('htmlConversion'),
    add(
        indexOf(
            body('htmlConversion'),
            'Reviewer_Job_Title'
            ),
        20)
)
</code></pre>
<p>The second substring expression is quite similar. You find your landmark, then walk <em>n</em> number of characters forward or backward from there.</p>
<ol start="4">
<li>I borrowed this next method after some web searching yield <a target="_blank" href="https://365basics.com/microsoft-flow-parse-email-and-extract-information/">a great article</a> on the subject.</li>
</ol>
<p>Now that we have the table values isolated in plaintext, we can split them into an array.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647370904768/EzJ8Z__eY.png" alt="image.png" /></p>
<p>The expression inside is a bit tricky. Let's walk through it, starting from the inside out:</p>
<pre><code class="lang-plaintext">split(
    trim(
        replace(
            uriComponent(
                    outputs('trimEnd')
                        ),
                '%0A',
                '~~'
                )
            ),
    '~~')
</code></pre>
<ol>
<li><p>Take the trimmed plaintext...</p>
</li>
<li><p>Encode it</p>
</li>
<li><p>Replace the linebreaks with an arbitrary string ("~~")</p>
</li>
<li><p>Split the entire thing into an array, using the arbitrary string</p>
</li>
</ol>
<ol start="5">
<li>We're almost there! The next step is to dump any empty nodes from the array, like this:</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647371161497/j_VvnCqkW.png" alt="removing empty nodes from array" /></p>
<ol start="6">
<li>After that, we're pretty much home free. We can iterate over the array, and record the data wherever we like (SQL, SharePoint, etc.). I used SharePoint in this example.</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647371234773/nzHVbpcp5.png" alt="image.png" /></p>
<p>The method I employed is a bit flaky - but it works. Since I knew the table had 17 columns, I looped through the primary array until I had a complete row, creating a temporary array in the process. Once my counter hit 17, I cleared the temporary array and reset the sub counter. Rinse, repeat, until the whole thing is processed.</p>
<p>You will want to unencode the values, as you create your temporary array:</p>
<p><code>uriComponentToString(items('Apply_to_each'))</code></p>
<h3 id="heading-what-are-the-weak-points-how-could-we-improve-this-method">What Are the Weak Points? How Could We Improve This Method?</h3>
<ol>
<li><p>Creating an object for each row, instead of manually mapping array position. Would be more elegant, but not really needed in this case.</p>
</li>
<li><p>Relying on “landmarks” in the HTML is really flaky. If the vendor changes anything in their email format, even something trivial like their logo, this solution may stop working.</p>
</li>
<li><p>Looping a fixed number of times only works if table columns are consistent</p>
</li>
<li><p>The flow runs <em>very</em> slow due to serial concurrency. About 12 minutes to process 93 rows of data. Solution not scalable.</p>
</li>
</ol>
<h3 id="heading-final-flow">Final Flow</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647372079204/t2Y7SgW5J.png" alt="image.png" /></p>
]]></content:encoded></item><item><title><![CDATA[Detecting Microsoft Roadmap Changes with Power Automate]]></title><description><![CDATA[Microsoft communicates important product updates, feature additions and bug fixes via the Roadmap. It's quite effective and gives developers, administrators and stakeholders great visibility into what's coming down the release pipeline.

Major update...]]></description><link>https://hamsandwich.dev/detecting-microsoft-roadmap-changes-power-automate</link><guid isPermaLink="true">https://hamsandwich.dev/detecting-microsoft-roadmap-changes-power-automate</guid><category><![CDATA[rss]]></category><category><![CDATA[Microsoft]]></category><category><![CDATA[automation]]></category><category><![CDATA[Roadmap]]></category><category><![CDATA[PowerAutomate]]></category><category><![CDATA[power-automate]]></category><dc:creator><![CDATA[Jake Mannion]]></dc:creator><pubDate>Mon, 14 Mar 2022 21:25:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1647287387169/vxFsrrUJ5.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Microsoft communicates important product updates, feature additions and bug fixes via the <a target="_blank" href="https://www.microsoft.com/en-us/microsoft-365/roadmap?filters=">Roadmap</a>. It's quite effective and gives developers, administrators and stakeholders great visibility into what's coming down the release pipeline.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647284811284/3br06nRvh.png" alt="image.png" /></p>
<p>Major updates to Roadmap items are communicated via email and within each tenant's Message Center. But the Roadmap is vast - not every update warrants direct communication. Items change all the time without notification.</p>
<p>So how do we detect changes to the Roadmap items for which we are acutely interested? If you're eagerly awaiting a new feature, for example, how will you know if the release date gets pushed out?</p>
<h3 id="heading-option-one-check-the-roadmap-every-day">Option One - Check the Roadmap every day</h3>
<p>Nobody has time for that!</p>
<p>Additionally, how would you know <em>definitively</em> that what you're looking at today is different than what you saw yesterday?</p>
<h3 id="heading-option-two-subscribe-to-an-items-rss-feed">Option Two - Subscribe to an item's RSS feed</h3>
<p>Every item on the Roadmap has an RSS link. You could subscribe to the items you care about, and monitor for developments.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647284956732/p8Hwp_PrL.png" alt="image.png" /></p>
<p>However, not everyone uses RSS readers - let alone on a frequent basis, so it might be difficult to detect changes this way.</p>
<h3 id="heading-option-three-poll-the-rss-feed-at-in-interval-with-power-automate">Option Three - Poll the RSS feed at in interval with Power Automate</h3>
<p>1) Create a scheduled, recurring flow using <a target="_blank" href="https://powerautomate.microsoft.com/">Power Automate</a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647285230276/iv6rMGTj3.png" alt="image.png" /></p>
<p>I chose to query the Roadmap weekly - every Wednesday.</p>
<p>2) Copy the RSS feed url from the Roadmap item you wish to track, and paste it within Automate's RSS action</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647285367672/c1FyZovP-.png" alt="image.png" /></p>
<p>3) Store the RSS output in a new object variable</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647285411751/M6v0LMYJE.png" alt="image.png" /></p>
<p>Use an expression to isolate the first return object:</p>
<p><code>first(outputs('Feed')?['body'])</code></p>
<p>4) Use the Get Past Time action to bracket your time range. You'll want this to be greater than or equal to your flow recurrence schedule. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647285650211/SODcXVanH.png" alt="image.png" /></p>
<p>5) Use a Condition to determine if the Roadmap item has been updated within this timeframe</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647285788796/J4JfqIlET.png" alt="image.png" /></p>
<p>You'll need to grab the updatedOn value from the RSS object using a simple expression:</p>
<p><code>variables('varObject')['updatedOn']</code> </p>
<p>6) If the Roadmap item was updated within the last 10 days, you can have Automate send an email to your team, notify people via Teams - the choice is yours! If the Roadmap item hasn't been updated recently, the flow simply terminates and tries again the following week.</p>
<p>I went the simplest route, and had Automate email my team with a link back to the original Roadmap item we were interested in:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647286183896/Z-be9ilh3.png" alt="image.png" /></p>
<h3 id="heading-how-it-works">How it Works</h3>
<p>The Power Automate method is possible thanks to the values returned inside the RSS output. When a Roadmap item is touched, the updatedOn value is refreshed automatically. Even the smallest changes to Roadmap items will trigger a refreshed updatedOn value. We've been using this flow method for about a year now and have found it quite reliable.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647286617566/ntIIHARRe.png" alt="image.png" /></p>
<h3 id="heading-how-could-this-method-be-improved">How Could This Method Be Improved?</h3>
<p>This method doesn't tell you <em>what</em> changed, specifically - only that <em>something</em> changed. Since we only use this for Roadmap items we are <strong>keenly</strong> interested in, it's usually pretty obvious what's changed, once we get the notification and take a look. And, it's almost always release dates. 😁</p>
<p>The RSS output doesn't contain all the information found within each Roadmap item, either. You won't see General Availability dates, release phase information, or tenant type specifics. If we had that information, we could do some cool change data capture and really drill down into the exact nature of each update. But for our use case, the method outlined here is more than enough.</p>
<h3 id="heading-the-flow">The Flow</h3>
<p>Here is the whole thing put together:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647287494320/SBORIHWya.png" alt="image.png" /></p>
]]></content:encoded></item></channel></rss>