<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: absterellio</title>
    <description>The latest articles on DEV Community by absterellio (@absterellio).</description>
    <link>https://dev.to/absterellio</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F993161%2Ff5ef1265-f268-4ded-9332-238bf905352e.png</url>
      <title>DEV Community: absterellio</title>
      <link>https://dev.to/absterellio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/absterellio"/>
    <language>en</language>
    <item>
      <title>Using an Elasticsearch client with Typescript and React to search for data</title>
      <dc:creator>absterellio</dc:creator>
      <pubDate>Tue, 01 Oct 2024 22:00:46 +0000</pubDate>
      <link>https://dev.to/absterellio/using-an-elasticsearch-client-with-typescript-and-react-to-search-for-data-17ig</link>
      <guid>https://dev.to/absterellio/using-an-elasticsearch-client-with-typescript-and-react-to-search-for-data-17ig</guid>
      <description>&lt;h4&gt;
  
  
  Overview
&lt;/h4&gt;

&lt;p&gt;I recently built a search form, pulling data from an Elasticsearch index to  display search results. I wanted to explain the concept, my goals, and the outcome. This is a basic example of using Elastic for searching, and through this exercise I feel more comfortable tackling more complex use cases in the future.&lt;/p&gt;

&lt;h4&gt;
  
  
  Structure of the index:
&lt;/h4&gt;

