public virtual async Task <IActionResult> RetrySubscriptionActionAsync(Guid id, long timestamp)
        {
            DateTime ts = DateTimeOffset.FromUnixTimeSeconds(timestamp).UtcDateTime;

            Data.Models.Subscription subscription = await _context.Subscriptions.Where(sub => sub.Id == id)
                                                    .FirstOrDefaultAsync();

            if (subscription == null)
            {
                return(NotFound());
            }

            SubscriptionUpdateHistoryEntry update = await _context.SubscriptionUpdateHistory
                                                    .Where(u => u.SubscriptionId == id)
                                                    .FirstOrDefaultAsync(u => Math.Abs(EF.Functions.DateDiffSecond(u.Timestamp, ts)) < 1);

            if (update == null)
            {
                return(NotFound());
            }

            if (update.Success)
            {
                return(StatusCode(
                           (int)HttpStatusCode.NotAcceptable,
                           new ApiError("That action was successful, it cannot be retried.")));
            }

            _queue.Post <SubscriptionActorActionWorkItem>(
                SubscriptionActorActionWorkItem.GetArguments(subscription.Id, update.Method, update.Arguments)
                );

            return(Accepted());
        }
예제 #2
0
        protected async Task <IActionResult> TriggerSubscriptionCore(Guid id, int buildId)
        {
            Data.Models.Subscription subscription = await _context.Subscriptions.Include(sub => sub.LastAppliedBuild)
                                                    .Include(sub => sub.Channel)
                                                    .FirstOrDefaultAsync(sub => sub.Id == id);

            if (buildId != 0)
            {
                Data.Models.Build build = await _context.Builds.Where(b => b.Id == buildId).FirstOrDefaultAsync();

                // Non-existent build
                if (build == null)
                {
                    return(BadRequest($"Build {buildId} was not found"));
                }
                // Build doesn't match source repo
                if (!(build.GitHubRepository?.Equals(subscription.SourceRepository, StringComparison.InvariantCultureIgnoreCase) == true ||
                      build.AzureDevOpsRepository?.Equals(subscription.SourceRepository, StringComparison.InvariantCultureIgnoreCase) == true))
                {
                    return(BadRequest($"Build {buildId} does not match source repo"));
                }
            }

            if (subscription == null)
            {
                return(NotFound());
            }

            var values = new { SubId = id, BuildId = buildId };

            _queue.Post <StartSubscriptionUpdateWorkItem>(JToken.FromObject(values));
            return(Accepted(new Subscription(subscription)));
        }
        public async Task <IActionResult> Create([FromBody] SubscriptionData subscription)
        {
            Data.Models.Channel channel = await _context.Channels.Where(c => c.Name == subscription.ChannelName)
                                          .FirstOrDefaultAsync();

            if (channel == null)
            {
                return(BadRequest(
                           new ApiError(
                               "the request is invalid",
                               new[] { $"The channel '{subscription.ChannelName}' could not be found." })));
            }

            var subscriptionModel = new Data.Models.Subscription(subscription)
            {
                Channel = channel
            };
            await _context.Subscriptions.AddAsync(subscriptionModel);

            await _context.SaveChangesAsync();

            return(CreatedAtRoute(
                       new { action = "GetSubscription", id = subscriptionModel.Id },
                       new Subscription(subscriptionModel)));
        }
