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

Adds plugin node in the manifest.json file if it already exists and create the manifest.json when it doesn't #4914

Merged
merged 12 commits into from
Jul 2, 2024
160 changes: 160 additions & 0 deletions src/Kiota.Builder/Plugins/Models/AppManifestModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Kiota.Builder.Plugins.Models;

internal class AppManifestModel
{
private const string DefaultSchema = "https://developer.microsoft.com/json-schemas/teams/vDevPreview/MicrosoftTeams.schema.json";
private const string DefaultManifestVersion = "devPreview";
private const string DefaultVersion = "1.0.0";

[JsonPropertyName("$schema")]
public string? Schema
{
get;
set;
} = DefaultSchema;

public string? ManifestVersion
{
get;
set;
} = DefaultManifestVersion;

public string? Version
{
get;
set;
} = DefaultVersion;
public string? Id
{
get; set;
}
public Developer? Developer
{
get; init;
}
public string? PackageName
{
get; set;
}
public Name? Name
{
get; set;
}
public Description? Description
{
get; set;
}
public Icons? Icons
{
get; set;
}
public string? AccentColor
{
get; set;
}
public CopilotExtensions? CopilotExtensions
{
get; set;
}

[JsonExtensionData]
public Dictionary<string, Object> AdditionalData { get; set; } = new();
}

[JsonSerializable(typeof(AppManifestModel))]
[JsonSerializable(typeof(JsonElement))]
internal partial class AppManifestModelGenerationContext : JsonSerializerContext
{
}

#pragma warning disable CA1054
internal class Developer
#pragma warning restore CA1054
{
public string? Name
{
get; set;
}
#pragma warning disable CA1056
public string? WebsiteUrl
{
get; set;
}
public string? PrivacyUrl
{
get; set;
}
public string? TermsOfUseUrl
{
get; set;
}
#pragma warning restore CA1056

[JsonExtensionData]
public Dictionary<string, Object> AdditionalData { get; set; } = new();
}

internal class Name
{
[JsonPropertyName("short")]
public string? ShortName
{
get; set;
}
[JsonPropertyName("full")]
public string? FullName
{
get; set;
}
}

internal class Description
{
[JsonPropertyName("short")]
public string? ShortName
{
get; set;
}
[JsonPropertyName("full")]
public string? FullName
{
get; set;
}
}

internal class Icons
{
public string? Color
{
get;
set;
} = "color.png";

public string? Outline
{
get;
set;
} = "outline.png";
}

internal class CopilotExtensions
{
public IList<Plugin> Plugins { get; set; } = new List<Plugin>();
}