&lt;p&gt;Below is an example index seen in ElasticVue Chrome extension (&lt;a href="https://elasticvue.com/" rel="noopener noreferrer"&gt;https://elasticvue.com/&lt;/a&gt;). ID's and other data points are blocked to protect the data, but the general structure of the index is still visible. In this example, we can see there is a list of contracts that each have an ID, a CreatedByContactID, a BillingContact, and a BillingContactID.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdnrv0jpo01a45bmf1ev9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdnrv0jpo01a45bmf1ev9.png" alt="Sample ElasticVue index"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Query we want to build:
&lt;/h4&gt;

&lt;p&gt;Let's say this search allows us to input a contact ID, and we want to see the contracts that have a CreatedByContactID or BillingContactID with this value, and we also want to filter any results based on other input criteria, such as a billing contact name and a contract ID (the _id field). &lt;/p&gt;

&lt;p&gt;To do this filtering and data extraction from the index, we'll construct a boolean query (&lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html" rel="noopener noreferrer"&gt;https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html&lt;/a&gt;), which will allow specifying many different types of fields that we want to search on. &lt;/p&gt;

&lt;p&gt;The scoring assigned for this query (aka the calculated relevance of the result) favors matches within the must clause. I can also add filters to this boolean query, although these will not affect the score of the documents returned.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "query": {
        "bool": {
            "must": {
                "multi_match": {
                    "query": 12345,
                    "type": "cross_fields",
                    "fields": [
                        "CreatedByContactID",
                        "BillingContactID"
                    ]
                }
            },
            "filter": {
                "term": {
                    "Id": 678910
                },
                "term": {
                    "BillingContact": "John Smith"
                }
            }
        }
    },
    "size": 10,
    "from": 0,
    "sort": []
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Search form
&lt;/h4&gt;

&lt;p&gt;I built a sample form in React as a UI for the searching. Suppose this is my form, and my React application takes in values for each of the fields specified. Within my app, I can take these values and build a query to search the records in my index.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnonww4e64kftw5oxzclf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnonww4e64kftw5oxzclf.png" alt="Sample search form"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Without going to deep into the infrastructure, I can construct my query in Typescript using an Elasticsearch client (&lt;a href="https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/index.html" rel="noopener noreferrer"&gt;https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/index.html&lt;/a&gt;) to make search calls to the existing Elasticsearch index I connect to. &lt;/p&gt;

&lt;p&gt;Simply put, this client provides a ".search()" method I will be calling with the constructed query to bring data back to my UI. &lt;/p&gt;

&lt;p&gt;Because my query will check on two fields for the same value, I plan to add a multi-match query that checks two fields on the index for the same value. For this particular multi_match, I specified a cross_fields type because I want the input contact ID to check either of the contact fields for that ID. Because I place this multi_match within a 'must' query, these fields must match the id specified and will contribute to the score of the documents returned.&lt;/p&gt;

&lt;p&gt;The extra filters will be added to my elastic query simply as filters to search on. These filters must appear in the documents to be returned, and as mentioned earlier, these filters will not contribute to the calculated score of the results.&lt;/p&gt;

&lt;p&gt;First, I can build an array of search filters that will be passed back to the query that we will pass into the Elastic client. Each filter has a term and a value to search on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  const searchFilter: Array&amp;lt;
  | { term: { [key: string]: string } }
  | { term: { [key: string]: number } }
&amp;gt; = [];

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To add the filters (the ID and Billing Contact), I can push elements to the array.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;searchFilter.push({ term: { ["ID"]: 678910 } }); 
searchFilter.push({ term: { ["BillingContact" ]: "John Smith" } }); 

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I can construct the multi-match 'must' fields array similarly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  const must: Array&amp;lt;
    { multi_match: { query: number; type: string; fields: string[] } }  &amp;gt; = [];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;must.push({
          multi_match: {
              query: 12345,
              type: "cross_fields",
              fields: [
                "CreatedByContactID",
                "BillingContactID"
              ],
          },
      });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Calling the ElasticSearch client
&lt;/h4&gt;

&lt;p&gt;Assuming we have an existing elasticSearchClient built (please see Elasticsearch documentation to learn how to construct a client for searching), we can pass all of our specifications in and hopefully get back some data from the index.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const results = (await elasticSearchClient.search({
    index: // insert index here,
    body: {
      size: 10,
      from: 1,
      query: {
        bool: {
          filter,
          must
        },
      },
      sort,
    },
  })) as ElasticResponse // create your own typescript object to hold the hits returned;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output of this code should match the query I outlined at the top of this article. I can use the results in my application and present them back to the user, who will hopefully be seeing the contracts they expected :)&lt;/p&gt;

&lt;h4&gt;
  
  
  Takeaway
&lt;/h4&gt;

&lt;p&gt;Overall, I learned a lot about constructing queries based on an Elastic index and how to pull out the data I need. I hope this was informative and painted a clearer picture of the concept of constructing boolean queries with bool, must, filter and multi_match queries, especially for those newer to Elasticsearch. I can see how this would be beneficial for quick access to large sets of data and I'm excited to play around more with Elasticsearch.&lt;/p&gt;

&lt;p&gt;Thanks for reading and as always feel free to share any thoughts!&lt;/p&gt;

</description>
      <category>elasticsearch</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>data</category>
    </item>
    <item>
      <title>Building a C# and .NET Git repository reporting tool with Azure DevOps</title>
      <dc:creator>absterellio</dc:creator>
      <pubDate>Mon, 24 Apr 2023 20:21:35 +0000</pubDate>
      <link>https://dev.to/absterellio/building-a-c-and-net-git-repository-reporting-tool-with-azure-devops-57fe</link>
      <guid>https://dev.to/absterellio/building-a-c-and-net-git-repository-reporting-tool-with-azure-devops-57fe</guid>
      <description>&lt;p&gt;I've been using the Azure Devops CLI to build programs to generate reports on a TFS repository that uses Git for version controlling (see: &lt;a href="https://dev.to/absterellio/using-azure-devops-cli-to-generate-repository-reports-1h7d"&gt;Using Azure Devops CLI to generate repository reports&lt;/a&gt; and &lt;a href="https://dev.to/absterellio/adding-a-simple-old-fashioned-gui-to-azure-devops-cli-script-4eoe"&gt;Adding a simple (old-fashioned) GUI to Azure Devops CLI script&lt;br&gt;
&lt;/a&gt;). &lt;/p&gt;

&lt;p&gt;I decided to build a more robust C# and .NET application that utilizes the Azure DevOps SDK.&lt;/p&gt;
&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;This program takes in a TFS iteration path and returns a report of TFS work items, Git pull requests and other details from that iteration. This report is generated as a .csv file with columns for WorkItem, Pull Request, Reviewers, and Target Branch.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Code
&lt;/h2&gt;

