From bec1df9243883627577c3649a78e012333743f8f Mon Sep 17 00:00:00 2001 From: Cheick Keita Date: Mon, 21 Aug 2023 16:12:05 -0700 Subject: [PATCH 1/4] storing the ado notification title and work item id return the list of job created in the job_summary --- src/ApiService/ApiService/Functions/Jobs.cs | 11 +++--- .../ApiService/OneFuzzTypes/Model.cs | 8 +++++ .../ApiService/OneFuzzTypes/Requests.cs | 4 ++- .../ApiService/OneFuzzTypes/Responses.cs | 10 ++++-- src/ApiService/ApiService/Program.cs | 5 +-- .../AdoNotificationEntryOperation.cs | 20 +++++++++++ .../ApiService/onefuzzlib/OnefuzzContext.cs | 3 ++ .../onefuzzlib/notifications/Ado.cs | 34 ++++++++++++++----- src/ApiService/FunctionalTests/1f-api/Jobs.cs | 1 + .../TestAdoNotificationEntryOperations.cs | 8 +++++ .../IntegrationTests/Fakes/TestContext.cs | 25 ++++++++++++++ src/ApiService/IntegrationTests/JobsTests.cs | 26 ++++++++++++++ src/cli/onefuzz/api.py | 8 +++-- src/pytypes/onefuzztypes/models.py | 1 + src/pytypes/onefuzztypes/requests.py | 1 + 15 files changed, 142 insertions(+), 23 deletions(-) create mode 100644 src/ApiService/ApiService/onefuzzlib/AdoNotificationEntryOperation.cs create mode 100644 src/ApiService/IntegrationTests/Fakes/TestAdoNotificationEntryOperations.cs diff --git a/src/ApiService/ApiService/Functions/Jobs.cs b/src/ApiService/ApiService/Functions/Jobs.cs index 3f8746df1f..a2badfe5b2 100644 --- a/src/ApiService/ApiService/Functions/Jobs.cs +++ b/src/ApiService/ApiService/Functions/Jobs.cs @@ -136,13 +136,10 @@ private async Task Get(HttpRequestData req) { static JobTaskInfo TaskToJobTaskInfo(Task t) => new(t.TaskId, t.Config.Task.Type, t.State); var tasks = _context.TaskOperations.SearchStates(jobId); - if (search.WithTasks ?? false) { - var ts = await tasks.ToListAsync(); - return await RequestHandling.Ok(req, JobResponse.ForJob(job, ts)); - } else { - var taskInfo = await tasks.Select(TaskToJobTaskInfo).ToListAsync(); - return await RequestHandling.Ok(req, JobResponse.ForJob(job, taskInfo)); - } + + IAsyncEnumerable taskInfo = search.WithTasks ?? false ? tasks : tasks.Select(TaskToJobTaskInfo); + var bugs = search.WithBugs ?? false ? _context.AdoNotificationEntryOperations.GetByJobId(jobId).Select(adoEntry => adoEntry.Id).ToEnumerable() : null; + return await RequestHandling.Ok(req, JobResponse.ForJob(job, taskInfo.ToEnumerable(), bugs)); } var jobs = await _context.JobOperations.SearchState(states: search.State ?? Enumerable.Empty()).ToListAsync(); diff --git a/src/ApiService/ApiService/OneFuzzTypes/Model.cs b/src/ApiService/ApiService/OneFuzzTypes/Model.cs index 23811e9fe0..7981eca974 100644 --- a/src/ApiService/ApiService/OneFuzzTypes/Model.cs +++ b/src/ApiService/ApiService/OneFuzzTypes/Model.cs @@ -1187,3 +1187,11 @@ string ReproCmd public interface ITruncatable { public T Truncate(int maxLength); } + +public record AdoNotificationEntry( + [PartitionKey] Guid JobId, + [RowKey] int Id, + string Title +) : EntityBase { + +} diff --git a/src/ApiService/ApiService/OneFuzzTypes/Requests.cs b/src/ApiService/ApiService/OneFuzzTypes/Requests.cs index f3cc407b15..caa24dc4bb 100644 --- a/src/ApiService/ApiService/OneFuzzTypes/Requests.cs +++ b/src/ApiService/ApiService/OneFuzzTypes/Requests.cs @@ -155,7 +155,9 @@ public record JobSearch( Guid? JobId = null, List? State = null, List? TaskState = null, - bool? WithTasks = null + bool? WithTasks = null, + bool? WithBugs = null + ) : BaseRequest; public record NodeAddSshKeyPost( diff --git a/src/ApiService/ApiService/OneFuzzTypes/Responses.cs b/src/ApiService/ApiService/OneFuzzTypes/Responses.cs index c1067305ad..9d5943d9e8 100644 --- a/src/ApiService/ApiService/OneFuzzTypes/Responses.cs +++ b/src/ApiService/ApiService/OneFuzzTypes/Responses.cs @@ -92,6 +92,7 @@ public record ContainerInfo( Uri SasUrl ) : BaseResponse(); + public record JobResponse( Guid JobId, JobState State, @@ -101,10 +102,12 @@ public record JobResponse( IEnumerable? TaskInfo, StoredUserInfo? UserInfo, [property: JsonPropertyName("Timestamp")] // must retain capital T for backcompat - DateTimeOffset? Timestamp + DateTimeOffset? Timestamp, + IEnumerable AdoBugIds + // not including UserInfo from Job model ) : BaseResponse() { - public static JobResponse ForJob(Job j, IEnumerable? taskInfo) + public static JobResponse ForJob(Job j, IEnumerable? taskInfo, IEnumerable? adoBugIds = null) => new( JobId: j.JobId, State: j.State, @@ -113,7 +116,8 @@ public static JobResponse ForJob(Job j, IEnumerable? taskInfo) EndTime: j.EndTime, TaskInfo: taskInfo, UserInfo: j.UserInfo, - Timestamp: j.Timestamp + Timestamp: j.Timestamp, + AdoBugIds: adoBugIds ?? new List() ); public DateTimeOffset? StartTime => EndTime is DateTimeOffset endTime ? endTime.Subtract(TimeSpan.FromHours(Config.Duration)) : null; } diff --git a/src/ApiService/ApiService/Program.cs b/src/ApiService/ApiService/Program.cs index d5ee30b45e..ca3e73457f 100644 --- a/src/ApiService/ApiService/Program.cs +++ b/src/ApiService/ApiService/Program.cs @@ -23,11 +23,11 @@ namespace Microsoft.OneFuzz.Service; public class Program { /// - /// + /// /// public class LoggingMiddleware : IFunctionsWorkerMiddleware { /// - /// + /// /// /// /// @@ -136,6 +136,7 @@ public static async Async.Task Main() { .AddScoped() .AddScoped() .AddScoped() + .AddScoped() .AddSingleton(new GraphServiceClient(new DefaultAzureCredential())) .AddSingleton() .AddSingleton() diff --git a/src/ApiService/ApiService/onefuzzlib/AdoNotificationEntryOperation.cs b/src/ApiService/ApiService/onefuzzlib/AdoNotificationEntryOperation.cs new file mode 100644 index 0000000000..8232ecef38 --- /dev/null +++ b/src/ApiService/ApiService/onefuzzlib/AdoNotificationEntryOperation.cs @@ -0,0 +1,20 @@ +using ApiService.OneFuzzLib.Orm; +using Microsoft.Extensions.Logging; +namespace Microsoft.OneFuzz.Service; + +public interface IAdoNotificationEntryOperations : IOrm { + + public IAsyncEnumerable GetByJobId(Guid jobId); + +} +public class AdoNotificationEntryOperations : Orm, IAdoNotificationEntryOperations { + + public AdoNotificationEntryOperations(ILogger log, IOnefuzzContext context) + : base(log, context) { + + } + + public IAsyncEnumerable GetByJobId(Guid jobId) { + return QueryAsync(filter: Query.PartitionKey(jobId.ToString())); + } +} diff --git a/src/ApiService/ApiService/onefuzzlib/OnefuzzContext.cs b/src/ApiService/ApiService/onefuzzlib/OnefuzzContext.cs index 03c6322663..0661f753ba 100644 --- a/src/ApiService/ApiService/onefuzzlib/OnefuzzContext.cs +++ b/src/ApiService/ApiService/onefuzzlib/OnefuzzContext.cs @@ -49,6 +49,7 @@ public interface IOnefuzzContext { ITeams Teams { get; } IGithubIssues GithubIssues { get; } IAdo Ado { get; } + IAdoNotificationEntryOperations AdoNotificationEntryOperations { get; } IFeatureManagerSnapshot FeatureManagerSnapshot { get; } IConfigurationRefresher ConfigurationRefresher { get; } @@ -105,4 +106,6 @@ public OnefuzzContext(IServiceProvider serviceProvider) { public IFeatureManagerSnapshot FeatureManagerSnapshot => _serviceProvider.GetRequiredService(); public IConfigurationRefresher ConfigurationRefresher => _serviceProvider.GetRequiredService().Refreshers.First(); + + public IAdoNotificationEntryOperations AdoNotificationEntryOperations => _serviceProvider.GetRequiredService(); } diff --git a/src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs b/src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs index 2b01afb37f..c9a0cc8185 100644 --- a/src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs +++ b/src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs @@ -21,6 +21,14 @@ public class Ado : NotificationsBase, IAdo { private const string TITLE_FIELD = "System.Title"; private static List DEFAULT_REGRESSION_IGNORE_STATES = new() { "New", "Commited", "Active" }; + public enum AdoNotificationPublishignState { + Added, + Updated, + Skipped, + } + + public record ProcessResult(AdoNotificationPublishignState State, WorkItem WorkItem); + public Ado(ILogger logTracer, IOnefuzzContext context) : base(logTracer, context) { } @@ -312,7 +320,14 @@ private static async Async.Task ProcessNotification(IOnefuzzContext context, Con var renderedConfig = RenderAdoTemplate(logTracer, renderer, config, instanceUrl); var ado = new AdoConnector(renderedConfig, project!, client, instanceUrl, logTracer, await GetValidFields(client, project)); - await ado.Process(notificationInfo, isRegression); + await foreach (var processState in ado.Process(notificationInfo, isRegression)) { + if (processState.State == AdoNotificationPublishignState.Added) { + if (processState.WorkItem.Id == null) { + continue; + } + _ = await context.AdoNotificationEntryOperations.Update(new AdoNotificationEntry(report.JobId, (int)processState.WorkItem.Id, (string)processState.WorkItem.Fields[TITLE_FIELD])); + } + } } public static RenderedAdoTemplate RenderAdoTemplate(ILogger logTracer, Renderer renderer, AdoTemplate original, Uri instanceUrl) { @@ -522,28 +537,25 @@ public async Async.Task UpdateExisting(WorkItem item, IList<(string, strin // the below was causing on_duplicate not to work // var systemState = JsonSerializer.Serialize(item.Fields["System.State"]); var systemState = (string)item.Fields["System.State"]; - var stateUpdated = false; if (_config.OnDuplicate.SetState.TryGetValue(systemState, out var v)) { document.Add(new JsonPatchOperation() { Operation = VisualStudio.Services.WebApi.Patch.Operation.Replace, Path = "/fields/System.State", Value = v }); - - stateUpdated = true; } if (document.Any()) { _ = await _client.UpdateWorkItemAsync(document, _project, (int)item.Id!); var adoEventType = "AdoUpdate"; _logTracer.LogEvent(adoEventType); + return true; } else { var adoEventType = "AdoNoUpdate"; _logTracer.LogEvent(adoEventType); + return false; } - - return stateUpdated; } private bool MatchesUnlessCase(WorkItem workItem) => @@ -600,7 +612,8 @@ private async Async.Task CreateNew() { return (taskType, document); } - public async Async.Task Process(IList<(string, string)> notificationInfo, bool isRegression) { + + public async IAsyncEnumerable Process(IList<(string, string)> notificationInfo, bool isRegression) { var updated = false; WorkItem? oldestWorkItem = null; await foreach (var workItem in ExistingWorkItems(notificationInfo)) { @@ -610,7 +623,9 @@ public async Async.Task Process(IList<(string, string)> notificationInfo, bool i _logTracer.AddTags(new List<(string, string)> { ("MatchingWorkItemIds", $"{workItem.Id}") }); _logTracer.LogInformation("Found matching work item"); } + if (IsADODuplicateWorkItem(workItem, _config.AdoDuplicateFields)) { + yield return new ProcessResult(AdoNotificationPublishignState.Skipped, workItem); continue; } @@ -627,11 +642,12 @@ public async Async.Task Process(IList<(string, string)> notificationInfo, bool i } _ = await UpdateExisting(workItem, notificationInfo); + yield return new ProcessResult(AdoNotificationPublishignState.Updated, workItem); updated = true; } if (updated || isRegression) { - return; + yield break; } if (oldestWorkItem != null) { @@ -649,6 +665,7 @@ public async Async.Task Process(IList<(string, string)> notificationInfo, bool i _project, (int)oldestWorkItem.Id!); } + yield return new ProcessResult(AdoNotificationPublishignState.Updated, oldestWorkItem); } else { // We never saw a work item like this before, it must be new var entry = await CreateNew(); @@ -656,6 +673,7 @@ public async Async.Task Process(IList<(string, string)> notificationInfo, bool i _logTracer.AddTags(notificationInfo); _logTracer.AddTag("WorkItemId", entry.Id.HasValue ? entry.Id.Value.ToString() : ""); _logTracer.LogEvent(adoEventType); + yield return new ProcessResult(AdoNotificationPublishignState.Added, entry); } } diff --git a/src/ApiService/FunctionalTests/1f-api/Jobs.cs b/src/ApiService/FunctionalTests/1f-api/Jobs.cs index 7e14f0ec8e..873be12333 100644 --- a/src/ApiService/FunctionalTests/1f-api/Jobs.cs +++ b/src/ApiService/FunctionalTests/1f-api/Jobs.cs @@ -50,6 +50,7 @@ public async Task, Error>> Get(Guid? jobId = null, List< .AddIfNotNullV("task_state", taskState) .AddIfNotNullV("with_tasks", withTasks); + var r = await Get(n); return IEnumerableResult(r); } diff --git a/src/ApiService/IntegrationTests/Fakes/TestAdoNotificationEntryOperations.cs b/src/ApiService/IntegrationTests/Fakes/TestAdoNotificationEntryOperations.cs new file mode 100644 index 0000000000..ec8f8dc16d --- /dev/null +++ b/src/ApiService/IntegrationTests/Fakes/TestAdoNotificationEntryOperations.cs @@ -0,0 +1,8 @@ +using Microsoft.Extensions.Logging; +using Microsoft.OneFuzz.Service; +namespace IntegrationTests.Fakes; + +public sealed class TestAdoNotificationEntryOperations : AdoNotificationEntryOperations { + public TestAdoNotificationEntryOperations(ILogger log, IOnefuzzContext context) + : base(log, context) { } +} diff --git a/src/ApiService/IntegrationTests/Fakes/TestContext.cs b/src/ApiService/IntegrationTests/Fakes/TestContext.cs index 66d121e746..5c1136263d 100644 --- a/src/ApiService/IntegrationTests/Fakes/TestContext.cs +++ b/src/ApiService/IntegrationTests/Fakes/TestContext.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Net.Http; using Microsoft.Extensions.Caching.Memory; @@ -42,6 +43,7 @@ public TestContext(IHttpClientFactory httpClientFactory, OneFuzzLoggerProvider p ReproOperations = new ReproOperations(provider.CreateLogger(), this); Reports = new Reports(provider.CreateLogger(), Containers); NotificationOperations = new NotificationOperations(provider.CreateLogger(), this); + AdoNotificationEntryOperations = new TestAdoNotificationEntryOperations(provider.CreateLogger(), this); FeatureManagerSnapshot = new TestFeatureManagerSnapshot(); WebhookOperations = new TestWebhookOperations(httpClientFactory, provider.CreateLogger(), this); @@ -65,9 +67,28 @@ public Async.Task InsertAll(params EntityBase[] objs) InstanceConfig ic => ConfigOperations.Insert(ic), Notification n => NotificationOperations.Insert(n), Webhook w => WebhookOperations.Insert(w), + AdoNotificationEntry ado => AdoNotificationEntryOperations.Insert(ado), _ => throw new NotSupportedException($"You will need to add an TestContext.InsertAll case for {x.GetType()} entities"), })); + public Async.Task InsertAll(IEnumerable objs) + => Async.Task.WhenAll( + objs.Select(x => x switch { + Task t => TaskOperations.Insert(t), + Node n => NodeOperations.Insert(n), + Pool p => PoolOperations.Insert(p), + Job j => JobOperations.Insert(j), + JobResult jr => JobResultOperations.Insert(jr), + Repro r => ReproOperations.Insert(r), + Scaleset ss => ScalesetOperations.Insert(ss), + NodeTasks nt => NodeTasksOperations.Insert(nt), + InstanceConfig ic => ConfigOperations.Insert(ic), + Notification n => NotificationOperations.Insert(n), + Webhook w => WebhookOperations.Insert(w), + AdoNotificationEntry ado => AdoNotificationEntryOperations.Insert(ado), + _ => throw new NotSupportedException($"You will need to add an TestContext.InsertAll case for {x.GetType()} entities"), + })); + // Implementations: public IMemoryCache Cache { get; } @@ -109,6 +130,8 @@ public Async.Task InsertAll(params EntityBase[] objs) public IWebhookMessageLogOperations WebhookMessageLogOperations { get; } + public IAdoNotificationEntryOperations AdoNotificationEntryOperations { get; } + // -- Remainder not implemented -- public IConfig Config => throw new System.NotImplementedException(); @@ -143,4 +166,6 @@ public Async.Task InsertAll(params EntityBase[] objs) public IAdo Ado => throw new NotImplementedException(); public IConfigurationRefresher ConfigurationRefresher => throw new NotImplementedException(); + + } diff --git a/src/ApiService/IntegrationTests/JobsTests.cs b/src/ApiService/IntegrationTests/JobsTests.cs index 28dbe8457f..1094ded5d4 100644 --- a/src/ApiService/IntegrationTests/JobsTests.cs +++ b/src/ApiService/IntegrationTests/JobsTests.cs @@ -226,4 +226,30 @@ await Context.InsertAll( Assert.Equal(task.Config.Task.Type, returnedTasks[0].Type); } + + [Fact] + public async Async.Task Get_CanFindSpecificJobWithBugs() { + var taskConfig = new TaskConfig(_jobId, new List(), new TaskDetails(TaskType.Coverage, 60)); + + var random = new Random(); + var bugs = Enumerable.Range(1, 100).Select(i => random.Next(0, 100)).Distinct().Select(i => new AdoNotificationEntry(_jobId, i, $"test_i")).ToList(); + await Context.InsertAll(bugs); + await Context.InsertAll( + new Job(_jobId, JobState.Stopped, _config, null), + new Task(_jobId, Guid.NewGuid(), TaskState.Running, Os.Windows, taskConfig) + ); + + var func = new Jobs(Context, LoggerProvider.CreateLogger()); + + var ctx = new TestFunctionContext(); + var result = await func.Run(TestHttpRequestData.FromJson("GET", new JobSearch(JobId: _jobId, WithBugs: true)), ctx); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + + var response = BodyAs(result); + Assert.Equal(_jobId, response.JobId); + Assert.NotNull(response.TaskInfo); + var returnedBugs = response.AdoBugIds.ToList(); + Assert.NotEmpty(returnedBugs); + Assert.Equal(bugs.Select(x => x.Id).Order(), returnedBugs.Order()); + } } diff --git a/src/cli/onefuzz/api.py b/src/cli/onefuzz/api.py index fdd9bd2197..17df63ed06 100644 --- a/src/cli/onefuzz/api.py +++ b/src/cli/onefuzz/api.py @@ -1071,7 +1071,9 @@ def delete(self, job_id: UUID_EXPANSION) -> models.Job: "DELETE", models.Job, data=requests.JobGet(job_id=job_id_expanded) ) - def get(self, job_id: UUID_EXPANSION, with_tasks: bool = False) -> models.Job: + def get( + self, job_id: UUID_EXPANSION, with_tasks: bool = False, with_bugs: bool = False + ) -> models.Job: """Get information about a specific job""" job_id_expanded = self._disambiguate_uuid( "job_id", job_id, lambda: [str(x.job_id) for x in self.list()] @@ -1080,7 +1082,9 @@ def get(self, job_id: UUID_EXPANSION, with_tasks: bool = False) -> models.Job: job = self._req_model( "GET", models.Job, - data=requests.JobGet(job_id=job_id_expanded, with_tasks=with_tasks), + data=requests.JobGet( + job_id=job_id_expanded, with_tasks=with_tasks, with_bugs=with_bugs + ), ) return job diff --git a/src/pytypes/onefuzztypes/models.py b/src/pytypes/onefuzztypes/models.py index 70e9831e0b..d96a983b11 100644 --- a/src/pytypes/onefuzztypes/models.py +++ b/src/pytypes/onefuzztypes/models.py @@ -760,6 +760,7 @@ class Job(BaseModel): task_info: Optional[List[Union[Task, JobTaskInfo]]] user_info: Optional[UserInfo] start_time: Optional[datetime] = None + ado_bug_ids: Optional[List[int]] = None class NetworkConfig(BaseModel): diff --git a/src/pytypes/onefuzztypes/requests.py b/src/pytypes/onefuzztypes/requests.py index d284fb416d..0b64b8ef87 100644 --- a/src/pytypes/onefuzztypes/requests.py +++ b/src/pytypes/onefuzztypes/requests.py @@ -41,6 +41,7 @@ class BaseRequest(BaseModel): class JobGet(BaseRequest): job_id: UUID with_tasks: Optional[bool] + with_bugs: Optional[bool] class JobSearch(BaseRequest): From 08788fecaa1e44251cdfdcbe1d1e46b0968ab72b Mon Sep 17 00:00:00 2001 From: Cheick Keita Date: Wed, 4 Oct 2023 16:19:39 -0700 Subject: [PATCH 2/4] remove bug list and add has_bugs --- src/ApiService/ApiService/Functions/Jobs.cs | 4 ++-- src/ApiService/ApiService/OneFuzzTypes/Requests.cs | 3 +-- src/ApiService/ApiService/OneFuzzTypes/Responses.cs | 7 +++---- .../onefuzzlib/AdoNotificationEntryOperation.cs | 6 ++++++ src/cli/onefuzz/api.py | 8 ++------ src/pytypes/onefuzztypes/models.py | 2 +- src/pytypes/onefuzztypes/requests.py | 1 - 7 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/ApiService/ApiService/Functions/Jobs.cs b/src/ApiService/ApiService/Functions/Jobs.cs index a2badfe5b2..ba76f45fb9 100644 --- a/src/ApiService/ApiService/Functions/Jobs.cs +++ b/src/ApiService/ApiService/Functions/Jobs.cs @@ -138,8 +138,8 @@ private async Task Get(HttpRequestData req) { var tasks = _context.TaskOperations.SearchStates(jobId); IAsyncEnumerable taskInfo = search.WithTasks ?? false ? tasks : tasks.Select(TaskToJobTaskInfo); - var bugs = search.WithBugs ?? false ? _context.AdoNotificationEntryOperations.GetByJobId(jobId).Select(adoEntry => adoEntry.Id).ToEnumerable() : null; - return await RequestHandling.Ok(req, JobResponse.ForJob(job, taskInfo.ToEnumerable(), bugs)); + var hasBugs = await _context.AdoNotificationEntryOperations.WasNotfied(jobId); + return await RequestHandling.Ok(req, JobResponse.ForJob(job, taskInfo.ToEnumerable(), hasBugs)); } var jobs = await _context.JobOperations.SearchState(states: search.State ?? Enumerable.Empty()).ToListAsync(); diff --git a/src/ApiService/ApiService/OneFuzzTypes/Requests.cs b/src/ApiService/ApiService/OneFuzzTypes/Requests.cs index caa24dc4bb..9001f4afb2 100644 --- a/src/ApiService/ApiService/OneFuzzTypes/Requests.cs +++ b/src/ApiService/ApiService/OneFuzzTypes/Requests.cs @@ -155,8 +155,7 @@ public record JobSearch( Guid? JobId = null, List? State = null, List? TaskState = null, - bool? WithTasks = null, - bool? WithBugs = null + bool? WithTasks = null ) : BaseRequest; diff --git a/src/ApiService/ApiService/OneFuzzTypes/Responses.cs b/src/ApiService/ApiService/OneFuzzTypes/Responses.cs index 9d5943d9e8..3548d784d3 100644 --- a/src/ApiService/ApiService/OneFuzzTypes/Responses.cs +++ b/src/ApiService/ApiService/OneFuzzTypes/Responses.cs @@ -103,11 +103,10 @@ public record JobResponse( StoredUserInfo? UserInfo, [property: JsonPropertyName("Timestamp")] // must retain capital T for backcompat DateTimeOffset? Timestamp, - IEnumerable AdoBugIds - + bool HasBugs // not including UserInfo from Job model ) : BaseResponse() { - public static JobResponse ForJob(Job j, IEnumerable? taskInfo, IEnumerable? adoBugIds = null) + public static JobResponse ForJob(Job j, IEnumerable? taskInfo, bool hasBugs = false) => new( JobId: j.JobId, State: j.State, @@ -117,7 +116,7 @@ public static JobResponse ForJob(Job j, IEnumerable? taskInfo, IEn TaskInfo: taskInfo, UserInfo: j.UserInfo, Timestamp: j.Timestamp, - AdoBugIds: adoBugIds ?? new List() + HasBugs: hasBugs ); public DateTimeOffset? StartTime => EndTime is DateTimeOffset endTime ? endTime.Subtract(TimeSpan.FromHours(Config.Duration)) : null; } diff --git a/src/ApiService/ApiService/onefuzzlib/AdoNotificationEntryOperation.cs b/src/ApiService/ApiService/onefuzzlib/AdoNotificationEntryOperation.cs index 8232ecef38..9a21428b23 100644 --- a/src/ApiService/ApiService/onefuzzlib/AdoNotificationEntryOperation.cs +++ b/src/ApiService/ApiService/onefuzzlib/AdoNotificationEntryOperation.cs @@ -6,6 +6,8 @@ public interface IAdoNotificationEntryOperations : IOrm { public IAsyncEnumerable GetByJobId(Guid jobId); + public Async.Task WasNotfied(Guid jobId); + } public class AdoNotificationEntryOperations : Orm, IAdoNotificationEntryOperations { @@ -17,4 +19,8 @@ public AdoNotificationEntryOperations(ILogger lo public IAsyncEnumerable GetByJobId(Guid jobId) { return QueryAsync(filter: Query.PartitionKey(jobId.ToString())); } + + public async Async.Task WasNotfied(Guid jobId) { + return await QueryAsync(filter: Query.PartitionKey(jobId.ToString()), maxPerPage: 1).AnyAsync(); + } } diff --git a/src/cli/onefuzz/api.py b/src/cli/onefuzz/api.py index 17df63ed06..fdd9bd2197 100644 --- a/src/cli/onefuzz/api.py +++ b/src/cli/onefuzz/api.py @@ -1071,9 +1071,7 @@ def delete(self, job_id: UUID_EXPANSION) -> models.Job: "DELETE", models.Job, data=requests.JobGet(job_id=job_id_expanded) ) - def get( - self, job_id: UUID_EXPANSION, with_tasks: bool = False, with_bugs: bool = False - ) -> models.Job: + def get(self, job_id: UUID_EXPANSION, with_tasks: bool = False) -> models.Job: """Get information about a specific job""" job_id_expanded = self._disambiguate_uuid( "job_id", job_id, lambda: [str(x.job_id) for x in self.list()] @@ -1082,9 +1080,7 @@ def get( job = self._req_model( "GET", models.Job, - data=requests.JobGet( - job_id=job_id_expanded, with_tasks=with_tasks, with_bugs=with_bugs - ), + data=requests.JobGet(job_id=job_id_expanded, with_tasks=with_tasks), ) return job diff --git a/src/pytypes/onefuzztypes/models.py b/src/pytypes/onefuzztypes/models.py index d96a983b11..a3784294b8 100644 --- a/src/pytypes/onefuzztypes/models.py +++ b/src/pytypes/onefuzztypes/models.py @@ -760,7 +760,7 @@ class Job(BaseModel): task_info: Optional[List[Union[Task, JobTaskInfo]]] user_info: Optional[UserInfo] start_time: Optional[datetime] = None - ado_bug_ids: Optional[List[int]] = None + has_bugs: Optional[bool] = None class NetworkConfig(BaseModel): diff --git a/src/pytypes/onefuzztypes/requests.py b/src/pytypes/onefuzztypes/requests.py index 0b64b8ef87..d284fb416d 100644 --- a/src/pytypes/onefuzztypes/requests.py +++ b/src/pytypes/onefuzztypes/requests.py @@ -41,7 +41,6 @@ class BaseRequest(BaseModel): class JobGet(BaseRequest): job_id: UUID with_tasks: Optional[bool] - with_bugs: Optional[bool] class JobSearch(BaseRequest): From 22e86514fbf25ad7a4e899918ca337a7f305886d Mon Sep 17 00:00:00 2001 From: Cheick Keita Date: Wed, 4 Oct 2023 16:26:38 -0700 Subject: [PATCH 3/4] fix test --- src/ApiService/IntegrationTests/JobsTests.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/ApiService/IntegrationTests/JobsTests.cs b/src/ApiService/IntegrationTests/JobsTests.cs index 1094ded5d4..d957e90e0c 100644 --- a/src/ApiService/IntegrationTests/JobsTests.cs +++ b/src/ApiService/IntegrationTests/JobsTests.cs @@ -242,14 +242,12 @@ await Context.InsertAll( var func = new Jobs(Context, LoggerProvider.CreateLogger()); var ctx = new TestFunctionContext(); - var result = await func.Run(TestHttpRequestData.FromJson("GET", new JobSearch(JobId: _jobId, WithBugs: true)), ctx); + var result = await func.Run(TestHttpRequestData.FromJson("GET", new JobSearch(JobId: _jobId)), ctx); Assert.Equal(HttpStatusCode.OK, result.StatusCode); var response = BodyAs(result); Assert.Equal(_jobId, response.JobId); Assert.NotNull(response.TaskInfo); - var returnedBugs = response.AdoBugIds.ToList(); - Assert.NotEmpty(returnedBugs); - Assert.Equal(bugs.Select(x => x.Id).Order(), returnedBugs.Order()); + Assert.True(response.HasBugs); } } From 3cc48d9188a44f8d927e57e7d0b591f259894621 Mon Sep 17 00:00:00 2001 From: Cheick Keita Date: Mon, 30 Oct 2023 16:27:38 -0700 Subject: [PATCH 4/4] report CrashReported instead --- src/ApiService/ApiService/Functions/Jobs.cs | 5 ++-- .../ApiService/Functions/QueueJobResult.cs | 4 +++ .../ApiService/OneFuzzTypes/Model.cs | 7 +++-- .../ApiService/OneFuzzTypes/Responses.cs | 6 ++-- src/ApiService/ApiService/Program.cs | 1 + .../ApiService/onefuzzlib/JobCrashReported.cs | 28 +++++++++++++++++++ .../ApiService/onefuzzlib/OnefuzzContext.cs | 2 ++ src/ApiService/IntegrationTests/JobsTests.cs | 2 +- src/pytypes/onefuzztypes/models.py | 2 +- 9 files changed, 48 insertions(+), 9 deletions(-) create mode 100644 src/ApiService/ApiService/onefuzzlib/JobCrashReported.cs diff --git a/src/ApiService/ApiService/Functions/Jobs.cs b/src/ApiService/ApiService/Functions/Jobs.cs index ba76f45fb9..f4b5f04fa9 100644 --- a/src/ApiService/ApiService/Functions/Jobs.cs +++ b/src/ApiService/ApiService/Functions/Jobs.cs @@ -138,8 +138,9 @@ private async Task Get(HttpRequestData req) { var tasks = _context.TaskOperations.SearchStates(jobId); IAsyncEnumerable taskInfo = search.WithTasks ?? false ? tasks : tasks.Select(TaskToJobTaskInfo); - var hasBugs = await _context.AdoNotificationEntryOperations.WasNotfied(jobId); - return await RequestHandling.Ok(req, JobResponse.ForJob(job, taskInfo.ToEnumerable(), hasBugs)); + + var crashReported = await _context.JobCrashReportedOperations.CrashReported(jobId); + return await RequestHandling.Ok(req, JobResponse.ForJob(job, taskInfo.ToEnumerable(), crashReported)); } var jobs = await _context.JobOperations.SearchState(states: search.State ?? Enumerable.Empty()).ToListAsync(); diff --git a/src/ApiService/ApiService/Functions/QueueJobResult.cs b/src/ApiService/ApiService/Functions/QueueJobResult.cs index 31b39802d6..3f863cb2f2 100644 --- a/src/ApiService/ApiService/Functions/QueueJobResult.cs +++ b/src/ApiService/ApiService/Functions/QueueJobResult.cs @@ -49,6 +49,10 @@ public async Async.Task Run([QueueTrigger("job-result", Connection = "AzureWebJo var jobResultType = data.Type; _log.LogInformation($"job result data type: {jobResultType}"); + if (jobResultType == "CrashReported") { + var _result = await _context.JobCrashReportedOperations.ReportCrash(job.JobId, jr.TaskId); + } + Dictionary value; if (jr.Value.Count > 0) { value = jr.Value; diff --git a/src/ApiService/ApiService/OneFuzzTypes/Model.cs b/src/ApiService/ApiService/OneFuzzTypes/Model.cs index afb1abd171..a5e2db1e26 100644 --- a/src/ApiService/ApiService/OneFuzzTypes/Model.cs +++ b/src/ApiService/ApiService/OneFuzzTypes/Model.cs @@ -968,9 +968,12 @@ public record Job( StoredUserInfo? UserInfo, string? Error = null, DateTimeOffset? EndTime = null -) : StatefulEntityBase(State) { +) : StatefulEntityBase(State); -} +public record JobCrashReported( + [PartitionKey] Guid JobId, + [RowKey] Guid TaskId +) : EntityBase; // This is like UserInfo but lacks the UPN: public record StoredUserInfo(Guid? ApplicationId, Guid? ObjectId); diff --git a/src/ApiService/ApiService/OneFuzzTypes/Responses.cs b/src/ApiService/ApiService/OneFuzzTypes/Responses.cs index 3548d784d3..2760837a66 100644 --- a/src/ApiService/ApiService/OneFuzzTypes/Responses.cs +++ b/src/ApiService/ApiService/OneFuzzTypes/Responses.cs @@ -103,10 +103,10 @@ public record JobResponse( StoredUserInfo? UserInfo, [property: JsonPropertyName("Timestamp")] // must retain capital T for backcompat DateTimeOffset? Timestamp, - bool HasBugs + bool CrashReported // not including UserInfo from Job model ) : BaseResponse() { - public static JobResponse ForJob(Job j, IEnumerable? taskInfo, bool hasBugs = false) + public static JobResponse ForJob(Job j, IEnumerable? taskInfo, bool crashReported = false) => new( JobId: j.JobId, State: j.State, @@ -116,7 +116,7 @@ public static JobResponse ForJob(Job j, IEnumerable? taskInfo, boo TaskInfo: taskInfo, UserInfo: j.UserInfo, Timestamp: j.Timestamp, - HasBugs: hasBugs + CrashReported: crashReported ); public DateTimeOffset? StartTime => EndTime is DateTimeOffset endTime ? endTime.Subtract(TimeSpan.FromHours(Config.Duration)) : null; } diff --git a/src/ApiService/ApiService/Program.cs b/src/ApiService/ApiService/Program.cs index f8114784e0..8e491b83e2 100644 --- a/src/ApiService/ApiService/Program.cs +++ b/src/ApiService/ApiService/Program.cs @@ -199,6 +199,7 @@ public static async Async.Task Main() { .AddScoped() .AddScoped() .AddScoped() + .AddScoped() .AddSingleton(new GraphServiceClient(new DefaultAzureCredential())) .AddSingleton() .AddSingleton() diff --git a/src/ApiService/ApiService/onefuzzlib/JobCrashReported.cs b/src/ApiService/ApiService/onefuzzlib/JobCrashReported.cs new file mode 100644 index 0000000000..f5d3227d30 --- /dev/null +++ b/src/ApiService/ApiService/onefuzzlib/JobCrashReported.cs @@ -0,0 +1,28 @@ +using System.Threading.Tasks; +using ApiService.OneFuzzLib.Orm; +using Microsoft.Extensions.Logging; +namespace Microsoft.OneFuzz.Service; + +public interface IJobCrashReportedOperations : IOrm { + public Task CrashReported(Guid jobId); + public Task ReportCrash(Guid jobId, Guid taskId); +} + +public class JobCrashReportedOperations : Orm, IJobCrashReportedOperations { + public JobCrashReportedOperations(ILogger logTracer, IOnefuzzContext context) : base(logTracer, context) { + } + + public async Task CrashReported(Guid jobId) { + return await QueryAsync(Query.RowKey(jobId.ToString())).AnyAsync(); + } + + public async Task ReportCrash(Guid jobId, Guid taskId) { + + var result = await Update(new JobCrashReported(jobId, taskId)); + if (!result.IsOk) { + return OneFuzzResultVoid.Error(ErrorCode.UNABLE_TO_UPDATE, "Failed to update job crash reported"); + } + + return OneFuzzResultVoid.Ok; + } +} diff --git a/src/ApiService/ApiService/onefuzzlib/OnefuzzContext.cs b/src/ApiService/ApiService/onefuzzlib/OnefuzzContext.cs index 0661f753ba..5001812287 100644 --- a/src/ApiService/ApiService/onefuzzlib/OnefuzzContext.cs +++ b/src/ApiService/ApiService/onefuzzlib/OnefuzzContext.cs @@ -50,6 +50,7 @@ public interface IOnefuzzContext { IGithubIssues GithubIssues { get; } IAdo Ado { get; } IAdoNotificationEntryOperations AdoNotificationEntryOperations { get; } + IJobCrashReportedOperations JobCrashReportedOperations =>; IFeatureManagerSnapshot FeatureManagerSnapshot { get; } IConfigurationRefresher ConfigurationRefresher { get; } @@ -102,6 +103,7 @@ public OnefuzzContext(IServiceProvider serviceProvider) { public ITeams Teams => _serviceProvider.GetRequiredService(); public IGithubIssues GithubIssues => _serviceProvider.GetRequiredService(); public IAdo Ado => _serviceProvider.GetRequiredService(); + public IJobCrashReportedOperations JobCrashReportedOperations => _serviceProvider.GetRequiredService(); public IFeatureManagerSnapshot FeatureManagerSnapshot => _serviceProvider.GetRequiredService(); diff --git a/src/ApiService/IntegrationTests/JobsTests.cs b/src/ApiService/IntegrationTests/JobsTests.cs index d957e90e0c..563e92cbdd 100644 --- a/src/ApiService/IntegrationTests/JobsTests.cs +++ b/src/ApiService/IntegrationTests/JobsTests.cs @@ -248,6 +248,6 @@ await Context.InsertAll( var response = BodyAs(result); Assert.Equal(_jobId, response.JobId); Assert.NotNull(response.TaskInfo); - Assert.True(response.HasBugs); + Assert.True(response.CrashReported); } } diff --git a/src/pytypes/onefuzztypes/models.py b/src/pytypes/onefuzztypes/models.py index e2a49f6aa0..8adae9c9ee 100644 --- a/src/pytypes/onefuzztypes/models.py +++ b/src/pytypes/onefuzztypes/models.py @@ -761,7 +761,7 @@ class Job(BaseModel): task_info: Optional[List[Union[Task, JobTaskInfo]]] user_info: Optional[UserInfo] start_time: Optional[datetime] = None - has_bugs: Optional[bool] = None + crash_reported: Optional[bool] = None class NetworkConfig(BaseModel):