예제 #4
0
        public async Task Subscribe(SubscriptionRequest request)
        {
            Data.Models.Subscription subscription = await m_context.Subscriptions.SingleOrDefaultAsync(x => x.Theme == request.Theme);

            if (subscription == null)
            {
                subscription = new Data.Models.Subscription {
                    Theme = request.Theme, Creator = request.Creator
                };
            }

            if (subscription.Id == null || !(await m_context.UserSubscriptions.AnyAsync(x => x.UserId == request.UserId && x.SubscriptionId == subscription.Id)))
            {
                await m_context.UserSubscriptions.AddAsync(new UserSubscription
                {
                    UserId                   = request.UserId,
                    Subscription             = subscription,
                    EmailNotificationNeeded  = request.EmailNotificationNeeded,
                    ClientNotificationNeeded = request.ClientNotificationNeeded,
                    IsSubscribed             = true
                });

                await m_context.SaveChangesAsync();
            }
        }
        public override async Task <IActionResult> UpdateSubscription(Guid id, [FromBody] v2018_07_16.Models.SubscriptionUpdate update)
        {
            Data.Models.Subscription subscription = await _context.Subscriptions.Where(sub => sub.Id == id)
                                                    .FirstOrDefaultAsync();

            if (subscription == null)
            {
                return(NotFound());
            }

            var doUpdate = false;

            if (!string.IsNullOrEmpty(update.SourceRepository))
            {
                subscription.SourceRepository = update.SourceRepository;
                doUpdate = true;
            }

            if (update.Policy != null)
            {
                subscription.PolicyObject = update.Policy.ToDb();
                doUpdate = true;
            }

            if (!string.IsNullOrEmpty(update.ChannelName))
            {
                Data.Models.Channel channel = await _context.Channels.Where(c => c.Name == update.ChannelName)
                                              .FirstOrDefaultAsync();

                if (channel == null)
                {
                    return(BadRequest(
                               new ApiError(
                                   "The request is invalid",
                                   new[] { $"The channel '{update.ChannelName}' could not be found." })));
                }

                subscription.Channel = channel;
                doUpdate             = true;
            }

            if (update.Enabled.HasValue)
            {
                subscription.Enabled = update.Enabled.Value;
                doUpdate             = true;
            }

            if (doUpdate)
            {
                _context.Subscriptions.Update(subscription);
                await _context.SaveChangesAsync();
            }


            return(Ok(new Subscription(subscription)));
        }
        public override async Task <IActionResult> TriggerSubscription(Guid id)
        {
            Data.Models.Subscription subscription = await TriggerSubscriptionCore(id);

            if (subscription == null)
            {
                return(NotFound());
            }
            return(Accepted(new Subscription(subscription)));
        }
예제 #7
0
        public async Task Unsubscribe(UnsubscriptionRequest request)
        {
            Data.Models.Subscription subscription = await m_context.Subscriptions.AsNoTracking().SingleOrDefaultAsync(x => x.Theme == request.Theme);

            UserSubscription userSubscription = await m_context.UserSubscriptions
                                                .SingleOrDefaultAsync(x => x.UserId == request.UserId && x.SubscriptionId == subscription.Id);

            userSubscription.IsSubscribed = false;
            await m_context.SaveChangesAsync();
        }
예제 #8
0
        public async Task <IActionResult> GetSubscription(Guid id)
        {
            Data.Models.Subscription subscription = await _context.Subscriptions.Where(sub => sub.Id == id)
                                                    .FirstOrDefaultAsync();

            if (subscription == null)
            {
                return(NotFound());
            }

            return(Ok(new Subscription(subscription)));
        }
예제 #9
0
        public override async Task <IActionResult> GetSubscription(Guid id)
        {
            Data.Models.Subscription subscription = await _context.Subscriptions.Include(sub => sub.LastAppliedBuild)
                                                    .Include(sub => sub.Channel)
                                                    .FirstOrDefaultAsync(sub => sub.Id == id);

            if (subscription == null)
            {
                return(NotFound());
            }

            return(Ok(new Subscription(subscription)));
        }
        protected async Task <Data.Models.Subscription> TriggerSubscriptionCore(Guid id)
        {
            Data.Models.Subscription subscription = await _context.Subscriptions.Include(sub => sub.LastAppliedBuild)
                                                    .Include(sub => sub.Channel)
                                                    .FirstOrDefaultAsync(sub => sub.Id == id);

            if (subscription == null)
            {
                return(null);
            }

            _queue.Post <StartSubscriptionUpdateWorkItem>(JToken.FromObject(id));
            return(subscription);
        }
예제 #11
0
        public Subscription([NotNull] Data.Models.Subscription other)
        {
            if (other == null)
            {
                throw new ArgumentNullException(nameof(other));
            }

            Id               = other.Id;
            Channel          = other.Channel == null ? null : new Channel(other.Channel);
            SourceRepository = other.SourceRepository;
            TargetRepository = other.TargetRepository;
            TargetBranch     = other.TargetBranch;
            Policy           = new SubscriptionPolicy(other.PolicyObject);
        }
예제 #12
0
        public virtual async Task <IActionResult> GetSubscriptionHistory(Guid id)
        {
            Data.Models.Subscription subscription = await _context.Subscriptions.Where(sub => sub.Id == id)
                                                    .FirstOrDefaultAsync();

            if (subscription == null)
            {
                return(NotFound());
            }

            IOrderedQueryable <SubscriptionUpdateHistoryEntry> query = _context.SubscriptionUpdateHistory
                                                                       .Where(u => u.SubscriptionId == id)
                                                                       .OrderByDescending(u => u.Timestamp);

            return(Ok(query));
        }