&lt;p&gt;The project can be found on my GitHub page: &lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/absterellio"&gt;
        absterellio
      &lt;/a&gt; / &lt;a href="https://github.com/absterellio/azure-csharp-dotnet-tool"&gt;
        azure-csharp-dotnet-tool
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;strong&gt;Overview:&lt;/strong&gt;
This project is a C# and .NET application that takes in a TFS iteration path and returns a report of TFS work items, Git pull requests and other details from that iteration. This report is generated as a .csv file with columns for WorkItem, Pull Request, Reviewers, and Target Branch.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;To use this program:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In the ConstantValues.cs file, replace the 'OrganizationURL' and 'PlatformTeamName' with values from your own Team Foundation instance.&lt;/li&gt;
&lt;li&gt;Create a personal access token for using the Azure Devops Service .NET SDK (see
&lt;a href="https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&amp;amp;tabs=Windows" rel="nofollow"&gt;https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&amp;amp;tabs=Windows&lt;/a&gt; for more information)&lt;/li&gt;
&lt;li&gt;This program is run from the command line. From the command line and in the directory for this project, run "&lt;strong&gt;dotnet run [full-iteration-path] [personal-access-token] [output-file-path.csv]&lt;/strong&gt;", replacing the bracketed values with your unique iteration path, personal access token and desired output file path.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/absterellio/azure-csharp-dotnet-tool"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;&lt;strong&gt;The Entry Point&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Program.cs is the main entry point into the application. It creates an instance of IterationLogic.cs and calls the GetIterationInformation method within that class, where all of my main logic lives.&lt;/p&gt;

&lt;p&gt;The constructor of this class creates an instance of a VssConnection, passing in an organization URL and a created VssBasicCredential. This VssConnection will be used to access the classes and methods and to return data from the DevOps services.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Uri orgUrl = new Uri(ConstantValues.OrganizationURL);&lt;br&gt;
this.connection = new VssConnection(orgUrl, new VssBasicCredential(string.Empty, personalAccessToken));&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The constructor also creates a new TeamContext variable which will be required later anywhere the WorkHttpClient is used.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;teamContext = new TeamContext(ConstantValues.PlatformTeamName);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retrieving an Iteration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The iteration path we want to work with is passed into this method as a string. In order to get information about this iteration we need to create an instance of the WorkHttpClient.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;WorkHttpClient workHttpClient = connection.GetClient&amp;lt;WorkHttpClient&amp;gt;();&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The following code will retrieve all of the iterations based on the TeamContext created earlier. Using the retrieved iterations, we can filter on the specified iteration path string.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;List&amp;lt;TeamSettingsIteration&amp;gt; results workHttpClient.GetTeamIterationsAsync(teamContext).Result;&lt;br&gt;
iterationMatch = results.First(x =&amp;gt; x.Path == iterationPath);&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retrieving Work Items for an Iteration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The following code will retrieve all work items for a given iteration. The WorkHttpClient contains a method to fetch all work items.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;workItems = workHttpClient.GetIterationWorkItemsAsync(teamContext, iteration.Id).Result;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Getting Details for a Work Item&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After fetching these work items, we can hammer down further to find more details about a work item. The WorkItemTrackingHttpClient provides a method to get details for a specific work item. This method can also optionally take in a list of specific fields to return as the second argument.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;WorkItemTrackingHttpClient witClient = connection.GetClient&amp;lt;WorkItemTrackingHttpClient&amp;gt;();&lt;br&gt;
workItem = witClient.GetWorkItemAsync(workItemId).Result;&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fetching Git Pull Requests for a Work Item&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Lastly, the final output I want to return is a list of pull requests submitted for each work item. This will require two clients, the WorkItemTrackingHttpClient and GitHttpClient.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;var witClient = connection.GetClient&amp;lt;WorkItemTrackingHttpClient&amp;gt;();&lt;br&gt;
var gitHttpClient = connection.GetClient&amp;lt;GitHttpClient&amp;gt;();&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Fetching the work item can be done with the following code.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;var workItem = witClient.GetWorkItemAsync(workItemId, expand: Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.WorkItemExpand.Relations).Result;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The returned work item will have a list of Relation attributes. We can filter out the pull requests from this list, and then loop through and use the GitHttpClient to get details for each PullRequestId returned.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;var gitPullRequest = gitHttpClient.GetPullRequestByIdAsync(Int32.Parse(ids[2]));&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;With the pull request returned, I can map the properties back to my own PullRequest view model to print later.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;var prObj = new models.PullRequest&lt;br&gt;
{&lt;br&gt;
    WorkItemId = workItemId,&lt;br&gt;
    ID = pullRequestResult.PullRequestId.ToString(),&lt;br&gt;
    TargetBranch = pullRequestResult.TargetRefName,&lt;br&gt;
    Description = pullRequestResult.Description,&lt;br&gt;
};&lt;/code&gt;&lt;/p&gt;