internal class Plugin
{
public string? Id
{
get; set;
}
public string? File
{
get; set;
}
}
84 changes: 82 additions & 2 deletions src/Kiota.Builder/Plugins/PluginsGenerationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Kiota.Builder.Configuration;
using Kiota.Builder.Extensions;
using Kiota.Builder.OpenApiExtensions;
using Kiota.Builder.Plugins.Models;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.OpenApi.ApiManifest;
using Microsoft.OpenApi.Models;
Expand Down Expand Up @@ -38,9 +40,10 @@
private const string ManifestFileNameSuffix = ".json";
private const string DescriptionPathSuffix = "openapi.yml";
private const string OpenAIManifestFileName = "openai-plugins";
private const string AppManifestFileName = "manifest.json";
public async Task GenerateManifestAsync(CancellationToken cancellationToken = default)
{
// write the description
// 1. write the OpenApi description
var descriptionRelativePath = $"{Configuration.ClientClassName.ToLowerInvariant()}-{DescriptionPathSuffix}";
var descriptionFullPath = Path.Combine(Configuration.OutputPath, descriptionRelativePath);
var directory = Path.GetDirectoryName(descriptionFullPath);
Expand All @@ -55,7 +58,8 @@
trimmedPluginDocument.SerializeAsV3(descriptionWriter);
descriptionWriter.Flush();

// write the plugins
// 2. write the plugins

foreach (var pluginType in Configuration.PluginTypes)
{
var manifestFileName = pluginType == PluginType.OpenAI ? OpenAIManifestFileName : $"{Configuration.ClientClassName.ToLowerInvariant()}-{pluginType.ToString().ToLowerInvariant()}";
Expand All @@ -72,7 +76,7 @@
pluginDocument.Write(writer);
break;
case PluginType.APIManifest:
var apiManifest = new ApiManifestDocument("application"); //TODO add application name

Check warning on line 79 in src/Kiota.Builder/Plugins/PluginsGenerationService.cs

View workflow job for this annotation

GitHub Actions / Build

Complete the task associated to this 'TODO' comment. (https://rules.sonarsource.com/csharp/RSPEC-1135)

Check warning on line 79 in src/Kiota.Builder/Plugins/PluginsGenerationService.cs

View workflow job for this annotation

GitHub Actions / Build

Complete the task associated to this 'TODO' comment. (https://rules.sonarsource.com/csharp/RSPEC-1135)
// pass empty config hash so that its not included in this manifest.
apiManifest.ApiDependencies.AddOrReplace(Configuration.ClientClassName, Configuration.ToApiDependency(string.Empty, TreeNode?.GetRequestInfo().ToDictionary(static x => x.Key, static x => x.Value) ?? [], WorkingDirectory));
var publisherName = string.IsNullOrEmpty(OAIDocument.Info?.Contact?.Name)
Expand All @@ -93,8 +97,84 @@
}
await writer.FlushAsync(cancellationToken).ConfigureAwait(false);
}

// 3. write the app manifest if its an Api Plugin
if (Configuration.PluginTypes.Any(static plugin => plugin == PluginType.APIPlugin))
{
var manifestFullPath = Path.Combine(Configuration.OutputPath, AppManifestFileName);
var pluginFileName = $"{Configuration.ClientClassName.ToLowerInvariant()}-{PluginType.APIPlugin.ToString().ToLowerInvariant()}{ManifestFileNameSuffix}";
var appManifestModel = await GetAppManifestModelAsync(pluginFileName, manifestFullPath, cancellationToken).ConfigureAwait(false);
#pragma warning disable CA2007
await using var appManifestStream = File.Open(manifestFullPath, FileMode.Create);
#pragma warning restore CA2007
await JsonSerializer.SerializeAsync(appManifestStream, appManifestModel, AppManifestModelGenerationContext.AppManifestModel, cancellationToken).ConfigureAwait(false);
}
}

private async Task<AppManifestModel> GetAppManifestModelAsync(string pluginFileName, string manifestFullPath, CancellationToken cancellationToken)
{
var manifestInfo = ExtractInfoFromDocument(OAIDocument.Info);
// create default model
var manifestModel = new AppManifestModel
{
Id = Guid.NewGuid().ToString(),
Developer = new Developer
{
Name = !string.IsNullOrEmpty(OAIDocument.Info?.Contact?.Name) ? OAIDocument.Info?.Contact?.Name : "Microsoft Kiota.",
WebsiteUrl = !string.IsNullOrEmpty(OAIDocument.Info?.Contact?.Url?.OriginalString) ? OAIDocument.Info?.Contact?.Url?.OriginalString : "https://www.example.com/contact/",
PrivacyUrl = !string.IsNullOrEmpty(manifestInfo.PrivacyUrl) ? manifestInfo.PrivacyUrl : "https://www.example.com/privacy/",
TermsOfUseUrl = !string.IsNullOrEmpty(OAIDocument.Info?.TermsOfService?.OriginalString) ? OAIDocument.Info?.TermsOfService?.OriginalString : "https://www.example.com/terms/",
},
PackageName = $"com.microsoft.kiota.plugin.{Configuration.ClientClassName}",
Name = new Name
{
ShortName = Configuration.ClientClassName,
FullName = $"API Plugin {Configuration.ClientClassName} for {OAIDocument.Info?.Title.CleanupXMLString() ?? "OpenApi Document"}"
},
Description = new Description
{
ShortName = !string.IsNullOrEmpty(OAIDocument.Info?.Description.CleanupXMLString()) ? $"API Plugin for {OAIDocument.Info?.Description.CleanupXMLString()}." : OAIDocument.Info?.Title.CleanupXMLString() ?? "OpenApi Document",
FullName = !string.IsNullOrEmpty(OAIDocument.Info?.Description.CleanupXMLString()) ? $"API Plugin for {OAIDocument.Info?.Description.CleanupXMLString()}." : OAIDocument.Info?.Title.CleanupXMLString() ?? "OpenApi Document"
},
Icons = new Icons(),
AccentColor = "#FFFFFF"
};

if (File.Exists(manifestFullPath)) // No need for default, try to update the model from the file
{
#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task
await using var fileStream = File.OpenRead(manifestFullPath);
#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task
var manifestModelFromFile = await JsonSerializer.DeserializeAsync(fileStream, AppManifestModelGenerationContext.AppManifestModel, cancellationToken).ConfigureAwait(false);
if (manifestModelFromFile != null)
manifestModel = manifestModelFromFile;
}

manifestModel.CopilotExtensions ??= new CopilotExtensions();// ensure its not null.

if (manifestModel.CopilotExtensions.Plugins.FirstOrDefault(pluginItem => Configuration.ClientClassName.Equals(pluginItem.Id, StringComparison.OrdinalIgnoreCase)) is { } plugin)
{
plugin.File = pluginFileName; // id is already consitent so make sure the file name is ok
}
else
{
// Add a new plugin entry
manifestModel.CopilotExtensions.Plugins.Add(new Plugin
{
File = pluginFileName,
Id = Configuration.ClientClassName
});
}

return manifestModel;
}

internal static readonly AppManifestModelGenerationContext AppManifestModelGenerationContext = new(new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
});

private OpenApiDocument GetDocumentWithTrimmedComponentsAndResponses(OpenApiDocument doc)
{
Expand Down Expand Up @@ -179,7 +259,7 @@
Schema = "https://aka.ms/json-schemas/copilot-extensions/v2.1/plugin.schema.json",
SchemaVersion = "v2.1",
NameForHuman = OAIDocument.Info?.Title.CleanupXMLString(),
// TODO name for model ???

Check warning on line 262 in src/Kiota.Builder/Plugins/PluginsGenerationService.cs

View workflow job for this annotation

GitHub Actions / Build

Complete the task associated to this 'TODO' comment. (https://rules.sonarsource.com/csharp/RSPEC-1135)

Check warning on line 262 in src/Kiota.Builder/Plugins/PluginsGenerationService.cs

View workflow job for this annotation

GitHub Actions / Build

Complete the task associated to this 'TODO' comment. (https://rules.sonarsource.com/csharp/RSPEC-1135)
DescriptionForHuman = descriptionForHuman,
DescriptionForModel = manifestInfo.DescriptionForModel ?? descriptionForHuman,
ContactEmail = manifestInfo.ContactEmail,
Expand Down
Loading
Loading