Skip to content

Commit

Permalink
Added filtering, sorting and location to Dev Box creation flow (#226)
Browse files Browse the repository at this point in the history
* Added locations

* Initial test changes

* Moved to Management Service

* Housekeeping

* Fixed sorting

* Changed function type and name

* Merge conflict fix

* Added display name

---------

Co-authored-by: Huzaifa Danish <modanish@microsoft.com>
  • Loading branch information
huzaifa-d and huzaifa-msft authored Jun 27, 2024
1 parent d0b1f36 commit 0b0a391
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@ public class DevBoxProjectProperties

public string DevCenterId { get; set; } = string.Empty;

public string Description { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;

public string DisplayName { get; set; } = string.Empty;
}
28 changes: 28 additions & 0 deletions src/AzureExtension/DevBox/Helpers/AbilitiesJSONToCSClasses.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace DevHomeAzureExtension.DevBox.Helpers;

// Example of a task JSON response that will be deserialized into the BaseClass class
// {
// "abilitiesAsAdmin": [],
// "abilitiesAsDeveloper": [
// "ReadDevBoxes",
// "WriteDevBoxes",
// ...
// ]
// }
//

/// <summary>
/// Represents the class for the abilities JSON response.
/// </summary>
public class AbilitiesJSONToCSClasses
{
public class BaseClass
{
public List<string> AbilitiesAsAdmin { get; set; } = new();

public List<string> AbilitiesAsDeveloper { get; set; } = new();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ private void BuildAllProjectsAndPoolJsonObjects()
// Add information for the specific project to the project array
var projectInfo = new JsonObject
{
{ "title", container.Project!.Name },
{ "title", container.Project!.Properties.DisplayName.Length > 0 ? container.Project!.Properties.DisplayName : container.Project!.Name },
{ "value", $"{i}" },
};

Expand All @@ -357,7 +357,7 @@ private void BuildAllProjectsAndPoolJsonObjects()
var poolHardwareSpecs = Resources.GetResource("DevBox_PoolSubtitle", pool.HardwareProfile.VCPUs, pool.HardwareProfile.MemoryGB, pool.StorageProfile.OsDisk.DiskSizeGB);
var poolInfo = new JsonObject
{
{ "title", $"{pool.Name}" },
{ "title", $"{pool.Name} ({pool.Location})" },
{ "subtitle", $"{poolHardwareSpecs}" },
{ "value", $"{k}" },
};
Expand Down
50 changes: 49 additions & 1 deletion src/AzureExtension/Services/DevBox/DevBoxManagementService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using DevHomeAzureExtension.Contracts;
using DevHomeAzureExtension.DevBox.DevBoxJsonToCsClasses;
using DevHomeAzureExtension.DevBox.Exceptions;
using DevHomeAzureExtension.DevBox.Helpers;
using DevHomeAzureExtension.DevBox.Models;
using Microsoft.Windows.DevHome.SDK;
using Serilog;
Expand All @@ -32,6 +33,14 @@ public class DevBoxManagementService : IDevBoxManagementService

private const string DevBoxManagementServiceName = nameof(DevBoxManagementService);

private readonly JsonSerializerOptions _jsonOptions = new()
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};

private const string _writeAbility = "WriteDevBoxes";

/// <inheritdoc cref="IDevBoxManagementService.HttpRequestToManagementPlane"/>/>
public async Task<DevBoxHttpsRequestResult> HttpsRequestToManagementPlane(Uri webUri, IDeveloperId developerId, HttpMethod method, HttpContent? requestContent = null)
{
Expand Down Expand Up @@ -119,6 +128,31 @@ public async Task<string> HttpsRequestToDataPlaneWithRawResponse(HttpClient clie
}
}

// Filter out projects that the user does not have write access to
private bool DeveloperHasWriteAbility(DevBoxProject project, IDeveloperId developerId)
{
try
{
var uri = $"{project.Properties.DevCenterUri}{DevBoxConstants.Projects}/{project.Name}/users/me/abilities?{DevBoxConstants.APIVersion}";
var result = HttpsRequestToDataPlane(new Uri(uri), developerId, HttpMethod.Get, null).Result;
var rawResponse = result.JsonResponseRoot.ToString();
var abilities = JsonSerializer.Deserialize<AbilitiesJSONToCSClasses.BaseClass>(rawResponse, _jsonOptions);
_log.Debug($"Response from abilities: {rawResponse}");

if (abilities!.AbilitiesAsDeveloper.Contains(_writeAbility) || abilities!.AbilitiesAsAdmin.Contains(_writeAbility))
{
return true;
}

return false;
}
catch (Exception ex)
{
_log.Error(ex, $"Unable to get abilities for {project.Name}");
return true;
}
}

/// <inheritdoc cref="IDevBoxManagementService.GetAllProjectsToPoolsMappingAsync"/>
public async Task<List<DevBoxProjectAndPoolContainer>> GetAllProjectsToPoolsMappingAsync(DevBoxProjects projects, IDeveloperId developerId)
{
Expand All @@ -137,14 +171,25 @@ public async Task<List<DevBoxProjectAndPoolContainer>> GetAllProjectsToPoolsMapp

await Parallel.ForEachAsync(projects.Data!, async (project, token) =>
{
if (!DeveloperHasWriteAbility(project, developerId))
{
return;
}
try
{
var properties = project.Properties;
var uriToRetrievePools = $"{properties.DevCenterUri}{DevBoxConstants.Projects}/{project.Name}/{DevBoxConstants.Pools}?{DevBoxConstants.APIVersion}";
var result = await HttpsRequestToDataPlane(new Uri(uriToRetrievePools), developerId, HttpMethod.Get);
var pools = JsonSerializer.Deserialize<DevBoxPoolRoot>(result.JsonResponseRoot.ToString(), DevBoxConstants.JsonOptions);
var container = new DevBoxProjectAndPoolContainer { Project = project, Pools = pools };
// Sort the pools by name, case insensitive
if (pools?.Value != null)
{
pools.Value = new(pools.Value.OrderBy(x => x.Name));
}
var container = new DevBoxProjectAndPoolContainer { Project = project, Pools = pools };
projectsToPoolsMapping.Add(container);
}
catch (Exception ex)
Expand All @@ -153,6 +198,9 @@ await Parallel.ForEachAsync(projects.Data!, async (project, token) =>
}
});

// Sort the mapping by project name, case insensitive
projectsToPoolsMapping = new(projectsToPoolsMapping.OrderByDescending(x => x.Project?.Name));

_projectAndPoolContainerMap.Add(uniqueUserId, projectsToPoolsMapping.ToList());
return projectsToPoolsMapping.ToList();
}
Expand Down

0 comments on commit 0b0a391

Please sign in to comment.