{"id":39055,"date":"2022-03-09T11:17:58","date_gmt":"2022-03-09T18:17:58","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/dotnet\/?p=39055"},"modified":"2022-03-09T12:36:40","modified_gmt":"2022-03-09T19:36:40","slug":"automate-code-metrics-and-class-diagrams-with-github-actions","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/automate-code-metrics-and-class-diagrams-with-github-actions\/","title":{"rendered":"Automate code metrics and class diagrams with GitHub Actions"},"content":{"rendered":"<p>Hi friends, in my previous post \u2014 <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/dotnet-loves-github-actions\">.NET \ud83d\udc9c GitHub Actions<\/a>, you were introduced to the GitHub Actions platform and the workflow composition syntax. You learned how common .NET CLI commands and actions can be used as building blocks for creating fully automated CI\/CD pipelines, directly from your GitHub repositories.<\/p>\n<p>This is the second post in the series dedicated to the <a href=\"https:\/\/github.com\/features\/actions\">GitHub Actions<\/a> platform and the relevance it has for .NET developers. In this post, I&#8217;ll summarize an existing GitHub Action written in .NET that can be used to maintain a code metrics markdown file. I&#8217;ll then explain how you can create your own GitHub Actions with .NET. I&#8217;ll show you how to define metadata that&#8217;s used to identify a GitHub repo as an action. Finally, I&#8217;ll bring it all together with a really cool example: updating an action in the .NET samples repository to include class diagram support using <a href=\"https:\/\/github.blog\/2022-02-14-include-diagrams-markdown-files-mermaid\">GitHub&#8217;s brand new Mermaid diagram support<\/a>, so it will build updated class diagrams on every commit.<\/p>\n<h2>Writing a custom GitHub Action with .NET<\/h2>\n<p>Actions support several variations of app development:<\/p>\n<table>\n<thead>\n<tr>\n<th>Action type<\/th>\n<th>Metadata specifier<\/th>\n<th>Description<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><a href=\"https:\/\/docs.github.com\/actions\/creating-actions\/creating-a-docker-container-action?wt.mc_id=dapine\">Docker<\/a><\/td>\n<td><code>runs.using: 'docker'<\/code><\/td>\n<td>Any app that can run as a Docker container.<\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/docs.github.com\/actions\/creating-actions\/creating-a-javascript-action?wt.mc_id=dapine\">JavaScript<\/a><\/td>\n<td><code>runs.using: 'javascript'<\/code><\/td>\n<td>Any Node.js app (includes the benefit of using <a href=\"https:\/\/github.com\/actions\/toolkit\"><code>actions\/toolkit<\/code><\/a>).<\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/docs.github.com\/actions\/creating-actions\/creating-a-composite-action?wt.mc_id=dapine\">Composite<\/a><\/td>\n<td><code>runs.using: 'composite'<\/code><\/td>\n<td>Composes multiple <code>run<\/code> commands and <code>uses<\/code> actions.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>.NET is capable of running in Docker containers, and when you want a fully functioning .NET app to run as your GitHub Action \u2014 you&#8217;ll have to containerize your app. For more information on .NET and Docker, see <a href=\"https:\/\/docs.microsoft.com\/dotnet\/core\/docker\/build-container?wt.mc_id=dapine\">.NET Docs: Containerize a .NET app<\/a>.<\/p>\n<blockquote><p>If you&#8217;re curious about creating JavaScript GitHub Actions, I wrote about that too, see <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/localize-net-applications-with-machine-translation\">Localize .NET applications with machine-translation<\/a>. In that blog post, I cover a TypeScript action that relies on Azure Cognitive Services to automatically generate pull requests for target translations.<\/p><\/blockquote>\n<p>In addition to containerizing a .NET app, you could alternatively create a .NET global tool that could be installed and called upon using the <code>run<\/code> syntax instead of <code>uses<\/code>. This alternative approach is useful for creating a .NET CLI app that can be used as a global tool, but it&#8217;s out of scope for this post. For more information on .NET global tools, see <a href=\"https:\/\/docs.microsoft.com\/dotnet\/core\/tools\/global-tools?wt.mc_id=dapine\">.NET Docs: Global tools<\/a>.<\/p>\n<h3>Intent of the tutorial<\/h3>\n<p>Over on the .NET Docs, there&#8217;s a tutorial on creating a <a href=\"https:\/\/docs.microsoft.com\/dotnet\/devops\/create-dotnet-github-action?wt.mc_id=dapine\">GitHub Action with .NET<\/a>. It covers exactly how to containerize a .NET app, how to author the <em>action.yml<\/em> which represents the action&#8217;s metadata, as well as how to consume it from a workflow. Rather than repeating the entire tutorial, I&#8217;ll summarize the intent of the action, and then we&#8217;ll look at how it was updated.<\/p>\n<p>The app in the tutorial performs code metric analysis by:<\/p>\n<ul>\n<li>Scanning and discovering <em>*.csproj<\/em> and <em>*.vbproj<\/em> project files in a target repository.<\/li>\n<li>Analyzing the discovered source code within these projects for:\n<ul>\n<li>Cyclomatic complexity<\/li>\n<li>Maintainability index<\/li>\n<li>Depth of inheritance<\/li>\n<li>Class coupling<\/li>\n<li>Number of lines of source code<\/li>\n<li>Approximated lines of executable code<\/li>\n<\/ul>\n<\/li>\n<li>Creating (or updating) a <em>CODE_METRICS.md<\/em> file.<\/li>\n<\/ul>\n<p>As part of the consuming workflow composition, a pull request is conditionally (and automatically) created when the <em>CODE_METRICS.md<\/em> file changes. In other words, as you <code>push<\/code> changes to your GitHub repository, the workflow <code>runs<\/code> and <code>uses<\/code> the .NET code metrics action \u2014 which updates the markdown representation of the code metrics. The <em>CODE_METRICS.md<\/em> file itself is navigable with automatic links, and collapsible sections. It uses emoji to highlight code metrics at a glance, for example, when a class has high cyclomatic complexity it bubbles an emoji up to the project level heading the markdown. From there, you can drill down into the class and see the metrics for each method.<\/p>\n<p>The <code>Microsoft.CodeAnalysis.CodeMetrics<\/code> namespace contains the <code>CodeAnalysisMetricData<\/code> type, which exposes the <code>CyclomaticComplexity<\/code> property. This property is a measurement of the structural complexity of the code. It is created by calculating the number of different code paths in the flow of the program. A program that has a complex control flow requires more tests to achieve good code coverage and is less maintainable. When the code is analyzed and the GitHub Action updates the <em>CODE_METRICS.md<\/em> file, it writes an emoji in the header using the following code:<\/p>\n<pre><code class=\"language-csharp\">internal static string ToCyclomaticComplexityEmoji(\r\n    this CodeAnalysisMetricData metric) =&gt;\r\n    metric.CyclomaticComplexity switch\r\n    {\r\n        &gt;= 0 and &lt;= 7 =&gt; \":heavy_check_mark:\",  \/\/ \u2714\ufe0f\r\n        8 or 9 =&gt; \":warning:\",                  \/\/ \u26a0\ufe0f\r\n        10 or 11 =&gt; \":radioactive:\",            \/\/ \u2622\ufe0f\r\n        &gt;= 12 and &lt;= 14 =&gt; \":x:\",               \/\/ \u274c\r\n        _ =&gt; \":exploding_head:\"                 \/\/ \ud83e\udd2f\r\n    };<\/code><\/pre>\n<p>All of the code for this action is provided in the <a href=\"https:\/\/github.com\/dotnet\/samples\/tree\/main\/github-actions\/DotNet.GitHubAction\">.NET samples repository<\/a> and is also part of the <a href=\"https:\/\/docs.microsoft.com\/samples\/dotnet\/samples\/create-dotnet-github-action?wt.mc_id=dapine\">.NET docs code samples browser experience<\/a>. As an example of usage, the action is self-consuming (or dogfooding). As the code updates the action runs, and maintains a <em>CODE_METRICS.md<\/em> file via automated pull requests. Consider the following screen captures showing some of the major parts of the markdown file:<\/p>\n<p><strong><em>CODE_METRICS.md heading<\/em><\/strong><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2022\/03\/code-metrics-markdown.png\" alt=\".NET code metrics markdown sample top-level heading.\" \/><\/p>\n<p><strong><em>CODE_METRICS.md <code>ProjectFileReference<\/code> drill-down heading<\/em><\/strong><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2022\/03\/code-metrics-markdown-drill-down.png\" alt=\".NET code metrics markdown sample drill-down.\" \/><\/p>\n<p>The code metrics markdown file represents the code it analyzes, by providing the following hierarchy:<\/p>\n<blockquote><p>Project \u27a1\ufe0f Namespace \u27a1\ufe0f Named Type \u27a1\ufe0f Members table<\/p><\/blockquote>\n<p>When you drill down into a named type, there is a link at the bottom of the table for the auto-generated class diagram. This is discussed in the <a href=\"#adding-new-functionality\">Adding new functionality<\/a> section. To navigate the example file yourself, see <a href=\"https:\/\/github.com\/dotnet\/samples\/blob\/main\/github-actions\/DotNet.GitHubAction\/CODE_METRICS.md\">.NET samples <em>CODE_METRICS.md<\/em><\/a>.<\/p>\n<h2>Action metadata<\/h2>\n<p>For a GitHub repository to be recognized as a GitHub Action, it must define metadata in an <em>action.yml<\/em> file.<\/p>\n<pre><code class=\"language-yml\"># Name, description, and branding. All of which are used for\r\n# displaying the action in the GitHub Action marketplace.\r\nname: '.NET code metric analyzer'\r\ndescription: 'A GitHub action that maintains a CODE_METRICS.md file,\r\n              reporting cyclomatic complexity, maintainability index, etc.'\r\nbranding:\r\n  icon: sliders\r\n  color: purple\r\n\r\n# Specify inputs, some are required and some are not.\r\ninputs:\r\n  owner:\r\n    description: 'The owner of the repo. Assign from github.repository_owner. Example, \"dotnet\".'\r\n    required: true\r\n  name:\r\n    description: 'The repository name. Example, \"samples\".'\r\n    required: true\r\n  branch:\r\n    description: 'The branch name. Assign from github.ref. Example, \"refs\/heads\/main\".'\r\n    required: true\r\n  dir:\r\n    description: 'The root directory to work from. Example, \"path\/to\/code\".'\r\n    required: true\r\n  workspace:\r\n    description: 'The workspace directory.'\r\n    required: false\r\n    default: '\/github\/workspace'\r\n\r\n# The action outputs the following values.\r\noutputs:\r\n  summary-title:\r\n    description: 'The title of the code metrics action.'\r\n  summary-details:\r\n    description: 'A detailed summary of all the projects that were flagged.'\r\n  updated-metrics:\r\n    description: 'A boolean value, indicating whether or not the CODE_METRICS.md \r\n                  was updated as a result of running this action.'\r\n\r\n# The action runs using docker and accepts the following arguments.\r\nruns:\r\n  using: 'docker'\r\n  image: 'Dockerfile'\r\n  args:\r\n  - '-o'\r\n  - ${{ inputs.owner }}\r\n  - '-n'\r\n  - ${{ inputs.name }}\r\n  - '-b'\r\n  - ${{ inputs.branch }}\r\n  - '-d'\r\n  - ${{ inputs.dir }}\r\n  - '-w'\r\n  - ${{ inputs.workspace }}<\/code><\/pre>\n<p>The metadata for the .NET samples code metrics action is nested within a subdirectory, as such, it is <em>not<\/em> recognized as an action that can be displayed in the <a href=\"https:\/\/github.com\/marketplace?type=actions\">GitHub Action marketplace<\/a>. However, it can still be used as an action.<\/p>\n<p>For more information on metadata, see <a href=\"https:\/\/docs.github.com\/actions\/creating-actions\/metadata-syntax-for-github-actions\">GitHub Docs: Metadata syntax for GitHub Actions<\/a>.<\/p>\n<h2>Consuming workflow<\/h2>\n<p>To consume the .NET code metrics action, a workflow file must exist in the <em>.github\/workflows<\/em> directory from the root of the GitHub repository. Consider the following workflow file:<\/p>\n<pre><code class=\"language-yml\">name: '.NET code metrics'\r\n\r\non:\r\n  push:\r\n    branches: [ main ]\r\n    paths-ignore:\r\n    # Ignore CODE_METRICS.md and README.md files\r\n    - '**.md'\r\n\r\njobs:\r\n  build:\r\n    runs-on: ubuntu-latest\r\n    permissions:\r\n        contents: write\r\n        pull-requests: write\r\n\r\n    steps:\r\n    - uses: actions\/checkout@v2\r\n\r\n    # Analyze repositories source metrics:\r\n    # Create (or update) CODE_METRICS.md file.\r\n    - name: .NET code metrics\r\n      id: dotnet-code-metrics\r\n      uses: dotnet\/samples\/github-actions\/DotNet.GitHubAction@main\r\n      with:\r\n        owner: ${{ github.repository_owner }}\r\n        name: ${{ github.repository }}\r\n        branch: ${{ github.ref }}\r\n        dir: ${{ '.\/github-actions\/DotNet.GitHubAction' }}\r\n\r\n    # Create a pull request if there are changes.\r\n    - name: Create pull request\r\n      uses: peter-evans\/create-pull-request@v3.4.1\r\n      if: ${{ steps.dotnet-code-metrics.outputs.updated-metrics }} == 'true'\r\n      with:\r\n        title: '${{ steps.dotnet-code-metrics.outputs.summary-title }}'\r\n        body: '${{ steps.dotnet-code-metrics.outputs.summary-details }}'\r\n        commit-message: '.NET code metrics, automated pull request.'<\/code><\/pre>\n<p>This workflow makes use of <code>jobs.&lt;job_id&gt;.permissions<\/code>, setting <code>contents<\/code> and <code>pull-requests<\/code> to <code>write<\/code>. This is required for the action to update contents in the repo and create a pull request from those changes. For more information on permissions, see <a href=\"https:\/\/docs.github.com\/actions\/using-workflows\/workflow-syntax-for-github-actions#jobsjob_idpermissions\">GitHub Docs: Workflow syntax for GitHub Actions &#8211; permissions<\/a>.<\/p>\n<p>To help visualize how this workflow functions, see the following sequence diagram:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2022\/03\/code-metrics-action-workflow-sequence-diagram.png\" alt=\"GitHub .NET code metrics workflow sequence diagram.\" \/><\/p>\n<p>The preceding sequence diagram shows the workflow for the .NET code metrics action:<\/p>\n<ol>\n<li>When a developer pushes code to the GitHub repository.\n<ol>\n<li>The workflow is triggered and starts to run.<\/li>\n<\/ol>\n<\/li>\n<li>The source code is checked out into the <code>$GITHUB_WORKSPACE<\/code>.<\/li>\n<li>The .NET code metrics action is invoked.\n<ol>\n<li>The source code is analyzed, and the <em>CODE_METRICS.md<\/em> file is updated.<\/li>\n<\/ol>\n<\/li>\n<li>If the .NET code metrics step (<code>dotnet-code-metrics<\/code>) outputs that metrics were updated, the <code>create-pull-request<\/code> action is invoked.\n<ol>\n<li>The <em>CODE_METRICS.md<\/em> file is checked into the repository.<\/li>\n<li>An automated pull request is created. For example pull requests created by the <code>app\/github-actions<\/code> bot, see <a href=\"https:\/\/github.com\/dotnet\/samples\/pulls?q=is%3Apr+author%3Aapp%2Fgithub-actions\">.NET samples \/ pull requests<\/a>.<\/li>\n<\/ol>\n<\/li>\n<\/ol>\n<h2>Adding new functionality<\/h2>\n<p>GitHub recently announced <a href=\"https:\/\/github.blog\/2022-02-14-include-diagrams-markdown-files-mermaid\">diagram support for Markdown powered by Mermaid<\/a>. Since our custom action is capable of analyzing C# as part of its execution, it has a semantic understanding of the classes it&#8217;s analyzing. This is used to automatically create Mermaid class diagrams in the <em>CODE_METRICS.md<\/em> file.<\/p>\n<p>The .NET code metrics GitHub Action sample code was updated to include Mermaid support.<\/p>\n<pre><code class=\"language-csharp\">static void AppendMermaidClassDiagrams(\r\n    MarkdownDocument document,\r\n    List&lt;(string Id, string Class, string MermaidCode)&gt; diagrams)\r\n{\r\n    document.AppendHeader(\"Mermaid class diagrams\", 2);\r\n\r\n    foreach (var (id, className, code) in diagrams)\r\n    {\r\n        document.AppendParagraph($\"&lt;div id=\"{id}\"&gt;&lt;\/div&gt;\");\r\n        document.AppendHeader($\"`{className}` class diagram\", 5);\r\n        document.AppendCode(\"mermaid\", code);\r\n    }\r\n}<\/code><\/pre>\n<p>If you&#8217;re interested in seeing the code that generates the <code>diagrams<\/code> argument, see the <a href=\"https:\/\/github.com\/dotnet\/samples\/blob\/b769afaa6bcf9721bceaf74375311a7b6d9d232d\/github-actions\/DotNet.GitHubAction\/DotNet.GitHubAction\/Extensions\/CodeAnalysisMetricDataExtensions.cs\"><code>ToMermaidClassDiagram<\/code><\/a> extension method.\nAs an example of what this renders like within the <em>CODE_METRICS.md<\/em>Markdown file, see the following diagram:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2022\/03\/mermaid-class-diagram.png\" alt=\".NET code metrics markdown class diagram.\" \/><\/p>\n<h2>Summary<\/h2>\n<p>In this post, you learned about the different types of GitHub Actions with an emphasis on Docker and .NET. I explained how the .NET code metrics GitHub Action was updated to include Mermaid class diagram support. You also saw an example <em>action.yml<\/em> file that serves as the metadata for a GitHub Action. You then saw a visualization of the consuming workflow, and how the code metrics action is consumed. I also covered how to add new functionality to the code metrics action.<\/p>\n<p>What will you build, and how will it help others? I encourage you to create and share your own .NET GitHub Actions. For more information on .NET and custom GitHub Actions, see the following resources:<\/p>\n<ul>\n<li><a href=\"https:\/\/docs.github.com\/actions\/creating-actions\/about-custom-actions\">GitHub Docs: Creating custom actions<\/a><\/li>\n<li><a href=\"https:\/\/docs.microsoft.com\/dotnet\/devops\/github-actions-overview?wt.mc_id=dapine\">.NET Docs: GitHub Actions and .NET<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Let&#8217;s take a look at how you can add code metrics and diagrams for your open-source .NET repositories with GitHub Actions.<\/p>\n","protected":false},"author":24662,"featured_media":39056,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[685,7589],"tags":[7605,7222,7608],"class_list":["post-39055","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet","category-github-actions","tag-devops","tag-github","tag-github-actions"],"acf":[],"blog_post_summary":"<p>Let&#8217;s take a look at how you can add code metrics and diagrams for your open-source .NET repositories with GitHub Actions.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/39055","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/24662"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=39055"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/39055\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/39056"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=39055"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=39055"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=39055"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}