예제 #13
0
        public Subscription([NotNull] Data.Models.Subscription other)
        {
            if (other == null)
            {
                throw new ArgumentNullException(nameof(other));
            }

            Id               = other.Id;
            Channel          = other.Channel == null ? null : new Channel(other.Channel);
            LastAppliedBuild = other.LastAppliedBuild == null ? null : new Build(other.LastAppliedBuild);
            SourceRepository = other.SourceRepository;
            TargetRepository = other.TargetRepository;
            TargetBranch     = other.TargetBranch;
            Enabled          = other.Enabled;
            Policy           = new v2018_07_16.Models.SubscriptionPolicy(other.PolicyObject);
        }
예제 #14
0
        public override async Task <IActionResult> TriggerSubscription(Guid id)
        {
            Data.Models.Subscription subscription = await _context.Subscriptions.Include(sub => sub.LastAppliedBuild)
                                                    .Include(sub => sub.Channel)
                                                    .FirstOrDefaultAsync(sub => sub.Id == id);

            if (subscription == null)
            {
                return(NotFound());
            }

            _queue.Post(
                async() =>
            {
                await _dependencyUpdater.StartSubscriptionUpdateAsync(id);
            });

            return(Accepted(new Subscription(subscription)));
        }
예제 #15
0
        public async Task NotifySubsribers(string theme, string text, Logic.Models.Enums.Severity severity)
        {
            Data.Models.Subscription subscription = await m_context.Subscriptions
                                                    .AsNoTracking()
                                                    .Include(x => x.UserSubscriptions.Where(u => u.IsSubscribed == true))
                                                    .SingleOrDefaultAsync(x => x.Theme == theme);

            if (subscription == null)
            {
                throw new System.Exception();
            }

            NotificationMessage message = new NotificationMessage
            {
                Title    = theme,
                Text     = text,
                Severity = severity
            };

            UserNotificationBulkRequest notificationRequest = new UserNotificationBulkRequest
            {
                UserIds = subscription.UserSubscriptions.Where(x => x.ClientNotificationNeeded == true).Select(x => x.UserId),
                Message = message
            };

            UserNotificationBulkRequest emailRequest = new UserNotificationBulkRequest
            {
                UserIds = subscription.UserSubscriptions.Where(x => x.EmailNotificationNeeded == true).Select(x => x.UserId),
                Message = message
            };

            if (notificationRequest.UserIds.Count() != 0)
            {
                await m_notificationService.AddUserNotifications(notificationRequest);
            }
            if (emailRequest.UserIds.Count() != 0)
            {
                await m_emailNotificationService.SendEmailNotifications(emailRequest);
            }
        }
        public override async Task <IActionResult> DeleteSubscription(Guid id)
        {
            Data.Models.Subscription subscription =
                await _context.Subscriptions.FirstOrDefaultAsync(sub => sub.Id == id);

            if (subscription == null)
            {
                return(NotFound());
            }

            Data.Models.SubscriptionUpdate subscriptionUpdate =
                await _context.SubscriptionUpdates.FirstOrDefaultAsync(u => u.SubscriptionId == subscription.Id);

            if (subscriptionUpdate != null)
            {
                _context.SubscriptionUpdates.Remove(subscriptionUpdate);
            }

            _context.Subscriptions.Remove(subscription);

            await _context.SaveChangesAsync();

            return(Ok(new Subscription(subscription)));
        }
        public async Task <IActionResult> UpdateSubscription(Guid id, [FromBody] SubscriptionUpdate update)
        {
            Data.Models.Subscription subscription = await _context.Subscriptions.Where(sub => sub.Id == id).Include(sub => sub.Channel)
                                                    .FirstOrDefaultAsync();

            if (subscription == null)
            {
                return(NotFound());
            }

            var doUpdate = false;

            if (!string.IsNullOrEmpty(update.SourceRepository))
            {
                subscription.SourceRepository = update.SourceRepository;
                doUpdate = true;
            }

            if (update.Policy != null)
            {
                subscription.PolicyObject = update.Policy.ToDb();
                doUpdate = true;
            }

            if (update.PullRequestFailureNotificationTags != null)
            {
                if (!await AllNotificationTagsValid(update.PullRequestFailureNotificationTags))
                {
                    return(BadRequest(new ApiError("Invalid value(s) provided in Pull Request Failure Notification Tags; is everyone listed publicly a member of the Microsoft github org?")));
                }

                subscription.PullRequestFailureNotificationTags = update.PullRequestFailureNotificationTags;
                doUpdate = true;
            }

            if (!string.IsNullOrEmpty(update.ChannelName))
            {
                Data.Models.Channel channel = await _context.Channels.Where(c => c.Name == update.ChannelName)
                                              .FirstOrDefaultAsync();

                if (channel == null)
                {
                    return(BadRequest(
                               new ApiError(
                                   "The request is invalid",
                                   new[] { $"The channel '{update.ChannelName}' could not be found." })));
                }

                subscription.Channel = channel;
                doUpdate             = true;
            }

            if (update.Enabled.HasValue)
            {
                subscription.Enabled = update.Enabled.Value;
                doUpdate             = true;
            }

            if (doUpdate)
            {
                Data.Models.Subscription equivalentSubscription = await FindEquivalentSubscription(subscription);

                if (equivalentSubscription != null)
                {
                    return(Conflict(
                               new ApiError(
                                   "the request is invalid",
                                   new[]
                    {
                        $"The subscription '{equivalentSubscription.Id}' already performs the same update."
                    })));
                }

                _context.Subscriptions.Update(subscription);
                await _context.SaveChangesAsync();
            }


            return(Ok(new Subscription(subscription)));
        }
        public async Task <IActionResult> Create([FromBody, Required] BuildData build)
        {
            Data.Models.Build buildModel = build.ToDb();
            buildModel.DateProduced = DateTimeOffset.UtcNow;

            if (build.Dependencies != null)
            {
                // For each Dependency, update the time to Inclusion.
                // This measure is to be used for telemetry purposes, and has several known corner cases
                // where the measurement will not be correct:
                // 1. For any dependencies that were added before this column was added, the TimeToInclusionInMinutes
                //    will be 0.
                // 2. For new release branches, until new builds of dependencies are added, this will recalculate
                //    the TimeToInclusion, so it will seem inordinately large until new builds are added. This will
                //    be particularly true for dependencies that are infrequently updated.
                foreach (var dep in build.Dependencies)
                {
                    // Heuristic to discover if this dependency has been added to the same repository and branch
                    // of the current build. If we find a match in the BuildDependencies table, it means
                    // that this is not a new dependency, and we should use the TimeToInclusionInMinutes
                    // of the previous time this dependency was added.
                    var buildDependency = _context.BuildDependencies.Where(d =>
                                                                           d.DependentBuildId == dep.BuildId &&
                                                                           d.Build.GitHubRepository == buildModel.GitHubRepository &&
                                                                           d.Build.GitHubBranch == buildModel.GitHubBranch &&
                                                                           d.Build.AzureDevOpsRepository == buildModel.AzureDevOpsRepository &&
                                                                           d.Build.AzureDevOpsBranch == buildModel.AzureDevOpsBranch
                                                                           ).FirstOrDefault();

                    if (buildDependency != null)
                    {
                        dep.TimeToInclusionInMinutes = buildDependency.TimeToInclusionInMinutes;
                    }
                    else
                    {
                        // If the dependent build is not currently in the BuildDependency table for this repo/branch (ie is a new dependency),
                        // find the dependency in the Builds table and calculate the time to inclusion

                        // We want to use the BuildChannel insert time if it exists. So we need to heuristically:
                        // 1. Find the subscription between these two repositories on the current branch
                        // 2. Find the entry in BuildChannels and get the insert time
                        // In certain corner cases, we may pick the wrong subscription or BuildChannel

                        Data.Models.Build depBuild = await _context.Builds.FindAsync(dep.BuildId);

                        // If we don't find a subscription or a BuildChannel entry, use the dependency's
                        // date produced.
                        DateTimeOffset startTime = depBuild.DateProduced;

                        Data.Models.Subscription subscription = _context.Subscriptions.Where(s =>
                                                                                             (s.SourceRepository == depBuild.GitHubRepository || s.SourceRepository == depBuild.AzureDevOpsRepository) &&
                                                                                             (s.TargetRepository == buildModel.GitHubRepository || s.TargetRepository == buildModel.AzureDevOpsRepository) &&
                                                                                             (s.TargetBranch == buildModel.GitHubBranch || s.TargetBranch == buildModel.AzureDevOpsBranch)
                                                                                             ).LastOrDefault();


                        if (subscription != null)
                        {
                            Data.Models.BuildChannel buildChannel = _context.BuildChannels.Where(bc =>
                                                                                                 bc.BuildId == depBuild.Id &&
                                                                                                 bc.ChannelId == subscription.ChannelId
                                                                                                 ).LastOrDefault();

                            if (buildChannel != null)
                            {
                                startTime = buildChannel.DateTimeAdded;
                            }
                        }

                        dep.TimeToInclusionInMinutes = (buildModel.DateProduced - startTime).TotalMinutes;
                    }
                }

                await _context.BuildDependencies.AddRangeAsync(
                    build.Dependencies.Select(
                        b => new Data.Models.BuildDependency
                {
                    Build = buildModel, DependentBuildId = b.BuildId, IsProduct = b.IsProduct, TimeToInclusionInMinutes = b.TimeToInclusionInMinutes,
                }));
            }

            await _context.Builds.AddAsync(buildModel);

            await _context.SaveChangesAsync();

            // Compute the dependency incoherencies of the build.
            // Since this might be an expensive operation we do it asynchronously.
            Queue.Post(
                async() =>
            {
                await SetBuildIncoherencyInfoAsync(buildModel.Id);
            });

            return(CreatedAtRoute(
                       new
            {
                action = "GetBuild",
                id = buildModel.Id
            },
                       new Models.Build(buildModel)));
        }
