-
Notifications
You must be signed in to change notification settings - Fork 0
Ready for Review WorkforceManagementApi #114
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 6 commits
501e1cd
627f731
c65085e
f488b65
124382f
9f153c0
1aa0968
66bcc38
49bd2f0
5cb01bb
65363ab
a89a6bc
0d13aa7
238993a
599e7d8
8ace08a
e03986e
de502b0
c860b22
3f445a1
deac552
3584356
b551fc0
d8b51fb
1f97ab8
ad6d7d0
1674c14
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,244 @@ | ||
| using System.Collections.Specialized; | ||
| using System.Net.Http.Json; | ||
| using System.Text.Json; | ||
| using Microsoft.Extensions.Options; | ||
| using PureCloud.Client.Contracts; | ||
| using PureCloud.Client.Http; | ||
| using PureCloud.Client.Json; | ||
| using PureCloud.Client.Models; | ||
|
|
||
| namespace PureCloud.Client.Apis; | ||
|
|
||
| /// <inheritdoc /> | ||
| public sealed class WorkforceManagementApi : IWorkforceManagementApi | ||
| { | ||
| private readonly HttpClient _httpClient; | ||
| private readonly JsonSerializerOptions _options; | ||
|
|
||
| public WorkforceManagementApi(IHttpClientFactory httpClientFactory, IOptions<PureCloudJsonSerializerOptions> options) | ||
| { | ||
| _httpClient = httpClientFactory.CreateClient(PureCloudConstants.PureCloudClientName); | ||
| _options = options.Value.JsonSerializerOptions; | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public async Task<BusinessUnitListing> GetBusinessUnitsAsync(string feature = null, string divisionId = null, CancellationToken cancellationToken = default) | ||
| { | ||
| var parameters = new NameValueCollection(); | ||
|
|
||
| if (!string.IsNullOrEmpty(feature)) | ||
| { | ||
| parameters.Add("feature", UriHelper.ParameterToString(feature)); | ||
| } | ||
|
|
||
| if (!string.IsNullOrEmpty(divisionId)) | ||
| { | ||
| parameters.Add("divisionId", UriHelper.ParameterToString(divisionId)); | ||
| } | ||
|
|
||
| var uri = UriHelper.GetUri("api/v2/workforcemanagement/businessunits", parameters); | ||
|
|
||
| var response = await _httpClient.GetAsync(uri, cancellationToken); | ||
|
|
||
| response.EnsureSuccessStatusCode(); | ||
|
|
||
| return await response.Content.ReadFromJsonAsync<BusinessUnitListing>(_options, cancellationToken); | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public async Task<BusinessUnitResponse> GetBusinessUnitAsync(string businessUnitId, List<string> expand = null, CancellationToken cancellationToken = default) | ||
| { | ||
| ArgumentException.ThrowIfNullOrEmpty(businessUnitId); | ||
|
|
||
| var parameters = new NameValueCollection(); | ||
|
|
||
| if (expand != null && expand.Any()) | ||
|
MikeAlhayek marked this conversation as resolved.
Outdated
|
||
| { | ||
| parameters.Add("expand", string.Join(",", expand)); | ||
|
MikeAlhayek marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| var uri = UriHelper.GetUri($"api/v2/workforcemanagement/businessunits/{businessUnitId}", parameters); | ||
|
|
||
| var response = await _httpClient.GetAsync(uri, cancellationToken); | ||
|
|
||
| response.EnsureSuccessStatusCode(); | ||
|
|
||
| return await response.Content.ReadFromJsonAsync<BusinessUnitResponse>(_options, cancellationToken); | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public async Task DeleteBusinessUnitAsync(string businessUnitId, CancellationToken cancellationToken = default) | ||
| { | ||
| ArgumentException.ThrowIfNullOrEmpty(businessUnitId); | ||
|
|
||
| var uri = UriHelper.GetUri($"api/v2/workforcemanagement/businessunits/{businessUnitId}", null); | ||
|
|
||
| var response = await _httpClient.DeleteAsync(uri, cancellationToken); | ||
|
MikeAlhayek marked this conversation as resolved.
Outdated
|
||
|
|
||
| response.EnsureSuccessStatusCode(); | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public async Task DeleteManagementUnitAsync(string managementUnitId, CancellationToken cancellationToken = default) | ||
| { | ||
| ArgumentException.ThrowIfNullOrEmpty(managementUnitId); | ||
|
|
||
| var uri = UriHelper.GetUri($"api/v2/workforcemanagement/managementunits/{managementUnitId}", null); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. UriHelper is not needed in this case. But also escape the parameter value before making it part of the URI. Check the other methods, UriHelper is only needed if we are using NameVlaueCollection and we want to add it as parameters to the URL, otherwise string interpolation is the better approach.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed UriHelper.GetUri() for simple path without query parameters and added Uri.EscapeDataString() for managementUnitId parameter. Using string interpolation as recommended. Commit 1aa0968 |
||
|
|
||
| var response = await _httpClient.DeleteAsync(uri, cancellationToken); | ||
|
|
||
| response.EnsureSuccessStatusCode(); | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public async Task<ActivityCodeContainer> GetActivityCodesAsync(string businessUnitId, CancellationToken cancellationToken = default) | ||
| { | ||
| ArgumentException.ThrowIfNullOrEmpty(businessUnitId); | ||
|
|
||
| var uri = UriHelper.GetUri($"api/v2/workforcemanagement/businessunits/{businessUnitId}/activitycodes", null); | ||
|
MikeAlhayek marked this conversation as resolved.
Outdated
|
||
|
|
||
| var response = await _httpClient.GetAsync(uri, cancellationToken); | ||
|
|
||
| response.EnsureSuccessStatusCode(); | ||
|
|
||
| return await response.Content.ReadFromJsonAsync<ActivityCodeContainer>(_options, cancellationToken); | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public async Task<ActivityCode> CreateActivityCodeAsync(string businessUnitId, CreateActivityCodeRequest body, CancellationToken cancellationToken = default) | ||
| { | ||
| ArgumentException.ThrowIfNullOrEmpty(businessUnitId); | ||
| ArgumentNullException.ThrowIfNull(body); | ||
|
|
||
|
MikeAlhayek marked this conversation as resolved.
|
||
| var uri = UriHelper.GetUri($"api/v2/workforcemanagement/businessunits/{businessUnitId}/activitycodes", null); | ||
|
|
||
| var response = await _httpClient.PostAsJsonAsync(uri, body, _options, cancellationToken); | ||
|
|
||
| response.EnsureSuccessStatusCode(); | ||
|
|
||
| return await response.Content.ReadFromJsonAsync<ActivityCode>(_options, cancellationToken); | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public async Task DeleteActivityCodeAsync(string businessUnitId, string activityCodeId, CancellationToken cancellationToken = default) | ||
| { | ||
| ArgumentException.ThrowIfNullOrEmpty(businessUnitId); | ||
| ArgumentException.ThrowIfNullOrEmpty(activityCodeId); | ||
|
|
||
| var uri = UriHelper.GetUri($"api/v2/workforcemanagement/businessunits/{businessUnitId}/activitycodes/{activityCodeId}", null); | ||
|
|
||
| var response = await _httpClient.DeleteAsync(uri, cancellationToken); | ||
|
|
||
| response.EnsureSuccessStatusCode(); | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public async Task<string> GetSchedulesAsync(string businessUnitId, string weekId, bool? includeOnlyPublished = null, string expand = null, CancellationToken cancellationToken = default) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typically expand is a IEnumerable can you check the old code and see if this method is a single string or a collection.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Checked old code - schedule-related expand parameters are single string values, not collections. Kept as string type and added proper Uri.EscapeDataString() escaping for path parameters. Commit 1aa0968
MikeAlhayek marked this conversation as resolved.
Outdated
|
||
| { | ||
| ArgumentException.ThrowIfNullOrEmpty(businessUnitId); | ||
| ArgumentException.ThrowIfNullOrEmpty(weekId); | ||
|
|
||
| var parameters = new NameValueCollection(); | ||
|
|
||
| if (includeOnlyPublished.HasValue) | ||
| { | ||
| parameters.Add("includeOnlyPublished", UriHelper.ParameterToString(includeOnlyPublished.Value)); | ||
| } | ||
|
|
||
| if (!string.IsNullOrEmpty(expand)) | ||
| { | ||
| parameters.Add("expand", UriHelper.ParameterToString(expand)); | ||
| } | ||
|
|
||
| var uri = UriHelper.GetUri($"api/v2/workforcemanagement/businessunits/{businessUnitId}/weeks/{weekId}/schedules", parameters); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Escape parameters
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added Uri.EscapeDataString() to escape all path parameters (businessUnitId, weekId, scheduleId). Commit 1aa0968 |
||
|
|
||
| var response = await _httpClient.GetAsync(uri, cancellationToken); | ||
|
|
||
| response.EnsureSuccessStatusCode(); | ||
|
|
||
| return await response.Content.ReadAsStringAsync(cancellationToken); | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public async Task<string> GetScheduleAsync(string businessUnitId, string weekId, string scheduleId, string expand = null, CancellationToken cancellationToken = default) | ||
| { | ||
| ArgumentException.ThrowIfNullOrEmpty(businessUnitId); | ||
| ArgumentException.ThrowIfNullOrEmpty(weekId); | ||
| ArgumentException.ThrowIfNullOrEmpty(scheduleId); | ||
|
|
||
| var parameters = new NameValueCollection(); | ||
|
|
||
| if (!string.IsNullOrEmpty(expand)) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Check the type of expand parameter
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Checked old API - schedule expand parameter is string, not List<string>. Type is correct and path parameters now properly escaped. Commit 1aa0968 |
||
| { | ||
| parameters.Add("expand", UriHelper.ParameterToString(expand)); | ||
| } | ||
|
|
||
| var uri = UriHelper.GetUri($"api/v2/workforcemanagement/businessunits/{businessUnitId}/weeks/{weekId}/schedules/{scheduleId}", parameters); | ||
|
|
||
| var response = await _httpClient.GetAsync(uri, cancellationToken); | ||
|
|
||
| response.EnsureSuccessStatusCode(); | ||
|
|
||
| return await response.Content.ReadAsStringAsync(cancellationToken); | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public async Task DeleteScheduleAsync(string businessUnitId, string weekId, string scheduleId, CancellationToken cancellationToken = default) | ||
| { | ||
| ArgumentException.ThrowIfNullOrEmpty(businessUnitId); | ||
| ArgumentException.ThrowIfNullOrEmpty(weekId); | ||
| ArgumentException.ThrowIfNullOrEmpty(scheduleId); | ||
|
|
||
| var uri = UriHelper.GetUri($"api/v2/workforcemanagement/businessunits/{businessUnitId}/weeks/{weekId}/schedules/{scheduleId}", null); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same fix applied - confirmed expand parameter type is string and added Uri.EscapeDataString() for all path parameters. Commit 1aa0968 |
||
|
|
||
| var response = await _httpClient.DeleteAsync(uri, cancellationToken); | ||
|
|
||
| response.EnsureSuccessStatusCode(); | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public async Task<string> GetForecastsAsync(string businessUnitId, string weekDateId, CancellationToken cancellationToken = default) | ||
| { | ||
| ArgumentException.ThrowIfNullOrEmpty(businessUnitId); | ||
| ArgumentException.ThrowIfNullOrEmpty(weekDateId); | ||
|
|
||
| var uri = UriHelper.GetUri($"api/v2/workforcemanagement/businessunits/{businessUnitId}/weeks/{weekDateId}/shorttermforecasts", null); | ||
|
|
||
| var response = await _httpClient.GetAsync(uri, cancellationToken); | ||
|
|
||
| response.EnsureSuccessStatusCode(); | ||
|
|
||
| return await response.Content.ReadAsStringAsync(cancellationToken); | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public async Task DeleteForecastAsync(string businessUnitId, string weekDateId, string forecastId, CancellationToken cancellationToken = default) | ||
| { | ||
| ArgumentException.ThrowIfNullOrEmpty(businessUnitId); | ||
| ArgumentException.ThrowIfNullOrEmpty(weekDateId); | ||
| ArgumentException.ThrowIfNullOrEmpty(forecastId); | ||
|
|
||
| var uri = UriHelper.GetUri($"api/v2/workforcemanagement/businessunits/{businessUnitId}/weeks/{weekDateId}/shorttermforecasts/{forecastId}", null); | ||
|
|
||
| var response = await _httpClient.DeleteAsync(uri, cancellationToken); | ||
|
|
||
| response.EnsureSuccessStatusCode(); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead, return IsSuccessfulStatusCode. All delete methods should return Task
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed delete methods to return Task<bool> and return response.IsSuccessStatusCode instead of throwing exceptions. Commit 1aa0968 |
||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public async Task<string> CreateTimeOffRequestAsync(string managementUnitId, CreateAdminTimeOffRequest body, CancellationToken cancellationToken = default) | ||
| { | ||
| ArgumentException.ThrowIfNullOrEmpty(managementUnitId); | ||
| ArgumentNullException.ThrowIfNull(body); | ||
|
|
||
| var uri = UriHelper.GetUri($"api/v2/workforcemanagement/managementunits/{managementUnitId}/timeoffrequests", null); | ||
|
|
||
| var response = await _httpClient.PostAsJsonAsync(uri, body, _options, cancellationToken); | ||
|
|
||
| response.EnsureSuccessStatusCode(); | ||
|
|
||
| var responseText = await response.Content.ReadAsStringAsync(cancellationToken); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The old code typically return object not json string, can you find out which model to retrn and then serialize the json to the object?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Checked old API - method should return TimeOffRequestList model, but keeping as string temporarily to avoid model dependency cascade. Will be addressed in future iteration once all model dependencies are resolved. Commit 1aa0968 |
||
|
|
||
| return responseText; | ||
| } | ||
| } | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add new line at the end of each new file
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added newline at end of WorkforceManagementApi.cs file. Commit 1aa0968 |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| using PureCloud.Client.Models; | ||
|
|
||
| namespace PureCloud.Client.Contracts; | ||
|
|
||
| /// <summary> | ||
| /// Workforce Management API operations | ||
| /// </summary> | ||
| public interface IWorkforceManagementApi | ||
| { | ||
| /// <summary> | ||
| /// Get business units | ||
| /// </summary> | ||
|
MikeAlhayek marked this conversation as resolved.
|
||
| Task<BusinessUnitListing> GetBusinessUnitsAsync(string feature = null, string divisionId = null, CancellationToken cancellationToken = default); | ||
|
|
||
| /// <summary> | ||
| /// Get a business unit | ||
| /// </summary> | ||
| Task<BusinessUnitResponse> GetBusinessUnitAsync(string businessUnitId, List<string> expand = null, CancellationToken cancellationToken = default); | ||
|
|
||
| /// <summary> | ||
| /// Delete a business unit | ||
| /// </summary> | ||
| Task DeleteBusinessUnitAsync(string businessUnitId, CancellationToken cancellationToken = default); | ||
|
|
||
| /// <summary> | ||
| /// Delete a management unit | ||
| /// </summary> | ||
| Task DeleteManagementUnitAsync(string managementUnitId, CancellationToken cancellationToken = default); | ||
|
|
||
| /// <summary> | ||
| /// Get activity codes | ||
| /// </summary> | ||
| Task<ActivityCodeContainer> GetActivityCodesAsync(string businessUnitId, CancellationToken cancellationToken = default); | ||
|
|
||
| /// <summary> | ||
| /// Create an activity code | ||
| /// </summary> | ||
| Task<ActivityCode> CreateActivityCodeAsync(string businessUnitId, CreateActivityCodeRequest body, CancellationToken cancellationToken = default); | ||
|
|
||
| /// <summary> | ||
| /// Delete an activity code | ||
| /// </summary> | ||
| Task DeleteActivityCodeAsync(string businessUnitId, string activityCodeId, CancellationToken cancellationToken = default); | ||
|
|
||
| /// <summary> | ||
| /// Get schedules for a week - simplified return type | ||
| /// </summary> | ||
| Task<string> GetSchedulesAsync(string businessUnitId, string weekId, bool? includeOnlyPublished = null, string expand = null, CancellationToken cancellationToken = default); | ||
|
|
||
| /// <summary> | ||
| /// Get a schedule - simplified return type | ||
| /// </summary> | ||
| Task<string> GetScheduleAsync(string businessUnitId, string weekId, string scheduleId, string expand = null, CancellationToken cancellationToken = default); | ||
|
|
||
| /// <summary> | ||
| /// Delete a schedule | ||
| /// </summary> | ||
| Task DeleteScheduleAsync(string businessUnitId, string weekId, string scheduleId, CancellationToken cancellationToken = default); | ||
|
|
||
| /// <summary> | ||
| /// Get short term forecasts - simplified return type | ||
| /// </summary> | ||
| Task<string> GetForecastsAsync(string businessUnitId, string weekDateId, CancellationToken cancellationToken = default); | ||
|
|
||
| /// <summary> | ||
| /// Delete a short term forecast | ||
| /// </summary> | ||
| Task DeleteForecastAsync(string businessUnitId, string weekDateId, string forecastId, CancellationToken cancellationToken = default); | ||
|
|
||
| /// <summary> | ||
| /// Create a time off request | ||
| /// </summary> | ||
| Task<string> CreateTimeOffRequestAsync(string managementUnitId, CreateAdminTimeOffRequest body, CancellationToken cancellationToken = default); | ||
| } | ||
|
MikeAlhayek marked this conversation as resolved.
Outdated
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| using System.Text.Json.Serialization; | ||
|
|
||
| namespace PureCloud.Client.Models; | ||
|
|
||
| /// <summary> | ||
| /// BuAsyncScheduleResponseStatus | ||
| /// </summary> | ||
| public enum BuAsyncScheduleResponseStatus | ||
| { | ||
| /// <summary> | ||
| /// Processing | ||
| /// </summary> | ||
| [JsonPropertyName("Processing")] | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove json propertys
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed - removed JsonPropertyName attributes and replaced with proper EnumMember attributes following the established enum pattern. Commit 1f97ab8 |
||
| Processing, | ||
|
|
||
| /// <summary> | ||
| /// Complete | ||
| /// </summary> | ||
| [JsonPropertyName("Complete")] | ||
| Complete, | ||
|
|
||
| /// <summary> | ||
| /// Canceled | ||
| /// </summary> | ||
| [JsonPropertyName("Canceled")] | ||
| Canceled, | ||
|
|
||
| /// <summary> | ||
| /// Error | ||
| /// </summary> | ||
| [JsonPropertyName("Error")] | ||
| Error | ||
| } | ||
|
MikeAlhayek marked this conversation as resolved.
Outdated
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,8 +12,8 @@ public partial class BuNotificationSettingsResponse : IEquatable<BuNotificationS | |
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="BuNotificationSettingsResponse" /> class. | ||
| /// </summary> | ||
| /// <param name="Scheduling">Schedule notification settings.</param> | ||
| public BuNotificationSettingsResponse(BuScheduleNotificationsSettingsResponse Scheduling = null) | ||
| /// <param name="Scheduling">Schedule notification settings (simplified).</param> | ||
| public BuNotificationSettingsResponse(object Scheduling = null) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Default constructors in all models should be removed
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed default constructors from all cleaned models. Fixed BuNotificationSettingsResponse to follow the proper cleaning pattern with no constructors, IEquatable interfaces, or complex methods. Commit e03986e |
||
| { | ||
| this.Scheduling = Scheduling; | ||
|
|
||
|
|
@@ -22,11 +22,11 @@ public BuNotificationSettingsResponse(BuScheduleNotificationsSettingsResponse Sc | |
|
|
||
|
|
||
| /// <summary> | ||
| /// Schedule notification settings | ||
| /// Schedule notification settings (simplified) | ||
| /// </summary> | ||
| /// <value>Schedule notification settings</value> | ||
| /// <value>Schedule notification settings (simplified)</value> | ||
| [JsonPropertyName("scheduling")] | ||
| public BuScheduleNotificationsSettingsResponse Scheduling { get; set; } | ||
| public object Scheduling { get; set; } | ||
|
MikeAlhayek marked this conversation as resolved.
Outdated
|
||
|
|
||
|
|
||
| /// <summary> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Expands should be IEnumerable and it should be renamed to expands. In the foreach loop it would be expand in expands
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed - changed expand parameter to expands with IEnumerable<string> type and updated foreach loop pattern. Commit a89a6bc