/// <summary> /// Method that will delete an orphaned open shift entity. /// </summary> /// <param name="allRequiredConfiguration">The required configuration details.</param> /// <param name="lookUpDataFoundList">The found open shifts.</param> /// <param name="lookUpData">All of the look up (reference data).</param> /// <param name="mappedTeam">The list of mapped teams.</param> /// <returns>A unit of execution.</returns> private async Task DeleteOrphanDataOpenShiftsEntityMappingAsync( SetupDetails allRequiredConfiguration, List <AllOpenShiftMappingEntity> lookUpDataFoundList, List <AllOpenShiftMappingEntity> lookUpData, TeamToDepartmentJobMappingEntity mappedTeam) { // delete entries from orphan list var orphanList = lookUpData.Except(lookUpDataFoundList); this.telemetryClient.TrackTrace($"DeleteOrphanDataOpenShiftsEntityMappingAsync started at: {DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture)}"); // This foreach loop iterates over items that are to be deleted from Shifts UI. In other // words, these are Open Shifts which have been deleted in Kronos WFC, and those deletions // are propagating to Shifts. foreach (var item in orphanList) { this.telemetryClient.TrackTrace($"OpenShiftController - Checking {item.RowKey} to see if there are any Open Shift Requests"); var isInOpenShiftRequestMappingTable = await this.openShiftRequestMappingEntityProvider.CheckOpenShiftRequestExistance(item.RowKey).ConfigureAwait(false); if (!isInOpenShiftRequestMappingTable) { this.telemetryClient.TrackTrace($"{item.RowKey} is not in the Open Shift Request mapping table - deletion can be done."); await this.DeleteOpenShiftInTeams(allRequiredConfiguration, item, mappedTeam).ConfigureAwait(false); } else { // Log that the open shift exists in another table and it should not be deleted. this.telemetryClient.TrackTrace($"OpenShiftController - Open Shift ID: {item.RowKey} is being handled by another process."); } } this.telemetryClient.TrackTrace($"DeleteOrphanDataOpenShiftsEntityMappingAsync ended at: {DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture)}"); }
/// <summary> /// This method will remove open shifts from Teams in the event we find more OS in cache /// than retrieved from Kronos. /// </summary> /// <param name="allRequiredConfigurations">The required configuration details.</param> /// <param name="mappedOrgJobEntity">The team deatils.</param> /// <param name="openShiftsToProcess">All of the matching entities in cache.</param> /// <param name="numberOfOpenShiftsToRemove">The number of matching entities we want to remove.</param> /// <returns>A unit of execution.</returns> private async Task RemoveAdditionalOpenShiftsFromTeamsAsync( SetupDetails allRequiredConfigurations, TeamToDepartmentJobMappingEntity mappedOrgJobEntity, List <AllOpenShiftMappingEntity> openShiftsToProcess, int numberOfOpenShiftsToRemove) { var totalSlotsInCache = 0; openShiftsToProcess.ForEach(x => totalSlotsInCache += x.KronosSlots); if (!openShiftsToProcess.Any() || totalSlotsInCache < numberOfOpenShiftsToRemove) { // This code should not ever be used in theory however it protects against an infinite loop. this.telemetryClient.TrackTrace($"Error when removing open shifts from Teams. We need to remove {numberOfOpenShiftsToRemove} slots from cache but there is only {totalSlotsInCache} remaining in cache."); return; } do { // Find the mapping entity with the most slots var mappingEntityToDecrement = openShiftsToProcess.OrderByDescending(x => x.KronosSlots).First(); if (mappingEntityToDecrement.KronosSlots - numberOfOpenShiftsToRemove > 0) { // More slots than what we need to remove so update slot count in Teams and update cache. var teamsOpenShiftEntity = await this.GetOpenShiftFromTeams(allRequiredConfigurations, mappedOrgJobEntity, mappingEntityToDecrement).ConfigureAwait(false); if (teamsOpenShiftEntity != null) { teamsOpenShiftEntity.SharedOpenShift.OpenSlotCount -= numberOfOpenShiftsToRemove; var response = await this.UpdateOpenShiftInTeams(allRequiredConfigurations, teamsOpenShiftEntity, mappedOrgJobEntity).ConfigureAwait(false); if (response.IsSuccessStatusCode) { mappingEntityToDecrement.KronosSlots -= numberOfOpenShiftsToRemove; await this.openShiftMappingEntityProvider.SaveOrUpdateOpenShiftMappingEntityAsync(mappingEntityToDecrement).ConfigureAwait(false); numberOfOpenShiftsToRemove = 0; } } } else { // We need to remove more so delete the entity in Teams and delete from cache var response = await this.DeleteOpenShiftInTeams(allRequiredConfigurations, mappingEntityToDecrement, mappedOrgJobEntity).ConfigureAwait(false); if (response.IsSuccessStatusCode) { numberOfOpenShiftsToRemove -= mappingEntityToDecrement.KronosSlots; } } // Remove the mapping entity so we can process the next entity if necessary. openShiftsToProcess.Remove(mappingEntityToDecrement); }while (numberOfOpenShiftsToRemove > 0); }
/// <summary> /// Retrieve an open shift from Teams by Team open shift Id. /// </summary> /// <param name="allRequiredConfigurations">The required configuration details.</param> /// <param name="mappedOrgJobEntity">The team details.</param> /// <param name="mappingEntityToDecrement">The mapping entity conatianing the details of the OS we want to retrieve.</param> /// <returns>A Graph open shift object.</returns> private async Task <GraphOpenShift> GetOpenShiftFromTeams(SetupDetails allRequiredConfigurations, TeamToDepartmentJobMappingEntity mappedOrgJobEntity, AllOpenShiftMappingEntity mappingEntityToDecrement) { var httpClient = this.httpClientFactory.CreateClient("ShiftsAPI"); var requestUrl = $"teams/{mappedOrgJobEntity.TeamId}/schedule/openShifts/{mappingEntityToDecrement.RowKey}"; var response = await this.graphUtility.SendHttpRequest(allRequiredConfigurations.GraphConfigurationDetails, httpClient, HttpMethod.Get, requestUrl).ConfigureAwait(false); if (response.IsSuccessStatusCode) { var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); return(JsonConvert.DeserializeObject <GraphOpenShift>(responseContent)); } else { this.telemetryClient.TrackTrace($"The open shift with id {mappingEntityToDecrement.RowKey} could not be found in Teams. "); return(null); } }
/// <summary> /// Delete an open shift entity from Teams. /// </summary> /// <param name="allRequiredConfiguration">The required configuration details.</param> /// <param name="openShiftMapping">The open shift entity to delete.</param> /// <param name="mappedTeam">The team details.</param> /// <returns>A unit of execution.</returns> private async Task <HttpResponseMessage> DeleteOpenShiftInTeams(SetupDetails allRequiredConfiguration, AllOpenShiftMappingEntity openShiftMapping, TeamToDepartmentJobMappingEntity mappedTeam) { var httpClient = this.httpClientFactory.CreateClient("ShiftsAPI"); httpClient.DefaultRequestHeaders.Add("X-MS-WFMPassthrough", allRequiredConfiguration.WFIId); var requestUrl = $"teams/{mappedTeam.TeamId}/schedule/openShifts/{openShiftMapping.RowKey}"; var response = await this.graphUtility.SendHttpRequest(allRequiredConfiguration.GraphConfigurationDetails, httpClient, HttpMethod.Delete, requestUrl).ConfigureAwait(false); if (response.IsSuccessStatusCode) { var successfulDeleteProps = new Dictionary <string, string>() { { "ResponseCode", response.StatusCode.ToString() }, { "ResponseHeader", response.Headers.ToString() }, { "MappedTeamId", mappedTeam?.TeamId }, { "OpenShiftIdToDelete", openShiftMapping.RowKey }, }; this.telemetryClient.TrackTrace(Resource.DeleteOrphanDataOpenShiftsEntityMappingAsync, successfulDeleteProps); await this.openShiftMappingEntityProvider.DeleteOrphanDataFromOpenShiftMappingAsync(openShiftMapping).ConfigureAwait(false); } else { var errorDeleteProps = new Dictionary <string, string>() { { "ResponseCode", response.StatusCode.ToString() }, { "ResponseHeader", response.Headers.ToString() }, { "MappedTeamId", mappedTeam?.TeamId }, { "OpenShiftIdToDelete", openShiftMapping.RowKey }, }; this.telemetryClient.TrackTrace(Resource.DeleteOrphanDataOpenShiftsEntityMappingAsync, errorDeleteProps); } return(response); }
/// <summary> /// Update an open shift entity in Teams. /// </summary> /// <param name="allRequiredConfiguration">The required configuration details.</param> /// <param name="openShift">The open shift entity to update.</param> /// <param name="mappedTeam">The team details.</param> /// <returns>A unit of execution.</returns> private async Task <HttpResponseMessage> UpdateOpenShiftInTeams(SetupDetails allRequiredConfiguration, GraphOpenShift openShift, TeamToDepartmentJobMappingEntity mappedTeam) { var httpClient = this.httpClientFactory.CreateClient("ShiftsAPI"); httpClient.DefaultRequestHeaders.Add("X-MS-WFMPassthrough", allRequiredConfiguration.WFIId); var requestString = JsonConvert.SerializeObject(openShift); var requestUrl = $"teams/{mappedTeam.TeamId}/schedule/openShifts/{openShift.Id}"; var response = await this.graphUtility.SendHttpRequest(allRequiredConfiguration.GraphConfigurationDetails, httpClient, HttpMethod.Put, requestUrl, requestString).ConfigureAwait(false); if (response.IsSuccessStatusCode) { var successfulUpdateProps = new Dictionary <string, string>() { { "ResponseCode", response.StatusCode.ToString() }, { "ResponseHeader", response.Headers.ToString() }, { "MappedTeamId", mappedTeam?.TeamId }, { "OpenShiftIdToDelete", openShift.Id }, }; this.telemetryClient.TrackTrace("Open shift updated.", successfulUpdateProps); } else { var errorUpdateProps = new Dictionary <string, string>() { { "ResponseCode", response.StatusCode.ToString() }, { "ResponseHeader", response.Headers.ToString() }, { "MappedTeamId", mappedTeam?.TeamId }, { "OpenShiftIdToDelete", openShift.Id }, }; this.telemetryClient.TrackTrace("Open shift could not be updated.", errorUpdateProps); } return(response); }
/// <summary> /// Method that creates the Open Shift Entity Mapping, posts to Graph, and saves the data in Azure /// table storage upon successful creation in Graph. /// </summary> /// <param name="allRequiredConfiguration">The required configuration details.</param> /// <param name="openShiftNotFound">The open shift to post to Graph.</param> /// <param name="monthPartitionKey">The monthwise partition key.</param> /// <param name="mappedTeam">The mapped team.</param> /// <returns>A unit of execution.</returns> private async Task CreateEntryOpenShiftsEntityMappingAsync( SetupDetails allRequiredConfiguration, List <OpenShiftRequestModel> openShiftNotFound, List <AllOpenShiftMappingEntity> lookUpEntriesFoundList, string monthPartitionKey, TeamToDepartmentJobMappingEntity mappedTeam) { this.telemetryClient.TrackTrace($"CreateEntryOpenShiftsEntityMappingAsync start at: {DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture)}"); // This foreach loop iterates over the OpenShifts which are to be added into Shifts UI. foreach (var item in openShiftNotFound) { this.telemetryClient.TrackTrace($"Processing the open shift entity with schedulingGroupId: {item.SchedulingGroupId}"); // create entries from not found list var telemetryProps = new Dictionary <string, string>() { { "SchedulingGroupId", item.SchedulingGroupId }, }; var httpClient = this.httpClientFactory.CreateClient("ShiftsAPI"); httpClient.DefaultRequestHeaders.Add("X-MS-WFMPassthrough", allRequiredConfiguration.WFIId); var requestString = JsonConvert.SerializeObject(item); var requestUrl = $"teams/{mappedTeam.TeamId}/schedule/openShifts"; var response = await this.graphUtility.SendHttpRequest(allRequiredConfiguration.GraphConfigurationDetails, httpClient, HttpMethod.Post, requestUrl, requestString).ConfigureAwait(false); if (response.IsSuccessStatusCode) { var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var openShiftResponse = JsonConvert.DeserializeObject <Models.Response.OpenShifts.GraphOpenShift>(responseContent); var openShiftMappingEntity = this.CreateNewOpenShiftMappingEntity(openShiftResponse, item.KronosUniqueId, monthPartitionKey, mappedTeam?.RowKey); telemetryProps.Add("ResultCode", response.StatusCode.ToString()); telemetryProps.Add("TeamsOpenShiftId", openShiftResponse.Id); this.telemetryClient.TrackTrace(Resource.CreateEntryOpenShiftsEntityMappingAsync, telemetryProps); await this.openShiftMappingEntityProvider.SaveOrUpdateOpenShiftMappingEntityAsync(openShiftMappingEntity).ConfigureAwait(false); // Add the entity to the found list to prevent later processes from deleting // the newly added entity. lookUpEntriesFoundList.Add(openShiftMappingEntity); } else { var errorProps = new Dictionary <string, string>() { { "ResultCode", response.StatusCode.ToString() }, { "ResponseHeader", response.Headers.ToString() }, { "SchedulingGroupId", item.SchedulingGroupId }, { "MappedTeamId", mappedTeam?.TeamId }, }; // Have the log to capture the error. this.telemetryClient.TrackTrace(Resource.CreateEntryOpenShiftsEntityMappingAsync, errorProps); } } this.telemetryClient.TrackTrace($"CreateEntryOpenShiftsEntityMappingAsync end at: {DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture)}"); }
/// <summary> /// This method finds any idnetical open shifts before performing logic specific to OS /// that occur more than once enabling open shift slot count support. /// </summary> /// <param name="allRequiredConfigurations">The required configuration details.</param> /// <param name="monthPartitionKey">The month partition key currently being synced.</param> /// <param name="openShiftsFoundList">The list of Teams open shift entities we found in cache.</param> /// <param name="lookUpEntriesFoundList">The list of mapping entities retrieved from Kronos and found in cache.</param> /// <param name="mappedOrgJobEntity">The team deatils.</param> /// <param name="lookUpData">All of the cache records retrieved for the query date span.</param> /// <returns>A unit of execution.</returns> private async Task ProcessIdenticalOpenShifts( SetupDetails allRequiredConfigurations, string monthPartitionKey, List <OpenShiftRequestModel> openShiftsFoundList, List <AllOpenShiftMappingEntity> lookUpEntriesFoundList, TeamToDepartmentJobMappingEntity mappedOrgJobEntity, List <AllOpenShiftMappingEntity> lookUpData) { var identicalOpenShifts = new List <AllOpenShiftMappingEntity>(); // Count up each individual open shift stored in our cache. var map = new Dictionary <string, int>(); foreach (var openShiftMapping in lookUpData) { if (map.ContainsKey(openShiftMapping.KronosOpenShiftUniqueId)) { map[openShiftMapping.KronosOpenShiftUniqueId] += openShiftMapping.KronosSlots; } else { map.Add(openShiftMapping.KronosOpenShiftUniqueId, openShiftMapping.KronosSlots); } } foreach (var item in map) { if (item.Value > 1) { // Where there are more than one identical entity in cache so we require seperate processing. identicalOpenShifts.AddRange(lookUpData.Where(x => x.KronosOpenShiftUniqueId == item.Key)); } } // Get each unique hash from the list of open shifts that occur more than once in cache. var identicalOpenShiftHash = identicalOpenShifts.Select(x => x.KronosOpenShiftUniqueId).Distinct(); foreach (var openShiftHash in identicalOpenShiftHash) { // Retrieve the open shifts to process from cache as well as what we have retrieved from Kronos using the hash. var openShiftsInCacheToProcess = identicalOpenShifts.Where(x => x.KronosOpenShiftUniqueId == openShiftHash).ToList(); var kronosOpenShiftsToProcess = openShiftsFoundList.Where(x => x.KronosUniqueId == openShiftHash).ToList(); // Calculate the difference in number of open shifts between Kronos and cache var cacheOpenShiftSlotCount = 0; openShiftsInCacheToProcess.ForEach(x => cacheOpenShiftSlotCount += x.KronosSlots); var numberOfOpenShiftsToRemove = cacheOpenShiftSlotCount - kronosOpenShiftsToProcess.Count; if (numberOfOpenShiftsToRemove > 0) { // More open shifts in cache than found in Kronos await this.RemoveAdditionalOpenShiftsFromTeamsAsync(allRequiredConfigurations, mappedOrgJobEntity, openShiftsInCacheToProcess, numberOfOpenShiftsToRemove).ConfigureAwait(false); continue; } if (numberOfOpenShiftsToRemove < 0) { // Less open shifts in cache than found in Kronos var openShiftsToAdd = new List <OpenShiftRequestModel>(); for (int i = numberOfOpenShiftsToRemove; i < 0; i++) { openShiftsToAdd.Add(kronosOpenShiftsToProcess.First()); } await this.CreateEntryOpenShiftsEntityMappingAsync(allRequiredConfigurations, openShiftsToAdd, lookUpEntriesFoundList, monthPartitionKey, mappedOrgJobEntity).ConfigureAwait(false); continue; } // The number of open shifts in Kronos and Teams matches meaning no action is needed continue; } }