Skip to content

Commit

Permalink
JSON deserialization improvements
Browse files Browse the repository at this point in the history
- Use classes directly to deserialize. This makes future maintenance easier and clearer
- Updated list box picker to allow choosing a "value". Also added classes and interface to make this more generic
  • Loading branch information
ralph-msft committed Mar 6, 2024
1 parent 80cf0a7 commit 21b9099
Show file tree
Hide file tree
Showing 7 changed files with 213 additions and 131 deletions.
58 changes: 39 additions & 19 deletions src/common/details/ai_python_generative_sdk/AiSdkConsoleGui.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,53 @@
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
//

using System;
using System.Linq;
using System.Threading.Tasks;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Azure.AI.Details.Common.CLI.ConsoleGui;
using System.Text.Json;
using System.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Azure.AI.Details.Common.CLI
{
public struct AiHubResourceInfo
public readonly struct AiHubResourceInfo
{
public string Id;
public string Group;
public string Name;
public string RegionLocation;
[JsonProperty("id")]
public string Id { get; init; }

[JsonProperty("resource_group")]
public string Group { get; init; }

[JsonProperty("name")]
public string Name { get; init; }

[JsonProperty("location")]
public string RegionLocation { get; init; }

[JsonProperty("display_name")]
public string DisplayName { get; init; }

public override string ToString() => $"{DisplayName ?? Name} ({RegionLocation})";
}

public struct AiHubProjectInfo
public readonly struct AiHubProjectInfo
{
public string Id;
public string Group;
public string Name;
public string DisplayName;
public string RegionLocation;
public string HubId;
[JsonProperty("id")]
public string Id { get; init; }

[JsonProperty("resource_group")]
public string Group{ get; init; }

[JsonProperty("name")]
public string Name{ get; init; }

[JsonProperty("display_name")]
public string DisplayName{ get; init; }

[JsonProperty("location")]
public string RegionLocation{ get; init; }

[JsonProperty("workspace_hub")]
public string HubId{ get; init; }

public override string ToString() => $"{DisplayName} ({RegionLocation})";
}

public partial class AiSdkConsoleGui
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,27 +40,23 @@ private static async Task<AiHubResourceInfo> PickOrCreateAiHubResource(bool allo
var json = PythonSDKWrapper.ListResources(values, subscription);
if (Program.Debug) Console.WriteLine(json);

var parsed = !string.IsNullOrEmpty(json) ? JToken.Parse(json) : null;
var items = parsed?.Type == JTokenType.Object ? parsed["resources"] : new JArray();
var items = JsonHelpers.DeserializePropertyValueOrDefault<IEnumerable<AiHubResourceInfo>>(json, "resources")
?.OrderBy(res => res.DisplayName + " " + res.Name)
.ThenBy(res => res.RegionLocation)
.ToArray()
?? Array.Empty<AiHubResourceInfo>();

var choices = new List<string>();
foreach (var item in items)
{
var name = item["name"].Value<string>();
var location = item["location"].Value<string>();
var displayName = item["display_name"].Value<string>();

choices.Add(string.IsNullOrEmpty(displayName)
? $"{name} ({location})"
: $"{displayName} ({location})");
}

if (allowCreate)
{
choices.Insert(0, "(Create w/ integrated Open AI + AI Services)");
choices.Insert(1, "(Create w/ standalone Open AI resource)");
choices.Add("(Create w/ integrated Open AI + AI Services)");
choices.Add("(Create w/ standalone Open AI resource)");
}

choices.AddRange(items
.Select(item =>
$"{(string.IsNullOrEmpty(item.DisplayName) ? item.Name : item.DisplayName)} ({item.RegionLocation})"));

if (choices.Count == 0)
{
throw new ApplicationException($"CANCELED: No resources found");
Expand All @@ -74,9 +70,9 @@ private static async Task<AiHubResourceInfo> PickOrCreateAiHubResource(bool allo
}

Console.WriteLine($"\rName: {choices[picked]}");
var resource = allowCreate
? (picked >= 2 ? items.ToArray()[picked - 2] : null)
: items.ToArray()[picked];
AiHubResourceInfo resource = allowCreate
? (picked >= 2 ? items[picked - 2] : default)
: items[picked];

var byoServices = allowCreate && picked == 1;
if (byoServices)
Expand Down Expand Up @@ -109,7 +105,7 @@ private static async Task<AiHubResourceInfo> PickOrCreateAiHubResource(bool allo
return FinishPickOrCreateAiHubResource(values, resource);
}

private static async Task<JToken> TryCreateAiHubResourceInteractive(ICommandValues values, string subscription)
private static async Task<AiHubResourceInfo> TryCreateAiHubResourceInteractive(ICommandValues values, string subscription)
{
var locationName = values.GetOrDefault("service.resource.region.name", "");
var groupName = ResourceGroupNameToken.Data().GetOrDefault(values);
Expand All @@ -125,25 +121,17 @@ private static async Task<JToken> TryCreateAiHubResourceInteractive(ICommandValu
return await TryCreateAiHubResourceInteractive(values, subscription, locationName, groupName, displayName, description, openAiResourceId, openAiResourceKind, smartName, smartNameKind);
}

private static AiHubResourceInfo FinishPickOrCreateAiHubResource(ICommandValues values, JToken resource)
private static AiHubResourceInfo FinishPickOrCreateAiHubResource(ICommandValues values, AiHubResourceInfo resource)
{
var aiHubResource = new AiHubResourceInfo
{
Id = resource["id"].Value<string>(),
Group = resource["resource_group"].Value<string>(),
Name = resource["name"].Value<string>(),
RegionLocation = resource["location"].Value<string>(),
};

ResourceIdToken.Data().Set(values, aiHubResource.Id);
ResourceNameToken.Data().Set(values, aiHubResource.Name);
ResourceGroupNameToken.Data().Set(values, aiHubResource.Group);
RegionLocationToken.Data().Set(values, aiHubResource.RegionLocation);

return aiHubResource;
ResourceIdToken.Data().Set(values, resource.Id);
ResourceNameToken.Data().Set(values, resource.Name);
ResourceGroupNameToken.Data().Set(values, resource.Group);
RegionLocationToken.Data().Set(values, resource.RegionLocation);

return resource;
}

private static async Task<JToken> TryCreateAiHubResourceInteractive(ICommandValues values, string subscription, string locationName, string groupName, string displayName, string description, string openAiResourceId, string openAiResourceKind, string smartName = null, string smartNameKind = null)
private static async Task<AiHubResourceInfo> TryCreateAiHubResourceInteractive(ICommandValues values, string subscription, string locationName, string groupName, string displayName, string description, string openAiResourceId, string openAiResourceKind, string smartName = null, string smartNameKind = null)
{
var sectionHeader = $"\n`CREATE AZURE AI RESOURCE`";
ConsoleHelpers.WriteLineWithHighlight(sectionHeader);
Expand Down Expand Up @@ -178,8 +166,7 @@ private static async Task<JToken> TryCreateAiHubResourceInteractive(ICommandValu

Console.WriteLine("\r*** CREATED *** ");

var parsed = !string.IsNullOrEmpty(json) ? JToken.Parse(json) : null;
return parsed["resource"];
return JsonHelpers.DeserializePropertyValueOrDefault<AiHubResourceInfo>(json, "resource");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,7 @@ public static AiHubProjectInfo PickOrCreateAiHubProject(ICommandValues values, s

public static AiHubProjectInfo CreateAiHubProject(ICommandValues values, string subscription, string resourceId)
{
var project = TryCreateAiHubProjectInteractive(values, subscription, resourceId);
return AiHubProjectInfoFromToken(values, project);
return TryCreateAiHubProjectInteractive(values, subscription, resourceId);
}

private static AiHubProjectInfo PickOrCreateAiHubProject(bool allowCreate, ICommandValues values, string subscription, string resourceId, out bool createNew)
Expand All @@ -107,34 +106,23 @@ private static AiHubProjectInfo PickOrCreateAiHubProject(bool allowCreate, IComm
var json = PythonSDKWrapper.ListProjects(values, subscription);
if (Program.Debug) Console.WriteLine(json);

var parsed = !string.IsNullOrEmpty(json) ? JToken.Parse(json) : null;
var items = parsed?.Type == JTokenType.Object ? parsed["projects"] : new JArray();
var items = JsonHelpers.DeserializePropertyValueOrDefault<IEnumerable<AiHubProjectInfo>>(json, "projects")
?.Where(proj => string.IsNullOrEmpty(resourceId) || proj.HubId == resourceId)
.OrderBy(item => item.DisplayName + " " + item.Name)
.ThenBy(item => item.RegionLocation)
.ToArray()
?? Array.Empty<AiHubProjectInfo>();

var choices = new List<string>();
var itemJTokens = new List<JToken>();
foreach (var item in items)
{
var hub = item["workspace_hub"]?.Value<string>();

var hubOk = string.IsNullOrEmpty(resourceId) || hub == resourceId;
if (!hubOk) continue;

itemJTokens.Add(item);

var name = item["name"].Value<string>();
var location = item["location"].Value<string>();
var displayName = item["display_name"].Value<string>();

choices.Add(string.IsNullOrEmpty(displayName)
? $"{name} ({location})"
: $"{displayName} ({location})");
}

if (allowCreate)
{
choices.Insert(0, "(Create new)");
choices.Add("(Create new)");
}

choices.AddRange(items
.Select(proj =>
$"{(string.IsNullOrEmpty(proj.DisplayName) ? proj.Name : proj.DisplayName)} ({proj.RegionLocation})"));

if (choices.Count == 0)
{
throw new ApplicationException($"CANCELED: No projects found");
Expand All @@ -148,20 +136,27 @@ private static AiHubProjectInfo PickOrCreateAiHubProject(bool allowCreate, IComm
}

Console.WriteLine($"\rName: {choices[picked]}");
var project = allowCreate
? (picked > 0 ? itemJTokens[picked - 1] : null)
: itemJTokens[picked];

createNew = allowCreate && picked == 0;
if (createNew)
createNew = false;
AiHubProjectInfo project;
if (allowCreate && picked == 0)
{
createNew = true;
project = TryCreateAiHubProjectInteractive(values, subscription, resourceId);
}
else if (allowCreate && picked > 0)
{
project = items[picked - 1];
}
else
{
project = items[picked];
}

return AiHubProjectInfoFromToken(values, project);
return project;
}

private static JToken TryCreateAiHubProjectInteractive(ICommandValues values, string subscription, string resourceId)
private static AiHubProjectInfo TryCreateAiHubProjectInteractive(ICommandValues values, string subscription, string resourceId)
{
var group = ResourceGroupNameToken.Data().GetOrDefault(values);
var location = RegionLocationToken.Data().GetOrDefault(values, "");
Expand All @@ -174,22 +169,7 @@ private static JToken TryCreateAiHubProjectInteractive(ICommandValues values, st
return TryCreateAiHubProjectInteractive(values, subscription, resourceId, group, location, ref displayName, ref description, smartName, smartNameKind);
}

private static AiHubProjectInfo AiHubProjectInfoFromToken(ICommandValues values, JToken project)
{
var aiHubProject = new AiHubProjectInfo
{
Id = project["id"].Value<string>(),
Group = project["resource_group"].Value<string>(),
Name = project["name"].Value<string>(),
DisplayName = project["display_name"].Value<string>(),
RegionLocation = project["location"].Value<string>(),
HubId = project["workspace_hub"].Value<string>(),
};

return aiHubProject;
}

private static JToken TryCreateAiHubProjectInteractive(ICommandValues values, string subscription, string resourceId, string group, string location, ref string displayName, ref string description, string smartName = null, string smartNameKind = null)
private static AiHubProjectInfo TryCreateAiHubProjectInteractive(ICommandValues values, string subscription, string resourceId, string group, string location, ref string displayName, ref string description, string smartName = null, string smartNameKind = null)
{
ConsoleHelpers.WriteLineWithHighlight($"\n`CREATE AZURE AI PROJECT`");

Expand All @@ -208,8 +188,7 @@ private static JToken TryCreateAiHubProjectInteractive(ICommandValues values, st

Console.WriteLine("\r*** CREATED *** ");

var parsed = !string.IsNullOrEmpty(json) ? JToken.Parse(json) : null;
return parsed["project"];
return JsonHelpers.DeserializePropertyValueOrDefault<AiHubProjectInfo>(json, "project");
}

public static void GetOrCreateAiHubProjectConnections(ICommandValues values, bool create, string subscription, string groupName, string projectName, string openAiEndpoint, string openAiKey, string searchEndpoint, string searchKey)
Expand Down
13 changes: 1 addition & 12 deletions src/common/details/azcli/AzCli.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,18 +181,7 @@ public static async Task<ParsedJsonProcessOutput<SubscriptionInfo[]>> ListAccoun
var accounts = parsed.Payload;

var x = new ParsedJsonProcessOutput<SubscriptionInfo[]>(parsed.Output);
x.Payload = new SubscriptionInfo[accounts.Count];

var i = 0;
foreach (var account in accounts)
{
x.Payload[i].Id = account["Id"].Value<string>();
x.Payload[i].Name = account["Name"].Value<string>();
x.Payload[i].IsDefault = account["IsDefault"].Value<bool>();
x.Payload[i].UserName = account["UserName"].Value<string>();
i++;
}

x.Payload = accounts.ToObject<SubscriptionInfo[]>();
return x;
}

Expand Down
21 changes: 12 additions & 9 deletions src/common/details/azcli/AzCliConsoleGui_SubscriptionPicker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,19 +155,22 @@ public static async Task<string> PickSubscriptionIdAsync(bool allowInteractiveLo

private static AzCli.SubscriptionInfo? ListBoxPickSubscription(AzCli.SubscriptionInfo[] subscriptions)
{
var list = subscriptions.Select(x => x.Name).ToArray();
var defaultIndex = subscriptions.Select((x, i) => new { x, i }).Where(x => x.x.IsDefault).Select(x => x.i).FirstOrDefault();

var picked = ListBoxPicker.PickIndexOf(list, defaultIndex);
if (picked < 0)
var selected = ListBoxPicker.PickValue(
subscriptions
.Select(s => new ListBoxPickerChoice<AzCli.SubscriptionInfo>()
{
IsDefault = s.IsDefault,
DisplayName = s.Name,
Value = s
}));
if (selected == null)
{
throw new OperationCanceledException("User canceled");
}

var subscription = subscriptions[picked];
DisplayNameAndId(subscription);
CacheSubscriptionUserName(subscription);
return subscription;
DisplayNameAndId(selected.Value);
CacheSubscriptionUserName(selected.Value);
return selected.Value;
}

private static bool MatchSubscriptionFilter(AzCli.SubscriptionInfo subscription, string subscriptionFilter)
Expand Down
Loading

0 comments on commit 21b9099

Please sign in to comment.