Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: generate adaptive cards #5999

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
8 changes: 8 additions & 0 deletions src/Kiota.Builder/Configuration/GenerationConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,14 @@ public PluginAuthConfiguration? PluginAuthInformation
{
get; set;
}

/// <summary>
/// When true, should allow generation of adaptive cards
/// </summary>
public bool? ShouldGenerateAdaptiveCards
{
get; set;
}
}
#pragma warning restore CA1056
#pragma warning restore CA2227
1 change: 1 addition & 0 deletions src/Kiota.Builder/Kiota.Builder.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<NoWarn>$(NoWarn);CS8785;NU5048;NU5104;CA1724;CA1055;CA1848;CA1308;CA1822</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AdaptiveCards" Version="3.1.0" />
<PackageReference Include="AsyncKeyedLock" Version="7.1.4" />
<PackageReference Include="DotNet.Glob" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
Expand Down
63 changes: 63 additions & 0 deletions src/Kiota.Builder/Plugins/AdaptiveCardGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AdaptiveCards;
using Microsoft.OpenApi.Models;

namespace Kiota.Builder.Plugins
{
public class AdaptiveCardGenerator
{
public AdaptiveCardGenerator()
{
}

public AdaptiveCard GenerateAdaptiveCard(OpenApiOperation operation)
{
ArgumentNullException.ThrowIfNull(operation);

var responses = operation.Responses;
var response = responses["200"];
ArgumentNullException.ThrowIfNull(response);

var schema = response.Content["application/json"].Schema;
ArgumentNullException.ThrowIfNull(schema);

var properties = schema.Properties;
ArgumentNullException.ThrowIfNull(properties);

AdaptiveCard card = new AdaptiveCard(new AdaptiveSchemaVersion(1, 5));

foreach (var property in properties)
{

if (property.Value.Type == "string" && property.Value.Format == "uri")
{
card.Body.Add(new AdaptiveImage()
{
Url = new Uri($"${{{property.Key}}}"),
Size = AdaptiveImageSize.Large,
});
}
else if (property.Value.Type == "array")
{
card.Body.Add(new AdaptiveTextBlock()
{
Text = $"${{{property.Key}.join(', ')}}",
});
}
else
{
card.Body.Add(new AdaptiveTextBlock()
{
Text = $"${{{property.Key}, {property.Key}, 'N/A'}}",
});
}
}
return card;
}
}
}
32 changes: 24 additions & 8 deletions src/Kiota.Builder/Plugins/PluginsGenerationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection.Emit;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading;
Expand Down Expand Up @@ -332,7 +333,7 @@ private OpenApiDocument GetDocumentWithTrimmedComponentsAndResponses(OpenApiDocu

private PluginManifestDocument GetManifestDocument(string openApiDocumentPath)
{
var (runtimes, functions, conversationStarters) = GetRuntimesFunctionsAndConversationStartersFromTree(OAIDocument, Configuration.PluginAuthInformation, TreeNode, openApiDocumentPath, Logger);
var (runtimes, functions, conversationStarters) = GetRuntimesFunctionsAndConversationStartersFromTree(OAIDocument, Configuration, TreeNode, openApiDocumentPath, Logger);
var descriptionForHuman = OAIDocument.Info?.Description is string d && !string.IsNullOrEmpty(d) ? d : $"Description for {OAIDocument.Info?.Title}";
var manifestInfo = ExtractInfoFromDocument(OAIDocument.Info);
var pluginManifestDocument = new PluginManifestDocument
Expand Down Expand Up @@ -411,13 +412,14 @@ private sealed record OpenApiManifestInfo(
string? PrivacyUrl = null,
string ContactEmail = DefaultContactEmail);

private static (OpenApiRuntime[], Function[], ConversationStarter[]) GetRuntimesFunctionsAndConversationStartersFromTree(OpenApiDocument document, PluginAuthConfiguration? authInformation, OpenApiUrlTreeNode currentNode,
private static (OpenApiRuntime[], Function[], ConversationStarter[]) GetRuntimesFunctionsAndConversationStartersFromTree(OpenApiDocument document, GenerationConfiguration configuration, OpenApiUrlTreeNode currentNode,
string openApiDocumentPath, ILogger<KiotaBuilder> logger)
{
var runtimes = new List<OpenApiRuntime>();
var functions = new List<Function>();
var conversationStarters = new List<ConversationStarter>();
var configAuth = authInformation?.ToPluginManifestAuth();
var configAuth = configuration.PluginAuthInformation?.ToPluginManifestAuth();
bool shouldGenerateAdaptiveCards = configuration.ShouldGenerateAdaptiveCards ?? false;
if (currentNode.PathItems.TryGetValue(Constants.DefaultOpenApiLabel, out var pathItem))
{
foreach (var operation in pathItem.Operations.Values.Where(static x => !string.IsNullOrEmpty(x.OperationId)))
Expand All @@ -443,14 +445,28 @@ private static (OpenApiRuntime[], Function[], ConversationStarter[]) GetRuntimes

var summary = operation.Summary.CleanupXMLString();
var description = operation.Description.CleanupXMLString();

functions.Add(new Function
var function = new Function
{
Name = operation.OperationId,
Description = !string.IsNullOrEmpty(description) ? description : summary,
States = GetStatesFromOperation(operation),
States = GetStatesFromOperation(operation)
};

if (shouldGenerateAdaptiveCards)
{
var generator = new AdaptiveCardGenerator();
var card = generator.GenerateAdaptiveCard(operation);
function.Capabilities = new FunctionCapabilities
{
ResponseSemantics = new ResponseSemantics
{
StaticTemplate = JsonDocument.Parse(card.ToJson()).RootElement
}
};
}

functions.Add(function);

});
conversationStarters.Add(new ConversationStarter
{
Text = !string.IsNullOrEmpty(summary) ? summary : description
Expand All @@ -461,7 +477,7 @@ private static (OpenApiRuntime[], Function[], ConversationStarter[]) GetRuntimes

foreach (var node in currentNode.Children)
{
var (childRuntimes, childFunctions, childConversationStarters) = GetRuntimesFunctionsAndConversationStartersFromTree(document, authInformation, node.Value, openApiDocumentPath, logger);
var (childRuntimes, childFunctions, childConversationStarters) = GetRuntimesFunctionsAndConversationStartersFromTree(document, configuration, node.Value, openApiDocumentPath, logger);
runtimes.AddRange(childRuntimes);
functions.AddRange(childFunctions);
conversationStarters.AddRange(childConversationStarters);
Expand Down
65 changes: 65 additions & 0 deletions tests/Kiota.Builder.Tests/Plugins/AdaptiveCardGeneratorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using AdaptiveCards;
using Kiota.Builder.Plugins;
using Microsoft.OpenApi.Models;
using Xunit;

namespace Kiota.Builder.Tests.Plugins
{
public sealed class AdaptiveCardGeneratorTests
{
[Fact]
public void GenerateAdaptiveCardFromOperation()
{
var sample = new OpenApiOperation
{
Responses = new OpenApiResponses
{
["200"] = new OpenApiResponse
{
Description = "OK",
Content = new Dictionary<string, OpenApiMediaType>
{
["application/json"] = new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Type = "object",
Properties = new Dictionary<string, OpenApiSchema>
{
["name"] = new OpenApiSchema
{
Type = "string"
},
["age"] = new OpenApiSchema
{
Type = "number"
}
}
}
}
}
}
}
};
var expectedCard = new AdaptiveCard(new AdaptiveSchemaVersion(1, 5));
expectedCard.Body.Add(new AdaptiveTextBlock()
{
Text = "${name, name, 'N/A'}",
});
expectedCard.Body.Add(new AdaptiveTextBlock()
{
Text = "${age, age, 'N/A'}",
});

var generator = new AdaptiveCardGenerator();
var actualCard = generator.GenerateAdaptiveCard(sample);
Assert.Equal(expectedCard.Body.Count, actualCard.Body.Count);
}
}
}
Loading
Loading