&lt;p&gt;Overall, this tool ended up being helpful to get some quick data and track workflow on a TFS repository. There are so many more functions that the Azure DevOps .NET SDK provides but I wanted to highlight some of the methods that were helpful to me when building this reporting tool. Writing this program was a great way to get started and jump into programming with the Azure DevOps SDK.&lt;/p&gt;

&lt;p&gt;Thanks for reading and feel free to share your thoughts or check out the repository on &lt;a href="https://github.com/absterellio/azure-csharp-dotnet-tool"&gt;GitHub&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>devops</category>
    </item>
    <item>
      <title>Adding a simple (old-fashioned) GUI to Azure Devops CLI script</title>
      <dc:creator>absterellio</dc:creator>
      <pubDate>Sat, 25 Feb 2023 21:09:39 +0000</pubDate>
      <link>https://dev.to/absterellio/adding-a-simple-old-fashioned-gui-to-azure-devops-cli-script-4eoe</link>
      <guid>https://dev.to/absterellio/adding-a-simple-old-fashioned-gui-to-azure-devops-cli-script-4eoe</guid>
      <description>&lt;h1&gt;
  
  
  Using WinForms!
&lt;/h1&gt;

&lt;p&gt;I wrote a script to pull information from my codebase (see: &lt;a href="https://dev.to/absterellio/using-azure-devops-cli-to-generate-repository-reports-1h7d"&gt;Using Azure Devops CLI to generate repository reports&lt;/a&gt;. I updated the script to present a (very simple) interface to the user to ease usage and provide a way to use the tool without having to deal with command line inputs.&lt;/p&gt;

&lt;p&gt;The updated script takes in an input file path, an output file path and personal access token to access the Azure CLI. For more information on the Azure Devops CLI, see the &lt;a href="https://learn.microsoft.com/en-us/azure/devops/cli/?toc=%2Fazure%2Fdevops%2Fdev-resources%2Ftoc.json&amp;amp;view=azure-devops"&gt;Azure DevOps CLI documentation&lt;/a&gt; or my previous article.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3YrhMu0G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cw2f8xi9fwni56pc7qmy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3YrhMu0G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cw2f8xi9fwni56pc7qmy.png" alt="Repo Report interface" width="726" height="485"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Code
&lt;/h2&gt;

&lt;p&gt;The script can be found on my GitHub page: &lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/absterellio"&gt;
        absterellio
      &lt;/a&gt; / &lt;a href="https://github.com/absterellio/azure-devops-cli-tool-v2"&gt;
        azure-devops-cli-tool-v2
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      azure-devops-cli-tool-v1 with a GUI added
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
azure-devops-cli-tool-v2&lt;/h1&gt;
&lt;p&gt;This project adds a basic GUI to the azure-devops-cli-tool-v1 project found here: &lt;a href="https://github.com/absterellio/azure-devops-cli-tool-v1"&gt;https://github.com/absterellio/azure-devops-cli-tool-v1&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;NOTE: In the azure-repo-script.ps1 file, replace any variables that begin with "ex-" with your own applicable Azure devops values in order for this script to run.&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/absterellio/azure-devops-cli-tool-v2"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;For this script, I added a simple user interface using Windows Forms. I've never used Windows Forms to build a GUI before so I wanted to take note of how I made use of it and some things I learned along the way. This article will walk through the very basics of adding some input controls and logic to the script.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Getting started with Windows Forms&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First, in order to use Windows Forms, I added these line to the top of my file.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Add-Type -AssemblyName System.Windows.Forms&lt;br&gt;
Add-Type -AssemblyName System.Drawing&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I built an intial landing screen that will pop up when the script is first run. The following code creates a basic pop-up form with the dimensions 600 x 400 pixels in the middle of the screen view area.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$form = New-Object System.Windows.Forms.Form&lt;br&gt;
$form.Text = 'Repo Report'&lt;br&gt;
$form.Size = New-Object System.Drawing.Size(600,400)&lt;br&gt;
$form.StartPosition = 'CenterScreen'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adding inputs and text fields:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;New inputs can be added as follows. To build a label, I added a Label control, specifying a start point, size, and text value for the label. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;$label1 = New-Object System.Windows.Forms.Label&lt;br&gt;
$label1.Location = New-Object System.Drawing.Point(150,70)&lt;br&gt;
$label1.Size = New-Object System.Drawing.Size(280,40)&lt;br&gt;
$label1.Text = 'Enter a text file to read from. (Note: Specify each repo you would like to query on a new line of the file):'&lt;br&gt;
$form.Controls.Add($label1)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;To build an input field, I added a TextBox, specifying a start point and size for the input.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$textBox1 = New-Object System.Windows.Forms.TextBox&lt;br&gt;
$textBox1.Location = New-Object System.Drawing.Point(150,110)&lt;br&gt;
$textBox1.Size = New-Object System.Drawing.Size(260,20)&lt;br&gt;
$form.Controls.Add($textBox1)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I added a cancel button with the Button control. In addition to specifying dimensions and size, I added a DiaglogResult property, which will help to handle actions when this button is clicked.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$okButton = New-Object System.Windows.Forms.Button&lt;br&gt;
$okButton.Location = New-Object System.Drawing.Point(200,200)&lt;br&gt;
$okButton.Size = New-Object System.Drawing.Size(75,23)&lt;br&gt;
$okButton.Text = 'OK'&lt;br&gt;
$okButton.DialogResult = [System.Windows.Forms.DialogResult]::OK&lt;br&gt;
$form.AcceptButton = $okButton&lt;br&gt;
$form.Controls.Add($okButton)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Handling events:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After creating all of the inputs, I showed the dialog by specifying:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$result = $form.ShowDialog()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now, '$result' will hold the next button press or event thrown from the UI. We can now handle any event, such as a button press, within an 'if' check for that control.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;if ($result -eq [System.Windows.Forms.DialogResult]::OK)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Any logic that follows can live here.&lt;/p&gt;




&lt;p&gt;I hope this article gave some insight into getting started with WinForms and adding a very simple interface to a PowerShell script. Input or suggestions always welcome.&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>winforms</category>
      <category>powershell</category>
      <category>devops</category>
      <category>azure</category>
    </item>
    <item>
      <title>Using Azure Devops CLI to generate repository reports</title>
      <dc:creator>absterellio</dc:creator>
      <pubDate>Thu, 22 Dec 2022 21:27:51 +0000</pubDate>
      <link>https://dev.to/absterellio/using-azure-devops-cli-to-generate-repository-reports-1h7d</link>
      <guid>https://dev.to/absterellio/using-azure-devops-cli-to-generate-repository-reports-1h7d</guid>
      <description>&lt;p&gt;I needed to see some reports on my codebase, such as information about merges to the main branch of code. To do this, I made use of Microsoft’s Azure DevOps CLI and its corresponding extensions to write a PowerShell script that generates a CSV file with a report of completed pull requests.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/devops/cli/?toc=%2Fazure%2Fdevops%2Fdev-resources%2Ftoc.json&amp;amp;view=azure-devops" rel="noopener noreferrer"&gt;Azure DevOps CLI documentation&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The completed initial version of this script can be found on my GitHub: &lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/absterellio" rel="noopener noreferrer"&gt;
        absterellio
      &lt;/a&gt; / &lt;a href="https://github.com/absterellio/azure-devops-cli-tool-v1" rel="noopener noreferrer"&gt;
        azure-devops-cli-tool-v1
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Basic Powershell script that uses the Azure Devops CLI to generate repository reports
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;azure-devops-cli-tool-v1&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;Basic Powershell script that uses the Azure Devops CLI to generate repository reports.&lt;/p&gt;
&lt;p&gt;NOTE: In the script.ps1 file, replace any variables that begin with "ex-" with your own applicable Azure devops values in order for this script to run.&lt;/p&gt;
&lt;p&gt;Blog post with more details: &lt;a href="https://dev.to/absterellio/using-azure-devops-cli-to-generate-repository-reports-1h7d" rel="nofollow"&gt;https://dev.to/absterellio/using-azure-devops-cli-to-generate-repository-reports-1h7d&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;

  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/absterellio/azure-devops-cli-tool-v1" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;To begin, we need to set up a few things. The Azure DevOps CLI documentation linked above has more details on this. The CLI installation is a very quick download process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Install Azure DevOps CLI&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;2. Add the Azure DevOps extension&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Code
&lt;/h2&gt;

&lt;p&gt;Like stated above, my goal was to generate reports on specific repositories in my codebase. Below, I noted some key ways I made use of the CLI in my script.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authentication:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To begin using the CLI, we need to authenticate. Following the instructions on this page, &lt;a href="https://learn.microsoft.com/en-us/azure/devops/cli/log-in-via-pat?view=azure-devops&amp;amp;tabs=windows" rel="noopener noreferrer"&gt;Azure DevOps PAT documentation&lt;/a&gt;, I created my own personal access token (PAT). The following line takes in the token and authenticates against your organization. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;echo $pat | az devops login --organization ex-projectLink&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;"ex-projectLink" should be replaced with the applicable link to the project, and the $pat variable should be the created access token. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listing pull requests made into a repository:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The CLI specifies a reference for working with repositories (az repos). I added flags to specify completed PR’s that went into the repo’s 'main' branch. "$projectName" should be the name of the project that you are querying.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;az repos pr list --project $projectName --repository $repo --status completed --target-branch ex-main --include-links | ConvertFrom-Json&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The ConvertFrom-Json is a built in cmdlet that I used to convert the json return value into an object so I can grab properties from it later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retrieving work items attached to a pull request:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;az repos pr work-item list --id $pr.pullRequestId | ConvertFrom-Json&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Replace {$pr.pullRequestId} with the corresponding Id of the pull request you’d like to grab work items for and this will return a list of items that were attached to this PR.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Outputting the results to a csv file:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First, I set the headers of the CSV file that I’ll output my results to.&lt;br&gt;
&lt;code&gt;Set-Content -Path $location -Value '"PR Number","Date Closed","Repo Name","Created By","Reviewers","Work Item Id"'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;With the returned PR, grab the id, names of creators/reviewers, closed date, etc. from the pull request and any other applicable values.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$workItemId = $workItem.id&lt;br&gt;
$createdBy = $pr.createdBy.displayName&lt;br&gt;
$reviewedBy = $pr.reviewers.displayName&lt;br&gt;
$date = $pr.closedDate&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then, output the results to the file.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Add-Content -Path $location -Value "$prNumber,$date,$repo,$createdBy,$reviewedByString,$workItemId"&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;I hope this serves as a quick introduction to the Azure DevOps CLI and its basic uses for querying repository and pull request history. Next, I plan to add a GUI and more usability features to this tool for everyday usage.&lt;/p&gt;

&lt;p&gt;Thanks for reading! Feel free to have a look at the script in &lt;a href="https://github.com/absterellio/azure-devops-cli-tool-v1" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and leave thoughts or suggestions for improvement.&lt;/p&gt;

</description>
      <category>vscode</category>
      <category>ai</category>
      <category>security</category>
      <category>privacy</category>
    </item>
  </channel>
</rss>
