chore: import upstream snapshot with attribution
This commit is contained in:
+73
@@ -0,0 +1,73 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
using Grpc.Net.Client;
|
||||
using Microsoft.SemanticKernel;
|
||||
using ProcessWithCloudEvents.Grpc.DocumentationGenerator;
|
||||
using ProcessWithCloudEvents.Processes;
|
||||
using ProcessWithCloudEvents.Processes.Models;
|
||||
|
||||
namespace ProcessWithCloudEvents.Grpc.Clients;
|
||||
|
||||
/// <summary>
|
||||
/// Client that implements the <see cref="IExternalKernelProcessMessageChannel"/> interface used internally by the SK process
|
||||
/// to emit events to external systems.<br/>
|
||||
/// This implementation is an example of a gRPC client that emits events to a gRPC server
|
||||
/// </summary>
|
||||
public class DocumentGenerationGrpcClient : IExternalKernelProcessMessageChannel
|
||||
{
|
||||
private GrpcChannel? _grpcChannel;
|
||||
private GrpcDocumentationGeneration.GrpcDocumentationGenerationClient? _grpcClient;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask Initialize()
|
||||
{
|
||||
this._grpcChannel = GrpcChannel.ForAddress("http://localhost:58641");
|
||||
this._grpcClient = new GrpcDocumentationGeneration.GrpcDocumentationGenerationClient(this._grpcChannel);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask Uninitialize()
|
||||
{
|
||||
if (this._grpcChannel != null)
|
||||
{
|
||||
await this._grpcChannel.ShutdownAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task EmitExternalEventAsync(string externalTopicEvent, KernelProcessProxyMessage message)
|
||||
{
|
||||
if (this._grpcClient != null && message.EventData != null)
|
||||
{
|
||||
switch (externalTopicEvent)
|
||||
{
|
||||
case DocumentGenerationProcess.DocGenerationTopics.RequestUserReview:
|
||||
var requestDocument = message.EventData.ToObject() as DocumentInfo;
|
||||
if (requestDocument != null)
|
||||
{
|
||||
await this._grpcClient.RequestUserReviewDocumentationFromProcessAsync(new()
|
||||
{
|
||||
Title = requestDocument.Title,
|
||||
AssistantMessage = "Document ready for user revision. Approve or reject document",
|
||||
Content = requestDocument.Content,
|
||||
ProcessData = new() { ProcessId = message.ProcessId }
|
||||
});
|
||||
}
|
||||
return;
|
||||
|
||||
case DocumentGenerationProcess.DocGenerationTopics.PublishDocumentation:
|
||||
var publishedDocument = message.EventData.ToObject() as DocumentInfo;
|
||||
if (publishedDocument != null)
|
||||
{
|
||||
await this._grpcClient.PublishDocumentationAsync(new()
|
||||
{
|
||||
Title = publishedDocument.Title,
|
||||
AssistantMessage = "Published Document Ready",
|
||||
Content = publishedDocument.Content,
|
||||
ProcessData = new() { ProcessId = message.ProcessId }
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace ProcessWithCloudEvents.Grpc.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Class with extension methods for app configuration.
|
||||
/// </summary>
|
||||
public static class ConfigurationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns <typeparamref name="TOptions"/> if it's valid or throws <see cref="ValidationException"/>.
|
||||
/// </summary>
|
||||
public static TOptions GetValid<TOptions>(this IConfigurationRoot configurationRoot, string sectionName)
|
||||
{
|
||||
var options = configurationRoot.GetSection(sectionName).Get<TOptions>()!;
|
||||
|
||||
Validator.ValidateObject(options, new(options));
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace ProcessWithCloudEvents.Grpc.Options;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for OpenAI chat completion service.
|
||||
/// </summary>
|
||||
public class OpenAIOptions
|
||||
{
|
||||
public const string SectionName = "OpenAI";
|
||||
|
||||
[Required]
|
||||
public string ChatModelId { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public string ApiKey { get; set; } = string.Empty;
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<NoWarn>
|
||||
$(NoWarn);CA2007,CS1591,CA1861,VSTHRD111,SKEXP0001,SKEXP0010,SKEXP0040,SKEXP0050,SKEXP0060,SKEXP0080,SKEXP0110
|
||||
</NoWarn>
|
||||
<UserSecretsId>5ee045b0-aea3-4f08-8d31-32d1a6f8fed0</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Protobuf Include="Protos\documentationGenerator.proto" GrpcServices="Both" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Connectors\Connectors.OpenAI\Connectors.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Experimental\Process.Abstractions\Process.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Experimental\Process.Core\Process.Core.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Experimental\Process.Runtime.Dapr\Process.Runtime.Dapr.csproj" />
|
||||
<ProjectReference Include="..\ProcessWithCloudEvents.Processes\ProcessWithCloudEvents.Processes.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapr.Actors" />
|
||||
<PackageReference Include="Dapr.Actors.AspNetCore" />
|
||||
<PackageReference Include="Google.Protobuf" />
|
||||
<PackageReference Include="Grpc.AspNetCore" />
|
||||
<PackageReference Include="Grpc.AspNetCore.Server.Reflection" />
|
||||
<PackageReference Include="Grpc.AspNetCore.Web" />
|
||||
<PackageReference Include="Grpc.Net.Client" />
|
||||
<PackageReference Include="Grpc.Tools">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Media\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using ProcessWithCloudEvents.Grpc.Clients;
|
||||
using ProcessWithCloudEvents.Grpc.Extensions;
|
||||
using ProcessWithCloudEvents.Grpc.Options;
|
||||
using ProcessWithCloudEvents.Grpc.Services;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddUserSecrets<Program>()
|
||||
.AddEnvironmentVariables()
|
||||
.Build();
|
||||
|
||||
// Configure logging
|
||||
builder.Services.AddLogging((logging) =>
|
||||
{
|
||||
logging.AddConsole();
|
||||
logging.AddDebug();
|
||||
});
|
||||
|
||||
var openAIOptions = config.GetValid<OpenAIOptions>(OpenAIOptions.SectionName);
|
||||
|
||||
// Configure the Kernel with DI. This is required for dependency injection to work with processes.
|
||||
builder.Services.AddKernel();
|
||||
builder.Services.AddOpenAIChatCompletion(openAIOptions.ChatModelId, openAIOptions.ApiKey);
|
||||
|
||||
builder.Services.AddSingleton<DocumentGenerationService>();
|
||||
// Injecting SK Process custom grpc client IExternalKernelProcessMessageChannel implementation
|
||||
builder.Services.AddSingleton<IExternalKernelProcessMessageChannel, DocumentGenerationGrpcClient>();
|
||||
|
||||
// Configure Dapr
|
||||
builder.Services.AddActors(static options =>
|
||||
{
|
||||
// Register the actors required to run Processes
|
||||
options.AddProcessActors();
|
||||
});
|
||||
|
||||
// Enabling CORS for grpc-web when using webApp as client, remove if not needed
|
||||
builder.Services.AddCors(o => o.AddPolicy("AllowAll", builder =>
|
||||
{
|
||||
builder.AllowAnyOrigin()
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
}));
|
||||
|
||||
// Add grpc related services.
|
||||
builder.Services.AddGrpc();
|
||||
builder.Services.AddGrpcReflection();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.UseCors();
|
||||
|
||||
// Grpc services mapping
|
||||
// Enabling grpc-web, remove if not needed
|
||||
app.UseGrpcWeb();
|
||||
// Enabling CORS for grpc-web, remove if not needed
|
||||
app.MapGrpcReflectionService().RequireCors("AllowAll");
|
||||
app.MapGrpcService<DocumentGenerationService>().EnableGrpcWeb().RequireCors("AllowAll");
|
||||
|
||||
// Dapr actors related mapping
|
||||
app.MapActorsHandlers();
|
||||
app.Run();
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option csharp_namespace = "ProcessWithCloudEvents.Grpc.DocumentationGenerator";
|
||||
|
||||
service GrpcDocumentationGeneration {
|
||||
rpc UserRequestFeatureDocumentation (FeatureDocumentationRequest) returns (ProcessData);
|
||||
rpc RequestUserReviewDocumentationFromProcess (DocumentationContentRequest) returns (Empty);
|
||||
rpc RequestUserReviewDocumentation (ProcessData) returns (stream DocumentationContentRequest);
|
||||
rpc UserReviewedDocumentation (DocumentationApprovalRequest) returns (Empty);
|
||||
rpc PublishDocumentation (DocumentationContentRequest) returns (Empty);
|
||||
rpc ReceivePublishedDocumentation (ProcessData) returns (stream DocumentationContentRequest);
|
||||
}
|
||||
|
||||
message FeatureDocumentationRequest {
|
||||
string title = 1;
|
||||
string userDescription = 2;
|
||||
string content = 3;
|
||||
string processId = 10;
|
||||
}
|
||||
|
||||
message DocumentationContentRequest {
|
||||
string title = 1;
|
||||
string content = 2;
|
||||
string assistantMessage = 3;
|
||||
ProcessData processData = 10;
|
||||
}
|
||||
|
||||
message DocumentationApprovalRequest {
|
||||
bool documentationApproved = 1;
|
||||
string reason = 2;
|
||||
ProcessData processData = 10;
|
||||
}
|
||||
|
||||
message ProcessData {
|
||||
string processId = 1;
|
||||
}
|
||||
|
||||
message Empty {}
|
||||
@@ -0,0 +1,131 @@
|
||||
# Process With Cloud Events - using gRPC
|
||||
|
||||
For using gRPC, this demo follows the guidelines suggested for any [gRPC ASP.NET Core App](https://learn.microsoft.com/en-us/aspnet/core/grpc/test-tools?view=aspnetcore-9.0).
|
||||
|
||||
Which for this demo means:
|
||||
|
||||
- Making use of `builder.Services.AddGrpcReflection()` and `app.MapGrpcReflectionService()`
|
||||
- Making use of [`gRPCui`](https://github.com/fullstorydev/grpcui) for testing
|
||||
|
||||
## Explanation
|
||||
|
||||
This demo showcases how SK Process Framework could interact with a gRPC Server and clients.
|
||||
|
||||
The main difference of this demo is the custom implementation of the gRPC Server and client used internally by the SK Process in the SK Proxy Step.
|
||||
|
||||
Main gRPC components:
|
||||
|
||||
- `documentationGenerator.proto`: `<root>\dotnet\samples\Demos\ProcessWithCloudEvents\ProcessWithCloudEvents.Grpc\Protos\documentationGenerator.proto`
|
||||
- gRPC Server: `<root>\dotnet\samples\Demos\ProcessWithCloudEvents\ProcessWithCloudEvents.Grpc\Services\DocumentGenerationService.cs`
|
||||
- gRPC Client/IExternalKernelProcessMessageChannel implementation: `<root>\dotnet\samples\Demos\ProcessWithCloudEvents\ProcessWithCloudEvents.Grpc\Clients\DocumentGenerationGrpcClient.cs`
|
||||
|
||||
### SK Process and gRPC Events
|
||||
|
||||
``` mermaid
|
||||
|
||||
sequenceDiagram
|
||||
participant grpcClient as gRPC Client
|
||||
box Server
|
||||
participant grpcServer as gRPC Server
|
||||
participant SKP as SK Process
|
||||
end
|
||||
|
||||
grpcClient->>grpcServer: UserRequestFeatureDocumentation <br/>gRPC
|
||||
grpcServer->>SKP: StartDocumentGeneration <br/>SK event
|
||||
SKP->>grpcServer: RequestUserReview (SK Topic)/<br/>RequestUserReviewDocumentationFromProcess (gRPC)
|
||||
grpcServer->>grpcClient: RequestUserReviewDocumentation <br/>gRPC
|
||||
grpcClient->>grpcServer: UserReviewedDocumentation <br/>gRPC
|
||||
grpcServer->>SKP: UserApprovedDocument/UserRejectedDocument <br/>SK event
|
||||
SKP->>grpcServer: PublishDocumentation (SK Topic)/<br/>PublishDocumentation (gRPC)
|
||||
grpcServer->>grpcClient: ReceivePublishedDocumentation <br/>gRPC
|
||||
```
|
||||
1. When the `UserRequestFeatureDocumentation` gRPC request is received from the gRPC client, the server initiates an SK Process and emits the `StartDocumentGeneration` SK event.
|
||||
2. The `RequestUserReview` topic is emitted when the `DocumentationApproved` event is triggered during the `ProofReadDocumentationStep`. This event invokes the `RequestUserReviewDocumentationFromProcess` gRPC method to communicate with the server.
|
||||
3. The `RequestUserReviewDocumentationFromProcess` method updates the shared stream, which is used to communicate with the subscribers of `RequestUserReviewDocumentation`. The gRPC client then receives the document for review and approval.
|
||||
4. The gRPC client can approve or reject the document using the `UserReviewedDocumentation` method to communicate with the server. The server then sends the `UserApprovedDocument` or `UserRejectedDocument` SK event to the SK Process.
|
||||
5. The SK Process resumes, and the `PublishDocumentationStep` now has all the necessary parameters to execute. Upon execution, the `PublishDocumentation` topic is triggered, invoking the `PublishDocumentation` method on the gRPC server.
|
||||
6. The PublishDocumentation method updates the shared stream used by `ReceivePublishedDocumentation`, ensuring that all subscribers receive the update of the latest published document
|
||||
## Demo
|
||||
### Requirements
|
||||
|
||||
- Have Dapr setup ready
|
||||
- Build and Run the app
|
||||
- Interact with the server by:
|
||||
- Install and run `gRPCui` listening to the address `localhost:58641`:
|
||||
```
|
||||
./grpcui.exe -plaintext localhost:58641
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
- Use the `ProcessWithCloudEvents.Client` App and use it to interact with the server. This app uses gRPC Web, which interacts with the server through `localhost:58640`.
|
||||
|
||||
### Usage without UI
|
||||
|
||||
For interacting with the gRPC server, the
|
||||
|
||||
1. Build and run the app
|
||||
2. Open 2 windows of `gRPCui` with the following methods:
|
||||
- Window 1:
|
||||
- Method name: `UserRequestFeatureDocumentation` and `UserReviewedDocumentation`
|
||||
- Window 2:
|
||||
- Method name: `RequestUserReviewDocumentation`
|
||||
- Window 3:
|
||||
- Method name: `ReceivePublishedDocumentation`
|
||||
|
||||
3. Select a process id to be used with all methods. Example: processId = "100"
|
||||
4. Execute different methods in the following order:
|
||||
1. `RequestUserReviewDocumentation` with Request Data:
|
||||
```json
|
||||
{
|
||||
"processId": "100"
|
||||
}
|
||||
```
|
||||
This will subscribe to any request for review done for the specific process id and a response will be received when the process emits a notification.
|
||||
Set timeout to 30 seconds.
|
||||
|
||||
2. `UserRequestFeatureDocumentation` with Request Data:
|
||||
```json
|
||||
{
|
||||
"title": "some product title",
|
||||
"userDescription": "some user description",
|
||||
"content": "some product content",
|
||||
"processId": "100",
|
||||
}
|
||||
```
|
||||
This request will kickstart the creation of a new process with the specific processId passing an initial event to the SK process.
|
||||
|
||||
5. Once the `RequestUserReviewDocumentation` is received, execute the following methods:
|
||||
1. `ReceivePublishedDocumentation` with Request Data:
|
||||
```json
|
||||
{
|
||||
"processId": "100"
|
||||
}
|
||||
```
|
||||
This will subscribe to any request for review done for the specific process id and a response will be received when the process emits a notification.
|
||||
Set timeout to 30 seconds.
|
||||
|
||||
2. `UserReviewedDocumentation` with Request Data:
|
||||
```json
|
||||
{
|
||||
"documentationApproved": true,
|
||||
"reason": "",
|
||||
"processData":
|
||||
{
|
||||
"processId": "100"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Debugging
|
||||
|
||||
For debugging and be able to set breakpoints in different stages of the app, you can:
|
||||
|
||||
- Install the [Visual Studio Dapr Extension](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vs-dapr) and make use of it by making use of the `<root>\dotnet\dapr.yaml` file already in the repository.
|
||||
|
||||
or
|
||||
|
||||
- Set the `ProcessWithCloudEvents.Grpc` as startup app, run and attach the Visual Studio debugger:
|
||||
```
|
||||
dapr run --app-id processwithcloudevents-grpc --app-port 58640 --app-protocol http -- dotnet run --no-build
|
||||
```
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using Dapr.Actors.Client;
|
||||
using Grpc.Core;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
using ProcessWithCloudEvents.Grpc.Clients;
|
||||
using ProcessWithCloudEvents.Grpc.DocumentationGenerator;
|
||||
using ProcessWithCloudEvents.Processes;
|
||||
using ProcessWithCloudEvents.Processes.Models;
|
||||
|
||||
namespace ProcessWithCloudEvents.Grpc.Services;
|
||||
|
||||
/// <summary>
|
||||
/// This gRPC service handles the generation of documents using/invoking a SK Process
|
||||
/// </summary>
|
||||
public class DocumentGenerationService : GrpcDocumentationGeneration.GrpcDocumentationGenerationBase
|
||||
{
|
||||
private readonly ILogger<DocumentGenerationService> _logger;
|
||||
private readonly Kernel _kernel;
|
||||
private readonly IActorProxyFactory _actorProxyFactory;
|
||||
private readonly ConcurrentDictionary<string, ConcurrentBag<IServerStreamWriter<DocumentationContentRequest>>> _docReviewSubscribers;
|
||||
private readonly ConcurrentDictionary<string, ConcurrentBag<IServerStreamWriter<DocumentationContentRequest>>> _publishDocumentSubscribers;
|
||||
/// <summary>
|
||||
/// Constructor for the <see cref="DocumentGenerationService"/>
|
||||
/// </summary>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="kernel"></param>
|
||||
/// <param name="actorProxy"></param>
|
||||
public DocumentGenerationService(ILogger<DocumentGenerationService> logger, Kernel kernel, IActorProxyFactory actorProxy)
|
||||
{
|
||||
this._logger = logger;
|
||||
this._kernel = kernel;
|
||||
this._actorProxyFactory = actorProxy;
|
||||
this._docReviewSubscribers = new();
|
||||
this._publishDocumentSubscribers = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method that receives a request to generate documentation, this will start the SK process
|
||||
/// defined in <see cref="DocumentGenerationProcess.CreateProcessBuilder"/> <br/>
|
||||
/// It will use the processId passed in the request or generate a new one if not provided
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public override async Task<ProcessData> UserRequestFeatureDocumentation(FeatureDocumentationRequest request, ServerCallContext context)
|
||||
{
|
||||
var processId = string.IsNullOrEmpty(request.ProcessId) ? Guid.NewGuid().ToString() : request.ProcessId;
|
||||
var process = DocumentGenerationProcess.CreateProcessBuilder().Build();
|
||||
|
||||
var processContext = await process.StartAsync(new KernelProcessEvent()
|
||||
{
|
||||
Id = DocumentGenerationProcess.DocGenerationEvents.StartDocumentGeneration,
|
||||
// The object ProductInfo is sent because this is the type the GatherProductInfoStep is expecting
|
||||
Data = new ProductInfo() { Title = request.Title, Content = request.Content, UserInput = request.UserDescription },
|
||||
},
|
||||
processId,
|
||||
this._actorProxyFactory);
|
||||
|
||||
return new ProcessData { ProcessId = processId };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method that receives a request to request user review of documentation, this will send a request to the client
|
||||
/// if subscribed to the <see cref="RequestUserReviewDocumentation"/> method previously with the same process id.<br/>
|
||||
/// This method is meant to be used within the SK process from the <see cref="DocumentGenerationGrpcClient"/> implementation.
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public override async Task<Empty> RequestUserReviewDocumentationFromProcess(DocumentationContentRequest request, ServerCallContext context)
|
||||
{
|
||||
if (this._docReviewSubscribers.TryGetValue(request.ProcessData.ProcessId, out var subscribers))
|
||||
{
|
||||
foreach (var subscriber in subscribers)
|
||||
{
|
||||
await subscriber.WriteAsync(request).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
return new Empty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method that receives request to receive user review of documentation. <br/>
|
||||
/// This is meant to be used by the external client
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="responseStream"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public override async Task RequestUserReviewDocumentation(ProcessData request, IServerStreamWriter<DocumentationContentRequest> responseStream, ServerCallContext context)
|
||||
{
|
||||
var subscribers = this._docReviewSubscribers.GetOrAdd(request.ProcessId, []);
|
||||
subscribers.Add(responseStream);
|
||||
|
||||
try
|
||||
{
|
||||
// Wait until the client disconnects
|
||||
await context.CancellationToken.WaitHandle.ToTask();
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Remove the subscriber when client disconnects
|
||||
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
|
||||
subscribers.TryTake(out responseStream);
|
||||
#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type.
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method that receives a request to approve or reject documentation, this will send the response to the SK process.
|
||||
/// This is meant to be used by the external client.
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public override async Task<Empty> UserReviewedDocumentation(DocumentationApprovalRequest request, ServerCallContext context)
|
||||
{
|
||||
var process = DocumentGenerationProcess.CreateProcessBuilder().Build();
|
||||
|
||||
KernelProcessEvent processEvent;
|
||||
if (request.DocumentationApproved)
|
||||
{
|
||||
processEvent = new()
|
||||
{
|
||||
Id = DocumentGenerationProcess.DocGenerationEvents.UserApprovedDocument,
|
||||
Data = true,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
processEvent = new()
|
||||
{
|
||||
Id = DocumentGenerationProcess.DocGenerationEvents.UserRejectedDocument,
|
||||
Data = request.Reason,
|
||||
};
|
||||
}
|
||||
|
||||
var processContext = await process.StartAsync(processEvent, request.ProcessData.ProcessId);
|
||||
|
||||
return new Empty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method used to publish the generated documentation, this will send the documentation to the client
|
||||
/// if subscribed to the <see cref="ReceivePublishedDocumentation"/> method with the same process id.<br/>
|
||||
/// This method is meant to be used within the SK process from the <see cref="DocumentGenerationGrpcClient"/> implementation.
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public override async Task<Empty> PublishDocumentation(DocumentationContentRequest request, ServerCallContext context)
|
||||
{
|
||||
if (this._publishDocumentSubscribers.TryGetValue(request.ProcessData.ProcessId, out var subscribers))
|
||||
{
|
||||
foreach (var subscriber in subscribers)
|
||||
{
|
||||
await subscriber.WriteAsync(request).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
return new Empty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method that receives request to receive published documentation from a specific process id.
|
||||
/// This is meant to be used by the external client.
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="responseStream"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public override async Task ReceivePublishedDocumentation(ProcessData request, IServerStreamWriter<DocumentationContentRequest> responseStream, ServerCallContext context)
|
||||
{
|
||||
var subscribers = this._publishDocumentSubscribers.GetOrAdd(request.ProcessId, []);
|
||||
subscribers.Add(responseStream);
|
||||
|
||||
try
|
||||
{
|
||||
// Wait until the client disconnects
|
||||
await context.CancellationToken.WaitHandle.ToTask();
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Remove the subscriber when client disconnects
|
||||
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
|
||||
subscribers.TryTake(out responseStream);
|
||||
#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type.
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"Kestrel": {
|
||||
"Endpoints": {
|
||||
"Http1": {
|
||||
"Url": "http://*:58640",
|
||||
"Protocols": "Http1"
|
||||
},
|
||||
"Http2": {
|
||||
"Url": "http://*:58641",
|
||||
"Protocols": "Http2"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user