Hi, I am Samson Amaugo, a Microsoft MVP and a Microsoft Learn Student Ambassador. I love writing and talking about all things DotNet. I am currently a student at the Federal University of Technology, Owerri. We could connect on Linkedin at My Linkedin Profile.
Have you ever thought about going through all your GitHub Repositories, taking note of the languages used, aggregating them, and visualizing it on Excel?
Well, that is what this post is all about except you don’t have to do it manually in a mundane way.
With the Aid of the Octokit GraphQL Library and Microsoft Graph .NET SDK, you could code up a cool that automates this process.
To build out this project in a sandboxed environment with the appropriate permissions I had to sign up on Microsoft 365 Dev Center to Create an Account that I could use to interact with Microsoft 365 products.
The outcome of the project could be seen below
<ItemGroup>
<PackageReference Include="Azure.Identity" Version="1.9.0-beta.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
<PackageReference Include="Microsoft.Graph" Version="4.54.0" />
<PackageReference Include="Octokit" Version="5.0.0" />
<PackageReference Include="Octokit.GraphQL" Version="0.2.0-beta" />
</ItemGroup>
{
"AzureClientID": "eff50f7f-6900-49fb-a245-168fa53d2730",
"AzureClientSecret": "vUx8Q~plb15_Q~2ZscyfxKnR6VrWm634lIYVRb.V",
"AzureTenantID": "33f6d3c4-7d26-473b-a7f0-13b53b72b52b",
"GitHubClientSecret": "ghp_rtPprvqRPlykkYofA4V36EQPNV4SK210LNt7",
"NameOfNewFile": "chartFile.xlsx"
}
You would need to replace the credential above with yours.
using Microsoft.Extensions.Configuration;
namespace MicrosoftGraphDotNet
{
internal class Config
{
// Define properties to hold configuration values
public string? AzureClientId { get; set; }
public string? AzureClientSecret { get; set; }
public string? AzureTenantId { get; set; }
public string? GitHubClientSecret { get; set; }
public string? NameOfNewFile { get; set; }
// Constructor to read configuration values from appsettings.json file
public Config()
{
// Create a new configuration builder and add appsettings.json as a configuration source
IConfiguration config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
// Bind configuration values to the properties of this class
config.Bind(this);
}
}
}
In the Program.cs file I imported the necessary namespaces that I would be needing by adding the code below:
// Import necessary packages
using System.Text.Json;
using Octokit.GraphQL;
using Octokit.GraphQL.Core;
using Octokit.GraphQL.Model;
using Azure.Identity;
using Microsoft.Graph;
using MicrosoftGraphDotNet;
// retrieve the config
var config = new Config();
// Define user agent and connection string for GitHub GraphQL API
var userAgent = new ProductHeaderValue("YOUR_PRODUCT_NAME", "1.0.0");
var connection = new Connection(userAgent, config.GitHubClientSecret!);
// Define GraphQL query to fetch repository names and their associated programming languages
var query = new Query()
.Viewer.Repositories(
isFork: false,
affiliations: new Arg<IEnumerable<RepositoryAffiliation?>>(
new RepositoryAffiliation?[] { RepositoryAffiliation.Owner })
).AllPages().Select(repo => new
{
repo.Name,
Languages = repo.Languages(null, null, null, null, null).AllPages().Select(language => language.Name).ToList()
}).Compile();
// Execute the GraphQL query and deserialize the result into a list of repositories
var result = await connection.Run(query);
var languages = result.SelectMany(repo => repo.Languages).Distinct().ToList();
var repoNameAndLanguages = JsonSerializer.Deserialize<Repository[]>(JsonSerializer.Serialize(result));
// Define a class to hold repository data
class Repository
{
public string? Name { get; set; }
public List<string>? Languages { get; set; }
}
// Define credentials and access scopes for Microsoft Graph API
var tokenCred = new ClientSecretCredential(
config.AzureTenantId!,
config.AzureClientId!,
config.AzureClientSecret!);
var graphClient = new GraphServiceClient(tokenCred);
// Define the file name and create a new Excel file in OneDrive
var driveItem = new DriveItem
{
Name = config.NameOfNewFile!,
File = new Microsoft.Graph.File
{
}
};
var newFile = await graphClient.Drive.Root.Children
.Request()
.AddAsync(driveItem);
// Define the address of the Excel table and create a new table in the file
var address = "Sheet1!A1:" + (char)('A' + languages.Count) + repoNameAndLanguages?.Count();
var hasHeaders = true;
var table = await graphClient.Drive.Items[newFile.Id].Workbook.Tables
.Add(hasHeaders, address)
.Request()
.PostAsync();
The code that represents the data above can be seen below:
// Define the first row of the Excel table with the column headers
var firstRow = new List<string> { "Repository Name" }.Concat(languages).ToList();
// Convert the repository data into a two-dimensional list
List<List<string>> totalRows = new List<List<string>> { firstRow };
foreach (var value in repoNameAndLanguages!)
{
var row = new List<string> { value.Name! };
foreach (var language in languages)
{
row.Add(value.Languages!.Contains(language) ? "1" : "0");
}
totalRows.Add(row);
}
// Add a new row to the table with the total number of repositories for each language
var languageTotalRow = new List<string>();
// Add "Total" as the first item in the list
languageTotalRow.Add("Total");
// Loop through each programming language in the header row
for (var languageIndex = 1; languageIndex < totalRows[0].Count; languageIndex++)
{
// Set the total count for this language to 0
var languageTotal = 0;
// Loop through each repository in the table
for (var repoIndex = 1; repoIndex < totalRows.Count; repoIndex++)
{
// If the repository uses this language, increment the count
if (totalRows[repoIndex][languageIndex] == "1")
{
languageTotal++;
}
}
// Add the total count for this language to the languageTotalRow list
languageTotalRow.Add(languageTotal.ToString());
}
// Add the languageTotalRow list to the bottom of the table
totalRows.Add(languageTotalRow);
// Create a new WorkbookTableRow object with the totalRows list serialized as a JSON document
var workbookTableRow = new WorkbookTableRow
{
Values = JsonSerializer.SerializeToDocument(totalRows),
Index = 0,
};
// Add the new row to the workbook table
await graphClient.Drive.Items[newFile.Id].Workbook.
Tables[table.Id].Rows
.Request()
.AddAsync(workbookTableRow);
// Add a new chart to the worksheet with the language totals as data
await graphClient.Drive.Items[newFile.Id].Workbook.Worksheets["Sheet1"].Charts
.Add("ColumnClustered", "Auto", JsonSerializer.SerializeToDocument($"Sheet1!B2:{(char)('A' + languages.Count)}2, Sheet1!B{repoNameAndLanguages.Count() + 3}:{(char)('A' + languages.Count)}{repoNameAndLanguages.Count() + 3}"))
.Request()
.PostAsync();
// Print the URL of the new file to the console
Console.WriteLine(newFile.WebUrl);
And that’s the end of this article. I hope you enjoyed it and got to see how I used Microsoft Graph .NET SDK to automate this process.
To learn more about Microsoft Graph API and SDKs:
Microsoft Graph https://developer.microsoft.com/graph
Develop apps with the Microsoft Graph Toolkit - Training
Hack Together: Microsoft Graph and .NET
Is a hackathon for beginners to get started building scenario-based apps using .NET and Microsoft Graph. In this hackathon, you will kick-start learning how to build apps with Microsoft Graph and develop apps based on the given Top Microsoft Graph Scenarios, for a chance to win exciting prizes while meeting Microsoft Graph Product Group Leaders, Cloud Advocates, MVPs and Student Ambassadors. The hackathon starts on March 1st and ends on March 15th. It is recommended for participants to follow the Hack Together Roadmap for a successful hackathon.
Demo/Sample Code
You can access the code for this project at https://github.com/sammychinedu2ky/MicrosoftGraphDotNet
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.