Skip to content
Open
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
16 changes: 16 additions & 0 deletions ServiceNow.Api.Test/QueryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,22 @@ public async Task InternalPagingTestAsync()
Assert.Empty(dupes);
}

[Theory]
[InlineData(null, "core_company")]
[InlineData("sysparm_display_value=all", "core_company")]
public async Task DisplayValueAllTestAsync(string? extraQueryString, string tableName)
{
// This test fails if the sysparm_display_value=all is specified since the
// property value is an object and not something that can parse as a DateTimeOffset
const string query = "";
const string customOrderByField = "sys_created_on";
var fieldList = new List<string> { };

var result = await Client.GetAllByQueryAsync(tableName, query, fieldList, extraQueryString, customOrderByField).ConfigureAwait(false);
Assert.NotNull(result);
Assert.NotEmpty(result);
}

[Fact]
public async Task PagingTestAsync()
{
Expand Down
61 changes: 54 additions & 7 deletions ServiceNow.Api/ServiceNowClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
Expand Down Expand Up @@ -243,12 +244,12 @@ internal async Task<List<JObject>> GetAllByQueryInternalJObjectAsync(
string? customOrderByField,
CancellationToken cancellationToken)
{
var orderByField = string.IsNullOrWhiteSpace(customOrderByField)
var orderByField = (string.IsNullOrWhiteSpace(customOrderByField)
? _options.PagingFieldName
: customOrderByField;

: customOrderByField)
?? throw new ServiceNowApiException("value for PagingFieldName option is required");
string orderByCommand;
if (orderByField?.StartsWith("-", StringComparison.Ordinal) ?? false)
if (orderByField.StartsWith("-", StringComparison.Ordinal))
{
orderByCommand = "ORDERBYDESC";
orderByField = orderByField.Substring(1);
Expand Down Expand Up @@ -346,9 +347,7 @@ internal async Task<List<JObject>> GetAllByQueryInternalJObjectAsync(
}

// At this point, we can be sure that we have the paging field in the data
maxDateTimeRetrieved = response.Items.Max(jObject =>
// Parse and enforce source as being UTC (Z)
DateTimeOffset.Parse((jObject[orderByField]?.ToString() ?? string.Empty) + "Z"));
maxDateTimeRetrieved = response.Items.Max(jObject => ParseDateTimeOffsetField(jObject, orderByField));

if (previousMaxDateTimeRetrieved == maxDateTimeRetrieved)
{
Expand Down Expand Up @@ -414,6 +413,54 @@ internal async Task<List<JObject>> GetAllByQueryInternalJObjectAsync(
return finalResult.Items;
}

private DateTimeOffset ParseDateTimeOffsetField(JObject jObject, string orderByField)
{
if (jObject.TryGetValue(orderByField, out var jtoken) == false)
{
// field was not found
return DateTimeOffset.MinValue;
}

var jtokenToParse = GetJTokenToParse(jtoken);
var stringValue = jtokenToParse.ToObject<string>();
var parsed = DateTimeOffset.TryParse(stringValue, null, DateTimeStyles.AssumeUniversal, out DateTimeOffset result);
return parsed ? result : DateTimeOffset.MinValue;
}

private JToken GetJTokenToParse(JToken jtoken)
{
if (jtoken.Type == JTokenType.Object)
{
var jobject = jtoken.ToObject<JObject>();
return GetJTokenForObject(jobject);
}

// anything not an object we'll just parse directly
return jtoken;
}

private static JToken GetJTokenForObject(JObject? jobject)
{
if (jobject == null)
{
return string.Empty;
}

// needed to handle the case of passing extraQueryString of sysparm_display_value=all where the
// response values are objects with keys of "value" and "display_value"
if (jobject.TryGetValue("value", out var propValue))
{
// this is the expected case, a value property should be present
return propValue;
}

// use display_value property as a fallback, then whatever property we can find
return jobject.TryGetValue("display_value", out propValue)
? propValue
: jobject.First ?? string.Empty;
}


public Task<Page<JObject>> GetPageByQueryAsync(
int skip,
int take,
Expand Down