예제 #19
0
        private async Task SaveCandidates(SophiaDbContext dbContext, IEnumerable <Recommending.Candidate> candidates, Octokit.PullRequest pullRequest, Data.Models.Subscription subscription)
        {
            var dateTime = DateTimeOffset.UtcNow;

            dbContext.AddRange(candidates.Select(q => new Data.Models.Candidate()
            {
                GitHubLogin       = q.Contributor.GitHubLogin,
                PullRequestNumber = pullRequest.Number,
                Rank               = q.Rank,
                RecommenderType    = RecommenderType.Chrev,
                SubscriptionId     = subscription.Id,
                SuggestionDateTime = dateTime
            }));

            await dbContext.SaveChangesAsync();
        }
예제 #20
0
        private async Task GetCandidates(EventContext eventContext, SophiaDbContext dbContext, int issueNumber, long repositoryId, Data.Models.Subscription subscription, int topCandidatesLength)
        {
            var installationClient = eventContext.InstallationContext.Client;
            var recommender        = new CodeReviewerRecommender(RecommenderType.Chrev, dbContext);

            var pullRequest = await installationClient.PullRequest.Get(repositoryId, issueNumber);

            var pullRequestFiles = await installationClient.PullRequest.Files(repositoryId, issueNumber);

            var candidates = await recommender.Recommend(subscription.Id, pullRequest, pullRequestFiles, topCandidatesLength);

            await SaveCandidates(dbContext, candidates, pullRequest, subscription);

            var message = GenerateMessage(candidates, pullRequestFiles);

            await installationClient.Issue.Comment.Create(repositoryId, issueNumber, message);
        }
        private async Task GetCandidates(EventContext eventContext, SophiaDbContext dbContext, int issueNumber, long repositoryId, Data.Models.Subscription subscription, int topCandidatesLength)
        {
            var installationClient = eventContext.InstallationContext.Client;
            var pullRequest        = await installationClient.PullRequest.Get(repositoryId, issueNumber);

            var pullRequestFiles = await installationClient.PullRequest.Files(repositoryId, issueNumber);

            var fileOwners = await GetFileOwners(dbContext, subscription.Id, pullRequestFiles);

            var expertCandidates = await GetExpertCandidates(dbContext, subscription.Id, pullRequest, pullRequestFiles, topCandidatesLength);

            var learnerCandidates = await GetLearnerCandidates(dbContext, subscription.Id, pullRequest, pullRequestFiles, topCandidatesLength);

            await SaveCandidates(dbContext, expertCandidates, learnerCandidates, pullRequest, subscription);

            var message = GenerateMessage(expertCandidates, learnerCandidates, pullRequestFiles, fileOwners);

            await installationClient.Issue.Comment.Create(repositoryId, issueNumber, message);
        }