Skip to content
This repository was archived by the owner on Nov 1, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/ApiService/ApiService/Log.cs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ public interface ILogTracer {
void ForceFlush();
void Info(LogStringHandler message);
void Warning(LogStringHandler message);
void Warning(Error error);
void Verbose(LogStringHandler message);

ILogTracer WithTag(string k, string v);
Expand Down Expand Up @@ -342,6 +343,10 @@ public void ForceFlush() {
public void Error(Error error) {
Error($"{error:Tag:Error}");
}

public void Warning(Error error) {
Warning($"{error:Tag:Error}");
}
}

public interface ILogTracerFactory {
Expand Down
1 change: 1 addition & 0 deletions src/ApiService/ApiService/OneFuzzTypes/Enums.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public enum ErrorCode {
UNABLE_TO_CREATE_CONTAINER = 474,
UNABLE_TO_DOWNLOAD_FILE = 475,
VM_UPDATE_FAILED = 476,
UNSUPPORTED_FIELD_OPERATION = 477,
}

public enum VmState {
Expand Down
59 changes: 36 additions & 23 deletions src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,25 +122,31 @@ public async IAsyncEnumerable<WorkItem> ExistingWorkItems() {
var parts = new List<string>();
foreach (var key in filters.Keys) {
//# Only add pre-system approved fields to the query
if (!validFields.Contains(key)) {
if (!validFields.ContainsKey(key)) {
postQueryFilter.Add(key, filters[key]);
continue;
}

/*
# WIQL supports wrapping values in ' or " and escaping ' by doubling it
#
# For this System.Title: hi'there
# use this query fragment: [System.Title] = 'hi''there'
#
# For this System.Title: hi"there
# use this query fragment: [System.Title] = 'hi"there'
#
# For this System.Title: hi'"there
# use this query fragment: [System.Title] = 'hi''"there'
*/
var single = "'";
parts.Add($"[{key}] = '{filters[key].Replace(single, single + single)}'");
var field = validFields[key];
var operation = GetSupportedOperation(field);
if (operation.IsOk) {
/*
# WIQL supports wrapping values in ' or " and escaping ' by doubling it
#
# For this System.Title: hi'there
# use this query fragment: [System.Title] = 'hi''there'
#
# For this System.Title: hi"there
# use this query fragment: [System.Title] = 'hi"there'
#
# For this System.Title: hi'"there
# use this query fragment: [System.Title] = 'hi''"there'
*/
var single = "'";
parts.Add($"[{key}] {operation.OkV} '{filters[key].Replace(single, single + single)}'");
} else {
_logTracer.Warning(operation.ErrorV);
}
}

var query = "select [System.Id] from WorkItems";
Expand Down Expand Up @@ -176,7 +182,7 @@ public async Async.Task<bool> UpdateExisting(WorkItem item, (string, string)[] n
Text = comment
},
_project,
(int)(item.Id!));
(int)item.Id!);
}

var document = new JsonPatchDocument();
Expand Down Expand Up @@ -212,7 +218,7 @@ public async Async.Task<bool> UpdateExisting(WorkItem item, (string, string)[] n
}

if (document.Any()) {
_ = await _client.UpdateWorkItemAsync(document, _project, (int)(item.Id!));
_ = await _client.UpdateWorkItemAsync(document, _project, (int)item.Id!);
var adoEventType = "AdoUpdate";
_logTracer.WithTags(notificationInfo).Event($"{adoEventType} {item.Id:Tag:WorkItemId}");

Expand All @@ -224,10 +230,9 @@ public async Async.Task<bool> UpdateExisting(WorkItem item, (string, string)[] n
return stateUpdated;
}

private async Async.Task<List<string>> GetValidFields(string? project) {
private async Async.Task<Dictionary<string, WorkItemField>> GetValidFields(string? project) {
return (await _client.GetFieldsAsync(project, expand: GetFieldsExpand.ExtensionFields))
.Select(field => field.ReferenceName.ToLowerInvariant())
.ToList();
.ToDictionary(field => field.ReferenceName.ToLowerInvariant());
}

private async Async.Task<WorkItem> CreateNew() {
Expand All @@ -241,7 +246,7 @@ private async Async.Task<WorkItem> CreateNew() {
Text = comment,
},
_project,
(int)(entry.Id!));
(int)entry.Id!);
}
return entry;
}
Expand Down Expand Up @@ -324,7 +329,7 @@ private static bool IsADODuplicateWorkItem(WorkItem wi) {
// OR it could have System.State == Closed && System.Reason == Duplicate
// I haven't found any other combinations where System.Reason could be duplicate but just to be safe
// we're explicitly _not_ checking the state of the work item to determine if it's duplicate
return (wi.Fields.ContainsKey("System.Reason") && string.Equals(wi.Fields["System.Reason"].ToString(), "Duplicate"))
return wi.Fields.ContainsKey("System.Reason") && string.Equals(wi.Fields["System.Reason"].ToString(), "Duplicate")
// Alternatively, the work item can also specify a 'relation' to another work item.
// This is typically used to create parent/child relationships between work items but can also
// Be used to mark duplicates so we should check this as well.
Expand All @@ -334,7 +339,15 @@ private static bool IsADODuplicateWorkItem(WorkItem wi) {
// "Duplicate Of" are the duplicates. That is why we search for the relation type "Duplicate Of".
// "Duplicate Of" has the relation type: "System.LinkTypes.Duplicate-Forward"
// Source: https://learn.microsoft.com/en-us/azure/devops/boards/queries/link-type-reference?view=azure-devops#work-link-types
|| (wi.Relations != null && wi.Relations.Any(relation => string.Equals(relation.Rel, "System.LinkTypes.Duplicate-Forward")));
|| wi.Relations != null && wi.Relations.Any(relation => string.Equals(relation.Rel, "System.LinkTypes.Duplicate-Forward"));
}

private static OneFuzzResult<string> GetSupportedOperation(WorkItemField field) {
return field.SupportedOperations switch {
var supportedOps when supportedOps.Any(op => op.ReferenceName == "SupportedOperations.Equals") => OneFuzzResult.Ok("="),
var supportedOps when supportedOps.Any(op => op.ReferenceName == "SupportedOperations.ContainsWords") => OneFuzzResult.Ok("Contains Words"),
_ => OneFuzzResult<string>.Error(ErrorCode.UNSUPPORTED_FIELD_OPERATION, $"OneFuzz only support operations ['Equals', 'ContainsWords']. Field {field.ReferenceName} only support operations: {string.Join(',', field.SupportedOperations.Select(op => op.ReferenceName))}"),
};
}
}
}
4 changes: 4 additions & 0 deletions src/ApiService/IntegrationTests/TestLogTracer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,8 @@ public ILogTracer WithTags(IEnumerable<(string, string)>? tags) {
public void Error(Error error) {
Error($"{error}");
}

public void Warning(Error error) {
Warning($"{error}");
}
}