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());
            }
        }
Exemplo n.º 2
0
        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());
        }
Exemplo n.º 4
0
        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());
        }
Exemplo n.º 5
0
        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());
            }
        }
Exemplo n.º 6
0
        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);
        }
Exemplo n.º 7
0
        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
            }));
        }
Exemplo n.º 8
0
        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
                });
            }
        }
Exemplo n.º 9
0
        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
            }));
        }