public async Task <IActionResult> Run( [HttpTrigger(AuthorizationLevel.Admin, "post", Route = "clearschedule")] ClearScheduleModel clearScheduleModel, [DurableClient] IDurableOrchestrationClient starter, ILogger log) { if (!clearScheduleModel.IsValid()) { return(new BadRequestResult()); } // ensure that the team's orchestrators will not execute by disabling them await _scheduleConnectorService.UpdateEnabledAsync(clearScheduleModel.TeamId, false).ConfigureAwait(false); // get the connection model as we need the time zone information for the team var connectionModel = await _scheduleConnectorService.GetConnectionAsync(clearScheduleModel.TeamId).ConfigureAwait(false); try { SetStartAndEndDates(clearScheduleModel, connectionModel.TimeZoneInfoId); } catch (ArgumentException ex) { return(new BadRequestObjectResult(ex.Message)); } if (await starter.TryStartSingletonAsync(nameof(ClearScheduleOrchestrator), ClearScheduleOrchestrator.InstanceId(clearScheduleModel.TeamId), clearScheduleModel).ConfigureAwait(false)) { return(new OkResult()); } else { return(new ConflictResult()); } }
public async Task <IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "config/{teamId?}")] HttpRequest req, string teamId, ILogger log) { var configModel = new ConfigModel { AuthorizeUrl = _microsoftGraphOptions.AuthorizeUrl, ClientId = _microsoftGraphOptions.ClientId, Scope = _microsoftGraphOptions.Scope, JdaBaseAddress = _jdaPersonaOptions.JdaBaseAddress, ShiftsAppUrl = _microsoftGraphOptions.ShiftsAppUrl }; if (teamId != null) { try { var connectionModel = await _scheduleConnectorService.GetConnectionAsync(teamId); configModel.Connected = true; } catch (KeyNotFoundException) { configModel.Connected = false; } } return(new JsonResult(configModel)); }
public async Task <IActionResult> Run( [HttpTrigger(AuthorizationLevel.Admin, "post", Route = "unsubscribe/{teamId}")] HttpRequest req, [DurableClient] IDurableOrchestrationClient starter, string teamId, ILogger log) { try { var connection = await _scheduleConnectorService.GetConnectionAsync(teamId).ConfigureAwait(false); } catch (KeyNotFoundException) { return(new NotFoundResult()); } // ensure that in the very brief period of time before the connection is deleted that // the orchestrators are not started await _scheduleConnectorService.UpdateEnabledAsync(teamId, false).ConfigureAwait(false); // and that any running instances are terminated await StopTrigger.StopRunningOrchestratorsAsync(teamId, starter).ConfigureAwait(false); // finally, delete the connection await _scheduleConnectorService.DeleteConnectionAsync(teamId).ConfigureAwait(false); log.LogUnsubscribeTeam(teamId); return(new OkResult()); }
public async Task <IActionResult> Run( [HttpTrigger(AuthorizationLevel.Admin, "post", Route = "unsubscribe/{teamId}")] HttpRequest req, [OrchestrationClient] DurableOrchestrationClient starter, string teamId, ILogger log) { try { var connection = await _scheduleConnectorService.GetConnectionAsync(teamId); } catch (KeyNotFoundException) { return(new NotFoundResult()); } var tasks = new Task[] { starter.TerminateAsync(teamId, nameof(UnsubscribeTrigger)), _secretsService.DeleteCredentialsAsync(teamId), _secretsService.DeleteTokenAsync(teamId), _scheduleConnectorService.DeleteConnectionAsync(teamId) }; await Task.WhenAll(tasks); return(new OkResult()); }
public async Task <IActionResult> Run( [HttpTrigger(AuthorizationLevel.Admin, "post", Route = "start/{teamId}")] HttpRequest req, [OrchestrationClient] DurableOrchestrationClient starter, string teamId, ILogger log) { var connectionModel = await _scheduleConnectorService.GetConnectionAsync(teamId); var teamModel = TeamModel.FromConnection(connectionModel); if (await starter.TryStartSingletonAsync(nameof(TeamOrchestrator), teamModel.TeamId, teamModel)) { return(new OkResult()); } else { return(new ConflictResult()); } }
public static async Task <string> GetAndUpdateTimeZoneAsync(string teamId, ITimeZoneService timeZoneService, IScheduleConnectorService scheduleConnectorService, IScheduleSourceService scheduleSourceService) { var connection = await scheduleConnectorService.GetConnectionAsync(teamId).ConfigureAwait(false); if (connection == null) { // the team must have been unsubscribed throw new ArgumentException("This team cannot be found in the teams table.", teamId); } if (!string.IsNullOrEmpty(connection.TimeZoneInfoId)) { return(connection.TimeZoneInfoId); } else { // we don't have a timezone for this existing connection, so try to get one var store = await scheduleSourceService.GetStoreAsync(connection.TeamId, connection.StoreId).ConfigureAwait(false); if (store?.TimeZoneId != null) { var jdaTimeZoneName = await scheduleSourceService.GetJdaTimeZoneNameAsync(teamId, store.TimeZoneId.Value).ConfigureAwait(false); var timeZoneInfoId = await timeZoneService.GetTimeZoneInfoIdAsync(jdaTimeZoneName).ConfigureAwait(false); if (timeZoneInfoId != null) { connection.TimeZoneInfoId = timeZoneInfoId; await scheduleConnectorService.SaveConnectionAsync(connection).ConfigureAwait(false); return(timeZoneInfoId); } } } return(null); }
public async Task <IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "subscribe")] SubscribeModel subscribeModel, [OrchestrationClient] DurableOrchestrationClient starter, ILogger log) { // validate model if (!subscribeModel.IsValid()) { log.LogError("Validating model failed."); return(new BadRequestResult()); } // validate with JDA var credentials = subscribeModel.AsCredentialsModel(); _scheduleSourceService.SetCredentials(subscribeModel.TeamId, credentials); StoreModel store; try { store = await _scheduleSourceService.GetStoreAsync(subscribeModel.TeamId, subscribeModel.StoreId).ConfigureAwait(false); } catch (ArgumentException e) { log.LogError(e, "Subscribe failed - JDA store id incorrect."); return(new BadRequestResult()); } catch (KeyNotFoundException e) { log.LogError(e, "Subscribe failed - JDA store not found."); return(new NotFoundResult()); } catch (UnauthorizedAccessException e) { log.LogError(e, "Subscribe failed - Invalid url or credentials."); return(new UnauthorizedResult()); } // ensure that we can map the timezone for the store var timeZoneInfoId = await TimeZoneHelper.GetTimeZoneAsync(subscribeModel.TeamId, store.TimeZoneId, _timeZoneService, _scheduleSourceService, _scheduleConnectorService, log).ConfigureAwait(false); if (timeZoneInfoId == null) { log.LogError($"Subscribe failed - No time zone mapping found for store TimeZoneId={store.TimeZoneId}."); return(new InternalServerErrorResult()); } // exchange and save access token if (!string.IsNullOrEmpty(subscribeModel.AuthorizationCode)) { var tokenResponse = await _httpClientFactory.Client.RequestTokenAsync(_options, subscribeModel.RedirectUri, subscribeModel.AuthorizationCode).ConfigureAwait(false); if (tokenResponse.IsError) { log.LogError("Subscribe failed - Invalid authorization code."); return(new ForbidResult()); } var tokenModel = tokenResponse.AsTokenModel(); await _secretsService.SaveTokenAsync(subscribeModel.TeamId, tokenModel).ConfigureAwait(false); } else if (!string.IsNullOrEmpty(subscribeModel.AccessToken)) { var tokenModel = subscribeModel.AsTokenModel(); await _secretsService.SaveTokenAsync(subscribeModel.TeamId, tokenModel).ConfigureAwait(false); } // save JDA creds await _secretsService.SaveCredentialsAsync(subscribeModel.TeamId, credentials).ConfigureAwait(false); // get the team from Teams GroupModel team; try { team = await _scheduleDestinationService.GetTeamAsync(subscribeModel.TeamId).ConfigureAwait(false); } catch (Exception e) { log.LogError(e, "Subscribe failed - Not authorized to access details for the team."); return(new ForbidResult()); } var teamModel = subscribeModel.AsTeamModel(); teamModel.TimeZoneInfoId = timeZoneInfoId; var connectionModel = subscribeModel.AsConnectionModel(); connectionModel.TimeZoneInfoId = timeZoneInfoId; connectionModel.StoreName = store.StoreName; connectionModel.TeamName = team.Name; try { // ensure that if the team is re-subscribing, that they haven't changed the store // that they are connecting to var existingModel = await _scheduleConnectorService.GetConnectionAsync(subscribeModel.TeamId).ConfigureAwait(false); if (connectionModel.StoreId != existingModel.StoreId) { log.LogError("Re-subscribe failed - JDA store id changed."); return(new BadRequestResult()); } else { // as the team is re-subscribing, ensure that the schedule is not re-initialized teamModel.Initialized = true; } } catch { /* nothing to do - new subscription */ } // save connection settings await _scheduleConnectorService.SaveConnectionAsync(connectionModel).ConfigureAwait(false); // start singleton team orchestrator await starter.TryStartSingletonAsync(nameof(TeamOrchestrator), teamModel.TeamId, teamModel).ConfigureAwait(false); return(new OkObjectResult(new StoreModel { StoreId = connectionModel.StoreId, StoreName = connectionModel.StoreName })); }
public async Task <IActionResult> Run( [HttpTrigger(AuthorizationLevel.Function, "get", Route = "teamHealth/{teamId}")] HttpRequest req, [DurableClient] IDurableOrchestrationClient starter, string teamId, ILogger log) { try { var connection = await _scheduleConnectorService.GetConnectionAsync(teamId).ConfigureAwait(false); var date = DateTime.Today; if (req.Query.ContainsKey("date")) { DateTime.TryParse(req.Query["date"], out date); } var weekStartDate = date.StartOfWeek(_options.StartDayOfWeek); var employees = await _wfmDataService.GetEmployeesAsync(connection.TeamId, connection.WfmBuId, weekStartDate).ConfigureAwait(false); await UpdateEmployeesWithTeamsData(connection.TeamId, employees).ConfigureAwait(false); var cachedShifts = await GetCachedShiftsAsync(connection, weekStartDate).ConfigureAwait(false); var jobIds = cachedShifts.SelectMany(s => s.Jobs).Select(j => j.WfmJobId).Distinct().ToList(); var jobs = await GetJobsAsync(connection, jobIds).ConfigureAwait(false); ExpandIds(cachedShifts, employees, jobs); var missingUsers = employees.Where(e => string.IsNullOrEmpty(e.TeamsEmployeeId)).ToList(); var missingShifts = await GetMissingShiftsAsync(connection, weekStartDate, cachedShifts).ConfigureAwait(false); jobIds = missingShifts.SelectMany(s => s.Jobs).Select(j => j.WfmJobId).Distinct().ToList(); jobs = await GetJobsAsync(connection, jobIds).ConfigureAwait(false); ExpandIds(missingShifts, employees, jobs); var mappedUsers = await GetMappedUsersAsync(connection.TeamId).ConfigureAwait(false); var teamHealthResponseModel = new TeamHealthResponseModel { TeamId = connection.TeamId, WeekStartDate = weekStartDate.AsDateString(), EmployeeCacheOrchestratorStatus = await starter.GetStatusAsync(string.Format(EmployeeCacheOrchestrator.InstanceIdPattern, connection.TeamId)).ConfigureAwait(false), EmployeeTokenRefreshOrchestratorStatus = await starter.GetStatusAsync(string.Format(EmployeeTokenRefreshOrchestrator.InstanceIdPattern, connection.TeamId)).ConfigureAwait(false), TeamOrchestratorStatus = await starter.GetStatusAsync(string.Format(ShiftsOrchestrator.InstanceIdPattern, connection.TeamId)).ConfigureAwait(false), MappedUsers = mappedUsers, MissingUsers = missingUsers, MissingShifts = missingShifts, CachedShifts = cachedShifts }; if (_featureOptions.EnableOpenShiftSync) { teamHealthResponseModel.OpenShiftsOrchestratorStatus = await starter.GetStatusAsync(string.Format(OpenShiftsOrchestrator.InstanceIdPattern, connection.TeamId)).ConfigureAwait(false); } if (_featureOptions.EnableTimeOffSync) { teamHealthResponseModel.TimeOffOrchestratorStatus = await starter.GetStatusAsync(string.Format(TimeOffOrchestrator.InstanceIdPattern, connection.TeamId)).ConfigureAwait(false); } if (_featureOptions.EnableAvailabilitySync) { teamHealthResponseModel.AvailabilityOrchestratorStatus = await starter.GetStatusAsync(string.Format(AvailabilityOrchestrator.InstanceIdPattern, connection.TeamId)).ConfigureAwait(false); } // N.B. the following block returns the JSON in a ContentResult rather than in the // rather more concise JsonResult because to return the Json with the settings // required adding a package dependency to Microsoft.AspNetCore.Mvc.NewtonsoftJson // as per https://github.com/Azure/azure-functions-core-tools/issues/1907 which then // caused an issue with incompatible dependencies and a significant issue with // deserializing json in HttpRequestExtensions var json = JsonConvert.SerializeObject(teamHealthResponseModel, Formatting.Indented); return(new ContentResult { Content = json, ContentType = "application/json", StatusCode = (int)HttpStatusCode.OK }); } catch (Exception ex) { log.LogError(ex, "Team health failed!"); return(new ContentResult { Content = $"Unexpected exception: {ex.Message}", StatusCode = StatusCodes.Status500InternalServerError }); } }
public async Task <IActionResult> Run( [HttpTrigger(AuthorizationLevel.Admin, "get", Route = "teamHealth/{teamId}")] HttpRequest req, [OrchestrationClient] DurableOrchestrationClient starter, string teamId, ILogger log) { try { var connection = await _scheduleConnectorService.GetConnectionAsync(teamId); var credentials = await _secretsService.GetCredentialsAsync(connection.TeamId); _scheduleSourceService.SetCredentials(connection.TeamId, credentials); var date = DateTime.Today; if (req.Query.ContainsKey("date")) { DateTime.TryParse(req.Query["date"], out date); } var weekStartDate = date.StartOfWeek(_options.StartDayOfWeek); var jdaEmployees = await _scheduleSourceService.GetEmployeesAsync(connection.TeamId, connection.StoreId, weekStartDate); await UpdateEmployeesWithTeamsData(connection.TeamId, jdaEmployees); var cachedShifts = await GetCachedShiftsAsync(connection, weekStartDate); var jobIds = cachedShifts.SelectMany(s => s.Jobs).Select(j => j.JdaJobId).Distinct().ToList(); var jobs = await GetJobsAsync(connection, jobIds); ExpandIds(cachedShifts, jdaEmployees, jobs); var missingUsers = jdaEmployees.Where(e => string.IsNullOrEmpty(e.DestinationId)).ToList(); var missingShifts = await GetMissingShifts(connection, weekStartDate, cachedShifts); jobIds = missingShifts.SelectMany(s => s.Jobs).Select(j => j.JdaJobId).Distinct().ToList(); jobs = await GetJobsAsync(connection, jobIds); ExpandIds(missingShifts, missingUsers, jobs); var teamHealthResponseModel = new TeamHealthResponseModel { TeamId = connection.TeamId, WeekStartDate = weekStartDate, TeamOrchestratorStatus = await starter.GetStatusAsync(connection.TeamId), MissingUsers = missingUsers, MissingShifts = missingShifts, CachedShifts = cachedShifts }; return(new JsonResult(teamHealthResponseModel, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })); } catch (Exception ex) { log.LogError(ex, "Team health failed!"); return(new ContentResult { Content = $"Unexpected exception: {ex.Message}", StatusCode = StatusCodes.Status500InternalServerError }); } }
public async Task <IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "subscribe")] SubscribeModel subscribeModel, [DurableClient] IDurableOrchestrationClient starter, ILogger log) { // validate model if (!subscribeModel.IsValid()) { log.LogError("Validating model failed."); return(new BadRequestResult()); } // validate with WFM Provider BusinessUnitModel businessUnit; try { businessUnit = await _wfmDataService.GetBusinessUnitAsync(subscribeModel.WfmBuId, log).ConfigureAwait(false); } catch (ArgumentException e) { log.LogError(e, "Subscribe failed - business unit id invalid."); return(new BadRequestResult()); } catch (KeyNotFoundException e) { log.LogError(e, "Subscribe failed - business unit not found."); return(new NotFoundResult()); } catch (UnauthorizedAccessException e) { log.LogError(e, "Subscribe failed - invalid credentials."); return(new UnauthorizedResult()); } // get the team from Teams GroupModel team; try { team = await _teamsService.GetTeamAsync(subscribeModel.TeamId).ConfigureAwait(false); } catch (Exception e) { log.LogError(e, "Subscribe failed - Not authorized to access details for the team."); return(new ForbidResult()); } var teamModel = subscribeModel.AsTeamModel(); teamModel.TimeZoneInfoId = businessUnit.TimeZoneInfoId; var connectionModel = subscribeModel.AsConnectionModel(); connectionModel.TimeZoneInfoId = businessUnit.TimeZoneInfoId; connectionModel.WfmBuName = businessUnit.WfmBuName; connectionModel.TeamName = team.Name; connectionModel.Enabled = true; try { // ensure that if the team is re-subscribing, that they haven't changed the business // unit that they are connecting to var existingModel = await _scheduleConnectorService.GetConnectionAsync(subscribeModel.TeamId).ConfigureAwait(false); if (connectionModel.WfmBuId != existingModel.WfmBuId) { log.LogError("Re-subscribe failed - WFM business unit id changed."); return(new BadRequestResult()); } } catch { // as this is a new subscription, we need to initialize the team with a new schedule await starter.StartNewAsync(nameof(InitializeOrchestrator), teamModel).ConfigureAwait(false); // and delay the first execution of the sync orchestrators by at least 5 minutes DelayOrchestratorsFirstExecution(connectionModel, 5); } // save connection settings await _scheduleConnectorService.SaveConnectionAsync(connectionModel).ConfigureAwait(false); log.LogSubscribeTeam(connectionModel); return(new OkObjectResult(new BusinessUnitModel { WfmBuId = connectionModel.WfmBuId, WfmBuName = connectionModel.WfmBuName })); }