-
Notifications
You must be signed in to change notification settings - Fork 183
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add CRUD API for Copilot comments (#6754)
* Create CRUD API for copilot comments feature * Delete unused code * Remove comments * Update status codes * Remove unused fields * Apply review changes
- Loading branch information
Showing
8 changed files
with
392 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
91 changes: 91 additions & 0 deletions
91
src/dotnet/APIView/APIViewWeb/Controllers/CopilotCommentsController.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
using System.Text.Json; | ||
using System.Threading.Tasks; | ||
using APIViewWeb.Managers; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace APIViewWeb.Controllers | ||
{ | ||
public class CopilotCommentsController : Controller | ||
{ | ||
private readonly ICopilotCommentsManager _copilotManager; | ||
private readonly ILogger _logger; | ||
|
||
public CopilotCommentsController(ICopilotCommentsManager copilotManager, ILogger<CopilotCommentsController> logger) | ||
{ | ||
_copilotManager = copilotManager; | ||
_logger = logger; | ||
} | ||
|
||
[HttpPost] | ||
public async Task<ActionResult> CreateDocument(string badCode, string language, string goodCode = null, string comment = null, string[] guidelineIds = null) | ||
{ | ||
if (badCode == null) | ||
{ | ||
_logger.LogInformation("Request does not have the required badCode field for CREATE."); | ||
return StatusCode(statusCode: StatusCodes.Status500InternalServerError); | ||
} | ||
|
||
var id = await _copilotManager.CreateDocumentAsync(User.GetGitHubLogin(), badCode, goodCode, language, comment, guidelineIds); | ||
_logger.LogInformation("Added a new document to database."); | ||
return StatusCode(statusCode: StatusCodes.Status201Created, id); | ||
|
||
} | ||
|
||
[HttpPut] | ||
public async Task<ActionResult> UpdateDocument(string id, string badCode = null, string language = null, string goodCode = null, string comment = null, string[] guidelineIds = null) | ||
{ | ||
if (id == null) | ||
{ | ||
_logger.LogInformation("Request does not have the required ID field for UPDATE."); | ||
return StatusCode(statusCode: StatusCodes.Status500InternalServerError); | ||
} | ||
|
||
var result = await _copilotManager.UpdateDocumentAsync(User.GetGitHubLogin(), id, badCode, goodCode, language, comment, guidelineIds); | ||
if (result.ModifiedCount > 0) | ||
{ | ||
_logger.LogInformation("Found existing document with ID. Updating document."); | ||
var document = await _copilotManager.GetDocumentAsync(id); | ||
return Ok(document); | ||
} else | ||
{ | ||
_logger.LogInformation("Could not find a match for the given ID."); | ||
return StatusCode(statusCode: StatusCodes.Status404NotFound); | ||
} | ||
} | ||
|
||
[HttpGet] | ||
public async Task<ActionResult> GetDocument(string id) | ||
{ | ||
if (id == null) | ||
{ | ||
_logger.LogInformation("Request does not have the required ID field for GET."); | ||
return StatusCode(statusCode: StatusCodes.Status400BadRequest); | ||
} | ||
|
||
var document = await _copilotManager.GetDocumentAsync(id); | ||
if (document != null) | ||
{ | ||
return Ok(document); | ||
} else | ||
{ | ||
_logger.LogInformation("No document with this id exists in the database."); | ||
return StatusCode(statusCode: StatusCodes.Status404NotFound); | ||
} | ||
} | ||
|
||
[HttpDelete] | ||
public async Task<ActionResult> DeleteDocument(string id) | ||
{ | ||
if (id == null) | ||
{ | ||
_logger.LogInformation("Request does not have the required ID field for DELETE."); | ||
return StatusCode(statusCode: StatusCodes.Status400BadRequest); | ||
} | ||
|
||
await _copilotManager.DeleteDocumentAsync(User.GetGitHubLogin(), id); | ||
return StatusCode(statusCode: StatusCodes.Status204NoContent); | ||
} | ||
} | ||
} |
193 changes: 193 additions & 0 deletions
193
src/dotnet/APIView/APIViewWeb/Managers/CopilotCommentsManager.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
using System; | ||
using System.Threading.Tasks; | ||
using APIViewWeb.Models; | ||
using APIViewWeb.Repositories; | ||
using Microsoft.Extensions.Configuration; | ||
using Azure; | ||
using Azure.AI.OpenAI; | ||
using MongoDB.Driver; | ||
using MongoDB.Bson; | ||
using System.Linq; | ||
using Newtonsoft.Json; | ||
|
||
namespace APIViewWeb.Managers | ||
{ | ||
public class CopilotCommentsManager : ICopilotCommentsManager | ||
{ | ||
private readonly ICopilotCommentsRepository _copilotCommentsRepository; | ||
private readonly IConfiguration _configuration; | ||
private readonly OpenAIClient _openAIClient; | ||
|
||
public CopilotCommentsManager( | ||
ICopilotCommentsRepository copilotCommentsRepository, | ||
IConfiguration configuration | ||
) | ||
{ | ||
_copilotCommentsRepository = copilotCommentsRepository; | ||
_configuration = configuration; | ||
|
||
_openAIClient = new OpenAIClient( | ||
new Uri(_configuration["OpenAI:Endpoint"]), | ||
new AzureKeyCredential(_configuration["OpenAI:Key"])); | ||
} | ||
|
||
public async Task<string> CreateDocumentAsync(string user, string badCode, string goodCode, string language, string comment, string[] guidelineIds) | ||
{ | ||
var embedding = await GetEmbeddingsAsync(badCode); | ||
|
||
var document = new CopilotCommentModel() | ||
{ | ||
BadCode = badCode, | ||
GoodCode = goodCode, | ||
Embedding = embedding, | ||
Language = language, | ||
Comment = comment, | ||
GuidelineIds = guidelineIds, | ||
ModifiedOn = DateTime.UtcNow, | ||
ModifiedBy = user | ||
}; | ||
|
||
return await _copilotCommentsRepository.InsertDocumentAsync(document); | ||
} | ||
|
||
public async Task<UpdateResult> UpdateDocumentAsync(string user, string id, string badCode, string goodCode, string language, string comment, string[] guidelineIds) | ||
{ | ||
var filter = GetIdFilter(id); | ||
|
||
var updateBuilder = Builders<CopilotCommentModel>.Update; | ||
var update = updateBuilder | ||
.Set("ModifiedOn", DateTime.UtcNow) | ||
.Set("ModifiedBy", user); | ||
|
||
if (goodCode != null) | ||
{ | ||
update = update.Set("GoodCode", goodCode); | ||
} | ||
|
||
if (language != null) | ||
{ | ||
update = update.Set("Language", language); | ||
} | ||
|
||
if (comment != null) | ||
{ | ||
update = update.Set("Comment", comment); | ||
} | ||
|
||
if (guidelineIds != null) | ||
{ | ||
update = update.Set("GuidelineIds", guidelineIds); | ||
} | ||
|
||
if (badCode != null) | ||
{ | ||
var embedding = await GetEmbeddingsAsync(badCode); | ||
update = update | ||
.Set("BadCode", badCode) | ||
.Set("Embedding", embedding); | ||
} | ||
|
||
return await _copilotCommentsRepository.UpdateDocumentAsync(filter, update); | ||
} | ||
|
||
public Task DeleteDocumentAsync(string user, string id) | ||
{ | ||
var filter = GetIdFilter(id); | ||
|
||
var updateBuilder = Builders<CopilotCommentModel>.Update; | ||
var update = updateBuilder | ||
.Set("IsDeleted", true) | ||
.Set("ModifiedOn", DateTime.UtcNow) | ||
.Set("ModifiedBy", user); | ||
|
||
return _copilotCommentsRepository.DeleteDocumentAsync(filter, update); | ||
} | ||
|
||
public async Task<string> GetDocumentAsync(string id) | ||
{ | ||
var filter = GetIdFilter(id); | ||
var document = await _copilotCommentsRepository.GetDocumentAsync(filter); | ||
|
||
var documentJson = JsonConvert.SerializeObject(document); | ||
|
||
return documentJson; | ||
} | ||
|
||
private FilterDefinition<CopilotCommentModel> GetIdFilter(string id) | ||
{ | ||
return Builders<CopilotCommentModel>.Filter | ||
.Where(f => f.Id.ToString() == id && f.IsDeleted == false); | ||
} | ||
|
||
private async Task<float[]> GetEmbeddingsAsync(string badCode) | ||
{ | ||
/* | ||
* Structure of Embeddings object | ||
* { | ||
* { | ||
* "Data", | ||
* [ | ||
* { | ||
* "EmbeddingsItem", | ||
* { | ||
* { "Embedding", [ "float1", "float2" ]}, | ||
* { "Index", "1"} | ||
* } | ||
* } | ||
* ] | ||
* }, | ||
* { | ||
* "Usage", | ||
* { | ||
* { "PromptTokens", "1"}, | ||
* { "TotalTokens", "2"} | ||
* } | ||
* } | ||
* } | ||
*/ | ||
|
||
var options = new EmbeddingsOptions(badCode); | ||
var response = await _openAIClient.GetEmbeddingsAsync(_configuration["OpenAI:Model"], options); | ||
var embeddings = response.Value; | ||
|
||
var embedding = embeddings.Data[0].Embedding.ToArray(); | ||
|
||
return embedding; | ||
} | ||
|
||
public BsonDocument EmbeddingToBson(Embeddings embedding) | ||
{ | ||
var data = embedding.Data; | ||
|
||
var embeddingUsageBson = new BsonDocument | ||
{ | ||
{ "promptTokens", embedding.Usage.PromptTokens }, | ||
{ "TotalTokens", embedding.Usage.TotalTokens } | ||
}; | ||
|
||
var embeddingData = new BsonArray(); | ||
|
||
var embeddingBson = new BsonDocument | ||
{ | ||
{ "Data", embeddingData }, | ||
{ "Usage", embeddingUsageBson } | ||
}; | ||
|
||
foreach (var embeddingItem in data) | ||
{ | ||
var vectors = embeddingItem.Embedding; | ||
var vectorsBson = BsonArray.Create(vectors); | ||
|
||
var embeddingItemBson = new BsonDocument | ||
{ | ||
{ "Embedding", vectorsBson }, | ||
{ "Index", embeddingItem.Index } | ||
}; | ||
|
||
embeddingData.Add(embeddingItemBson); | ||
} | ||
|
||
return embeddingBson; | ||
} | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
src/dotnet/APIView/APIViewWeb/Managers/ICopilotCommentsManager.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
using System.Threading.Tasks; | ||
using MongoDB.Driver; | ||
|
||
namespace APIViewWeb.Managers | ||
{ | ||
public interface ICopilotCommentsManager | ||
{ | ||
public Task<string> CreateDocumentAsync(string user, string badCode, string goodCode, string language, string comment, string[] guidelineIds); | ||
public Task<UpdateResult> UpdateDocumentAsync(string user, string id, string badCode, string goodCode, string language, string comment, string[] guidelineIds); | ||
public Task<string> GetDocumentAsync(string id); | ||
public Task DeleteDocumentAsync(string user, string id); | ||
} | ||
} |
21 changes: 21 additions & 0 deletions
21
src/dotnet/APIView/APIViewWeb/Models/CopilotCommentModel.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
using System; | ||
using MongoDB.Bson; | ||
using MongoDB.Bson.Serialization.Attributes; | ||
|
||
namespace APIViewWeb.Models | ||
{ | ||
public class CopilotCommentModel | ||
{ | ||
[BsonId] | ||
public ObjectId Id { get; set; } | ||
public string BadCode { get; set; } | ||
public string GoodCode { get; set; } = null; | ||
public float[] Embedding { get; set; } | ||
public string Language { get; set; } | ||
public string Comment { get; set; } = null; | ||
public string[] GuidelineIds { get; set; } = null; | ||
public DateTime ModifiedOn { get; set; } | ||
public string ModifiedBy { get; set; } | ||
public bool IsDeleted { get; set; } = false; | ||
} | ||
} |
48 changes: 48 additions & 0 deletions
48
src/dotnet/APIView/APIViewWeb/Repositories/CopilotCommentsRepository.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
using System.Threading.Tasks; | ||
using APIViewWeb.Models; | ||
using Microsoft.Azure.Cosmos.Serialization.HybridRow; | ||
using Microsoft.Extensions.Configuration; | ||
using MongoDB.Driver; | ||
using MongoDB.Driver.Linq; | ||
|
||
namespace APIViewWeb.Repositories | ||
{ | ||
public class CopilotCommentsRepository : ICopilotCommentsRepository | ||
{ | ||
private readonly MongoClient _mongoClient; | ||
private readonly IMongoDatabase _database; | ||
private readonly IMongoCollection<CopilotCommentModel> _collection; | ||
|
||
public CopilotCommentsRepository(IConfiguration configuration) | ||
{ | ||
_mongoClient = new MongoClient(configuration["CosmosMongoDB:ConnectionString"]); | ||
_database = _mongoClient.GetDatabase(configuration["CosmosMongoDB:DatabaseName"]); | ||
_collection = _database.GetCollection<CopilotCommentModel>(configuration["CosmosMongoDB:CollectionName"]); | ||
} | ||
|
||
public async Task<string> InsertDocumentAsync(CopilotCommentModel document) | ||
{ | ||
await _collection.InsertOneAsync(document); | ||
return document.Id.ToString(); | ||
} | ||
|
||
public async Task<UpdateResult> UpdateDocumentAsync( | ||
FilterDefinition<CopilotCommentModel> filter, | ||
UpdateDefinition<CopilotCommentModel> update) | ||
{ | ||
return await _collection.UpdateOneAsync(filter, update); | ||
} | ||
|
||
public Task DeleteDocumentAsync( | ||
FilterDefinition<CopilotCommentModel> filter, | ||
UpdateDefinition<CopilotCommentModel> update) | ||
{ | ||
return _collection.UpdateOneAsync(filter, update); | ||
} | ||
|
||
public Task<CopilotCommentModel> GetDocumentAsync(FilterDefinition<CopilotCommentModel> filter) | ||
{ | ||
return _collection.Find(filter).FirstOrDefaultAsync(); | ||
} | ||
} | ||
} |
Oops, something went wrong.