{"id":1302,"date":"2024-12-01T11:33:00","date_gmt":"2024-12-01T16:33:00","guid":{"rendered":"https:\/\/automatethemundane.com\/index.php\/2024\/12\/01\/automatic-code-review-of-power-platform-canvas-app-reviews\/"},"modified":"2024-12-01T12:00:30","modified_gmt":"2024-12-01T17:00:30","slug":"automatic-code-review-of-power-platform-canvas-app-reviews","status":"publish","type":"post","link":"https:\/\/automatethemundane.com\/index.php\/2024\/12\/01\/automatic-code-review-of-power-platform-canvas-app-reviews\/","title":{"rendered":"Automatic Code Review of Power Platform Canvas App with Power Platform Pipelines"},"content":{"rendered":"\n<p class=\"has-text-color\" style=\"color:rgb(0, 0, 0)\">A few weeks ago, I was reading on some strategies to do \u201ccode\u201d reviews for power platform solutions.  My goal was to figure how to review a solution that was being pushed via Power Platform Pipelines and give the developer and project lead a scoring for how it performed. This got me reading more into Power Apps Code Review tool created by Power CAT <a href=\"https:\/\/github.com\/microsoft\/powerapps-tools\/tree\/master\/Tools\/Apps\/Microsoft.PowerApps.CodeReview\">powerapps-tools\/Tools\/Apps\/Microsoft.PowerApps.CodeReview at master \u00b7 microsoft\/powerapps-tools \u00b7 GitHub<\/a>. This provided a framework for getting Canvas apps reviewed and ranked but wanted to automate the reviews so that it would do the review as it was being promoted in the pipeline. <\/p>\n\n\n\n<p class=\"has-text-color\" style=\"color:rgb(0, 0, 0)\">The basic architecture will be like this\n<\/p>\n\n\n\n<div class=\"wp-block-wp-mermaid-block mermaid\">\nflowchart TD\n\tsubgraph Tennant\n\tB[1.2.1 - Start App Review] --> |Solution Name &amp; Solution Version &amp; Canvas App Score &amp; Link to Report| C\n\tdirection TB\n    subgraph Pipeline Orchestrator\n        A[1.2 - When a push to test happens] -->|Solution Name &amp; Solution Version &amp; Stage Run ID &amp; ArtifactID| B\n    end\n    subgraph Production\n        C[Solution History Table]\n    end\n    end\n<\/div>\n\n\n\n<p>&nbsp;<\/p>\n\n\n\n<p class=\"has-text-color\" style=\"color:rgb(0, 0, 0)\">When a new push to test occurs, a review of the solution will occur. After the review, the output is sent to the PPPM Solution History table. From here we can add the score to the notification sent to the project lead before approving the solution into production. <\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Solution History Table<\/h1>\n\n\n\n<p class=\"has-text-color\" style=\"color:rgb(0, 0, 0)\">Within the PPPM solution, create a new table called Power Platform Solutions History<\/p>\n\n\n\n<div class=\"wp-block-wp-mermaid-block mermaid\">\n---\ntitle: Power Platform Solution History\n---\nerDiagram\nSolution_History}| -- || PPPM_Solution_Tracking:contains\n     Solution_History {\n        string Solution_Version_Name PK\n        url Link_to_report\n        float App_Review_Score\n        float Review_Score\n        GUID PPPM_Solution FK\n    }\n<\/div>\n\n\n\n<p>&nbsp;<\/p>\n\n\n\n<p class=\"has-text-color\" style=\"color:rgb(0, 0, 0)\">&nbsp;<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-140-1024x674.png\" alt=\"\"\/><\/figure>\n\n\n\n<h1 class=\"wp-block-heading\">Power Apps Code Review Tool<\/h1>\n\n\n\n<p class=\"has-text-color\" style=\"color:rgb(0, 0, 0)\">The Power Apps Code Review tool is comprised of two apps (MDA and a canvas). The MDA is where all the rules and the reviews can be reviewed.  The canvas app is where the an amazing showcase for what a canvas app can do. It provides a great interface for Code Reviews Checklists, App Checker, App Overview, Code Review, and Review Results<\/p>\n\n\n\n<p class=\"has-text-color\" style=\"color:rgb(0, 0, 0)\">&nbsp;<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-141-1024x442.png\" alt=\"\"\/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-142-1024x280.png\" alt=\"\"\/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-143-1024x432.png\" alt=\"\"\/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-144-1024x453.png\" alt=\"\"\/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-145-1024x445.png\" alt=\"\"\/><\/figure>\n\n\n\n<p class=\"has-text-color\" style=\"color:rgb(0, 0, 0)\">&nbsp;<\/p>\n\n\n\n<p class=\"has-text-color\" style=\"color:rgb(0, 0, 0)\">Within the app I made an update to help with deep linking. <\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Within the AppOnStart I set the AppID to the environment variable the equals the environment variable andy_PowerAppsReviewAppID\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-146.png\" alt=\"\"\/><\/figure>\n\n\n\n<pre class=\"wp-block-code\"><code>Set(\n    AppID,\n    LookUp('Environment Variable Values', 'Schema Name' = \"andy_PowerAppsReviewAppID\").Value\n);<\/code><\/pre>\n\n\n\n<p>&nbsp;<\/p>\n<\/li>\n\n\n\n<li>Within the AppStartScreen I updated the If statement to navigate to the SolutionDetailsScreen\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-147.png\" alt=\"\"\/><\/figure>\n\n\n\n<pre class=\"wp-block-code\"><code>If(!IsBlank(Param(\"reviewid\")), SolutionDetailsScreen)<\/code><\/pre>\n\n\n\n<p>&nbsp;<\/p>\n<\/li>\n\n\n\n<li>Within the OnVisible of the SolutionDetailsScreen, add an if statement that will set the variable ActiveSolutionReviewID to be either reviewid or SolutionReviewID. Then update the var SelectedSolutionReview and collection SolutionReviewApps. \n\n<pre class=\"wp-block-code\"><code>\/\/ Set ActiveSolutionReviewID based on whether the parameter is blank\nSet(\n    ActiveSolutionReviewID,\n    If(\n        IsBlank(Param(\"reviewid\")),\n        SolutionReviewID,\n        Param(\"reviewid\")\n    )\n);\n\n\/\/ Set SelectedSolutionReview using ActiveSolutionReviewID\nSet(\n    SelectedSolutionReview,\n    LookUp(\n        PatchedSolutionReviews,\n        Rev_SolutionReview = GUID(ActiveSolutionReviewID)\n    )\n);\n\n\/\/ Populate SolutionReviewApps using ActiveSolutionReviewID\nClearCollect(\n    SolutionReviewApps,\n    Filter(\n        Rev_Apps,\n        Rev_Apps_SolutionReview.Rev_SolutionReview = GUID(ActiveSolutionReviewID) &amp;&amp;\n        Chx_AppType &lt;&gt; AppType.'Model Driven App'\n    )\n);\n<\/code><\/pre>\n\n\n\n<p>&nbsp;<\/p>\n<\/li>\n\n\n\n<li>The Solution score button in the upper right needs to be updated as well to use the new variable \n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-148.png\" alt=\"\"\/><\/figure>\n\n\n\n<pre class=\"wp-block-code\"><code>Round(\n    LookUp(\n        Rev_SolutionReview,\n        Rev_SolutionReview = GUID(ActiveSolutionReviewID)\n    ).Nb_SolutionScore * 100,\n    0\n) &amp; \"%\"<\/code><\/pre>\n\n\n\n<p>&nbsp;<\/p>\n<\/li>\n\n\n\n<li>I also updated the header back button to be hidden if the review is not blank. This way the user can only navigate to the report they were sent.\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-149.png\" alt=\"\"\/><\/figure>\n<\/li>\n<\/ol>\n\n\n\n<h1 class=\"wp-block-heading\">Pipeline Flow Configuration<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">1.2 - When a push to test happens<\/h2>\n\n\n\n<p class=\"has-text-color\" style=\"color:rgb(0, 0, 0)\">The trigger for the flow will be When an action is performed<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-150.png\" alt=\"\"\/><\/figure>\n\n\n\n<p class=\"has-text-color\" style=\"color:rgb(0, 0, 0)\">To ensure that the trigger only occurs when a push to test occurs, a trigger condition needs to be set. For me, it is the deploy to test stage<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-151.png\" alt=\"\"\/><\/figure>\n\n\n\n<pre class=\"wp-block-code\"><code>@equals(triggerOutputs()?&#91;'body\/OutputParameters\/DeploymentStageName'], 'STAGENAME')<\/code><\/pre>\n\n\n\n<p>&nbsp;<\/p>\n\n\n\n<p class=\"has-text-color\" style=\"color:rgb(0, 0, 0)\">Next will be a Scope with two Dataverse Get row ID actions<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-152.png\" alt=\"\"\/><\/figure>\n\n\n\n<p class=\"has-text-color\" style=\"color:rgb(0, 0, 0)\">&nbsp;<\/p>\n\n\n\n<p class=\"has-text-color\" style=\"color:rgb(0, 0, 0)\">The first will get the stage run information using the stage run id from the trigger<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>triggerOutputs()?&#91;'body\/InputParameters\/StageRunId']<\/code><\/pre>\n\n\n\n<p>&nbsp;<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-153.png\" alt=\"\"\/><\/figure>\n\n\n\n<p class=\"has-text-color\" style=\"color:rgb(0, 0, 0)\">&nbsp;<\/p>\n\n\n\n<p class=\"has-text-color\" style=\"color:rgb(0, 0, 0)\">The other will get the Deployment Artifact from the action above<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>outputs('Get_a_row_by_ID_-_Deployment_Stage_Runs')?&#91;'body\/_artifactid_value']<\/code><\/pre>\n\n\n\n<p>&nbsp;<\/p>\n\n\n\n<p class=\"has-text-color\" style=\"color:rgb(0, 0, 0)\">&nbsp;<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-154.png\" alt=\"\"\/><\/figure>\n\n\n\n<p class=\"has-text-color\" style=\"color:rgb(0, 0, 0)\">A child flow will be triggered that starts the actual app review<\/p>\n\n\n\n<p class=\"has-text-color\" style=\"color:rgb(0, 0, 0)\">&nbsp;<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-155.png\" alt=\"\"\/><\/figure>\n\n\n\n<p class=\"has-text-color\" style=\"color:rgb(0, 0, 0)\">&nbsp;<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-156.png\" alt=\"\"\/><\/figure>\n\n\n\n<p class=\"has-text-color\" style=\"color:rgb(0, 0, 0)\">A scope with a condition based on the review status of the app review<\/p>\n\n\n\n<p class=\"has-text-color\" style=\"color:rgb(0, 0, 0)\">&nbsp;<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-157.png\" alt=\"\"\/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-158.png\" alt=\"\"\/><\/figure>\n\n\n\n<p class=\"has-text-color\" style=\"color:rgb(0, 0, 0)\">Within the True side, add two Dataverse Actions. <\/p>\n\n\n\n<p class=\"has-text-color\" style=\"color:rgb(0, 0, 0)\">&nbsp;<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-159.png\" alt=\"\"\/><\/figure>\n\n\n\n<p class=\"has-text-color\" style=\"color:rgb(0, 0, 0)\">The first will get the GUID of the solutions tracking table in the production environment using the name of the Artifact<\/p>\n\n\n\n<p class=\"has-text-color\" style=\"color:rgb(0, 0, 0)\">&nbsp;<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-160-1024x345.png\" alt=\"\"\/><\/figure>\n\n\n\n<p class=\"has-text-color\" style=\"color:rgb(0, 0, 0)\">The second adds a row to the Solution History table in Production table<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-161.png\" alt=\"\"\/><\/figure>\n\n\n\n<p class=\"has-text-color\" style=\"color:rgb(0, 0, 0)\">&nbsp;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">1.2.1 - Start App Review<\/h2>\n\n\n\n<p class=\"has-text-color\" style=\"color:rgb(0, 0, 0)\">The 1.2.1 flow is the child flow called from the 1.2.1 flow and start the actual app review<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>The trigger will be a manual trigger that asks for the StageRunID, ArtifactID, ArtifactName, ArtifactVersion\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-162.png\" alt=\"\"\/><\/figure>\n<\/li>\n\n\n\n<li>Next, add a Boolean variable called varStatus\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-163.png\" alt=\"\"\/><\/figure>\n<\/li>\n\n\n\n<li>To get the artifact out of the pipeline a HTTP action\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-164.png\" alt=\"\"\/><\/figure>\n<\/li>\n\n\n\n<li>Within a Scope action four Dataverse Actions are added\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-165.png\" alt=\"\"\/><\/figure>\n<\/li>\n\n\n\n<li>The first will add a new row to the Solution Review table\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-166.png\" alt=\"\"\/><\/figure>\n<\/li>\n\n\n\n<li>The second will upload the file to the Solution Review table\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-167.png\" alt=\"\"\/><\/figure>\n<\/li>\n\n\n\n<li>The third adds a new row to the Review Request table\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-168.png\" alt=\"\"\/><\/figure>\n<\/li>\n\n\n\n<li>The fourth will set the review status of In Progress\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-169.png\" alt=\"\"\/><\/figure>\n<\/li>\n\n\n\n<li>After the Scope another scope is added with a Do Until Action and a Dataverse Action\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-170.png\" alt=\"\"\/><\/figure>\n<\/li>\n\n\n\n<li>The Do until will keep running until varStatus is equal to true\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-171.png\" alt=\"\"\/><\/figure>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Within the Do until add a delay action, this will give the review time to run<\/li>\n\n\n\n<li>Next add a Get row by ID of the Solution Review table\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-172.png\" alt=\"\"\/><\/figure>\n<\/li>\n\n\n\n<li>Add a condition that checks to see if the status of the review is done\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-173.png\" alt=\"\"\/><\/figure>\n<\/li>\n\n\n\n<li>If it equals Done or Error and not In Progress then it will set the variable varStatus to true\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-174.png\" alt=\"\"\/><\/figure>\n<\/li>\n<\/ol>\n<\/li>\n\n\n\n<li>The last action within the scope will get the Solution Review record via ID\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-175.png\" alt=\"\"\/><\/figure>\n<\/li>\n\n\n\n<li>The last action within the flow will be to respond to the parent flow with the app review score, the review status, and the reviewid<\/li>\n<\/ol>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/11\/image-176.png\" alt=\"\"\/><\/figure>\n\n\n\n<p class=\"has-text-color\" style=\"color:rgb(0, 0, 0)\">&nbsp;<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity is-style-wide\"\/>\n\n\n\n<p class=\"has-text-color\" style=\"color:rgb(0, 0, 0)\">This was a really interesting post to write. I have always wanted to do \u201creviews\u201d with my teams that do not have ADO or another connected system. In the next post, I will go over how I build a method for doing reviews on Power Automate flows using Power Platform Pipelines.  <\/p>\n","protected":false},"excerpt":{"rendered":"<p>A few weeks ago, I was reading on some strategies to do \u201ccode\u201d reviews for power platform solutions. My goal was to figure how to review a solution that was being pushed via Power Platform Pipelines and give the developer and project lead a scoring for how it performed. This got me reading more into [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1303,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8,22,30,42,45,37,35],"tags":[],"class_list":["post-1302","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-canvas-app","category-dataverse","category-power-automate","category-power-pipelines","category-pppm","category-security","category-tables","entry","has-media"],"jetpack_featured_media_url":"https:\/\/automatethemundane.com\/wp-content\/uploads\/2024\/12\/photo-1609177338111-c5cb3694d0e9-scaled.jpg","_links":{"self":[{"href":"https:\/\/automatethemundane.com\/index.php\/wp-json\/wp\/v2\/posts\/1302","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/automatethemundane.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/automatethemundane.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/automatethemundane.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/automatethemundane.com\/index.php\/wp-json\/wp\/v2\/comments?post=1302"}],"version-history":[{"count":2,"href":"https:\/\/automatethemundane.com\/index.php\/wp-json\/wp\/v2\/posts\/1302\/revisions"}],"predecessor-version":[{"id":1305,"href":"https:\/\/automatethemundane.com\/index.php\/wp-json\/wp\/v2\/posts\/1302\/revisions\/1305"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/automatethemundane.com\/index.php\/wp-json\/wp\/v2\/media\/1303"}],"wp:attachment":[{"href":"https:\/\/automatethemundane.com\/index.php\/wp-json\/wp\/v2\/media?parent=1302"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/automatethemundane.com\/index.php\/wp-json\/wp\/v2\/categories?post=1302"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/automatethemundane.com\/index.php\/wp-json\/wp\/v2\/tags?post=1302"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}